Skip to content

Commit 7e0f738

Browse files
AlexTugarevroboquat
authored andcommitted
[projects] enable GitLab projects
1 parent 98a7dc7 commit 7e0f738

16 files changed

+436
-132
lines changed

components/dashboard/src/projects/NewProject.tsx

Lines changed: 125 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ export default function NewProject() {
2323
const location = useLocation();
2424
const history = useHistory();
2525
const { teams } = useContext(TeamsContext);
26-
const { user } = useContext(UserContext);
26+
const { user, setUser } = useContext(UserContext);
2727

28-
const [provider, setProvider] = useState<string>("github.com");
28+
const [provider, setProvider] = useState<string | undefined>();
2929
const [reposInAccounts, setReposInAccounts] = useState<ProviderRepository[]>([]);
3030
const [repoSearchFilter, setRepoSearchFilter] = useState<string>("");
3131
const [selectedAccount, setSelectedAccount] = useState<string | undefined>(undefined);
@@ -37,6 +37,16 @@ export default function NewProject() {
3737
const [showNewTeam, setShowNewTeam] = useState<boolean>(false);
3838
const [loaded, setLoaded] = useState<boolean>(false);
3939

40+
useEffect(() => {
41+
if (user && provider === undefined) {
42+
if (user.identities.find(i => i.authProviderId === "Public-GitLab")) {
43+
setProvider("gitlab.com");
44+
} else if (user.identities.find(i => i.authProviderId === "Public-GitHub")) {
45+
setProvider("github.com");
46+
}
47+
}
48+
}, [user]);
49+
4050
useEffect(() => {
4151
const params = new URLSearchParams(location.search);
4252
const teamParam = params.get("team");
@@ -45,16 +55,6 @@ export default function NewProject() {
4555
const team = teams?.find(t => t.slug === teamParam);
4656
setSelectedTeamOrUser(team);
4757
}
48-
49-
(async () => {
50-
updateOrgsState();
51-
const repos = await updateReposInAccounts();
52-
const first = repos[0];
53-
if (first) {
54-
setSelectedAccount(first.account);
55-
}
56-
setLoaded(true);
57-
})();
5858
}, []);
5959

6060
useEffect(() => {
@@ -77,9 +77,27 @@ export default function NewProject() {
7777
setRepoSearchFilter("");
7878
}, [selectedAccount]);
7979

80+
useEffect(() => {
81+
if (!provider) {
82+
return;
83+
}
84+
(async () => {
85+
updateOrgsState();
86+
const repos = await updateReposInAccounts();
87+
const first = repos[0];
88+
if (first) {
89+
setSelectedAccount(first.account);
90+
}
91+
setLoaded(true);
92+
})();
93+
}, [provider]);
94+
8095
const isGitHub = () => provider === "github.com";
8196

8297
const updateReposInAccounts = async (installationId?: string) => {
98+
if (!provider) {
99+
return [];
100+
}
83101
try {
84102
const repos = await getGitpodService().server.getProviderRepositoriesForUser({ provider, hints: { installationId } });
85103
setReposInAccounts(repos);
@@ -96,7 +114,7 @@ export default function NewProject() {
96114
}
97115

98116
const updateOrgsState = async () => {
99-
if (isGitHub()) {
117+
if (provider && isGitHub()) {
100118
try {
101119
const ghToken = await getToken(provider);
102120
setNoOrgs(ghToken?.scopes.includes("read:org") !== true);
@@ -130,6 +148,9 @@ export default function NewProject() {
130148
}
131149

132150
const createProject = async (teamOrUser: Team | User, selectedRepo: string) => {
151+
if (!provider) {
152+
return;
153+
}
133154
const repo = reposInAccounts.find(r => r.account === selectedAccount && r.name === selectedRepo);
134155
if (!repo) {
135156
console.error("No repo selected!")
@@ -156,7 +177,6 @@ export default function NewProject() {
156177
return splitted.shift() && splitted.join("/");
157178
}
158179

159-
const reposToRender = Array.from(reposInAccounts).filter(r => r.account === selectedAccount && r.name.includes(repoSearchFilter));
160180
const accounts = new Map<string, { avatarUrl: string }>();
161181
reposInAccounts.forEach(r => { if (!accounts.has(r.account)) accounts.set(r.account, { avatarUrl: r.accountAvatarUrl }) });
162182

@@ -193,33 +213,40 @@ export default function NewProject() {
193213

194214
const renderSelectRepository = () => {
195215

216+
const noReposAvailable = reposInAccounts.length === 0;
217+
const filteredRepos = Array.from(reposInAccounts).filter(r => r.account === selectedAccount && r.name.includes(repoSearchFilter));
196218
const icon = selectedAccount && accounts.get(selectedAccount)?.avatarUrl;
197219

198-
const renderRepos = () => (<div className="mt-10 border rounded-t-xl border-gray-100 flex-col">
199-
<div className="px-8 pt-8 flex flex-col space-y-2">
200-
<ContextMenu classes="w-full left-0 cursor-pointer" menuEntries={getDropDownEntries(accounts)}>
201-
<div className="w-full">
202-
<img src={icon} className="rounded-full w-6 h-6 absolute top-1/4 left-4" />
203-
<input className="w-full px-12 cursor-pointer font-semibold" readOnly type="text" value={selectedAccount || ""}></input>
204-
<img src={CaretDown} title="Select Account" className="filter-grayscale absolute top-1/2 right-3" />
205-
</div>
206-
</ContextMenu>
207-
<div className="w-full relative ">
208-
<img src={search} title="Search" className="filter-grayscale absolute top-1/3 left-3" />
209-
<input className="w-96 pl-10 border-0" type="text" placeholder="Search Repositories" value={repoSearchFilter}
210-
onChange={(e) => setRepoSearchFilter(e.target.value)}></input>
220+
const renderRepos = () => (<>
221+
<div className={`mt-10 border rounded-xl border-gray-100 flex-col`}>
222+
<div className="px-8 pt-8 flex flex-col space-y-2">
223+
<ContextMenu classes="w-full left-0 cursor-pointer" menuEntries={getDropDownEntries(accounts)}>
224+
<div className="w-full">
225+
{icon && (
226+
<img src={icon} className="rounded-full w-6 h-6 absolute top-1/4 left-4" />
227+
)}
228+
<input className="w-full px-12 cursor-pointer font-semibold" readOnly type="text" value={selectedAccount || ""}></input>
229+
<img src={CaretDown} title="Select Account" className="filter-grayscale absolute top-1/2 right-3" />
230+
</div>
231+
</ContextMenu>
232+
{filteredRepos.length > 0 && (
233+
<div className="w-full relative ">
234+
<img src={search} title="Search" className="filter-grayscale absolute top-1/3 left-3" />
235+
<input className="w-96 pl-10 border-0" type="text" placeholder="Search Repositories" value={repoSearchFilter}
236+
onChange={(e) => setRepoSearchFilter(e.target.value)}></input>
237+
</div>
238+
)}
211239
</div>
212-
</div>
213-
<div className="p-6 flex-col">
214-
{reposToRender.length > 0 && (
215-
<div className="overscroll-contain max-h-80 overflow-y-auto pr-2">
216-
{reposToRender.map(r => (
217-
<div key={`repo-${r.name}`} className="flex p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group" >
218-
219-
<div className="flex-grow">
220-
<div className="text-base text-gray-900 dark:text-gray-50 font-medium rounded-xl whitespace-nowrap">{toSimpleName(r.name)}</div>
221-
<p>Updated {moment(r.updatedAt).fromNow()}</p>
222-
</div>
240+
<div className="p-6 flex-col">
241+
{filteredRepos.length > 0 && (
242+
<div className="overscroll-contain max-h-80 overflow-y-auto pr-2">
243+
{filteredRepos.map((r, index) => (
244+
<div key={`repo-${index}-${r.account}-${r.name}`} className="flex p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group" >
245+
246+
<div className="flex-grow">
247+
<div className="text-base text-gray-900 dark:text-gray-50 font-medium rounded-xl whitespace-nowrap">{toSimpleName(r.name)}</div>
248+
<p>Updated {moment(r.updatedAt).fromNow()}</p>
249+
</div>
223250
<div className="flex justify-end">
224251
<div className="h-full my-auto flex self-center opacity-0 group-hover:opacity-100">
225252
{!r.inUse ? (
@@ -229,54 +256,73 @@ export default function NewProject() {
229256
)}
230257
</div>
231258
</div>
259+
</div>
260+
))}
261+
</div>
262+
)}
263+
{!noReposAvailable && filteredRepos.length === 0 && (
264+
<p className="text-center">No Results</p>
265+
)}
266+
{loaded && noReposAvailable && isGitHub() && (<div>
267+
<div className="px-12 py-16 text-center text-gray-500 bg-gray-50 dark:bg-gray-800 rounded-xl">
268+
<img src={NoAccess} title="No Access" className="m-auto mb-4" />
269+
<h3 className="mb-2 text-gray-600 dark:text-gray-400">
270+
No Access
271+
</h3>
272+
<span className="dark:text-gray-500">
273+
Authorize GitHub (github.com) or select a different account.
274+
</span>
275+
<br />
276+
<button className="mt-6" onClick={() => reconfigure()}>Authorize</button>
277+
</div>
278+
</div>)}
279+
</div>
280+
281+
</div>
282+
{isGitHub() && (
283+
<div className="pt-3">
284+
<div className="text-gray-500 text-center w-96 mx-8">
285+
Repository not found? <a href="javascript:void(0)" onClick={e => reconfigure()} className="text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600">Reconfigure</a>
286+
</div>
287+
{isGitHub() && noOrgs && (
288+
<div className="text-gray-500 mx-auto text-center">
289+
Missing organizations? <a href="javascript:void(0)" onClick={e => grantReadOrgPermissions()} className="text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600">Grant permissions</a>
232290
</div>
233-
))}
291+
)}
234292
</div>
235-
)}
236-
{reposToRender.length === 0 && (
237-
<p className="text-center ">not found</p>
238-
)}
239-
</div>
240-
241-
<div className="px-3 pt-3 bg-gray-100">
242-
<div className="text-gray-500 text-center">
243-
Repository not found? <a href="javascript:void(0)" onClick={e => reconfigure()} className="text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600">Reconfigure</a>
244-
</div>
245-
{isGitHub() && noOrgs && (
246-
<div className="text-gray-500 mx-auto text-center">
247-
Missing organizations? <a href="javascript:void(0)" onClick={e => grantReadOrgPermissions()} className="text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600">Grant permissions</a>
293+
)}
294+
</>
295+
);
296+
297+
const renderEmptyState = () => (<div>
298+
<div className="mt-8 border rounded-xl border-gray-100 dark:border-gray-700 flex-col">
299+
<div>
300+
<div className="px-12 py-16 text-center text-gray-500 bg-gray-50 dark:bg-gray-800 rounded-xl w-96 h-h96 flex items-center justify-center">
301+
<h3 className="mb-2 text-gray-400 dark:text-gray-600 animate-pulse">
302+
Loading ...
303+
</h3>
248304
</div>
249-
)}
250-
</div>
251-
<div className="h-3 border rounded-b-xl border-gray-100 bg-gray-100"></div>
252-
</div>);
253-
254-
const renderEmptyState = () => (<div className="mt-8 border rounded-xl border-gray-100 dark:border-gray-700 flex-col">
255-
<div>
256-
<div className="px-12 py-16 text-center text-gray-500 bg-gray-50 dark:bg-gray-800 rounded-xl">
257-
<img src={NoAccess} title="No Access" className="m-auto mb-4" />
258-
<h3 className="mb-2 text-gray-600 dark:text-gray-400">
259-
No Access
260-
</h3>
261-
<span className="dark:text-gray-500">
262-
Authorize GitHub (github.com) or select a different account.
263-
</span>
264-
<br/>
265-
<button className="mt-6" onClick={() => reconfigure()}>Authorize Provider</button>
266305
</div>
267306
</div>
268307
</div>)
269308

270-
const empty = reposInAccounts.length === 0;
271-
272-
const onGitProviderSeleted = (host: string) => {
309+
const onGitProviderSeleted = async (host: string, updateUser?: boolean) => {
310+
if (updateUser) {
311+
setUser(await getGitpodService().server.getLoggedInUser());
312+
}
273313
setShowGitProviders(false);
274314
setProvider(host);
275315
}
276316

277-
return (<>
278-
{(loaded && empty) ? renderEmptyState() : (showGitProviders ? (<GitProviders onHostSelected={onGitProviderSeleted} />) : renderRepos())}
279-
</>)
317+
if (!loaded) {
318+
return renderEmptyState();
319+
}
320+
321+
if (showGitProviders) {
322+
return (<GitProviders onHostSelected={onGitProviderSeleted} />);
323+
}
324+
325+
return renderRepos();
280326
};
281327

282328
const renderSelectTeam = () => {
@@ -322,7 +368,7 @@ export default function NewProject() {
322368

323369
return (<div className="flex flex-col w-96 mt-24 mx-auto items-center">
324370
<h1>New Project</h1>
325-
<p className="text-gray-500 text-center text-base">Select a git repository.</p>
371+
<p className="text-gray-500 text-center text-base">Select a git repository on <strong>{provider}</strong>.</p>
326372

327373
{!selectedRepo && renderSelectRepository()}
328374

@@ -335,7 +381,7 @@ export default function NewProject() {
335381
}
336382

337383
function GitProviders(props: {
338-
onHostSelected: (host: string) => void
384+
onHostSelected: (host: string, updateUser?: boolean) => void
339385
}) {
340386
const [authProviders, setAuthProviders] = useState<AuthProviderInfo[]>([]);
341387

@@ -355,8 +401,8 @@ function GitProviders(props: {
355401
await openAuthorizeWindow({
356402
host: ap.host,
357403
scopes: ap.requirements?.default,
358-
onSuccess: () => {
359-
props.onHostSelected(ap.host);
404+
onSuccess: async () => {
405+
props.onHostSelected(ap.host, true);
360406
},
361407
onError: (error) => {
362408
console.log(error);

components/dashboard/src/projects/Prebuild.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import PrebuildLogs from "../components/PrebuildLogs";
1616
import { shortCommitMessage } from "./render-utils";
1717

1818
export default function () {
19-
const { teams } = useContext(TeamsContext);
2019
const location = useLocation();
20+
21+
const { teams } = useContext(TeamsContext);
22+
const team = getCurrentTeam(location, teams);
23+
2124
const match = useRouteMatch<{ team: string, project: string, prebuildId: string }>("/:team/:project/:prebuildId");
2225
const projectName = match?.params?.project;
2326
const prebuildId = match?.params?.prebuildId;
24-
const team = getCurrentTeam(location, teams);
2527

2628
const [ prebuild, setPrebuild ] = useState<PrebuildInfo | undefined>();
2729

@@ -44,7 +46,7 @@ export default function () {
4446
});
4547
setPrebuild(prebuilds[0]);
4648
})();
47-
}, [ teams, team ]);
49+
}, [ teams ]);
4850

4951
const renderTitle = () => {
5052
if (!prebuild) {

components/dashboard/src/projects/Prebuilds.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import { shortCommitMessage } from "./render-utils";
1818

1919
export default function () {
2020
const history = useHistory();
21-
const { teams } = useContext(TeamsContext);
2221
const location = useLocation();
22+
23+
const { teams } = useContext(TeamsContext);
24+
const team = getCurrentTeam(location, teams);
25+
2326
const match = useRouteMatch<{ team: string, resource: string }>("/:team/:resource");
2427
const projectName = match?.params?.resource;
25-
const team = getCurrentTeam(location, teams);
2628

2729
// @ts-ignore
2830
const [project, setProject] = useState<Project | undefined>();
@@ -55,7 +57,7 @@ export default function () {
5557
}
5658
}
5759
})();
58-
}, [ teams, team ]);
60+
}, [ teams ]);
5961

6062
const prebuildContextMenu = (p: PrebuildInfo) => {
6163
const running = p.status === "building";

components/dashboard/src/projects/Project.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import { shortCommitMessage } from "./render-utils";
1818

1919
export default function () {
2020
const history = useHistory();
21-
const { teams } = useContext(TeamsContext);
2221
const location = useLocation();
22+
23+
const { teams } = useContext(TeamsContext);
24+
const team = getCurrentTeam(location, teams);
25+
2326
const match = useRouteMatch<{ team: string, resource: string }>("/:team/:resource");
2427
const projectName = match?.params?.resource;
25-
const team = getCurrentTeam(location, teams);
2628

2729
const [project, setProject] = useState<Project | undefined>();
2830

@@ -34,7 +36,7 @@ export default function () {
3436

3537
useEffect(() => {
3638
updateProject();
37-
}, [ teams, team ]);
39+
}, [ teams ]);
3840

3941
const updateProject = async () => {
4042
if (!teams || !projectName) {
@@ -87,7 +89,7 @@ export default function () {
8789
// TODO(at): this need to be revised once prebuild events are integrated
8890
return;
8991
}
90-
if (!team || !project) {
92+
if (!project) {
9193
return;
9294
}
9395
prebuildLoaders.add(branch.name);

components/dashboard/src/projects/Projects.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default function () {
3232

3333
useEffect(() => {
3434
updateProjects();
35-
}, [ teams, team ]);
35+
}, [ teams ]);
3636

3737
const updateProjects = async () => {
3838
if (!teams) {

0 commit comments

Comments
 (0)