Skip to content

fix: handle workspace errors #3341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 5, 2022
5 changes: 3 additions & 2 deletions site/src/components/Resources/Resources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import TableContainer from "@material-ui/core/TableContainer"
import TableHead from "@material-ui/core/TableHead"
import TableRow from "@material-ui/core/TableRow"
import useTheme from "@material-ui/styles/useTheme"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FC } from "react"
import { Workspace, WorkspaceResource } from "../../api/typesGenerated"
import { AvatarData } from "../../components/AvatarData/AvatarData"
Expand All @@ -28,7 +29,7 @@ const Language = {

interface ResourcesProps {
resources?: WorkspaceResource[]
getResourcesError?: Error
getResourcesError?: Error | unknown
workspace: Workspace
canUpdateWorkspace: boolean
}
Expand All @@ -45,7 +46,7 @@ export const Resources: FC<ResourcesProps> = ({
return (
<div aria-label={Language.resources} className={styles.wrapper}>
{getResourcesError ? (
{ getResourcesError }
<ErrorSummary error={getResourcesError} />
) : (
<TableContainer className={styles.tableContainer}>
<Table>
Expand Down
33 changes: 32 additions & 1 deletion site/src/components/Workspace/Workspace.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { action } from "@storybook/addon-actions"
import { Story } from "@storybook/react"
import * as Mocks from "../../testHelpers/entities"
import { Workspace, WorkspaceProps } from "./Workspace"
import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace"

export default {
title: "components/Workspace",
Expand Down Expand Up @@ -31,6 +31,7 @@ Started.args = {
resources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
builds: [Mocks.MockWorkspaceBuild],
canUpdateWorkspace: true,
workspaceErrors: {},
}

export const WithoutUpdateAccess = Template.bind({})
Expand Down Expand Up @@ -71,6 +72,11 @@ Error.args = {
transition: "start",
},
},
workspaceErrors: {
[WorkspaceErrors.BUILD_ERROR]: Mocks.makeMockApiError({
message: "A workspace build is already active.",
}),
},
}

export const Deleting = Template.bind({})
Expand All @@ -95,10 +101,35 @@ export const Canceled = Template.bind({})
Canceled.args = {
...Started.args,
workspace: Mocks.MockCanceledWorkspace,
workspaceErrors: {
[WorkspaceErrors.CANCELLATION_MESSAGE]: Mocks.makeMockApiError({
message: "Job has been marked as canceled...",
}),
},
}

export const Outdated = Template.bind({})
Outdated.args = {
...Started.args,
workspace: Mocks.MockOutdatedWorkspace,
}

export const GetBuildsError = Template.bind({})
GetBuildsError.args = {
...Started.args,
workspaceErrors: {
[WorkspaceErrors.GET_BUILDS_ERROR]: Mocks.makeMockApiError({
message: "There is a problem fetching builds.",
}),
},
}

export const GetResourcesError = Template.bind({})
GetResourcesError.args = {
...Started.args,
workspaceErrors: {
[WorkspaceErrors.GET_RESOURCES_ERROR]: Mocks.makeMockApiError({
message: "There is a problem fetching workspace resources.",
}),
},
}
28 changes: 24 additions & 4 deletions site/src/components/Workspace/Workspace.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeStyles } from "@material-ui/core/styles"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
import { FC } from "react"
import { useNavigate } from "react-router-dom"
Expand All @@ -15,6 +16,13 @@ import { WorkspaceScheduleButton } from "../WorkspaceScheduleButton/WorkspaceSch
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"

export enum WorkspaceErrors {
GET_RESOURCES_ERROR = "getResourcesError",
GET_BUILDS_ERROR = "getBuildsError",
BUILD_ERROR = "buildError",
CANCELLATION_MESSAGE = "cancellationMessage",
}

export interface WorkspaceProps {
bannerProps: {
isLoading?: boolean
Expand All @@ -31,9 +39,9 @@ export interface WorkspaceProps {
handleCancel: () => void
workspace: TypesGen.Workspace
resources?: TypesGen.WorkspaceResource[]
getResourcesError?: Error
builds?: TypesGen.WorkspaceBuild[]
canUpdateWorkspace: boolean
workspaceErrors: Partial<Record<WorkspaceErrors, Error | unknown>>
}

/**
Expand All @@ -49,15 +57,23 @@ export const Workspace: FC<WorkspaceProps> = ({
handleCancel,
workspace,
resources,
getResourcesError,
builds,
canUpdateWorkspace,
workspaceErrors,
}) => {
const styles = useStyles()
const navigate = useNavigate()

return (
<Margins>
<Stack spacing={1}>
{workspaceErrors[WorkspaceErrors.BUILD_ERROR] && (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]} dismissible />
)}
{workspaceErrors[WorkspaceErrors.CANCELLATION_MESSAGE] && (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.CANCELLATION_MESSAGE]} dismissible />
)}
</Stack>
<PageHeader
actions={
<Stack direction="row" spacing={1} className={styles.actions}>
Expand Down Expand Up @@ -101,14 +117,18 @@ export const Workspace: FC<WorkspaceProps> = ({
{!!resources && !!resources.length && (
<Resources
resources={resources}
getResourcesError={getResourcesError}
getResourcesError={workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]}
workspace={workspace}
canUpdateWorkspace={canUpdateWorkspace}
/>
)}

<WorkspaceSection title="Logs" contentsProps={{ className: styles.timelineContents }}>
<BuildsTable builds={builds} className={styles.timelineTable} />
{workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR]} />
) : (
<BuildsTable builds={builds} className={styles.timelineTable} />
)}
</WorkspaceSection>
</Stack>
</Stack>
Expand Down
39 changes: 34 additions & 5 deletions site/src/pages/WorkspacePage/WorkspacePage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { makeStyles } from "@material-ui/core/styles"
import { useMachine, useSelector } from "@xstate/react"
import dayjs from "dayjs"
import minMax from "dayjs/plugin/minMax"
Expand All @@ -7,7 +8,7 @@ import { useParams } from "react-router-dom"
import { DeleteWorkspaceDialog } from "../../components/DeleteWorkspaceDialog/DeleteWorkspaceDialog"
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
import { Workspace } from "../../components/Workspace/Workspace"
import { Workspace, WorkspaceErrors } from "../../components/Workspace/Workspace"
import { firstOrItem } from "../../util/array"
import { pageTitle } from "../../util/page"
import { getFaviconByStatus } from "../../util/workspace"
Expand All @@ -31,13 +32,25 @@ export const WorkspacePage: React.FC = () => {
userId: me?.id,
},
})
const { workspace, resources, getWorkspaceError, getResourcesError, builds, permissions } =
workspaceState.context
const {
workspace,
getWorkspaceError,
resources,
getResourcesError,
builds,
getBuildsError,
permissions,
checkPermissionsError,
buildError,
cancellationMessage,
} = workspaceState.context

const canUpdateWorkspace = !!permissions?.updateWorkspace

const [bannerState, bannerSend] = useMachine(workspaceScheduleBannerMachine)

const styles = useStyles()

/**
* Get workspace, template, and organization on mount and whenever workspaceId changes.
* workspaceSend should not change.
Expand All @@ -47,7 +60,12 @@ export const WorkspacePage: React.FC = () => {
}, [username, workspaceName, workspaceSend])

if (workspaceState.matches("error")) {
return <ErrorSummary error={getWorkspaceError} />
return (
<div className={styles.error}>
{getWorkspaceError && <ErrorSummary error={getWorkspaceError} />}
{checkPermissionsError && <ErrorSummary error={checkPermissionsError} />}
</div>
)
} else if (!workspace) {
return <FullScreenLoader />
} else {
Expand Down Expand Up @@ -100,9 +118,14 @@ export const WorkspacePage: React.FC = () => {
handleUpdate={() => workspaceSend("UPDATE")}
handleCancel={() => workspaceSend("CANCEL")}
resources={resources}
getResourcesError={getResourcesError instanceof Error ? getResourcesError : undefined}
builds={builds}
canUpdateWorkspace={canUpdateWorkspace}
workspaceErrors={{
[WorkspaceErrors.GET_RESOURCES_ERROR]: getResourcesError,
[WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError,
[WorkspaceErrors.BUILD_ERROR]: buildError,
[WorkspaceErrors.CANCELLATION_MESSAGE]: cancellationMessage,
}}
/>
<DeleteWorkspaceDialog
isOpen={workspaceState.matches({ ready: { build: "askingDelete" } })}
Expand All @@ -121,3 +144,9 @@ export const boundedDeadline = (newDeadline: dayjs.Dayjs, now: dayjs.Dayjs): day
const maxDeadline = now.add(24, "hours")
return dayjs.min(dayjs.max(minDeadline, newDeadline), maxDeadline)
}

const useStyles = makeStyles((theme) => ({
error: {
margin: theme.spacing(2),
},
}))
16 changes: 5 additions & 11 deletions site/src/xServices/workspace/workspaceXService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface WorkspaceContext {
builds?: TypesGen.WorkspaceBuild[]
getBuildsError?: Error | unknown
loadMoreBuildsError?: Error | unknown
cancellationMessage: string
cancellationMessage: Error | unknown
// permissions
permissions?: Permissions
checkPermissionsError?: Error | unknown
Expand Down Expand Up @@ -213,7 +213,7 @@ export const workspaceMachine = createMachine(
},
onError: {
target: "idle",
actions: ["assignBuildError", "displayBuildError"],
actions: ["assignBuildError"],
},
},
},
Expand All @@ -228,7 +228,7 @@ export const workspaceMachine = createMachine(
},
onError: {
target: "idle",
actions: ["assignBuildError", "displayBuildError"],
actions: ["assignBuildError"],
},
},
},
Expand All @@ -243,7 +243,7 @@ export const workspaceMachine = createMachine(
},
onError: {
target: "idle",
actions: ["assignBuildError", "displayBuildError"],
actions: ["assignBuildError"],
},
},
},
Expand All @@ -258,7 +258,7 @@ export const workspaceMachine = createMachine(
},
onError: {
target: "idle",
actions: ["assignCancellationMessage", "displayCancellationError"],
actions: ["assignCancellationMessage"],
},
},
},
Expand Down Expand Up @@ -395,9 +395,6 @@ export const workspaceMachine = createMachine(
assign({
buildError: event.data,
}),
displayBuildError: () => {
displayError(Language.buildError)
},
clearBuildError: (_) =>
assign({
buildError: undefined,
Expand All @@ -410,9 +407,6 @@ export const workspaceMachine = createMachine(
assign({
cancellationMessage: undefined,
}),
displayCancellationError: (context) => {
displayError(context.cancellationMessage)
},
assignRefreshWorkspaceError: (_, event) =>
assign({
refreshWorkspaceError: event.data,
Expand Down