fix: handle create workspace errors (#3346)

This commit is contained in:
Abhineet Jain
2022-08-02 13:19:00 -04:00
committed by GitHub
parent 83c63d4a63
commit 8bcf23e60a
5 changed files with 143 additions and 13 deletions

View File

@ -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

View File

@ -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 (
<>
<Helmet>
@ -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")
}}

View File

@ -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>): ParameterSchema => {
return {
@ -40,6 +44,7 @@ NoParameters.args = {
templates: [MockTemplate],
selectedTemplate: MockTemplate,
templateSchema: [],
createWorkspaceErrors: {},
}
export const Parameters = Template.bind({})
@ -60,4 +65,48 @@ 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.",
},
],
}),
},
initialTouched: {
name: true,
},
}

View File

@ -1,6 +1,7 @@
import { makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import { FormikContextType, useFormik } from "formik"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FC, useState } from "react"
import * as Yup from "yup"
import * as TypesGen from "../../api/typesGenerated"
@ -9,23 +10,33 @@ 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<Record<CreateWorkspaceErrors, Error | unknown>>
onCancel: () => void
onSubmit: (req: TypesGen.CreateWorkspaceRequest) => void
// initialTouched is only used for testing the error state of the form.
initialTouched?: FormikTouched<TypesGen.CreateWorkspaceRequest>
}
export const validationSchema = Yup.object({
@ -44,6 +55,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
},
enableReinitialize: true,
validationSchema,
initialTouched: props.initialTouched,
onSubmit: (request) => {
if (!props.templateSchema) {
throw new Error("No template schema loaded")
@ -62,18 +74,45 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
source_value: value,
})
})
return props.onSubmit({
props.onSubmit({
...request,
parameter_values: createRequests,
})
form.setSubmitting(false)
},
})
const getFieldHelpers = getFormHelpers<TypesGen.CreateWorkspaceRequest>(form)
const getFieldHelpers = getFormHelpersWithError<TypesGen.CreateWorkspaceRequest>(
form,
props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR],
)
if (props.hasTemplateErrors) {
return (
<Stack>
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] && (
<ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]}
/>
)}
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] && (
<ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]}
/>
)}
</Stack>
)
}
return (
<FullPageForm title="Create workspace" onCancel={props.onCancel}>
<form onSubmit={form.handleSubmit}>
<Stack>
{props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] && (
<ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]}
/>
)}
<TextField
disabled
fullWidth

View File

@ -15,6 +15,9 @@ type CreateWorkspaceContext = {
templateSchema?: ParameterSchema[]
createWorkspaceRequest?: CreateWorkspaceRequest
createdWorkspace?: Workspace
createWorkspaceError?: Error | unknown
getTemplatesError?: Error | unknown
getTemplateSchemaError?: Error | unknown
}
type CreateWorkspaceEvent = {
@ -44,6 +47,7 @@ export const createWorkspaceMachine = createMachine(
tsTypes: {} as import("./createWorkspaceXService.typegen").Typegen0,
states: {
gettingTemplates: {
entry: "clearGetTemplatesError",
invoke: {
src: "getTemplates",
onDone: [
@ -57,11 +61,13 @@ export const createWorkspaceMachine = createMachine(
},
],
onError: {
actions: ["assignGetTemplatesError"],
target: "error",
},
},
},
gettingTemplateSchema: {
entry: "clearGetTemplateSchemaError",
invoke: {
src: "getTemplateSchema",
onDone: {
@ -69,6 +75,7 @@ export const createWorkspaceMachine = createMachine(
target: "fillingParams",
},
onError: {
actions: ["assignGetTemplateSchemaError"],
target: "error",
},
},
@ -82,6 +89,7 @@ export const createWorkspaceMachine = createMachine(
},
},
creatingWorkspace: {
entry: "clearCreateWorkspaceError",
invoke: {
src: "createWorkspace",
onDone: {
@ -89,7 +97,8 @@ export const createWorkspaceMachine = createMachine(
target: "created",
},
onError: {
target: "error",
actions: ["assignCreateWorkspaceError"],
target: "fillingParams",
},
},
},
@ -142,6 +151,24 @@ export const createWorkspaceMachine = createMachine(
assignCreateWorkspaceRequest: assign({
createWorkspaceRequest: (_, event) => 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,
}),
},
},
)