From 1ebc2176248c4a77e1a3743938a3b9085a4e750e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 29 Oct 2025 15:45:45 +0000 Subject: [PATCH] fix: update task link AppStatus using task_id (#20543) Fixes https://github.com/coder/coder/issues/20515 Alternative to https://github.com/coder/coder/pull/20519 Adds `task_id` to `workspaces_expanded` view and updates the "View Task" link in `AppStatuses` component. NOTE: this contains a migration --- cli/testdata/coder_list_--output_json.golden | 3 +- coderd/aitasks_test.go | 12 ++++++ coderd/apidoc/docs.go | 8 ++++ coderd/apidoc/swagger.json | 8 ++++ coderd/database/dump.sql | 8 ++-- ...00393_workspaces_expanded_task_id.down.sql | 39 +++++++++++++++++ .../000393_workspaces_expanded_task_id.up.sql | 42 +++++++++++++++++++ coderd/database/modelqueries.go | 1 + coderd/database/models.go | 1 + coderd/database/queries.sql.go | 26 ++++++++---- coderd/database/queries/workspaces.sql | 1 + coderd/workspaces.go | 1 + codersdk/workspaces.go | 2 + docs/reference/api/schemas.md | 9 ++++ docs/reference/api/workspaces.md | 24 +++++++++++ site/src/api/typesGenerated.ts | 4 ++ .../WorkspacePage/AppStatuses.stories.tsx | 21 +++++++++- site/src/pages/WorkspacePage/AppStatuses.tsx | 16 ++++--- .../pages/WorkspacePage/Workspace.stories.tsx | 4 +- site/src/testHelpers/entities.ts | 5 +++ 20 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql create mode 100644 coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql diff --git a/cli/testdata/coder_list_--output_json.golden b/cli/testdata/coder_list_--output_json.golden index 66afcf563d..8da5753633 100644 --- a/cli/testdata/coder_list_--output_json.golden +++ b/cli/testdata/coder_list_--output_json.golden @@ -90,6 +90,7 @@ "allow_renames": false, "favorite": false, "next_start_at": "====[timestamp]=====", - "is_prebuild": false + "is_prebuild": false, + "task_id": null } ] diff --git a/coderd/aitasks_test.go b/coderd/aitasks_test.go index 80af3e993e..d3b5e240d8 100644 --- a/coderd/aitasks_test.go +++ b/coderd/aitasks_test.go @@ -259,6 +259,9 @@ func TestTasks(t *testing.T) { // Wait for the workspace to be built. workspace, err := client.Workspace(ctx, task.WorkspaceID.UUID) require.NoError(t, err) + if assert.True(t, workspace.TaskID.Valid, "task id should be set on workspace") { + assert.Equal(t, task.ID, workspace.TaskID.UUID, "workspace task id should match") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) // List tasks via experimental API and verify the prompt and status mapping. @@ -297,6 +300,9 @@ func TestTasks(t *testing.T) { // Get the workspace and wait for it to be ready. ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) require.NoError(t, err) + if assert.True(t, ws.TaskID.Valid, "task id should be set on workspace") { + assert.Equal(t, task.ID, ws.TaskID.UUID, "workspace task id should match") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) ws = coderdtest.MustWorkspace(t, client, task.WorkspaceID.UUID) // Assert invariant: the workspace has exactly one resource with one agent with one app. @@ -371,6 +377,9 @@ func TestTasks(t *testing.T) { require.True(t, task.WorkspaceID.Valid, "task should have a workspace ID") ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) require.NoError(t, err) + if assert.True(t, ws.TaskID.Valid, "task id should be set on workspace") { + assert.Equal(t, task.ID, ws.TaskID.UUID, "workspace task id should match") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) err = exp.DeleteTask(ctx, "me", task.ID) @@ -417,6 +426,9 @@ func TestTasks(t *testing.T) { coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) ws := coderdtest.CreateWorkspace(t, client, template.ID) + if assert.False(t, ws.TaskID.Valid, "task id should not be set on non-task workspace") { + assert.Zero(t, ws.TaskID, "non-task workspace task id should be empty") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) exp := codersdk.NewExperimentalClient(client) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 344a0299f8..e459b94b3f 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -19712,6 +19712,14 @@ const docTemplate = `{ "description": "OwnerName is the username of the owner of the workspace.", "type": "string" }, + "task_id": { + "description": "TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task.", + "allOf": [ + { + "$ref": "#/definitions/uuid.NullUUID" + } + ] + }, "template_active_version_id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index b472f2f4ef..8eef9dfb8e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -18098,6 +18098,14 @@ "description": "OwnerName is the username of the owner of the workspace.", "type": "string" }, + "task_id": { + "description": "TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task.", + "allOf": [ + { + "$ref": "#/definitions/uuid.NullUUID" + } + ] + }, "template_active_version_id": { "type": "string", "format": "uuid" diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 837c657402..8790bd27df 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -2922,11 +2922,13 @@ CREATE VIEW workspaces_expanded AS templates.name AS template_name, templates.display_name AS template_display_name, templates.icon AS template_icon, - templates.description AS template_description - FROM (((workspaces + templates.description AS template_description, + tasks.id AS task_id + FROM ((((workspaces JOIN visible_users ON ((workspaces.owner_id = visible_users.id))) JOIN organizations ON ((workspaces.organization_id = organizations.id))) - JOIN templates ON ((workspaces.template_id = templates.id))); + JOIN templates ON ((workspaces.template_id = templates.id))) + LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id))); COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql b/coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql new file mode 100644 index 0000000000..ed30e6a0f6 --- /dev/null +++ b/coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql @@ -0,0 +1,39 @@ +DROP VIEW workspaces_expanded; + +-- Recreate the view from 000354_workspace_acl.up.sql +CREATE VIEW workspaces_expanded AS + SELECT workspaces.id, + workspaces.created_at, + workspaces.updated_at, + workspaces.owner_id, + workspaces.organization_id, + workspaces.template_id, + workspaces.deleted, + workspaces.name, + workspaces.autostart_schedule, + workspaces.ttl, + workspaces.last_used_at, + workspaces.dormant_at, + workspaces.deleting_at, + workspaces.automatic_updates, + workspaces.favorite, + workspaces.next_start_at, + workspaces.group_acl, + workspaces.user_acl, + visible_users.avatar_url AS owner_avatar_url, + visible_users.username AS owner_username, + visible_users.name AS owner_name, + organizations.name AS organization_name, + organizations.display_name AS organization_display_name, + organizations.icon AS organization_icon, + organizations.description AS organization_description, + templates.name AS template_name, + templates.display_name AS template_display_name, + templates.icon AS template_icon, + templates.description AS template_description + FROM (((workspaces + JOIN visible_users ON ((workspaces.owner_id = visible_users.id))) + JOIN organizations ON ((workspaces.organization_id = organizations.id))) + JOIN templates ON ((workspaces.template_id = templates.id))); + +COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql b/coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql new file mode 100644 index 0000000000..f01354e65b --- /dev/null +++ b/coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql @@ -0,0 +1,42 @@ +DROP VIEW workspaces_expanded; + +-- Add nullable task_id to workspaces_expanded view +CREATE VIEW workspaces_expanded AS + SELECT workspaces.id, + workspaces.created_at, + workspaces.updated_at, + workspaces.owner_id, + workspaces.organization_id, + workspaces.template_id, + workspaces.deleted, + workspaces.name, + workspaces.autostart_schedule, + workspaces.ttl, + workspaces.last_used_at, + workspaces.dormant_at, + workspaces.deleting_at, + workspaces.automatic_updates, + workspaces.favorite, + workspaces.next_start_at, + workspaces.group_acl, + workspaces.user_acl, + visible_users.avatar_url AS owner_avatar_url, + visible_users.username AS owner_username, + visible_users.name AS owner_name, + organizations.name AS organization_name, + organizations.display_name AS organization_display_name, + organizations.icon AS organization_icon, + organizations.description AS organization_description, + templates.name AS template_name, + templates.display_name AS template_display_name, + templates.icon AS template_icon, + templates.description AS template_description, + tasks.id AS task_id + FROM ((((workspaces + JOIN visible_users ON ((workspaces.owner_id = visible_users.id))) + JOIN organizations ON ((workspaces.organization_id = organizations.id))) + JOIN templates ON ((workspaces.template_id = templates.id))) + LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id))); + +COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; + diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index c9c7879627..f9b058a409 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -321,6 +321,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, &i.TemplateVersionID, &i.TemplateVersionName, &i.LatestBuildCompletedAt, diff --git a/coderd/database/models.go b/coderd/database/models.go index e55f3a5537..ade3348ba3 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -4663,6 +4663,7 @@ type Workspace struct { TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` TemplateIcon string `db:"template_icon" json:"template_icon"` TemplateDescription string `db:"template_description" json:"template_description"` + TaskID uuid.NullUUID `db:"task_id" json:"task_id"` } type WorkspaceAgent struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index de5a4d0233..ff32a11267 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -21927,7 +21927,7 @@ func (q *sqlQuerier) GetWorkspaceACLByID(ctx context.Context, id uuid.UUID) (Get const getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -21988,13 +21988,14 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByID = `-- name: GetWorkspaceByID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded WHERE @@ -22036,13 +22037,14 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByOwnerIDAndName = `-- name: GetWorkspaceByOwnerIDAndName :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -22091,13 +22093,14 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByResourceID = `-- name: GetWorkspaceByResourceID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -22153,13 +22156,14 @@ func (q *sqlQuerier) GetWorkspaceByResourceID(ctx context.Context, resourceID uu &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByWorkspaceAppID = `-- name: GetWorkspaceByWorkspaceAppID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -22227,6 +22231,7 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } @@ -22276,7 +22281,7 @@ SELECT ), filtered_workspaces AS ( SELECT - workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, + workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, workspaces.task_id, latest_build.template_version_id, latest_build.template_version_name, latest_build.completed_at as latest_build_completed_at, @@ -22567,7 +22572,7 @@ WHERE -- @authorize_filter ), filtered_workspaces_order AS ( SELECT - fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task, fw.latest_build_has_external_agent + fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task, fw.latest_build_has_external_agent FROM filtered_workspaces fw ORDER BY @@ -22588,7 +22593,7 @@ WHERE $25 ), filtered_workspaces_order_with_summary AS ( SELECT - fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task, fwo.latest_build_has_external_agent + fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task, fwo.latest_build_has_external_agent FROM filtered_workspaces_order fwo -- Return a technical summary row with total count of workspaces. @@ -22624,6 +22629,7 @@ WHERE '', -- template_display_name '', -- template_icon '', -- template_description + '00000000-0000-0000-0000-000000000000'::uuid, -- task_id -- Extra columns added to ` + "`" + `filtered_workspaces` + "`" + ` '00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id '', -- template_version_name @@ -22643,7 +22649,7 @@ WHERE filtered_workspaces ) SELECT - fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task, fwos.latest_build_has_external_agent, + fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task, fwos.latest_build_has_external_agent, tc.count FROM filtered_workspaces_order_with_summary fwos @@ -22711,6 +22717,7 @@ type GetWorkspacesRow struct { TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` TemplateIcon string `db:"template_icon" json:"template_icon"` TemplateDescription string `db:"template_description" json:"template_description"` + TaskID uuid.NullUUID `db:"task_id" json:"task_id"` TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` @@ -22793,6 +22800,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, &i.TemplateVersionID, &i.TemplateVersionName, &i.LatestBuildCompletedAt, diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 8ccc69b9a8..d48285bb7d 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -457,6 +457,7 @@ WHERE '', -- template_display_name '', -- template_icon '', -- template_description + '00000000-0000-0000-0000-000000000000'::uuid, -- task_id -- Extra columns added to `filtered_workspaces` '00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id '', -- template_version_name diff --git a/coderd/workspaces.go b/coderd/workspaces.go index e8b7ff5153..3519442c3e 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -2654,6 +2654,7 @@ func convertWorkspace( Favorite: requesterFavorite, NextStartAt: nextStartAt, IsPrebuild: workspace.IsPrebuild(), + TaskID: workspace.TaskID, }, nil } diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index f190d58be6..709c9257c8 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -72,6 +72,8 @@ type Workspace struct { // Once a prebuilt workspace is claimed by a user, it transitions to a regular workspace, // and IsPrebuild returns false. IsPrebuild bool `json:"is_prebuild"` + // TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task. + TaskID uuid.NullUUID `json:"task_id,omitempty"` } func (w Workspace) FullName() string { diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 037c9cfa10..4317324f00 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -10184,6 +10184,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -10222,6 +10226,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `owner_avatar_url` | string | false | | | | `owner_id` | string | false | | | | `owner_name` | string | false | | Owner name is the username of the owner of the workspace. | +| `task_id` | [uuid.NullUUID](#uuidnulluuid) | false | | Task ID if set, indicates that the workspace is relevant to the given codersdk.Task. | | `template_active_version_id` | string | false | | | | `template_allow_user_cancel_workspace_jobs` | boolean | false | | | | `template_display_name` | string | false | | | @@ -12178,6 +12183,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index 91ab23f926..3e52d9e0a2 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -297,6 +297,10 @@ of the template will be used. "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -589,6 +593,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -906,6 +914,10 @@ of the template will be used. "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -1184,6 +1196,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -1477,6 +1493,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -2029,6 +2049,10 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index b964cf4d05..6d703cbcfe 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -5875,6 +5875,10 @@ export interface Workspace { * and IsPrebuild returns false. */ readonly is_prebuild: boolean; + /** + * TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task. + */ + readonly task_id?: string; } // From codersdk/workspaces.go diff --git a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx index 2e8324aef2..d27e64ae70 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx @@ -1,5 +1,6 @@ import { createTimestamp, + MockTaskWorkspace, MockWorkspace, MockWorkspaceAgent, MockWorkspaceApp, @@ -18,7 +19,7 @@ const meta: Meta = { args: { referenceDate: new Date("2024-03-26T15:15:00Z"), agent: mockAgent(MockWorkspaceAppStatuses), - workspace: MockWorkspace, + workspace: MockTaskWorkspace, }, decorators: [withProxyProvider()], }; @@ -148,6 +149,24 @@ export const MultipleStatuses: Story = { }, }; +export const NoTaskWorkspace: Story = { + args: { + agent: mockAgent([ + { + ...MockWorkspaceAppStatus, + id: "status-9", + icon: "", + message: "status updated via curl", + created_at: createTimestamp(5, 15), + uri: "", + state: "complete" as const, + }, + ...MockWorkspaceAppStatuses, + ]), + workspace: MockWorkspace, + }, +}; + function mockAgent(statuses: WorkspaceAppStatus[]) { return { ...MockWorkspaceAgent, diff --git a/site/src/pages/WorkspacePage/AppStatuses.tsx b/site/src/pages/WorkspacePage/AppStatuses.tsx index 26f239b627..2f1845db37 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.tsx @@ -121,12 +121,16 @@ export const AppStatuses: FC = ({ ))} - + {workspace.task_id && ( + + )} diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index 5a49e0fa57..8ae0c1b309 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -112,9 +112,9 @@ export const RunningWithChildAgent: Story = { export const RunningWithAppStatuses: Story = { args: { workspace: { - ...Mocks.MockWorkspace, + ...Mocks.MockTaskWorkspace, latest_build: { - ...Mocks.MockWorkspace.latest_build, + ...Mocks.MockTaskWorkspace.latest_build, resources: [ { ...Mocks.MockWorkspaceResource, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index a12d91cf51..b0ce392fa1 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -5027,6 +5027,11 @@ export const MockTask = { updated_at: "2022-05-17T17:39:01.382927298Z", } satisfies TypesGen.Task; +export const MockTaskWorkspace: TypesGen.Workspace = { + ...MockWorkspace, + task_id: MockTask.id, +}; + export const MockTasks = [ MockTask, {