Skip to content

Commit 740cf8f

Browse files
committed
[dashboard] Create branch detail page
1 parent 5e04b21 commit 740cf8f

File tree

4 files changed

+58
-246
lines changed

4 files changed

+58
-246
lines changed

components/dashboard/src/App.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ const NewProject = React.lazy(() => import(/* webpackPrefetch: true */ './projec
3838
const ConfigureProject = React.lazy(() => import(/* webpackPrefetch: true */ './projects/ConfigureProject'));
3939
const Projects = React.lazy(() => import(/* webpackPrefetch: true */ './projects/Projects'));
4040
const Project = React.lazy(() => import(/* webpackPrefetch: true */ './projects/Project'));
41-
const Prebuilds = React.lazy(() => import(/* webpackPrefetch: true */ './projects/Prebuilds'));
42-
const Prebuild = React.lazy(() => import(/* webpackPrefetch: true */ './projects/Prebuild'));
41+
const Branch = React.lazy(() => import(/* webpackPrefetch: true */ './projects/Branch'));
4342
const InstallGitHubApp = React.lazy(() => import(/* webpackPrefetch: true */ './prebuilds/InstallGitHubApp'));
4443
const FromReferrer = React.lazy(() => import(/* webpackPrefetch: true */ './FromReferrer'));
4544
const UserSearch = React.lazy(() => import(/* webpackPrefetch: true */ './admin/UserSearch'));
@@ -268,10 +267,7 @@ function App() {
268267
if (resourceOrPrebuild === "workspaces") {
269268
return <Workspaces />;
270269
}
271-
if (resourceOrPrebuild === "prebuilds") {
272-
return <Prebuilds />;
273-
}
274-
return resourceOrPrebuild ? <Prebuild /> : <Project />;
270+
return resourceOrPrebuild ? <Branch /> : <Project />;
275271
}} />
276272
</Route>
277273
<Route path="/teams">
@@ -301,10 +297,7 @@ function App() {
301297
if (resourceOrPrebuild === "workspaces") {
302298
return <Workspaces />;
303299
}
304-
if (resourceOrPrebuild === "prebuilds") {
305-
return <Prebuilds />;
306-
}
307-
return resourceOrPrebuild ? <Prebuild /> : <Project />;
300+
return resourceOrPrebuild ? <Branch /> : <Project />;
308301
}} />
309302
</Route>)}
310303
<Route path="*" render={

components/dashboard/src/projects/Prebuild.tsx renamed to components/dashboard/src/projects/Branch.tsx

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,26 @@ import { useContext, useEffect, useState } from "react";
1010
import { useLocation, useRouteMatch } from "react-router";
1111
import Header from "../components/Header";
1212
import PrebuildLogs from "../components/PrebuildLogs";
13-
import { getGitpodService, gitpodHostUrl } from "../service/service";
13+
import { getGitpodService } from "../service/service";
1414
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
15-
import { PrebuildInstanceStatus } from "./Prebuilds";
1615
import { shortCommitMessage } from "./render-utils";
16+
import { PrebuildInstanceStatus } from "./Prebuilds";
1717

1818
export default function () {
1919
const location = useLocation();
2020

2121
const { teams } = useContext(TeamsContext);
2222
const team = getCurrentTeam(location, teams);
2323

24-
const match = useRouteMatch<{ team: string, project: string, prebuildId: string }>("/(t/)?:team/:project/:prebuildId");
24+
const match = useRouteMatch<{ team: string, project: string, branchName: string }>("/(t/)?:team/:project/:branchname");
2525
const projectName = match?.params?.project;
26-
const prebuildId = match?.params?.prebuildId;
26+
const branchName = match?.params?.branchName;
2727

28-
const [ prebuild, setPrebuild ] = useState<PrebuildWithStatus | undefined>();
28+
const [ prebuilds, setPrebuilds ] = useState<PrebuildWithStatus[]>([]);
2929
const [ prebuildInstance, setPrebuildInstance ] = useState<WorkspaceInstance | undefined>();
3030

3131
useEffect(() => {
32-
if (!teams || !projectName || !prebuildId) {
32+
if (!teams || !projectName || !branchName) {
3333
return;
3434
}
3535
(async () => {
@@ -43,31 +43,30 @@ export default function () {
4343
}
4444
const prebuilds = await getGitpodService().server.findPrebuilds({
4545
projectId: project.id,
46-
prebuildId
46+
branch: branchName
4747
});
48-
setPrebuild(prebuilds[0]);
48+
setPrebuilds(prebuilds);
4949
})();
5050
}, [ teams ]);
5151

5252
const renderTitle = () => {
53-
if (!prebuild) {
54-
return "unknown prebuild";
55-
}
56-
return (<h1 className="tracking-tight">{prebuild.info.branch} </h1>);
53+
return (<h1 className="tracking-tight">{branchName} </h1>);
5754
};
5855

56+
const startedByAvatar = (prebuild: PrebuildWithStatus) => prebuild.info.startedByAvatar && <img className="rounded-full w-4 h-4 inline-block align-text-bottom mr-2" src={prebuild.info.startedByAvatar || ''} alt={prebuild.info.startedBy} />;
57+
5958
const renderSubtitle = () => {
60-
if (!prebuild) {
59+
if (prebuilds.length === 0) {
6160
return "";
6261
}
63-
const startedByAvatar = prebuild.info.startedByAvatar && <img className="rounded-full w-4 h-4 inline-block align-text-bottom mr-2" src={prebuild.info.startedByAvatar || ''} alt={prebuild.info.startedBy} />;
62+
const startedByAvatar = prebuilds[0].info.startedByAvatar && <img className="rounded-full w-4 h-4 inline-block align-text-bottom mr-2" src={prebuilds[0].info.startedByAvatar || ''} alt={prebuilds[0].info.startedBy} />;
6463
return (<div className="flex">
6564
<div className="my-auto">
66-
<p>{startedByAvatar}Triggered {moment(prebuild.info.startedAt).fromNow()}</p>
65+
<p>{startedByAvatar}Triggered {moment(prebuilds[0].info.startedAt).fromNow()}</p>
6766
</div>
6867
<p className="mx-2 my-auto">·</p>
6968
<div className="my-auto">
70-
<p className="text-gray-500 dark:text-gray-50">{shortCommitMessage(prebuild.info.changeTitle)}</p>
69+
<p className="text-gray-500 dark:text-gray-50">{shortCommitMessage(prebuilds[0].info.changeTitle)}</p>
7170
</div>
7271
</div>)
7372
};
@@ -81,18 +80,26 @@ export default function () {
8180
return <>
8281
<Header title={renderTitle()} subtitle={renderSubtitle()} />
8382
<div className="lg:px-28 px-10 mt-8">
84-
<div className="rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-800 flex flex-col">
85-
<div className="h-96 flex">
86-
<PrebuildLogs workspaceId={prebuild?.info?.buildWorkspaceId} onInstanceUpdate={onInstanceUpdate} />
87-
</div>
88-
<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">
89-
{prebuildInstance && <PrebuildInstanceStatus prebuildInstance={prebuildInstance} />}
90-
<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>}
94-
</div>
95-
</div>
83+
{
84+
prebuilds.map(prebuild =>
85+
<div className="rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-800 flex flex-col">
86+
<div className="h-96 flex">
87+
<PrebuildLogs workspaceId={prebuild?.info?.buildWorkspaceId} onInstanceUpdate={onInstanceUpdate} />
88+
</div>
89+
<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">
90+
{prebuildInstance && <PrebuildInstanceStatus prebuildInstance={prebuildInstance} />}
91+
<div className="flex-grow" />
92+
<div className="my-auto">
93+
<p>{startedByAvatar(prebuild)}Triggered {moment(prebuild.info.startedAt).fromNow()}</p>
94+
</div>
95+
<p className="mx-2 my-auto">·</p>
96+
<div className="my-auto">
97+
<p className="text-gray-500 dark:text-gray-50">{shortCommitMessage(prebuild.info.changeTitle)}</p>
98+
</div>
99+
</div>
100+
</div>
101+
)
102+
}
96103
</div>
97104
</>;
98105

components/dashboard/src/projects/Prebuilds.tsx

Lines changed: 1 addition & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -4,205 +4,13 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import moment from "moment";
8-
import { PrebuildInfo, PrebuildWithStatus, PrebuiltWorkspaceState, Project, WorkspaceInstance } from "@gitpod/gitpod-protocol";
9-
import { useContext, useEffect, useState } from "react";
10-
import { useHistory, useLocation, useRouteMatch } from "react-router";
11-
import Header from "../components/Header";
12-
import DropDown, { DropDownEntry } from "../components/DropDown";
13-
import { ItemsList, Item, ItemField, ItemFieldContextMenu } from "../components/ItemsList";
7+
import { PrebuiltWorkspaceState, WorkspaceInstance } from "@gitpod/gitpod-protocol";
148
import Spinner from "../icons/Spinner.svg";
159
import StatusDone from "../icons/StatusDone.svg";
1610
import StatusFailed from "../icons/StatusFailed.svg";
1711
import StatusPaused from "../icons/StatusPaused.svg";
1812
import StatusRunning from "../icons/StatusRunning.svg";
19-
import { getGitpodService } from "../service/service";
20-
import { TeamsContext, getCurrentTeam } from "../teams/teams-context";
21-
import { ContextMenuEntry } from "../components/ContextMenu";
22-
import { shortCommitMessage } from "./render-utils";
2313

24-
export default function () {
25-
const history = useHistory();
26-
const location = useLocation();
27-
28-
const { teams } = useContext(TeamsContext);
29-
const team = getCurrentTeam(location, teams);
30-
31-
const match = useRouteMatch<{ team: string, resource: string }>("/(t/)?:team/:resource");
32-
const projectName = match?.params?.resource;
33-
34-
const [project, setProject] = useState<Project | undefined>();
35-
36-
const [searchFilter, setSearchFilter] = useState<string | undefined>();
37-
const [statusFilter, setStatusFilter] = useState<PrebuiltWorkspaceState | undefined>();
38-
39-
const [prebuilds, setPrebuilds] = useState<PrebuildWithStatus[]>([]);
40-
41-
useEffect(() => {
42-
if (!project) {
43-
return;
44-
}
45-
const registration = getGitpodService().registerClient({
46-
onPrebuildUpdate: (update: PrebuildWithStatus) => {
47-
if (update.info.projectId === project.id) {
48-
setPrebuilds(prev => [update, ...prev.filter(p => p.info.id !== update.info.id)])
49-
}
50-
}
51-
});
52-
53-
(async () => {
54-
const prebuilds = await getGitpodService().server.findPrebuilds({ projectId: project.id });
55-
setPrebuilds(prebuilds);
56-
})();
57-
58-
return () => {
59-
registration.dispose();
60-
}
61-
}, [project]);
62-
63-
useEffect(() => {
64-
if (!teams) {
65-
return;
66-
}
67-
(async () => {
68-
const projects = (!!team
69-
? await getGitpodService().server.getTeamProjects(team.id)
70-
: await getGitpodService().server.getUserProjects());
71-
72-
const newProject = projectName && projects.find(p => p.name === projectName);
73-
if (newProject) {
74-
setProject(newProject);
75-
}
76-
})();
77-
}, [teams]);
78-
79-
const prebuildContextMenu = (p: PrebuildWithStatus) => {
80-
const running = p.status === "building";
81-
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) {
92-
entries.push({
93-
title: "Cancel Prebuild",
94-
customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300',
95-
onClick: () => window.alert('cancellation not yet supported')
96-
})
97-
}
98-
return entries;
99-
}
100-
101-
const statusFilterEntries = () => {
102-
const entries: DropDownEntry[] = [];
103-
entries.push({
104-
title: 'All',
105-
onClick: () => setStatusFilter(undefined)
106-
});
107-
entries.push({
108-
title: 'READY',
109-
onClick: () => setStatusFilter("available")
110-
});
111-
return entries;
112-
}
113-
114-
const filter = (p: PrebuildWithStatus) => {
115-
if (statusFilter && statusFilter !== p.status) {
116-
return false;
117-
}
118-
if (searchFilter && `${p.info.changeTitle} ${p.info.branch}`.toLowerCase().includes(searchFilter.toLowerCase()) === false) {
119-
return false;
120-
}
121-
return true;
122-
}
123-
124-
const prebuildSorter = (a: PrebuildWithStatus, b: PrebuildWithStatus) => {
125-
if (a.info.startedAt < b.info.startedAt) {
126-
return 1;
127-
}
128-
if (a.info.startedAt === b.info.startedAt) {
129-
return 0;
130-
}
131-
return -1;
132-
}
133-
134-
const openPrebuild = (pb: PrebuildInfo) => {
135-
history.push(`/${!!team ? 't/'+team.slug : 'projects'}/${projectName}/${pb.id}`);
136-
}
137-
138-
const triggerPrebuild = (branchName: string | null) => {
139-
if (project) {
140-
getGitpodService().server.triggerPrebuild(project.id, branchName);
141-
}
142-
}
143-
144-
const formatDate = (date: string | undefined) => {
145-
return date ? moment(date).fromNow() : "";
146-
}
147-
148-
return <>
149-
<Header title="Prebuilds" subtitle={`View recent prebuilds for active branches.`} />
150-
<div className="lg:px-28 px-10">
151-
<div className="flex mt-8">
152-
<div className="flex">
153-
<div className="py-4">
154-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" width="16" height="16"><path fill="#A8A29E" d="M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" /></svg>
155-
</div>
156-
<input type="search" placeholder="Search Prebuilds" onChange={e => setSearchFilter(e.target.value)} />
157-
</div>
158-
<div className="flex-1" />
159-
<div className="py-3 pl-3">
160-
<DropDown prefix="Prebuild Status: " contextMenuWidth="w-32" entries={statusFilterEntries()} />
161-
</div>
162-
<button disabled={!project} onClick={() => triggerPrebuild(null)} className="ml-2">Trigger Prebuild</button>
163-
</div>
164-
<ItemsList className="mt-2">
165-
<Item header={true} className="grid grid-cols-3">
166-
<ItemField>
167-
<span>Prebuild</span>
168-
</ItemField>
169-
<ItemField>
170-
<span>Commit</span>
171-
</ItemField>
172-
<ItemField>
173-
<span>Branch</span>
174-
<ItemFieldContextMenu />
175-
</ItemField>
176-
</Item>
177-
{prebuilds.filter(filter).sort(prebuildSorter).map((p, index) => <Item key={`prebuild-${p.info.id}`} className="grid grid-cols-3">
178-
<ItemField className="flex items-center">
179-
<div className="cursor-pointer" onClick={() => openPrebuild(p.info)}>
180-
<div className="text-base text-gray-900 dark:text-gray-50 font-medium uppercase mb-1">
181-
<div className="inline-block align-text-bottom mr-2 w-4 h-4">{prebuildStatusIcon(p.status)}</div>
182-
{prebuildStatusLabel(p.status)}
183-
</div>
184-
<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>
185-
</div>
186-
</ItemField>
187-
<ItemField className="flex items-center">
188-
<div>
189-
<div className="text-base text-gray-500 dark:text-gray-50 font-medium mb-1">{shortCommitMessage(p.info.changeTitle)}</div>
190-
<p>{p.info.changeAuthorAvatar && <img className="rounded-full w-4 h-4 inline-block align-text-bottom mr-2" src={p.info.changeAuthorAvatar || ''} alt={p.info.changeAuthor} />}Authored {formatDate(p.info.changeDate)} · {p.info.changeHash?.substring(0, 8)}</p>
191-
</div>
192-
</ItemField>
193-
<ItemField className="flex items-center">
194-
<div className="flex space-x-2">
195-
<span className="font-medium text-gray-500 dark:text-gray-50">{p.info.branch}</span>
196-
</div>
197-
<span className="flex-grow" />
198-
<ItemFieldContextMenu menuEntries={prebuildContextMenu(p)} />
199-
</ItemField>
200-
</Item>)}
201-
</ItemsList>
202-
</div>
203-
204-
</>;
205-
}
20614

20715
export function prebuildStatusLabel(status: PrebuiltWorkspaceState | undefined) {
20816
switch (status) {

0 commit comments

Comments
 (0)