@@ -23,102 +23,87 @@ import {
23
23
templateByName ,
24
24
templateVersionExternalAuth ,
25
25
} from "api/queries/templates" ;
26
- import { autoCreateWorkspace , createWorkspace } from "api/queries/workspaces" ;
26
+ import {
27
+ AutoCreateWorkspaceOptions ,
28
+ autoCreateWorkspace ,
29
+ createWorkspace ,
30
+ } from "api/queries/workspaces" ;
27
31
import { checkAuthorization } from "api/queries/authCheck" ;
28
32
import { CreateWSPermissions , createWorkspaceChecks } from "./permissions" ;
29
33
import { richParameters } from "api/queries/templateVersions" ;
30
- import { paramsUsedToCreateWorkspace } from "utils/workspace" ;
31
34
import { useEffectEvent } from "hooks/hookPolyfills" ;
32
35
33
- type CreateWorkspaceMode = "form" | "auto" ;
36
+ export const createWorkspaceModes = [ "form" , "auto" , "duplicate" ] as const ;
37
+ export type CreateWorkspaceMode = ( typeof createWorkspaceModes ) [ number ] ;
34
38
35
- export type ExternalAuthPollingState = "idle" | "polling" | "abandoned" ;
39
+ export type ExternalAuthPollingStatus = "idle" | "polling" | "abandoned" ;
36
40
37
41
const CreateWorkspacePage : FC = ( ) => {
38
42
const organizationId = useOrganizationId ( ) ;
43
+ const [ searchParams ] = useSearchParams ( ) ;
39
44
const { template : templateName } = useParams ( ) as { template : string } ;
40
- const me = useMe ( ) ;
41
45
const navigate = useNavigate ( ) ;
42
- const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
43
- const defaultBuildParameters = getDefaultBuildParameters ( searchParams ) ;
44
- const mode = ( searchParams . get ( "mode" ) ?? "form" ) as CreateWorkspaceMode ;
45
- const customVersionId = searchParams . get ( "version" ) ?? undefined ;
46
- const defaultName =
47
- mode === "auto" ? generateUniqueName ( ) : searchParams . get ( "name" ) ?? "" ;
46
+ const me = useMe ( ) ;
48
47
49
48
const queryClient = useQueryClient ( ) ;
50
- const autoCreateWorkspaceMutation = useMutation (
51
- autoCreateWorkspace ( queryClient ) ,
52
- ) ;
53
49
const createWorkspaceMutation = useMutation ( createWorkspace ( queryClient ) ) ;
54
50
55
51
const templateQuery = useQuery ( templateByName ( organizationId , templateName ) ) ;
56
52
const permissionsQuery = useQuery (
57
- checkAuthorization ( {
58
- checks : createWorkspaceChecks ( organizationId ) ,
59
- } ) ,
53
+ checkAuthorization ( { checks : createWorkspaceChecks ( organizationId ) } ) ,
60
54
) ;
61
- const realizedVersionId =
62
- customVersionId ?? templateQuery . data ?. active_version_id ;
55
+
56
+ const versionId =
57
+ searchParams . get ( "version" ) ?? templateQuery . data ?. active_version_id ;
58
+
59
+ const { authList, pollingStatus, startPolling } = useExternalAuth ( versionId ) ;
63
60
const richParametersQuery = useQuery ( {
64
- ...richParameters ( realizedVersionId ?? "" ) ,
65
- enabled : realizedVersionId !== undefined ,
61
+ ...richParameters ( versionId ?? "" ) ,
62
+ enabled : versionId !== undefined ,
66
63
} ) ;
67
- const realizedParameters = richParametersQuery . data
68
- ? richParametersQuery . data . filter ( paramsUsedToCreateWorkspace )
69
- : undefined ;
70
64
71
- const { externalAuth, externalAuthPollingState, startPollingExternalAuth } =
72
- useExternalAuth ( realizedVersionId ) ;
65
+ const defaultBuildParameters = getDefaultBuildParameters ( searchParams ) ;
66
+ const mode = getWorkspaceMode ( searchParams ) ;
67
+ const defaultName =
68
+ mode === "auto" ? generateUniqueName ( ) : searchParams . get ( "name" ) ?? "" ;
69
+
70
+ const onCreateWorkspace = ( workspace : Workspace ) => {
71
+ navigate ( `/@${ workspace . owner_name } /${ workspace . name } ` ) ;
72
+ } ;
73
+
74
+ const isAutoCreating = useAutomatedWorkspaceCreation ( {
75
+ auto : mode === "auto" ,
76
+ onSuccess : onCreateWorkspace ,
77
+ payload : {
78
+ templateName,
79
+ organizationId,
80
+ defaultBuildParameters,
81
+ defaultName,
82
+ versionId,
83
+ } ,
84
+ } ) ;
73
85
74
86
const isLoadingFormData =
75
87
templateQuery . isLoading ||
76
88
permissionsQuery . isLoading ||
77
89
richParametersQuery . isLoading ;
90
+
78
91
const loadFormDataError =
79
92
templateQuery . error ?? permissionsQuery . error ?? richParametersQuery . error ;
80
93
81
- const title = autoCreateWorkspaceMutation . isLoading
82
- ? "Creating workspace..."
83
- : "Create workspace" ;
84
-
85
- const onCreateWorkspace = useCallback (
86
- ( workspace : Workspace ) => {
87
- navigate ( `/@${ workspace . owner_name } /${ workspace . name } ` ) ;
88
- } ,
89
- [ navigate ] ,
90
- ) ;
91
-
92
- const automateWorkspaceCreation = useEffectEvent ( async ( ) => {
93
- try {
94
- const newWorkspace = await autoCreateWorkspaceMutation . mutateAsync ( {
95
- templateName,
96
- organizationId,
97
- defaultBuildParameters,
98
- defaultName,
99
- versionId : realizedVersionId ,
100
- } ) ;
101
-
102
- onCreateWorkspace ( newWorkspace ) ;
103
- } catch ( err ) {
104
- searchParams . delete ( "mode" ) ;
105
- setSearchParams ( searchParams ) ;
106
- }
107
- } ) ;
108
-
109
- useEffect ( ( ) => {
110
- if ( mode === "auto" ) {
111
- void automateWorkspaceCreation ( ) ;
112
- }
113
- } , [ automateWorkspaceCreation , mode ] ) ;
114
-
115
94
return (
116
95
< >
117
96
< Helmet >
118
- < title > { pageTitle ( title ) } </ title >
97
+ < title >
98
+ { pageTitle (
99
+ isAutoCreating ? "Creating workspace..." : "Create workspace" ,
100
+ ) }
101
+ </ title >
119
102
</ Helmet >
103
+
120
104
{ loadFormDataError && < ErrorAlert error = { loadFormDataError } /> }
121
- { isLoadingFormData || autoCreateWorkspaceMutation . isLoading ? (
105
+
106
+ { isLoadingFormData || isAutoCreating ? (
122
107
< Loader />
123
108
) : (
124
109
< CreateWorkspacePageView
@@ -128,22 +113,22 @@ const CreateWorkspacePage: FC = () => {
128
113
defaultBuildParameters = { defaultBuildParameters }
129
114
error = { createWorkspaceMutation . error }
130
115
template = { templateQuery . data ! }
131
- versionId = { realizedVersionId }
132
- externalAuth = { externalAuth ?? [ ] }
133
- externalAuthPollingState = { externalAuthPollingState }
134
- startPollingExternalAuth = { startPollingExternalAuth }
116
+ versionId = { versionId }
117
+ externalAuth = { authList ?? [ ] }
118
+ externalAuthPollingStatus = { pollingStatus }
119
+ startPollingExternalAuth = { startPolling }
135
120
permissions = { permissionsQuery . data as CreateWSPermissions }
136
- parameters = { realizedParameters as TemplateVersionParameter [ ] }
137
121
creatingWorkspace = { createWorkspaceMutation . isLoading }
138
- onCancel = { ( ) => {
139
- navigate ( - 1 ) ;
140
- } }
122
+ onCancel = { ( ) => navigate ( - 1 ) }
123
+ parameters = { richParametersQuery . data ! . filter (
124
+ ( param ) => ! param . ephemeral ,
125
+ ) }
141
126
onSubmit = { async ( request , owner ) => {
142
- if ( realizedVersionId ) {
127
+ if ( versionId ) {
143
128
request = {
144
129
...request ,
145
130
template_id : undefined ,
146
- template_version_id : realizedVersionId ,
131
+ template_version_id : versionId ,
147
132
} ;
148
133
}
149
134
@@ -161,64 +146,103 @@ const CreateWorkspacePage: FC = () => {
161
146
} ;
162
147
163
148
const useExternalAuth = ( versionId : string | undefined ) => {
164
- const [ externalAuthPollingState , setExternalAuthPollingState ] =
165
- useState < ExternalAuthPollingState > ( "idle" ) ;
149
+ const [ pollingStatus , setPollingStatus ] =
150
+ useState < ExternalAuthPollingStatus > ( "idle" ) ;
166
151
167
- const startPollingExternalAuth = useCallback ( ( ) => {
168
- setExternalAuthPollingState ( "polling" ) ;
152
+ const startPolling = useCallback ( ( ) => {
153
+ setPollingStatus ( "polling" ) ;
169
154
} , [ ] ) ;
170
155
171
- const { data : externalAuth } = useQuery (
156
+ const { data : authList } = useQuery (
172
157
versionId
173
158
? {
174
159
...templateVersionExternalAuth ( versionId ) ,
175
- refetchInterval :
176
- externalAuthPollingState === "polling" ? 1000 : false ,
160
+ refetchInterval : pollingStatus === "polling" ? 1000 : false ,
177
161
}
178
162
: { enabled : false } ,
179
163
) ;
180
164
181
- const allSignedIn = externalAuth ?. every ( ( it ) => it . authenticated ) ;
182
-
183
165
useEffect ( ( ) => {
184
- if ( allSignedIn ) {
185
- setExternalAuthPollingState ( "idle" ) ;
186
- return ;
187
- }
188
-
189
- if ( externalAuthPollingState !== "polling" ) {
166
+ if ( pollingStatus !== "polling" ) {
190
167
return ;
191
168
}
192
169
193
- // Poll for a maximum of one minute
194
- const quitPolling = setTimeout (
195
- ( ) => setExternalAuthPollingState ( "abandoned" ) ,
170
+ const timeoutId = window . setTimeout (
171
+ ( ) => setPollingStatus ( "abandoned" ) ,
196
172
60_000 ,
197
173
) ;
198
- return ( ) => {
199
- clearTimeout ( quitPolling ) ;
200
- } ;
201
- } , [ externalAuthPollingState , allSignedIn ] ) ;
202
-
203
- return {
204
- startPollingExternalAuth,
205
- externalAuth,
206
- externalAuthPollingState,
207
- } ;
174
+
175
+ return ( ) => clearTimeout ( timeoutId ) ;
176
+ } , [ pollingStatus ] ) ;
177
+
178
+ const isAllSignedIn = authList ?. every ( ( it ) => it . authenticated ) ?? false ;
179
+
180
+ // Doing inline state sync to minimize extra re-renders that useEffect
181
+ // approach would involve
182
+ if ( isAllSignedIn && pollingStatus === "polling" ) {
183
+ setPollingStatus ( "idle" ) ;
184
+ }
185
+
186
+ return { authList, isAllSignedIn, pollingStatus, startPolling } as const ;
208
187
} ;
209
188
189
+ function getWorkspaceMode ( params : URLSearchParams ) : CreateWorkspaceMode {
190
+ const paramMode = params . get ( "mode" ) ;
191
+ if ( createWorkspaceModes . includes ( paramMode as CreateWorkspaceMode ) ) {
192
+ return paramMode as CreateWorkspaceMode ;
193
+ }
194
+
195
+ return "form" ;
196
+ }
197
+
198
+ type AutomatedWorkspaceConfig = {
199
+ auto : boolean ;
200
+ payload : AutoCreateWorkspaceOptions ;
201
+ onSuccess : ( newWorkspace : Workspace ) => void ;
202
+ } ;
203
+
204
+ function useAutomatedWorkspaceCreation ( config : AutomatedWorkspaceConfig ) {
205
+ // Duplicates some of the hook calls from the parent, but that was preferable
206
+ // to having the function arguments balloon in complexity
207
+ const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
208
+ const queryClient = useQueryClient ( ) ;
209
+ const autoCreateWorkspaceMutation = useMutation (
210
+ autoCreateWorkspace ( queryClient ) ,
211
+ ) ;
212
+
213
+ const automateWorkspaceCreation = useEffectEvent ( async ( ) => {
214
+ try {
215
+ const newWorkspace = await autoCreateWorkspaceMutation . mutateAsync (
216
+ config . payload ,
217
+ ) ;
218
+
219
+ config . onSuccess ( newWorkspace ) ;
220
+ } catch ( err ) {
221
+ searchParams . delete ( "mode" ) ;
222
+ setSearchParams ( searchParams ) ;
223
+ }
224
+ } ) ;
225
+
226
+ useEffect ( ( ) => {
227
+ if ( config . auto ) {
228
+ void automateWorkspaceCreation ( ) ;
229
+ }
230
+ } , [ automateWorkspaceCreation , config . auto ] ) ;
231
+
232
+ return autoCreateWorkspaceMutation . isLoading ;
233
+ }
234
+
210
235
const getDefaultBuildParameters = (
211
236
urlSearchParams : URLSearchParams ,
212
237
) : WorkspaceBuildParameter [ ] => {
213
- const buildValues : WorkspaceBuildParameter [ ] = [ ] ;
214
- Array . from ( urlSearchParams . keys ( ) )
238
+ return [ ...urlSearchParams . keys ( ) ]
215
239
. filter ( ( key ) => key . startsWith ( "param." ) )
216
- . forEach ( ( key ) => {
217
- const name = key . replace ( "param." , "" ) ;
218
- const value = urlSearchParams . get ( key ) ?? "" ;
219
- buildValues . push ( { name, value } ) ;
240
+ . map ( ( key ) => {
241
+ return {
242
+ name : key . replace ( "param." , "" ) ,
243
+ value : urlSearchParams . get ( key ) ?? "" ,
244
+ } ;
220
245
} ) ;
221
- return buildValues ;
222
246
} ;
223
247
224
248
export const orderedTemplateParameters = (
0 commit comments