Skip to content

Commit 75a4562

Browse files
committed
feat: add visual distinction for workspaces with running startup scripts
- Add helper function hasStartingAgents to check agent lifecycle states - Update getDisplayWorkspaceStatus to show 'Running (Starting...)' when agents are still starting - Enhance WorkspaceStatusIndicator with tooltips for starting workspaces - Add comprehensive tests for the new functionality
1 parent aa1a985 commit 75a4562

File tree

3 files changed

+173
-21
lines changed

3 files changed

+173
-21
lines changed

site/src/modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,24 @@ export const WorkspaceStatusIndicator: FC<WorkspaceStatusIndicatorProps> = ({
4141
let { text, type } = getDisplayWorkspaceStatus(
4242
workspace.latest_build.status,
4343
workspace.latest_build.job,
44+
workspace,
4445
);
4546

4647
if (!workspace.health.healthy) {
4748
type = "warning";
4849
}
4950

51+
// Check if workspace is running but agents are still starting
52+
const isStarting =
53+
workspace.latest_build.status === "running" &&
54+
workspace.latest_build.resources.some((resource) =>
55+
resource.agents?.some(
56+
(agent) =>
57+
agent.lifecycle_state === "starting" ||
58+
agent.lifecycle_state === "created",
59+
),
60+
);
61+
5062
const statusIndicator = (
5163
<StatusIndicator variant={variantByStatusType[type]}>
5264
<StatusIndicatorDot />
@@ -55,24 +67,27 @@ export const WorkspaceStatusIndicator: FC<WorkspaceStatusIndicatorProps> = ({
5567
</StatusIndicator>
5668
);
5769

58-
if (workspace.health.healthy) {
59-
return statusIndicator;
70+
// Show tooltip for unhealthy or starting workspaces
71+
if (!workspace.health.healthy || isStarting) {
72+
const tooltipMessage = !workspace.health.healthy
73+
? "Your workspace is running but some agents are unhealthy."
74+
: "Your workspace is running but startup scripts are still executing.";
75+
76+
return (
77+
<TooltipProvider>
78+
<Tooltip>
79+
<TooltipTrigger asChild>
80+
<StatusIndicator variant={variantByStatusType[type]}>
81+
<StatusIndicatorDot />
82+
<span className="sr-only">Workspace status:</span> {text}
83+
{children}
84+
</StatusIndicator>
85+
</TooltipTrigger>
86+
<TooltipContent>{tooltipMessage}</TooltipContent>
87+
</Tooltip>
88+
</TooltipProvider>
89+
);
6090
}
6191

62-
return (
63-
<TooltipProvider>
64-
<Tooltip>
65-
<TooltipTrigger asChild>
66-
<StatusIndicator variant={variantByStatusType[type]}>
67-
<StatusIndicatorDot />
68-
<span className="sr-only">Workspace status:</span> {text}
69-
{children}
70-
</StatusIndicator>
71-
</TooltipTrigger>
72-
<TooltipContent>
73-
Your workspace is running but some agents are unhealthy.
74-
</TooltipContent>
75-
</Tooltip>
76-
</TooltipProvider>
77-
);
92+
return statusIndicator;
7893
};

site/src/utils/workspace.test.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
getDisplayVersionStatus,
88
getDisplayWorkspaceBuildInitiatedBy,
99
getDisplayWorkspaceTemplateName,
10+
getDisplayWorkspaceStatus,
11+
hasStartingAgents,
1012
isWorkspaceOn,
1113
} from "./workspace";
1214

@@ -157,4 +159,125 @@ describe("util > workspace", () => {
157159
expect(displayed).toEqual(workspace.template_display_name);
158160
});
159161
});
162+
163+
describe("hasStartingAgents", () => {
164+
it("returns true when agents are starting", () => {
165+
const workspace: TypesGen.Workspace = {
166+
...Mocks.MockWorkspace,
167+
latest_build: {
168+
...Mocks.MockWorkspaceBuild,
169+
resources: [
170+
{
171+
...Mocks.MockWorkspaceResource,
172+
agents: [
173+
{
174+
...Mocks.MockWorkspaceAgent,
175+
lifecycle_state: "starting",
176+
},
177+
],
178+
},
179+
],
180+
},
181+
};
182+
expect(hasStartingAgents(workspace)).toBe(true);
183+
});
184+
185+
it("returns true when agents are created", () => {
186+
const workspace: TypesGen.Workspace = {
187+
...Mocks.MockWorkspace,
188+
latest_build: {
189+
...Mocks.MockWorkspaceBuild,
190+
resources: [
191+
{
192+
...Mocks.MockWorkspaceResource,
193+
agents: [
194+
{
195+
...Mocks.MockWorkspaceAgent,
196+
lifecycle_state: "created",
197+
},
198+
],
199+
},
200+
],
201+
},
202+
};
203+
expect(hasStartingAgents(workspace)).toBe(true);
204+
});
205+
206+
it("returns false when all agents are ready", () => {
207+
const workspace: TypesGen.Workspace = {
208+
...Mocks.MockWorkspace,
209+
latest_build: {
210+
...Mocks.MockWorkspaceBuild,
211+
resources: [
212+
{
213+
...Mocks.MockWorkspaceResource,
214+
agents: [
215+
{
216+
...Mocks.MockWorkspaceAgent,
217+
lifecycle_state: "ready",
218+
},
219+
],
220+
},
221+
],
222+
},
223+
};
224+
expect(hasStartingAgents(workspace)).toBe(false);
225+
});
226+
});
227+
228+
describe("getDisplayWorkspaceStatus with starting agents", () => {
229+
it("shows 'Running (Starting...)' when workspace is running with starting agents", () => {
230+
const workspace: TypesGen.Workspace = {
231+
...Mocks.MockWorkspace,
232+
latest_build: {
233+
...Mocks.MockWorkspaceBuild,
234+
status: "running",
235+
resources: [
236+
{
237+
...Mocks.MockWorkspaceResource,
238+
agents: [
239+
{
240+
...Mocks.MockWorkspaceAgent,
241+
lifecycle_state: "starting",
242+
},
243+
],
244+
},
245+
],
246+
},
247+
};
248+
const status = getDisplayWorkspaceStatus("running", undefined, workspace);
249+
expect(status.text).toBe("Running (Starting...)");
250+
expect(status.type).toBe("active");
251+
});
252+
253+
it("shows 'Running' when workspace is running with all agents ready", () => {
254+
const workspace: TypesGen.Workspace = {
255+
...Mocks.MockWorkspace,
256+
latest_build: {
257+
...Mocks.MockWorkspaceBuild,
258+
status: "running",
259+
resources: [
260+
{
261+
...Mocks.MockWorkspaceResource,
262+
agents: [
263+
{
264+
...Mocks.MockWorkspaceAgent,
265+
lifecycle_state: "ready",
266+
},
267+
],
268+
},
269+
],
270+
},
271+
};
272+
const status = getDisplayWorkspaceStatus("running", undefined, workspace);
273+
expect(status.text).toBe("Running");
274+
expect(status.type).toBe("success");
275+
});
276+
277+
it("shows 'Running' when workspace parameter is not provided", () => {
278+
const status = getDisplayWorkspaceStatus("running", undefined);
279+
expect(status.text).toBe("Running");
280+
expect(status.type).toBe("success");
281+
});
282+
});
160283
});

