Skip to content

Commit a683997

Browse files
Copilotalexr00
andauthored
Convert "Ready for Review" icon button to ContextDropdown (#8138)
* Initial plan * Convert icon button to dropdown in ReadyForReview component Co-authored-by: alexr00 <[email protected]> * Replace Dropdown with ContextDropdown per feedback Co-authored-by: alexr00 <[email protected]> * Add menu configuration for ContextDropdown in package.json Co-authored-by: alexr00 <[email protected]> * Also adopt in focus view * Implement pr.readyForReviewAndMerge command with busy state handling Co-authored-by: alexr00 <[email protected]> * Move command registrations from pullRequestOverview.ts to commands.ts Co-authored-by: alexr00 <[email protected]> * Revert "Move command registrations from pullRequestOverview.ts to commands.ts" This reverts commit c01c824. * Finish implementing context dropdowns. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: alexr00 <[email protected]>
1 parent cda402b commit a683997

File tree

9 files changed

+246
-60
lines changed

9 files changed

+246
-60
lines changed

package.json

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,21 @@
982982
"title": "%command.pr.readyForReview.title%",
983983
"category": "%command.pull.request.category%"
984984
},
985+
{
986+
"command": "pr.readyForReviewAndMerge",
987+
"title": "%command.pr.readyForReviewAndMerge.title%",
988+
"category": "%command.pull.request.category%"
989+
},
990+
{
991+
"command": "pr.readyForReviewDescription",
992+
"title": "%command.pr.readyForReview.title%",
993+
"category": "%command.pull.request.category%"
994+
},
995+
{
996+
"command": "pr.readyForReviewAndMergeDescription",
997+
"title": "%command.pr.readyForReviewAndMerge.title%",
998+
"category": "%command.pull.request.category%"
999+
},
9851000
{
9861001
"command": "pr.openPullRequestOnGitHub",
9871002
"title": "%command.pr.openPullRequestOnGitHub.title%",
@@ -2058,7 +2073,19 @@
20582073
},
20592074
{
20602075
"command": "pr.readyForReview",
2061-
"when": "gitHubOpenRepositoryCount != 0 && github:inReviewMode"
2076+
"when": "false"
2077+
},
2078+
{
2079+
"command": "pr.readyForReviewDescription",
2080+
"when": "false"
2081+
},
2082+
{
2083+
"command": "pr.readyForReviewAndMergeDescription",
2084+
"when": "false"
2085+
},
2086+
{
2087+
"command": "pr.readyForReviewAndMerge",
2088+
"when": "false"
20622089
},
20632090
{
20642091
"command": "pr.openPullRequestOnGitHub",
@@ -3534,6 +3561,22 @@
35343561
"command": "pr.copyVscodeDevPrLink",
35353562
"when": "webviewId == PullRequestOverview && github:copyMenu"
35363563
},
3564+
{
3565+
"command": "pr.readyForReviewDescription",
3566+
"when": "(webviewId == PullRequestOverview) && github:readyForReviewMenu"
3567+
},
3568+
{
3569+
"command": "pr.readyForReviewAndMergeDescription",
3570+
"when": "(webviewId == PullRequestOverview) && github:readyForReviewMenu && github:readyForReviewMenuWithMerge"
3571+
},
3572+
{
3573+
"command": "pr.readyForReview",
3574+
"when": "(webviewId == 'github:activePullRequest') && github:readyForReviewMenu"
3575+
},
3576+
{
3577+
"command": "pr.readyForReviewAndMerge",
3578+
"when": "(webviewId == 'github:activePullRequest') && github:readyForReviewMenu && github:readyForReviewMenuWithMerge"
3579+
},
35373580
{
35383581
"command": "pr.openChanges",
35393582
"group": "checkout@0",

package.nls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@
193193
"command.pr.dismissNotification.title": "Dismiss Notification",
194194
"command.pr.markAllCopilotNotificationsAsRead.title": "Dismiss All Copilot Notifications",
195195
"command.pr.merge.title": "Merge Pull Request",
196-
"command.pr.readyForReview.title": "Mark Pull Request Ready For Review",
196+
"command.pr.readyForReview.title": "Ready for Review",
197+
"command.pr.readyForReviewAndMerge.title": "Ready, Approve, and Auto-Merge",
197198
"command.pr.openPullRequestOnGitHub.title": "Open Pull Request on GitHub",
198199
"command.pr.openAllDiffs.title": "Open All Diffs",
199200
"command.pr.refreshPullRequest.title": "Refresh Pull Request",

src/commands.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -813,37 +813,6 @@ export function registerCommands(
813813
}),
814814
);
815815

