Skip to content

refactor(site): Refactor workspace schedule page #7069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 11, 2023
15 changes: 12 additions & 3 deletions site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { RequireAuth } from "./components/RequireAuth/RequireAuth"
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout"
import { TemplateSettingsLayout } from "pages/TemplateSettingsPage/TemplateSettingsLayout"
import { WorkspaceSettingsLayout } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout"

// Lazy load pages
// - Pages that are secondary, not in the main navigation or not usually accessed
Expand Down Expand Up @@ -45,7 +46,10 @@ const WorkspaceBuildPage = lazy(
)
const WorkspacePage = lazy(() => import("./pages/WorkspacePage/WorkspacePage"))
const WorkspaceSchedulePage = lazy(
() => import("./pages/WorkspaceSchedulePage/WorkspaceSchedulePage"),
() =>
import(
"./pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage"
),
)
const TerminalPage = lazy(() => import("./pages/TerminalPage/TerminalPage"))
const TemplatePermissionsPage = lazy(
Expand Down Expand Up @@ -260,12 +264,17 @@ export const AppRouter: FC = () => {
<Route path="/@:username">
<Route path=":workspace">
<Route index element={<WorkspacePage />} />
<Route path="schedule" element={<WorkspaceSchedulePage />} />
<Route
path="builds/:buildNumber"
element={<WorkspaceBuildPage />}
/>
<Route path="settings" element={<WorkspaceSettingsPage />} />
<Route path="settings" element={<WorkspaceSettingsLayout />}>
<Route index element={<WorkspaceSettingsPage />} />
<Route
path="schedule"
element={<WorkspaceSchedulePage />}
/>
</Route>
</Route>
</Route>
</Route>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const WorkspaceSchedule: FC<
<Link
className={styles.scheduleAction}
component={RouterLink}
to={`/@${workspace.owner_name}/${workspace.name}/schedule`}
to={`/@${workspace.owner_name}/${workspace.name}/settings/schedule`}
>
{Language.editScheduleLink}
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import utc from "dayjs/plugin/utc"
import {
defaultSchedule,
emptySchedule,
} from "pages/WorkspaceSchedulePage/schedule"
import { emptyTTL } from "pages/WorkspaceSchedulePage/ttl"
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule"
import { emptyTTL } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/ttl"
import { makeMockApiError } from "testHelpers/entities"
import {
WorkspaceScheduleForm,
Expand Down
210 changes: 103 additions & 107 deletions site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ 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 { AlertBanner } from "components/AlertBanner/AlertBanner"
import { Section } from "components/Section/Section"
import {
HorizontalForm,
FormFooter,
FormSection,
FormFields,
} from "components/Form/Form"
import { Stack } from "components/Stack/Stack"
import dayjs from "dayjs"
import advancedFormat from "dayjs/plugin/advancedFormat"
import duration from "dayjs/plugin/duration"
Expand All @@ -20,13 +25,10 @@ import { FormikTouched, useFormik } from "formik"
import {
defaultSchedule,
emptySchedule,
} from "pages/WorkspaceSchedulePage/schedule"
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule"
import { ChangeEvent, FC } from "react"
import * as Yup from "yup"
import { getFormHelpers } from "../../util/formUtils"
import { FormFooter } from "../FormFooter/FormFooter"
import { FullPageForm } from "../FullPageForm/FullPageForm"
import { Stack } from "../Stack/Stack"
import { zones } from "./zones"

// REMARK: some plugins depend on utc, so it's listed first. Otherwise they're
Expand All @@ -48,16 +50,14 @@ export const Language = {
errorTtlMax:
"Please enter a limit that is less than or equal to 168 hours (7 days).",
daysOfWeekLabel: "Days of Week",
daySundayLabel: "Sunday",
dayMondayLabel: "Monday",
dayTuesdayLabel: "Tuesday",
dayWednesdayLabel: "Wednesday",
dayThursdayLabel: "Thursday",
dayFridayLabel: "Friday",
daySaturdayLabel: "Saturday",
daySundayLabel: "Sun",
dayMondayLabel: "Mon",
dayTuesdayLabel: "Tue",
dayWednesdayLabel: "Wed",
dayThursdayLabel: "Thu",
dayFridayLabel: "Fri",
daySaturdayLabel: "Sat",
startTimeLabel: "Start time",
startTimeHelperText: "Your workspace will automatically start at this time.",
noStartTimeHelperText: "Your workspace will not automatically start.",
timezoneLabel: "Timezone",
ttlLabel: "Time until shutdown (hours)",
ttlCausesShutdownHelperText: "Your workspace will shut down",
Expand All @@ -67,13 +67,13 @@ export const Language = {
"Your workspace will not automatically shut down.",
formTitle: "Workspace schedule",
startSection: "Start",
startSwitch: "Autostart",
startSwitch: "Enable Autostart",
stopSection: "Stop",
stopSwitch: "Autostop",
stopSwitch: "Enable Autostop",
}

export interface WorkspaceScheduleFormProps {
submitScheduleError?: Error | unknown
submitScheduleError?: unknown
initialValues: WorkspaceScheduleFormValues
isLoading: boolean
onCancel: () => void
Expand Down Expand Up @@ -280,31 +280,26 @@ export const WorkspaceScheduleForm: FC<
}

return (
<FullPageForm title={Language.formTitle}>
<form onSubmit={form.handleSubmit} className={styles.form}>
<Stack>
{Boolean(submitScheduleError) && (
<AlertBanner severity="error" error={submitScheduleError} />
)}
<Section title={Language.startSection}>
<FormControlLabel
control={
<Switch
name="autostartEnabled"
checked={form.values.autostartEnabled}
onChange={handleToggleAutostart}
color="primary"
/>
}
label={Language.startSwitch}
/>
<HorizontalForm onSubmit={form.handleSubmit}>
<FormSection
title="Autostart"
description="Select the time and days of week on which you want the workspace starting automatically."
>
<FormFields>
<FormControlLabel
control={
<Switch
name="autostartEnabled"
checked={form.values.autostartEnabled}
onChange={handleToggleAutostart}
color="primary"
/>
}
label={Language.startSwitch}
/>
<Stack direction="row">
<TextField
{...formHelpers(
"startTime",
form.values.autostartEnabled
? Language.startTimeHelperText
: Language.noStartTimeHelperText,
)}
{...formHelpers("startTime")}
disabled={isLoading || !form.values.autostartEnabled}
InputLabelProps={{
shrink: true,
Expand All @@ -313,7 +308,6 @@ export const WorkspaceScheduleForm: FC<
type="time"
fullWidth
/>

<TextField
{...formHelpers("timezone")}
disabled={isLoading || !form.values.autostartEnabled}
Expand All @@ -330,67 +324,68 @@ export const WorkspaceScheduleForm: FC<
</MenuItem>
))}
</TextField>
</Stack>

<FormControl
component="fieldset"
error={Boolean(form.errors.monday)}
>
<FormLabel className={styles.daysOfWeekLabel} component="legend">
{Language.daysOfWeekLabel}
</FormLabel>
<FormControl component="fieldset" error={Boolean(form.errors.monday)}>
<FormLabel className={styles.daysOfWeekLabel} component="legend">
{Language.daysOfWeekLabel}
</FormLabel>

<FormGroup>
{checkboxes.map((checkbox) => (
<FormControlLabel
control={
<Checkbox
checked={checkbox.value}
disabled={isLoading || !form.values.autostartEnabled}
onChange={form.handleChange}
name={checkbox.name}
color="primary"
size="small"
disableRipple
/>
}
key={checkbox.name}
label={checkbox.label}
/>
))}
</FormGroup>

{form.errors.monday && (
<FormHelperText>{Language.errorNoDayOfWeek}</FormHelperText>
)}
</FormControl>
</Section>

<Section title={Language.stopSection}>
<FormControlLabel
control={
<Switch
name="autostopEnabled"
checked={form.values.autostopEnabled}
onChange={handleToggleAutostop}
color="primary"
<FormGroup className={styles.daysOfWeekOptions}>
{checkboxes.map((checkbox) => (
<FormControlLabel
control={
<Checkbox
checked={checkbox.value}
disabled={isLoading || !form.values.autostartEnabled}
onChange={form.handleChange}
name={checkbox.name}
color="primary"
size="small"
disableRipple
/>
}
key={checkbox.name}
label={checkbox.label}
/>
}
label={Language.stopSwitch}
/>
<TextField
{...formHelpers("ttl", ttlShutdownAt(form.values.ttl), "ttl_ms")}
disabled={isLoading || !form.values.autostopEnabled}
inputProps={{ min: 0, step: 1 }}
label={Language.ttlLabel}
type="number"
fullWidth
/>
</Section>
))}
</FormGroup>

{form.errors.monday && (
<FormHelperText>{Language.errorNoDayOfWeek}</FormHelperText>
)}
</FormControl>
</FormFields>
</FormSection>

<FormFooter onCancel={onCancel} isLoading={isLoading} />
</Stack>
</form>
</FullPageForm>
<FormSection
title="Autostop"
description="Set how many hours should elapse after a workspace is started before it automatically shuts down. If workspace connection activity is detected, the autostop timer will be bumped up one hour."
>
<FormFields>
<FormControlLabel
control={
<Switch
name="autostopEnabled"
checked={form.values.autostopEnabled}
onChange={handleToggleAutostop}
color="primary"
/>
}
label={Language.stopSwitch}
/>
<TextField
{...formHelpers("ttl", ttlShutdownAt(form.values.ttl), "ttl_ms")}
disabled={isLoading || !form.values.autostopEnabled}
inputProps={{ min: 0, step: 1 }}
label={Language.ttlLabel}
type="number"
fullWidth
/>
</FormFields>
</FormSection>
<FormFooter onCancel={onCancel} isLoading={isLoading} />
</HorizontalForm>
)
}

Expand All @@ -405,13 +400,14 @@ export const ttlShutdownAt = (formTTL: number): string => {
}
}

const useStyles = makeStyles({
form: {
"& input": {
colorScheme: "dark",
},
},
const useStyles = makeStyles((theme) => ({
daysOfWeekLabel: {
fontSize: 12,
},
})
daysOfWeekOptions: {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
paddingTop: theme.spacing(0.5),
},
}))
Loading