|
4 | 4 | * See License-AGPL.txt in the project root for license information.
|
5 | 5 | */
|
6 | 6 |
|
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"; |
14 | 8 | import Spinner from "../icons/Spinner.svg";
|
15 | 9 | import StatusDone from "../icons/StatusDone.svg";
|
16 | 10 | import StatusFailed from "../icons/StatusFailed.svg";
|
17 | 11 | import StatusPaused from "../icons/StatusPaused.svg";
|
18 | 12 | 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"; |
23 | 13 |
|
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 |
| -} |
206 | 14 |
|
207 | 15 | export function prebuildStatusLabel(status: PrebuiltWorkspaceState | undefined) {
|
208 | 16 | switch (status) {
|
|
0 commit comments