From 8709d42fe037a78425b8c49edafd45c756501964 Mon Sep 17 00:00:00 2001 From: Jeremy Ruppel Date: Fri, 1 May 2026 10:57:30 -0400 Subject: [PATCH] feat(site): add loading `` to AgentRow (#24825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `` next to the log count during agent startup. Also, there was some complexity in sizing the spinner because `` automatically sizes any `svg`s it contains to `size-icon-xs`. In order to maintain the `svg` sizing inside existing Badges across the site, this introduces a new `svgSize` prop that defaults to `xs`. Existing consumers will still get `[&_svg]:size-icon-xs` regardless of the Badge size, but can now be overridden to `sm` or `lg` (there is no `md`). Also also fixes a tiny spacing issue with the warning triangle :male_detective: Screenshot 2026-04-29 at 3 24 35 PM --- site/src/components/Badge/Badge.tsx | 18 +++-- .../modules/resources/AgentRow.stories.tsx | 11 ++-- site/src/modules/resources/AgentRow.tsx | 32 ++++++++- site/src/pages/TaskPage/TaskPage.stories.tsx | 25 +++++-- site/src/testHelpers/entities.ts | 66 ++++++++++++++++++- 5 files changed, 133 insertions(+), 19 deletions(-) diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index 487dd9cd34..9cd5dec809 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -30,9 +30,14 @@ const badgeVariants = cva( info: "border-border-pending bg-surface-sky text-highlight-sky shadow", }, size: { - xs: "border-0 text-2xs font-normal h-[18px] [&_svg]:size-icon-xs rounded", - sm: "text-2xs font-normal h-5.5 py-1 [&_svg]:size-icon-xs", - md: "text-xs font-normal py-1 [&_svg]:size-icon-xs", + xs: "border-0 text-2xs font-normal h-[18px] rounded", + sm: "text-2xs font-normal h-5.5 py-1", + md: "text-xs font-normal py-1", + }, + svgSize: { + xs: "[&_svg]:size-icon-xs", + sm: "[&_svg]:size-icon-sm", + lg: "[&_svg]:size-icon-lg", }, hover: { false: null, @@ -54,6 +59,7 @@ const badgeVariants = cva( defaultVariants: { variant: "default", size: "md", + svgSize: "xs", hover: false, }, }, @@ -68,6 +74,7 @@ export const Badge: React.FC = ({ className, variant, size, + svgSize = "xs", hover, asChild = false, ...props @@ -77,7 +84,10 @@ export const Badge: React.FC = ({ return ( ); }; diff --git a/site/src/modules/resources/AgentRow.stories.tsx b/site/src/modules/resources/AgentRow.stories.tsx index d614eefcc5..139233bd31 100644 --- a/site/src/modules/resources/AgentRow.stories.tsx +++ b/site/src/modules/resources/AgentRow.stories.tsx @@ -165,7 +165,7 @@ export const Example: Story = {}; export const BunchOfApps: Story = { args: { agent: { - ...M.MockWorkspaceAgent, + ...M.MockWorkspaceAgentReady, apps: [ M.MockWorkspaceApp, M.MockWorkspaceApp, @@ -203,7 +203,10 @@ export const Timeout: Story = { export const Starting: Story = { args: { - agent: M.MockWorkspaceAgentStarting, + agent: { + ...M.MockWorkspaceAgentStarting, + logs_length: logs.length, + }, }, }; @@ -357,7 +360,7 @@ export const Deprecated: Story = { export const HideApp: Story = { args: { agent: { - ...M.MockWorkspaceAgent, + ...M.MockWorkspaceAgentReady, apps: [ { ...M.MockWorkspaceApp, @@ -371,7 +374,7 @@ export const HideApp: Story = { export const GroupApp: Story = { args: { agent: { - ...M.MockWorkspaceAgent, + ...M.MockWorkspaceAgentReady, apps: [ { ...M.MockWorkspaceApp, diff --git a/site/src/modules/resources/AgentRow.tsx b/site/src/modules/resources/AgentRow.tsx index edbf1873b6..da10057904 100644 --- a/site/src/modules/resources/AgentRow.tsx +++ b/site/src/modules/resources/AgentRow.tsx @@ -2,6 +2,7 @@ import Collapse from "@mui/material/Collapse"; import { CopyIcon, EllipsisIcon, + InfoIcon, PackageIcon, PlayIcon, SquareCheckBigIcon, @@ -39,6 +40,7 @@ import { import { ExternalImage } from "#/components/ExternalImage/ExternalImage"; import type { Line } from "#/components/Logs/LogLine"; import { Skeleton } from "#/components/Skeleton/Skeleton"; +import { Spinner } from "#/components/Spinner/Spinner"; import { Tabs, TabsContent, @@ -157,6 +159,7 @@ export const AgentRow: FC = ({ const hasStartupFeatures = Boolean(agent.logs_length); const healthIssues = getAgentHealthIssues(agent); const hasAgentIssues = healthIssues.length > 0; + const hasWarningIssues = healthIssues.some((i) => i.severity === "warning"); const failedStartTimings = agentScriptTimings?.filter( (t) => t.workspace_agent_id === agent.id && @@ -500,9 +503,34 @@ export const AgentRow: FC = ({ > Logs + {agent.lifecycle_state === "starting" && + agent.log_sources.length > 0 && + healthIssues.length === 0 && ( + + + {agent.log_sources.length} + + )} {healthIssues.length > 0 && ( - - + + {hasWarningIssues ? ( + + ) : ( + + )} {healthIssues.length} )} diff --git a/site/src/pages/TaskPage/TaskPage.stories.tsx b/site/src/pages/TaskPage/TaskPage.stories.tsx index 328aeb6bc5..3f8b013510 100644 --- a/site/src/pages/TaskPage/TaskPage.stories.tsx +++ b/site/src/pages/TaskPage/TaskPage.stories.tsx @@ -30,7 +30,6 @@ import { MockTasks, MockUserOwner, MockWorkspace, - MockWorkspaceAgent, MockWorkspaceAgentLogSource, MockWorkspaceAgentReady, MockWorkspaceAgentStarting, @@ -804,7 +803,7 @@ export const StartupScriptError: Story = { resources: [ { ...MockWorkspaceResource, - agents: [MockWorkspaceAgent], + agents: [MockWorkspaceAgentStarting], }, ], }, @@ -859,7 +858,7 @@ export const StartupScriptTimeout: Story = { resources: [ { ...MockWorkspaceResource, - agents: [MockWorkspaceAgent], + agents: [MockWorkspaceAgentStarting], }, ], }, @@ -1188,14 +1187,22 @@ export const LongDisplayName: Story = { // Sidebar: uses getTasks() which returns an array key: ["tasks", { owner: MockTask.owner_name }], data: [ - { ...MockDisplayNameTasks[0], display_name: longDisplayName }, + { + ...MockDisplayNameTasks[0], + display_name: longDisplayName, + workspace_agent_lifecycle: "starting", + }, ...MockDisplayNameTasks.slice(1), ], }, { // TaskTopbar: uses getTask() which returns a single task key: ["tasks", MockTask.owner_name, MockTask.id], - data: { ...MockDisplayNameTasks[0], display_name: longDisplayName }, + data: { + ...MockDisplayNameTasks[0], + display_name: longDisplayName, + workspace_agent_lifecycle: "starting", + }, }, { // Workspace data for the task @@ -1205,7 +1212,13 @@ export const LongDisplayName: Story = { MockTask.workspace_name, "settings", ], - data: MockWorkspace, + data: { + ...MockStartingWorkspace, + latest_build: { + ...MockStartingWorkspace.latest_build, + has_ai_task: true, + }, + }, }, ], }, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index d47ff56d39..35af473aef 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1009,7 +1009,7 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { }, connection_timeout_seconds: 120, troubleshooting_url: "https://coder.com/troubleshoot", - lifecycle_state: "starting", + lifecycle_state: "ready", logs_length: 0, logs_overflowed: false, log_sources: [MockWorkspaceAgentLogSource], @@ -1496,7 +1496,16 @@ export const MockFavoriteWorkspace: TypesGen.Workspace = { export const MockStoppedWorkspace: TypesGen.Workspace = { ...MockWorkspace, id: "test-stopped-workspace", - latest_build: { ...MockWorkspaceBuildStop, status: "stopped" }, + latest_build: { + ...MockWorkspaceBuildStop, + status: "stopped", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentOff], + }, + ], + }, }; export const MockStoppingWorkspace: TypesGen.Workspace = { ...MockWorkspace, @@ -1505,6 +1514,12 @@ export const MockStoppingWorkspace: TypesGen.Workspace = { ...MockWorkspaceBuildStop, job: MockRunningProvisionerJob, status: "stopping", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentShuttingDown], + }, + ], }, }; export const MockUnhealthyWorkspace: TypesGen.Workspace = { @@ -1529,6 +1544,12 @@ export const MockStartingWorkspace: TypesGen.Workspace = { job: MockRunningProvisionerJob, transition: "start", status: "starting", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentStarting], + }, + ], }, }; export const MockCancelingWorkspace: TypesGen.Workspace = { @@ -1538,6 +1559,12 @@ export const MockCancelingWorkspace: TypesGen.Workspace = { ...MockWorkspaceBuild, job: MockCancelingProvisionerJob, status: "canceling", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentShuttingDown], + }, + ], }, }; export const MockCanceledWorkspace: TypesGen.Workspace = { @@ -1547,6 +1574,12 @@ export const MockCanceledWorkspace: TypesGen.Workspace = { ...MockWorkspaceBuild, job: MockCanceledProvisionerJob, status: "canceled", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentOff], + }, + ], }, }; export const MockFailedWorkspace: TypesGen.Workspace = { @@ -1556,6 +1589,12 @@ export const MockFailedWorkspace: TypesGen.Workspace = { ...MockWorkspaceBuild, job: MockFailedProvisionerJob, status: "failed", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentStartError], + }, + ], }, }; export const MockDeletingWorkspace: TypesGen.Workspace = { @@ -1565,6 +1604,12 @@ export const MockDeletingWorkspace: TypesGen.Workspace = { ...MockWorkspaceBuildDelete, job: MockRunningProvisionerJob, status: "deleting", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentShuttingDown], + }, + ], }, }; @@ -1576,7 +1621,16 @@ const MockWorkspaceWithDeletion = { export const MockDeletedWorkspace: TypesGen.Workspace = { ...MockWorkspace, id: "test-deleted-workspace", - latest_build: { ...MockWorkspaceBuildDelete, status: "deleted" }, + latest_build: { + ...MockWorkspaceBuildDelete, + status: "deleted", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentOff], + }, + ], + }, }; export const MockOutdatedWorkspace: TypesGen.Workspace = { @@ -1648,6 +1702,12 @@ export const MockPendingWorkspace: TypesGen.Workspace = { job: MockPendingProvisionerJob, transition: "start", status: "pending", + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgentConnecting], + }, + ], }, };