Skip to content

Commit 4eb07ee

Browse files
committed
[server] For GitLab projects without an owner avatar, fall back to the namespace avatar, or generate the default GitLab avatar
1 parent 8b9a40a commit 4eb07ee

File tree

1 file changed

+43
-6
lines changed

1 file changed

+43
-6
lines changed

components/server/ee/src/gitlab/gitlab-app-support.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@ import { inject, injectable } from "inversify";
99
import { TokenProvider } from "../../../src/user/token-provider";
1010
import { UserDB } from "@gitpod/gitpod-db/lib";
1111
import { Gitlab } from "@gitbeaker/node";
12+
import { ProjectSchemaDefault, NamespaceInfoSchemaDefault } from "@gitbeaker/core/dist/types/services/Projects";
13+
14+
// Add missing fields to Gitbeaker's ProjectSchema type
15+
type ProjectSchema = ProjectSchemaDefault & {
16+
last_activity_at: string;
17+
namespace: NamespaceInfoSchemaDefault & {
18+
avatar_url: string;
19+
};
20+
owner?: {
21+
id: number;
22+
name: string;
23+
avatar_url: string;
24+
};
25+
};
1226

1327
@injectable()
1428
export class GitLabAppSupport {
@@ -38,12 +52,12 @@ export class GitLabAppSupport {
3852
//
3953
const projectsWithAccess = await api.Projects.all({ min_access_level: "40", perPage: 100 });
4054
for (const project of projectsWithAccess) {
41-
const anyProject = project as any;
42-
const path = anyProject.path as string;
43-
const fullPath = anyProject.path_with_namespace as string;
44-
const cloneUrl = anyProject.http_url_to_repo as string;
45-
const updatedAt = anyProject.last_activity_at as string;
46-
const accountAvatarUrl = anyProject.owner?.avatar_url as string;
55+
const aProject = project as ProjectSchema;
56+
const path = aProject.path as string;
57+
const fullPath = aProject.path_with_namespace as string;
58+
const cloneUrl = aProject.http_url_to_repo as string;
59+
const updatedAt = aProject.last_activity_at as string;
60+
const accountAvatarUrl = await this.getAccountAvatarUrl(aProject, params.provider.host);
4761
const account = fullPath.split("/")[0];
4862

4963
(account === usersGitLabAccount ? ownersRepos : result).push({
@@ -61,4 +75,27 @@ export class GitLabAppSupport {
6175
result.unshift(...ownersRepos);
6276
return result;
6377
}
78+
79+
protected async getAccountAvatarUrl(project: ProjectSchema, providerHost: string): Promise<string> {
80+
const owner = project.owner || project.namespace;
81+
if (owner.avatar_url) {
82+
const url = owner.avatar_url;
83+
// Sometimes GitLab avatar URLs are relative -- ensure we always use the correct host
84+
return url[0] === "/" ? `https://${providerHost}${url}` : url;
85+
}
86+
// If there is no avatar, generate the same default avatar that GitLab uses. Based on:
87+
// - https://gitlab.com/gitlab-org/gitlab/-/blob/b2a22b6e85200ce55ab09b5c765043441b086c96/app/helpers/avatars_helper.rb#L151-161
88+
// - https://gitlab.com/gitlab-org/gitlab-foss/-/blob/84b4743475246e91dc78c3f25f9b335c40be84cd/app/assets/stylesheets/startup/startup-general.scss#L1611-1631
89+
// - https://gitlab.com/gitlab-org/gitlab-foss/-/blob/84b4743475246e91dc78c3f25f9b335c40be84cd/app/assets/stylesheets/startup/startup-general.scss#L420-422
90+
const text = owner.name[0].toUpperCase();
91+
const backgroundColor = ["#fcf1ef", "#f4f0ff", "#f1f1ff", "#e9f3fc", "#ecf4ee", "#fdf1dd", "#f0f0f0"][
92+
owner.id % 7
93+
];
94+
const svg = `<svg viewBox="0 0 32 32" height="32" width="32" style="background-color: ${backgroundColor}" xmlns="http://www.w3.org/2000/svg">
95+
<text x="50%" y="50%" dominant-baseline="middle" 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"'>
96+
${text}
97+
</text>
98+
</svg>`;
99+
return `data:image/svg+xml,${encodeURIComponent(svg.replace(/\s+/g, " "))}`;
100+
}
64101
}

0 commit comments

Comments
 (0)