From 54bf130ed51418a3012674e14c4a0cb837bd5734 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 29 Jul 2022 19:59:20 +0000 Subject: [PATCH 01/26] Add elements --- site/src/components/Section/Section.tsx | 4 ++-- .../WorkspaceScheduleForm.tsx | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/site/src/components/Section/Section.tsx b/site/src/components/Section/Section.tsx index 9e2b993ed38d7..7cdb7efa85d72 100644 --- a/site/src/components/Section/Section.tsx +++ b/site/src/components/Section/Section.tsx @@ -30,7 +30,7 @@ export const Section: SectionFC = ({ }) => { const styles = useStyles({ layout }) return ( -
+
{(title || description) && (
@@ -49,7 +49,7 @@ export const Section: SectionFC = ({ {alert &&
{alert}
} {children}
-
+
) } diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 6eb500550ff38..dbc88319cf3df 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -6,8 +6,10 @@ import FormHelperText from "@material-ui/core/FormHelperText" import FormLabel from "@material-ui/core/FormLabel" import MenuItem from "@material-ui/core/MenuItem" import makeStyles from "@material-ui/core/styles/makeStyles" +import Switch from "@material-ui/core/Switch" import TextField from "@material-ui/core/TextField" import { ErrorSummary } from "components/ErrorSummary/ErrorSummary" +import { Section } from "components/Section/Section" import dayjs from "dayjs" import advancedFormat from "dayjs/plugin/advancedFormat" import duration from "dayjs/plugin/duration" @@ -215,6 +217,11 @@ export const WorkspaceScheduleForm: FC = ({
{submitScheduleError && } +
+ } + label="Auto-start" + /> = ({ {form.errors.monday && {Language.errorNoDayOfWeek}} +
+
+ } + label="Auto-Stop" + /> = ({ label={Language.ttlLabel} type="number" /> +
From ecc113705f27be0eaffac8e1958ce47927888193 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 29 Jul 2022 21:01:29 +0000 Subject: [PATCH 02/26] Add Loading story --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index cb24e1316dc5e..811a616c4791f 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -77,3 +77,6 @@ WithError.args = { validations: [{ field: "ttl_ms", detail: "Invalid time until shutdown." }], }), } + +export const Loading = Template.bind({}) +Loading.args = { isLoading: true } From 67050760018851326509d8ff388e2056c47d45a4 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 29 Jul 2022 21:52:55 +0000 Subject: [PATCH 03/26] Make form show empty values when manual --- .../WorkspaceScheduleForm.tsx | 17 ++++++++ .../WorkspaceSchedulePage.test.tsx | 39 ++++++++++++++----- .../WorkspaceSchedulePage.tsx | 7 ++-- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index dbc88319cf3df..273ba64319a5c 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -164,6 +164,23 @@ export const validationSchema = Yup.object({ export const defaultWorkspaceScheduleTTL = 8 +export const emptyWorkspaceSchedule = ( + ttl = 0, + timezone = dayjs.tz.guess(), +): WorkspaceScheduleFormValues => ({ + sunday: false, + monday: false, + tuesday: false, + wednesday: false, + thursday: false, + friday: false, + saturday: false, + + startTime: "", + timezone, + ttl, +}) + export const defaultWorkspaceSchedule = ( ttl = defaultWorkspaceScheduleTTL, timezone = dayjs.tz.guess(), diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 1f440e18eea5c..61ac5ddfc7f0d 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -166,15 +166,15 @@ describe("WorkspaceSchedulePage", () => { }, { sunday: false, - monday: true, - tuesday: true, - wednesday: true, - thursday: true, - friday: true, + monday: false, + tuesday: false, + wednesday: false, + thursday: false, + friday: false, saturday: false, - startTime: "09:30", + startTime: "", timezone: "", - ttl: 8, + ttl: 0, }, ], @@ -185,6 +185,27 @@ describe("WorkspaceSchedulePage", () => { autostart_schedule: "", ttl_ms: 7_200_000, }, + { + sunday: false, + monday: false, + tuesday: false, + wednesday: false, + thursday: false, + friday: false, + saturday: false, + startTime: "", + timezone: "", + ttl: 2, + }, + ], + + // start schedule only + [ + { + ...Mocks.MockWorkspace, + autostart_schedule: "CRON_TZ=UTC 30 9 * * 1-5", + ttl_ms: undefined, + }, { sunday: false, monday: true, @@ -194,8 +215,8 @@ describe("WorkspaceSchedulePage", () => { friday: true, saturday: false, startTime: "09:30", - timezone: "", - ttl: 2, + timezone: "UTC", + ttl: 0, }, ], diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index 3e74c1e17a6ad..0477fc9347b07 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -9,8 +9,7 @@ import * as TypesGen from "../../api/typesGenerated" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" import { - defaultWorkspaceSchedule, - defaultWorkspaceScheduleTTL, + emptyWorkspaceSchedule, WorkspaceScheduleForm, WorkspaceScheduleFormValues, } from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" @@ -111,10 +110,10 @@ export const workspaceToInitialValues = ( const schedule = workspace.autostart_schedule const ttlHours = workspace.ttl_ms ? Math.round(workspace.ttl_ms / (1000 * 60 * 60)) - : defaultWorkspaceScheduleTTL + : 0 if (!schedule) { - return defaultWorkspaceSchedule(ttlHours, defaultTimeZone) + return emptyWorkspaceSchedule(ttlHours, defaultTimeZone) } const timezone = extractTimezone(schedule, defaultTimeZone) From 91259c4fc7b7d05f78155b11b993aeaebf1d205e Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 1 Aug 2022 20:54:38 +0000 Subject: [PATCH 04/26] Make form depend on switches --- .../WorkspaceScheduleForm.tsx | 62 +++------ .../WorkspaceSchedulePage.tsx | 118 ++++++++++-------- site/src/pages/WorkspacesPage/schedule.ts | 93 ++++++++++++++ site/src/pages/WorkspacesPage/ttl.ts | 14 +++ 4 files changed, 189 insertions(+), 98 deletions(-) create mode 100644 site/src/pages/WorkspacesPage/schedule.ts create mode 100644 site/src/pages/WorkspacesPage/ttl.ts diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 273ba64319a5c..96924398dfa82 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -17,6 +17,8 @@ import relativeTime from "dayjs/plugin/relativeTime" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" import { FormikTouched, useFormik } from "formik" +import { AutoStart } from "pages/WorkspacesPage/schedule" +import { AutoStop } from "pages/WorkspacesPage/ttl" import { FC } from "react" import * as Yup from "yup" import { getFormHelpersWithError } from "../../util/formUtils" @@ -57,7 +59,10 @@ export const Language = { export interface WorkspaceScheduleFormProps { submitScheduleError?: Error | unknown - initialValues?: WorkspaceScheduleFormValues + autoStart: AutoStart + toggleAutoStart: () => void + autoStop: AutoStop + toggleAutoStop: () => void isLoading: boolean onCancel: () => void onSubmit: (values: WorkspaceScheduleFormValues) => void @@ -162,54 +167,23 @@ export const validationSchema = Yup.object({ .max(24 * 7 /* 7 days */), }) -export const defaultWorkspaceScheduleTTL = 8 - -export const emptyWorkspaceSchedule = ( - ttl = 0, - timezone = dayjs.tz.guess(), -): WorkspaceScheduleFormValues => ({ - sunday: false, - monday: false, - tuesday: false, - wednesday: false, - thursday: false, - friday: false, - saturday: false, - - startTime: "", - timezone, - ttl, -}) - -export const defaultWorkspaceSchedule = ( - ttl = defaultWorkspaceScheduleTTL, - timezone = dayjs.tz.guess(), -): WorkspaceScheduleFormValues => ({ - sunday: false, - monday: true, - tuesday: true, - wednesday: true, - thursday: true, - friday: true, - saturday: false, - - startTime: "09:30", - timezone, - ttl, -}) - export const WorkspaceScheduleForm: FC = ({ submitScheduleError, - initialValues = defaultWorkspaceSchedule(), + autoStart, + toggleAutoStart, + autoStop, + toggleAutoStop, isLoading, onCancel, onSubmit, initialTouched, }) => { const styles = useStyles() + const initialValues = { ...autoStart.schedule, ttl: autoStop.ttl } const form = useFormik({ initialValues, + enableReinitialize: true, onSubmit, validationSchema, initialTouched, @@ -236,12 +210,12 @@ export const WorkspaceScheduleForm: FC = ({ {submitScheduleError && }
} + control={} label="Auto-start" /> = ({ = ({ control={ = ({
} + control={} label="Auto-Stop" /> { - const schedule = workspace.autostart_schedule - const ttlHours = workspace.ttl_ms - ? Math.round(workspace.ttl_ms / (1000 * 60 * 60)) - : 0 - - if (!schedule) { - return emptyWorkspaceSchedule(ttlHours, defaultTimeZone) - } - - const timezone = extractTimezone(schedule, defaultTimeZone) - - const expression = cronParser.parseExpression(stripTimezone(schedule)) - - const HH = expression.fields.hour.join("").padStart(2, "0") - const mm = expression.fields.minute.join("").padStart(2, "0") - - const weeklyFlags = [false, false, false, false, false, false, false] - - for (const day of expression.fields.dayOfWeek) { - weeklyFlags[day % 7] = true - } - - return { - sunday: weeklyFlags[0], - monday: weeklyFlags[1], - tuesday: weeklyFlags[2], - wednesday: weeklyFlags[3], - thursday: weeklyFlags[4], - friday: weeklyFlags[5], - saturday: weeklyFlags[6], - startTime: `${HH}:${mm}`, - timezone, - ttl: ttlHours, - } -} - export const WorkspaceSchedulePage: React.FC = () => { const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() const navigate = useNavigate() @@ -166,6 +116,63 @@ export const WorkspaceSchedulePage: React.FC = () => { username && workspaceName && scheduleSend({ type: "GET_WORKSPACE", username, workspaceName }) }, [username, workspaceName, scheduleSend]) + const getAutoStart = (workspace?: TypesGen.Workspace) => scheduleToAutoStart(workspace?.autostart_schedule) + const getAutoStop = (workspace?: TypesGen.Workspace) => ttlMsToAutoStop(workspace?.ttl_ms) + + const [autoStart, setAutoStart] = useState(getAutoStart(workspace)) + const [autoStop, setAutoStop] = useState(getAutoStop(workspace)) + + useEffect(() => { + setAutoStart(getAutoStart(workspace)) + setAutoStop(getAutoStop(workspace)) + }, [workspace]) + + const onToggleAutoStart = () => { + if (autoStart.enabled) { + setAutoStart({ + enabled: false, + schedule: emptySchedule + }) + } else { + if (workspace?.autostart_schedule) { + // repopulate saved schedule + setAutoStart({ + enabled: true, + schedule: getAutoStart(workspace).schedule + }) + } else { + // populate with defaults + setAutoStart({ + enabled: true, + schedule: defaultSchedule() + }) + } + } + } + + const onToggleAutoStop = () => { + if (autoStop.enabled) { + setAutoStop({ + enabled: false, + ttl: emptyTTL + }) + } else { + if (workspace?.ttl_ms) { + // repopulate saved ttl + setAutoStop({ + enabled: true, + ttl: getAutoStop(workspace).ttl + }) + } else { + // set default + setAutoStop({ + enabled: true, + ttl: defaultTTL + }) + } + } + } + if (!username || !workspaceName) { navigate("/workspaces") return null @@ -200,7 +207,10 @@ export const WorkspaceSchedulePage: React.FC = () => { return ( { navigate(`/@${username}/${workspaceName}`) diff --git a/site/src/pages/WorkspacesPage/schedule.ts b/site/src/pages/WorkspacesPage/schedule.ts new file mode 100644 index 0000000000000..d39d261ef7711 --- /dev/null +++ b/site/src/pages/WorkspacesPage/schedule.ts @@ -0,0 +1,93 @@ +import * as cronParser from "cron-parser" +import dayjs from "dayjs" +import timezone from "dayjs/plugin/timezone" +import utc from "dayjs/plugin/utc" +import { extractTimezone, stripTimezone } from "../../util/schedule" + +// REMARK: timezone plugin depends on UTC +// +// SEE: https://day.js.org/docs/en/timezone/timezone +dayjs.extend(utc) +dayjs.extend(timezone) + +export interface AutoStartSchedule { + sunday: boolean + monday: boolean + tuesday: boolean + wednesday: boolean + thursday: boolean + friday: boolean + saturday: boolean + startTime: string + timezone: string +} + +export interface AutoStart { + enabled: boolean + schedule: AutoStartSchedule +} + +export const emptySchedule = { + sunday: false, + monday: false, + tuesday: false, + wednesday: false, + thursday: false, + friday: false, + saturday: false, + + startTime: "", + timezone: "", +} + +export const defaultSchedule = ( +): AutoStartSchedule => ({ + sunday: false, + monday: true, + tuesday: true, + wednesday: true, + thursday: true, + friday: true, + saturday: false, + + startTime: "09:30", + timezone: dayjs.tz.guess(), +}) + +const transformSchedule = (schedule: string) => { + const timezone = extractTimezone(schedule, dayjs.tz.guess()) + + const expression = cronParser.parseExpression(stripTimezone(schedule)) + + const HH = expression.fields.hour.join("").padStart(2, "0") + const mm = expression.fields.minute.join("").padStart(2, "0") + + const weeklyFlags = [false, false, false, false, false, false, false] + + for (const day of expression.fields.dayOfWeek) { + weeklyFlags[day % 7] = true + } + + return { + sunday: weeklyFlags[0], + monday: weeklyFlags[1], + tuesday: weeklyFlags[2], + wednesday: weeklyFlags[3], + thursday: weeklyFlags[4], + friday: weeklyFlags[5], + saturday: weeklyFlags[6], + startTime: `${HH}:${mm}`, + timezone, + } +} + +export const scheduleToAutoStart = (schedule?: string): AutoStart => { + if (schedule) { + return { + enabled: true, + schedule: transformSchedule(schedule) + } + } else { + return { enabled: false, schedule: emptySchedule } + } +} diff --git a/site/src/pages/WorkspacesPage/ttl.ts b/site/src/pages/WorkspacesPage/ttl.ts new file mode 100644 index 0000000000000..48459eeffb4e7 --- /dev/null +++ b/site/src/pages/WorkspacesPage/ttl.ts @@ -0,0 +1,14 @@ +export interface AutoStop { + enabled: boolean + ttl: number +} + +export const emptyTTL = 0 + +export const defaultTTL = 8 + +const msToHours = (ms: number) => Math.round(ms / (1000 * 60 * 60)) + +export const ttlMsToAutoStop = (ttl_ms?: number): AutoStop => ( + ttl_ms ? { enabled: true, ttl: msToHours(ttl_ms) } : { enabled: false, ttl: 0 } +) From 70228bd14b71ed60641e6f81982add66170caedd Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 1 Aug 2022 20:55:55 +0000 Subject: [PATCH 05/26] Fix style --- .../components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 96924398dfa82..8186cf352559d 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -221,6 +221,7 @@ export const WorkspaceScheduleForm: FC = ({ }} label={Language.startTimeLabel} type="time" + fullWidth /> = ({ }} label={Language.timezoneLabel} select + fullWidth > {zones.map((zone) => ( @@ -279,6 +281,7 @@ export const WorkspaceScheduleForm: FC = ({ inputProps={{ min: 0, step: 1 }} label={Language.ttlLabel} type="number" + fullWidth />
From 52b26bf421de05ac6a2b025e401ddfd979f541ab Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 1 Aug 2022 20:56:15 +0000 Subject: [PATCH 06/26] Format --- .../WorkspaceScheduleForm.tsx | 134 +++++++++--------- .../WorkspaceSchedulePage.tsx | 17 +-- site/src/pages/WorkspacesPage/schedule.ts | 5 +- site/src/pages/WorkspacesPage/ttl.ts | 3 +- 4 files changed, 79 insertions(+), 80 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 8186cf352559d..b63ed4edfbb37 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -209,80 +209,80 @@ export const WorkspaceScheduleForm: FC = ({ {submitScheduleError && }
- } - label="Auto-start" - /> - + } + label="Auto-start" + /> + - - {zones.map((zone) => ( - - {zone} - - ))} - + + {zones.map((zone) => ( + + {zone} + + ))} + - - - {Language.daysOfWeekLabel} - + + + {Language.daysOfWeekLabel} + - - {checkboxes.map((checkbox) => ( - - } - key={checkbox.name} - label={checkbox.label} - /> - ))} - + + {checkboxes.map((checkbox) => ( + + } + key={checkbox.name} + label={checkbox.label} + /> + ))} + - {form.errors.monday && {Language.errorNoDayOfWeek}} - + {form.errors.monday && {Language.errorNoDayOfWeek}} +
- } - label="Auto-Stop" - /> - + } + label="Auto-Stop" + /> +
diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index 70bf14848caf2..bc3a113b94ef5 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -1,6 +1,6 @@ import { useMachine, useSelector } from "@xstate/react" import { defaultSchedule, emptySchedule, scheduleToAutoStart } from "pages/WorkspacesPage/schedule" -import { ttlMsToAutoStop, emptyTTL, defaultTTL } from "pages/WorkspacesPage/ttl" +import { defaultTTL, emptyTTL, ttlMsToAutoStop } from "pages/WorkspacesPage/ttl" import React, { useContext, useEffect, useState } from "react" import { useNavigate, useParams } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" @@ -116,7 +116,8 @@ export const WorkspaceSchedulePage: React.FC = () => { username && workspaceName && scheduleSend({ type: "GET_WORKSPACE", username, workspaceName }) }, [username, workspaceName, scheduleSend]) - const getAutoStart = (workspace?: TypesGen.Workspace) => scheduleToAutoStart(workspace?.autostart_schedule) + const getAutoStart = (workspace?: TypesGen.Workspace) => + scheduleToAutoStart(workspace?.autostart_schedule) const getAutoStop = (workspace?: TypesGen.Workspace) => ttlMsToAutoStop(workspace?.ttl_ms) const [autoStart, setAutoStart] = useState(getAutoStart(workspace)) @@ -131,20 +132,20 @@ export const WorkspaceSchedulePage: React.FC = () => { if (autoStart.enabled) { setAutoStart({ enabled: false, - schedule: emptySchedule + schedule: emptySchedule, }) } else { if (workspace?.autostart_schedule) { // repopulate saved schedule setAutoStart({ enabled: true, - schedule: getAutoStart(workspace).schedule + schedule: getAutoStart(workspace).schedule, }) } else { // populate with defaults setAutoStart({ enabled: true, - schedule: defaultSchedule() + schedule: defaultSchedule(), }) } } @@ -154,20 +155,20 @@ export const WorkspaceSchedulePage: React.FC = () => { if (autoStop.enabled) { setAutoStop({ enabled: false, - ttl: emptyTTL + ttl: emptyTTL, }) } else { if (workspace?.ttl_ms) { // repopulate saved ttl setAutoStop({ enabled: true, - ttl: getAutoStop(workspace).ttl + ttl: getAutoStop(workspace).ttl, }) } else { // set default setAutoStop({ enabled: true, - ttl: defaultTTL + ttl: defaultTTL, }) } } diff --git a/site/src/pages/WorkspacesPage/schedule.ts b/site/src/pages/WorkspacesPage/schedule.ts index d39d261ef7711..6518872cb68f9 100644 --- a/site/src/pages/WorkspacesPage/schedule.ts +++ b/site/src/pages/WorkspacesPage/schedule.ts @@ -40,8 +40,7 @@ export const emptySchedule = { timezone: "", } -export const defaultSchedule = ( -): AutoStartSchedule => ({ +export const defaultSchedule = (): AutoStartSchedule => ({ sunday: false, monday: true, tuesday: true, @@ -85,7 +84,7 @@ export const scheduleToAutoStart = (schedule?: string): AutoStart => { if (schedule) { return { enabled: true, - schedule: transformSchedule(schedule) + schedule: transformSchedule(schedule), } } else { return { enabled: false, schedule: emptySchedule } diff --git a/site/src/pages/WorkspacesPage/ttl.ts b/site/src/pages/WorkspacesPage/ttl.ts index 48459eeffb4e7..4035ab09edeb9 100644 --- a/site/src/pages/WorkspacesPage/ttl.ts +++ b/site/src/pages/WorkspacesPage/ttl.ts @@ -9,6 +9,5 @@ export const defaultTTL = 8 const msToHours = (ms: number) => Math.round(ms / (1000 * 60 * 60)) -export const ttlMsToAutoStop = (ttl_ms?: number): AutoStop => ( +export const ttlMsToAutoStop = (ttl_ms?: number): AutoStop => ttl_ms ? { enabled: true, ttl: msToHours(ttl_ms) } : { enabled: false, ttl: 0 } -) From b22425865dbe967f88ba2e55eba04758caecd33f Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 1 Aug 2022 21:14:58 +0000 Subject: [PATCH 07/26] Update unit tests --- .../WorkspaceSchedulePage.test.tsx | 167 +++++++----------- 1 file changed, 63 insertions(+), 104 deletions(-) diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 61ac5ddfc7f0d..29f564fce6274 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -1,11 +1,8 @@ +import { AutoStart, scheduleToAutoStart } from "pages/WorkspacesPage/schedule" +import { AutoStop, ttlMsToAutoStop } from "pages/WorkspacesPage/ttl" import * as TypesGen from "../../api/typesGenerated" import { WorkspaceScheduleFormValues } from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" -import * as Mocks from "../../testHelpers/entities" -import { - formValuesToAutoStartRequest, - formValuesToTTLRequest, - workspaceToInitialValues, -} from "./WorkspaceSchedulePage" +import { formValuesToAutoStartRequest, formValuesToTTLRequest } from "./WorkspaceSchedulePage" const validValues: WorkspaceScheduleFormValues = { sunday: false, @@ -155,117 +152,79 @@ describe("WorkspaceSchedulePage", () => { }) }) - describe("workspaceToInitialValues", () => { - it.each<[TypesGen.Workspace, WorkspaceScheduleFormValues]>([ + describe("scheduleToAutoStart", () => { + it.each<[string | undefined, AutoStart]>([ // Empty case [ - { - ...Mocks.MockWorkspace, - autostart_schedule: undefined, - ttl_ms: undefined, - }, - { - sunday: false, - monday: false, - tuesday: false, - wednesday: false, - thursday: false, - friday: false, - saturday: false, - startTime: "", - timezone: "", - ttl: 0, + undefined, + { + enabled: false, + schedule: { + sunday: false, + monday: false, + tuesday: false, + wednesday: false, + thursday: false, + friday: false, + saturday: false, + startTime: "", + timezone: "", + }, }, ], - // ttl-only case (2 hours) + // Basic case: 9:30 1-5 UTC [ - { - ...Mocks.MockWorkspace, - autostart_schedule: "", - ttl_ms: 7_200_000, - }, - { - sunday: false, - monday: false, - tuesday: false, - wednesday: false, - thursday: false, - friday: false, - saturday: false, - startTime: "", - timezone: "", - ttl: 2, + "CRON_TZ=UTC 30 9 * * 1-5", + { + enabled: true, + schedule: { + sunday: false, + monday: true, + tuesday: true, + wednesday: true, + thursday: true, + friday: true, + saturday: false, + startTime: "09:30", + timezone: "UTC", + }, }, ], - // start schedule only + // Complex case: 4:20 1 3-4 6 Canada/Eastern [ - { - ...Mocks.MockWorkspace, - autostart_schedule: "CRON_TZ=UTC 30 9 * * 1-5", - ttl_ms: undefined, - }, - { - sunday: false, - monday: true, - tuesday: true, - wednesday: true, - thursday: true, - friday: true, - saturday: false, - startTime: "09:30", - timezone: "UTC", - ttl: 0, - }, - ], - - // Basic case: 9:30 1-5 UTC running for 2 hours - // - // NOTE: We have to set CRON_TZ here because otherwise this test will - // flake based off of where it runs! - [ - { - ...Mocks.MockWorkspace, - autostart_schedule: "CRON_TZ=UTC 30 9 * * 1-5", - ttl_ms: 7_200_000, - }, - { - sunday: false, - monday: true, - tuesday: true, - wednesday: true, - thursday: true, - friday: true, - saturday: false, - startTime: "09:30", - timezone: "UTC", - ttl: 2, + "CRON_TZ=Canada/Eastern 20 16 * * 1,3-4,6", + { + enabled: true, + schedule: { + sunday: false, + monday: true, + tuesday: false, + wednesday: true, + thursday: true, + friday: false, + saturday: true, + startTime: "16:20", + timezone: "Canada/Eastern", + }, }, ], + ])(`scheduleToAutoStart(%p) returns %p`, (schedule, autoStart) => { + expect(scheduleToAutoStart(schedule)).toEqual(autoStart) + }) + }) - // Complex case: 4:20 1 3-4 6 Canada/Eastern for 8 hours - [ - { - ...Mocks.MockWorkspace, - autostart_schedule: "CRON_TZ=Canada/Eastern 20 16 * * 1,3-4,6", - ttl_ms: 28_800_000, - }, - { - sunday: false, - monday: true, - tuesday: false, - wednesday: true, - thursday: true, - friday: false, - saturday: true, - startTime: "16:20", - timezone: "Canada/Eastern", - ttl: 8, - }, - ], - ])(`workspaceToInitialValues(%p) returns %p`, (workspace, formValues) => { - expect(workspaceToInitialValues(workspace)).toEqual(formValues) + describe("ttlMsToAutoStop", () => { + it.each<[number | undefined, AutoStop]>([ + // empty case + [undefined, { enabled: false, ttl: 0 }], + // zero + [0, { enabled: false, ttl: 0 }], + // basic case + [28_800_000, { enabled: true, ttl: 8 }], + ])(`ttlMsToAutoStop(%p) returns %p`, (ttlMs, autoStop) => { + expect(ttlMsToAutoStop(ttlMs)).toEqual(autoStop) }) }) }) From 4f68bb16e00c94b1a4a3944450f5e896eedbd736 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 1 Aug 2022 22:25:49 +0000 Subject: [PATCH 08/26] Tweaks --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 2 +- .../WorkspaceSchedulePage/WorkspaceSchedulePage.tsx | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index b63ed4edfbb37..d9699ff56d8e4 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -273,7 +273,7 @@ export const WorkspaceScheduleForm: FC = ({
} - label="Auto-Stop" + label="Auto-stop" /> { } if (!username || !workspaceName) { - navigate("/workspaces") - return null + return } if ( @@ -228,12 +227,10 @@ export const WorkspaceSchedulePage: React.FC = () => { } if (scheduleState.matches("submitSuccess")) { - navigate(`/@${username}/${workspaceName}`) - return + return } // Theoretically impossible - log and bail console.error("WorkspaceSchedulePage: unknown state :: ", scheduleState) - navigate("/") - return null + return } From ff48adf8a1daef0c490982999894f77d3e35dc5f Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 2 Aug 2022 14:43:21 +0000 Subject: [PATCH 09/26] Update storybook --- .../WorkspaceScheduleForm.stories.tsx | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index 811a616c4791f..e6e2e9613717a 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -3,12 +3,10 @@ import dayjs from "dayjs" import advancedFormat from "dayjs/plugin/advancedFormat" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" +import { defaultSchedule, emptySchedule } from "pages/WorkspacesPage/schedule" +import { emptyTTL } from "pages/WorkspacesPage/ttl" import { makeMockApiError } from "testHelpers/entities" -import { - defaultWorkspaceSchedule, - WorkspaceScheduleForm, - WorkspaceScheduleFormProps, -} from "./WorkspaceScheduleForm" +import { WorkspaceScheduleForm, WorkspaceScheduleFormProps } from "./WorkspaceScheduleForm" dayjs.extend(advancedFormat) dayjs.extend(utc) @@ -24,53 +22,51 @@ export default { onSubmit: { action: "onSubmit", }, + toggleAutoStart: { + action: "toggleAutoStart", + }, + toggleAutoStop: { + action: "toggleAutoStop", + }, }, } const Template: Story = (args) => -export const WorkspaceWillNotShutDown = Template.bind({}) -WorkspaceWillNotShutDown.args = { - initialValues: { - ...defaultWorkspaceSchedule(5), - ttl: 0, - }, +export const AllDisabled = Template.bind({}) +AllDisabled.args = { + autoStart: { enabled: false, schedule: emptySchedule }, + autoStop: { enabled: false, ttl: emptyTTL }, } -export const WorkspaceWillShutdownInAnHour = Template.bind({}) -WorkspaceWillShutdownInAnHour.args = { - initialValues: { - ...defaultWorkspaceSchedule(5), - ttl: 1, - }, +export const AutoStart = Template.bind({}) +AutoStart.args = { + autoStart: { enabled: true, schedule: defaultSchedule() }, + autoStop: { enabled: false, ttl: emptyTTL }, } export const WorkspaceWillShutdownInTwoHours = Template.bind({}) WorkspaceWillShutdownInTwoHours.args = { - initialValues: { - ...defaultWorkspaceSchedule(2), - ttl: 2, - }, + autoStart: { enabled: true, schedule: defaultSchedule() }, + autoStop: { enabled: true, ttl: 2 }, } export const WorkspaceWillShutdownInADay = Template.bind({}) WorkspaceWillShutdownInADay.args = { - initialValues: { - ...defaultWorkspaceSchedule(2), - ttl: 24, - }, + autoStart: { enabled: true, schedule: defaultSchedule() }, + autoStop: { enabled: true, ttl: 24 }, } export const WorkspaceWillShutdownInTwoDays = Template.bind({}) WorkspaceWillShutdownInTwoDays.args = { - initialValues: { - ...defaultWorkspaceSchedule(2), - ttl: 48, - }, + autoStart: { enabled: true, schedule: defaultSchedule() }, + autoStop: { enabled: true, ttl: 48 }, } export const WithError = Template.bind({}) WithError.args = { + autoStart: { enabled: false, schedule: emptySchedule }, + autoStop: { enabled: true, ttl: 100 }, initialTouched: { ttl: true }, submitScheduleError: makeMockApiError({ message: "Something went wrong.", @@ -79,4 +75,8 @@ WithError.args = { } export const Loading = Template.bind({}) -Loading.args = { isLoading: true } +Loading.args = { + autoStart: { enabled: true, schedule: defaultSchedule() }, + autoStop: { enabled: true, ttl: 2 }, + isLoading: true, +} From bbaee7d04384f94724b2739f1cca0b59b174ddaa Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 8 Aug 2022 18:21:00 +0000 Subject: [PATCH 10/26] Move util files --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx | 4 ++-- .../WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 4 ++-- .../WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx | 4 ++-- .../src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx | 4 ++-- .../{WorkspacesPage => WorkspaceSchedulePage}/schedule.ts | 0 .../pages/{WorkspacesPage => WorkspaceSchedulePage}/ttl.ts | 0 6 files changed, 8 insertions(+), 8 deletions(-) rename site/src/pages/{WorkspacesPage => WorkspaceSchedulePage}/schedule.ts (100%) rename site/src/pages/{WorkspacesPage => WorkspaceSchedulePage}/ttl.ts (100%) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index e6e2e9613717a..e351f71c8cc74 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -3,8 +3,8 @@ import dayjs from "dayjs" import advancedFormat from "dayjs/plugin/advancedFormat" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" -import { defaultSchedule, emptySchedule } from "pages/WorkspacesPage/schedule" -import { emptyTTL } from "pages/WorkspacesPage/ttl" +import { defaultSchedule, emptySchedule } from "pages/WorkspaceSchedulePage/schedule" +import { emptyTTL } from "pages/WorkspaceSchedulePage/ttl" import { makeMockApiError } from "testHelpers/entities" import { WorkspaceScheduleForm, WorkspaceScheduleFormProps } from "./WorkspaceScheduleForm" diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index d9699ff56d8e4..3cbcc4603503f 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -17,8 +17,8 @@ import relativeTime from "dayjs/plugin/relativeTime" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" import { FormikTouched, useFormik } from "formik" -import { AutoStart } from "pages/WorkspacesPage/schedule" -import { AutoStop } from "pages/WorkspacesPage/ttl" +import { AutoStart } from "pages/WorkspaceSchedulePage/schedule" +import { AutoStop } from "pages/WorkspaceSchedulePage/ttl" import { FC } from "react" import * as Yup from "yup" import { getFormHelpersWithError } from "../../util/formUtils" diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 29f564fce6274..5d7aff3c95ed4 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -1,5 +1,5 @@ -import { AutoStart, scheduleToAutoStart } from "pages/WorkspacesPage/schedule" -import { AutoStop, ttlMsToAutoStop } from "pages/WorkspacesPage/ttl" +import { AutoStart, scheduleToAutoStart } from "pages/WorkspaceSchedulePage/schedule" +import { AutoStop, ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl" import * as TypesGen from "../../api/typesGenerated" import { WorkspaceScheduleFormValues } from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" import { formValuesToAutoStartRequest, formValuesToTTLRequest } from "./WorkspaceSchedulePage" diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index 6e807a7d76276..ef63bd4b84e8e 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -1,6 +1,6 @@ import { useMachine, useSelector } from "@xstate/react" -import { defaultSchedule, emptySchedule, scheduleToAutoStart } from "pages/WorkspacesPage/schedule" -import { defaultTTL, emptyTTL, ttlMsToAutoStop } from "pages/WorkspacesPage/ttl" +import { defaultSchedule, emptySchedule, scheduleToAutoStart } from "pages/WorkspaceSchedulePage/schedule" +import { defaultTTL, emptyTTL, ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl" import React, { useContext, useEffect, useState } from "react" import { Navigate, useNavigate, useParams } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" diff --git a/site/src/pages/WorkspacesPage/schedule.ts b/site/src/pages/WorkspaceSchedulePage/schedule.ts similarity index 100% rename from site/src/pages/WorkspacesPage/schedule.ts rename to site/src/pages/WorkspaceSchedulePage/schedule.ts diff --git a/site/src/pages/WorkspacesPage/ttl.ts b/site/src/pages/WorkspaceSchedulePage/ttl.ts similarity index 100% rename from site/src/pages/WorkspacesPage/ttl.ts rename to site/src/pages/WorkspaceSchedulePage/ttl.ts From f251efa758f9f3172dec0e4440378c2aff22c534 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 8 Aug 2022 18:24:40 +0000 Subject: [PATCH 11/26] Pull out more util functions --- .../WorkspaceSchedulePage.tsx | 84 ++----------------- .../WorkspaceSchedulePage/formToRequest.ts | 74 ++++++++++++++++ 2 files changed, 81 insertions(+), 77 deletions(-) create mode 100644 site/src/pages/WorkspaceSchedulePage/formToRequest.ts diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index ef63bd4b84e8e..a59b4b0804ebe 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -1,19 +1,21 @@ import { useMachine, useSelector } from "@xstate/react" -import { defaultSchedule, emptySchedule, scheduleToAutoStart } from "pages/WorkspaceSchedulePage/schedule" +import { + defaultSchedule, + emptySchedule, + scheduleToAutoStart, +} from "pages/WorkspaceSchedulePage/schedule" import { defaultTTL, emptyTTL, ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl" import React, { useContext, useEffect, useState } from "react" import { Navigate, useNavigate, useParams } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" -import { - WorkspaceScheduleForm, - WorkspaceScheduleFormValues, -} from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" +import { WorkspaceScheduleForm } from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" import { firstOrItem } from "../../util/array" import { selectUser } from "../../xServices/auth/authSelectors" import { XServiceContext } from "../../xServices/StateContext" import { workspaceSchedule } from "../../xServices/workspaceSchedule/workspaceScheduleXService" +import { formValuesToAutoStartRequest, formValuesToTTLRequest } from "./formToRequest" const Language = { forbiddenError: "You don't have permissions to update the schedule for this workspace.", @@ -21,78 +23,6 @@ const Language = { checkPermissionsError: "Failed to fetch permissions.", } -export const formValuesToAutoStartRequest = ( - values: WorkspaceScheduleFormValues, -): TypesGen.UpdateWorkspaceAutostartRequest => { - if (!values.startTime) { - return { - schedule: "", - } - } - - const [HH, mm] = values.startTime.split(":") - - // Note: Space after CRON_TZ if timezone is defined - const preparedTZ = values.timezone ? `CRON_TZ=${values.timezone} ` : "" - - const makeCronString = (dow: string) => `${preparedTZ}${mm} ${HH} * * ${dow}` - - const days = [ - values.sunday, - values.monday, - values.tuesday, - values.wednesday, - values.thursday, - values.friday, - values.saturday, - ] - - const isEveryDay = days.every((day) => day) - - const isMonThroughFri = - !values.sunday && - values.monday && - values.tuesday && - values.wednesday && - values.thursday && - values.friday && - !values.saturday && - !values.sunday - - // Handle special cases, falling through to comma-separation - if (isEveryDay) { - return { - schedule: makeCronString("*"), - } - } else if (isMonThroughFri) { - return { - schedule: makeCronString("1-5"), - } - } else { - const dow = days.reduce((previous, current, idx) => { - if (!current) { - return previous - } else { - const prefix = previous ? "," : "" - return previous + prefix + idx - } - }, "") - - return { - schedule: makeCronString(dow), - } - } -} - -export const formValuesToTTLRequest = ( - values: WorkspaceScheduleFormValues, -): TypesGen.UpdateWorkspaceTTLRequest => { - return { - // minutes to nanoseconds - ttl_ms: values.ttl ? values.ttl * 60 * 60 * 1000 : undefined, - } -} - export const WorkspaceSchedulePage: React.FC = () => { const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() const navigate = useNavigate() diff --git a/site/src/pages/WorkspaceSchedulePage/formToRequest.ts b/site/src/pages/WorkspaceSchedulePage/formToRequest.ts new file mode 100644 index 0000000000000..88dafe245d5b9 --- /dev/null +++ b/site/src/pages/WorkspaceSchedulePage/formToRequest.ts @@ -0,0 +1,74 @@ +import * as TypesGen from "api/typesGenerated" +import { WorkspaceScheduleFormValues } from "components/WorkspaceScheduleForm/WorkspaceScheduleForm" + +export const formValuesToAutoStartRequest = ( + values: WorkspaceScheduleFormValues, +): TypesGen.UpdateWorkspaceAutostartRequest => { + if (!values.startTime) { + return { + schedule: "", + } + } + + const [HH, mm] = values.startTime.split(":") + + // Note: Space after CRON_TZ if timezone is defined + const preparedTZ = values.timezone ? `CRON_TZ=${values.timezone} ` : "" + + const makeCronString = (dow: string) => `${preparedTZ}${mm} ${HH} * * ${dow}` + + const days = [ + values.sunday, + values.monday, + values.tuesday, + values.wednesday, + values.thursday, + values.friday, + values.saturday, + ] + + const isEveryDay = days.every((day) => day) + + const isMonThroughFri = + !values.sunday && + values.monday && + values.tuesday && + values.wednesday && + values.thursday && + values.friday && + !values.saturday && + !values.sunday + + // Handle special cases, falling through to comma-separation + if (isEveryDay) { + return { + schedule: makeCronString("*"), + } + } else if (isMonThroughFri) { + return { + schedule: makeCronString("1-5"), + } + } else { + const dow = days.reduce((previous, current, idx) => { + if (!current) { + return previous + } else { + const prefix = previous ? "," : "" + return previous + prefix + idx + } + }, "") + + return { + schedule: makeCronString(dow), + } + } +} + +export const formValuesToTTLRequest = ( + values: WorkspaceScheduleFormValues, +): TypesGen.UpdateWorkspaceTTLRequest => { + return { + // minutes to nanoseconds + ttl_ms: values.ttl ? values.ttl * 60 * 60 * 1000 : undefined, + } +} From eab6e5fc9ce5d53d48201a7dc89d05d1f210ebe3 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 8 Aug 2022 18:27:44 +0000 Subject: [PATCH 12/26] Pull out strings --- .../WorkspaceScheduleForm.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 3cbcc4603503f..c9f3e01789d1c 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -55,6 +55,11 @@ export const Language = { ttlCausesShutdownHelperText: "Your workspace will shut down", ttlCausesShutdownAfterStart: "after start", ttlCausesNoShutdownHelperText: "Your workspace will not automatically shut down.", + formTitle: "Workspace schedule", + startSection: "Start", + startSwitch: "Auto-start", + stopSection: "Stop", + stopSwitch: "Auto-stop" } export interface WorkspaceScheduleFormProps { @@ -204,14 +209,14 @@ export const WorkspaceScheduleForm: FC = ({ ] return ( - + {submitScheduleError && } -
+
} - label="Auto-start" + label={Language.startSwitch} /> = ({
-
+
} - label="Auto-stop" + label={Language.stopSwitch} /> Date: Mon, 8 Aug 2022 18:47:34 +0000 Subject: [PATCH 13/26] Add border to section --- site/src/components/Section/Section.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/components/Section/Section.tsx b/site/src/components/Section/Section.tsx index 7cdb7efa85d72..40e68161b75b6 100644 --- a/site/src/components/Section/Section.tsx +++ b/site/src/components/Section/Section.tsx @@ -63,6 +63,7 @@ const useStyles = makeStyles((theme) => ({ marginBottom: theme.spacing(1), padding: theme.spacing(6), borderRadius: theme.shape.borderRadius, + border: `1px solid ${theme.palette.divider}`, [theme.breakpoints.down("sm")]: { padding: theme.spacing(4, 3, 4, 3), From a35bf20160f6d40c4f7e64c2c3da8fe05e78d34f Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 8 Aug 2022 18:49:03 +0000 Subject: [PATCH 14/26] Make min ttl 1 --- .../components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index c9f3e01789d1c..4073e496b0d1c 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -168,7 +168,7 @@ export const validationSchema = Yup.object({ }), ttl: Yup.number() .integer() - .min(0) + .min(1) .max(24 * 7 /* 7 days */), }) From 750f08bee1a732a586a5cc6b547ce5df2c91c366 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 8 Aug 2022 18:49:32 +0000 Subject: [PATCH 15/26] Format --- .../components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 4073e496b0d1c..583ef04cef6cd 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -59,7 +59,7 @@ export const Language = { startSection: "Start", startSwitch: "Auto-start", stopSection: "Stop", - stopSwitch: "Auto-stop" + stopSwitch: "Auto-stop", } export interface WorkspaceScheduleFormProps { From 3dfa41baf4f6f19e46cbd3f320b5c5abf38a758c Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 8 Aug 2022 19:59:36 +0000 Subject: [PATCH 16/26] Fix import --- .../pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 5d7aff3c95ed4..66923e03db0d7 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -2,7 +2,7 @@ import { AutoStart, scheduleToAutoStart } from "pages/WorkspaceSchedulePage/sche import { AutoStop, ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl" import * as TypesGen from "../../api/typesGenerated" import { WorkspaceScheduleFormValues } from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" -import { formValuesToAutoStartRequest, formValuesToTTLRequest } from "./WorkspaceSchedulePage" +import { formValuesToAutoStartRequest, formValuesToTTLRequest } from "pages/WorkspaceSchedulePage/formToRequest" const validValues: WorkspaceScheduleFormValues = { sunday: false, From 848732a2851582f532fd0371c98a6c85a31a9a84 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 8 Aug 2022 22:51:23 +0000 Subject: [PATCH 17/26] Fix validation for falsey values --- .../WorkspaceScheduleForm.test.ts | 28 ++++++------- .../WorkspaceScheduleForm.tsx | 42 +++++++++---------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts index 0b08446f0fcc8..c3b2cf1f40e69 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts @@ -1,7 +1,7 @@ import { Language, ttlShutdownAt, - validationSchema, + getValidationSchema, WorkspaceScheduleFormValues, } from "./WorkspaceScheduleForm" import { zones } from "./zones" @@ -35,7 +35,7 @@ describe("validationSchema", () => { timezone: "", ttl: 0, } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).not.toThrow() }) @@ -44,7 +44,7 @@ describe("validationSchema", () => { ...valid, ttl: -1, } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrow() }) @@ -59,7 +59,7 @@ describe("validationSchema", () => { friday: false, saturday: false, } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError(Language.errorNoDayOfWeek) }) @@ -75,7 +75,7 @@ describe("validationSchema", () => { saturday: false, startTime: "", } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError(Language.errorNoTime) }) @@ -84,7 +84,7 @@ describe("validationSchema", () => { ...valid, startTime: "16:20", } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).not.toThrow() }) @@ -93,7 +93,7 @@ describe("validationSchema", () => { ...valid, startTime: "9:30", } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -102,7 +102,7 @@ describe("validationSchema", () => { ...valid, startTime: "09:5", } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -111,7 +111,7 @@ describe("validationSchema", () => { ...valid, startTime: "24:01", } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -120,7 +120,7 @@ describe("validationSchema", () => { ...valid, startTime: "09:60", } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -129,7 +129,7 @@ describe("validationSchema", () => { ...valid, timezone: "Canada/North", } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError(Language.errorTimezone) }) @@ -138,7 +138,7 @@ describe("validationSchema", () => { ...valid, timezone: zone, } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).not.toThrow() }) @@ -147,7 +147,7 @@ describe("validationSchema", () => { ...valid, ttl: 24 * 7, } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).not.toThrowError() }) @@ -156,7 +156,7 @@ describe("validationSchema", () => { ...valid, ttl: 24 * 7 + 1, } - const validate = () => validationSchema.validateSync(values) + const validate = () => getValidationSchema(true, true).validateSync(values) expect(validate).toThrowError("ttl must be less than or equal to 168") }) }) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 583ef04cef6cd..8fb8f4cf7c2ef 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -21,6 +21,7 @@ import { AutoStart } from "pages/WorkspaceSchedulePage/schedule" import { AutoStop } from "pages/WorkspaceSchedulePage/ttl" import { FC } from "react" import * as Yup from "yup" +import { OptionalObjectSchema } from "yup/lib/object" import { getFormHelpersWithError } from "../../util/formUtils" import { FormFooter } from "../FormFooter/FormFooter" import { FullPageForm } from "../FullPageForm/FullPageForm" @@ -36,10 +37,11 @@ dayjs.extend(relativeTime) dayjs.extend(timezone) export const Language = { - errorNoDayOfWeek: "Must set at least one day of week if start time is set", - errorNoTime: "Start time is required when days of the week are selected", + errorNoDayOfWeek: "Must set at least one day of week if auto-start is enabled", + errorNoTime: "Start time is required when auto-start is enabled", errorTime: "Time must be in HH:mm format (24 hours)", errorTimezone: "Invalid timezone", + errorNoStop: "Time until shutdown must be greater than zero when auto-stop is enabled", daysOfWeekLabel: "Days of Week", daySundayLabel: "Sunday", dayMondayLabel: "Monday", @@ -89,12 +91,13 @@ export interface WorkspaceScheduleFormValues { ttl: number } -export const validationSchema = Yup.object({ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const getValidationSchema = (autoStartEnabled: boolean, autoStopEnabled: boolean) => (Yup.object({ sunday: Yup.boolean(), monday: Yup.boolean().test("at-least-one-day", Language.errorNoDayOfWeek, function (value) { const parent = this.parent as WorkspaceScheduleFormValues - if (!parent.startTime) { + if (!autoStartEnabled) { return true } else { return ![ @@ -116,20 +119,8 @@ export const validationSchema = Yup.object({ startTime: Yup.string() .ensure() - .test("required-if-day-selected", Language.errorNoTime, function (value) { - const parent = this.parent as WorkspaceScheduleFormValues - - const isDaySelected = [ - parent.sunday, - parent.monday, - parent.tuesday, - parent.wednesday, - parent.thursday, - parent.friday, - parent.saturday, - ].some((day) => day) - - if (isDaySelected) { + .test("required-if-auto-start", Language.errorNoTime, function (value) { + if (autoStartEnabled) { return value !== "" } else { return true @@ -168,9 +159,16 @@ export const validationSchema = Yup.object({ }), ttl: Yup.number() .integer() - .min(1) - .max(24 * 7 /* 7 days */), -}) + .min(0) + .max(24 * 7 /* 7 days */) + .test("positive-if-auto-stop", Language.errorNoStop, (value) => { + if (autoStopEnabled) { + return !!value + } else { + return true + } + }), +})) export const WorkspaceScheduleForm: FC = ({ submitScheduleError, @@ -190,7 +188,7 @@ export const WorkspaceScheduleForm: FC = ({ initialValues, enableReinitialize: true, onSubmit, - validationSchema, + validationSchema: () => getValidationSchema(autoStart.enabled, autoStop.enabled), initialTouched, }) const formHelpers = getFormHelpersWithError( From bfbabe121300d06d49c7120c87c2d8dc4b21dc9b Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 9 Aug 2022 18:24:28 +0000 Subject: [PATCH 18/26] Format and fix tests --- .../WorkspaceScheduleForm.test.ts | 14 +- .../WorkspaceScheduleForm.tsx | 138 +++++++++--------- .../WorkspaceSchedulePage.test.tsx | 5 +- 3 files changed, 80 insertions(+), 77 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts index c3b2cf1f40e69..49d97a3c7c5dd 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts @@ -1,7 +1,7 @@ import { + getValidationSchema, Language, ttlShutdownAt, - getValidationSchema, WorkspaceScheduleFormValues, } from "./WorkspaceScheduleForm" import { zones } from "./zones" @@ -21,7 +21,7 @@ const valid: WorkspaceScheduleFormValues = { } describe("validationSchema", () => { - it("allows everything to be falsy", () => { + it("allows everything to be falsy when switches are off", () => { const values: WorkspaceScheduleFormValues = { sunday: false, monday: false, @@ -35,7 +35,7 @@ describe("validationSchema", () => { timezone: "", ttl: 0, } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => getValidationSchema(false, false).validateSync(values) expect(validate).not.toThrow() }) @@ -48,7 +48,7 @@ describe("validationSchema", () => { expect(validate).toThrow() }) - it("disallows all days-of-week to be false when startTime is set", () => { + it("disallows all days-of-week to be false when auto-start is enabled", () => { const values: WorkspaceScheduleFormValues = { ...valid, sunday: false, @@ -59,11 +59,11 @@ describe("validationSchema", () => { friday: false, saturday: false, } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => getValidationSchema(true, false).validateSync(values) expect(validate).toThrowError(Language.errorNoDayOfWeek) }) - it("disallows empty startTime when at least one day is set", () => { + it("disallows empty startTime when auto-start is enabled", () => { const values: WorkspaceScheduleFormValues = { ...valid, sunday: false, @@ -75,7 +75,7 @@ describe("validationSchema", () => { saturday: false, startTime: "", } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => getValidationSchema(true, false).validateSync(values) expect(validate).toThrowError(Language.errorNoTime) }) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 8fb8f4cf7c2ef..3fda342cad163 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -21,7 +21,6 @@ import { AutoStart } from "pages/WorkspaceSchedulePage/schedule" import { AutoStop } from "pages/WorkspaceSchedulePage/ttl" import { FC } from "react" import * as Yup from "yup" -import { OptionalObjectSchema } from "yup/lib/object" import { getFormHelpersWithError } from "../../util/formUtils" import { FormFooter } from "../FormFooter/FormFooter" import { FullPageForm } from "../FullPageForm/FullPageForm" @@ -92,83 +91,84 @@ export interface WorkspaceScheduleFormValues { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const getValidationSchema = (autoStartEnabled: boolean, autoStopEnabled: boolean) => (Yup.object({ - sunday: Yup.boolean(), - monday: Yup.boolean().test("at-least-one-day", Language.errorNoDayOfWeek, function (value) { - const parent = this.parent as WorkspaceScheduleFormValues - - if (!autoStartEnabled) { - return true - } else { - return ![ - parent.sunday, - value, - parent.tuesday, - parent.wednesday, - parent.thursday, - parent.friday, - parent.saturday, - ].every((day) => day === false) - } - }), - tuesday: Yup.boolean(), - wednesday: Yup.boolean(), - thursday: Yup.boolean(), - friday: Yup.boolean(), - saturday: Yup.boolean(), +export const getValidationSchema = (autoStartEnabled: boolean, autoStopEnabled: boolean) => + Yup.object({ + sunday: Yup.boolean(), + monday: Yup.boolean().test("at-least-one-day", Language.errorNoDayOfWeek, function (value) { + const parent = this.parent as WorkspaceScheduleFormValues - startTime: Yup.string() - .ensure() - .test("required-if-auto-start", Language.errorNoTime, function (value) { - if (autoStartEnabled) { - return value !== "" - } else { + if (!autoStartEnabled) { return true - } - }) - .test("is-time-string", Language.errorTime, (value) => { - if (value === "") { - return true - } else if (!/^[0-9][0-9]:[0-9][0-9]$/.test(value)) { - return false } else { - const parts = value.split(":") - const HH = Number(parts[0]) - const mm = Number(parts[1]) - return HH >= 0 && HH <= 23 && mm >= 0 && mm <= 59 + return ![ + parent.sunday, + value, + parent.tuesday, + parent.wednesday, + parent.thursday, + parent.friday, + parent.saturday, + ].every((day) => day === false) } }), - timezone: Yup.string() - .ensure() - .test("is-timezone", Language.errorTimezone, function (value) { - const parent = this.parent as WorkspaceScheduleFormValues + tuesday: Yup.boolean(), + wednesday: Yup.boolean(), + thursday: Yup.boolean(), + friday: Yup.boolean(), + saturday: Yup.boolean(), - if (!parent.startTime) { - return true - } else { - // Unfortunately, there's not a good API on dayjs at this time for - // evaluating a timezone. Attempt to parse today in the supplied timezone - // and return as valid if the function doesn't throw. - try { - dayjs.tz(dayjs(), value) + startTime: Yup.string() + .ensure() + .test("required-if-auto-start", Language.errorNoTime, function (value) { + if (autoStartEnabled) { + return value !== "" + } else { return true - } catch (e) { + } + }) + .test("is-time-string", Language.errorTime, (value) => { + if (value === "") { + return true + } else if (!/^[0-9][0-9]:[0-9][0-9]$/.test(value)) { return false + } else { + const parts = value.split(":") + const HH = Number(parts[0]) + const mm = Number(parts[1]) + return HH >= 0 && HH <= 23 && mm >= 0 && mm <= 59 } - } - }), - ttl: Yup.number() - .integer() - .min(0) - .max(24 * 7 /* 7 days */) - .test("positive-if-auto-stop", Language.errorNoStop, (value) => { - if (autoStopEnabled) { - return !!value - } else { - return true - } - }), -})) + }), + timezone: Yup.string() + .ensure() + .test("is-timezone", Language.errorTimezone, function (value) { + const parent = this.parent as WorkspaceScheduleFormValues + + if (!parent.startTime) { + return true + } else { + // Unfortunately, there's not a good API on dayjs at this time for + // evaluating a timezone. Attempt to parse today in the supplied timezone + // and return as valid if the function doesn't throw. + try { + dayjs.tz(dayjs(), value) + return true + } catch (e) { + return false + } + } + }), + ttl: Yup.number() + .integer() + .min(0) + .max(24 * 7 /* 7 days */) + .test("positive-if-auto-stop", Language.errorNoStop, (value) => { + if (autoStopEnabled) { + return !!value + } else { + return true + } + }), + }) export const WorkspaceScheduleForm: FC = ({ submitScheduleError, diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 66923e03db0d7..365d2540a9a8b 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -1,8 +1,11 @@ +import { + formValuesToAutoStartRequest, + formValuesToTTLRequest, +} from "pages/WorkspaceSchedulePage/formToRequest" import { AutoStart, scheduleToAutoStart } from "pages/WorkspaceSchedulePage/schedule" import { AutoStop, ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl" import * as TypesGen from "../../api/typesGenerated" import { WorkspaceScheduleFormValues } from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" -import { formValuesToAutoStartRequest, formValuesToTTLRequest } from "pages/WorkspaceSchedulePage/formToRequest" const validValues: WorkspaceScheduleFormValues = { sunday: false, From d1420de03f0fd12e54ed8a50eed10c6a10d42a93 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 10 Aug 2022 19:44:32 +0000 Subject: [PATCH 19/26] Put switches in form, persist form state --- .../WorkspaceScheduleForm.test.ts | 36 +-- .../WorkspaceScheduleForm.tsx | 208 ++++++++++-------- .../WorkspaceSchedulePage.test.tsx | 84 +++---- .../WorkspaceSchedulePage.tsx | 59 +---- .../WorkspaceSchedulePage/formToRequest.ts | 4 +- .../pages/WorkspaceSchedulePage/schedule.ts | 13 +- site/src/pages/WorkspaceSchedulePage/ttl.ts | 4 +- 7 files changed, 196 insertions(+), 212 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts index 49d97a3c7c5dd..101635a13cd00 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts @@ -1,12 +1,13 @@ import { - getValidationSchema, Language, ttlShutdownAt, + validationSchema, WorkspaceScheduleFormValues, } from "./WorkspaceScheduleForm" import { zones } from "./zones" const valid: WorkspaceScheduleFormValues = { + autoStartEnabled: true, sunday: false, monday: true, tuesday: true, @@ -14,15 +15,17 @@ const valid: WorkspaceScheduleFormValues = { thursday: true, friday: true, saturday: false, - startTime: "09:30", timezone: "Canada/Eastern", + + autoStopEnabled: true, ttl: 120, } describe("validationSchema", () => { it("allows everything to be falsy when switches are off", () => { const values: WorkspaceScheduleFormValues = { + autoStartEnabled: false, sunday: false, monday: false, tuesday: false, @@ -30,12 +33,13 @@ describe("validationSchema", () => { thursday: false, friday: false, saturday: false, - startTime: "", timezone: "", + + autoStopEnabled: false, ttl: 0, } - const validate = () => getValidationSchema(false, false).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).not.toThrow() }) @@ -44,7 +48,7 @@ describe("validationSchema", () => { ...valid, ttl: -1, } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrow() }) @@ -59,7 +63,7 @@ describe("validationSchema", () => { friday: false, saturday: false, } - const validate = () => getValidationSchema(true, false).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(Language.errorNoDayOfWeek) }) @@ -75,7 +79,7 @@ describe("validationSchema", () => { saturday: false, startTime: "", } - const validate = () => getValidationSchema(true, false).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(Language.errorNoTime) }) @@ -84,7 +88,7 @@ describe("validationSchema", () => { ...valid, startTime: "16:20", } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).not.toThrow() }) @@ -93,7 +97,7 @@ describe("validationSchema", () => { ...valid, startTime: "9:30", } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -102,7 +106,7 @@ describe("validationSchema", () => { ...valid, startTime: "09:5", } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -111,7 +115,7 @@ describe("validationSchema", () => { ...valid, startTime: "24:01", } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -120,7 +124,7 @@ describe("validationSchema", () => { ...valid, startTime: "09:60", } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(Language.errorTime) }) @@ -129,7 +133,7 @@ describe("validationSchema", () => { ...valid, timezone: "Canada/North", } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(Language.errorTimezone) }) @@ -138,7 +142,7 @@ describe("validationSchema", () => { ...valid, timezone: zone, } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).not.toThrow() }) @@ -147,7 +151,7 @@ describe("validationSchema", () => { ...valid, ttl: 24 * 7, } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).not.toThrowError() }) @@ -156,7 +160,7 @@ describe("validationSchema", () => { ...valid, ttl: 24 * 7 + 1, } - const validate = () => getValidationSchema(true, true).validateSync(values) + const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError("ttl must be less than or equal to 168") }) }) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 3fda342cad163..e80cfad8901c3 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -17,9 +17,9 @@ import relativeTime from "dayjs/plugin/relativeTime" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" import { FormikTouched, useFormik } from "formik" -import { AutoStart } from "pages/WorkspaceSchedulePage/schedule" -import { AutoStop } from "pages/WorkspaceSchedulePage/ttl" -import { FC } from "react" +import { defaultSchedule } from "pages/WorkspaceSchedulePage/schedule" +import { defaultTTL } from "pages/WorkspaceSchedulePage/ttl" +import { ChangeEvent, FC } from "react" import * as Yup from "yup" import { getFormHelpersWithError } from "../../util/formUtils" import { FormFooter } from "../FormFooter/FormFooter" @@ -65,10 +65,7 @@ export const Language = { export interface WorkspaceScheduleFormProps { submitScheduleError?: Error | unknown - autoStart: AutoStart - toggleAutoStart: () => void - autoStop: AutoStop - toggleAutoStop: () => void + initialValues: WorkspaceScheduleFormValues isLoading: boolean onCancel: () => void onSubmit: (values: WorkspaceScheduleFormValues) => void @@ -77,6 +74,7 @@ export interface WorkspaceScheduleFormProps { } export interface WorkspaceScheduleFormValues { + autoStartEnabled: boolean sunday: boolean monday: boolean tuesday: boolean @@ -84,111 +82,108 @@ export interface WorkspaceScheduleFormValues { thursday: boolean friday: boolean saturday: boolean - startTime: string timezone: string + + autoStopEnabled: boolean ttl: number } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const getValidationSchema = (autoStartEnabled: boolean, autoStopEnabled: boolean) => - Yup.object({ - sunday: Yup.boolean(), - monday: Yup.boolean().test("at-least-one-day", Language.errorNoDayOfWeek, function (value) { - const parent = this.parent as WorkspaceScheduleFormValues +export const validationSchema = Yup.object({ + sunday: Yup.boolean(), + monday: Yup.boolean().test("at-least-one-day", Language.errorNoDayOfWeek, function (value) { + const parent = this.parent as WorkspaceScheduleFormValues - if (!autoStartEnabled) { + if (parent.autoStartEnabled) { + return true + } else { + return ![ + parent.sunday, + value, + parent.tuesday, + parent.wednesday, + parent.thursday, + parent.friday, + parent.saturday, + ].every((day) => day === false) + } + }), + tuesday: Yup.boolean(), + wednesday: Yup.boolean(), + thursday: Yup.boolean(), + friday: Yup.boolean(), + saturday: Yup.boolean(), + + startTime: Yup.string() + .ensure() + .test("required-if-auto-start", Language.errorNoTime, function (value) { + const parent = this.parent as WorkspaceScheduleFormValues + if (parent.autoStartEnabled) { + return value !== "" + } else { + return true + } + }) + .test("is-time-string", Language.errorTime, (value) => { + if (value === "") { return true + } else if (!/^[0-9][0-9]:[0-9][0-9]$/.test(value)) { + return false } else { - return ![ - parent.sunday, - value, - parent.tuesday, - parent.wednesday, - parent.thursday, - parent.friday, - parent.saturday, - ].every((day) => day === false) + const parts = value.split(":") + const HH = Number(parts[0]) + const mm = Number(parts[1]) + return HH >= 0 && HH <= 23 && mm >= 0 && mm <= 59 } }), - tuesday: Yup.boolean(), - wednesday: Yup.boolean(), - thursday: Yup.boolean(), - friday: Yup.boolean(), - saturday: Yup.boolean(), + timezone: Yup.string() + .ensure() + .test("is-timezone", Language.errorTimezone, function (value) { + const parent = this.parent as WorkspaceScheduleFormValues - startTime: Yup.string() - .ensure() - .test("required-if-auto-start", Language.errorNoTime, function (value) { - if (autoStartEnabled) { - return value !== "" - } else { - return true - } - }) - .test("is-time-string", Language.errorTime, (value) => { - if (value === "") { + if (!parent.startTime) { + return true + } else { + // Unfortunately, there's not a good API on dayjs at this time for + // evaluating a timezone. Attempt to parse today in the supplied timezone + // and return as valid if the function doesn't throw. + try { + dayjs.tz(dayjs(), value) return true - } else if (!/^[0-9][0-9]:[0-9][0-9]$/.test(value)) { + } catch (e) { return false - } else { - const parts = value.split(":") - const HH = Number(parts[0]) - const mm = Number(parts[1]) - return HH >= 0 && HH <= 23 && mm >= 0 && mm <= 59 - } - }), - timezone: Yup.string() - .ensure() - .test("is-timezone", Language.errorTimezone, function (value) { - const parent = this.parent as WorkspaceScheduleFormValues - - if (!parent.startTime) { - return true - } else { - // Unfortunately, there's not a good API on dayjs at this time for - // evaluating a timezone. Attempt to parse today in the supplied timezone - // and return as valid if the function doesn't throw. - try { - dayjs.tz(dayjs(), value) - return true - } catch (e) { - return false - } } - }), - ttl: Yup.number() - .integer() - .min(0) - .max(24 * 7 /* 7 days */) - .test("positive-if-auto-stop", Language.errorNoStop, (value) => { - if (autoStopEnabled) { - return !!value - } else { - return true - } - }), - }) + } + }), + ttl: Yup.number() + .integer() + .min(0) + .max(24 * 7 /* 7 days */) + .test("positive-if-auto-stop", Language.errorNoStop, function (value) { + const parent = this.parent as WorkspaceScheduleFormValues + if (parent.autoStopEnabled) { + return !!value + } else { + return true + } + }), +}) export const WorkspaceScheduleForm: FC = ({ submitScheduleError, - autoStart, - toggleAutoStart, - autoStop, - toggleAutoStop, + initialValues, isLoading, onCancel, onSubmit, initialTouched, }) => { const styles = useStyles() - const initialValues = { ...autoStart.schedule, ttl: autoStop.ttl } const form = useFormik({ initialValues, - enableReinitialize: true, onSubmit, - validationSchema: () => getValidationSchema(autoStart.enabled, autoStop.enabled), + validationSchema, initialTouched, }) const formHelpers = getFormHelpersWithError( @@ -206,6 +201,27 @@ export const WorkspaceScheduleForm: FC = ({ { value: form.values.saturday, name: "saturday", label: Language.daySaturdayLabel }, ] + const handleToggleAutoStart = async (e: ChangeEvent) => { + form.handleChange(e) + // if enabling from empty values, fill with defaults + if (!form.values.autoStartEnabled && !form.values.startTime) { + const defaults = defaultSchedule() + checkboxes.forEach(async ({ name }) => { + await form.setFieldValue(name, defaults[name]) + }) + await form.setFieldValue("startTime", defaults.startTime) + await form.setFieldValue("timezone", defaults.timezone) + } + } + + const handleToggleAutoStop = async (e: ChangeEvent) => { + form.handleChange(e) + // if enabling from empty values, fill with defaults + if (!form.values.autoStopEnabled && !form.values.ttl) { + await form.setFieldValue("ttl", defaultTTL) + } + } + return ( @@ -213,12 +229,18 @@ export const WorkspaceScheduleForm: FC = ({ {submitScheduleError && }
} + control={ + + } label={Language.startSwitch} /> = ({ = ({ control={ = ({
} + control={ + + } label={Language.stopSwitch} /> { [ // Empty case { + autoStartEnabled: false, sunday: false, monday: false, tuesday: false, @@ -35,6 +38,7 @@ describe("WorkspaceSchedulePage", () => { saturday: false, startTime: "", timezone: "", + autoStopEnabled: false, ttl: 0, }, { @@ -44,6 +48,7 @@ describe("WorkspaceSchedulePage", () => { [ // Single day { + autoStartEnabled: true, sunday: true, monday: false, tuesday: false, @@ -53,6 +58,7 @@ describe("WorkspaceSchedulePage", () => { saturday: false, startTime: "16:20", timezone: "Canada/Eastern", + autoStopEnabled: true, ttl: 120, }, { @@ -62,6 +68,7 @@ describe("WorkspaceSchedulePage", () => { [ // Standard 1-5 case { + autoStartEnabled: true, sunday: false, monday: true, tuesday: true, @@ -71,6 +78,7 @@ describe("WorkspaceSchedulePage", () => { saturday: false, startTime: "09:30", timezone: "America/Central", + autoStopEnabled: true, ttl: 120, }, { @@ -80,6 +88,7 @@ describe("WorkspaceSchedulePage", () => { [ // Everyday { + autoStartEnabled: true, sunday: true, monday: true, tuesday: true, @@ -89,6 +98,7 @@ describe("WorkspaceSchedulePage", () => { saturday: true, startTime: "09:00", timezone: "", + autoStopEnabled: true, ttl: 60 * 8, }, { @@ -98,6 +108,7 @@ describe("WorkspaceSchedulePage", () => { [ // Mon, Wed, Fri Evenings { + autoStartEnabled: true, sunday: false, monday: true, tuesday: false, @@ -107,6 +118,7 @@ describe("WorkspaceSchedulePage", () => { saturday: false, startTime: "16:20", timezone: "", + autoStopEnabled: true, ttl: 60 * 3, }, { @@ -161,18 +173,16 @@ describe("WorkspaceSchedulePage", () => { [ undefined, { - enabled: false, - schedule: { - sunday: false, - monday: false, - tuesday: false, - wednesday: false, - thursday: false, - friday: false, - saturday: false, - startTime: "", - timezone: "", - }, + autoStartEnabled: false, + sunday: false, + monday: false, + tuesday: false, + wednesday: false, + thursday: false, + friday: false, + saturday: false, + startTime: "", + timezone: "", }, ], @@ -180,18 +190,16 @@ describe("WorkspaceSchedulePage", () => { [ "CRON_TZ=UTC 30 9 * * 1-5", { - enabled: true, - schedule: { - sunday: false, - monday: true, - tuesday: true, - wednesday: true, - thursday: true, - friday: true, - saturday: false, - startTime: "09:30", - timezone: "UTC", - }, + autoStartEnabled: true, + sunday: false, + monday: true, + tuesday: true, + wednesday: true, + thursday: true, + friday: true, + saturday: false, + startTime: "09:30", + timezone: "UTC", }, ], @@ -199,18 +207,16 @@ describe("WorkspaceSchedulePage", () => { [ "CRON_TZ=Canada/Eastern 20 16 * * 1,3-4,6", { - enabled: true, - schedule: { - sunday: false, - monday: true, - tuesday: false, - wednesday: true, - thursday: true, - friday: false, - saturday: true, - startTime: "16:20", - timezone: "Canada/Eastern", - }, + autoStartEnabled: true, + sunday: false, + monday: true, + tuesday: false, + wednesday: true, + thursday: true, + friday: false, + saturday: true, + startTime: "16:20", + timezone: "Canada/Eastern", }, ], ])(`scheduleToAutoStart(%p) returns %p`, (schedule, autoStart) => { @@ -221,11 +227,11 @@ describe("WorkspaceSchedulePage", () => { describe("ttlMsToAutoStop", () => { it.each<[number | undefined, AutoStop]>([ // empty case - [undefined, { enabled: false, ttl: 0 }], + [undefined, { autoStopEnabled: false, ttl: 0 }], // zero - [0, { enabled: false, ttl: 0 }], + [0, { autoStopEnabled: false, ttl: 0 }], // basic case - [28_800_000, { enabled: true, ttl: 8 }], + [28_800_000, { autoStopEnabled: true, ttl: 8 }], ])(`ttlMsToAutoStop(%p) returns %p`, (ttlMs, autoStop) => { expect(ttlMsToAutoStop(ttlMs)).toEqual(autoStop) }) diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index a59b4b0804ebe..b20be6b5ded83 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -1,10 +1,6 @@ import { useMachine, useSelector } from "@xstate/react" -import { - defaultSchedule, - emptySchedule, - scheduleToAutoStart, -} from "pages/WorkspaceSchedulePage/schedule" -import { defaultTTL, emptyTTL, ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl" +import { scheduleToAutoStart } from "pages/WorkspaceSchedulePage/schedule" +import { ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl" import React, { useContext, useEffect, useState } from "react" import { Navigate, useNavigate, useParams } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" @@ -58,52 +54,6 @@ export const WorkspaceSchedulePage: React.FC = () => { setAutoStop(getAutoStop(workspace)) }, [workspace]) - const onToggleAutoStart = () => { - if (autoStart.enabled) { - setAutoStart({ - enabled: false, - schedule: emptySchedule, - }) - } else { - if (workspace?.autostart_schedule) { - // repopulate saved schedule - setAutoStart({ - enabled: true, - schedule: getAutoStart(workspace).schedule, - }) - } else { - // populate with defaults - setAutoStart({ - enabled: true, - schedule: defaultSchedule(), - }) - } - } - } - - const onToggleAutoStop = () => { - if (autoStop.enabled) { - setAutoStop({ - enabled: false, - ttl: emptyTTL, - }) - } else { - if (workspace?.ttl_ms) { - // repopulate saved ttl - setAutoStop({ - enabled: true, - ttl: getAutoStop(workspace).ttl, - }) - } else { - // set default - setAutoStop({ - enabled: true, - ttl: defaultTTL, - }) - } - } - } - if (!username || !workspaceName) { return } @@ -137,10 +87,7 @@ export const WorkspaceSchedulePage: React.FC = () => { return ( { navigate(`/@${username}/${workspaceName}`) diff --git a/site/src/pages/WorkspaceSchedulePage/formToRequest.ts b/site/src/pages/WorkspaceSchedulePage/formToRequest.ts index 88dafe245d5b9..8802139c769d6 100644 --- a/site/src/pages/WorkspaceSchedulePage/formToRequest.ts +++ b/site/src/pages/WorkspaceSchedulePage/formToRequest.ts @@ -4,7 +4,7 @@ import { WorkspaceScheduleFormValues } from "components/WorkspaceScheduleForm/Wo export const formValuesToAutoStartRequest = ( values: WorkspaceScheduleFormValues, ): TypesGen.UpdateWorkspaceAutostartRequest => { - if (!values.startTime) { + if (!values.autoStartEnabled || !values.startTime) { return { schedule: "", } @@ -69,6 +69,6 @@ export const formValuesToTTLRequest = ( ): TypesGen.UpdateWorkspaceTTLRequest => { return { // minutes to nanoseconds - ttl_ms: values.ttl ? values.ttl * 60 * 60 * 1000 : undefined, + ttl_ms: values.autoStopEnabled && values.ttl ? values.ttl * 60 * 60 * 1000 : undefined, } } diff --git a/site/src/pages/WorkspaceSchedulePage/schedule.ts b/site/src/pages/WorkspaceSchedulePage/schedule.ts index 6518872cb68f9..ef08da81e9193 100644 --- a/site/src/pages/WorkspaceSchedulePage/schedule.ts +++ b/site/src/pages/WorkspaceSchedulePage/schedule.ts @@ -22,10 +22,9 @@ export interface AutoStartSchedule { timezone: string } -export interface AutoStart { - enabled: boolean - schedule: AutoStartSchedule -} +export type AutoStart = { + autoStartEnabled: boolean +} & AutoStartSchedule export const emptySchedule = { sunday: false, @@ -83,10 +82,10 @@ const transformSchedule = (schedule: string) => { export const scheduleToAutoStart = (schedule?: string): AutoStart => { if (schedule) { return { - enabled: true, - schedule: transformSchedule(schedule), + autoStartEnabled: true, + ...transformSchedule(schedule), } } else { - return { enabled: false, schedule: emptySchedule } + return { autoStartEnabled: false, ...emptySchedule } } } diff --git a/site/src/pages/WorkspaceSchedulePage/ttl.ts b/site/src/pages/WorkspaceSchedulePage/ttl.ts index 4035ab09edeb9..0d82563b64ff8 100644 --- a/site/src/pages/WorkspaceSchedulePage/ttl.ts +++ b/site/src/pages/WorkspaceSchedulePage/ttl.ts @@ -1,5 +1,5 @@ export interface AutoStop { - enabled: boolean + autoStopEnabled: boolean ttl: number } @@ -10,4 +10,4 @@ export const defaultTTL = 8 const msToHours = (ms: number) => Math.round(ms / (1000 * 60 * 60)) export const ttlMsToAutoStop = (ttl_ms?: number): AutoStop => - ttl_ms ? { enabled: true, ttl: msToHours(ttl_ms) } : { enabled: false, ttl: 0 } + ttl_ms ? { autoStopEnabled: true, ttl: msToHours(ttl_ms) } : { autoStopEnabled: false, ttl: 0 } From 0577ea9d4543ac94b3ee6bd3c701ddd72f86d9a4 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 10 Aug 2022 19:55:52 +0000 Subject: [PATCH 20/26] Fix bug --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index e80cfad8901c3..3751cc7cf9c6c 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -17,7 +17,7 @@ import relativeTime from "dayjs/plugin/relativeTime" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" import { FormikTouched, useFormik } from "formik" -import { defaultSchedule } from "pages/WorkspaceSchedulePage/schedule" +import { AutoStartSchedule, defaultSchedule } from "pages/WorkspaceSchedulePage/schedule" import { defaultTTL } from "pages/WorkspaceSchedulePage/ttl" import { ChangeEvent, FC } from "react" import * as Yup from "yup" @@ -95,7 +95,7 @@ export const validationSchema = Yup.object({ monday: Yup.boolean().test("at-least-one-day", Language.errorNoDayOfWeek, function (value) { const parent = this.parent as WorkspaceScheduleFormValues - if (parent.autoStartEnabled) { + if (!parent.autoStartEnabled) { return true } else { return ![ @@ -207,7 +207,7 @@ export const WorkspaceScheduleForm: FC = ({ if (!form.values.autoStartEnabled && !form.values.startTime) { const defaults = defaultSchedule() checkboxes.forEach(async ({ name }) => { - await form.setFieldValue(name, defaults[name]) + await form.setFieldValue(name, defaults[name as keyof AutoStartSchedule]) }) await form.setFieldValue("startTime", defaults.startTime) await form.setFieldValue("timezone", defaults.timezone) From a6271ca6c4f83d149c283021fd6831d4ea771a3c Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 10 Aug 2022 20:03:48 +0000 Subject: [PATCH 21/26] Remove helper text when disabled --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 3751cc7cf9c6c..784e0a8b1ed03 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -239,7 +239,8 @@ export const WorkspaceScheduleForm: FC = ({ label={Language.startSwitch} /> = ({ label={Language.stopSwitch} /> Date: Wed, 10 Aug 2022 20:11:10 +0000 Subject: [PATCH 22/26] Fix storybook --- .../WorkspaceScheduleForm.stories.tsx | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index e351f71c8cc74..6d52575719ed6 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -4,7 +4,7 @@ import advancedFormat from "dayjs/plugin/advancedFormat" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" import { defaultSchedule, emptySchedule } from "pages/WorkspaceSchedulePage/schedule" -import { emptyTTL } from "pages/WorkspaceSchedulePage/ttl" +import { defaultTTL, emptyTTL } from "pages/WorkspaceSchedulePage/ttl" import { makeMockApiError } from "testHelpers/entities" import { WorkspaceScheduleForm, WorkspaceScheduleFormProps } from "./WorkspaceScheduleForm" @@ -22,51 +22,56 @@ export default { onSubmit: { action: "onSubmit", }, - toggleAutoStart: { - action: "toggleAutoStart", - }, - toggleAutoStop: { - action: "toggleAutoStop", - }, }, } const Template: Story = (args) => +const defaultInitialValues = { + autoStartEnabled: true, + ...defaultSchedule(), + autoStopEnabled: true, + ttl: defaultTTL +} + export const AllDisabled = Template.bind({}) AllDisabled.args = { - autoStart: { enabled: false, schedule: emptySchedule }, - autoStop: { enabled: false, ttl: emptyTTL }, + initialValues: { + autoStartEnabled: false, + ...emptySchedule, + autoStopEnabled: false, + ttl: emptyTTL + } } export const AutoStart = Template.bind({}) AutoStart.args = { - autoStart: { enabled: true, schedule: defaultSchedule() }, - autoStop: { enabled: false, ttl: emptyTTL }, + initialValues: { + autoStartEnabled: true, + ...defaultSchedule(), + autoStopEnabled: false, + ttl: emptyTTL + }, } export const WorkspaceWillShutdownInTwoHours = Template.bind({}) WorkspaceWillShutdownInTwoHours.args = { - autoStart: { enabled: true, schedule: defaultSchedule() }, - autoStop: { enabled: true, ttl: 2 }, + initialValues: { ...defaultInitialValues, ttl: 2 } } export const WorkspaceWillShutdownInADay = Template.bind({}) WorkspaceWillShutdownInADay.args = { - autoStart: { enabled: true, schedule: defaultSchedule() }, - autoStop: { enabled: true, ttl: 24 }, + initialValues: { ...defaultInitialValues, ttl: 24 } } export const WorkspaceWillShutdownInTwoDays = Template.bind({}) WorkspaceWillShutdownInTwoDays.args = { - autoStart: { enabled: true, schedule: defaultSchedule() }, - autoStop: { enabled: true, ttl: 48 }, + initialValues: { ...defaultInitialValues, ttl: 48 } } export const WithError = Template.bind({}) WithError.args = { - autoStart: { enabled: false, schedule: emptySchedule }, - autoStop: { enabled: true, ttl: 100 }, + initialValues: { ...defaultInitialValues, ttl: 100 }, initialTouched: { ttl: true }, submitScheduleError: makeMockApiError({ message: "Something went wrong.", @@ -76,7 +81,6 @@ WithError.args = { export const Loading = Template.bind({}) Loading.args = { - autoStart: { enabled: true, schedule: defaultSchedule() }, - autoStop: { enabled: true, ttl: 2 }, + initialValues: defaultInitialValues, isLoading: true, } From e8ae5ba27bb269c62979a3e5a825cb14bda0076f Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 10 Aug 2022 20:11:37 +0000 Subject: [PATCH 23/26] Revert "Remove helper text when disabled" This reverts commit a6271ca6c4f83d149c283021fd6831d4ea771a3c. --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 784e0a8b1ed03..3751cc7cf9c6c 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -239,8 +239,7 @@ export const WorkspaceScheduleForm: FC = ({ label={Language.startSwitch} /> = ({ label={Language.stopSwitch} /> Date: Wed, 10 Aug 2022 20:15:58 +0000 Subject: [PATCH 24/26] Format --- .../WorkspaceScheduleForm.stories.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index 6d52575719ed6..7d1957bcdff8d 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -31,7 +31,7 @@ const defaultInitialValues = { autoStartEnabled: true, ...defaultSchedule(), autoStopEnabled: true, - ttl: defaultTTL + ttl: defaultTTL, } export const AllDisabled = Template.bind({}) @@ -40,8 +40,8 @@ AllDisabled.args = { autoStartEnabled: false, ...emptySchedule, autoStopEnabled: false, - ttl: emptyTTL - } + ttl: emptyTTL, + }, } export const AutoStart = Template.bind({}) @@ -50,23 +50,23 @@ AutoStart.args = { autoStartEnabled: true, ...defaultSchedule(), autoStopEnabled: false, - ttl: emptyTTL + ttl: emptyTTL, }, } export const WorkspaceWillShutdownInTwoHours = Template.bind({}) WorkspaceWillShutdownInTwoHours.args = { - initialValues: { ...defaultInitialValues, ttl: 2 } + initialValues: { ...defaultInitialValues, ttl: 2 }, } export const WorkspaceWillShutdownInADay = Template.bind({}) WorkspaceWillShutdownInADay.args = { - initialValues: { ...defaultInitialValues, ttl: 24 } + initialValues: { ...defaultInitialValues, ttl: 24 }, } export const WorkspaceWillShutdownInTwoDays = Template.bind({}) WorkspaceWillShutdownInTwoDays.args = { - initialValues: { ...defaultInitialValues, ttl: 48 } + initialValues: { ...defaultInitialValues, ttl: 48 }, } export const WithError = Template.bind({}) From a17dc2e3f37918cf81fba107559eac43cc1026ee Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 10 Aug 2022 21:41:50 +0000 Subject: [PATCH 25/26] Use nicer function to set values --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 3751cc7cf9c6c..621d19729191e 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -205,12 +205,7 @@ export const WorkspaceScheduleForm: FC = ({ form.handleChange(e) // if enabling from empty values, fill with defaults if (!form.values.autoStartEnabled && !form.values.startTime) { - const defaults = defaultSchedule() - checkboxes.forEach(async ({ name }) => { - await form.setFieldValue(name, defaults[name as keyof AutoStartSchedule]) - }) - await form.setFieldValue("startTime", defaults.startTime) - await form.setFieldValue("timezone", defaults.timezone) + await form.setValues({ ...form.values, autoStartEnabled: true, ...defaultSchedule() }) } } From bf22caad199dd85dfbb8c024dbd8249e7b933bbb Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 10 Aug 2022 21:51:14 +0000 Subject: [PATCH 26/26] Format --- .../components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 621d19729191e..94c378c7f7e95 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -17,7 +17,7 @@ import relativeTime from "dayjs/plugin/relativeTime" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" import { FormikTouched, useFormik } from "formik" -import { AutoStartSchedule, defaultSchedule } from "pages/WorkspaceSchedulePage/schedule" +import { defaultSchedule } from "pages/WorkspaceSchedulePage/schedule" import { defaultTTL } from "pages/WorkspaceSchedulePage/ttl" import { ChangeEvent, FC } from "react" import * as Yup from "yup"