Skip to content

Commit cb7a768

Browse files
committed
feat: add workspace build status to task page
1 parent 4fd0312 commit cb7a768

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

site/src/pages/TaskPage/TaskPage.stories.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { expect, spyOn, within } from "@storybook/test";
3+
import { API } from "api/api";
34
import type {
45
Workspace,
56
WorkspaceApp,
@@ -9,14 +10,16 @@ import {
910
MockFailedWorkspace,
1011
MockStartingWorkspace,
1112
MockStoppedWorkspace,
13+
MockTemplate,
1214
MockWorkspace,
1315
MockWorkspaceAgent,
1416
MockWorkspaceApp,
1517
MockWorkspaceAppStatus,
18+
MockWorkspaceBuildLogs,
1619
MockWorkspaceResource,
1720
mockApiError,
1821
} from "testHelpers/entities";
19-
import { withProxyProvider } from "testHelpers/storybook";
22+
import { withProxyProvider, withWebSocket } from "testHelpers/storybook";
2023
import TaskPage, { data, WorkspaceDoesNotHaveAITaskError } from "./TaskPage";
2124

2225
const meta: Meta<typeof TaskPage> = {
@@ -59,6 +62,33 @@ export const WaitingOnBuild: Story = {
5962
},
6063
};
6164

65+
export const WaitingOnBuildWithTemplate: Story = {
66+
beforeEach: () => {
67+
spyOn(API, "getTemplate").mockResolvedValue(MockTemplate);
68+
spyOn(data, "fetchTask").mockResolvedValue({
69+
prompt: "Create competitors page",
70+
workspace: MockStartingWorkspace,
71+
});
72+
},
73+
};
74+
75+
export const WaitingOnBuildWithLogs: Story = {
76+
parameters: {
77+
decorators: [withWebSocket],
78+
webSocket: MockWorkspaceBuildLogs.map((log) => ({
79+
event: "message",
80+
data: JSON.stringify(log),
81+
})),
82+
},
83+
beforeEach: () => {
84+
spyOn(API, "getTemplate").mockResolvedValue(MockTemplate);
85+
spyOn(data, "fetchTask").mockResolvedValue({
86+
prompt: "Create competitors page",
87+
workspace: MockStartingWorkspace,
88+
});
89+
},
90+
};
91+
6292
export const WaitingOnStatus: Story = {
6393
beforeEach: () => {
6494
spyOn(data, "fetchTask").mockResolvedValue({

site/src/pages/TaskPage/TaskPage.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { API } from "api/api";
22
import { getErrorDetail, getErrorMessage } from "api/errors";
3+
import { template as templateQueryOptions } from "api/queries/templates";
34
import type { Workspace, WorkspaceStatus } from "api/typesGenerated";
45
import { Button } from "components/Button/Button";
56
import { Loader } from "components/Loader/Loader";
67
import { Margins } from "components/Margins/Margins";
78
import { Spinner } from "components/Spinner/Spinner";
9+
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
810
import { ArrowLeftIcon, RotateCcwIcon } from "lucide-react";
911
import { AI_PROMPT_PARAMETER_NAME, type Task } from "modules/tasks/tasks";
1012
import type { ReactNode } from "react";
@@ -14,6 +16,10 @@ import { useParams } from "react-router-dom";
1416
import { Link as RouterLink } from "react-router-dom";
1517
import { ellipsizeText } from "utils/ellipsizeText";
1618
import { pageTitle } from "utils/page";
19+
import {
20+
ActiveTransition,
21+
WorkspaceBuildProgress,
22+
} from "../WorkspacePage/WorkspaceBuildProgress";
1723
import { TaskApps } from "./TaskApps";
1824
import { TaskSidebar } from "./TaskSidebar";
1925

@@ -32,6 +38,19 @@ const TaskPage = () => {
3238
refetchInterval: 5_000,
3339
});
3440

41+
const { data: template } = useQuery({
42+
...templateQueryOptions(task?.workspace.template_id ?? ""),
43+
enabled: Boolean(task),
44+
});
45+
46+
const waitingStatuses: WorkspaceStatus[] = ["starting", "pending"];
47+
const shouldStreamBuildLogs =
48+
task && waitingStatuses.includes(task.workspace.latest_build.status);
49+
const buildLogs = useWorkspaceBuildLogs(
50+
task?.workspace.latest_build.id ?? "",
51+
shouldStreamBuildLogs,
52+
);
53+
3554
if (error) {
3655
return (
3756
<>
@@ -77,7 +96,6 @@ const TaskPage = () => {
7796
}
7897

7998
let content: ReactNode = null;
80-
const waitingStatuses: WorkspaceStatus[] = ["starting", "pending"];
8199
const terminatedStatuses: WorkspaceStatus[] = [
82100
"canceled",
83101
"canceling",
@@ -88,17 +106,27 @@ const TaskPage = () => {
88106
];
89107

90108
if (waitingStatuses.includes(task.workspace.latest_build.status)) {
109+
// If no template yet, use null values for an indeterminate progress bar.
110+
const transition = (template &&
111+
ActiveTransition(template, task.workspace)) || { P50: null, P95: null };
112+
const lastStage = buildLogs?.[buildLogs.length - 1]?.stage;
91113
content = (
92-
<div className="w-full min-h-80 flex items-center justify-center">
114+
<div className="w-full min-h-80 flex flex-col items-center justify-center gap-2">
93115
<div className="flex flex-col items-center">
94-
<Spinner loading className="mb-4" />
95116
<h3 className="m-0 font-medium text-content-primary text-base">
96117
Starting your workspace
97118
</h3>
98119
<span className="text-content-secondary text-sm">
99120
This should take a few minutes
100121
</span>
101122
</div>
123+
{lastStage && (
124+
<div className="text-content-secondary text-sm">{lastStage}</div>
125+
)}
126+
<WorkspaceBuildProgress
127+
workspace={task.workspace}
128+
transitionStats={transition}
129+
/>
102130
</div>
103131
);
104132
} else if (task.workspace.latest_build.status === "failed") {

0 commit comments

Comments
 (0)