From 47639a7aeae4ebcefed01779ba0986c194be10c6 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Wed, 19 Nov 2025 19:57:37 +0000 Subject: [PATCH] feat: display Task icon for workspaces associated with tasks --- coderd/database/modelmethods.go | 1 + coderd/workspaces_test.go | 58 +++++++++++++++++++ .../WorkspacesPageView.stories.tsx | 16 +++++ .../pages/WorkspacesPage/WorkspacesTable.tsx | 6 ++ 4 files changed, 81 insertions(+) diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index b3202342e3ffa..21d077d52795c 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -662,6 +662,7 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace { TemplateIcon: r.TemplateIcon, TemplateDescription: r.TemplateDescription, NextStartAt: r.NextStartAt, + TaskID: r.TaskID, } } diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 7449c0355abd5..536202bce7b96 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -4794,6 +4794,64 @@ func TestWorkspaceFilterHasAITask(t *testing.T) { require.Len(t, res.Workspaces, 4) } +func TestWorkspaceListTasks(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitShort) + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + expClient := codersdk.NewExperimentalClient(client) + + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: echo.ApplyComplete, + ProvisionPlan: []*proto.Response{ + {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ + HasAiTasks: true, + }}}, + }, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + + // Given: a regular user workspace + workspaceWithoutTask, err := client.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{ + TemplateID: template.ID, + Name: "user-workspace", + }) + require.NoError(t, err) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceWithoutTask.LatestBuild.ID) + + // Given: a workspace associated with a task + task, err := expClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{ + TemplateVersionID: template.ActiveVersionID, + Input: "Some task prompt", + }) + require.NoError(t, err) + assert.True(t, task.WorkspaceID.Valid) + workspaceWithTask, err := client.Workspace(ctx, task.WorkspaceID.UUID) + require.NoError(t, err) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceWithTask.LatestBuild.ID) + assert.NotEmpty(t, task.Name) + assert.Equal(t, template.ID, task.TemplateID) + + // When: listing the workspaces + workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{}) + require.NoError(t, err) + + assert.Equal(t, workspaces.Count, 2) + + // Then: verify TaskID is only set for task workspaces + for _, workspace := range workspaces.Workspaces { + if workspace.ID == workspaceWithoutTask.ID { + assert.False(t, workspace.TaskID.Valid) + } else if workspace.ID == workspaceWithTask.ID { + assert.True(t, workspace.TaskID.Valid) + assert.Equal(t, task.ID, workspace.TaskID.UUID) + } + } +} + func TestWorkspaceAppUpsertRestart(t *testing.T) { t.Parallel() diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx index a1c0a65aea29b..13f075efb66fb 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx @@ -2,6 +2,7 @@ import { MockBuildInfo, MockOrganization, MockPendingProvisionerJob, + MockTaskWorkspace, MockTemplate, MockUserOwner, MockWorkspace, @@ -381,3 +382,18 @@ export const ShowOrganizations: Story = { expect(accessibleTableCell).toBeDefined(); }, }; + +export const ShowWorkspaceTasks: Story = { + args: { + workspaces: [ + { + ...MockWorkspace, + name: "regular-user-workspace", + }, + { + ...MockTaskWorkspace, + name: "task-workspace", + }, + ], + }, +}; diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 7ab941821d7b1..56f1cf9428171 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -17,6 +17,7 @@ import type { import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton"; +import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; @@ -207,6 +208,11 @@ export const WorkspacesTable: FC = ({ {workspace.outdated && ( )} + {workspace.task_id && ( + + Task + + )} } subtitle={