site/src/utils/workspace.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,21 @@ type DisplayWorkspaceStatus = {
182182
icon: React.ReactNode;
183183
};
184184

185+
// Helper function to check if any agents are still starting
186+
export const hasStartingAgents = (workspace: TypesGen.Workspace): boolean => {
187+
return workspace.latest_build.resources.some((resource) =>
188+
resource.agents?.some(
189+
(agent) =>
190+
agent.lifecycle_state === "starting" ||
191+
agent.lifecycle_state === "created",
192+
),
193+
);
194+
};
195+
185196
export const getDisplayWorkspaceStatus = (
186197
workspaceStatus: TypesGen.WorkspaceStatus,
187198
provisionerJob?: TypesGen.ProvisionerJob,
199+
workspace?: TypesGen.Workspace,
188200
): DisplayWorkspaceStatus => {
189201
switch (workspaceStatus) {
190202
case undefined:
@@ -194,10 +206,12 @@ export const getDisplayWorkspaceStatus = (
194206
icon: <PillSpinner />,
195207
} as const;
196208
case "running":
209+
// Check if workspace has agents that are still starting
210+
const isStarting = workspace && hasStartingAgents(workspace);
197211
return {
198-
type: "success",
199-
text: "Running",
200-
icon: <PlayIcon />,
212+
type: isStarting ? "active" : "success",
213+
text: isStarting ? "Running (Starting...)" : "Running",
214+
icon: isStarting ? <PillSpinner /> : <PlayIcon />,
201215
} as const;
202216
case "starting":
203217
return {

0 commit comments

Comments
 (0)
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