From e3f1b21c5264414351631cdf3c609843dca12a8f Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Mon, 18 Aug 2025 20:22:45 +0000 Subject: [PATCH 1/5] feat: show workspace health error alert above agents in WorkspacePage --- .../pages/WorkspacePage/Workspace.stories.tsx | 7 ++++ site/src/pages/WorkspacePage/Workspace.tsx | 37 +++++++++++++++++++ .../WorkspaceNotifications.tsx | 2 +- site/src/testHelpers/entities.ts | 8 ++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index 77ba7d67e94de..75081a669c14c 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -349,6 +349,13 @@ export const Stopping: Story = { }, }; +export const Unhealthy: Story = { + args: { + ...Running.args, + workspace: Mocks.MockUnhealthyWorkspace, + }, +}; + export const FailedWithLogs: Story = { args: { ...Running.args, diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 07c5ec26d0766..f5dc6e2ac7bc7 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -24,6 +24,8 @@ import { import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner"; import { WorkspaceTopbar } from "./WorkspaceTopbar"; import { resourceOptionValue, useResourcesNav } from "./useResourcesNav"; +import { findTroubleshootingURL } from "./WorkspaceNotifications/WorkspaceNotifications"; +import { NotificationActionButton } from "./WorkspaceNotifications/Notifications"; interface WorkspaceProps { workspace: TypesGen.Workspace; @@ -98,6 +100,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 (
= ({ )} + {!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 8aac9f6233615..d91774645e887 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1443,6 +1443,14 @@ export const MockStoppingWorkspace: TypesGen.Workspace = { status: "stopping", }, }; +export const MockUnhealthyWorkspace: TypesGen.Workspace = { + ...MockWorkspace, + id: "test-unhealthy-workspace", + health: { + healthy: false, + failing_agents: ["test-workspace-agent"], + }, +}; export const MockStartingWorkspace: TypesGen.Workspace = { ...MockWorkspace, id: "test-starting-workspace", From 2d5b7913229684e2d8ac3bfe87c64b7c605fcf71 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Tue, 19 Aug 2025 01:00:25 +0000 Subject: [PATCH 2/5] style: fix import order --- site/src/pages/WorkspacePage/Workspace.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index a67e75d4c017f..53dd01741ffe6 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -21,9 +21,9 @@ import { WorkspaceBuildProgress, } from "./WorkspaceBuildProgress"; import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner"; -import { WorkspaceTopbar } from "./WorkspaceTopbar"; -import { findTroubleshootingURL } from "./WorkspaceNotifications/WorkspaceNotifications"; import { NotificationActionButton } from "./WorkspaceNotifications/Notifications"; +import { findTroubleshootingURL } from "./WorkspaceNotifications/WorkspaceNotifications"; +import { WorkspaceTopbar } from "./WorkspaceTopbar"; interface WorkspaceProps { workspace: TypesGen.Workspace; From b6cfffced92dd8f38313ed26826a5cfa9a0f4627 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 21 Aug 2025 22:46:10 +0000 Subject: [PATCH 3/5] test: set MockUnhealthyWorkspace's agent to be in error state --- site/src/testHelpers/entities.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 71fcab75ec0c5..e29c9e9f43fd1 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -992,6 +992,15 @@ export const MockWorkspaceSubAgent: TypesGen.WorkspaceAgent = { ], }; +export 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", @@ -1448,7 +1457,13 @@ export const MockUnhealthyWorkspace: TypesGen.Workspace = { id: "test-unhealthy-workspace", health: { healthy: false, - failing_agents: ["test-workspace-agent"], + failing_agents: [MockWorkspaceUnhealthyAgent.id], + }, + latest_build: { + ...MockWorkspace.latest_build, + resources: [ + { ...MockWorkspaceResource, agents: [MockWorkspaceUnhealthyAgent] }, + ], }, }; export const MockStartingWorkspace: TypesGen.Workspace = { From 988c3d3176948ed4b5e17ca1f1b169479c1b32f4 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 21 Aug 2025 22:50:08 +0000 Subject: [PATCH 4/5] test: add UnhealthyWithoutUpdatePermission Workspace story --- site/src/pages/WorkspacePage/Workspace.stories.tsx | 11 +++++++++++ .../WorkspaceNotifications.stories.tsx | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index 69690a5bdc1fa..5a49e0fa57091 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 = ( @@ -356,6 +357,16 @@ export const Unhealthy: Story = { }, }; +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/WorkspaceNotifications/WorkspaceNotifications.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceNotifications/WorkspaceNotifications.stories.tsx index 073a5090f2e42..bcff8c53cca59 100644 --- a/site/src/pages/WorkspacePage/WorkspaceNotifications/WorkspaceNotifications.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceNotifications/WorkspaceNotifications.stories.tsx @@ -12,7 +12,7 @@ import type { WorkspacePermissions } from "modules/workspaces/permissions"; import { expect, userEvent, waitFor, within } from "storybook/test"; import { WorkspaceNotifications } from "./WorkspaceNotifications"; -const defaultPermissions: WorkspacePermissions = { +export const defaultPermissions: WorkspacePermissions = { readWorkspace: true, updateWorkspaceVersion: true, updateWorkspace: true, From eb0711fd4ee1b24336ff736f59cf67e90226a3a1 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 21 Aug 2025 22:56:47 +0000 Subject: [PATCH 5/5] refactor: remove unused export --- site/src/testHelpers/entities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index e29c9e9f43fd1..634c8f45886de 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -992,7 +992,7 @@ export const MockWorkspaceSubAgent: TypesGen.WorkspaceAgent = { ], }; -export const MockWorkspaceUnhealthyAgent: TypesGen.WorkspaceAgent = { +const MockWorkspaceUnhealthyAgent: TypesGen.WorkspaceAgent = { ...MockWorkspaceAgent, id: "test-workspace-unhealthy-agent", name: "a-workspace-unhealthy-agent",