Skip to content

Commit a17cf03

Browse files
authored
feat(site): add support for presets to the create workspace page (#16567)
This pull request adds support for presets to the create workspace page. This behaviour can be seen in the storybook. This will not be visible in dogfood until we merge support for presets in the provisioners. There is more frontend work to be done before this is ready for a general release, but this should be sufficient for dogfood testing
1 parent 7fd04d4 commit a17cf03

File tree

5 files changed

+150
-1
lines changed

5 files changed

+150
-1
lines changed

site/src/api/api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,15 @@ class ApiMethods {
11451145
return response.data;
11461146
};
11471147

1148+
getTemplateVersionPresets = async (
1149+
templateVersionId: string,
1150+
): Promise<TypesGen.Preset[]> => {
1151+
const response = await this.axios.get<TypesGen.Preset[]>(
1152+
`/api/v2/templateversions/${templateVersionId}/presets`,
1153+
);
1154+
return response.data;
1155+
};
1156+
11481157
startWorkspace = (
11491158
workspaceId: string,
11501159
templateVersionId: string,

site/src/api/queries/templates.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { API, type GetTemplatesOptions, type GetTemplatesQuery } from "api/api";
22
import type {
33
CreateTemplateRequest,
44
CreateTemplateVersionRequest,
5+
Preset,
56
ProvisionerJob,
67
ProvisionerJobStatus,
78
Template,
@@ -305,6 +306,13 @@ export const previousTemplateVersion = (
305306
};
306307
};
307308

309+
export const templateVersionPresets = (versionId: string) => {
310+
return {
311+
queryKey: ["templateVersion", versionId, "presets"],
312+
queryFn: () => API.getTemplateVersionPresets(versionId),
313+
};
314+
};
315+
308316
const waitBuildToBeFinished = async (
309317
version: TemplateVersion,
310318
onRequest?: (data: TemplateVersion) => void,

site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
richParameters,
66
templateByName,
77
templateVersionExternalAuth,
8+
templateVersionPresets,
89
} from "api/queries/templates";
910
import { autoCreateWorkspace, createWorkspace } from "api/queries/workspaces";
1011
import type {
@@ -56,6 +57,10 @@ const CreateWorkspacePage: FC = () => {
5657
const templateQuery = useQuery(
5758
templateByName(organizationName, templateName),
5859
);
60+
const templateVersionPresetsQuery = useQuery({
61+
...templateVersionPresets(templateQuery.data?.active_version_id ?? ""),
62+
enabled: templateQuery.data !== undefined,
63+
});
5964
const permissionsQuery = useQuery(
6065
templateQuery.data
6166
? checkAuthorization({
@@ -203,6 +208,7 @@ const CreateWorkspacePage: FC = () => {
203208
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
204209
permissions={permissionsQuery.data as CreateWSPermissions}
205210
parameters={realizedParameters as TemplateVersionParameter[]}
211+
presets={templateVersionPresetsQuery.data ?? []}
206212
creatingWorkspace={createWorkspaceMutation.isLoading}
207213
onCancel={() => {
208214
navigate(-1);

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { action } from "@storybook/addon-actions";
22
import type { Meta, StoryObj } from "@storybook/react";
3+
import { within } from "@testing-library/react";
4+
import userEvent from "@testing-library/user-event";
35
import { chromatic } from "testHelpers/chromatic";
46
import {
57
MockTemplate,
@@ -116,6 +118,47 @@ export const Parameters: Story = {
116118
},
117119
};
118120

121+
export const PresetsButNoneSelected: Story = {
122+
args: {
123+
presets: [
124+
{
125+
ID: "preset-1",
126+
Name: "Preset 1",
127+
Parameters: [
128+
{
129+
Name: MockTemplateVersionParameter1.name,
130+
Value: "preset 1 override",
131+
},
132+
],
133+
},
134+
{
135+
ID: "preset-2",
136+
Name: "Preset 2",
137+
Parameters: [
138+
{
139+
Name: MockTemplateVersionParameter2.name,
140+
Value: "42",
141+
},
142+
],
143+
},
144+
],
145+
parameters: [
146+
MockTemplateVersionParameter1,
147+
MockTemplateVersionParameter2,
148+
MockTemplateVersionParameter3,
149+
],
150+
},
151+
};
152+
153+
export const PresetSelected: Story = {
154+
args: PresetsButNoneSelected.args,
155+
play: async ({ canvasElement }) => {
156+
const canvas = within(canvasElement);
157+
await userEvent.click(canvas.getByLabelText("Preset"));
158+
await userEvent.click(canvas.getByText("Preset 1"));
159+
},
160+
};
161+
119162
export const ExternalAuth: Story = {
120163
args: {
121164
externalAuth: [

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Alert } from "components/Alert/Alert";
66
import { ErrorAlert } from "components/Alert/ErrorAlert";
77
import { Avatar } from "components/Avatar/Avatar";
88
import { Button } from "components/Button/Button";
9+
import { SelectFilter } from "components/Filter/SelectFilter";
910
import {
1011
FormFields,
1112
FormFooter,
@@ -64,6 +65,7 @@ export interface CreateWorkspacePageViewProps {
6465
hasAllRequiredExternalAuth: boolean;
6566
parameters: TypesGen.TemplateVersionParameter[];
6667
autofillParameters: AutofillBuildParameter[];
68+
presets: TypesGen.Preset[];
6769
permissions: CreateWSPermissions;
6870
creatingWorkspace: boolean;
6971
onCancel: () => void;
@@ -88,6 +90,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
8890
hasAllRequiredExternalAuth,
8991
parameters,
9092
autofillParameters,
93+
presets = [],
9194
permissions,
9295
creatingWorkspace,
9396
onSubmit,
@@ -145,6 +148,62 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
145148
[autofillParameters],
146149
);
147150

151+
const [presetOptions, setPresetOptions] = useState([
152+
{ label: "None", value: "" },
153+
]);
154+
useEffect(() => {
155+
setPresetOptions([
156+
{ label: "None", value: "" },
157+
...presets.map((preset) => ({
158+
label: preset.Name,
159+
value: preset.ID,
160+
})),
161+
]);
162+
}, [presets]);
163+
164+
const [selectedPresetIndex, setSelectedPresetIndex] = useState(0);
165+
const [presetParameterNames, setPresetParameterNames] = useState<string[]>(
166+
[],
167+
);
168+
169+
useEffect(() => {
170+
const selectedPresetOption = presetOptions[selectedPresetIndex];
171+
let selectedPreset: TypesGen.Preset | undefined;
172+
for (const preset of presets) {
173+
if (preset.ID === selectedPresetOption.value) {
174+
selectedPreset = preset;
175+
break;
176+
}
177+
}
178+
179+
if (!selectedPreset || !selectedPreset.Parameters) {
180+
setPresetParameterNames([]);
181+
return;
182+
}
183+
184+
setPresetParameterNames(selectedPreset.Parameters.map((p) => p.Name));
185+
186+
for (const presetParameter of selectedPreset.Parameters) {
187+
const parameterIndex = parameters.findIndex(
188+
(p) => p.name === presetParameter.Name,
189+
);
190+
if (parameterIndex === -1) continue;
191+
192+
const parameterField = `rich_parameter_values.${parameterIndex}`;
193+
194+
form.setFieldValue(parameterField, {
195+
name: presetParameter.Name,
196+
value: presetParameter.Value,
197+
});
198+
}
199+
}, [
200+
presetOptions,
201+
selectedPresetIndex,
202+
presets,
203+
parameters,
204+
form.setFieldValue,
205+
]);
206+
148207
return (
149208
<Margins size="medium">
150209
<PageHeader
@@ -213,6 +272,28 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
213272
</Stack>
214273
)}
215274

275+
{presets.length > 0 && (
276+
<Stack direction="column" spacing={2}>
277+
<span css={styles.description}>
278+
Select a preset to get started
279+
</span>
280+
<Stack direction="row" spacing={2}>
281+
<SelectFilter
282+
label="Preset"
283+
options={presetOptions}
284+
onSelect={(option) => {
285+
setSelectedPresetIndex(
286+
presetOptions.findIndex(
287+
(preset) => preset.value === option?.value,
288+
),
289+
);
290+
}}
291+
placeholder="Select a preset"
292+
selectedOption={presetOptions[selectedPresetIndex]}
293+
/>
294+
</Stack>
295+
</Stack>
296+
)}
216297
<div>
217298
<TextField
218299
{...getFieldHelpers("name")}
@@ -292,7 +373,9 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
292373
const isDisabled =
293374
disabledParams?.includes(
294375
parameter.name.toLowerCase().replace(/ /g, "_"),
295-
) || creatingWorkspace;
376+
) ||
377+
creatingWorkspace ||
378+
presetParameterNames.includes(parameter.name);
296379

297380
return (
298381
<RichParameterInput

0 commit comments

Comments
 (0)