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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy