diff --git a/components/server/ee/src/gitlab/gitlab-app-support.ts b/components/server/ee/src/gitlab/gitlab-app-support.ts index d07dbb1faaa25d..a488aff3271b6e 100644 --- a/components/server/ee/src/gitlab/gitlab-app-support.ts +++ b/components/server/ee/src/gitlab/gitlab-app-support.ts @@ -9,6 +9,20 @@ 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; + }; + owner?: { + id: number; + name: string; + avatar_url: string; + }; +}; @injectable() export class GitLabAppSupport { @@ -38,12 +52,12 @@ export class GitLabAppSupport { // const projectsWithAccess = await api.Projects.all({ min_access_level: "40", perPage: 100 }); for (const project of projectsWithAccess) { - const anyProject = project as any; - const path = anyProject.path as string; - const fullPath = anyProject.path_with_namespace as string; - const cloneUrl = anyProject.http_url_to_repo as string; - const updatedAt = anyProject.last_activity_at as string; - const accountAvatarUrl = anyProject.owner?.avatar_url as string; + 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({ @@ -61,4 +75,28 @@ export class GitLabAppSupport { result.unshift(...ownersRepos); return result; } + + protected async getAccountAvatarUrl(project: ProjectSchema, providerHost: string): Promise { + const owner = project.owner || project.namespace; + 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-foss/-/blob/84b4743475246e91dc78c3f25f9b335c40be84cd/app/assets/stylesheets/startup/startup-general.scss#L1611-1631 + // - https://gitlab.com/gitlab-org/gitlab-foss/-/blob/84b4743475246e91dc78c3f25f9b335c40be84cd/app/assets/stylesheets/startup/startup-general.scss#L420-422 + const text = owner.name[0].toUpperCase(); + const backgroundColor = ["#fcf1ef", "#f4f0ff", "#f1f1ff", "#e9f3fc", "#ecf4ee", "#fdf1dd", "#f0f0f0"][ + owner.id % 7 + ]; + const svg = + ` + ${text} + `.replace(/\s+/g, " "); + return `data:image/svg+xml,${encodeURIComponent(svg)}`; + } }