From 107da1711999d29a34c6de6a986513b3467e0df5 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Fri, 22 Nov 2024 19:00:40 +0100 Subject: [PATCH] Implements pull request search to GitLab integration using GitLab's API (#3788, #3795) --- src/plus/integrations/providers/gitlab.ts | 23 ++++ .../integrations/providers/gitlab/gitlab.ts | 112 ++++++++++++++++++ .../integrations/providers/gitlab/models.ts | 6 +- 3 files changed, 138 insertions(+), 3 deletions(-) diff --git a/src/plus/integrations/providers/gitlab.ts b/src/plus/integrations/providers/gitlab.ts index 9353999254412..3eb7a8404e026 100644 --- a/src/plus/integrations/providers/gitlab.ts +++ b/src/plus/integrations/providers/gitlab.ts @@ -312,6 +312,29 @@ abstract class GitLabIntegrationBase< .filter((result): result is SearchedIssue => result != null); } + protected override async searchProviderPullRequests( + { accessToken }: AuthenticationSession, + searchQuery: string, + repos?: GitLabRepositoryDescriptor[], + cancellation?: CancellationToken, + ): Promise { + const api = await this.container.gitlab; + if (!api) { + return undefined; + } + + return api.searchPullRequests( + this, + accessToken, + { + search: searchQuery, + repos: repos?.map(r => `${r.owner}/${r.name}`), + baseUrl: this.apiBaseUrl, + }, + cancellation, + ); + } + protected override async mergeProviderPullRequest( _session: AuthenticationSession, pr: PullRequest, diff --git a/src/plus/integrations/providers/gitlab/gitlab.ts b/src/plus/integrations/providers/gitlab/gitlab.ts index 60e23390669f7..f03b96150508a 100644 --- a/src/plus/integrations/providers/gitlab/gitlab.ts +++ b/src/plus/integrations/providers/gitlab/gitlab.ts @@ -45,6 +45,8 @@ import { fromGitLabMergeRequest, fromGitLabMergeRequestREST, fromGitLabMergeRequ // drop it as soon as we switch to @gitkraken/providers-api const gitlabUserIdPrefix = 'gid://gitlab/User/'; +const gitlabMergeRequestIdPrefix = 'gid://gitlab/MergeRequest/'; + function buildGitLabUserId(id: string | undefined): string | undefined { return id?.startsWith(gitlabUserIdPrefix) ? id.substring(gitlabUserIdPrefix.length) : id; } @@ -711,6 +713,116 @@ export class GitLabApi implements Disposable { } } + @debug({ args: { 0: p => p.name, 1: '' } }) + async searchPullRequests( + provider: Provider, + token: string, + options?: { search?: string; user?: string; repos?: string[]; baseUrl?: string; avatarSize?: number }, + cancellation?: CancellationToken, + ): Promise { + const scope = getLogScope(); + const search = options?.search; + if (!search) { + return []; + } + try { + const perPageLimit = 20; // with bigger amount we exceed the max GraphQL complexity in the next query + const restPRs = await this.request( + provider, + token, + options?.baseUrl, + `v4/search/?scope=merge_requests&search=${search}&per_page=${perPageLimit}`, + { + method: 'GET', + }, + cancellation, + scope, + ); + if (restPRs.length === 0) { + return []; + } + + interface QueryResult { + data: Record<`mergeRequest_${number}`, GitLabMergeRequestFull>; + } + + const queryArgs = restPRs.map((_, i) => `$id_${i}: MergeRequestID!`).join('\n'); + const queryFields = restPRs + .map((_, i) => `mergeRequest_${i}: mergeRequest(id: $id_${i}) { ...mergeRequestFields }`) + .join('\n'); + // Set of fields includes only additional fields that are not included in GitLabMergeRequestREST. + // It's limited as much as possible to reduce complexity of the query. + const queryMrFields = `fragment mergeRequestFields on MergeRequest { + diffRefs { + baseSha + headSha + } + project { + id + fullPath + webUrl + } + sourceProject { + id + fullPath + webUrl + } + }`; + const query = `query getMergeRequests (${queryArgs}) {${queryFields}} ${queryMrFields}`; + + const params = restPRs.reduce>((ids, gitlabRestPr, i) => { + ids[`id_${i}`] = `${gitlabMergeRequestIdPrefix}${gitlabRestPr.id}`; + return ids; + }, {}); + const rsp = await this.graphql( + provider, + token, + options?.baseUrl, + query, + params, + cancellation, + scope, + ); + if (rsp?.data != null) { + const resultPRs = restPRs.reduce((accum, restPR, i) => { + const graphQlPR = rsp.data[`mergeRequest_${i}`]; + if (graphQlPR == null) { + return accum; + } + + const fullPr: GitLabMergeRequestFull = { + ...graphQlPR, + iid: String(restPR.iid), + id: String(restPR.id), + state: restPR.state, + author: { + id: buildGitLabUserId(restPR.author?.id) ?? '', + name: restPR.author?.name ?? 'Unknown', + avatarUrl: restPR.author?.avatar_url ?? '', + webUrl: restPR.author?.web_url ?? '', + }, + title: restPR.title, + description: restPR.description, + webUrl: restPR.web_url, + createdAt: restPR.created_at, + updatedAt: restPR.updated_at, + mergedAt: restPR.merged_at, + sourceBranch: restPR.source_branch, + targetBranch: restPR.target_branch, + }; + accum.push(fromGitLabMergeRequest(fullPr, provider)); + return accum; + }, []); + return resultPRs; + } + return []; + } catch (ex) { + if (ex instanceof RequestNotFoundError) return []; + + throw this.handleException(ex, provider, scope); + } + } + private async findUser( provider: Provider, token: string, diff --git a/src/plus/integrations/providers/gitlab/models.ts b/src/plus/integrations/providers/gitlab/models.ts index f69c3891f162e..c60a15c66fc1e 100644 --- a/src/plus/integrations/providers/gitlab/models.ts +++ b/src/plus/integrations/providers/gitlab/models.ts @@ -80,7 +80,7 @@ export interface GitLabMergeRequestFull extends GitLabMergeRequest { diffRefs: { baseSha: string | null; headSha: string; - }; + } | null; project: GitLabRepositoryStub; sourceProject: GitLabRepositoryStub; } @@ -235,7 +235,7 @@ function fromGitLabMergeRequestRefs(pr: GitLabMergeRequestFull): PullRequestRefs exists: true, url: pr.sourceProject.webUrl, repo: pr.sourceProject.fullPath, - sha: pr.diffRefs.baseSha || '', + sha: pr.diffRefs?.baseSha || '', }, head: { owner: getRepoNamespace(pr.project.fullPath), @@ -243,7 +243,7 @@ function fromGitLabMergeRequestRefs(pr: GitLabMergeRequestFull): PullRequestRefs exists: true, url: pr.project.webUrl, repo: pr.project.fullPath, - sha: pr.diffRefs.headSha, + sha: pr.diffRefs?.headSha || '', }, isCrossRepository: pr.sourceProject.id !== pr.project.id, };