-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathgitlab-app-support.ts
110 lines (100 loc) · 5.33 KB
/
gitlab-app-support.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* Copyright (c) 2020 Gitpod GmbH. All rights reserved.
* Licensed under the Gitpod Enterprise Source Code License,
* See License.enterprise.txt in the project root folder.
*/
import { AuthProviderInfo, ProviderRepository, User } from "@gitpod/gitpod-protocol";
import { inject, injectable } from "inversify";
import { TokenProvider } from "../../../src/user/token-provider";
import { UserDB } from "@gitpod/gitpod-db/lib";
import { Gitlab } from "@gitbeaker/node";
import { ProjectSchemaDefault, NamespaceInfoSchemaDefault } from "@gitbeaker/core/dist/types/services/Projects";
// Add missing fields to Gitbeaker's ProjectSchema type
type ProjectSchema = ProjectSchemaDefault & {
last_activity_at: string;
namespace: NamespaceInfoSchemaDefault & {
avatar_url: string | null;
parent_id: number | null;
};
owner?: {
id: number;
name: string;
avatar_url: string | null;
};
};
@injectable()
export class GitLabAppSupport {
@inject(UserDB) protected readonly userDB: UserDB;
@inject(TokenProvider) protected readonly tokenProvider: TokenProvider;
async getProviderRepositoriesForUser(params: {
user: User;
provider: AuthProviderInfo;
}): Promise<ProviderRepository[]> {
const token = await this.tokenProvider.getTokenForHost(params.user, params.provider.host);
const oauthToken = token.value;
const api = new Gitlab({ oauthToken, host: `https://${params.provider.host}` });
const result: ProviderRepository[] = [];
const ownersRepos: ProviderRepository[] = [];
const identity = params.user.identities.find((i) => i.authProviderId === params.provider.authProviderId);
if (!identity) {
return result;
}
const usersGitLabAccount = identity.authName;
// cf. https://docs.gitlab.com/ee/api/projects.html#list-all-projects
// we are listing only those projects with access level of maintainers.
// also cf. https://docs.gitlab.com/ee/api/members.html#valid-access-levels
//
const projectsWithAccess = await api.Projects.all({ min_access_level: "40", perPage: 100 });
for (const project of projectsWithAccess) {
const aProject = project as ProjectSchema;
const path = aProject.path as string;
const fullPath = aProject.path_with_namespace as string;
const cloneUrl = aProject.http_url_to_repo as string;
const updatedAt = aProject.last_activity_at as string;
const accountAvatarUrl = await this.getAccountAvatarUrl(aProject, params.provider.host);
const account = fullPath.split("/")[0];
(account === usersGitLabAccount ? ownersRepos : result).push({
name: project.name,
path,
account,
cloneUrl,
updatedAt,
accountAvatarUrl,
// inUse: // todo(at) compute usage via ProjectHooks API
});
}
// put owner's repos first. the frontend will pick first account to continue with
result.unshift(...ownersRepos);
return result;
}
protected async getAccountAvatarUrl(project: ProjectSchema, providerHost: string): Promise<string> {
let owner = project.owner;
if (!owner && project.namespace && !project.namespace.parent_id) {
// Fall back to "root namespace" / "top-level group"
owner = project.namespace;
}
if (!owner) {
// Could not determine account avatar
return "";
}
if (owner.avatar_url) {
const url = owner.avatar_url;
// Sometimes GitLab avatar URLs are relative -- ensure we always use the correct host
return url[0] === "/" ? `https://${providerHost}${url}` : url;
}
// If there is no avatar, generate the same default avatar that GitLab uses. Based on:
// - https://gitlab.com/gitlab-org/gitlab/-/blob/b2a22b6e85200ce55ab09b5c765043441b086c96/app/helpers/avatars_helper.rb#L151-161
// - https://gitlab.com/gitlab-org/gitlab/-/blob/861f52858a1db07bdb122fe947dec9b0a09ce807/app/assets/stylesheets/startup/startup-general.scss#L1611-1631
// - https://gitlab.com/gitlab-org/gitlab/-/blob/861f52858a1db07bdb122fe947dec9b0a09ce807/app/assets/stylesheets/startup/startup-general.scss#L420-422
const backgroundColors = ["#fcf1ef", "#f4f0ff", "#f1f1ff", "#e9f3fc", "#ecf4ee", "#fdf1dd", "#f0f0f0"];
const backgroundColor = backgroundColors[owner.id % backgroundColors.length];
// Uppercase first character of the name, support emojis, default to whitespace.
const text = String.fromCodePoint(owner.name.codePointAt(0) || 32 /* space */).toUpperCase();
const svg = `<svg viewBox="0 0 32 32" height="32" width="32" style="background-color: ${backgroundColor}" xmlns="http://www.w3.org/2000/svg">
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" style='font-size: 0.875rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'>
${text}
</text>
</svg>`;
return `data:image/svg+xml,${encodeURIComponent(svg.replace(/\s+/g, " "))}`;
}
}