diff --git a/src/commands.ts b/src/commands.ts index ddb34e8736..067066ac4c 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1604,26 +1604,81 @@ ${contents} if (!githubRepo) { return; } - const prNumber = await vscode.window.showInputBox({ - ignoreFocusOut: true, prompt: vscode.l10n.t('Enter the pull request number or URL'), - validateInput: (input: string) => { - const result = validateAndParseInput(input, githubRepo.repo.remote.owner, githubRepo.repo.remote.repositoryName); - return result.isValid ? undefined : result.errorMessage; + + // Create QuickPick to show all PRs + const quickPick = vscode.window.createQuickPick(); + quickPick.placeholder = vscode.l10n.t('Enter a pull request number/URL or select from the list'); + quickPick.matchOnDescription = true; + quickPick.matchOnDetail = true; + quickPick.show(); + quickPick.busy = true; + + let acceptDisposable: vscode.Disposable | undefined; + let hideDisposable: vscode.Disposable | undefined; + + // Fetch all open PRs (lightweight query) + try { + const prs = await githubRepo.repo.getPullRequestNumbers(); + if (!prs) { + return vscode.window.showErrorMessage(vscode.l10n.t('Failed to fetch pull requests')); } - }); - if ((prNumber === undefined) || prNumber === '#') { - return; - } + // Sort PRs by number in descending order (most recent first) + const sortedPRs = prs.sort((a, b) => b.number - a.number); + const prItems: (vscode.QuickPickItem & { prNumber: number })[] = sortedPRs.map(pr => ({ + label: `#${pr.number} ${pr.title}`, + description: `by @${pr.author.login}`, + prNumber: pr.number + })); + + quickPick.items = prItems; + quickPick.busy = false; + + // Handle selection + const selected = await new Promise<(vscode.QuickPickItem & { prNumber?: number }) | string | undefined>((resolve) => { + acceptDisposable = quickPick.onDidAccept(() => { + if (quickPick.selectedItems.length > 0) { + resolve(quickPick.selectedItems[0]); + } else if (quickPick.value) { + // User typed something but didn't select from list + resolve(quickPick.value); + } else { + // User pressed Enter with no selection and no input + resolve(undefined); + } + }); + hideDisposable = quickPick.onDidHide(() => resolve(undefined)); + }); - // Extract PR number from input (either direct number or URL) - const parseResult = validateAndParseInput(prNumber, githubRepo.repo.remote.owner, githubRepo.repo.remote.repositoryName); - if (!parseResult.isValid) { - return vscode.window.showErrorMessage(parseResult.errorMessage || vscode.l10n.t('Invalid pull request number or URL')); - } + if (!selected) { + return; + } + quickPick.busy = true; + let prModel: PullRequestModel | undefined; + + // Check if user selected from the list or typed a custom value + if (typeof selected === 'string') { + // User typed a PR number or URL + const parseResult = validateAndParseInput(selected, githubRepo.repo.remote.owner, githubRepo.repo.remote.repositoryName); + if (!parseResult.isValid) { + return vscode.window.showErrorMessage(parseResult.errorMessage || vscode.l10n.t('Invalid pull request number or URL')); + } + prModel = await githubRepo.manager.fetchById(githubRepo.repo, parseResult.prNumber); + } else if (selected.prNumber) { + // User selected from the list + prModel = await githubRepo.manager.fetchById(githubRepo.repo, selected.prNumber); + } - const prModel = await githubRepo.manager.fetchById(githubRepo.repo, parseResult.prNumber); - if (prModel) { - return ReviewManager.getReviewManagerForFolderManager(reviewsManager.reviewManagers, githubRepo.manager)?.switch(prModel); + if (prModel) { + return ReviewManager.getReviewManagerForFolderManager(reviewsManager.reviewManagers, githubRepo.manager)?.switch(prModel); + } + } catch (e) { + vscode.window.showErrorMessage(vscode.l10n.t('Failed to fetch pull requests: {0}', formatError(e))); + } finally { + // Clean up event listeners and QuickPick + acceptDisposable?.dispose(); + hideDisposable?.dispose(); + quickPick.hide(); + quickPick.dispose(); } })); diff --git a/src/github/githubRepository.ts b/src/github/githubRepository.ts index a08bd61e93..5cee1a10c7 100644 --- a/src/github/githubRepository.ts +++ b/src/github/githubRepository.ts @@ -27,6 +27,8 @@ import { OrganizationTeamsCountResponse, OrganizationTeamsResponse, OrgProjectsResponse, + PullRequestNumberData, + PullRequestNumbersResponse, PullRequestParticipantsResponse, PullRequestResponse, PullRequestsResponse, @@ -668,6 +670,40 @@ export class GitHubRepository extends Disposable { return undefined; } + async getPullRequestNumbers(): Promise { + let remote: GitHubRemote | undefined; + try { + Logger.debug(`Fetch pull request numbers - enter`, this.id); + const ensured = await this.ensure(); + remote = ensured.remote; + const { query, schema } = ensured; + const { data } = await query({ + query: schema.PullRequestNumbers, + variables: { + owner: remote.owner, + name: remote.repositoryName, + first: 100, + }, + }); + Logger.debug(`Fetch pull request numbers - done`, this.id); + + if (data?.repository?.pullRequests) { + return data.repository.pullRequests.nodes; + } + } catch (e) { + Logger.error(`Fetching pull request numbers failed: ${e}`, this.id); + if (e.status === 404) { + // not found + vscode.window.showWarningMessage( + `Fetching pull request numbers for remote '${remote?.remoteName}' failed, please check if the repository ${remote?.owner}/${remote?.repositoryName} is valid.`, + ); + } else { + throw e; + } + } + return undefined; + } + async getPullRequestForBranch(branch: string, headOwner: string): Promise { let remote: GitHubRemote | undefined; try { diff --git a/src/github/graphql.ts b/src/github/graphql.ts index 68f41b1ded..23c3cdcc9a 100644 --- a/src/github/graphql.ts +++ b/src/github/graphql.ts @@ -844,6 +844,23 @@ export interface PullRequestsResponse { } | null; } +export interface PullRequestNumbersResponse { + repository: { + pullRequests: { + nodes: PullRequestNumberData[] + } + } | null; + rateLimit: RateLimit; +} + +export interface PullRequestNumberData { + number: number; + title: string; + author: { + login: string; + }; +} + export interface MaxIssueResponse { repository: { issues: { diff --git a/src/github/queriesShared.gql b/src/github/queriesShared.gql index 473841f0a1..c525b763f2 100644 --- a/src/github/queriesShared.gql +++ b/src/github/queriesShared.gql @@ -686,6 +686,27 @@ query PullRequestTemplates($owner: String!, $name: String!) { } } +fragment PullRequestNumberFragment on PullRequest { + number + title + author { + login + } +} + +query PullRequestNumbers($owner: String!, $name: String!, $first: Int!) { + repository(owner: $owner, name: $name) { + pullRequests(first: $first, states: OPEN, orderBy: { field: CREATED_AT, direction: DESC }) { + nodes { + ...PullRequestNumberFragment + } + } + } + rateLimit { + ...RateLimit + } +} + mutation AddComment($input: AddPullRequestReviewCommentInput!) { addPullRequestReviewComment(input: $input) { comment {