Skip to content

Commit cf632d5

Browse files
committed
fix: handle create workspace errors
1 parent 66a5b0f commit cf632d5

File tree

5 files changed

+136
-12
lines changed

5 files changed

+136
-12
lines changed

coderd/workspaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
329329
Message: fmt.Sprintf("Workspace %q already exists in the %q template.", createWorkspace.Name, template.Name),
330330
Validations: []codersdk.ValidationError{{
331331
Field: "name",
332-
Detail: "this value is already in use and should be unique",
332+
Detail: "This value is already in use and should be unique.",
333333
}},
334334
})
335335
return

site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useNavigate, useParams } from "react-router-dom"
55
import { useOrganizationId } from "../../hooks/useOrganizationId"
66
import { pageTitle } from "../../util/page"
77
import { createWorkspaceMachine } from "../../xServices/createWorkspace/createWorkspaceXService"
8-
import { CreateWorkspacePageView } from "./CreateWorkspacePageView"
8+
import { CreateWorkspaceErrors, CreateWorkspacePageView } from "./CreateWorkspacePageView"
99

1010
const CreateWorkspacePage: FC = () => {
1111
const organizationId = useOrganizationId()
@@ -21,6 +21,15 @@ const CreateWorkspacePage: FC = () => {
2121
},
2222
})
2323

24+
const {
25+
templates,
26+
templateSchema,
27+
selectedTemplate,
28+
getTemplateSchemaError,
29+
getTemplatesError,
30+
createWorkspaceError,
31+
} = createWorkspaceState.context
32+
2433
return (
2534
<>
2635
<Helmet>
@@ -30,10 +39,16 @@ const CreateWorkspacePage: FC = () => {
3039
loadingTemplates={createWorkspaceState.matches("gettingTemplates")}
3140
loadingTemplateSchema={createWorkspaceState.matches("gettingTemplateSchema")}
3241
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}
33-
templateName={createWorkspaceState.context.templateName}
34-
templates={createWorkspaceState.context.templates}
35-
selectedTemplate={createWorkspaceState.context.selectedTemplate}
36-
templateSchema={createWorkspaceState.context.templateSchema}
42+
hasTemplateErrors={createWorkspaceState.matches("error")}
43+
templateName={templateName}
44+
templates={templates}
45+
selectedTemplate={selectedTemplate}
46+
templateSchema={templateSchema}
47+
createWorkspaceErrors={{
48+
[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]: getTemplatesError,
49+
[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]: getTemplateSchemaError,
50+
[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]: createWorkspaceError,
51+
}}
3752
onCancel={() => {
3853
navigate("/templates")
3954
}}

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { ComponentMeta, Story } from "@storybook/react"
22
import { ParameterSchema } from "../../api/typesGenerated"
3-
import { MockTemplate } from "../../testHelpers/entities"
4-
import { CreateWorkspacePageView, CreateWorkspacePageViewProps } from "./CreateWorkspacePageView"
3+
import { makeMockApiError, MockTemplate } from "../../testHelpers/entities"
4+
import {
5+
CreateWorkspaceErrors,
6+
CreateWorkspacePageView,
7+
CreateWorkspacePageViewProps,
8+
} from "./CreateWorkspacePageView"
59

610
const createParameterSchema = (partial: Partial<ParameterSchema>): ParameterSchema => {
711
return {
@@ -40,6 +44,7 @@ NoParameters.args = {
4044
templates: [MockTemplate],
4145
selectedTemplate: MockTemplate,
4246
templateSchema: [],
47+
createWorkspaceErrors: {},
4348
}
4449

4550
export const Parameters = Template.bind({})
@@ -60,4 +65,45 @@ Parameters.args = {
6065
validation_contains: ["Small", "Medium", "Big"],
6166
}),
6267
],
68+
createWorkspaceErrors: {},
69+
}
70+
71+
export const GetTemplatesError = Template.bind({})
72+
GetTemplatesError.args = {
73+
...Parameters.args,
74+
createWorkspaceErrors: {
75+
[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]: makeMockApiError({
76+
message: "Failed to fetch templates.",
77+
detail: "You do not have permission to access this resource.",
78+
}),
79+
},
80+
hasTemplateErrors: true,
81+
}
82+
83+
export const GetTemplateSchemaError = Template.bind({})
84+
GetTemplateSchemaError.args = {
85+
...Parameters.args,
86+
createWorkspaceErrors: {
87+
[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]: makeMockApiError({
88+
message: 'Failed to fetch template schema for "docker-amd64".',
89+
detail: "You do not have permission to access this resource.",
90+
}),
91+
},
92+
hasTemplateErrors: true,
93+
}
94+
95+
export const CreateWorkspaceError = Template.bind({})
96+
CreateWorkspaceError.args = {
97+
...Parameters.args,
98+
createWorkspaceErrors: {
99+
[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]: makeMockApiError({
100+
message: 'Workspace "test" already exists in the "docker-amd64" template.',
101+
validations: [
102+
{
103+
field: "name",
104+
detail: "This value is already in use and should be unique.",
105+
},
106+
],
107+
}),
108+
},
63109
}

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { makeStyles } from "@material-ui/core/styles"
22
import TextField from "@material-ui/core/TextField"
3+
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
34
import { FormikContextType, useFormik } from "formik"
45
import { FC, useState } from "react"
56
import * as Yup from "yup"
@@ -9,21 +10,29 @@ import { FullPageForm } from "../../components/FullPageForm/FullPageForm"
910
import { Loader } from "../../components/Loader/Loader"
1011
import { ParameterInput } from "../../components/ParameterInput/ParameterInput"
1112
import { Stack } from "../../components/Stack/Stack"
12-
import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formUtils"
13+
import { getFormHelpersWithError, nameValidator, onChangeTrimmed } from "../../util/formUtils"
1314

