Skip to content

Commit 3f62677

Browse files
committed
Implements pull request search to GitLab integration using GitLab's API
(#3788, #3795)
1 parent ee08854 commit 3f62677

File tree

3 files changed

+138
-3
lines changed

3 files changed

+138
-3
lines changed

src/plus/integrations/providers/gitlab.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,29 @@ abstract class GitLabIntegrationBase<
312312
.filter((result): result is SearchedIssue => result != null);
313313
}
314314

315+
protected override async searchProviderPullRequests(
316+
{ accessToken }: AuthenticationSession,
317+
searchQuery: string,
318+
repos?: GitLabRepositoryDescriptor[],
319+
cancellation?: CancellationToken,
320+
): Promise<PullRequest[] | undefined> {
321+
const api = await this.container.gitlab;
322+
if (!api) {
323+
return undefined;
324+
}
325+
326+
return api.searchPullRequests(
327+
this,
328+
accessToken,
329+
{
330+
search: searchQuery,
331+
repos: repos?.map(r => `${r.owner}/${r.name}`),
332+
baseUrl: this.apiBaseUrl,
333+
},
334+
cancellation,
335+
);
336+
}
337+
315338
protected override async mergeProviderPullRequest(
316339
_session: AuthenticationSession,
317340
pr: PullRequest,

src/plus/integrations/providers/gitlab/gitlab.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import { fromGitLabMergeRequest, fromGitLabMergeRequestREST, fromGitLabMergeRequ
4545

4646
// drop it as soon as we switch to @gitkraken/providers-api
4747
const gitlabUserIdPrefix = 'gid://gitlab/User/';
48+
const gitlabMergeRequestIdPrefix = 'gid://gitlab/MergeRequest/';
49+
4850
function buildGitLabUserId(id: string | undefined): string | undefined {
4951
return id?.startsWith(gitlabUserIdPrefix) ? id.substring(gitlabUserIdPrefix.length) : id;
5052
}
@@ -711,6 +713,116 @@ export class GitLabApi implements Disposable {
711713
}
712714
}
713715

716+
@debug<GitLabApi['searchPullRequests']>({ args: { 0: p => p.name, 1: '<token>' } })
717+
async searchPullRequests(
718+
provider: Provider,
719+
token: string,
720+
options?: { search?: string; user?: string; repos?: string[]; baseUrl?: string; avatarSize?: number },
721+
cancellation?: CancellationToken,
722+
): Promise<PullRequest[]> {
723+
const scope = getLogScope();
724+
const search = options?.search;
725+
if (!search) {
726+
return [];
727+
}
728+
try {
729+
const perPageLimit = 20; // with bigger amount we exceed the max GraphQL complexity in the next query
730+
const restPRs = await this.request<GitLabMergeRequestREST[]>(
731+
provider,
732+
token,
733+
options?.baseUrl,
734+
`v4/search/?scope=merge_requests&search=${search}&per_page=${perPageLimit}`,
735+
{
736+
method: 'GET',
737+
},
738+
cancellation,
739+
scope,
740+
);
741+
if (restPRs.length === 0) {
742+
return [];
743+
}
744+
745+
interface QueryResult {
746+
data: Record<`mergeRequest_${number}`, GitLabMergeRequestFull>;
747+
}
748+
749+
const queryArgs = restPRs.map((_, i) => `$id_${i}: MergeRequestID!`).join('\n');
750+
const queryFields = restPRs
751+
.map((_, i) => `mergeRequest_${i}: mergeRequest(id: $id_${i}) { ...mergeRequestFields }`)
752+
.join('\n');
753+
// Set of fields includes only additional fields that are not included in GitLabMergeRequestREST.
754+
// It's limited as much as possible to reduce complexity of the query.
755+
const queryMrFields = `fragment mergeRequestFields on MergeRequest {
756+
diffRefs {
757+
baseSha
758+
headSha
759+
}
760+
project {
761+
id
762+
fullPath
763+
webUrl
764+
}
765+
sourceProject {
766+
id
767+
fullPath
768+
webUrl
769+
}
770+
}`;
771+
const query = `query getMergeRequests (${queryArgs}) {${queryFields}} ${queryMrFields}`;
772+
773+
const params = restPRs.reduce<Record<`id_${number}`, string>>((ids, gitlabRestPr, i) => {
774+
ids[`id_${i}`] = `${gitlabMergeRequestIdPrefix}${gitlabRestPr.id}`;
775+
return ids;
776+
}, {});
777+
const rsp = await this.graphql<QueryResult>(
778+
provider,
779+
token,
780+
options?.baseUrl,
781+
query,
782+
params,
783+
cancellation,
784+
scope,
785+
);
786+
if (rsp?.data != null) {
787+
const resultPRs = restPRs.reduce<PullRequest[]>((accum, restPR, i) => {
788+
const graphQlPR = rsp.data[`mergeRequest_${i}`];
789+
if (graphQlPR == null) {
790+
return accum;
791+
}
792+
793+
const fullPr: GitLabMergeRequestFull = {
794+
...graphQlPR,
795+
iid: String(restPR.iid),
796+
id: String(restPR.id),
797+
state: restPR.state,
798+
author: {
799+
id: buildGitLabUserId(restPR.author?.id) ?? '',
800+
name: restPR.author?.name ?? 'Unknown',
801+
avatarUrl: restPR.author?.avatar_url ?? '',
802+
webUrl: restPR.author?.web_url ?? '',
803+
},
804+
title: restPR.title,
805+
description: restPR.description,
806+
webUrl: restPR.web_url,
807+
createdAt: restPR.created_at,
808+
updatedAt: restPR.updated_at,
809+
mergedAt: restPR.merged_at,
810+
sourceBranch: restPR.source_branch,
811+
targetBranch: restPR.target_branch,
812+
};
813+
accum.push(fromGitLabMergeRequest(fullPr, provider));
814+
return accum;
815+
}, []);
816+
return resultPRs;
817+
}
818+
return [];
819+
} catch (ex) {
820+
if (ex instanceof RequestNotFoundError) return [];
821+
822+
throw this.handleException(ex, provider, scope);
823+
}
824+
}
825+
714826
private async findUser(
715827
provider: Provider,
716828
token: string,

src/plus/integrations/providers/gitlab/models.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export interface GitLabMergeRequestFull extends GitLabMergeRequest {
8080
diffRefs: {
8181
baseSha: string | null;
8282
headSha: string;
83-
};
83+
} | null;
8484
project: GitLabRepositoryStub;
8585
sourceProject: GitLabRepositoryStub;
8686
}
@@ -237,15 +237,15 @@ function fromGitLabMergeRequestRefs(pr: GitLabMergeRequestFull): PullRequestRefs
237237
exists: true,
238238
url: pr.sourceProject.webUrl,
239239
repo: pr.sourceProject.fullPath,
240-
sha: pr.diffRefs.baseSha || '',
240+
sha: pr.diffRefs?.baseSha || '',
241241
},
242242
head: {
243243
owner: getRepoNamespace(pr.project.fullPath),
244244
branch: pr.targetBranch,
245245
exists: true,
246246
url: pr.project.webUrl,
247247
repo: pr.project.fullPath,
248-
sha: pr.diffRefs.headSha,
248+
sha: pr.diffRefs?.headSha || '',
249249
},
250250
isCrossRepository: pr.sourceProject.id !== pr.project.id,
251251
};

0 commit comments

Comments
 (0)