816-
context.subscriptions.push(
817-
vscode.commands.registerCommand('pr.readyForReview', async (pr?: PRNode) => {
818-
const folderManager = reposManager.getManagerForIssueModel(pr?.pullRequestModel);
819-
if (!folderManager) {
820-
return;
821-
}
822-
const pullRequest = ensurePR(folderManager, pr);
823-
const yes = vscode.l10n.t('Yes');
824-
return vscode.window
825-
.showWarningMessage(
826-
vscode.l10n.t('Are you sure you want to mark this pull request as ready to review on GitHub?'),
827-
{ modal: true },
828-
yes,
829-
)
830-
.then(async value => {
831-
let isDraft;
832-
if (value === yes) {
833-
try {
834-
isDraft = (await pullRequest.setReadyForReview()).isDraft;
835-
return isDraft;
836-
} catch (e) {
837-
vscode.window.showErrorMessage(
838-
`Unable to mark pull request as ready to review. ${formatError(e)}`,
839-
);
840-
return isDraft;
841-
}
842-
}
843-
});
844-
}),
845-
);
846-
847816
context.subscriptions.push(
848817
vscode.commands.registerCommand('pr.dismissNotification', node => {
849818
if (node instanceof PRNode) {

src/github/activityBarViewProvider.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import * as vscode from 'vscode';
77
import { openPullRequestOnGitHub } from '../commands';
88
import { FolderRepositoryManager } from './folderRepositoryManager';
9-
import { GithubItemStateEnum, IAccount, isITeam, ITeam, PullRequestMergeability, reviewerId, ReviewEventEnum, ReviewState } from './interface';
10-
import { PullRequestModel } from './pullRequestModel';
9+
import { GithubItemStateEnum, IAccount, isITeam, ITeam, MergeMethod, PullRequestMergeability, reviewerId, ReviewEventEnum, ReviewState } from './interface';
10+
import { isCopilotOnMyBehalf, PullRequestModel } from './pullRequestModel';
1111
import { getDefaultMergeMethod } from './pullRequestOverview';
1212
import { PullRequestView } from './pullRequestOverviewCommon';
1313
import { isInCodespaces, parseReviewers } from './utils';
14-
import { MergeArguments, PullRequest, ReviewType, SubmitReviewReply } from './views';
14+
import { MergeArguments, PullRequest, ReadyForReviewReply, ReviewType, SubmitReviewReply } from './views';
1515
import { IComment } from '../common/comment';
1616
import { emojify, ensureEmojis } from '../common/emoji';
1717
import { disposeAll } from '../common/lifecycle';
@@ -34,6 +34,12 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W
3434
) {
3535
super(extensionUri);
3636

37+
this._register(vscode.commands.registerCommand('pr.readyForReview', async () => {
38+
return this.readyForReviewCommand();
39+
}));
40+
this._register(vscode.commands.registerCommand('pr.readyForReviewAndMerge', async (context: { mergeMethod: MergeMethod }) => {
41+
return this.readyForReviewAndMergeCommand(context);
42+
}));
3743
this._register(vscode.commands.registerCommand('review.approve', (e: { body: string }) => this.approvePullRequestCommand(e)));
3844
this._register(vscode.commands.registerCommand('review.comment', (e: { body: string }) => this.submitReviewCommand(e)));
3945
this._register(vscode.commands.registerCommand('review.requestChanges', (e: { body: string }) => this.requestChangesCommand(e)));
@@ -212,7 +218,7 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W
212218
this.registerPrSpecificListeners(pullRequestModel);
213219
}
214220
this._item = pullRequestModel;
215-
const [pullRequest, repositoryAccess, timelineEvents, requestedReviewers, branchInfo, defaultBranch, currentUser, viewerCanEdit, hasReviewDraft] = await Promise.all([
221+
const [pullRequest, repositoryAccess, timelineEvents, requestedReviewers, branchInfo, defaultBranch, currentUser, viewerCanEdit, hasReviewDraft, coAuthors] = await Promise.all([
216222
this._folderRepositoryManager.resolvePullRequest(
217223
pullRequestModel.remote.owner,
218224
pullRequestModel.remote.repositoryName,
@@ -226,6 +232,7 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W
226232
this._folderRepositoryManager.getCurrentUser(pullRequestModel.githubRepository),
227233
pullRequestModel.canEdit(),
228234
pullRequestModel.validateDraftMode(),
235+
pullRequestModel.getCoAuthors(),
229236
ensureEmojis(this._folderRepositoryManager.context)
230237
]);
231238

@@ -308,7 +315,8 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W
308315
isDarkTheme: vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark,
309316
isEnterprise: pullRequest.githubRepository.remote.isEnterprise,
310317
hasReviewDraft,
311-
currentUserReviewState: reviewState
318+
currentUserReviewState: reviewState,
319+
isCopilotOnMyBehalf: await isCopilotOnMyBehalf(pullRequest, currentUser, coAuthors)
312320
};
313321

314322
this._postMessage({
@@ -457,6 +465,51 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W
457465
});
458466
}
459467

468+
private async readyForReviewCommand(): Promise<void> {
469+
this._postMessage({
470+
command: 'pr.readying-for-review'
471+
});
472+
try {
473+
const result = await this._item.setReadyForReview();
474+
475+
const readiedResult: ReadyForReviewReply = {
476+
isDraft: result.isDraft
477+
};
478+
await this._postMessage({
479+
command: 'pr.readied-for-review',
480+
result: readiedResult
481+
});
482+
} catch (e) {
483+
vscode.window.showErrorMessage(`Unable to set pull request ready for review. ${formatError(e)}`);
484+
this._throwError(undefined, e.message);
485+
}
486+
}
487+
488+
private async readyForReviewAndMergeCommand(context: { mergeMethod: MergeMethod }): Promise<void> {
489+
this._postMessage({
490+
command: 'pr.readying-for-review'
491+
});
492+
try {
493+
const [readyResult, approveResult] = await Promise.all([this._item.setReadyForReview(), this._item.approve(this._folderRepositoryManager.repository)]);
494+
await this._item.enableAutoMerge(context.mergeMethod);
495+
this.updateReviewers(approveResult);
496+
497+
const readiedResult: ReadyForReviewReply = {
498+
isDraft: readyResult.isDraft,
499+
autoMerge: true,
500+
reviewEvent: approveResult,
501+
reviewers: this._existingReviewers
502+
};
503+
await this._postMessage({
504+
command: 'pr.readied-for-review',
505+
result: readiedResult
506+
});
507+
} catch (e) {
508+
vscode.window.showErrorMessage(`Unable to set pull request ready for review. ${formatError(e)}`);
509+
this._throwError(undefined, e.message);
510+
}
511+
}
512+
460513
private async mergePullRequest(
461514
message: IRequestMessage<MergeArguments>,
462515
): Promise<void> {

src/github/pullRequestOverview.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { isCopilotOnMyBehalf, PullRequestModel } from './pullRequestModel';
2727
import { PullRequestView } from './pullRequestOverviewCommon';
2828
import { pickEmail, reviewersQuickPick } from './quickPicks';
2929
import { parseReviewers } from './utils';
30-
import { CancelCodingAgentReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReviewType, SubmitReviewReply } from './views';
30+
import { CancelCodingAgentReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReadyForReviewReply, ReviewType, SubmitReviewReply } from './views';
3131
import { IComment } from '../common/comment';
3232
import { COPILOT_SWE_AGENT, copilotEventToStatus, CopilotPRStatus, mostRecentCopilotEvent } from '../common/copilot';
3333
import { commands, contexts } from '../common/executeCommands';
@@ -142,6 +142,12 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
142142

143143
this.setVisibilityContext();
144144

145+
this._register(vscode.commands.registerCommand('pr.readyForReviewDescription', async () => {
146+
return this.readyForReviewCommand();
147+
}));
148+
this._register(vscode.commands.registerCommand('pr.readyForReviewAndMergeDescription', async (context: { mergeMethod: MergeMethod }) => {
149+
return this.readyForReviewAndMergeCommand(context);
150+
}));
145151
this._register(vscode.commands.registerCommand('review.approveDescription', (e) => this.approvePullRequestCommand(e)));
146152
this._register(vscode.commands.registerCommand('review.commentDescription', (e) => this.submitReviewCommand(e)));
147153
this._register(vscode.commands.registerCommand('review.requestChangesDescription', (e) => this.requestChangesCommand(e)));
@@ -678,6 +684,51 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
678684
}
679685
}
680686

687+
private async readyForReviewCommand(): Promise<void> {
688+
this._postMessage({
689+
command: 'pr.readying-for-review'
690+
});
691+
try {
692+
const result = await this._item.setReadyForReview();
693+
694+
const readiedResult: ReadyForReviewReply = {
695+
isDraft: result.isDraft
696+
};
697+
await this._postMessage({
698+
command: 'pr.readied-for-review',
699+
result: readiedResult
700+
});
701+
} catch (e) {
702+
vscode.window.showErrorMessage(`Unable to set pull request ready for review. ${formatError(e)}`);
703+
this._throwError(undefined, e.message);
704+
}
705+
}
706+
707+
private async readyForReviewAndMergeCommand(context: { mergeMethod: MergeMethod }): Promise<void> {
708+
this._postMessage({
709+
command: 'pr.readying-for-review'
710+
});
711+
try {
712+
const [readyResult, approveResult] = await Promise.all([this._item.setReadyForReview(), this._item.approve(this._folderRepositoryManager.repository)]);
713+
await this._item.enableAutoMerge(context.mergeMethod);
714+
this.updateReviewers(approveResult);
715+
716+
const readiedResult: ReadyForReviewReply = {
717+
isDraft: readyResult.isDraft,
718+
autoMerge: true,
719+
reviewEvent: approveResult,
720+
reviewers: this._existingReviewers
721+
};
722+
await this._postMessage({
723+
command: 'pr.readied-for-review',
724+
result: readiedResult
725+
});
726+
} catch (e) {
727+
vscode.window.showErrorMessage(`Unable to set pull request ready for review. ${formatError(e)}`);
728+
this._throwError(undefined, e.message);
729+
}
730+
}
731+
681732
private async checkoutDefaultBranch(message: IRequestMessage<string>): Promise<void> {
682733
try {
683734
const prBranch = this._folderRepositoryManager.repository.state.HEAD?.name;

src/github/views.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ export interface SubmitReviewReply {
126126
reviewers?: ReviewState[];
127127
}
128128

129+
export interface ReadyForReviewReply {
130+
isDraft: boolean;
131+
reviewEvent?: ReviewEvent;
132+
reviewers?: ReviewState[];
133+
autoMerge?: boolean;
134+
}
135+
129136
export interface MergeArguments {
130137
title: string | undefined;
131138
description: string | undefined;

webviews/activityBarView/index.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,17 @@ form,
7575
width: 100%;
7676
}
7777

78-
.button-container button {
78+
.button-container>button {
79+
width: 100%;
80+
}
81+
82+
.button-container:has(> .dropdown-container) {
83+
display: flex;
84+
min-width: 0;
85+
}
86+
87+
.dropdown-container {
88+
flex-grow: 1;
7989
width: 100%;
8090
}
8191

webviews/common/context.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { CloseResult, OpenCommitChangesArgs } from '../../common/views';
1010
import { IComment } from '../../src/common/comment';
1111
import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../../src/common/timelineEvent';
1212
import { IProjectItem, MergeMethod, ReadyForReview } from '../../src/github/interface';
13-
import { CancelCodingAgentReply, ChangeAssigneesReply, DeleteReviewResult, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, SubmitReviewReply } from '../../src/github/views';
13+
import { CancelCodingAgentReply, ChangeAssigneesReply, DeleteReviewResult, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, ReadyForReviewReply, SubmitReviewReply } from '../../src/github/views';
1414

1515
export class PRContext {
1616
constructor(
@@ -245,6 +245,32 @@ export class PRContext {
245245
this.updatePR(state);
246246
}
247247

248+
private readyForReviewComplete(reply: ReadyForReviewReply) {
249+
const { pr: state } = this;
250+
if (!state) {
251+
throw new Error('Unexpectedly no pull request when trying to ready for review');
252+
}
253+
const { isDraft, reviewEvent, reviewers } = reply;
254+
state.busy = false;
255+
state.isDraft = isDraft;
256+
if (!reviewEvent) {
257+
this.updatePR(state);
258+
return;
259+
}
260+
if (reviewers) {
261+
state.reviewers = reviewers;
262+
}
263+
state.events = [...state.events, reviewEvent];
264+
if (reviewEvent.event === EventType.Reviewed) {
265+
state.currentUserReviewState = reviewEvent.state;
266+
}
267+
if (reply.autoMerge !== undefined) {
268+
state.autoMerge = reply.autoMerge;
269+
state.autoMergeMethod = state.defaultMergeMethod;
270+
}
271+
this.updatePR(state);
272+
}
273+
248274
public reRequestReview = async (reviewerId: string) => {
249275
const { pr: state } = this;
250276
if (!state) {
@@ -388,6 +414,10 @@ export class PRContext {
388414
return this.updatePR({ busy: true, lastReviewType: message.lastReviewType });
389415
case 'pr.append-review':
390416
return this.appendReview(message);
417+
case 'pr.readying-for-review':
418+
return this.updatePR({ busy: true });
419+
case 'pr.readied-for-review':
420+
return this.readyForReviewComplete(message);
391421
}
392422
};
393423

0 commit comments

Comments
 (0)