diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index df07c59c1c..5a49e0fa57 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -9,6 +9,7 @@ import type { ProvisionerJobLog } from "api/typesGenerated"; import { action } from "storybook/actions"; import type { WorkspacePermissions } from "../../modules/workspaces/permissions"; import { Workspace } from "./Workspace"; +import { defaultPermissions } from "./WorkspaceNotifications/WorkspaceNotifications.stories"; // Helper function to create timestamps easily - Copied from AppStatuses.stories.tsx const createTimestamp = ( @@ -349,6 +350,23 @@ export const Stopping: Story = { }, }; +export const Unhealthy: Story = { + args: { + ...Running.args, + workspace: Mocks.MockUnhealthyWorkspace, + }, +}; + +export const UnhealthyWithoutUpdatePermission: Story = { + args: { + ...Unhealthy.args, + permissions: { + ...defaultPermissions, + updateWorkspace: false, + }, + }, +}; + export const FailedWithLogs: Story = { args: { ...Running.args, diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index b81605dc23..b1eda16180 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -21,6 +21,8 @@ import { WorkspaceBuildProgress, } from "./WorkspaceBuildProgress"; import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner"; +import { NotificationActionButton } from "./WorkspaceNotifications/Notifications"; +import { findTroubleshootingURL } from "./WorkspaceNotifications/WorkspaceNotifications"; import { WorkspaceTopbar } from "./WorkspaceTopbar"; interface WorkspaceProps { @@ -97,6 +99,8 @@ export const Workspace: FC = ({ (workspace.latest_build.matched_provisioners?.available ?? 1) > 0; const shouldShowProvisionerAlert = workspacePending && !haveBuildLogs && !provisionersHealthy && !isRestarting; + const troubleshootingURL = findTroubleshootingURL(workspace.latest_build); + const hasActions = permissions.updateWorkspace || troubleshootingURL; return (
@@ -194,6 +198,41 @@ export const Workspace: FC = ({ )} + {!workspace.health.healthy && ( + + Workspace is unhealthy + +

+ Your workspace is running but{" "} + {workspace.health.failing_agents.length > 1 + ? `${workspace.health.failing_agents.length} agents are unhealthy` + : "1 agent is unhealthy"} + . +

+ {hasActions && ( +
+ {permissions.updateWorkspace && ( + handleRestart()} + > + Restart + + )} + {troubleshootingURL && ( + + window.open(troubleshootingURL, "_blank") + } + > + Troubleshooting + + )} +
+ )} +
+
+ )} + {transitionStats !== undefined && ( >; -const findTroubleshootingURL = ( +export const findTroubleshootingURL = ( workspaceBuild: WorkspaceBuild, ): string | undefined => { for (const resource of workspaceBuild.resources) { diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index c130c95218..993b012bc0 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -994,6 +994,15 @@ export const MockWorkspaceSubAgent: TypesGen.WorkspaceAgent = { ], }; +const MockWorkspaceUnhealthyAgent: TypesGen.WorkspaceAgent = { + ...MockWorkspaceAgent, + id: "test-workspace-unhealthy-agent", + name: "a-workspace-unhealthy-agent", + status: "timeout", + lifecycle_state: "start_error", + health: { healthy: false }, +}; + export const MockWorkspaceAppStatus: TypesGen.WorkspaceAppStatus = { id: "test-app-status", created_at: "2022-05-17T17:39:01.382927298Z", @@ -1445,6 +1454,20 @@ export const MockStoppingWorkspace: TypesGen.Workspace = { status: "stopping", }, }; +export const MockUnhealthyWorkspace: TypesGen.Workspace = { + ...MockWorkspace, + id: "test-unhealthy-workspace", + health: { + healthy: false, + failing_agents: [MockWorkspaceUnhealthyAgent.id], + }, + latest_build: { + ...MockWorkspace.latest_build, + resources: [ + { ...MockWorkspaceResource, agents: [MockWorkspaceUnhealthyAgent] }, + ], + }, +}; export const MockStartingWorkspace: TypesGen.Workspace = { ...MockWorkspace, id: "test-starting-workspace",