Skip to content

Commit 61b26f5

Browse files
committed
chore: get basic clone functionality done for UI
1 parent 0af0352 commit 61b26f5

File tree

6 files changed

+257
-162
lines changed

6 files changed

+257
-162
lines changed

site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
CreateWSPermissions,
1111
CreateWorkspaceMode,
1212
createWorkspaceMachine,
13+
createWorkspaceModes,
1314
} from "xServices/createWorkspace/createWorkspaceXService";
1415
import { CreateWorkspacePageView } from "./CreateWorkspacePageView";
1516
import { Loader } from "components/Loader/Loader";
@@ -33,7 +34,7 @@ const CreateWorkspacePage: FC = () => {
3334
const navigate = useNavigate();
3435

3536
const defaultBuildParameters = getDefaultBuildParameters(searchParams);
36-
const mode = (searchParams.get("mode") ?? "form") as CreateWorkspaceMode;
37+
const mode = getWorkspaceMode(searchParams);
3738
const [createWorkspaceState, send] = useMachine(createWorkspaceMachine, {
3839
context: {
3940
organizationId,
@@ -87,6 +88,11 @@ const CreateWorkspacePage: FC = () => {
8788
}
8889
}
8990

91+
const isFormLoading = Boolean(
92+
createWorkspaceState.matches("loadingFormData") ||
93+
createWorkspaceState.matches("autoCreating"),
94+
);
95+
9096
return (
9197
<>
9298
<Helmet>
@@ -99,10 +105,7 @@ const CreateWorkspacePage: FC = () => {
99105
</title>
100106
</Helmet>
101107

102-
{Boolean(
103-
createWorkspaceState.matches("loadingFormData") ||
104-
createWorkspaceState.matches("autoCreating"),
105-
) && <Loader />}
108+
{isFormLoading && <Loader />}
106109

107110
{createWorkspaceState.matches("loadError") && (
108111
<ErrorAlert error={error} />
@@ -121,6 +124,7 @@ const CreateWorkspacePage: FC = () => {
121124
permissions={permissions as CreateWSPermissions}
122125
parameters={parameters!}
123126
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}
127+
mode={mode}
124128
startPollingExternalAuth={() => {
125129
setExternalAuthPollingState("polling");
126130
}}
@@ -152,6 +156,15 @@ const getDefaultBuildParameters = (
152156
});
153157
};
154158

159+
function getWorkspaceMode(params: URLSearchParams): CreateWorkspaceMode {
160+
const paramMode = params.get("mode");
161+
if (createWorkspaceModes.includes(paramMode as CreateWorkspaceMode)) {
162+
return paramMode as CreateWorkspaceMode;
163+
}
164+
165+
return "form";
166+
}
167+
155168
const generateUniqueName = () => {
156169
const numberDictionary = NumberDictionary.generate({ min: 0, max: 99 });
157170
return uniqueNamesGenerator({

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 156 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import TextField from "@mui/material/TextField";
22
import * as TypesGen from "api/typesGenerated";
33
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
44
import { FormikContextType, useFormik } from "formik";
5-
import { FC, useEffect, useState } from "react";
5+
import { type FC, useEffect, useState, useReducer } from "react";
66
import {
77
getFormHelpers,
88
nameValidator,
@@ -26,12 +26,18 @@ import {
2626
ImmutableTemplateParametersSection,
2727
MutableTemplateParametersSection,
2828
} from "components/TemplateParameters/TemplateParameters";
29-
import { CreateWSPermissions } from "xServices/createWorkspace/createWorkspaceXService";
29+
import {
30+
CreateWSPermissions,
31+
CreateWorkspaceMode,
32+
} from "xServices/createWorkspace/createWorkspaceXService";
3033
import { ExternalAuth } from "./ExternalAuth";
3134
import { ErrorAlert } from "components/Alert/ErrorAlert";
3235
import { Stack } from "components/Stack/Stack";
3336
import { type ExternalAuthPollingState } from "./CreateWorkspacePage";
3437
import { useSearchParams } from "react-router-dom";
38+
import { Alert } from "components/Alert/Alert";
39+
import { Margins } from "components/Margins/Margins";
40+
import { useTheme } from "@emotion/react";
3541

3642
export interface CreateWorkspacePageViewProps {
3743
error: unknown;
@@ -47,6 +53,7 @@ export interface CreateWorkspacePageViewProps {
4753
permissions: CreateWSPermissions;
4854
creatingWorkspace: boolean;
4955
onCancel: () => void;
56+
mode: CreateWorkspaceMode;
5057
onSubmit: (
5158
req: TypesGen.CreateWorkspaceRequest,
5259
owner: TypesGen.User,
@@ -68,6 +75,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
6875
creatingWorkspace,
6976
onSubmit,
7077
onCancel,
78+
mode,
7179
}) => {
7280
const styles = useStyles();
7381
const [owner, setOwner] = useState(defaultOwner);
@@ -113,133 +121,143 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
113121
);
114122

115123
return (
116-
<FullPageHorizontalForm title="New workspace" onCancel={onCancel}>
117-
<HorizontalForm onSubmit={form.handleSubmit}>
118-
{Boolean(error) && <ErrorAlert error={error} />}
119-
{/* General info */}
120-
<FormSection
121-
title="General"
122-
description="The template and name of your new workspace."
123-
>
124-
<FormFields>
125-
<SelectedTemplate template={template} />
126-
{versionId && versionId !== template.active_version_id && (
127-
<Stack spacing={1} className={styles.hasDescription}>
128-
<TextField
129-
disabled
130-
fullWidth
131-
value={versionId}
132-
label="Version ID"
133-
/>
134-
<span className={styles.description}>
135-
This parameter has been preset, and cannot be modified.
136-
</span>
137-
</Stack>
138-
)}
139-
<TextField
140-
{...getFieldHelpers("name")}
141-
disabled={form.isSubmitting}
142-
onChange={onChangeTrimmed(form)}
143-
autoFocus
144-
fullWidth
145-
label="Workspace Name"
146-
/>
147-
</FormFields>
148-
</FormSection>
124+
<>
125+
{mode === "duplicate" && <DuplicateWarningMessage />}
149126

150-
{permissions.createWorkspaceForUser && (
127+
<FullPageHorizontalForm title="New workspace" onCancel={onCancel}>
128+
<HorizontalForm onSubmit={form.handleSubmit}>
129+
{Boolean(error) && <ErrorAlert error={error} />}
130+
{/* General info */}
151131
<FormSection
152-
title="Workspace Owner"
153-
description="Only admins can create workspace for other users."
132+
title="General"
133+
description="The template and name of your new workspace."
154134
>
155135
<FormFields>
156-
<UserAutocomplete
157-
value={owner}
158-
onChange={(user) => {
159-
setOwner(user ?? defaultOwner);
160-
}}
161-
label="Owner"
162-
size="medium"
136+
<SelectedTemplate template={template} />
137+
{versionId && versionId !== template.active_version_id && (
138+
<Stack spacing={1} className={styles.hasDescription}>
139+
<TextField
140+
disabled
141+
fullWidth
142+
value={versionId}
143+
label="Version ID"
144+
/>
145+
<span className={styles.description}>
146+
This parameter has been preset, and cannot be modified.
147+
</span>
148+
</Stack>
149+
)}
150+
<TextField
151+
{...getFieldHelpers("name")}
152+
disabled={form.isSubmitting}
153+
onChange={onChangeTrimmed(form)}
154+
autoFocus
155+
fullWidth
156+
label="Workspace Name"
163157
/>
164158
</FormFields>
165159
</FormSection>
166-
)}
167160

168-
{externalAuth && externalAuth.length > 0 && (
169-
<FormSection
170-
title="External Authentication"
171-
description="This template requires authentication to external services."
172-
>
173-
<FormFields>
174-
{externalAuth.map((auth) => (
175-
<ExternalAuth
176-
key={auth.id}
177-
authenticateURL={auth.authenticate_url}
178-
authenticated={auth.authenticated}
179-
externalAuthPollingState={externalAuthPollingState}
180-
startPollingExternalAuth={startPollingExternalAuth}
181-
displayName={auth.display_name}
182-
displayIcon={auth.display_icon}
183-
error={authErrors[auth.id]}
161+
{permissions.createWorkspaceForUser && (
162+
<FormSection
163+
title="Workspace Owner"
164+
description="Only admins can create workspace for other users."
165+
>
166+
<FormFields>
167+
<UserAutocomplete
168+
value={owner}
169+
onChange={(user) => {
170+
setOwner(user ?? defaultOwner);
171+
}}
172+
label="Owner"
173+
size="medium"
184174
/>
185-
))}
186-
</FormFields>
187-
</FormSection>
188-
)}
175+
</FormFields>
176+
</FormSection>
177+
)}
189178

190-
{parameters && (
191-
<>
192-
<MutableTemplateParametersSection
193-
templateParameters={parameters}
194-
getInputProps={(parameter, index) => {
195-
return {
196-
...getFieldHelpers(
197-
"rich_parameter_values[" + index + "].value",
198-
),
199-
onChange: async (value) => {
200-
await form.setFieldValue("rich_parameter_values." + index, {
201-
name: parameter.name,
202-
value: value,
203-
});
204-
},
205-
disabled:
206-
disabledParamsList?.includes(
207-
parameter.name.toLowerCase().replace(/ /g, "_"),
208-
) || form.isSubmitting,
209-
};
210-
}}
211-
/>
212-
<ImmutableTemplateParametersSection
213-
templateParameters={parameters}
214-
classes={{ root: styles.warningSection }}
215-
getInputProps={(parameter, index) => {
216-
return {
217-
...getFieldHelpers(
218-
"rich_parameter_values[" + index + "].value",
219-
),
220-
onChange: async (value) => {
221-
await form.setFieldValue("rich_parameter_values." + index, {
222-
name: parameter.name,
223-
value: value,
224-
});
225-
},
226-
disabled:
227-
disabledParamsList?.includes(
228-
parameter.name.toLowerCase().replace(/ /g, "_"),
229-
) || form.isSubmitting,
230-
};
231-
}}
232-
/>
233-
</>
234-
)}
179+
{externalAuth && externalAuth.length > 0 && (
180+
<FormSection
181+
title="External Authentication"
182+
description="This template requires authentication to external services."
183+
>
184+
<FormFields>
185+
{externalAuth.map((auth) => (
186+
<ExternalAuth
187+
key={auth.id}
188+
authenticateURL={auth.authenticate_url}
189+
authenticated={auth.authenticated}
190+
externalAuthPollingState={externalAuthPollingState}
191+
startPollingExternalAuth={startPollingExternalAuth}
192+
displayName={auth.display_name}
193+
displayIcon={auth.display_icon}
194+
error={authErrors[auth.id]}
195+
/>
196+
))}
197+
</FormFields>
198+
</FormSection>
199+
)}
200+
201+
{parameters && (
202+
<>
203+
<MutableTemplateParametersSection
204+
templateParameters={parameters}
205+
getInputProps={(parameter, index) => {
206+
return {
207+
...getFieldHelpers(
208+
"rich_parameter_values[" + index + "].value",
209+
),
210+
onChange: async (value) => {
211+
await form.setFieldValue(
212+
"rich_parameter_values." + index,
213+
{
214+
name: parameter.name,
215+
value: value,
216+
},
217+
);
218+
},
219+
disabled:
220+
disabledParamsList?.includes(
221+
parameter.name.toLowerCase().replace(/ /g, "_"),
222+
) || form.isSubmitting,
223+
};
224+
}}
225+
/>
226+
<ImmutableTemplateParametersSection
227+
templateParameters={parameters}
228+
classes={{ root: styles.warningSection }}
229+
getInputProps={(parameter, index) => {
230+
return {
231+
...getFieldHelpers(
232+
"rich_parameter_values[" + index + "].value",
233+
),
234+
onChange: async (value) => {
235+
await form.setFieldValue(
236+
"rich_parameter_values." + index,
237+
{
238+
name: parameter.name,
239+
value: value,
240+
},
241+
);
242+
},
243+
disabled:
244+
disabledParamsList?.includes(
245+
parameter.name.toLowerCase().replace(/ /g, "_"),
246+
) || form.isSubmitting,
247+
};
248+
}}
249+
/>
250+
</>
251+
)}
235252

236-
<FormFooter
237-
onCancel={onCancel}
238-
isLoading={creatingWorkspace}
239-
submitLabel="Create Workspace"
240-
/>
241-
</HorizontalForm>
242-
</FullPageHorizontalForm>
253+
<FormFooter
254+
onCancel={onCancel}
255+
isLoading={creatingWorkspace}
256+
submitLabel="Create Workspace"
257+
/>
258+
</HorizontalForm>
259+
</FullPageHorizontalForm>
260+
</>
243261
);
244262
};
245263

@@ -258,6 +276,26 @@ function getAuthErrors(
258276
return authErrors;
259277
}
260278

279+
function DuplicateWarningMessage() {
280+
const [isDismissed, dismiss] = useReducer(() => true, false);
281+
const theme = useTheme();
282+
283+
if (isDismissed) {
284+
return null;
285+
}
286+
287+
return (
288+
<div css={{ paddingTop: theme.spacing(6) }}>
289+
<Margins size="medium">
290+
<Alert severity="warning" dismissible onDismiss={dismiss}>
291+
Duplicating a workspace only copies its parameters. No state from the
292+
old workspace is copied over.
293+
</Alert>
294+
</Margins>
295+
</div>
296+
);
297+
}
298+
261299
const useStyles = makeStyles((theme) => ({
262300
hasDescription: {
263301
paddingBottom: theme.spacing(2),

0 commit comments

Comments
 (0)