1
1
import React , { useCallback , useEffect , useMemo , useState } from "react" ;
2
2
import { useQuery } from "@tanstack/react-query" ;
3
+ import axios from "axios" ;
3
4
import styled from "styled-components" ;
4
5
import { v4 as uuidv4 } from "uuid" ;
6
+ import { z } from "zod" ;
5
7
6
8
import Back from "components/porter/Back" ;
7
9
import Button from "components/porter/Button" ;
8
10
import Container from "components/porter/Container" ;
11
+ import { Error as ErrorComponent } from "components/porter/Error" ;
9
12
import Image from "components/porter/Image" ;
10
13
import Input from "components/porter/Input" ;
11
14
import Link from "components/porter/Link" ;
12
15
import Spacer from "components/porter/Spacer" ;
13
16
import Text from "components/porter/Text" ;
14
17
import VerticalSteps from "components/porter/VerticalSteps" ;
18
+ import { type ButtonStatus } from "main/home/app-dashboard/app-view/AppDataContainer" ;
15
19
import { CloudProviderAWS } from "lib/clusters/constants" ;
16
20
import { isAWSArnAccessible } from "lib/hooks/useCloudProvider" ;
17
21
import { useClusterAnalytics } from "lib/hooks/useClusterAnalytics" ;
22
+ import { useIntercom } from "lib/hooks/useIntercom" ;
18
23
19
24
import GrantAWSPermissionsHelpModal from "../../modals/help/permissions/GrantAWSPermissionsHelpModal" ;
20
25
import { CheckItem } from "../../modals/PreflightChecksModal" ;
@@ -38,9 +43,11 @@ const GrantAWSPermissions: React.FC<Props> = ({
38
43
const [ currentStep , setCurrentStep ] = useState < number > ( 0 ) ;
39
44
const [ showNeedHelpModal , setShowNeedHelpModal ] = useState ( false ) ;
40
45
const [ accountIdContinueButtonStatus , setAccountIdContinueButtonStatus ] =
41
- useState < string > ( "" ) ;
46
+ useState < ButtonStatus > ( "" ) ;
42
47
const [ isAccountAccessible , setIsAccountAccessible ] = useState ( false ) ;
43
48
const { reportToAnalytics } = useClusterAnalytics ( ) ;
49
+ const { showIntercomWithMessage } = useIntercom ( ) ;
50
+
44
51
const awsAccountIdInputError = useMemo ( ( ) => {
45
52
const regex = / ^ \d { 12 } $ / ;
46
53
if ( AWSAccountID . trim ( ) . length === 0 ) {
@@ -50,6 +57,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
50
57
}
51
58
return undefined ;
52
59
} , [ AWSAccountID ] ) ;
60
+
53
61
const externalId = useMemo ( ( ) => {
54
62
if ( ! AWSAccountID || awsAccountIdInputError ) {
55
63
return "" ;
@@ -62,6 +70,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
62
70
63
71
return externalId ;
64
72
} , [ AWSAccountID , awsAccountIdInputError ] ) ;
73
+
65
74
const data = useQuery (
66
75
[
67
76
"cloudFormationStackCreated" ,
@@ -71,11 +80,16 @@ const GrantAWSPermissions: React.FC<Props> = ({
71
80
externalId ,
72
81
] ,
73
82
async ( ) => {
74
- return await isAWSArnAccessible ( {
75
- targetArn : `arn:aws:iam::${ AWSAccountID } :role/porter-manager` ,
76
- externalId,
77
- projectId,
78
- } ) ;
83
+ try {
84
+ await isAWSArnAccessible ( {
85
+ targetArn : `arn:aws:iam::${ AWSAccountID } :role/porter-manager` ,
86
+ externalId,
87
+ projectId,
88
+ } ) ;
89
+ return true ;
90
+ } catch ( err ) {
91
+ return false ;
92
+ }
79
93
} ,
80
94
{
81
95
enabled : currentStep === 3 && ! isAccountAccessible , // no need to check if it's already accessible
@@ -88,30 +102,58 @@ const GrantAWSPermissions: React.FC<Props> = ({
88
102
setIsAccountAccessible ( data . data ) ;
89
103
}
90
104
} , [ data ] ) ;
105
+
91
106
const handleAWSAccountIDChange = ( accountId : string ) : void => {
92
107
setAWSAccountID ( accountId ) ;
93
108
setIsAccountAccessible ( false ) ; // any time they change the account ID, we need to re-check if it's accessible
94
109
} ;
110
+
95
111
const checkIfAlreadyAccessible = async ( ) : Promise < void > => {
96
112
setAccountIdContinueButtonStatus ( "loading" ) ;
97
- const isAlreadyAccessible = await isAWSArnAccessible ( {
98
- targetArn : `arn:aws:iam:: ${ AWSAccountID } :role/porter-manager` ,
99
- externalId ,
100
- projectId ,
101
- } ) ;
102
- if ( isAlreadyAccessible ) {
113
+ try {
114
+ await isAWSArnAccessible ( {
115
+ targetArn : `arn:aws:iam:: ${ AWSAccountID } :role/porter-manager` ,
116
+ externalId ,
117
+ projectId ,
118
+ } ) ;
103
119
setCurrentStep ( 3 ) ;
104
120
setIsAccountAccessible ( true ) ;
105
- } else {
106
- setCurrentStep ( 2 ) ;
121
+ } catch ( err ) {
122
+ let shouldProceed = true ;
123
+ if ( axios . isAxiosError ( err ) ) {
124
+ const parsed = z
125
+ . object ( { error : z . string ( ) } )
126
+ . safeParse ( err . response ?. data ) ;
127
+ if (
128
+ parsed . success &&
129
+ parsed . data . error . includes (
130
+ "user does not have access to all projects"
131
+ )
132
+ ) {
133
+ setAccountIdContinueButtonStatus (
134
+ < ErrorComponent
135
+ message = { "Unable to proceed. Please reach out to support." }
136
+ />
137
+ ) ;
138
+ showIntercomWithMessage ( {
139
+ message : "I need help granting AWS permissions." ,
140
+ } ) ;
141
+ shouldProceed = false ;
142
+ }
143
+ }
144
+ if ( shouldProceed ) {
145
+ setCurrentStep ( 2 ) ;
146
+ setAccountIdContinueButtonStatus ( "" ) ;
147
+ }
148
+ } finally {
149
+ void reportToAnalytics ( {
150
+ projectId,
151
+ step : "aws-account-id-complete" ,
152
+ awsAccountId : AWSAccountID ,
153
+ } ) ;
107
154
}
108
- void reportToAnalytics ( {
109
- projectId,
110
- step : "aws-account-id-complete" ,
111
- awsAccountId : AWSAccountID ,
112
- } ) ;
113
- setAccountIdContinueButtonStatus ( "" ) ;
114
155
} ;
156
+
115
157
const directToAWSLogin = ( ) : void => {
116
158
const loginUrl = `https://signin.aws.amazon.com/console` ;
117
159
void reportToAnalytics ( {
@@ -121,6 +163,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
121
163
} ) ;
122
164
window . open ( loginUrl , "_blank" ) ;
123
165
} ;
166
+
124
167
const directToCloudFormation = useCallback ( async ( ) => {
125
168
const trustArn = process . env . TRUST_ARN
126
169
? process . env . TRUST_ARN
@@ -136,6 +179,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
136
179
setCurrentStep ( 3 ) ;
137
180
window . open ( cloudFormationUrl , "_blank" ) ;
138
181
} , [ AWSAccountID , externalId ] ) ;
182
+
139
183
const handleGrantPermissionsComplete = ( ) : void => {
140
184
proceed ( {
141
185
cloudProviderCredentialIdentifier : `arn:aws:iam::${ AWSAccountID } :role/porter-manager` ,
@@ -220,6 +264,8 @@ const GrantAWSPermissions: React.FC<Props> = ({
220
264
< Button
221
265
onClick = { ( ) => {
222
266
setCurrentStep ( 0 ) ;
267
+ setAccountIdContinueButtonStatus ( "" ) ;
268
+ setAWSAccountID ( "" ) ;
223
269
} }
224
270
color = "#222222"
225
271
>
@@ -232,7 +278,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
232
278
disabled = {
233
279
awsAccountIdInputError != null ||
234
280
AWSAccountID . length === 0 ||
235
- accountIdContinueButtonStatus === "loading "
281
+ accountIdContinueButtonStatus !== " "
236
282
}
237
283
status = { accountIdContinueButtonStatus }
238
284
loadingText = { `Checking if Porter can already access this account` }
0 commit comments