diff --git a/site/src/components/Section/Section.tsx b/site/src/components/Section/Section.tsx
index 9e2b993ed38d7..40e68161b75b6 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}
-
+
)
}
@@ -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),
diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx
index cb24e1316dc5e..7d1957bcdff8d 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/WorkspaceSchedulePage/schedule"
+import { defaultTTL, emptyTTL } from "pages/WorkspaceSchedulePage/ttl"
import { makeMockApiError } from "testHelpers/entities"
-import {
- defaultWorkspaceSchedule,
- WorkspaceScheduleForm,
- WorkspaceScheduleFormProps,
-} from "./WorkspaceScheduleForm"
+import { WorkspaceScheduleForm, WorkspaceScheduleFormProps } from "./WorkspaceScheduleForm"
dayjs.extend(advancedFormat)
dayjs.extend(utc)
@@ -29,51 +27,60 @@ export default {
const Template: Story
= (args) =>
-export const WorkspaceWillNotShutDown = Template.bind({})
-WorkspaceWillNotShutDown.args = {
+const defaultInitialValues = {
+ autoStartEnabled: true,
+ ...defaultSchedule(),
+ autoStopEnabled: true,
+ ttl: defaultTTL,
+}
+
+export const AllDisabled = Template.bind({})
+AllDisabled.args = {
initialValues: {
- ...defaultWorkspaceSchedule(5),
- ttl: 0,
+ autoStartEnabled: false,
+ ...emptySchedule,
+ autoStopEnabled: false,
+ ttl: emptyTTL,
},
}
-export const WorkspaceWillShutdownInAnHour = Template.bind({})
-WorkspaceWillShutdownInAnHour.args = {
+export const AutoStart = Template.bind({})
+AutoStart.args = {
initialValues: {
- ...defaultWorkspaceSchedule(5),
- ttl: 1,
+ autoStartEnabled: true,
+ ...defaultSchedule(),
+ autoStopEnabled: false,
+ ttl: emptyTTL,
},
}
export const WorkspaceWillShutdownInTwoHours = Template.bind({})
WorkspaceWillShutdownInTwoHours.args = {
- initialValues: {
- ...defaultWorkspaceSchedule(2),
- ttl: 2,
- },
+ initialValues: { ...defaultInitialValues, ttl: 2 },
}
export const WorkspaceWillShutdownInADay = Template.bind({})
WorkspaceWillShutdownInADay.args = {
- initialValues: {
- ...defaultWorkspaceSchedule(2),
- ttl: 24,
- },
+ initialValues: { ...defaultInitialValues, ttl: 24 },
}
export const WorkspaceWillShutdownInTwoDays = Template.bind({})
WorkspaceWillShutdownInTwoDays.args = {
- initialValues: {
- ...defaultWorkspaceSchedule(2),
- ttl: 48,
- },
+ initialValues: { ...defaultInitialValues, ttl: 48 },
}
export const WithError = Template.bind({})
WithError.args = {
+ initialValues: { ...defaultInitialValues, ttl: 100 },
initialTouched: { ttl: true },
submitScheduleError: makeMockApiError({
message: "Something went wrong.",
validations: [{ field: "ttl_ms", detail: "Invalid time until shutdown." }],
}),
}
+
+export const Loading = Template.bind({})
+Loading.args = {
+ initialValues: defaultInitialValues,
+ isLoading: true,
+}
diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts
index 0b08446f0fcc8..101635a13cd00 100644
--- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts
+++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts
@@ -7,6 +7,7 @@ import {
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", () => {
+ it("allows everything to be falsy when switches are off", () => {
const values: WorkspaceScheduleFormValues = {
+ autoStartEnabled: false,
sunday: false,
monday: false,
tuesday: false,
@@ -30,9 +33,10 @@ describe("validationSchema", () => {
thursday: false,
friday: false,
saturday: false,
-
startTime: "",
timezone: "",
+
+ autoStopEnabled: false,
ttl: 0,
}
const validate = () => validationSchema.validateSync(values)
@@ -48,7 +52,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,
@@ -63,7 +67,7 @@ describe("validationSchema", () => {
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,
diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx
index 6eb500550ff38..94c378c7f7e95 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"
@@ -15,7 +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 { 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"
@@ -32,10 +36,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",
@@ -51,11 +56,16 @@ 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 {
submitScheduleError?: Error | unknown
- initialValues?: WorkspaceScheduleFormValues
+ initialValues: WorkspaceScheduleFormValues
isLoading: boolean
onCancel: () => void
onSubmit: (values: WorkspaceScheduleFormValues) => void
@@ -64,6 +74,7 @@ export interface WorkspaceScheduleFormProps {
}
export interface WorkspaceScheduleFormValues {
+ autoStartEnabled: boolean
sunday: boolean
monday: boolean
tuesday: boolean
@@ -71,18 +82,20 @@ 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 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 (!parent.startTime) {
+ if (!parent.autoStartEnabled) {
return true
} else {
return ![
@@ -104,20 +117,9 @@ export const validationSchema = Yup.object({
startTime: Yup.string()
.ensure()
- .test("required-if-day-selected", Language.errorNoTime, function (value) {
+ .test("required-if-auto-start", 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) {
+ if (parent.autoStartEnabled) {
return value !== ""
} else {
return true
@@ -157,31 +159,20 @@ export const validationSchema = Yup.object({
ttl: Yup.number()
.integer()
.min(0)
- .max(24 * 7 /* 7 days */),
-})
-
-export const defaultWorkspaceScheduleTTL = 8
-
-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,
+ .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,
- initialValues = defaultWorkspaceSchedule(),
+ initialValues,
isLoading,
onCancel,
onSubmit,
@@ -210,72 +201,115 @@ 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) {
+ await form.setValues({ ...form.values, autoStartEnabled: true, ...defaultSchedule() })
+ }
+ }
+
+ 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 (
-
+