Skip to content

Commit 4c71ccc

Browse files
authored
fix(site): disable autostart and autostop according to template settings (#11809)
* fix (site): disable autostart and autostop according to template settings * checking form values again; wrote tests * fixed closure and label bugs * fix broken query key * tweaks
1 parent 52c08a9 commit 4c71ccc

File tree

4 files changed

+221
-27
lines changed

4 files changed

+221
-27
lines changed

site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.stories.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,17 @@ const meta: Meta<typeof WorkspaceScheduleForm> = {
1919
title: "pages/WorkspaceSettingsPage/WorkspaceScheduleForm",
2020
component: WorkspaceScheduleForm,
2121
args: {
22-
enableAutoStart: true,
23-
enableAutoStop: true,
22+
allowTemplateAutoStart: true,
23+
allowTemplateAutoStop: true,
24+
allowedTemplateAutoStartDays: [
25+
"sunday",
26+
"monday",
27+
"tuesday",
28+
"wednesday",
29+
"thursday",
30+
"friday",
31+
"saturday",
32+
],
2433
},
2534
};
2635

@@ -42,8 +51,8 @@ export const AllDisabled: Story = {
4251
autostopEnabled: false,
4352
ttl: emptyTTL,
4453
},
45-
enableAutoStart: false,
46-
enableAutoStop: false,
54+
allowTemplateAutoStart: false,
55+
allowTemplateAutoStop: false,
4756
},
4857
};
4958

@@ -55,7 +64,7 @@ export const Autostart: Story = {
5564
autostopEnabled: false,
5665
ttl: emptyTTL,
5766
},
58-
enableAutoStop: false,
67+
allowTemplateAutoStop: false,
5968
},
6069
};
6170

site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.ts renamed to site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.tsx

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@ import {
33
ttlShutdownAt,
44
validationSchema,
55
WorkspaceScheduleFormValues,
6+
WorkspaceScheduleForm,
67
} from "./WorkspaceScheduleForm";
78
import { timeZones } from "utils/timeZones";
9+
import * as API from "api/api";
10+
import { MockTemplate } from "testHelpers/entities";
11+
import { render } from "testHelpers/renderHelpers";
12+
import { defaultSchedule } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule";
13+
import { screen } from "@testing-library/react";
814

