Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 288ec77

Browse files
authored
feat: add workspace build status to task page (#18520)
While a workspace is starting, display the build status and a progress bar.
1 parent a8e2c75 commit 288ec77

File tree

3 files changed

+81
-23
lines changed

3 files changed

+81
-23
lines changed

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

Lines changed: 12 additions & 0 deletions
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,6 +10,7 @@ import {
910
MockFailedWorkspace,
1011
MockStartingWorkspace,
1112
MockStoppedWorkspace,
13+
MockTemplate,
1214
MockWorkspace,
1315
MockWorkspaceAgent,
1416
MockWorkspaceApp,
@@ -59,6 +61,16 @@ export const WaitingOnBuild: Story = {
5961
},
6062
};
6163

64+
export const WaitingOnBuildWithTemplate: Story = {
65+
beforeEach: () => {
66+
spyOn(API, "getTemplate").mockResolvedValue(MockTemplate);
67+
spyOn(data, "fetchTask").mockResolvedValue({
68+
prompt: "Create competitors page",
69+
workspace: MockStartingWorkspace,
70+
});
71+
},
72+
};
73+
6274
export const WaitingOnStatus: Story = {
6375
beforeEach: () => {
6476
spyOn(data, "fetchTask").mockResolvedValue({

site/src/pages/TaskPage/TaskPage.tsx

Lines changed: 34 additions & 7 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,16 +106,25 @@ const TaskPage = () => {
88106
];
89107

90108
if (waitingStatuses.includes(task.workspace.latest_build.status)) {
109+
// If no template yet, use an indeterminate progress bar.
110+
const transition = (template &&
111+
ActiveTransition(template, task.workspace)) || { P50: 0, P95: null };
112+
const lastStage =
113+
buildLogs?.[buildLogs.length - 1]?.stage || "Waiting for build status";
91114
content = (
92-
<div className="w-full min-h-80 flex items-center justify-center">
93-
<div className="flex flex-col items-center">
94-
<Spinner loading className="mb-4" />
115+
<div className="w-full min-h-80 flex flex-col">
116+
<div className="flex flex-col items-center grow justify-center">
95117
<h3 className="m-0 font-medium text-content-primary text-base">
96118
Starting your workspace
97119
</h3>
98-
<span className="text-content-secondary text-sm">
99-
This should take a few minutes
100-
</span>
120+
<div className="text-content-secondary text-sm">{lastStage}</div>
121+
</div>
122+
<div className="w-full">
123+
<WorkspaceBuildProgress
124+
workspace={task.workspace}
125+
transitionStats={transition}
126+
variant="task"
127+
/>
101128
</div>
102129
</div>
103130
);

site/src/pages/WorkspacePage/WorkspaceBuildProgress.tsx

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,18 @@ const estimateFinish = (
6262
interface WorkspaceBuildProgressProps {
6363
workspace: Workspace;
6464
transitionStats: TransitionStats;
65+
// variant changes how the progress bar is displayed: with the workspace
66+
// variant the workspace transition and time remaining are displayed under the
67+
// bar aligned to the left and right respectively. With the task variant the
68+
// workspace transition is not displayed and the time remaining is displayed
69+
// centered above the bar, and the bar's border radius is removed.
70+
variant?: "workspace" | "task";
6571
}
6672

6773
export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
6874
workspace,
6975
transitionStats,
76+
variant,
7077
}) => {
7178
const job = workspace.latest_build.job;
7279
const [progressValue, setProgressValue] = useState<number | undefined>(0);
@@ -114,6 +121,13 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
114121
}
115122
return (
116123
<div css={styles.stack}>
124+
{variant === "task" && (
125+
<div className="mb-1 text-center">
126+
<div css={styles.label} data-chromatic="ignore">
127+
{progressText}
128+
</div>
129+
</div>
130+
)}
117131
<LinearProgress
118132
data-chromatic="ignore"
119133
value={progressValue !== undefined ? progressValue : 0}
@@ -126,26 +140,36 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
126140
? "determinate"
127141
: "indeterminate"
128142
}
129-
// If a transition is set, there is a moment on new load where the
130-
// bar accelerates to progressValue and then rapidly decelerates, which
131-
// is not indicative of true progress.
132-
classes={{ bar: classNames.bar }}
143+
classes={{
144+
// If a transition is set, there is a moment on new load where the bar
145+
// accelerates to progressValue and then rapidly decelerates, which is
146+
// not indicative of true progress.
147+
bar: classNames.bar,
148+
// With the "task" variant, the progress bar is fullscreen, so remove
149+
// the border radius.
150+
root: variant === "task" ? classNames.root : undefined,
151+
}}
133152
/>
134-
<div css={styles.barHelpers}>
135-
<div css={styles.label}>
136-
{capitalize(workspace.latest_build.status)} workspace...
137-
</div>
138-
<div css={styles.label} data-chromatic="ignore">
139-
{progressText}
153+
{variant !== "task" && (
154+
<div className="flex mt-1 justify-between">
155+
<div css={styles.label}>
156+
{capitalize(workspace.latest_build.status)} workspace...
157+
</div>
158+
<div css={styles.label} data-chromatic="ignore">
159+
{progressText}
160+
</div>
140161
</div>
141-
</div>
162+
)}
142163
</div>
143164
);
144165
};
145166

146167
const classNames = {
147168
bar: css`
148169
transition: none;
170+
`,
171+
root: css`
172+
border-radius: 0;
149173
`,
150174
};
151175

@@ -154,11 +178,6 @@ const styles = {
154178
paddingLeft: 2,
155179
paddingRight: 2,
156180
},
157-
barHelpers: {
158-
display: "flex",
159-
justifyContent: "space-between",
160-
marginTop: 4,
161-
},
162181
label: (theme) => ({
163182
fontSize: 12,
164183
display: "block",

0 commit comments

Comments
 (0)