@@ -29,6 +29,7 @@ import { checkAuthorization } from "api/queries/authCheck";
29
29
import { CreateWSPermissions , createWorkspaceChecks } from "./permissions" ;
30
30
import { paramsUsedToCreateWorkspace } from "utils/workspace" ;
31
31
import { useEffectEvent } from "hooks/hookPolyfills" ;
32
+ import { workspaceBuildParameters } from "api/queries/workspaceBuilds" ;
32
33
33
34
export const createWorkspaceModes = [ "form" , "auto" , "duplicate" ] as const ;
34
35
export type CreateWorkspaceMode = ( typeof createWorkspaceModes ) [ number ] ;
@@ -262,3 +263,64 @@ function getDefaultName(mode: CreateWorkspaceMode, params: URLSearchParams) {
262
263
263
264
return paramsName ?? "" ;
264
265
}
266
+
267
+ function getDuplicationUrlParams (
268
+ workspaceParams : readonly WorkspaceBuildParameter [ ] ,
269
+ workspace : Workspace ,
270
+ ) : URLSearchParams {
271
+ // Record type makes sure that every property key added starts with "param.";
272
+ // page is also set up to parse params with this prefix for auto mode
273
+ const consolidatedParams : Record < `param.${string } `, string > = { } ;
274
+
275
+ for ( const p of workspaceParams ) {
276
+ consolidatedParams [ `param.${ p . name } ` ] = p . value ;
277
+ }
278
+
279
+ return new URLSearchParams ( {
280
+ ...consolidatedParams ,
281
+ mode : "duplicate" satisfies CreateWorkspaceMode ,
282
+ name : workspace . name ,
283
+ version : workspace . template_active_version_id ,
284
+ } ) ;
285
+ }
286
+
287
+ /**
288
+ * Takes a workspace, and returns out a function that will navigate the user to
289
+ * the 'Create Workspace' page, pre-filling the form with as much information
290
+ * about the workspace as possible.
291
+ */
292
+ // Meant to be consumed by components outside of this file
293
+ export function useWorkspaceDuplication ( workspace : Workspace ) {
294
+ const navigate = useNavigate ( ) ;
295
+ const buildParametersQuery = useQuery (
296
+ workspaceBuildParameters ( workspace . latest_build . id ) ,
297
+ ) ;
298
+
299
+ // Not using useEffectEvent for this, because useEffect isn't really an
300
+ // intended use case for this custom hook
301
+ const duplicateWorkspace = useCallback ( ( ) => {
302
+ const buildParams = buildParametersQuery . data ;
303
+ if ( buildParams === undefined ) {
304
+ return ;
305
+ }
306
+
307
+ const newUrlParams = getDuplicationUrlParams ( buildParams , workspace ) ;
308
+
309
+ // Necessary for giving modals/popups time to flush their state changes and
310
+ // close the popup before actually navigating. MUI does provide the
311
+ // disablePortal prop, which also side-steps this issue, but you have to
312
+ // remember to put it on any component that calls this function. Better to
313
+ // code defensively and have some redundancy in case someone forgets
314
+ void Promise . resolve ( ) . then ( ( ) => {
315
+ navigate ( {
316
+ pathname : `/templates/${ workspace . template_name } /workspace` ,
317
+ search : newUrlParams . toString ( ) ,
318
+ } ) ;
319
+ } ) ;
320
+ } , [ navigate , workspace , buildParametersQuery . data ] ) ;
321
+
322
+ return {
323
+ duplicateWorkspace,
324
+ duplicationReady : buildParametersQuery . isSuccess ,
325
+ } as const ;
326
+ }
0 commit comments