Make form depend on switches

This commit is contained in:
Presley Pizzo
2022-08-01 20:54:38 +00:00
parent 6705076001
commit 91259c4fc7
4 changed files with 189 additions and 98 deletions
@@ -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<WorkspaceScheduleFormProps> = ({
submitScheduleError,
initialValues = defaultWorkspaceSchedule(),
autoStart,
toggleAutoStart,
autoStop,
toggleAutoStop,
isLoading,
onCancel,
onSubmit,
initialTouched,
}) => {
const styles = useStyles()
const initialValues = { ...autoStart.schedule, ttl: autoStop.ttl }
const form = useFormik<WorkspaceScheduleFormValues>({
initialValues,
enableReinitialize: true,
onSubmit,
validationSchema,
initialTouched,
@@ -236,12 +210,12 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
{submitScheduleError && <ErrorSummary error={submitScheduleError} />}
<Section title="Start">
<FormControlLabel
control={<Switch />}
control={<Switch checked={autoStart.enabled} onChange={toggleAutoStart} />}
label="Auto-start"
/>
<TextField
{...formHelpers("startTime", Language.startTimeHelperText)}
disabled={isLoading}
disabled={isLoading || !autoStart.enabled}
InputLabelProps={{
shrink: true,
}}
@@ -251,7 +225,7 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
<TextField
{...formHelpers("timezone")}
disabled={isLoading}
disabled={isLoading || !autoStart.enabled}
InputLabelProps={{
shrink: true,
}}
@@ -276,7 +250,7 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
control={
<Checkbox
checked={checkbox.value}
disabled={isLoading}
disabled={isLoading || !autoStart.enabled}
onChange={form.handleChange}
name={checkbox.name}
color="primary"
@@ -296,12 +270,12 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
<Section title="Stop">
<FormControlLabel
control={<Switch />}
control={<Switch checked={autoStop.enabled} onChange={toggleAutoStop} />}
label="Auto-Stop"
/>
<TextField
{...formHelpers("ttl", ttlShutdownAt(form.values.ttl), "ttl_ms")}
disabled={isLoading}
disabled={isLoading || !autoStop.enabled}
inputProps={{ min: 0, step: 1 }}
label={Language.ttlLabel}
type="number"
@@ -1,30 +1,20 @@
import { useMachine, useSelector } from "@xstate/react"
import * as cronParser from "cron-parser"
import dayjs from "dayjs"
import timezone from "dayjs/plugin/timezone"
import utc from "dayjs/plugin/utc"
import React, { useContext, useEffect } from "react"
import { defaultSchedule, emptySchedule, scheduleToAutoStart } from "pages/WorkspacesPage/schedule"
import { ttlMsToAutoStop, emptyTTL, defaultTTL } from "pages/WorkspacesPage/ttl"
import React, { useContext, useEffect, useState } from "react"
import { 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 {
emptyWorkspaceSchedule,
WorkspaceScheduleForm,
WorkspaceScheduleFormValues,
} from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm"
import { firstOrItem } from "../../util/array"
import { extractTimezone, stripTimezone } from "../../util/schedule"
import { selectUser } from "../../xServices/auth/authSelectors"
import { XServiceContext } from "../../xServices/StateContext"
import { workspaceSchedule } from "../../xServices/workspaceSchedule/workspaceScheduleXService"
// REMARK: timezone plugin depends on UTC
//
// SEE: https://day.js.org/docs/en/timezone/timezone
dayjs.extend(utc)
dayjs.extend(timezone)
const Language = {
forbiddenError: "You don't have permissions to update the schedule for this workspace.",
getWorkspaceError: "Failed to fetch workspace.",
@@ -103,46 +93,6 @@ export const formValuesToTTLRequest = (
}
}
export const workspaceToInitialValues = (
workspace: TypesGen.Workspace,
defaultTimeZone = "",
): WorkspaceScheduleFormValues => {
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 (
<WorkspaceScheduleForm
submitScheduleError={submitScheduleError}
initialValues={workspaceToInitialValues(workspace, dayjs.tz.guess())}
autoStart={autoStart}
toggleAutoStart={onToggleAutoStart}
autoStop={autoStop}
toggleAutoStop={onToggleAutoStop}
isLoading={scheduleState.tags.has("loading")}
onCancel={() => {
navigate(`/@${username}/${workspaceName}`)
+93
View File
@@ -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 }
}
}
+14
View File
@@ -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 }
)