Skip to content

Commit 0f53056

Browse files
committed
add support for presets to the coder frontend
1 parent edd982e commit 0f53056

File tree

5 files changed

+149
-1
lines changed

5 files changed

+149
-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: 7 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,11 @@ const CreateWorkspacePage: FC = () => {
5657
const templateQuery = useQuery(
5758
templateByName(organizationName, templateName),
5859
);
60+
const templateVersionPresetsQuery = useQuery(
61+
templateQuery.data
62+
? templateVersionPresets(templateQuery.data.active_version_id)
63+
: { enabled: false },
64+
);
5965
const permissionsQuery = useQuery(
6066
templateQuery.data
6167
? checkAuthorization({
@@ -203,6 +209,7 @@ const CreateWorkspacePage: FC = () => {
203209
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
204210
permissions={permissionsQuery.data as CreateWSPermissions}
205211
parameters={realizedParameters as TemplateVersionParameter[]}
212+
presets={templateVersionPresetsQuery.data ?? []}
206213
creatingWorkspace={createWorkspaceMutation.isLoading}
207214
onCancel={() => {
208215
navigate(-1);

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,38 @@ export const Parameters: Story = {
116116
},
117117
};
118118

119+
export const Presets: Story = {
120+
args: {
121+
presets: [
122+
{
123+
ID: "preset-1",
124+
Name: "Preset 1",
125+
Parameters: [
126+
{
127+
Name: MockTemplateVersionParameter1.name,
128+
Value: "preset 1 override",
129+
},
130+
],
131+
},
132+
{
133+
ID: "preset-2",
134+
Name: "Preset 2",
135+
Parameters: [
136+
{
137+
Name: MockTemplateVersionParameter2.name,
138+
Value: "42",
139+
},
140+
],
141+
},
142+
],
143+
parameters: [
144+
MockTemplateVersionParameter1,
145+
MockTemplateVersionParameter2,
146+
MockTemplateVersionParameter3,
147+
],
148+
},
149+
};
150+
119151
export const ExternalAuth: Story = {
120152
args: {
121153
externalAuth: [

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 93 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,68 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
145148
[autofillParameters],
146149
);
147150

151+
const presetOptions = useMemo(() => {
152+
return [
153+
{ label: "None", value: "" },
154+
...presets.map((preset) => ({
155+
label: preset.Name,
156+
value: preset.ID,
157+
})),
158+
];
159+
}, [presets]);
160+
161+
const [selectedPresetIndex, setSelectedPresetIndex] = useState(0);
162+
const [presetParameterNames, setPresetParameterNames] = useState<string[]>(
163+
[],
164+
);
165+
166+
useEffect(() => {
167+
// TODO (sasswart): test case: what if immutable parameters are used in the preset?
168+
// TODO (sasswart): test case: what if presets are defined for a template version with no params?
169+
// TODO (sasswart): test case: what if a non active version is selected?
170+
// TODO (sasswart): test case: what if a preset is selected that has no parameters?
171+
// TODO (sasswart): what if we have preset params and autofill params on the same param?
172+
// TODO (sasswart): test case: if we move from preset to no preset, do we reset the params?
173+
// If so, how should it behave? Reset to initial value? reset to last set value?
174+
// TODO (sasswart): test case: rich parameters
175+
176+
const selectedPresetOption = presetOptions[selectedPresetIndex];
177+
let selectedPreset: TypesGen.Preset | undefined;
178+
for (const preset of presets) {
179+
if (preset.ID === selectedPresetOption.value) {
180+
selectedPreset = preset;
181+
break;
182+
}
183+
}
184+
185+
if (!selectedPreset || !selectedPreset.Parameters) {
186+
setPresetParameterNames([]);
187+
return;
188+
}
189+
190+
setPresetParameterNames(selectedPreset.Parameters.map((p) => p.Name));
191+
192+
for (const presetParameter of selectedPreset.Parameters) {
193+
const parameterIndex = parameters.findIndex(
194+
(p) => p.name === presetParameter.Name,
195+
);
196+
if (parameterIndex === -1) continue;
197+
198+
const parameterField = `rich_parameter_values.${parameterIndex}`;
199+
200+
form.setFieldValue(parameterField, {
201+
name: presetParameter.Name,
202+
value: presetParameter.Value,
203+
});
204+
}
205+
}, [
206+
presetOptions,
207+
selectedPresetIndex,
208+
presets,
209+
parameters,
210+
form.setFieldValue,
211+
]);
212+
148213
return (
149214
<Margins size="medium">
150215
<PageHeader
@@ -189,6 +254,31 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
189254
</Alert>
190255
)}
191256

257+
{presets.length > 0 && (
258+
<FormSection
259+
title="Presets"
260+
description="A list of preset workspace configurations to get you started."
261+
>
262+
<FormFields>
263+
<Stack direction="row" spacing={2}>
264+
<SelectFilter
265+
label="Preset"
266+
options={presetOptions}
267+
onSelect={(option) => {
268+
setSelectedPresetIndex(
269+
presetOptions.findIndex(
270+
(preset) => preset.value === option?.value,
271+
),
272+
);
273+
}}
274+
placeholder="Select a preset"
275+
selectedOption={presetOptions[selectedPresetIndex]}
276+
/>
277+
</Stack>
278+
</FormFields>
279+
</FormSection>
280+
)}
281+
192282
{/* General info */}
193283
<FormSection
194284
title="General"
@@ -292,7 +382,9 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
292382
const isDisabled =
293383
disabledParams?.includes(
294384
parameter.name.toLowerCase().replace(/ /g, "_"),
295-
) || creatingWorkspace;
385+
) ||
386+
creatingWorkspace ||
387+
presetParameterNames.includes(parameter.name);
296388

297389
return (
298390
<RichParameterInput

0 commit comments

Comments
 (0)