915
const valid: WorkspaceScheduleFormValues = {
1016
autostartEnabled: true,
@@ -237,3 +243,139 @@ describe("ttlShutdownAt", () => {
237243
expect(ttlShutdownAt(ttlHours)).toEqual(expected);
238244
});
239245
});
246+
247+
const autoStartDayLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
248+
const defaultFormProps = {
249+
submitScheduleError: "",
250+
initialValues: {
251+
...defaultSchedule(),
252+
autostartEnabled: true,
253+
autostopEnabled: true,
254+
ttl: 24,
255+
},
256+
isLoading: false,
257+
defaultTTL: 24,
258+
onCancel: () => null,
259+
onSubmit: () => null,
260+
allowedTemplateAutoStartDays: autoStartDayLabels,
261+
allowTemplateAutoStart: true,
262+
allowTemplateAutoStop: true,
263+
};
264+
265+
describe("templateInheritance", () => {
266+
it("disables the entire autostart feature appropriately", async () => {
267+
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
268+
render(
269+
<WorkspaceScheduleForm
270+
{...defaultFormProps}
271+
allowTemplateAutoStart={false}
272+
/>,
273+
);
274+
275+
const autoStartToggle = await screen.findByLabelText("Enable Autostart");
276+
expect(autoStartToggle).toBeDisabled();
277+
278+
const startTimeInput = await screen.findByLabelText("Start time");
279+
expect(startTimeInput).toBeDisabled();
280+
281+
const timezoneInput = await screen.findByLabelText("Timezone");
282+
// MUI's input is wrapped in a div so we look at the aria-attribute instead
283+
expect(timezoneInput).toHaveAttribute("aria-disabled");
284+
285+
for (const label of autoStartDayLabels) {
286+
const checkbox = await screen.findByLabelText(label);
287+
expect(checkbox).toBeDisabled();
288+
}
289+
});
290+
it("disables the autostart days of the week appropriately", async () => {
291+
const enabledDayLabels = ["Sat", "Sun"];
292+
293+
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
294+
render(
295+
<WorkspaceScheduleForm
296+
{...defaultFormProps}
297+
allowedTemplateAutoStartDays={["saturday", "sunday"]}
298+
/>,
299+
);
300+
301+
const autoStartToggle = await screen.findByLabelText("Enable Autostart");
302+
expect(autoStartToggle).toBeEnabled();
303+
304+
const startTimeInput = await screen.findByLabelText("Start time");
305+
expect(startTimeInput).toBeEnabled();
306+
307+
const timezoneInput = await screen.findByLabelText("Timezone");
308+
// MUI's input is wrapped in a div so we look at the aria-attribute instead
309+
expect(timezoneInput).not.toHaveAttribute("aria-disabled");
310+
311+
for (const label of enabledDayLabels) {
312+
const checkbox = await screen.findByLabelText(label);
313+
expect(checkbox).toBeEnabled();
314+
}
315+
316+
for (const label of autoStartDayLabels.filter(
317+
(day) => !enabledDayLabels.includes(day),
318+
)) {
319+
const checkbox = await screen.findByLabelText(label);
320+
expect(checkbox).toBeDisabled();
321+
}
322+
});
323+
it("disables the entire autostop feature appropriately", async () => {
324+
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
325+
render(
326+
<WorkspaceScheduleForm
327+
{...defaultFormProps}
328+
allowTemplateAutoStop={false}
329+
/>,
330+
);
331+
332+
const autoStopToggle = await screen.findByLabelText("Enable Autostop");
333+
expect(autoStopToggle).toBeDisabled();
334+
335+
const ttlInput = await screen.findByLabelText(
336+
"Time until shutdown (hours)",
337+
);
338+
expect(ttlInput).toBeDisabled();
339+
});
340+
it("disables secondary autostart fields if main feature switch is toggled off", async () => {
341+
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
342+
render(
343+
<WorkspaceScheduleForm
344+
{...defaultFormProps}
345+
initialValues={{
346+
...defaultFormProps.initialValues,
347+
autostartEnabled: false,
348+
}}
349+
/>,
350+
);
351+
352+
const startTimeInput = await screen.findByLabelText("Start time");
353+
expect(startTimeInput).toBeDisabled();
354+
355+
const timezoneInput = await screen.findByLabelText("Timezone");
356+
// MUI's input is wrapped in a div so we look at the aria-attribute instead
357+
expect(timezoneInput).toHaveAttribute("aria-disabled");
358+
359+
autoStartDayLabels.forEach(async (label) => {
360+
const checkbox = await screen.findByLabelText(label);
361+
expect(checkbox).toBeDisabled();
362+
});
363+
});
364+
it("disables secondary autostop fields if main feature switch is toggled off", async () => {
365+
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
366+
render(
367+
<WorkspaceScheduleForm
368+
{...defaultFormProps}
369+
initialValues={{
370+
...defaultFormProps.initialValues,
371+
autostopEnabled: false,
372+
}}
373+
/>,
374+
);
375+
376+
const ttlInput = await screen.findByLabelText(
377+
"Time until shutdown (hours)",
378+
);
379+
expect(ttlInput).toBeDisabled();
380+
});
381+
});

site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.tsx

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { timeZones } from "utils/timeZones";
3232
import Tooltip from "@mui/material/Tooltip";
3333
import { formatDuration, intervalToDuration } from "date-fns";
3434
import { DisabledBadge } from "components/Badges/Badges";
35+
import { TemplateAutostartRequirement } from "api/typesGenerated";
36+
import { PropsWithChildren } from "react";
3537

