Skip to content

Commit 0739370

Browse files
Copilotbamurtaughalexr00
authored
Add Cancel Review button to review submission interface (#7317)
* Initial plan * Initial assessment: Adding Cancel Review functionality Co-authored-by: bamurtaugh <25310137+bamurtaugh@users.noreply.github.com> * Add Cancel Review functionality to webview and backend Co-authored-by: bamurtaugh <25310137+bamurtaugh@users.noreply.github.com> * Add VSCode language model tool source type definitions This change adds TypeScript definitions for language model tools from different sources including VS Code extensions and MCP (Model Context Protocol) servers. The new types provide source attribution and metadata for language model tools, enabling better tool management and user transparency about tool origins. Co-authored-by: bamurtaugh <25310137+bamurtaugh@users.noreply.github.com> * Remove unrelated TypeScript definitions for language model tools Co-authored-by: bamurtaugh <25310137+bamurtaugh@users.noreply.github.com> * Initial plan for addressing review feedback Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Add DeleteReviewResult type and validate review exists before clearing state Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Clean up * More clean up --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bamurtaugh <25310137+bamurtaugh@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 6066568 commit 0739370

6 files changed

Lines changed: 64 additions & 6 deletions

File tree

src/github/pullRequestOverview.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { isCopilotOnMyBehalf, PullRequestModel } from './pullRequestModel';
3737
import { PullRequestView } from './pullRequestOverviewCommon';
3838
import { pickEmail, reviewersQuickPick } from './quickPicks';
3939
import { parseReviewers } from './utils';
40-
import { CancelCodingAgentReply, MergeArguments, MergeResult, PullRequest, ReviewType, SubmitReviewReply } from './views';
40+
import { CancelCodingAgentReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReviewType, SubmitReviewReply } from './views';
4141

4242
export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestModel> {
4343
public static override ID: string = 'PullRequestOverviewPanel';
@@ -402,6 +402,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
402402
return this.cancelCodingAgent(message);
403403
case 'pr.openCommitChanges':
404404
return this.openCommitChanges(message);
405+
case 'pr.delete-review':
406+
return this.deleteReview(message);
405407
}
406408
}
407409

@@ -862,6 +864,17 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
862864
return this._item.deleteReviewComment(comment.id.toString());
863865
}
864866

