Skip to content

Commit fcfbe64

Browse files
committed
[dashboard] Better surface Prebuild errors & fix all 'Run Prebuild' buttons
1 parent 5375f50 commit fcfbe64

File tree

8 files changed

+109
-101
lines changed

8 files changed

+109
-101
lines changed

components/dashboard/src/components/PrebuildLogs.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ export default function PrebuildLogs(props: PrebuildLogsProps) {
110110
getGitpodService().server.watchWorkspaceImageBuildLogs(workspace!.id);
111111
break;
112112
}
113+
if (workspaceInstance?.status.conditions.headlessTaskFailed) {
114+
setError(new Error(workspaceInstance.status.conditions.headlessTaskFailed));
115+
}
113116
if (workspaceInstance?.status.conditions.failed) {
114117
setError(new Error(workspaceInstance.status.conditions.failed));
115118
}

components/dashboard/src/components/WorkspaceLogs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export default function WorkspaceLogs(props: WorkspaceLogsProps) {
7575

7676
useEffect(() => {
7777
if (terminalRef.current && props.errorMessage) {
78-
terminalRef.current.write(`\r\n\u001b[38;5;196m${props.errorMessage}\u001b[0m`);
78+
terminalRef.current.write(`\r\n\u001b[38;5;196m${props.errorMessage}\u001b[0m\r\n`);
7979
}
8080
}, [ terminalRef.current, props.errorMessage ]);
8181

components/dashboard/src/projects/Prebuild.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
import moment from "moment";
88
import { PrebuildWithStatus, WorkspaceInstance } from "@gitpod/gitpod-protocol";
99
import { useContext, useEffect, useState } from "react";
10-
import { useLocation, useRouteMatch } from "react-router";
10+
import { useHistory, useLocation, useRouteMatch } from "react-router";
1111
import Header from "../components/Header";
1212
import PrebuildLogs from "../components/PrebuildLogs";
13+
import Spinner from "../icons/Spinner.svg";
1314
import { getGitpodService, gitpodHostUrl } from "../service/service";
1415
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
1516
import { PrebuildInstanceStatus } from "./Prebuilds";
1617
import { shortCommitMessage } from "./render-utils";
1718

1819
export default function () {
20+
const history = useHistory();
1921
const location = useLocation();
2022

2123
const { teams } = useContext(TeamsContext);
@@ -27,6 +29,7 @@ export default function () {
2729

2830
const [ prebuild, setPrebuild ] = useState<PrebuildWithStatus | undefined>();
2931
const [ prebuildInstance, setPrebuildInstance ] = useState<WorkspaceInstance | undefined>();
32+
const [ isRerunningPrebuild, setIsRerunningPrebuild ] = useState<boolean>(false);
3033

3134
useEffect(() => {
3235
if (!teams || !projectName || !prebuildId) {
@@ -76,6 +79,22 @@ export default function () {
7679
setPrebuildInstance(instance);
7780
}
7881

82+
const rerunPrebuild = async () => {
83+
if (!prebuild) {
84+
return;
85+
}
86+
setIsRerunningPrebuild(true);
87+
try {
88+
await getGitpodService().server.triggerPrebuild(prebuild.info.projectId, prebuild.info.branch);
89+
// TODO: Open a Prebuilds page that's specific to `prebuild.info.branch`?
90+
history.push(`/${!!team ? 't/'+team.slug : 'projects'}/${projectName}/prebuilds`);
91+
} catch (error) {
92+
console.error('Could not rerun prebuild', error);
93+
} finally {
94+
setIsRerunningPrebuild(false);
95+
}
96+
}
97+
7998
useEffect(() => { document.title = 'Prebuild — Gitpod' }, []);
8099

81100
return <>
@@ -88,9 +107,14 @@ export default function () {
88107
<div className="h-20 px-6 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-600 flex space-x-2">
89108
{prebuildInstance && <PrebuildInstanceStatus prebuildInstance={prebuildInstance} />}
90109
<div className="flex-grow" />
91-
{prebuildInstance?.status.phase === "stopped"
92-
? <a className="my-auto" href={gitpodHostUrl.withContext(`${prebuild?.info.changeUrl}`).toString()}><button>New Workspace</button></a>
93-
: <button disabled={true}>New Workspace</button>}
110+
{(prebuild?.status === 'aborted' || prebuild?.status === 'timeout' || !!prebuild?.error)
111+
? <button className="flex items-center space-x-2" disabled={isRerunningPrebuild} onClick={rerunPrebuild}>
112+
{isRerunningPrebuild && <img className="h-4 w-4 animate-spin filter brightness-150" src={Spinner} />}
113+
<span>Rerun Prebuild ({prebuild.info.branch})</span>
114+
</button>
115+
: (prebuild?.status === 'available'
116+
? <a className="my-auto" href={gitpodHostUrl.withContext(`${prebuild?.info.changeUrl}`).toString()}><button>New Workspace ({prebuild?.info.branch})</button></a>
117+
: <button disabled={true}>New Workspace ({prebuild?.info.branch})</button>)}
94118
</div>
95119
</div>
96120
</div>

components/dashboard/src/projects/Prebuilds.tsx

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
*/
66

77
import moment from "moment";
8-
import { PrebuildInfo, PrebuildWithStatus, PrebuiltWorkspaceState, Project, WorkspaceInstance } from "@gitpod/gitpod-protocol";
8+
import { PrebuildWithStatus, PrebuiltWorkspaceState, Project, WorkspaceInstance } from "@gitpod/gitpod-protocol";
99
import { useContext, useEffect, useState } from "react";
10-
import { useHistory, useLocation, useRouteMatch } from "react-router";
10+
import { useLocation, useRouteMatch } from "react-router";
1111
import Header from "../components/Header";
1212
import DropDown, { DropDownEntry } from "../components/DropDown";
1313
import { ItemsList, Item, ItemField, ItemFieldContextMenu } from "../components/ItemsList";
@@ -22,7 +22,6 @@ import { ContextMenuEntry } from "../components/ContextMenu";
2222
import { shortCommitMessage } from "./render-utils";
2323

2424
export default function () {
25-
const history = useHistory();
2625
const location = useLocation();
2726

2827
const { teams } = useContext(TeamsContext);
@@ -45,7 +44,7 @@ export default function () {
4544
const registration = getGitpodService().registerClient({
4645
onPrebuildUpdate: (update: PrebuildWithStatus) => {
4746
if (update.info.projectId === project.id) {
48-
setPrebuilds(prev => [update, ...prev.filter(p => p.info.id !== update.info.id)])
47+
setPrebuilds(prev => [update, ...prev.filter(p => p.info.id !== update.info.id)]);
4948
}
5049
}
5150
});
@@ -77,18 +76,17 @@ export default function () {
7776
}, [teams]);
7877

7978
const prebuildContextMenu = (p: PrebuildWithStatus) => {
80-
const running = p.status === "building";
79+
const isFailed = p.status === "aborted" || p.status === "timeout" || !!p.error;
80+
const isRunning = p.status === "building";
8181
const entries: ContextMenuEntry[] = [];
82-
entries.push({
83-
title: "View Prebuild",
84-
onClick: () => openPrebuild(p.info)
85-
});
86-
entries.push({
87-
title: "Trigger Prebuild",
88-
onClick: () => triggerPrebuild(p.info.branch),
89-
separator: running
90-
});
91-
if (running) {
82+
if (isFailed) {
83+
entries.push({
84+
title: `Rerun Prebuild (${p.info.branch})`,
85+
onClick: () => triggerPrebuild(p.info.branch),
86+
separator: isRunning
87+
});
88+
}
89+
if (isRunning) {
9290
entries.push({
9391
title: "Cancel Prebuild",
9492
customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300',
@@ -131,10 +129,6 @@ export default function () {
131129
return -1;
132130
}
133131

134-
const openPrebuild = (pb: PrebuildInfo) => {
135-
history.push(`/${!!team ? 't/'+team.slug : 'projects'}/${projectName}/${pb.id}`);
136-
}
137-
138132
const triggerPrebuild = (branchName: string | null) => {
139133
if (project) {
140134
getGitpodService().server.triggerPrebuild(project.id, branchName);
@@ -159,7 +153,8 @@ export default function () {
159153
<div className="py-3 pl-3">
160154
<DropDown prefix="Prebuild Status: " contextMenuWidth="w-32" entries={statusFilterEntries()} />
161155
</div>
162-
<button disabled={!project} onClick={() => triggerPrebuild(null)} className="ml-2">Trigger Prebuild</button>
156+
{(!!project && prebuilds.length === 0) &&
157+
<button onClick={() => triggerPrebuild(null)} className="ml-2">Run Prebuild</button>}
163158
</div>
164159
<ItemsList className="mt-2">
165160
<Item header={true} className="grid grid-cols-3">
@@ -175,13 +170,13 @@ export default function () {
175170
</Item>
176171
{prebuilds.filter(filter).sort(prebuildSorter).map((p, index) => <Item key={`prebuild-${p.info.id}`} className="grid grid-cols-3">
177172
<ItemField className="flex items-center">
178-
<div className="cursor-pointer" onClick={() => openPrebuild(p.info)}>
173+
<a href={`/${!!team ? 't/'+team.slug : 'projects'}/${projectName}/${p.info.id}`} className="cursor-pointer">
179174
<div className="text-base text-gray-900 dark:text-gray-50 font-medium uppercase mb-1">
180-
<div className="inline-block align-text-bottom mr-2 w-4 h-4">{prebuildStatusIcon(p.status)}</div>
181-
{prebuildStatusLabel(p.status)}
175+
<div className="inline-block align-text-bottom mr-2 w-4 h-4">{prebuildStatusIcon(p)}</div>
176+
{prebuildStatusLabel(p)}
182177
</div>
183178
<p>{p.info.startedByAvatar && <img className="rounded-full w-4 h-4 inline-block align-text-bottom mr-2" src={p.info.startedByAvatar || ''} alt={p.info.startedBy} />}Triggered {formatDate(p.info.startedAt)}</p>
184-
</div>
179+
</a>
185180
</ItemField>
186181
<ItemField className="flex items-center">
187182
<div>
@@ -203,41 +198,39 @@ export default function () {
203198
</>;
204199
}
205200

206-
export function prebuildStatusLabel(status: PrebuiltWorkspaceState | undefined) {
207-
switch (status) {
208-
case "aborted":
201+
export function prebuildStatusLabel(prebuild?: PrebuildWithStatus) {
202+
if (prebuild?.error) {
203+
return (<span className="font-medium text-red-500 uppercase">failed</span>);
204+
}
205+
switch (prebuild?.status) {
206+
case undefined: // Fall through
207+
case "queued":
208+
return (<span className="font-medium text-orange-500 uppercase">pending</span>);
209+
case "building":
210+
return (<span className="font-medium text-blue-500 uppercase">running</span>);
211+
case "aborted": // Fall through
212+
case "timeout":
209213
return (<span className="font-medium text-red-500 uppercase">failed</span>);
210214
case "available":
211215
return (<span className="font-medium text-green-500 uppercase">ready</span>);
212-
case "building":
213-
return (<span className="font-medium text-blue-500 uppercase">running</span>);
214-
case "queued":
215-
return (<span className="font-medium text-orange-500 uppercase">pending</span>);
216-
default:
217-
break;
218216
}
219217
}
220218

221-
export function prebuildStatusIcon(status: PrebuiltWorkspaceState | undefined) {
222-
switch (status) {
223-
case "aborted":
224-
return (<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
225-
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM6.70711 5.29289C6.31658 4.90237 5.68342 4.90237 5.29289 5.29289C4.90237 5.68342 4.90237 6.31658 5.29289 6.70711L6.58579 8L5.29289 9.29289C4.90237 9.68342 4.90237 10.3166 5.29289 10.7071C5.68342 11.0976 6.31658 11.0976 6.70711 10.7071L8 9.41421L9.29289 10.7071C9.68342 11.0976 10.3166 11.0976 10.7071 10.7071C11.0976 10.3166 11.0976 9.68342 10.7071 9.29289L9.41421 8L10.7071 6.70711C11.0976 6.31658 11.0976 5.68342 10.7071 5.29289C10.3166 4.90237 9.68342 4.90237 9.29289 5.29289L8 6.58579L6.70711 5.29289Z" fill="#F87171" />
226-
</svg>)
227-
case "available":
228-
return (<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
229-
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM11.7071 6.70711C12.0976 6.31658 12.0976 5.68342 11.7071 5.29289C11.3166 4.90237 10.6834 4.90237 10.2929 5.29289L7 8.58578L5.7071 7.29289C5.31658 6.90237 4.68342 6.90237 4.29289 7.29289C3.90237 7.68342 3.90237 8.31658 4.29289 8.70711L6.29289 10.7071C6.68342 11.0976 7.31658 11.0976 7.7071 10.7071L11.7071 6.70711Z" fill="#84CC16" />
230-
</svg>);
231-
case "building":
232-
return (<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
233-
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99609 16C13.4144 16 16.9961 12.4183 16.9961 8C16.9961 3.58172 13.4144 0 8.99609 0C4.57781 0 0.996094 3.58172 0.996094 8C0.996094 12.4183 4.57781 16 8.99609 16ZM9.99609 4C9.99609 3.44772 9.54837 3 8.99609 3C8.4438 3 7.99609 3.44772 7.99609 4V8C7.99609 8.26522 8.10144 8.51957 8.28898 8.70711L11.1174 11.5355C11.5079 11.9261 12.1411 11.9261 12.5316 11.5355C12.9221 11.145 12.9221 10.5118 12.5316 10.1213L9.99609 7.58579V4Z" fill="#60A5FA" />
234-
</svg>);
219+
export function prebuildStatusIcon(prebuild?: PrebuildWithStatus) {
220+
if (prebuild?.error) {
221+
return <img className="h-4 w-4" src={StatusFailed} />;
222+
}
223+
switch (prebuild?.status) {
224+
case undefined: // Fall through
235225
case "queued":
236-
return (<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
237-
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8ZM5 6C5 5.44772 5.44772 5 6 5C6.55228 5 7 5.44772 7 6V10C7 10.5523 6.55228 11 6 11C5.44772 11 5 10.5523 5 10V6ZM10 5C9.44771 5 9 5.44772 9 6V10C9 10.5523 9.44771 11 10 11C10.5523 11 11 10.5523 11 10V6C11 5.44772 10.5523 5 10 5Z" fill="#FBBF24" />
238-
</svg>);
239-
default:
240-
break;
226+
return <img className="h-4 w-4" src={StatusPaused} />;
227+
case "building":
228+
return <img className="h-4 w-4" src={StatusRunning} />;
229+
case "aborted": // Fall through
230+
case "timeout":
231+
return <img className="h-4 w-4" src={StatusFailed} />;
232+
case "available":
233+
return <img className="h-4 w-4" src={StatusDone} />;
241234
}
242235
}
243236

@@ -285,7 +278,7 @@ export function PrebuildInstanceStatus(props: { prebuildInstance?: WorkspaceInst
285278
</div>;
286279
break;
287280
}
288-
if (props.prebuildInstance?.status.conditions.failed) {
281+
if (props.prebuildInstance?.status.conditions.failed || props.prebuildInstance?.status.conditions.headlessTaskFailed) {
289282
status = <div className="flex space-x-1 items-center text-gitpod-red">
290283
<img className="h-4 w-4" src={StatusFailed} />
291284
<span>FAILED</span>

components/dashboard/src/projects/Project.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { ItemsList, Item, ItemField, ItemFieldContextMenu } from "../components/
1313
import { getGitpodService, gitpodHostUrl } from "../service/service";
1414
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
1515
import { prebuildStatusIcon, prebuildStatusLabel } from "./Prebuilds";
16-
import { ContextMenuEntry } from "../components/ContextMenu";
1716
import { shortCommitMessage } from "./render-utils";
1817
import Spinner from "../icons/Spinner.svg";
1918

@@ -70,15 +69,6 @@ export default function () {
7069
}
7170
}
7271

73-
const branchContextMenu = (branch: Project.BranchDetails) => {
74-
const entries: ContextMenuEntry[] = [];
75-
entries.push({
76-
title: "Rerun Prebuild",
77-
onClick: () => triggerPrebuild(branch),
78-
});
79-
return entries;
80-
}
81-
8272
const lastPrebuild = (branch: Project.BranchDetails) => {
8373
const lastPrebuild = lastPrebuilds.get(branch.name);
8474
if (!lastPrebuild) {
@@ -160,18 +150,17 @@ export default function () {
160150
</div>}
161151
{branches.filter(filter).slice(0, 10).map((branch, index) => {
162152

163-
const branchName = branch.name;
164153
const prebuild = lastPrebuild(branch); // this might lazily trigger fetching of prebuild details
165154

166155
const avatar = branch.changeAuthorAvatar && <img className="rounded-full w-4 h-4 inline-block align-text-bottom mr-2" src={branch.changeAuthorAvatar || ''} alt={branch.changeAuthor} />;
167-
const statusIcon = prebuildStatusIcon(prebuild?.status);
168-
const status = prebuildStatusLabel(prebuild?.status);
156+
const statusIcon = prebuildStatusIcon(prebuild);
157+
const status = prebuildStatusLabel(prebuild);
169158

170-
return <Item key={`branch-${index}-${branchName}`} className="grid grid-cols-3 group">
159+
return <Item key={`branch-${index}-${branch.name}`} className="grid grid-cols-3 group">
171160
<ItemField className="flex items-center">
172161
<div>
173162
<div className="text-base text-gray-900 dark:text-gray-50 font-medium mb-1">
174-
{branchName}
163+
{branch.name}
175164
{branch.isDefault && (<span className="ml-2 self-center rounded-xl py-0.5 px-2 text-sm bg-blue-50 text-blue-40 dark:bg-blue-500 dark:text-blue-100">DEFAULT</span>)}
176165
</div>
177166
</div>
@@ -190,7 +179,12 @@ export default function () {
190179
<a href={gitpodHostUrl.withContext(`${branch.url}`).toString()}>
191180
<button className={`primary mr-2 py-2 opacity-0 group-hover:opacity-100`}>New Workspace</button>
192181
</a>
193-
<ItemFieldContextMenu className="py-0.5" menuEntries={branchContextMenu(branch)} />
182+
<ItemFieldContextMenu className="py-0.5" menuEntries={(!prebuild || prebuild.status === 'aborted' || prebuild.status === 'timeout' || !!prebuild.error)
183+
? [{
184+
title: `${prebuild ? 'Rerun' : 'Run'} Prebuild (${branch.name})`,
185+
onClick: () => triggerPrebuild(branch),
186+
}]
187+
: []} />
194188
</ItemField>
195189
</Item>
196190
}

0 commit comments

Comments
 (0)