diff --git a/package.json b/package.json index dcb1512b79..683a0c574a 100644 --- a/package.json +++ b/package.json @@ -3241,6 +3241,11 @@ } ], "editor/title": [ + { + "command": "pr.openPullRequestOnGitHub", + "group": "navigation", + "when": "activeWebviewPanelId == 'PullRequestOverview'" + }, { "command": "review.openFile", "group": "navigation", diff --git a/src/commands.ts b/src/commands.ts index 923d61832e..77fe0bdc29 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -103,14 +103,21 @@ export async function openDescription( } export async function openPullRequestOnGitHub(e: PRNode | RepositoryChangesNode | IssueModel | NotificationTreeItem, telemetry: ITelemetry) { + let url: string; if (e instanceof PRNode || e instanceof RepositoryChangesNode) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(e.pullRequestModel.html_url)); + url = e.pullRequestModel.html_url; } else if (isNotificationTreeItem(e)) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(e.model.html_url)); + url = e.model.html_url; } else { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(e.html_url)); + url = e.html_url; } + openPullRequestUrlOnGitHub(vscode.Uri.parse(url), telemetry); +} + +function openPullRequestUrlOnGitHub(url: vscode.Uri, telemetry: ITelemetry): void { + vscode.commands.executeCommand('vscode.open', url); + /** __GDPR__ "pr.openInGitHub" : {} */ @@ -141,6 +148,36 @@ export function findExactPullRequestNumberMatch(value: string, items: readonly P return items.find(item => item.prNumber === prNumber); } +export async function openPullRequestOnGitHubCommand( + e: PRNode | RepositoryChangesNode | PullRequestModel | vscode.Uri | undefined, + reposManager: Pick, + telemetry: ITelemetry, +): Promise { + if (!e || e instanceof vscode.Uri) { + const currentPullRequestUrl = PullRequestOverviewPanel.getCurrentPullRequestUrl(); + if (currentPullRequestUrl) { + openPullRequestUrlOnGitHub(currentPullRequestUrl, telemetry); + return; + } + + const activePullRequests: PullRequestModel[] = reposManager.folderManagers + .map(folderManager => folderManager.activePullRequest!) + .filter(activePR => !!activePR); + + if (activePullRequests.length >= 1) { + const result = await chooseItem( + activePullRequests, + itemValue => ({ label: itemValue.html_url }), + ); + if (result) { + openPullRequestOnGitHub(result, telemetry); + } + } + } else { + openPullRequestOnGitHub(e, telemetry); + } +} + function isCrossChatSessionWithPR(value: any): value is CrossChatSessionWithPR { const asCrossChatSessionWithPR = value as Partial; return !!asCrossChatSessionWithPR.pullRequestDetails; @@ -163,25 +200,7 @@ export function registerCommands( context.subscriptions.push( vscode.commands.registerCommand( 'pr.openPullRequestOnGitHub', - async (e: PRNode | RepositoryChangesNode | PullRequestModel | undefined) => { - if (!e) { - const activePullRequests: PullRequestModel[] = reposManager.folderManagers - .map(folderManager => folderManager.activePullRequest!) - .filter(activePR => !!activePR); - - if (activePullRequests.length >= 1) { - const result = await chooseItem( - activePullRequests, - itemValue => ({ label: itemValue.html_url }), - ); - if (result) { - openPullRequestOnGitHub(result, telemetry); - } - } - } else { - openPullRequestOnGitHub(e, telemetry); - } - }, + (e: PRNode | RepositoryChangesNode | PullRequestModel | vscode.Uri | undefined) => openPullRequestOnGitHubCommand(e, reposManager, telemetry), ), ); context.subscriptions.push( diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index c39e55f05d..206f8e95b3 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -130,6 +130,31 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel + repository.remote.owner.toLocaleLowerCase() === identity.owner.toLocaleLowerCase() + && repository.remote.repositoryName.toLocaleLowerCase() === identity.repo.toLocaleLowerCase() + ) ?? repositories[0])?.remote; + if (remote) { + return vscode.Uri.joinPath(vscode.Uri.parse(remote.normalizedHost), identity.owner, identity.repo, 'pull', identity.number.toString()); + } + return; + } + /** * Return the panel whose webview is currently active (focused), * or `undefined` when no PR panel is active. diff --git a/src/test/github/pullRequestOverview.test.ts b/src/test/github/pullRequestOverview.test.ts index d0469014fa..eedeabf4c9 100644 --- a/src/test/github/pullRequestOverview.test.ts +++ b/src/test/github/pullRequestOverview.test.ts @@ -99,6 +99,28 @@ describe('PullRequestOverview', function () { assert.notStrictEqual(PullRequestOverviewPanel.findPanel('aaa', 'bbb', 1000), undefined); }); + it('builds the active PR URL before the PR has loaded', async function () { + repo.addGraphQLPullRequest(builder => { + builder.pullRequest(response => { + response.repository(r => { + r.pullRequest(pr => pr.number(1000)); + }); + }); + }); + + const prItem = convertRESTPullRequestToRawPullRequest(new PullRequestBuilder().number(1000).build(), repo); + const prModel = new PullRequestModel(credentialStore, telemetry, repo, remote, prItem); + const identity = { owner: prModel.remote.owner, repo: prModel.remote.repositoryName, number: prModel.number }; + + await PullRequestOverviewPanel.createOrShow(telemetry, EXTENSION_URI, pullRequestManager, identity, prModel); + const panel = PullRequestOverviewPanel.findPanel(identity.owner, identity.repo, identity.number)!; + sinon.stub(PullRequestOverviewPanel, 'getActivePanel').returns(panel); + (pullRequestManager as any)._githubRepositories = [repo]; + (panel as any)._item = undefined; + + assert.strictEqual(PullRequestOverviewPanel.getCurrentPullRequestUrl()?.toString(), 'https://github.com/aaa/bbb/pull/1000'); + }); + it('reveals an existing panel for the same PR', async function () { const createWebviewPanel = sinon.spy(vscode.window, 'createWebviewPanel');