867+
private async deleteReview(message: IRequestMessage<void>) {
868+
try {
869+
const result: DeleteReviewResult = await this._item.deleteReview();
870+
await this._replyMessage(message, result);
871+
} catch (e) {
872+
Logger.error(formatError(e), PullRequestOverviewPanel.ID);
873+
vscode.window.showErrorMessage(vscode.l10n.t('Deleting review failed. {0}', formatError(e)));
874+
this._throwError(message, `${formatError(e)}`);
875+
}
876+
}
877+
865878
override dispose() {
866879
super.dispose();
867880
disposeAll(this._prListeners);

src/github/views.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { IComment } from '../common/comment';
67
import { CommentEvent, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../common/timelineEvent';
78
import {
89
GithubItemStateEnum,
@@ -138,6 +139,11 @@ export interface MergeResult {
138139
events?: TimelineEvent[];
139140
}
140141

142+
export interface DeleteReviewResult {
143+
deletedReviewId: number;
144+
deletedReviewComments: IComment[];
145+
}
146+
141147
export enum PreReviewState {
142148
None = 0,
143149
Available,

src/test/github/pullRequestGitHelper.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ describe('PullRequestGitHelper', function () {
128128
// Verify that the original local branch is preserved with its commit
129129
const originalBranch = await repository.getBranch('my-branch');
130130
assert.strictEqual(originalBranch.commit, 'local-commit-hash', 'Original branch should be preserved');
131-
131+
132132
// Verify that a unique branch was created and checked out
133133
const uniqueBranch = await repository.getBranch('pr/me/100');
134134
assert.strictEqual(uniqueBranch.commit, 'remote-commit-hash', 'Unique branch should have remote commit');

src/test/issues/stateManager.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import { USE_BRANCH_FOR_ISSUES, ISSUES_SETTINGS_NAMESPACE } from '../../common/s
1111

1212
// Mock classes for testing
1313
class MockFolderRepositoryManager {
14-
constructor(public repository: { rootUri: vscode.Uri }) {}
14+
constructor(public repository: { rootUri: vscode.Uri }) { }
1515
}
1616

1717
class MockSingleRepoState {
1818
currentIssue?: MockCurrentIssue;
19-
constructor(public folderManager: MockFolderRepositoryManager) {}
19+
constructor(public folderManager: MockFolderRepositoryManager) { }
2020
}
2121

2222
class MockCurrentIssue {

webviews/common/context.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CloseResult, OpenCommitChangesArgs } from '../../common/views';
88
import { IComment } from '../../src/common/comment';
99
import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../../src/common/timelineEvent';
1010
import { IProjectItem, MergeMethod, ReadyForReview } from '../../src/github/interface';
11-
import { CancelCodingAgentReply, ChangeAssigneesReply, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, SubmitReviewReply } from '../../src/github/views';
11+
import { CancelCodingAgentReply, ChangeAssigneesReply, DeleteReviewResult, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, SubmitReviewReply } from '../../src/github/views';
1212
import { getState, setState, updateState } from './cache';
1313
import { getMessageHandler, MessageHandler } from './message';
1414

@@ -158,6 +158,30 @@ export class PRContext {
158158

159159
public submit = (body: string) => this.submitReviewCommand('pr.submit', body);
160160

161+
public deleteReview = async () => {
162+
try {
163+
const result: DeleteReviewResult = await this.postMessage({ command: 'pr.delete-review' });
164+
165+
const state = this.pr;
166+
const eventsWithoutPendingReview = state?.events.filter(event =>
167+
!(event.event === EventType.Reviewed && event.id === result.deletedReviewId)
168+
) ?? [];
169+
170+
if (state && (eventsWithoutPendingReview.length < state.events.length)) {
171+
// Update the PR state to reflect the deleted review
172+
state.busy = false;
173+
state.pendingCommentText = '';
174+
state.pendingCommentDrafts = {};
175+
// Remove the deleted review from events
176+
state.events = eventsWithoutPendingReview;
177+
this.updatePR(state);
178+
}
179+
return result;
180+
} catch (error) {
181+
return this.updatePR({ busy: false });
182+
}
183+
};
184+
161185
public close = async (body?: string) => {
162186
const { pr } = this;
163187
if (!pr) {

webviews/components/timeline.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ function CommentThread({ thread, event }: { thread: IComment[]; event: ReviewEve
269269
}
270270

271271
function AddReviewSummaryComment() {
272-
const { requestChanges, approve, submit, pr } = useContext(PullRequestContext);
272+
const { requestChanges, approve, submit, deleteReview, pr } = useContext(PullRequestContext);
273273
const isAuthor = pr?.isAuthor;
274274
const comment = useRef<HTMLTextAreaElement>();
275275
const [isBusy, setBusy] = useState(false);
@@ -292,6 +292,13 @@ function AddReviewSummaryComment() {
292292
setBusy(false);
293293
}
294294

295+
async function cancelReview(event: React.MouseEvent): Promise<void> {
296+
event.preventDefault();
297+
setBusy(true);
298+
await deleteReview();
299+
setBusy(false);
300+
}
301+
295302
const onKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
296303
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
297304
submitAction(event, ReviewType.Comment);
@@ -317,6 +324,14 @@ function AddReviewSummaryComment() {
317324
value={commentText}
318325
></textarea>
319326
<div className="form-actions">
327+
<button
328+
id="cancel-review"
329+
className='secondary'
330+
disabled={isBusy || pr?.busy}
331+
onClick={cancelReview}
332+
>
333+
Cancel Review
334+
</button>
320335
{isAuthor ? null : (
321336
<button
322337
id="request-changes"

0 commit comments

Comments
 (0)