Skip to content

Commit

Permalink
Implements pull request search to GitLab integration using GitLab's API
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeibbb committed Dec 27, 2024
1 parent ffce252 commit 107da17
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 3 deletions.
23 changes: 23 additions & 0 deletions src/plus/integrations/providers/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PullRequest[] | undefined> {
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,
Expand Down
112 changes: 112 additions & 0 deletions src/plus/integrations/providers/gitlab/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -711,6 +713,116 @@ export class GitLabApi implements Disposable {
}
}

@debug<GitLabApi['searchPullRequests']>({ args: { 0: p => p.name, 1: '<token>' } })
async searchPullRequests(
provider: Provider,
token: string,
options?: { search?: string; user?: string; repos?: string[]; baseUrl?: string; avatarSize?: number },
cancellation?: CancellationToken,
): Promise<PullRequest[]> {
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<GitLabMergeRequestREST[]>(
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<Record<`id_${number}`, string>>((ids, gitlabRestPr, i) => {
ids[`id_${i}`] = `${gitlabMergeRequestIdPrefix}${gitlabRestPr.id}`;
return ids;
}, {});
const rsp = await this.graphql<QueryResult>(
provider,
token,
options?.baseUrl,
query,
params,
cancellation,
scope,
);
if (rsp?.data != null) {
const resultPRs = restPRs.reduce<PullRequest[]>((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,
Expand Down
6 changes: 3 additions & 3 deletions src/plus/integrations/providers/gitlab/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export interface GitLabMergeRequestFull extends GitLabMergeRequest {
diffRefs: {
baseSha: string | null;
headSha: string;
};
} | null;
project: GitLabRepositoryStub;
sourceProject: GitLabRepositoryStub;
}
Expand Down Expand Up @@ -235,15 +235,15 @@ 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),
branch: pr.targetBranch,
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,
};
Expand Down

0 comments on commit 107da17

Please sign in to comment.