From cf632d55d0c4bf09da881ff28d89ff72e70962b3 Mon Sep 17 00:00:00 2001 From: Abhineet Jain Date: Mon, 1 Aug 2022 20:08:14 +0000 Subject: [PATCH 1/3] fix: handle create workspace errors --- coderd/workspaces.go | 2 +- .../CreateWorkspacePage.tsx | 25 ++++++++-- .../CreateWorkspacePageView.stories.tsx | 50 ++++++++++++++++++- .../CreateWorkspacePageView.tsx | 42 ++++++++++++++-- .../createWorkspaceXService.ts | 29 ++++++++++- 5 files changed, 136 insertions(+), 12 deletions(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 8101772232e1e..fd696d5f0edb1 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -329,7 +329,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req Message: fmt.Sprintf("Workspace %q already exists in the %q template.", createWorkspace.Name, template.Name), Validations: []codersdk.ValidationError{{ Field: "name", - Detail: "this value is already in use and should be unique", + Detail: "This value is already in use and should be unique.", }}, }) return diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index d534c0bbc9f26..2359ccb42a4e1 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -5,7 +5,7 @@ import { useNavigate, useParams } from "react-router-dom" import { useOrganizationId } from "../../hooks/useOrganizationId" import { pageTitle } from "../../util/page" import { createWorkspaceMachine } from "../../xServices/createWorkspace/createWorkspaceXService" -import { CreateWorkspacePageView } from "./CreateWorkspacePageView" +import { CreateWorkspaceErrors, CreateWorkspacePageView } from "./CreateWorkspacePageView" const CreateWorkspacePage: FC = () => { const organizationId = useOrganizationId() @@ -21,6 +21,15 @@ const CreateWorkspacePage: FC = () => { }, }) + const { + templates, + templateSchema, + selectedTemplate, + getTemplateSchemaError, + getTemplatesError, + createWorkspaceError, + } = createWorkspaceState.context + return ( <> @@ -30,10 +39,16 @@ const CreateWorkspacePage: FC = () => { loadingTemplates={createWorkspaceState.matches("gettingTemplates")} loadingTemplateSchema={createWorkspaceState.matches("gettingTemplateSchema")} creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")} - templateName={createWorkspaceState.context.templateName} - templates={createWorkspaceState.context.templates} - selectedTemplate={createWorkspaceState.context.selectedTemplate} - templateSchema={createWorkspaceState.context.templateSchema} + hasTemplateErrors={createWorkspaceState.matches("error")} + templateName={templateName} + templates={templates} + selectedTemplate={selectedTemplate} + templateSchema={templateSchema} + createWorkspaceErrors={{ + [CreateWorkspaceErrors.GET_TEMPLATES_ERROR]: getTemplatesError, + [CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]: getTemplateSchemaError, + [CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]: createWorkspaceError, + }} onCancel={() => { navigate("/templates") }} diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx index 926e0d61f286d..61b689029d403 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx @@ -1,7 +1,11 @@ import { ComponentMeta, Story } from "@storybook/react" import { ParameterSchema } from "../../api/typesGenerated" -import { MockTemplate } from "../../testHelpers/entities" -import { CreateWorkspacePageView, CreateWorkspacePageViewProps } from "./CreateWorkspacePageView" +import { makeMockApiError, MockTemplate } from "../../testHelpers/entities" +import { + CreateWorkspaceErrors, + CreateWorkspacePageView, + CreateWorkspacePageViewProps, +} from "./CreateWorkspacePageView" const createParameterSchema = (partial: Partial): ParameterSchema => { return { @@ -40,6 +44,7 @@ NoParameters.args = { templates: [MockTemplate], selectedTemplate: MockTemplate, templateSchema: [], + createWorkspaceErrors: {}, } export const Parameters = Template.bind({}) @@ -60,4 +65,45 @@ Parameters.args = { validation_contains: ["Small", "Medium", "Big"], }), ], + createWorkspaceErrors: {}, +} + +export const GetTemplatesError = Template.bind({}) +GetTemplatesError.args = { + ...Parameters.args, + createWorkspaceErrors: { + [CreateWorkspaceErrors.GET_TEMPLATES_ERROR]: makeMockApiError({ + message: "Failed to fetch templates.", + detail: "You do not have permission to access this resource.", + }), + }, + hasTemplateErrors: true, +} + +export const GetTemplateSchemaError = Template.bind({}) +GetTemplateSchemaError.args = { + ...Parameters.args, + createWorkspaceErrors: { + [CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]: makeMockApiError({ + message: 'Failed to fetch template schema for "docker-amd64".', + detail: "You do not have permission to access this resource.", + }), + }, + hasTemplateErrors: true, +} + +export const CreateWorkspaceError = Template.bind({}) +CreateWorkspaceError.args = { + ...Parameters.args, + createWorkspaceErrors: { + [CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]: makeMockApiError({ + message: 'Workspace "test" already exists in the "docker-amd64" template.', + validations: [ + { + field: "name", + detail: "This value is already in use and should be unique.", + }, + ], + }), + }, } diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 1b4e27d3b08e5..9fdb53ba117f3 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -1,5 +1,6 @@ import { makeStyles } from "@material-ui/core/styles" import TextField from "@material-ui/core/TextField" +import { ErrorSummary } from "components/ErrorSummary/ErrorSummary" import { FormikContextType, useFormik } from "formik" import { FC, useState } from "react" import * as Yup from "yup" @@ -9,21 +10,29 @@ import { FullPageForm } from "../../components/FullPageForm/FullPageForm" import { Loader } from "../../components/Loader/Loader" import { ParameterInput } from "../../components/ParameterInput/ParameterInput" import { Stack } from "../../components/Stack/Stack" -import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formUtils" +import { getFormHelpersWithError, nameValidator, onChangeTrimmed } from "../../util/formUtils" export const Language = { templateLabel: "Template", nameLabel: "Name", } +export enum CreateWorkspaceErrors { + GET_TEMPLATES_ERROR = "getTemplatesError", + GET_TEMPLATE_SCHEMA_ERROR = "getTemplateSchemaError", + CREATE_WORKSPACE_ERROR = "createWorkspaceError", +} + export interface CreateWorkspacePageViewProps { loadingTemplates: boolean loadingTemplateSchema: boolean creatingWorkspace: boolean + hasTemplateErrors: boolean templateName: string templates?: TypesGen.Template[] selectedTemplate?: TypesGen.Template templateSchema?: TypesGen.ParameterSchema[] + createWorkspaceErrors: Partial> onCancel: () => void onSubmit: (req: TypesGen.CreateWorkspaceRequest) => void } @@ -62,18 +71,45 @@ export const CreateWorkspacePageView: FC = (props) source_value: value, }) }) - return props.onSubmit({ + props.onSubmit({ ...request, parameter_values: createRequests, }) + form.setSubmitting(false) }, }) - const getFieldHelpers = getFormHelpers(form) + + const getFieldHelpers = getFormHelpersWithError( + form, + props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR], + ) + + if (props.hasTemplateErrors) { + return ( + + {props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] && ( + + )} + {props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] && ( + + )} + + ) + } return (
+ {props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] && ( + + )} event.request, }), + assignCreateWorkspaceError: assign({ + createWorkspaceError: (_, event) => event.data, + }), + clearCreateWorkspaceError: assign({ + createWorkspaceError: (_) => undefined, + }), + assignGetTemplatesError: assign({ + getTemplatesError: (_, event) => event.data, + }), + clearGetTemplatesError: assign({ + getTemplatesError: (_) => undefined, + }), + assignGetTemplateSchemaError: assign({ + getTemplateSchemaError: (_, event) => event.data, + }), + clearGetTemplateSchemaError: assign({ + getTemplateSchemaError: (_) => undefined, + }), }, }, ) From ce2b2f3210575502608279d747dc8cc77c45b59d Mon Sep 17 00:00:00 2001 From: Abhineet Jain Date: Mon, 1 Aug 2022 20:26:28 +0000 Subject: [PATCH 2/3] fix story with form error --- .../CreateWorkspacePage/CreateWorkspacePageView.stories.tsx | 3 +++ .../pages/CreateWorkspacePage/CreateWorkspacePageView.tsx | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx index 61b689029d403..0554389fd3924 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx @@ -106,4 +106,7 @@ CreateWorkspaceError.args = { ], }), }, + initialTouched: { + name: true, + }, } diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 9fdb53ba117f3..564891ccae025 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -1,7 +1,7 @@ import { makeStyles } from "@material-ui/core/styles" import TextField from "@material-ui/core/TextField" import { ErrorSummary } from "components/ErrorSummary/ErrorSummary" -import { FormikContextType, useFormik } from "formik" +import { FormikContextType, FormikTouched, useFormik } from "formik" import { FC, useState } from "react" import * as Yup from "yup" import * as TypesGen from "../../api/typesGenerated" @@ -35,6 +35,8 @@ export interface CreateWorkspacePageViewProps { createWorkspaceErrors: Partial> onCancel: () => void onSubmit: (req: TypesGen.CreateWorkspaceRequest) => void + // initialTouched is only used for testing the error state of the form. + initialTouched?: FormikTouched } export const validationSchema = Yup.object({ @@ -53,6 +55,7 @@ export const CreateWorkspacePageView: FC = (props) }, enableReinitialize: true, validationSchema, + initialTouched: props.initialTouched, onSubmit: (request) => { if (!props.templateSchema) { throw new Error("No template schema loaded") From 50ee56951852a46b3ae9da4296a6cd51d2a9eee3 Mon Sep 17 00:00:00 2001 From: Abhineet Jain Date: Tue, 2 Aug 2022 05:53:55 +0000 Subject: [PATCH 3/3] fix: chromatic workflow filter --- .github/workflows/coder.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/coder.yaml b/.github/workflows/coder.yaml index ca09ef2629607..f25857fbb09bf 100644 --- a/.github/workflows/coder.yaml +++ b/.github/workflows/coder.yaml @@ -68,10 +68,7 @@ jobs: sh: - "**.sh" ts: - - "**.tsx?" - - "**.jsx?" - - "**.lock" - - "**.json" + - 'site/**' - id: debug run: | echo "${{ toJSON(steps.filter )}}"