3638
// REMARK: some plugins depend on utc, so it's listed first. Otherwise they're
3739
// sorted alphabetically.
@@ -73,8 +75,9 @@ export interface WorkspaceScheduleFormProps {
7375
submitScheduleError?: unknown;
7476
initialValues: WorkspaceScheduleFormValues;
7577
isLoading: boolean;
76-
enableAutoStop: boolean;
77-
enableAutoStart: boolean;
78+
allowedTemplateAutoStartDays: TemplateAutostartRequirement["days_of_week"];
79+
allowTemplateAutoStop: boolean;
80+
allowTemplateAutoStart: boolean;
7881
onCancel: () => void;
7982
onSubmit: (values: WorkspaceScheduleFormValues) => void;
8083
// for storybook
@@ -182,7 +185,7 @@ export const validationSchema = Yup.object({
182185
});
183186

184187
export const WorkspaceScheduleForm: FC<
185-
React.PropsWithChildren<WorkspaceScheduleFormProps>
188+
PropsWithChildren<WorkspaceScheduleFormProps>
186189
> = ({
187190
submitScheduleError,
188191
initialValues,
@@ -191,8 +194,9 @@ export const WorkspaceScheduleForm: FC<
191194
onSubmit,
192195
initialTouched,
193196
defaultTTL,
194-
enableAutoStop,
195-
enableAutoStart,
197+
allowedTemplateAutoStartDays,
198+
allowTemplateAutoStop,
199+
allowTemplateAutoStart,
196200
}) => {
197201
const form = useFormik<WorkspaceScheduleFormValues>({
198202
initialValues,
@@ -207,11 +211,6 @@ export const WorkspaceScheduleForm: FC<
207211
);
208212

209213
const checkboxes: Array<{ value: boolean; name: string; label: string }> = [
210-
{
211-
value: form.values.sunday,
212-
name: "sunday",
213-
label: Language.daySundayLabel,
214-
},
215214
{
216215
value: form.values.monday,
217216
name: "monday",
@@ -242,6 +241,11 @@ export const WorkspaceScheduleForm: FC<
242241
name: "saturday",
243242
label: Language.daySaturdayLabel,
244243
},
244+
{
245+
value: form.values.sunday,
246+
name: "sunday",
247+
label: Language.daySundayLabel,
248+
},
245249
];
246250

247251
const handleToggleAutostart = async (e: ChangeEvent) => {
@@ -288,7 +292,7 @@ export const WorkspaceScheduleForm: FC<
288292
Select the time and days of week on which you want the workspace
289293
starting automatically.
290294
</div>
291-
{!enableAutoStart && (
295+
{!allowTemplateAutoStart && (
292296
<Tooltip title="This option can be enabled in the template settings">
293297
<DisabledBadge />
294298
</Tooltip>
@@ -300,7 +304,7 @@ export const WorkspaceScheduleForm: FC<
300304
<FormControlLabel
301305
control={
302306
<Switch
303-
disabled={!enableAutoStart}
307+
disabled={!allowTemplateAutoStart}
304308
name="autostartEnabled"
305309
checked={form.values.autostartEnabled}
306310
onChange={handleToggleAutostart}
@@ -311,14 +315,26 @@ export const WorkspaceScheduleForm: FC<
311315
<Stack direction="row">
312316
<TextField
313317
{...formHelpers("startTime")}
314-
disabled={isLoading || !form.values.autostartEnabled}
318+
// disabled if template does not allow autostart
319+
// or if primary feature is toggled off via the switch above
320+
disabled={
321+
isLoading ||
322+
!allowTemplateAutoStart ||
323+
!form.values.autostartEnabled
324+
}
315325
label={Language.startTimeLabel}
316326
type="time"
317327
fullWidth
318328
/>
319329
<TextField
320330
{...formHelpers("timezone")}
321-
disabled={isLoading || !form.values.autostartEnabled}
331+
// disabled if template does not allow autostart
332+
// or if primary feature is toggled off via the switch above
333+
disabled={
334+
isLoading ||
335+
!allowTemplateAutoStart ||
336+
!form.values.autostartEnabled
337+
}
322338
label={Language.timezoneLabel}
323339
select
324340
fullWidth
@@ -349,7 +365,15 @@ export const WorkspaceScheduleForm: FC<
349365
control={
350366
<Checkbox
351367
checked={checkbox.value}
352-
disabled={isLoading || !form.values.autostartEnabled}
368+
// template admins can disable the autostart feature in general,
369+
// or they can disallow autostart on specific days of the week.
370+
// also disabled if primary feature switch (above) is toggled off
371+
disabled={
372+
isLoading ||
373+
!allowTemplateAutoStart ||
374+
!allowedTemplateAutoStartDays.includes(checkbox.name) ||
375+
!form.values.autostartEnabled
376+
}
353377
onChange={form.handleChange}
354378
name={checkbox.name}
355379
size="small"
@@ -378,7 +402,7 @@ export const WorkspaceScheduleForm: FC<
378402
extended by 1 hour after last activity in the workspace was
379403
detected.
380404
</div>
381-
{!enableAutoStop && (
405+
{!allowTemplateAutoStop && (
382406
<Tooltip title="This option can be enabled in the template settings">
383407
<DisabledBadge />
384408
</Tooltip>
@@ -393,7 +417,7 @@ export const WorkspaceScheduleForm: FC<
393417
name="autostopEnabled"
394418
checked={form.values.autostopEnabled}
395419
onChange={handleToggleAutostop}
396-
disabled={!enableAutoStop}
420+
disabled={!allowTemplateAutoStop}
397421
/>
398422
}
399423
label={Language.stopSwitch}
@@ -403,15 +427,28 @@ export const WorkspaceScheduleForm: FC<
403427
helperText: ttlShutdownAt(form.values.ttl),
404428
backendFieldName: "ttl_ms",
405429
})}
406-
disabled={isLoading || !form.values.autostopEnabled}
430+
// disabled if autostop disabled at template level or
431+
// if autostop feature is toggled off via the switch above
432+
disabled={
433+
isLoading ||
434+
!allowTemplateAutoStop ||
435+
!form.values.autostopEnabled
436+
}
407437
inputProps={{ min: 0, step: "any" }}
408438
label={Language.ttlLabel}
409439
type="number"
410440
fullWidth
411441
/>
412442
</FormFields>
413443
</FormSection>
414-
<FormFooter onCancel={onCancel} isLoading={isLoading} />
444+
<FormFooter
445+
onCancel={onCancel}
446+
isLoading={isLoading}
447+
submitDisabled={
448+
(!allowTemplateAutoStart && !allowTemplateAutoStop) ||
449+
(!form.values.autostartEnabled && !form.values.autostopEnabled)
450+
}
451+
/>
415452
</HorizontalForm>
416453
);
417454
};

site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ export const WorkspaceSchedulePage: FC = () => {
5959
mutationFn: submitSchedule,
6060
onSuccess: async () => {
6161
await queryClient.invalidateQueries(
62-
workspaceByOwnerAndNameKey(params.username, params.workspace),
62+
workspaceByOwnerAndNameKey(
63+
params.username.replace(/^@/, ""),
64+
params.workspace,
65+
),
6366
);
6467
},
6568
});
@@ -98,8 +101,11 @@ export const WorkspaceSchedulePage: FC = () => {
98101

99102
{template && (
100103
<WorkspaceScheduleForm
101-
enableAutoStart={template.allow_user_autostart}
102-
enableAutoStop={template.allow_user_autostop}
104+
allowedTemplateAutoStartDays={
105+
template.autostart_requirement.days_of_week
106+
}
107+
allowTemplateAutoStart={template.allow_user_autostart}
108+
allowTemplateAutoStop={template.allow_user_autostop}
103109
submitScheduleError={submitScheduleMutation.error}
104110
initialValues={{
105111
...getAutostart(workspace),

0 commit comments

Comments
 (0)