Skip to content

Commit e7bc013

Browse files
authored
fix: handle workspace errors (#3341)
1 parent 01fe5e6 commit e7bc013

File tree

5 files changed

+157
-69
lines changed

5 files changed

+157
-69
lines changed

site/src/components/Resources/Resources.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import TableContainer from "@material-ui/core/TableContainer"
66
import TableHead from "@material-ui/core/TableHead"
77
import TableRow from "@material-ui/core/TableRow"
88
import useTheme from "@material-ui/styles/useTheme"
9+
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
910
import { FC } from "react"
1011
import { Workspace, WorkspaceResource } from "../../api/typesGenerated"
1112
import { AvatarData } from "../../components/AvatarData/AvatarData"
@@ -28,7 +29,7 @@ const Language = {
2829

2930
interface ResourcesProps {
3031
resources?: WorkspaceResource[]
31-
getResourcesError?: Error
32+
getResourcesError?: Error | unknown
3233
workspace: Workspace
3334
canUpdateWorkspace: boolean
3435
}
@@ -45,7 +46,7 @@ export const Resources: FC<ResourcesProps> = ({
4546
return (
4647
<div aria-label={Language.resources} className={styles.wrapper}>
4748
{getResourcesError ? (
48-
{ getResourcesError }
49+
<ErrorSummary error={getResourcesError} />
4950
) : (
5051
<TableContainer className={styles.tableContainer}>
5152
<Table>

site/src/components/Workspace/Workspace.stories.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { action } from "@storybook/addon-actions"
22
import { Story } from "@storybook/react"
33
import * as Mocks from "../../testHelpers/entities"
4-
import { Workspace, WorkspaceProps } from "./Workspace"
4+
import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace"
55

66
export default {
77
title: "components/Workspace",
@@ -31,6 +31,7 @@ Started.args = {
3131
resources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
3232
builds: [Mocks.MockWorkspaceBuild],
3333
canUpdateWorkspace: true,
34+
workspaceErrors: {},
3435
}
3536

3637
export const WithoutUpdateAccess = Template.bind({})
@@ -71,6 +72,11 @@ Error.args = {
7172
transition: "start",
7273
},
7374
},
75+
workspaceErrors: {
76+
[WorkspaceErrors.BUILD_ERROR]: Mocks.makeMockApiError({
77+
message: "A workspace build is already active.",
78+
}),
79+
},
7480
}
7581

7682
export const Deleting = Template.bind({})
@@ -102,3 +108,33 @@ Outdated.args = {
102108
...Started.args,
103109
workspace: Mocks.MockOutdatedWorkspace,
104110
}
111+
112+
export const GetBuildsError = Template.bind({})
113+
GetBuildsError.args = {
114+
...Started.args,
115+
workspaceErrors: {
116+
[WorkspaceErrors.GET_BUILDS_ERROR]: Mocks.makeMockApiError({
117+
message: "There is a problem fetching builds.",
118+
}),
119+
},
120+
}
121+
122+
export const GetResourcesError = Template.bind({})
123+
GetResourcesError.args = {
124+
...Started.args,
125+
workspaceErrors: {
126+
[WorkspaceErrors.GET_RESOURCES_ERROR]: Mocks.makeMockApiError({
127+
message: "There is a problem fetching workspace resources.",
128+
}),
129+
},
130+
}
131+
132+
export const CancellationError = Template.bind({})
133+
CancellationError.args = {
134+
...Error.args,
135+
workspaceErrors: {
136+
[WorkspaceErrors.CANCELLATION_ERROR]: Mocks.makeMockApiError({
137+
message: "Job could not be canceled.",
138+
}),
139+
},
140+
}

site/src/components/Workspace/Workspace.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { makeStyles } from "@material-ui/core/styles"
2+
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
23
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
34
import { FC } from "react"
45
import { useNavigate } from "react-router-dom"
@@ -15,6 +16,13 @@ import { WorkspaceScheduleButton } from "../WorkspaceScheduleButton/WorkspaceSch
1516
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
1617
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"
1718

19+
export enum WorkspaceErrors {
20+
GET_RESOURCES_ERROR = "getResourcesError",
21+
GET_BUILDS_ERROR = "getBuildsError",
22+
BUILD_ERROR = "buildError",
23+
CANCELLATION_ERROR = "cancellationError",
24+
}
25+
1826
export interface WorkspaceProps {
1927
bannerProps: {
2028
isLoading?: boolean
@@ -31,9 +39,9 @@ export interface WorkspaceProps {
3139
handleCancel: () => void
3240
workspace: TypesGen.Workspace
3341
resources?: TypesGen.WorkspaceResource[]
34-
getResourcesError?: Error
3542
builds?: TypesGen.WorkspaceBuild[]
3643
canUpdateWorkspace: boolean
44+
workspaceErrors: Partial<Record<WorkspaceErrors, Error | unknown>>
3745
}
3846

3947
/**
@@ -49,15 +57,23 @@ export const Workspace: FC<WorkspaceProps> = ({
4957
handleCancel,
5058
workspace,
5159
resources,
52-
getResourcesError,
5360
builds,
5461
canUpdateWorkspace,
62+
workspaceErrors,
5563
}) => {
5664
const styles = useStyles()
5765
const navigate = useNavigate()
5866

5967
return (
6068
<Margins>
69+
<Stack spacing={1}>
70+
{workspaceErrors[WorkspaceErrors.BUILD_ERROR] && (
71+
<ErrorSummary error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]} dismissible />
72+
)}
73+
{workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] && (
74+
<ErrorSummary error={workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]} dismissible />
75+
)}
76+
</Stack>
6177
<PageHeader
6278
actions={
6379
<Stack direction="row" spacing={1} className={styles.actions}>
@@ -101,14 +117,18 @@ export const Workspace: FC<WorkspaceProps> = ({
101117
{!!resources && !!resources.length && (
102118
<Resources
103119
resources={resources}
104-
getResourcesError={getResourcesError}
120+
getResourcesError={workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]}
105121
workspace={workspace}
106122
canUpdateWorkspace={canUpdateWorkspace}
107123
/>
108124
)}
109125

110126
<WorkspaceSection title="Logs" contentsProps={{ className: styles.timelineContents }}>
111-
<BuildsTable builds={builds} className={styles.timelineTable} />
127+
{workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? (
128+
<ErrorSummary error={workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR]} />
129+
) : (
130+
<BuildsTable builds={builds} className={styles.timelineTable} />
131+
)}
112132
</WorkspaceSection>
113133
</Stack>
114134
</Stack>

site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { makeStyles } from "@material-ui/core/styles"
12
import { useMachine, useSelector } from "@xstate/react"
23
import dayjs from "dayjs"
34
import minMax from "dayjs/plugin/minMax"
@@ -7,7 +8,7 @@ import { useParams } from "react-router-dom"
78
import { DeleteWorkspaceDialog } from "../../components/DeleteWorkspaceDialog/DeleteWorkspaceDialog"
89
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
910
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
10-
import { Workspace } from "../../components/Workspace/Workspace"
11+
import { Workspace, WorkspaceErrors } from "../../components/Workspace/Workspace"
1112
import { firstOrItem } from "../../util/array"
1213
import { pageTitle } from "../../util/page"
1314
import { getFaviconByStatus } from "../../util/workspace"
@@ -31,13 +32,25 @@ export const WorkspacePage: React.FC = () => {
3132
userId: me?.id,
3233
},
3334
})
34-
const { workspace, resources, getWorkspaceError, getResourcesError, builds, permissions } =
35-
workspaceState.context
35+
const {
36+
workspace,
37+
getWorkspaceError,
38+
resources,
39+
getResourcesError,
40+
builds,
41+
getBuildsError,
42+
permissions,
43+
checkPermissionsError,
44+
buildError,
45+
cancellationError,
46+
} = workspaceState.context
3647

3748
const canUpdateWorkspace = !!permissions?.updateWorkspace
3849

3950
const [bannerState, bannerSend] = useMachine(workspaceScheduleBannerMachine)
4051

52+
const styles = useStyles()
53+
4154
/**
4255
* Get workspace, template, and organization on mount and whenever workspaceId changes.
4356
* workspaceSend should not change.
@@ -47,7 +60,12 @@ export const WorkspacePage: React.FC = () => {
4760
}, [username, workspaceName, workspaceSend])
4861

4962
if (workspaceState.matches("error")) {
50-
return <ErrorSummary error={getWorkspaceError} />
63+
return (
64+
<div className={styles.error}>
65+
{getWorkspaceError && <ErrorSummary error={getWorkspaceError} />}
66+
{checkPermissionsError && <ErrorSummary error={checkPermissionsError} />}
67+
</div>
68+
)
5169
} else if (!workspace) {
5270
return <FullScreenLoader />
5371
} else {
@@ -100,9 +118,14 @@ export const WorkspacePage: React.FC = () => {
100118
handleUpdate={() => workspaceSend("UPDATE")}
101119
handleCancel={() => workspaceSend("CANCEL")}
102120
resources={resources}
103-
getResourcesError={getResourcesError instanceof Error ? getResourcesError : undefined}
104121
builds={builds}
105122
canUpdateWorkspace={canUpdateWorkspace}
123+
workspaceErrors={{
124+
[WorkspaceErrors.GET_RESOURCES_ERROR]: getResourcesError,
125+
[WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError,
126+
[WorkspaceErrors.BUILD_ERROR]: buildError,
127+
[WorkspaceErrors.CANCELLATION_ERROR]: cancellationError,
128+
}}
106129
/>
107130
<DeleteWorkspaceDialog
108131
isOpen={workspaceState.matches({ ready: { build: "askingDelete" } })}
@@ -121,3 +144,9 @@ export const boundedDeadline = (newDeadline: dayjs.Dayjs, now: dayjs.Dayjs): day
121144
const maxDeadline = now.add(24, "hours")
122145
return dayjs.min(dayjs.max(minDeadline, newDeadline), maxDeadline)
123146
}
147+
148+
const useStyles = makeStyles((theme) => ({
149+
error: {
150+
margin: theme.spacing(2),
151+
},
152+
}))

0 commit comments

Comments
 (0)