refactor!: remove TaskAppID from codersdk.WorkspaceBuild (#20583)

Remove the `TaskAppID` field from `codersdk.WorkspaceBuild`. Consumers can instead use the new `codersdk.Task` data model for this information.
This commit is contained in:
Danielle Maywood
2025-10-30 16:45:51 +00:00
committed by GitHub
parent 197b422a31
commit d80b5fc8ed
12 changed files with 121 additions and 145 deletions
+1 -5
View File
@@ -20530,7 +20530,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"ai_task_sidebar_app_id": {
"description": "Deprecated: This field has been replaced with ` + "`" + `TaskAppID` + "`" + `",
"description": "Deprecated: This field has been replaced with ` + "`" + `Task.WorkspaceAppID` + "`" + `",
"type": "string",
"format": "uuid"
},
@@ -20612,10 +20612,6 @@ const docTemplate = `{
}
]
},
"task_app_id": {
"type": "string",
"format": "uuid"
},
"template_version_id": {
"type": "string",
"format": "uuid"
+1 -5
View File
@@ -18864,7 +18864,7 @@
"type": "object",
"properties": {
"ai_task_sidebar_app_id": {
"description": "Deprecated: This field has been replaced with `TaskAppID`",
"description": "Deprecated: This field has been replaced with `Task.WorkspaceAppID`",
"type": "string",
"format": "uuid"
},
@@ -18942,10 +18942,6 @@
}
]
},
"task_app_id": {
"type": "string",
"format": "uuid"
},
"template_version_id": {
"type": "string",
"format": "uuid"
-1
View File
@@ -1228,7 +1228,6 @@ func (api *API) convertWorkspaceBuild(
TemplateVersionPresetID: presetID,
HasAITask: hasAITask,
AITaskSidebarAppID: taskAppID,
TaskAppID: taskAppID,
HasExternalAgent: hasExternalAgent,
}, nil
}
+1 -2
View File
@@ -89,9 +89,8 @@ type WorkspaceBuild struct {
MatchedProvisioners *MatchedProvisioners `json:"matched_provisioners,omitempty"`
TemplateVersionPresetID *uuid.UUID `json:"template_version_preset_id" format:"uuid"`
HasAITask *bool `json:"has_ai_task,omitempty"`
// Deprecated: This field has been replaced with `TaskAppID`
// Deprecated: This field has been replaced with `Task.WorkspaceAppID`
AITaskSidebarAppID *uuid.UUID `json:"ai_task_sidebar_app_id,omitempty" format:"uuid"`
TaskAppID *uuid.UUID `json:"task_app_id,omitempty" format:"uuid"`
HasExternalAgent *bool `json:"has_external_agent,omitempty"`
}
+1 -7
View File
@@ -222,7 +222,6 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -464,7 +463,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -1197,7 +1195,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -1512,7 +1509,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -1540,7 +1536,7 @@ Status Code **200**
| Name | Type | Required | Restrictions | Description |
|----------------------------------|--------------------------------------------------------------------------------------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `[array item]` | array | false | | |
| `» ai_task_sidebar_app_id` | string(uuid) | false | | Deprecated: This field has been replaced with `TaskAppID` |
| `» ai_task_sidebar_app_id` | string(uuid) | false | | Deprecated: This field has been replaced with `Task.WorkspaceAppID` |
| `» build_number` | integer | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
@@ -1691,7 +1687,6 @@ Status Code **200**
| `»» type` | string | false | | |
| `»» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| `» status` | [codersdk.WorkspaceStatus](schemas.md#codersdkworkspacestatus) | false | | |
| `» task_app_id` | string(uuid) | false | | |
| `» template_version_id` | string(uuid) | false | | |
| `» template_version_name` | string | false | | |
| `» template_version_preset_id` | string(uuid) | false | | |
@@ -2013,7 +2008,6 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
+1 -5
View File
@@ -10166,7 +10166,6 @@ If the schedule is empty, the user will be updated to use the default schedule.|
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -11341,7 +11340,6 @@ If the schedule is empty, the user will be updated to use the default schedule.|
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -11359,7 +11357,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
| Name | Type | Required | Restrictions | Description |
|------------------------------|-------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------|
| `ai_task_sidebar_app_id` | string | false | | Deprecated: This field has been replaced with `TaskAppID` |
| `ai_task_sidebar_app_id` | string | false | | Deprecated: This field has been replaced with `Task.WorkspaceAppID` |
| `build_number` | integer | false | | |
| `created_at` | string | false | | |
| `daily_cost` | integer | false | | |
@@ -11375,7 +11373,6 @@ If the schedule is empty, the user will be updated to use the default schedule.|
| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | |
| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | |
| `task_app_id` | string | false | | |
| `template_version_id` | string | false | | |
| `template_version_name` | string | false | | |
| `template_version_preset_id` | string | false | | |
@@ -12165,7 +12162,6 @@ If the schedule is empty, the user will be updated to use the default schedule.|
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
-6
View File
@@ -277,7 +277,6 @@ of the template will be used.
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -573,7 +572,6 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -894,7 +892,6 @@ of the template will be used.
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -1176,7 +1173,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -1473,7 +1469,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
@@ -2029,7 +2024,6 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \
}
],
"status": "pending",
"task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"template_version_name": "string",
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
+1 -2
View File
@@ -6394,10 +6394,9 @@ export interface WorkspaceBuild {
readonly template_version_preset_id: string | null;
readonly has_ai_task?: boolean;
/**
* Deprecated: This field has been replaced with `TaskAppID`
* Deprecated: This field has been replaced with `Task.WorkspaceAppID`
*/
readonly ai_task_sidebar_app_id?: string;
readonly task_app_id?: string;
readonly has_external_agent?: boolean;
}
+13 -1
View File
@@ -1,5 +1,6 @@
import {
MockPrimaryWorkspaceProxy,
MockTask,
MockUserOwner,
MockWorkspace,
MockWorkspaceAgent,
@@ -8,7 +9,7 @@ import {
} from "testHelpers/entities";
import { withAuthProvider, withProxyProvider } from "testHelpers/storybook";
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { Workspace, WorkspaceApp } from "api/typesGenerated";
import type { Task, Workspace, WorkspaceApp } from "api/typesGenerated";
import { getPreferredProxy } from "contexts/ProxyContext";
import kebabCase from "lodash/kebabCase";
import { TaskApps } from "./TaskApps";
@@ -19,6 +20,11 @@ const mockExternalApp: WorkspaceApp = {
health: "healthy",
};
const mockTask: Task = {
...MockTask,
workspace_app_id: null,
};
const meta: Meta<typeof TaskApps> = {
title: "pages/TaskPage/TaskApps",
component: TaskApps,
@@ -33,24 +39,28 @@ type Story = StoryObj<typeof TaskApps>;
export const NoEmbeddedApps: Story = {
args: {
task: mockTask,
workspace: mockWorkspaceWithApps([]),
},
};
export const WithExternalAppsOnly: Story = {
args: {
task: mockTask,
workspace: mockWorkspaceWithApps([mockExternalApp]),
},
};
export const WithEmbeddedApps: Story = {
args: {
task: mockTask,
workspace: mockWorkspaceWithApps([mockEmbeddedApp()]),
},
};
export const WithMixedApps: Story = {
args: {
task: mockTask,
workspace: mockWorkspaceWithApps([mockEmbeddedApp(), mockExternalApp]),
},
};
@@ -69,6 +79,7 @@ export const WithWildcardWarning: Story = {
user: MockUserOwner,
},
args: {
task: mockTask,
workspace: mockWorkspaceWithApps([
{
...mockEmbeddedApp(),
@@ -80,6 +91,7 @@ export const WithWildcardWarning: Story = {
export const WithManyEmbeddedApps: Story = {
args: {
task: mockTask,
workspace: mockWorkspaceWithApps([
mockEmbeddedApp("Code Server"),
mockEmbeddedApp("Jupyter Notebook"),
+6 -7
View File
@@ -1,4 +1,4 @@
import type { Workspace } from "api/typesGenerated";
import type { Task, Workspace } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import {
DropdownMenu,
@@ -24,18 +24,17 @@ import { docs } from "utils/docs";
import { TaskAppIFrame, TaskIframe } from "./TaskAppIframe";
type TaskAppsProps = {
task: Task;
workspace: Workspace;
};
const TERMINAL_TAB_ID = "terminal";
export const TaskApps: FC<TaskAppsProps> = ({ workspace }) => {
export const TaskApps: FC<TaskAppsProps> = ({ task, workspace }) => {
const apps = getAllAppsWithAgent(workspace).filter(
// The Chat UI app will be displayed in the sidebar, so we don't want to
// show it as a web app.
(app) =>
app.id !== workspace.latest_build.task_app_id &&
app.health !== "disabled",
(app) => app.id !== task.workspace_app_id && app.health !== "disabled",
);
const [embeddedApps, externalApps] = splitEmbeddedAndExternalApps(apps);
const [activeAppId, setActiveAppId] = useState(embeddedApps.at(0)?.id);
@@ -43,8 +42,8 @@ export const TaskApps: FC<TaskAppsProps> = ({ workspace }) => {
embeddedApps.length > 0 || externalApps.length > 0;
const taskAgent = apps.at(0)?.agent;
const terminalHref = getTerminalHref({
username: workspace.owner_name,
workspace: workspace.name,
username: task.owner_name,
workspace: task.workspace_name,
agent: taskAgent?.name,
});
const isTerminalActive = activeAppId === TERMINAL_TAB_ID;
+94 -102
View File
@@ -23,7 +23,7 @@ import {
} from "testHelpers/storybook";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { API } from "api/api";
import type { Workspace, WorkspaceApp } from "api/typesGenerated";
import type { Task, Workspace, WorkspaceApp } from "api/typesGenerated";
import { expect, spyOn, userEvent, waitFor, within } from "storybook/test";
import { reactRouterParameters } from "storybook-addon-remix-react-router";
import TaskPage from "./TaskPage";
@@ -220,87 +220,70 @@ export const WaitingStartupScripts: Story = {
export const SidebarAppNotFound: Story = {
beforeEach: () => {
const workspace = mockTaskWorkspace(MockClaudeCodeApp, MockVSCodeApp);
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue({
...workspace,
latest_build: {
...workspace.latest_build,
task_app_id: "non-existent-app-id",
},
const [task, workspace] = mockTaskWithWorkspace(
MockClaudeCodeApp,
MockVSCodeApp,
);
spyOn(API.experimental, "getTask").mockResolvedValue({
...task,
workspace_app_id: null,
});
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
};
export const SidebarAppHealthDisabled: Story = {
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
mockTaskWorkspace(
{
...MockClaudeCodeApp,
health: "disabled",
},
MockVSCodeApp,
),
const [task, workspace] = mockTaskWithWorkspace(
{ ...MockClaudeCodeApp, health: "disabled" },
MockVSCodeApp,
);
spyOn(API.experimental, "getTask").mockResolvedValue(task);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
};
export const SidebarAppInitializing: Story = {
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
mockTaskWorkspace(
{
...MockClaudeCodeApp,
health: "initializing",
},
MockVSCodeApp,
),
const [task, workspace] = mockTaskWithWorkspace(
{ ...MockClaudeCodeApp, health: "initializing" },
MockVSCodeApp,
);
spyOn(API.experimental, "getTask").mockResolvedValue(task);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
};
export const SidebarAppHealthy: Story = {
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
mockTaskWorkspace(
{
...MockClaudeCodeApp,
health: "healthy",
},
MockVSCodeApp,
),
const [task, workspace] = mockTaskWithWorkspace(
{ ...MockClaudeCodeApp, health: "healthy" },
MockVSCodeApp,
);
spyOn(API.experimental, "getTask").mockResolvedValue(task);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
};
export const SidebarAppUnhealthy: Story = {
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
mockTaskWorkspace(
{
...MockClaudeCodeApp,
health: "unhealthy",
},
MockVSCodeApp,
),
const [task, workspace] = mockTaskWithWorkspace(
{ ...MockClaudeCodeApp, health: "unhealthy" },
MockVSCodeApp,
);
spyOn(API.experimental, "getTask").mockResolvedValue(task);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
};
const mainAppHealthStory = (health: WorkspaceApp["health"]) => ({
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
mockTaskWorkspace(MockClaudeCodeApp, {
...MockVSCodeApp,
health,
}),
);
const [task, workspace] = mockTaskWithWorkspace(MockClaudeCodeApp, {
...MockVSCodeApp,
health,
});
spyOn(API.experimental, "getTask").mockResolvedValue(task);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
});
@@ -311,10 +294,12 @@ export const MainAppUnhealthy: Story = mainAppHealthStory("unhealthy");
export const Active: Story = {
decorators: [withProxyProvider()],
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
mockTaskWorkspace(MockClaudeCodeApp, MockVSCodeApp),
const [task, workspace] = mockTaskWithWorkspace(
MockClaudeCodeApp,
MockVSCodeApp,
);
spyOn(API.experimental, "getTask").mockResolvedValue(task);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -332,10 +317,12 @@ export const Active: Story = {
export const ActivePreview: Story = {
decorators: [withProxyProvider()],
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
mockTaskWorkspace(MockClaudeCodeApp, MockVSCodeApp),
const [task, workspace] = mockTaskWithWorkspace(
MockClaudeCodeApp,
MockVSCodeApp,
);
spyOn(API.experimental, "getTask").mockResolvedValue(task);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -464,51 +451,56 @@ export const WorkspaceStartFailureWithDialog: Story = {
},
};
function mockTaskWorkspace(
function mockTaskWithWorkspace(
sidebarApp: WorkspaceApp,
activeApp: WorkspaceApp,
): Workspace {
return {
...MockWorkspace,
latest_build: {
...MockWorkspace.latest_build,
has_ai_task: true,
task_app_id: sidebarApp.id,
resources: [
{
...MockWorkspaceResource,
agents: [
{
...MockWorkspaceAgentReady,
apps: [
sidebarApp,
activeApp,
{
...MockWorkspaceApp,
slug: "zed",
id: "zed",
display_name: "Zed",
icon: "/icon/zed.svg",
health: "healthy",
},
{
...MockWorkspaceApp,
slug: "preview",
id: "preview",
display_name: "Preview",
health: "healthy",
},
{
...MockWorkspaceApp,
slug: "disabled",
id: "disabled",
display_name: "Disabled",
},
],
},
],
},
],
): [Task, Workspace] {
return [
{
...MockTask,
workspace_app_id: sidebarApp.id,
},
};
{
...MockWorkspace,
latest_build: {
...MockWorkspace.latest_build,
has_ai_task: true,
resources: [
{
...MockWorkspaceResource,
agents: [
{
...MockWorkspaceAgentReady,
apps: [
sidebarApp,
activeApp,
{
...MockWorkspaceApp,
slug: "zed",
id: "zed",
display_name: "Zed",
icon: "/icon/zed.svg",
health: "healthy",
},
{
...MockWorkspaceApp,
slug: "preview",
id: "preview",
display_name: "Preview",
health: "healthy",
},
{
...MockWorkspaceApp,
slug: "disabled",
id: "disabled",
display_name: "Disabled",
},
],
},
],
},
],
},
},
];
}
+2 -2
View File
@@ -149,7 +149,7 @@ const TaskPage = () => {
content = <TaskStartingAgent agent={agent} />;
} else {
const chatApp = getAllAppsWithAgent(workspace).find(
(app) => app.id === workspace.latest_build.task_app_id,
(app) => app.id === task.workspace_app_id,
);
content = (
<PanelGroup autoSaveId="task" direction="horizontal">
@@ -174,7 +174,7 @@ const TaskPage = () => {
<div className="w-1 bg-border h-full hover:bg-border-hover transition-all relative" />
</PanelResizeHandle>
<Panel className="[&>*]:h-full">
<TaskApps workspace={workspace} />
<TaskApps task={task} workspace={workspace} />
</Panel>
</PanelGroup>
);