Skip to content

Commit 25c41d7

Browse files
committed
feat: add workspace build status to task page
1 parent 64a2214 commit 25c41d7

File tree

2 files changed

+64
-11
lines changed

2 files changed

+64
-11
lines changed

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
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 { Workspace, WorkspaceApp } from "api/typesGenerated";
45
import {
56
MockFailedWorkspace,
67
MockStartingWorkspace,
78
MockStoppedWorkspace,
9+
MockTemplate,
810
MockWorkspace,
911
MockWorkspaceAgent,
1012
MockWorkspaceApp,
1113
MockWorkspaceAppStatus,
14+
MockWorkspaceBuildLogs,
1215
MockWorkspaceResource,
1316
mockApiError,
1417
} from "testHelpers/entities";
15-
import { withProxyProvider } from "testHelpers/storybook";
18+
import { withProxyProvider, withWebSocket } from "testHelpers/storybook";
1619
import TaskPage, { data } from "./TaskPage";
1720

1821
const meta: Meta<typeof TaskPage> = {
@@ -54,6 +57,33 @@ export const WaitingOnBuild: Story = {
5457
},
5558
};
5659

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

site/src/pages/TaskPage/TaskPage.tsx

Lines changed: 33 additions & 10 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 { 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") {
@@ -186,13 +214,8 @@ export const data = {
186214
const parameters = await API.getWorkspaceBuildParameters(
187215
workspace.latest_build.id,
188216
);
189-
const prompt = parameters.find(
190-
(p) => p.name === AI_PROMPT_PARAMETER_NAME,
191-
)?.value;
192-
193-
if (!prompt) {
194-
return;
195-
}
217+
const prompt =
218+
parameters.find((p) => p.name === AI_PROMPT_PARAMETER_NAME)?.value ?? "";
196219

197220
return {
198221
workspace,

0 commit comments

Comments
 (0)