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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy