mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: show workspace health error alert above agents in WorkspacePage (#19400)
closes #19338 <img width="1840" height="1191" alt="image" src="https://github.com/user-attachments/assets/eeefda13-88d1-4a26-ba57-9749abda3f17" />
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<WorkspaceProps> = ({
|
||||
(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 (
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
@@ -194,6 +198,41 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!workspace.health.healthy && (
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Workspace is unhealthy</AlertTitle>
|
||||
<AlertDetail>
|
||||
<p>
|
||||
Your workspace is running but{" "}
|
||||
{workspace.health.failing_agents.length > 1
|
||||
? `${workspace.health.failing_agents.length} agents are unhealthy`
|
||||
: "1 agent is unhealthy"}
|
||||
.
|
||||
</p>
|
||||
{hasActions && (
|
||||
<div className="flex items-center gap-2">
|
||||
{permissions.updateWorkspace && (
|
||||
<NotificationActionButton
|
||||
onClick={() => handleRestart()}
|
||||
>
|
||||
Restart
|
||||
</NotificationActionButton>
|
||||
)}
|
||||
{troubleshootingURL && (
|
||||
<NotificationActionButton
|
||||
onClick={() =>
|
||||
window.open(troubleshootingURL, "_blank")
|
||||
}
|
||||
>
|
||||
Troubleshooting
|
||||
</NotificationActionButton>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</AlertDetail>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{transitionStats !== undefined && (
|
||||
<WorkspaceBuildProgress
|
||||
workspace={workspace}
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -275,7 +275,7 @@ const styles = {
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
const findTroubleshootingURL = (
|
||||
export const findTroubleshootingURL = (
|
||||
workspaceBuild: WorkspaceBuild,
|
||||
): string | undefined => {
|
||||
for (const resource of workspaceBuild.resources) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user