Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: form for editing ws schedule #1634

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 4 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions site/src/components/Stack/Stack.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { makeStyles } from "@material-ui/core/styles"
import React from "react"
import { combineClasses } from "../../util/combineClasses"

type Direction = "column" | "row"

interface StyleProps {
spacing: number
direction: Direction
spacing: number
}

const useStyles = makeStyles((theme) => ({
Expand All @@ -17,11 +18,13 @@ const useStyles = makeStyles((theme) => ({
}))

export interface StackProps {
spacing?: number
className?: string
direction?: Direction
spacing?: number
}

export const Stack: React.FC<StackProps> = ({ children, spacing = 2, direction = "column" }) => {
export const Stack: React.FC<StackProps> = ({ children, className, direction = "column", spacing = 2 }) => {
const styles = useStyles({ spacing, direction })
return <div className={styles.stack}>{children}</div>

return <div className={combineClasses([styles.stack, className])}>{children}</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { action } from "@storybook/addon-actions"
import { Story } from "@storybook/react"
import React from "react"
import { WorkspaceScheduleForm, WorkspaceScheduleFormProps } from "./WorkspaceScheduleForm"

export default {
title: "components/WorkspaceScheduleForm",
component: WorkspaceScheduleForm,
}

const Template: Story<WorkspaceScheduleFormProps> = (args) => <WorkspaceScheduleForm {...args} />

export const Example = Template.bind({})
Example.args = {
onCancel: () => action("onCancel"),
onSubmit: () => {
action("onSubmit")
return Promise.resolve()
},
}
57 changes: 57 additions & 0 deletions site/src/components/WorkspaceStats/WorkspaceScheduleForm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Language, validationSchema, WorkspaceScheduleFormValues } from "./WorkspaceScheduleForm"

const valid: WorkspaceScheduleFormValues = {
sunday: false,
monday: true,
tuesday: true,
wednesday: true,
thursday: true,
friday: true,
saturday: false,

startTime: "09:30",
ttl: 120,
}

describe("validationSchema", () => {
it("allows everything to be falsy", () => {
const values: WorkspaceScheduleFormValues = {
sunday: false,
monday: false,
tuesday: false,
wednesday: false,
thursday: false,
friday: false,
saturday: false,

startTime: "",
ttl: 0,
}
const validate = () => validationSchema.validateSync(values)
expect(validate).not.toThrow()
})

it("disallows ttl to be negative", () => {
const values = {
...valid,
ttl: -1,
}
const validate = () => validationSchema.validateSync(values)
expect(validate).toThrow()
})

it("disallows all days-of-week to be false when startTime is set", () => {
const values = {
...valid,
sunday: false,
monday: false,
tuesday: false,
wednesday: false,
thursday: false,
friday: false,
saturday: false,
}
const validate = () => validationSchema.validateSync(values)
expect(validate).toThrowError(Language.errorNoDayOfWeek)
})
})
233 changes: 233 additions & 0 deletions site/src/components/WorkspaceStats/WorkspaceScheduleForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import Checkbox from "@material-ui/core/Checkbox"
import FormControl from "@material-ui/core/FormControl"
import FormControlLabel from "@material-ui/core/FormControlLabel"
import FormGroup from "@material-ui/core/FormGroup"
import FormHelperText from "@material-ui/core/FormHelperText"
import FormLabel from "@material-ui/core/FormLabel"
import makeStyles from "@material-ui/core/styles/makeStyles"
import TextField from "@material-ui/core/TextField"
import { useFormik } from "formik"
import React 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"

export const Language = {
errorNoDayOfWeek: "Must set at least one day of week",
daysOfWeekLabel: "Days of Week",
daySundayLabel: "Sunday",
dayMondayLabel: "Monday",
dayTuesdayLabel: "Tuesday",
dayWednesdayLabel: "Wednesday",
dayThursdayLabel: "Thursday",
dayFridayLabel: "Friday",
daySaturdayLabel: "Saturday",
startTimeLabel: "Start time",
startTimeHelperText: "Your workspace will automatically start at this time.",
ttlLabel: "Runtime (minutes)",
ttlHelperText: "Your workspace will automatically shutdown after the runtime.",
}

export interface WorkspaceScheduleFormProps {
onCancel: () => void
onSubmit: (values: WorkspaceScheduleFormValues) => Promise<void>
}

export interface WorkspaceScheduleFormValues {
sunday: boolean
monday: boolean
tuesday: boolean
wednesday: boolean
thursday: boolean
friday: boolean
saturday: boolean

startTime: string
ttl: number
}

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) {
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(),
ttl: Yup.number().min(0).integer(),
})

export const WorkspaceScheduleForm: React.FC<WorkspaceScheduleFormProps> = ({ onCancel, onSubmit }) => {
const styles = useStyles()

const form = useFormik<WorkspaceScheduleFormValues>({
initialValues: {
sunday: false,
monday: true,
tuesday: true,
wednesday: true,
thursday: true,
friday: true,
saturday: false,
Comment on lines +84 to +91
Copy link
Member

@johnstcn johnstcn May 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non-blocking): I'm not sure how this is going to be translated into a CRON string, but the below are all valid representations:

MON-FRI
MON,TUE,THU,SUN
1-3
2,3,4,5

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parent controlling the form will map the startTime and each of these into an acceptable cron string.

It will likely do , format for simplicity. We will test each of these cases both with code and in crontab.guru.

However, this is all to say - that's a follow-up to the form, that doesn't need to know anything about cron. The code that maps between the form and API is dependency injected via onSubmit, which allows us to test and maintain it in isolation, should something about our cron format change (which is nice).


startTime: "09:30",
ttl: 120,
},
onSubmit,
validationSchema,
})
const formHelpers = getFormHelpers<WorkspaceScheduleFormValues>(form)

return (
<FullPageForm onCancel={onCancel} title="Workspace Schedule">
<form className={styles.form} onSubmit={form.handleSubmit}>
<Stack className={styles.stack}>
<TextField
{...formHelpers("startTime", Language.startTimeHelperText)}
InputLabelProps={{
shrink: true,
}}
label={Language.startTimeLabel}
type="time"
variant="standard"
/>

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

<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={form.values.sunday}
disabled={!form.values.startTime}
onChange={form.handleChange}
name="sunday"
/>
}
label={Language.daySundayLabel}
/>
<FormControlLabel
control={
<Checkbox
checked={form.values.monday}
disabled={!form.values.startTime}
onChange={form.handleChange}
name="monday"
/>
}
label={Language.dayMondayLabel}
/>
<FormControlLabel
control={
<Checkbox
checked={form.values.tuesday}
disabled={!form.values.startTime}
onChange={form.handleChange}
name="tuesday"
/>
}
label={Language.dayTuesdayLabel}
/>
<FormControlLabel
control={
<Checkbox
checked={form.values.wednesday}
disabled={!form.values.startTime}
onChange={form.handleChange}
name="wednesday"
/>
}
label={Language.dayWednesdayLabel}
/>
<FormControlLabel
control={
<Checkbox
checked={form.values.thursday}
disabled={!form.values.startTime}
onChange={form.handleChange}
name="thursday"
/>
}
label={Language.dayThursdayLabel}
/>
<FormControlLabel
control={
<Checkbox
checked={form.values.friday}
disabled={!form.values.startTime}
onChange={form.handleChange}
name="friday"
/>
}
label={Language.dayFridayLabel}
/>
<FormControlLabel
control={
<Checkbox
checked={form.values.saturday}
disabled={!form.values.startTime}
onChange={form.handleChange}
name="saturday"
/>
}
label={Language.daySaturdayLabel}
/>
</FormGroup>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: I can create a map for these as a refactor...will do in follow-up/later.

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

<TextField
{...formHelpers("ttl", Language.ttlHelperText)}
inputProps={{ min: 0, step: 30 }}
label={Language.ttlLabel}
type="number"
variant="standard"
/>

<FormFooter onCancel={onCancel} isLoading={form.isSubmitting} />
</Stack>
</form>
</FullPageForm>
)
}

const useStyles = makeStyles({
form: {
display: "flex",
justifyContent: "center",
},
stack: {
// REMARK: 360 is 'arbitrary' in that it gives the helper text enough room
// to render on one line. If we change the text, we might want to
// adjust these. Without constraining the width, the date picker
// and number inputs aren't visually appealing or maximally usable.
maxWidth: 360,
minWidth: 360,
},
daysOfWeekLabel: {
fontSize: 12,
},
})
2 changes: 1 addition & 1 deletion site/src/util/formUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const getFormHelpers =
...form.getFieldProps(name),
id: name,
error: touched && Boolean(error),
helperText: touched ? error : helperText,
helperText: touched ? error || helperText : helperText,
}
}

Expand Down