diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.stories.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.stories.tsx index 6b643fd911bff..ec028498053b1 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.stories.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.stories.tsx @@ -19,8 +19,17 @@ const meta: Meta = { title: "pages/WorkspaceSettingsPage/WorkspaceScheduleForm", component: WorkspaceScheduleForm, args: { - enableAutoStart: true, - enableAutoStop: true, + allowTemplateAutoStart: true, + allowTemplateAutoStop: true, + allowedTemplateAutoStartDays: [ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + ], }, }; @@ -42,8 +51,8 @@ export const AllDisabled: Story = { autostopEnabled: false, ttl: emptyTTL, }, - enableAutoStart: false, - enableAutoStop: false, + allowTemplateAutoStart: false, + allowTemplateAutoStop: false, }, }; @@ -55,7 +64,7 @@ export const Autostart: Story = { autostopEnabled: false, ttl: emptyTTL, }, - enableAutoStop: false, + allowTemplateAutoStop: false, }, }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.ts b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.tsx similarity index 60% rename from site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.ts rename to site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.tsx index 5fdd6616357f9..e43a8617d1dcc 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.ts +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.tsx @@ -3,8 +3,14 @@ import { ttlShutdownAt, validationSchema, WorkspaceScheduleFormValues, + WorkspaceScheduleForm, } from "./WorkspaceScheduleForm"; import { timeZones } from "utils/timeZones"; +import * as API from "api/api"; +import { MockTemplate } from "testHelpers/entities"; +import { render } from "testHelpers/renderHelpers"; +import { defaultSchedule } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule"; +import { screen } from "@testing-library/react"; const valid: WorkspaceScheduleFormValues = { autostartEnabled: true, @@ -237,3 +243,139 @@ describe("ttlShutdownAt", () => { expect(ttlShutdownAt(ttlHours)).toEqual(expected); }); }); + +const autoStartDayLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const defaultFormProps = { + submitScheduleError: "", + initialValues: { + ...defaultSchedule(), + autostartEnabled: true, + autostopEnabled: true, + ttl: 24, + }, + isLoading: false, + defaultTTL: 24, + onCancel: () => null, + onSubmit: () => null, + allowedTemplateAutoStartDays: autoStartDayLabels, + allowTemplateAutoStart: true, + allowTemplateAutoStop: true, +}; + +describe("templateInheritance", () => { + it("disables the entire autostart feature appropriately", async () => { + jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate); + render( + , + ); + + const autoStartToggle = await screen.findByLabelText("Enable Autostart"); + expect(autoStartToggle).toBeDisabled(); + + const startTimeInput = await screen.findByLabelText("Start time"); + expect(startTimeInput).toBeDisabled(); + + const timezoneInput = await screen.findByLabelText("Timezone"); + // MUI's input is wrapped in a div so we look at the aria-attribute instead + expect(timezoneInput).toHaveAttribute("aria-disabled"); + + for (const label of autoStartDayLabels) { + const checkbox = await screen.findByLabelText(label); + expect(checkbox).toBeDisabled(); + } + }); + it("disables the autostart days of the week appropriately", async () => { + const enabledDayLabels = ["Sat", "Sun"]; + + jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate); + render( + , + ); + + const autoStartToggle = await screen.findByLabelText("Enable Autostart"); + expect(autoStartToggle).toBeEnabled(); + + const startTimeInput = await screen.findByLabelText("Start time"); + expect(startTimeInput).toBeEnabled(); + + const timezoneInput = await screen.findByLabelText("Timezone"); + // MUI's input is wrapped in a div so we look at the aria-attribute instead + expect(timezoneInput).not.toHaveAttribute("aria-disabled"); + + for (const label of enabledDayLabels) { + const checkbox = await screen.findByLabelText(label); + expect(checkbox).toBeEnabled(); + } + + for (const label of autoStartDayLabels.filter( + (day) => !enabledDayLabels.includes(day), + )) { + const checkbox = await screen.findByLabelText(label); + expect(checkbox).toBeDisabled(); + } + }); + it("disables the entire autostop feature appropriately", async () => { + jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate); + render( + , + ); + + const autoStopToggle = await screen.findByLabelText("Enable Autostop"); + expect(autoStopToggle).toBeDisabled(); + + const ttlInput = await screen.findByLabelText( + "Time until shutdown (hours)", + ); + expect(ttlInput).toBeDisabled(); + }); + it("disables secondary autostart fields if main feature switch is toggled off", async () => { + jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate); + render( + , + ); + + const startTimeInput = await screen.findByLabelText("Start time"); + expect(startTimeInput).toBeDisabled(); + + const timezoneInput = await screen.findByLabelText("Timezone"); + // MUI's input is wrapped in a div so we look at the aria-attribute instead + expect(timezoneInput).toHaveAttribute("aria-disabled"); + + autoStartDayLabels.forEach(async (label) => { + const checkbox = await screen.findByLabelText(label); + expect(checkbox).toBeDisabled(); + }); + }); + it("disables secondary autostop fields if main feature switch is toggled off", async () => { + jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate); + render( + , + ); + + const ttlInput = await screen.findByLabelText( + "Time until shutdown (hours)", + ); + expect(ttlInput).toBeDisabled(); + }); +}); diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.tsx index fc72ffb3089e1..21120df0e62e9 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.tsx @@ -32,6 +32,8 @@ import { timeZones } from "utils/timeZones"; import Tooltip from "@mui/material/Tooltip"; import { formatDuration, intervalToDuration } from "date-fns"; import { DisabledBadge } from "components/Badges/Badges"; +import { TemplateAutostartRequirement } from "api/typesGenerated"; +import { PropsWithChildren } from "react"; // REMARK: some plugins depend on utc, so it's listed first. Otherwise they're // sorted alphabetically. @@ -73,8 +75,9 @@ export interface WorkspaceScheduleFormProps { submitScheduleError?: unknown; initialValues: WorkspaceScheduleFormValues; isLoading: boolean; - enableAutoStop: boolean; - enableAutoStart: boolean; + allowedTemplateAutoStartDays: TemplateAutostartRequirement["days_of_week"]; + allowTemplateAutoStop: boolean; + allowTemplateAutoStart: boolean; onCancel: () => void; onSubmit: (values: WorkspaceScheduleFormValues) => void; // for storybook @@ -182,7 +185,7 @@ export const validationSchema = Yup.object({ }); export const WorkspaceScheduleForm: FC< - React.PropsWithChildren + PropsWithChildren > = ({ submitScheduleError, initialValues, @@ -191,8 +194,9 @@ export const WorkspaceScheduleForm: FC< onSubmit, initialTouched, defaultTTL, - enableAutoStop, - enableAutoStart, + allowedTemplateAutoStartDays, + allowTemplateAutoStop, + allowTemplateAutoStart, }) => { const form = useFormik({ initialValues, @@ -207,11 +211,6 @@ export const WorkspaceScheduleForm: FC< ); const checkboxes: Array<{ value: boolean; name: string; label: string }> = [ - { - value: form.values.sunday, - name: "sunday", - label: Language.daySundayLabel, - }, { value: form.values.monday, name: "monday", @@ -242,6 +241,11 @@ export const WorkspaceScheduleForm: FC< name: "saturday", label: Language.daySaturdayLabel, }, + { + value: form.values.sunday, + name: "sunday", + label: Language.daySundayLabel, + }, ]; const handleToggleAutostart = async (e: ChangeEvent) => { @@ -288,7 +292,7 @@ export const WorkspaceScheduleForm: FC< Select the time and days of week on which you want the workspace starting automatically. - {!enableAutoStart && ( + {!allowTemplateAutoStart && ( @@ -300,7 +304,7 @@ export const WorkspaceScheduleForm: FC< - {!enableAutoStop && ( + {!allowTemplateAutoStop && ( @@ -393,7 +417,7 @@ export const WorkspaceScheduleForm: FC< name="autostopEnabled" checked={form.values.autostopEnabled} onChange={handleToggleAutostop} - disabled={!enableAutoStop} + disabled={!allowTemplateAutoStop} /> } label={Language.stopSwitch} @@ -403,7 +427,13 @@ export const WorkspaceScheduleForm: FC< helperText: ttlShutdownAt(form.values.ttl), backendFieldName: "ttl_ms", })} - disabled={isLoading || !form.values.autostopEnabled} + // disabled if autostop disabled at template level or + // if autostop feature is toggled off via the switch above + disabled={ + isLoading || + !allowTemplateAutoStop || + !form.values.autostopEnabled + } inputProps={{ min: 0, step: "any" }} label={Language.ttlLabel} type="number" @@ -411,7 +441,14 @@ export const WorkspaceScheduleForm: FC< /> - + ); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index b54edcf437e61..2c1eefc545aa0 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -59,7 +59,10 @@ export const WorkspaceSchedulePage: FC = () => { mutationFn: submitSchedule, onSuccess: async () => { await queryClient.invalidateQueries( - workspaceByOwnerAndNameKey(params.username, params.workspace), + workspaceByOwnerAndNameKey( + params.username.replace(/^@/, ""), + params.workspace, + ), ); }, }); @@ -98,8 +101,11 @@ export const WorkspaceSchedulePage: FC = () => { {template && (