Skip to content

Commit fca6504

Browse files
committed
Refactor loading views and duplicate template
1 parent b1a0b25 commit fca6504

File tree

9 files changed

+247
-98
lines changed

9 files changed

+247
-98
lines changed

site/src/api/queries/templates.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,16 @@ export const templateVersionExternalAuth = (versionId: string) => {
121121
};
122122
};
123123

124-
const createTemplate = async (options: {
124+
export const createTemplate = () => {
125+
return {
126+
mutationFn: createTemplateFn,
127+
};
128+
};
129+
130+
const createTemplateFn = async (options: {
125131
organizationId: string;
126132
version: CreateTemplateVersionRequest;
127-
data: CreateTemplateRequest;
133+
data: Omit<CreateTemplateRequest, "template_version_id">;
128134
}) => {
129135
const version = await API.createTemplateVersion(
130136
options.organizationId,

site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -198,43 +198,47 @@ const getInitialValues = ({
198198
return initialValues;
199199
};
200200

201-
export interface CreateTemplateFormProps {
201+
type CopiedTemplateForm = { copiedTemplate: Template };
202+
type StarterTemplateForm = { starterTemplate: TemplateExample };
203+
type UploadTemplateForm = { upload: TemplateUploadProps };
204+
205+
export type CreateTemplateFormProps = (
206+
| CopiedTemplateForm
207+
| StarterTemplateForm
208+
| UploadTemplateForm
209+
) & {
202210
onCancel: () => void;
203211
onSubmit: (data: CreateTemplateData) => void;
204212
isSubmitting: boolean;
205-
upload: TemplateUploadProps;
206-
starterTemplate?: TemplateExample;
207213
variables?: TemplateVersionVariable[];
208214
error?: unknown;
209215
jobError?: string;
210216
logs?: ProvisionerJobLog[];
211217
allowAdvancedScheduling: boolean;
212-
copiedTemplate?: Template;
213218
allowDisableEveryoneAccess: boolean;
214219
allowAutostopRequirement: boolean;
215-
}
220+
};
216221

217-
export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
218-
onCancel,
219-
onSubmit,
220-
starterTemplate,
221-
copiedTemplate,
222-
variables,
223-
isSubmitting,
224-
upload,
225-
error,
226-
jobError,
227-
logs,
228-
allowAdvancedScheduling,
229-
allowDisableEveryoneAccess,
230-
allowAutostopRequirement,
231-
}) => {
222+
export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
223+
const {
224+
onCancel,
225+
onSubmit,
226+
variables,
227+
isSubmitting,
228+
error,
229+
jobError,
230+
logs,
231+
allowAdvancedScheduling,
232+
allowDisableEveryoneAccess,
233+
allowAutostopRequirement,
234+
} = props;
232235
const styles = useStyles();
233236
const form = useFormik<CreateTemplateData>({
234237
initialValues: getInitialValues({
235238
allowAdvancedScheduling,
236-
fromExample: starterTemplate,
237-
fromCopy: copiedTemplate,
239+
fromExample:
240+
"starterTemplate" in props ? props.starterTemplate : undefined,
241+
fromCopy: "copiedTemplate" in props ? props.copiedTemplate : undefined,
238242
variables,
239243
}),
240244
validationSchema,
@@ -281,16 +285,18 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
281285
description="The name is used to identify the template in URLs and the API."
282286
>
283287
<FormFields>
284-
{starterTemplate ? (
285-
<SelectedTemplate template={starterTemplate} />
286-
) : copiedTemplate ? (
287-
<SelectedTemplate template={copiedTemplate} />
288-
) : (
288+
{"starterTemplate" in props && (
289+
<SelectedTemplate template={props.starterTemplate} />
290+
)}
291+
{"copiedTemplate" in props && (
292+
<SelectedTemplate template={props.copiedTemplate} />
293+
)}
294+
{"upload" in props && (
289295
<TemplateUpload
290-
{...upload}
296+
{...props.upload}
291297
onUpload={async (file) => {
292298
await fillNameAndDisplayWithFilename(file.name, form);
293-
upload.onUpload(file);
299+
props.upload.onUpload(file);
294300
}}
295301
/>
296302
)}

site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx

Lines changed: 167 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,57 @@
1-
import { useMachine } from "@xstate/react";
2-
import { isApiValidationError } from "api/errors";
31
import { useDashboard } from "components/Dashboard/DashboardProvider";
42
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm";
53
import { Loader } from "components/Loader/Loader";
6-
import { Stack } from "components/Stack/Stack";
74
import { useOrganizationId } from "hooks/useOrganizationId";
85
import { FC } from "react";
96
import { Helmet } from "react-helmet-async";
107
import { useNavigate, useSearchParams } from "react-router-dom";
118
import { pageTitle } from "utils/page";
12-
import { createTemplateMachine } from "xServices/createTemplate/createTemplateXService";
9+
import { CreateTemplateData } from "xServices/createTemplate/createTemplateXService";
1310
import { CreateTemplateForm } from "./CreateTemplateForm";
1411
import { ErrorAlert } from "components/Alert/ErrorAlert";
12+
import { useMutation, useQuery } from "@tanstack/react-query";
13+
import {
14+
createTemplate,
15+
templateByName,
16+
templateVersion,
17+
templateVersionVariables,
18+
} from "api/queries/templates";
19+
import { ProvisionerType } from "api/typesGenerated";
20+
import { calculateAutostopRequirementDaysValue } from "utils/schedule";
21+
22+
const provisioner: ProvisionerType =
23+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Playwright needs to use a different provisioner type!
24+
typeof (window as any).playwright !== "undefined" ? "echo" : "terraform";
1525

1626
const CreateTemplatePage: FC = () => {
1727
const navigate = useNavigate();
18-
const organizationId = useOrganizationId();
1928
const [searchParams] = useSearchParams();
20-
const [state, send] = useMachine(createTemplateMachine, {
21-
context: {
22-
organizationId,
23-
exampleId: searchParams.get("exampleId"),
24-
templateNameToCopy: searchParams.get("fromTemplate"),
25-
},
26-
actions: {
27-
onCreate: (_, { data }) => {
28-
navigate(`/templates/${data.name}`);
29-
},
30-
},
31-
});
29+
// const organizationId = useOrganizationId();
30+
// const [state, send] = useMachine(createTemplateMachine, {
31+
// context: {
32+
// organizationId,
33+
// exampleId: searchParams.get("exampleId"),
34+
// templateNameToCopy: searchParams.get("fromTemplate"),
35+
// },
36+
// actions: {
37+
// onCreate: (_, { data }) => {
38+
// navigate(`/templates/${data.name}`);
39+
// },
40+
// },
41+
// });
3242

33-
const { starterTemplate, error, file, jobError, jobLogs, variables } =
34-
state.context;
35-
const shouldDisplayForm = !state.hasTag("loading");
36-
const { entitlements } = useDashboard();
37-
const allowAdvancedScheduling =
38-
entitlements.features["advanced_template_scheduling"].enabled;
39-
// Requires the template RBAC feature, otherwise disabling everyone access
40-
// means no one can access.
41-
const allowDisableEveryoneAccess =
42-
entitlements.features["template_rbac"].enabled;
43-
const allowAutostopRequirement =
44-
entitlements.features["template_autostop_requirement"].enabled;
43+
// const { starterTemplate, error, file, jobError, jobLogs, variables } =
44+
// state.context;
45+
// const shouldDisplayForm = !state.hasTag("loading");
46+
// const { entitlements } = useDashboard();
47+
// const allowAdvancedScheduling =
48+
// entitlements.features["advanced_template_scheduling"].enabled;
49+
// // Requires the template RBAC feature, otherwise disabling everyone access
50+
// // means no one can access.
51+
// const allowDisableEveryoneAccess =
52+
// entitlements.features["template_rbac"].enabled;
53+
// const allowAutostopRequirement =
54+
// entitlements.features["template_autostop_requirement"].enabled;
4555

4656
const onCancel = () => {
4757
navigate(-1);
@@ -54,7 +64,14 @@ const CreateTemplatePage: FC = () => {
5464
</Helmet>
5565

5666
<FullPageHorizontalForm title="Create Template" onCancel={onCancel}>
57-
{state.hasTag("loading") && <Loader />}
67+
{searchParams.has("fromTemplate") ? (
68+
<DuplicateTemplateView />
69+
) : searchParams.has("exampleId") ? (
70+
<ImportStaterTemplateView />
71+
) : (
72+
<UploadTemplateView />
73+
)}
74+
{/* {state.hasTag("loading") && <Loader />}
5875
5976
<Stack spacing={6}>
6077
{Boolean(error) && !isApiValidationError(error) && (
@@ -92,10 +109,129 @@ const CreateTemplatePage: FC = () => {
92109
logs={jobLogs}
93110
/>
94111
)}
95-
</Stack>
112+
</Stack> */}
96113
</FullPageHorizontalForm>
97114
</>
98115
);
99116
};
100117

118+
const DuplicateTemplateView = () => {
119+
const navigate = useNavigate();
120+
const organizationId = useOrganizationId();
121+
const [searchParams] = useSearchParams();
122+
const templateByNameQuery = useQuery(
123+
templateByName(organizationId, searchParams.get("fromTemplate")!),
124+
);
125+
const templateVersionQuery = useQuery({
126+
...templateVersion(
127+
templateByNameQuery.data?.template.active_version_id ?? "",
128+
),
129+
enabled: templateByNameQuery.data !== undefined,
130+
});
131+
const templateVersionVariablesQuery = useQuery({
132+
...templateVersionVariables(
133+
templateByNameQuery.data?.template.active_version_id ?? "",
134+
),
135+
enabled: templateByNameQuery.data !== undefined,
136+
});
137+
const isLoading =
138+
templateByNameQuery.isLoading ||
139+
templateVersionQuery.isLoading ||
140+
templateVersionVariablesQuery.isLoading;
141+
const loadingError =
142+
templateByNameQuery.error ||
143+
templateVersionQuery.error ||
144+
templateVersionVariablesQuery.error;
145+
146+
const formEntitlements = useFormEntitlements();
147+
148+
const createTemplateMutation = useMutation(createTemplate());
149+
150+
if (isLoading) {
151+
return <Loader />;
152+
}
153+
154+
if (loadingError) {
155+
return <ErrorAlert error={loadingError} />;
156+
}
157+
158+
return (
159+
<CreateTemplateForm
160+
{...formEntitlements}
161+
copiedTemplate={templateByNameQuery.data!.template}
162+
error={createTemplateMutation.error}
163+
isSubmitting={createTemplateMutation.isLoading}
164+
variables={templateVersionVariablesQuery.data}
165+
onCancel={() => navigate(-1)}
166+
onSubmit={async (formData) => {
167+
const template = await createTemplateMutation.mutateAsync({
168+
organizationId,
169+
version: {
170+
storage_method: "file",
171+
file_id: templateVersionQuery.data!.job.file_id,
172+
provisioner: provisioner,
173+
tags: {},
174+
},
175+
data: prepareData(formData),
176+
});
177+
navigate(`/templates/${template.name}`);
178+
}}
179+
180+
// jobError={jobError}
181+
// logs={jobLogs}
182+
/>
183+
);
184+
};
185+
186+
const useFormEntitlements = () => {
187+
const { entitlements } = useDashboard();
188+
const allowAdvancedScheduling =
189+
entitlements.features["advanced_template_scheduling"].enabled;
190+
// Requires the template RBAC feature, otherwise disabling everyone access
191+
// means no one can access.
192+
const allowDisableEveryoneAccess =
193+
entitlements.features["template_rbac"].enabled;
194+
const allowAutostopRequirement =
195+
entitlements.features["template_autostop_requirement"].enabled;
196+
197+
return {
198+
allowAdvancedScheduling,
199+
allowDisableEveryoneAccess,
200+
allowAutostopRequirement,
201+
};
202+
};
203+
204+
const ImportStaterTemplateView = () => {
205+
return <div>Import</div>;
206+
};
207+
208+
const UploadTemplateView = () => {
209+
return <div>Upload</div>;
210+
};
211+
212+
const prepareData = (formData: CreateTemplateData) => {
213+
const {
214+
default_ttl_hours,
215+
max_ttl_hours,
216+
parameter_values_by_name,
217+
allow_everyone_group_access,
218+
autostop_requirement_days_of_week,
219+
autostop_requirement_weeks,
220+
...safeTemplateData
221+
} = formData;
222+
223+
return {
224+
...safeTemplateData,
225+
disable_everyone_group_access: !formData.allow_everyone_group_access,
226+
default_ttl_ms: formData.default_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
227+
max_ttl_ms: formData.max_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
228+
autostop_requirement: {
229+
days_of_week: calculateAutostopRequirementDaysValue(
230+
formData.autostop_requirement_days_of_week,
231+
),
232+
weeks: formData.autostop_requirement_weeks,
233+
},
234+
};
235+
};
236+
101237
export default CreateTemplatePage;

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText.tsx

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { Template } from "api/typesGenerated";
2-
3-
export type TemplateAutostopRequirementDaysValue =
4-
| "off"
5-
| "daily"
6-
| "saturday"
7-
| "sunday";
2+
import { TemplateAutostopRequirementDaysValue } from "utils/schedule";
83

94
const autostopRequirementDescriptions = {
105
off: "Workspaces are not required to stop periodically.",
@@ -31,29 +26,6 @@ export const convertAutostopRequirementDaysValue = (
3126
return "off";
3227
};
3328

34-
export const calculateAutostopRequirementDaysValue = (
35-
value: TemplateAutostopRequirementDaysValue,
36-
): Template["autostop_requirement"]["days_of_week"] => {
37-
switch (value) {
38-
case "daily":
39-
return [
40-
"monday",
41-
"tuesday",
42-
"wednesday",
43-
"thursday",
44-
"friday",
45-
"saturday",
46-
"sunday",
47-
];
48-
case "saturday":
49-
return ["saturday"];
50-
case "sunday":
51-
return ["sunday"];
52-
}
53-
54-
return [];
55-
};
56-
5729
export const AutostopRequirementDaysHelperText = ({
5830
days = "off",
5931
}: {

0 commit comments

Comments
 (0)