From 26727ffce4a3546a1b618d38c7c15e4d615d2a4f Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Fri, 20 Dec 2024 15:53:40 +0100 Subject: [PATCH] Makes by URL or ID searched GitLab PRs checkoutable (#3788, #3795) --- src/plus/integrations/integration.ts | 2 +- .../integrations/providers/gitlab/gitlab.ts | 66 ++++++++++++-- .../integrations/providers/gitlab/models.ts | 87 ++++++++++++++++++- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/src/plus/integrations/integration.ts b/src/plus/integrations/integration.ts index e5e4811d28587..f29d7225d054d 100644 --- a/src/plus/integrations/integration.ts +++ b/src/plus/integrations/integration.ts @@ -557,7 +557,7 @@ export abstract class IntegrationBase< const connected = this.maybeConnected ?? (await this.isConnected()); if (!connected) return undefined; - const pr = this.container.cache.getPullRequest(id, resource, this, () => ({ + const pr = await this.container.cache.getPullRequest(id, resource, this, () => ({ value: (async () => { try { const result = await this.getProviderPullRequest?.(this._session!, resource, id); diff --git a/src/plus/integrations/providers/gitlab/gitlab.ts b/src/plus/integrations/providers/gitlab/gitlab.ts index 46021a9628529..1870f56fd999c 100644 --- a/src/plus/integrations/providers/gitlab/gitlab.ts +++ b/src/plus/integrations/providers/gitlab/gitlab.ts @@ -35,12 +35,13 @@ import type { GitLabCommit, GitLabIssue, GitLabMergeRequest, + GitLabMergeRequestFull, GitLabMergeRequestREST, GitLabMergeRequestState, GitLabProjectREST, GitLabUser, } from './models'; -import { fromGitLabMergeRequestREST, fromGitLabMergeRequestState } from './models'; +import { fromGitLabMergeRequest, fromGitLabMergeRequestREST, fromGitLabMergeRequestState } from './models'; // drop it as soon as we switch to @gitkraken/providers-api const gitlabUserIdPrefix = 'gid://gitlab/User/'; @@ -585,24 +586,73 @@ export class GitLabApi implements Disposable { ): Promise { const scope = getLogScope(); - const projectId = await this.getProjectId(provider, token, owner, repo, options?.baseUrl, cancellation); - if (!projectId) return undefined; + interface QueryResult { + data: { + project: { + mergeRequest: GitLabMergeRequestFull | null; + } | null; + }; + } try { - const mr = await this.request( + const query = `query getMergeRequest( + $fullPath: ID! + $iid: String! +) { + project(fullPath: $fullPath) { + mergeRequest(iid: $iid) { + id, + iid + state, + author { + id + name + avatarUrl + webUrl + } + diffRefs { + baseSha + headSha + } + title + description + webUrl + createdAt + updatedAt + mergedAt + targetBranch + sourceBranch + project { + id + fullPath + webUrl + } + sourceProject { + id + fullPath + webUrl + } + } + } +}`; + + const rsp = await this.graphql( provider, token, options?.baseUrl, - `v4/projects/${projectId}/merge_requests/${id}`, + query, { - method: 'GET', + fullPath: `${owner}/${repo}`, + iid: String(id), }, cancellation, scope, ); - if (mr == null) return undefined; - return fromGitLabMergeRequestREST(mr, provider, { owner: owner, repo: repo }); + if (rsp?.data?.project?.mergeRequest == null) return undefined; + + const pr = rsp.data.project.mergeRequest; + return fromGitLabMergeRequest(pr, provider); } catch (ex) { if (ex instanceof RequestNotFoundError) return undefined; diff --git a/src/plus/integrations/providers/gitlab/models.ts b/src/plus/integrations/providers/gitlab/models.ts index a86d70356099e..7b1c2eff6137d 100644 --- a/src/plus/integrations/providers/gitlab/models.ts +++ b/src/plus/integrations/providers/gitlab/models.ts @@ -1,4 +1,4 @@ -import type { PullRequestState } from '../../../../git/models/pullRequest'; +import type { PullRequestRefs, PullRequestState } from '../../../../git/models/pullRequest'; import { PullRequest, PullRequestMergeableState } from '../../../../git/models/pullRequest'; import type { Provider } from '../../../../git/models/remoteProvider'; import type { Integration } from '../../integration'; @@ -65,6 +65,24 @@ export interface GitLabMergeRequest { webUrl: string; } +export interface GitLabRepositoryStub { + id: string; + fullPath: string; + webUrl: string; +} + +export interface GitLabMergeRequestFull extends GitLabMergeRequest { + id: string; + targetBranch: string; + sourceBranch: string; + diffRefs: { + baseSha: string | null; + headSha: string; + }; + project: GitLabRepositoryStub; + sourceProject: GitLabRepositoryStub; +} + export type GitLabMergeRequestState = 'opened' | 'closed' | 'locked' | 'merged'; export function fromGitLabMergeRequestState(state: GitLabMergeRequestState): PullRequestState { @@ -92,6 +110,15 @@ export interface GitLabMergeRequestREST { closed_at: string | null; merged_at: string | null; detailed_merge_status: 'conflict' | 'mergeable' | string; // https://docs.gitlab.com/ee/api/merge_requests.html#merge-status + diff_refs: { + base_sha: string; + head_sha: string; + start_sha: string; + }; + source_branch: string; + source_project_id: number; + target_branch: string; + target_project_id: number; web_url: string; references: { full: string; @@ -160,3 +187,61 @@ export function fromGitLabMergeRequestProvidersApi(pr: ProviderPullRequest, prov }; return fromProviderPullRequest(wrappedPr, provider); } + +export function fromGitLabMergeRequest(pr: GitLabMergeRequestFull, provider: Provider): PullRequest { + return new PullRequest( + provider, + { + // author + id: pr.author?.id ?? '', + name: pr.author?.name ?? 'Unknown', + avatarUrl: pr.author?.avatarUrl ?? '', + url: pr.author?.webUrl ?? '', + }, + pr.iid, // id + pr.id, // nodeId + pr.title, + pr.webUrl || '', + { + // IssueRepository + owner: '', + repo: '', + url: '', + }, + fromGitLabMergeRequestState(pr.state), // PullRequestState + new Date(pr.createdAt), + new Date(pr.updatedAt), + // TODO@eamodio this isn't right, but GitLab doesn't seem to provide a closedAt on merge requests in GraphQL + pr.state !== 'closed' ? undefined : new Date(pr.updatedAt), + pr.mergedAt == null ? undefined : new Date(pr.mergedAt), + PullRequestMergeableState.Unknown, + undefined, // viewerCanUpdate + fromGitLabMergeRequestRefs(pr), // PullRequestRefs + ); +} + +function fromGitLabMergeRequestRefs(pr: GitLabMergeRequestFull): PullRequestRefs | undefined { + return { + base: { + owner: getRepoNamespace(pr.sourceProject.fullPath), + branch: pr.sourceBranch, + exists: true, + url: pr.sourceProject.webUrl, + repo: pr.sourceProject.fullPath, + 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, + }, + isCrossRepository: pr.sourceProject.id !== pr.project.id, + }; +} + +function getRepoNamespace(projectFullPath: string) { + return projectFullPath.split('/').slice(0, -1).join('/'); +}