diff --git a/site/src/components/Resources/Resources.tsx b/site/src/components/Resources/Resources.tsx index debbf2bfe45d7..989ddda75dab6 100644 --- a/site/src/components/Resources/Resources.tsx +++ b/site/src/components/Resources/Resources.tsx @@ -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" @@ -28,7 +29,7 @@ const Language = { interface ResourcesProps { resources?: WorkspaceResource[] - getResourcesError?: Error + getResourcesError?: Error | unknown workspace: Workspace canUpdateWorkspace: boolean } @@ -45,7 +46,7 @@ export const Resources: FC = ({ return (
{getResourcesError ? ( - { getResourcesError } + ) : ( diff --git a/site/src/components/Workspace/Workspace.stories.tsx b/site/src/components/Workspace/Workspace.stories.tsx index c94da97054640..7a64657d4d5dd 100644 --- a/site/src/components/Workspace/Workspace.stories.tsx +++ b/site/src/components/Workspace/Workspace.stories.tsx @@ -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", @@ -31,6 +31,7 @@ Started.args = { resources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2], builds: [Mocks.MockWorkspaceBuild], canUpdateWorkspace: true, + workspaceErrors: {}, } export const WithoutUpdateAccess = Template.bind({}) @@ -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({}) @@ -102,3 +108,33 @@ 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.", + }), + }, +} + +export const CancellationError = Template.bind({}) +CancellationError.args = { + ...Error.args, + workspaceErrors: { + [WorkspaceErrors.CANCELLATION_ERROR]: Mocks.makeMockApiError({ + message: "Job could not be canceled.", + }), + }, +} diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index de4b4eb131daf..e52099fac1d16 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -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" @@ -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_ERROR = "cancellationError", +} + export interface WorkspaceProps { bannerProps: { isLoading?: boolean @@ -31,9 +39,9 @@ export interface WorkspaceProps { handleCancel: () => void workspace: TypesGen.Workspace resources?: TypesGen.WorkspaceResource[] - getResourcesError?: Error builds?: TypesGen.WorkspaceBuild[] canUpdateWorkspace: boolean + workspaceErrors: Partial> } /** @@ -49,15 +57,23 @@ export const Workspace: FC = ({ handleCancel, workspace, resources, - getResourcesError, builds, canUpdateWorkspace, + workspaceErrors, }) => { const styles = useStyles() const navigate = useNavigate() return ( + + {workspaceErrors[WorkspaceErrors.BUILD_ERROR] && ( + + )} + {workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] && ( + + )} + @@ -101,14 +117,18 @@ export const Workspace: FC = ({ {!!resources && !!resources.length && ( )} - + {workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? ( + + ) : ( + + )} diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 9a87398707711..99a25f994a813 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -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" @@ -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" @@ -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, + cancellationError, + } = 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. @@ -47,7 +60,12 @@ export const WorkspacePage: React.FC = () => { }, [username, workspaceName, workspaceSend]) if (workspaceState.matches("error")) { - return + return ( +
+ {getWorkspaceError && } + {checkPermissionsError && } +
+ ) } else if (!workspace) { return } else { @@ -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_ERROR]: cancellationError, + }} /> ({ + error: { + margin: theme.spacing(2), + }, +})) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index a6db5a6d61498..acdb7b0987cbe 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -3,7 +3,7 @@ import { pure } from "xstate/lib/actions" import * as API from "../../api/api" import * as Types from "../../api/types" import * as TypesGen from "../../api/typesGenerated" -import { displayError } from "../../components/GlobalSnackbar/utils" +import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => { // Cloning builds to not change the origin object with the sort() @@ -35,7 +35,8 @@ export interface WorkspaceContext { builds?: TypesGen.WorkspaceBuild[] getBuildsError?: Error | unknown loadMoreBuildsError?: Error | unknown - cancellationMessage: string + cancellationMessage?: Types.Message + cancellationError?: Error | unknown // permissions permissions?: Permissions checkPermissionsError?: Error | unknown @@ -97,6 +98,9 @@ export const workspaceMachine = createMachine( stopWorkspace: { data: TypesGen.WorkspaceBuild } + deleteWorkspace: { + data: TypesGen.WorkspaceBuild + } cancelWorkspace: { data: Types.Message } @@ -213,7 +217,7 @@ export const workspaceMachine = createMachine( }, onError: { target: "idle", - actions: ["assignBuildError", "displayBuildError"], + actions: ["assignBuildError"], }, }, }, @@ -228,7 +232,7 @@ export const workspaceMachine = createMachine( }, onError: { target: "idle", - actions: ["assignBuildError", "displayBuildError"], + actions: ["assignBuildError"], }, }, }, @@ -243,22 +247,26 @@ export const workspaceMachine = createMachine( }, onError: { target: "idle", - actions: ["assignBuildError", "displayBuildError"], + actions: ["assignBuildError"], }, }, }, requestingCancel: { - entry: "clearCancellationMessage", + entry: ["clearCancellationMessage", "clearCancellationError"], invoke: { id: "cancelWorkspace", src: "cancelWorkspace", onDone: { target: "idle", - actions: ["assignCancellationMessage", "refreshTimeline"], + actions: [ + "assignCancellationMessage", + "displayCancellationMessage", + "refreshTimeline", + ], }, onError: { target: "idle", - actions: ["assignCancellationMessage", "displayCancellationError"], + actions: ["assignCancellationError"], }, }, }, @@ -387,63 +395,57 @@ export const workspaceMachine = createMachine( clearGetPermissionsError: assign({ checkPermissionsError: (_) => undefined, }), - assignBuild: (_, event) => - assign({ - build: event.data, - }), - assignBuildError: (_, event) => - assign({ - buildError: event.data, - }), - displayBuildError: () => { - displayError(Language.buildError) - }, - clearBuildError: (_) => - assign({ - buildError: undefined, - }), - assignCancellationMessage: (_, event) => - assign({ - cancellationMessage: event.data, - }), - clearCancellationMessage: (_) => - assign({ - cancellationMessage: undefined, - }), - displayCancellationError: (context) => { - displayError(context.cancellationMessage) + assignBuild: assign({ + build: (_, event) => event.data, + }), + assignBuildError: assign({ + buildError: (_, event) => event.data, + }), + clearBuildError: assign({ + buildError: (_) => undefined, + }), + assignCancellationMessage: assign({ + cancellationMessage: (_, event) => event.data, + }), + clearCancellationMessage: assign({ + cancellationMessage: (_) => undefined, + }), + displayCancellationMessage: (context) => { + if (context.cancellationMessage) { + displaySuccess(context.cancellationMessage.message) + } }, - assignRefreshWorkspaceError: (_, event) => - assign({ - refreshWorkspaceError: event.data, - }), - clearRefreshWorkspaceError: (_) => - assign({ - refreshWorkspaceError: undefined, - }), - assignRefreshTemplateError: (_, event) => - assign({ - refreshTemplateError: event.data, - }), + assignCancellationError: assign({ + cancellationError: (_, event) => event.data, + }), + clearCancellationError: assign({ + cancellationError: (_) => undefined, + }), + assignRefreshWorkspaceError: assign({ + refreshWorkspaceError: (_, event) => event.data, + }), + clearRefreshWorkspaceError: assign({ + refreshWorkspaceError: (_) => undefined, + }), + assignRefreshTemplateError: assign({ + refreshTemplateError: (_, event) => event.data, + }), displayRefreshTemplateError: () => { displayError(Language.refreshTemplateError) }, - clearRefreshTemplateError: (_) => - assign({ - refreshTemplateError: undefined, - }), + clearRefreshTemplateError: assign({ + refreshTemplateError: (_) => undefined, + }), // Resources assignResources: assign({ resources: (_, event) => event.data, }), - assignGetResourcesError: (_, event) => - assign({ - getResourcesError: event.data, - }), - clearGetResourcesError: (_) => - assign({ - getResourcesError: undefined, - }), + assignGetResourcesError: assign({ + getResourcesError: (_, event) => event.data, + }), + clearGetResourcesError: assign({ + getResourcesError: (_) => undefined, + }), // Timeline assignBuilds: assign({ builds: (_, event) => event.data, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy