From ec4914d9d8cc46296380f807f7aeb1b1a6aebe2b Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 14:56:55 +0000 Subject: [PATCH 01/12] Update color palette --- site/src/theme/palettes.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/src/theme/palettes.ts b/site/src/theme/palettes.ts index 7b78334e27cd6..f237d4a61d237 100644 --- a/site/src/theme/palettes.ts +++ b/site/src/theme/palettes.ts @@ -34,9 +34,11 @@ export const darkPalette: PaletteOptions = { info: { main: colors.blue[11], dark: colors.blue[15], + contrastText: colors.gray[3] }, error: { - main: colors.red[11], + main: colors.red[5], dark: colors.red[15], + contrastText: colors.gray[3] }, } From cfe1cb1be6d27968714f27be579fbe91700e8893 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 14:57:11 +0000 Subject: [PATCH 02/12] Edit dialog error colors --- site/src/components/Dialog/Dialog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/components/Dialog/Dialog.tsx b/site/src/components/Dialog/Dialog.tsx index 8da09f2480c50..6462061f01cfc 100644 --- a/site/src/components/Dialog/Dialog.tsx +++ b/site/src/components/Dialog/Dialog.tsx @@ -2,7 +2,7 @@ import MuiDialog, { DialogProps as MuiDialogProps } from "@material-ui/core/Dial import MuiDialogTitle from "@material-ui/core/DialogTitle" import InputAdornment from "@material-ui/core/InputAdornment" import OutlinedInput, { OutlinedInputProps } from "@material-ui/core/OutlinedInput" -import { darken, fade, makeStyles } from "@material-ui/core/styles" +import { darken, lighten, fade, makeStyles } from "@material-ui/core/styles" import SvgIcon from "@material-ui/core/SvgIcon" import * as React from "react" import { combineClasses } from "../../util/combineClasses" @@ -200,10 +200,10 @@ const useButtonStyles = makeStyles((theme) => ({ }, errorButton: { "&.MuiButton-contained": { - backgroundColor: theme.palette.error.main, + backgroundColor: lighten(theme.palette.error.dark, 0.15), color: theme.palette.error.contrastText, "&:hover": { - backgroundColor: darken(theme.palette.error.main, 0.3), + backgroundColor: theme.palette.error.dark, "@media (hover: none)": { backgroundColor: "transparent", }, From 3838756d3fee173a7fe6fa1bc262fe045d80c0dc Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 15:51:57 +0000 Subject: [PATCH 03/12] Format --- site/src/components/Dialog/Dialog.tsx | 2 +- site/src/theme/palettes.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/components/Dialog/Dialog.tsx b/site/src/components/Dialog/Dialog.tsx index 6462061f01cfc..39f2dc14394de 100644 --- a/site/src/components/Dialog/Dialog.tsx +++ b/site/src/components/Dialog/Dialog.tsx @@ -2,7 +2,7 @@ import MuiDialog, { DialogProps as MuiDialogProps } from "@material-ui/core/Dial import MuiDialogTitle from "@material-ui/core/DialogTitle" import InputAdornment from "@material-ui/core/InputAdornment" import OutlinedInput, { OutlinedInputProps } from "@material-ui/core/OutlinedInput" -import { darken, lighten, fade, makeStyles } from "@material-ui/core/styles" +import { darken, fade, lighten, makeStyles } from "@material-ui/core/styles" import SvgIcon from "@material-ui/core/SvgIcon" import * as React from "react" import { combineClasses } from "../../util/combineClasses" diff --git a/site/src/theme/palettes.ts b/site/src/theme/palettes.ts index f237d4a61d237..e2e23cdf4dbeb 100644 --- a/site/src/theme/palettes.ts +++ b/site/src/theme/palettes.ts @@ -34,11 +34,11 @@ export const darkPalette: PaletteOptions = { info: { main: colors.blue[11], dark: colors.blue[15], - contrastText: colors.gray[3] + contrastText: colors.gray[3], }, error: { main: colors.red[5], dark: colors.red[15], - contrastText: colors.gray[3] + contrastText: colors.gray[3], }, } From 3b6e0c6629ec9bb0cc555e1e77b1448bdc7e4132 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 16:45:55 +0000 Subject: [PATCH 04/12] Lighten links --- site/src/theme/palettes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/theme/palettes.ts b/site/src/theme/palettes.ts index e2e23cdf4dbeb..76d0701705f6d 100644 --- a/site/src/theme/palettes.ts +++ b/site/src/theme/palettes.ts @@ -6,7 +6,7 @@ export const darkPalette: PaletteOptions = { primary: { main: colors.blue[7], contrastText: colors.gray[3], - light: colors.blue[6], + light: colors.blue[5], dark: colors.blue[9], }, secondary: { From 6e0b03688089e9c87ff9d26e87cadb8d0dc8f6ca Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 17:14:25 +0000 Subject: [PATCH 05/12] Lighten link just in ErrorSummary --- site/src/components/ErrorSummary/ErrorSummary.tsx | 3 ++- site/src/theme/palettes.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/site/src/components/ErrorSummary/ErrorSummary.tsx b/site/src/components/ErrorSummary/ErrorSummary.tsx index 77bea7b056363..150cf9fbedcf0 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.tsx @@ -2,7 +2,7 @@ import Button from "@material-ui/core/Button" import Collapse from "@material-ui/core/Collapse" import IconButton from "@material-ui/core/IconButton" import Link from "@material-ui/core/Link" -import { darken, makeStyles, Theme } from "@material-ui/core/styles" +import { darken, lighten, makeStyles, Theme } from "@material-ui/core/styles" import CloseIcon from "@material-ui/icons/Close" import RefreshIcon from "@material-ui/icons/Refresh" import { ApiError, getErrorDetail, getErrorMessage } from "api/errors" @@ -104,6 +104,7 @@ const useStyles = makeStyles((theme) => ({ }, detailsLink: { cursor: "pointer", + color: `${lighten(theme.palette.primary.light, 0.2)}` }, details: { marginTop: `${theme.spacing(2)}px`, diff --git a/site/src/theme/palettes.ts b/site/src/theme/palettes.ts index 76d0701705f6d..e2e23cdf4dbeb 100644 --- a/site/src/theme/palettes.ts +++ b/site/src/theme/palettes.ts @@ -6,7 +6,7 @@ export const darkPalette: PaletteOptions = { primary: { main: colors.blue[7], contrastText: colors.gray[3], - light: colors.blue[5], + light: colors.blue[6], dark: colors.blue[9], }, secondary: { From 6f2cfe68798a64d373f05d772733010fd57fe5bf Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 17:21:39 +0000 Subject: [PATCH 06/12] Format --- site/src/components/ErrorSummary/ErrorSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/ErrorSummary/ErrorSummary.tsx b/site/src/components/ErrorSummary/ErrorSummary.tsx index 150cf9fbedcf0..3c92afce4e6fc 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.tsx @@ -104,7 +104,7 @@ const useStyles = makeStyles((theme) => ({ }, detailsLink: { cursor: "pointer", - color: `${lighten(theme.palette.primary.light, 0.2)}` + color: `${lighten(theme.palette.primary.light, 0.2)}`, }, details: { marginTop: `${theme.spacing(2)}px`, From 82ee773c56a4ab3c003eeab0192f6faaf9edcca5 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 18:10:15 +0000 Subject: [PATCH 07/12] Fix errors in schedule xservice --- .../WorkspaceScheduleForm.tsx | 9 ++++---- .../WorkspaceSchedulePage.tsx | 7 +++++-- .../workspaceScheduleXService.ts | 21 ++++++------------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 7b9905cced32d..7a088eb018470 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -16,8 +16,7 @@ import utc from "dayjs/plugin/utc" import { useFormik } from "formik" import { FC } from "react" import * as Yup from "yup" -import { FieldErrors } from "../../api/errors" -import { getFormHelpers } from "../../util/formUtils" +import { getFormHelpersWithError } from "../../util/formUtils" import { FormFooter } from "../FormFooter/FormFooter" import { FullPageForm } from "../FullPageForm/FullPageForm" import { Stack } from "../Stack/Stack" @@ -54,7 +53,7 @@ export const Language = { } export interface WorkspaceScheduleFormProps { - fieldErrors?: FieldErrors + submitScheduleError?: Error | unknown initialValues?: WorkspaceScheduleFormValues isLoading: boolean onCancel: () => void @@ -178,7 +177,7 @@ export const defaultWorkspaceSchedule = ( }) export const WorkspaceScheduleForm: FC = ({ - fieldErrors, + submitScheduleError, initialValues = defaultWorkspaceSchedule(), isLoading, onCancel, @@ -191,7 +190,7 @@ export const WorkspaceScheduleForm: FC = ({ onSubmit, validationSchema, }) - const formHelpers = getFormHelpers(form, fieldErrors) + const formHelpers = getFormHelpersWithError(form, submitScheduleError) const checkboxes: Array<{ value: boolean; name: string; label: string }> = [ { value: form.values.sunday, name: "sunday", label: Language.daySundayLabel }, diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index fca16071350b3..93c2f8fefb5cb 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -28,6 +28,8 @@ dayjs.extend(timezone) const Language = { forbiddenError: "You don't have permissions to update the schedule for this workspace.", + getWorkspaceError: "Failed to fetch workspace.", + checkPermissionsError: "Failed to fetch permissions." } export const formValuesToAutoStartRequest = ( @@ -156,7 +158,7 @@ export const WorkspaceSchedulePage: React.FC = () => { userId: me?.id, }, }) - const { checkPermissionsError, formErrors, getWorkspaceError, permissions, workspace } = + const { checkPermissionsError, submitScheduleError, getWorkspaceError, permissions, workspace } = scheduleState.context // Get workspace on mount and whenever the args for getting a workspace change. @@ -183,6 +185,7 @@ export const WorkspaceSchedulePage: React.FC = () => { return ( scheduleSend({ type: "GET_WORKSPACE", username, workspaceName })} /> ) @@ -195,7 +198,7 @@ export const WorkspaceSchedulePage: React.FC = () => { if (scheduleState.matches("presentForm") || scheduleState.matches("submittingSchedule")) { return ( { diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts index fec8356f80b96..284b573282b4a 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts @@ -4,20 +4,16 @@ */ import { assign, createMachine } from "xstate" import * as API from "../../api/api" -import { ApiError, FieldErrors, mapApiErrorToFieldErrors } from "../../api/errors" import * as TypesGen from "../../api/typesGenerated" -import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" +import { displaySuccess } from "../../components/GlobalSnackbar/utils" export const Language = { - errorSubmissionFailed: "Failed to update schedule", - errorWorkspaceFetch: "Failed to fetch workspace", successMessage: "Successfully updated workspace schedule.", } type Permissions = Record, boolean> export interface WorkspaceScheduleContext { - formErrors?: FieldErrors getWorkspaceError?: Error | unknown /** * Each workspace has their own schedule (start and ttl). For this reason, we @@ -29,6 +25,7 @@ export interface WorkspaceScheduleContext { userId?: string permissions?: Permissions checkPermissionsError?: Error | unknown + submitScheduleError?: Error | unknown } export const checks = { @@ -86,7 +83,7 @@ export const workspaceSchedule = createMachine( }, onError: { target: "error", - actions: ["assignGetWorkspaceError", "displayWorkspaceError"], + actions: ["assignGetWorkspaceError"], }, }, tags: "loading", @@ -125,7 +122,7 @@ export const workspaceSchedule = createMachine( }, onError: { target: "presentForm", - actions: ["assignSubmissionError", "displaySubmissionError"], + actions: ["assignSubmissionError"], }, }, tags: "loading", @@ -145,7 +142,7 @@ export const workspaceSchedule = createMachine( { actions: { assignSubmissionError: assign({ - formErrors: (_, event) => mapApiErrorToFieldErrors((event.data as ApiError).response.data), + submitScheduleError: (_, event) => event.data }), assignWorkspace: assign({ workspace: (_, event) => event.data, @@ -170,12 +167,6 @@ export const workspaceSchedule = createMachine( clearGetWorkspaceError: (context) => { assign({ ...context, getWorkspaceError: undefined }) }, - displayWorkspaceError: () => { - displayError(Language.errorWorkspaceFetch) - }, - displaySubmissionError: () => { - displayError(Language.errorSubmissionFailed) - }, displaySuccess: () => { displaySuccess(Language.successMessage) }, @@ -197,7 +188,7 @@ export const workspaceSchedule = createMachine( submitSchedule: async (context, event) => { if (!context.workspace?.id) { // This state is theoretically impossible, but helps TS - throw new Error("failed to load workspace") + throw new Error("Failed to load workspace.") } // REMARK: These calls are purposefully synchronous because if one From ce60368dc8b7c4ecde6550c1afd35306d901af00 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 18:43:18 +0000 Subject: [PATCH 08/12] Add error summary to form for generic message --- .../components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 7a088eb018470..4290dcebe0e2b 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -7,6 +7,7 @@ import FormLabel from "@material-ui/core/FormLabel" import MenuItem from "@material-ui/core/MenuItem" import makeStyles from "@material-ui/core/styles/makeStyles" import TextField from "@material-ui/core/TextField" +import { ErrorSummary } from "components/ErrorSummary/ErrorSummary" import dayjs from "dayjs" import advancedFormat from "dayjs/plugin/advancedFormat" import duration from "dayjs/plugin/duration" @@ -206,6 +207,7 @@ export const WorkspaceScheduleForm: FC = ({
+ {submitScheduleError && } Date: Wed, 27 Jul 2022 19:49:34 +0000 Subject: [PATCH 09/12] Format --- .../WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 5 ++++- .../pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx | 6 ++++-- .../workspaceSchedule/workspaceScheduleXService.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 4290dcebe0e2b..b96f8c4be5db3 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -191,7 +191,10 @@ export const WorkspaceScheduleForm: FC = ({ onSubmit, validationSchema, }) - const formHelpers = getFormHelpersWithError(form, submitScheduleError) + const formHelpers = getFormHelpersWithError( + form, + submitScheduleError, + ) const checkboxes: Array<{ value: boolean; name: string; label: string }> = [ { value: form.values.sunday, name: "sunday", label: Language.daySundayLabel }, diff --git a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index 93c2f8fefb5cb..3e74c1e17a6ad 100644 --- a/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -29,7 +29,7 @@ dayjs.extend(timezone) const Language = { forbiddenError: "You don't have permissions to update the schedule for this workspace.", getWorkspaceError: "Failed to fetch workspace.", - checkPermissionsError: "Failed to fetch permissions." + checkPermissionsError: "Failed to fetch permissions.", } export const formValuesToAutoStartRequest = ( @@ -185,7 +185,9 @@ export const WorkspaceSchedulePage: React.FC = () => { return ( scheduleSend({ type: "GET_WORKSPACE", username, workspaceName })} /> ) diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts index 284b573282b4a..9fef3b34cd0f4 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleXService.ts @@ -142,7 +142,7 @@ export const workspaceSchedule = createMachine( { actions: { assignSubmissionError: assign({ - submitScheduleError: (_, event) => event.data + submitScheduleError: (_, event) => event.data, }), assignWorkspace: assign({ workspace: (_, event) => event.data, From 77df9ca36e9025f6e33e568e7772452c8eea06c2 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 27 Jul 2022 22:42:03 +0000 Subject: [PATCH 10/12] Extend getFormHelpers to remap field name --- .../WorkspaceScheduleForm.tsx | 2 +- site/src/util/formUtils.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index b96f8c4be5db3..6554de2d22f51 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -266,7 +266,7 @@ export const WorkspaceScheduleForm: FC = ({ (form: FormikContextType, formErrors?: FormikErrors) => - (name: keyof T, HelperText: ReactNode = ""): FormHelpers => { + (form: FormikContextType, apiValidationErrors?: FormikErrors) => + (name: keyof T, HelperText: ReactNode = "", backendErrorName?: string): FormHelpers => { if (typeof name !== "string") { throw new Error(`name must be type of string, instead received '${typeof name}'`) } + const apiErrorName = backendErrorName ?? name // getIn is a util function from Formik that gets at any depth of nesting // and is necessary for the types to work const touched = getIn(form.touched, name) - const apiError = getIn(formErrors, name) - const validationError = getIn(form.errors, name) - const error = apiError ?? validationError + const apiError = getIn(apiValidationErrors, apiErrorName) + const frontendError = getIn(form.errors, name) + const error = apiError ?? frontendError return { ...form.getFieldProps(name), id: name, @@ -49,7 +51,7 @@ export const getFormHelpers = export const getFormHelpersWithError = ( form: FormikContextType, error?: Error | unknown, -): ((name: keyof T, HelperText?: ReactNode) => FormHelpers) => { +): ((name: keyof T, HelperText?: ReactNode, errorName?: string) => FormHelpers) => { const apiValidationErrors = isApiError(error) && hasApiFieldErrors(error) ? (mapApiErrorToFieldErrors(error.response.data) as FormikErrors) From a1b8f9f9581e62d2e9d33b884447aba4b36567e8 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 28 Jul 2022 00:42:38 +0000 Subject: [PATCH 11/12] Add mock error and use in storybook --- site/src/api/errors.ts | 2 +- .../WorkspaceScheduleForm.stories.tsx | 29 ++++++++++++------- .../WorkspaceScheduleForm.tsx | 6 +++- site/src/testHelpers/entities.ts | 15 ++++++++++ 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/site/src/api/errors.ts b/site/src/api/errors.ts index 189ea12f43c4b..56f385b19959d 100644 --- a/site/src/api/errors.ts +++ b/site/src/api/errors.ts @@ -6,7 +6,7 @@ export const Language = { }, } -interface FieldError { +export interface FieldError { field: string detail: string } diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index 8a9c1bcdecbf5..a0da90bf21238 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -1,9 +1,9 @@ -import { action } from "@storybook/addon-actions" import { Story } from "@storybook/react" import dayjs from "dayjs" import advancedFormat from "dayjs/plugin/advancedFormat" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" +import { makeMockApiError } from "testHelpers/entities" import { defaultWorkspaceSchedule, WorkspaceScheduleForm, @@ -17,6 +17,14 @@ dayjs.extend(timezone) export default { title: "components/WorkspaceScheduleForm", component: WorkspaceScheduleForm, + argTypes: { + onCancel: { + action: "onCancel" + }, + onSubmit: { + action: "onSubmit" + } + } } const Template: Story = (args) => @@ -27,8 +35,6 @@ WorkspaceWillNotShutDown.args = { ...defaultWorkspaceSchedule(5), ttl: 0, }, - onCancel: () => action("onCancel"), - onSubmit: () => action("onSubmit"), } export const WorkspaceWillShutdownInAnHour = Template.bind({}) @@ -37,8 +43,6 @@ WorkspaceWillShutdownInAnHour.args = { ...defaultWorkspaceSchedule(5), ttl: 1, }, - onCancel: () => action("onCancel"), - onSubmit: () => action("onSubmit"), } export const WorkspaceWillShutdownInTwoHours = Template.bind({}) @@ -47,8 +51,6 @@ WorkspaceWillShutdownInTwoHours.args = { ...defaultWorkspaceSchedule(2), ttl: 2, }, - onCancel: () => action("onCancel"), - onSubmit: () => action("onSubmit"), } export const WorkspaceWillShutdownInADay = Template.bind({}) @@ -57,8 +59,6 @@ WorkspaceWillShutdownInADay.args = { ...defaultWorkspaceSchedule(2), ttl: 24, }, - onCancel: () => action("onCancel"), - onSubmit: () => action("onSubmit"), } export const WorkspaceWillShutdownInTwoDays = Template.bind({}) @@ -67,6 +67,13 @@ WorkspaceWillShutdownInTwoDays.args = { ...defaultWorkspaceSchedule(2), ttl: 48, }, - onCancel: () => action("onCancel"), - onSubmit: () => action("onSubmit"), +} + +export const WithError = Template.bind({}) +WithError.args = { + initialTouched: { ttl: true }, + submitScheduleError: makeMockApiError({ + message: "Something went wrong.", + validations: [{ field: "ttl_ms", detail: "Invalid time until shutdown." }] + }) } diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 6554de2d22f51..6e583e0826ed5 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -14,7 +14,7 @@ import duration from "dayjs/plugin/duration" import relativeTime from "dayjs/plugin/relativeTime" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" -import { useFormik } from "formik" +import { FormikTouched, useFormik } from "formik" import { FC } from "react" import * as Yup from "yup" import { getFormHelpersWithError } from "../../util/formUtils" @@ -59,6 +59,8 @@ export interface WorkspaceScheduleFormProps { isLoading: boolean onCancel: () => void onSubmit: (values: WorkspaceScheduleFormValues) => void + // for storybook + initialTouched?: FormikTouched } export interface WorkspaceScheduleFormValues { @@ -183,6 +185,7 @@ export const WorkspaceScheduleForm: FC = ({ isLoading, onCancel, onSubmit, + initialTouched }) => { const styles = useStyles() @@ -190,6 +193,7 @@ export const WorkspaceScheduleForm: FC = ({ initialValues, onSubmit, validationSchema, + initialTouched }) const formHelpers = getFormHelpersWithError( form, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index d847cdca8fadd..3eb86313fd2ed 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1,3 +1,4 @@ +import { FieldError } from "api/errors" import * as Types from "../api/types" import * as TypesGen from "../api/typesGenerated" @@ -584,3 +585,17 @@ export const MockWorkspaceBuildLogs: TypesGen.ProvisionerJobLog[] = [ export const MockCancellationMessage = { message: "Job successfully canceled", } + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const makeMockApiError = ({ message, detail, validations }: { message?: string, detail?: string, validations?: FieldError[] }) => ( + { + response: { + data: { + message: message ?? "Something went wrong.", + detail: detail ?? undefined, + validations: validations ?? undefined, + }, + }, + isAxiosError: true, + } +) From ad871dc684fbda1d76cdfd3a5f653917ff505bf8 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 28 Jul 2022 01:06:26 +0000 Subject: [PATCH 12/12] Format --- .../WorkspaceScheduleForm.stories.tsx | 12 ++++---- .../WorkspaceScheduleForm.tsx | 4 +-- site/src/testHelpers/entities.ts | 28 +++++++++++-------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index a0da90bf21238..cb24e1316dc5e 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -19,12 +19,12 @@ export default { component: WorkspaceScheduleForm, argTypes: { onCancel: { - action: "onCancel" + action: "onCancel", }, onSubmit: { - action: "onSubmit" - } - } + action: "onSubmit", + }, + }, } const Template: Story = (args) => @@ -74,6 +74,6 @@ WithError.args = { initialTouched: { ttl: true }, submitScheduleError: makeMockApiError({ message: "Something went wrong.", - validations: [{ field: "ttl_ms", detail: "Invalid time until shutdown." }] - }) + validations: [{ field: "ttl_ms", detail: "Invalid time until shutdown." }], + }), } diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 6e583e0826ed5..6eb500550ff38 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -185,7 +185,7 @@ export const WorkspaceScheduleForm: FC = ({ isLoading, onCancel, onSubmit, - initialTouched + initialTouched, }) => { const styles = useStyles() @@ -193,7 +193,7 @@ export const WorkspaceScheduleForm: FC = ({ initialValues, onSubmit, validationSchema, - initialTouched + initialTouched, }) const formHelpers = getFormHelpersWithError( form, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 3eb86313fd2ed..efe26681ca63b 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -587,15 +587,21 @@ export const MockCancellationMessage = { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const makeMockApiError = ({ message, detail, validations }: { message?: string, detail?: string, validations?: FieldError[] }) => ( - { - response: { - data: { - message: message ?? "Something went wrong.", - detail: detail ?? undefined, - validations: validations ?? undefined, - }, +export const makeMockApiError = ({ + message, + detail, + validations, +}: { + message?: string + detail?: string + validations?: FieldError[] +}) => ({ + response: { + data: { + message: message ?? "Something went wrong.", + detail: detail ?? undefined, + validations: validations ?? undefined, }, - isAxiosError: true, - } -) + }, + isAxiosError: true, +})