1415
export const Language = {
1516
templateLabel: "Template",
1617
nameLabel: "Name",
1718
}
1819

20+
export enum CreateWorkspaceErrors {
21+
GET_TEMPLATES_ERROR = "getTemplatesError",
22+
GET_TEMPLATE_SCHEMA_ERROR = "getTemplateSchemaError",
23+
CREATE_WORKSPACE_ERROR = "createWorkspaceError",
24+
}
25+
1926
export interface CreateWorkspacePageViewProps {
2027
loadingTemplates: boolean
2128
loadingTemplateSchema: boolean
2229
creatingWorkspace: boolean
30+
hasTemplateErrors: boolean
2331
templateName: string
2432
templates?: TypesGen.Template[]
2533
selectedTemplate?: TypesGen.Template
2634
templateSchema?: TypesGen.ParameterSchema[]
35+
createWorkspaceErrors: Partial<Record<CreateWorkspaceErrors, Error | unknown>>
2736
onCancel: () => void
2837
onSubmit: (req: TypesGen.CreateWorkspaceRequest) => void
2938
}
@@ -62,18 +71,45 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
6271
source_value: value,
6372
})
6473
})
65-
return props.onSubmit({
74+
props.onSubmit({
6675
...request,
6776
parameter_values: createRequests,
6877
})
78+
form.setSubmitting(false)
6979
},
7080
})
71-
const getFieldHelpers = getFormHelpers<TypesGen.CreateWorkspaceRequest>(form)
81+
82+
const getFieldHelpers = getFormHelpersWithError<TypesGen.CreateWorkspaceRequest>(
83+
form,
84+
props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR],
85+
)
86+
87+
if (props.hasTemplateErrors) {
88+
return (
89+
<Stack>
90+
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] && (
91+
<ErrorSummary
92+
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]}
93+
/>
94+
)}
95+
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] && (
96+
<ErrorSummary
97+
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]}
98+
/>
99+
)}
100+
</Stack>
101+
)
102+
}
72103

73104
return (
74105
<FullPageForm title="Create workspace" onCancel={props.onCancel}>
75106
<form onSubmit={form.handleSubmit}>
76107
<Stack>
108+
{props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] && (
109+
<ErrorSummary
110+
error={props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]}
111+
/>
112+
)}
77113
<TextField
78114
disabled
79115
fullWidth

site/src/xServices/createWorkspace/createWorkspaceXService.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ type CreateWorkspaceContext = {
1515
templateSchema?: ParameterSchema[]
1616
createWorkspaceRequest?: CreateWorkspaceRequest
1717
createdWorkspace?: Workspace
18+
createWorkspaceError?: Error | unknown
19+
getTemplatesError?: Error | unknown
20+
getTemplateSchemaError?: Error | unknown
1821
}
1922

2023
type CreateWorkspaceEvent = {
@@ -44,6 +47,7 @@ export const createWorkspaceMachine = createMachine(
4447
tsTypes: {} as import("./createWorkspaceXService.typegen").Typegen0,
4548
states: {
4649
gettingTemplates: {
50+
entry: "clearGetTemplatesError",
4751
invoke: {
4852
src: "getTemplates",
4953
onDone: [
@@ -57,18 +61,21 @@ export const createWorkspaceMachine = createMachine(
5761
},
5862
],
5963
onError: {
64+
actions: ["assignGetTemplatesError"],
6065
target: "error",
6166
},
6267
},
6368
},
6469
gettingTemplateSchema: {
70+
entry: "clearGetTemplateSchemaError",
6571
invoke: {
6672
src: "getTemplateSchema",
6773
onDone: {
6874
actions: ["assignTemplateSchema"],
6975
target: "fillingParams",
7076
},
7177
onError: {
78+
actions: ["assignGetTemplateSchemaError"],
7279
target: "error",
7380
},
7481
},
@@ -82,14 +89,16 @@ export const createWorkspaceMachine = createMachine(
8289
},
8390
},
8491
creatingWorkspace: {
92+
entry: "clearCreateWorkspaceError",
8593
invoke: {
8694
src: "createWorkspace",
8795
onDone: {
8896
actions: ["onCreateWorkspace"],
8997
target: "created",
9098
},
9199
onError: {
92-
target: "error",
100+
actions: ["assignCreateWorkspaceError"],
101+
target: "fillingParams",
93102
},
94103
},
95104
},
@@ -142,6 +151,24 @@ export const createWorkspaceMachine = createMachine(
142151
assignCreateWorkspaceRequest: assign({
143152
createWorkspaceRequest: (_, event) => event.request,
144153
}),
154+
assignCreateWorkspaceError: assign({
155+
createWorkspaceError: (_, event) => event.data,
156+
}),
157+
clearCreateWorkspaceError: assign({
158+
createWorkspaceError: (_) => undefined,
159+
}),
160+
assignGetTemplatesError: assign({
161+
getTemplatesError: (_, event) => event.data,
162+
}),
163+
clearGetTemplatesError: assign({
164+
getTemplatesError: (_) => undefined,
165+
}),
166+
assignGetTemplateSchemaError: assign({
167+
getTemplateSchemaError: (_, event) => event.data,
168+
}),
169+
clearGetTemplateSchemaError: assign({
170+
getTemplateSchemaError: (_) => undefined,
171+
}),
145172
},
146173
},
147174
)

0 commit comments

Comments
 (0)