Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/icons/codicons/check-all.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions src/github/pullRequestOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
return this.deleteBranch(message);
case 'pr.readyForReview':
return this.setReadyForReview(message);
case 'pr.readyForReviewAndMerge':
return this.setReadyForReviewAndMerge(message);
case 'pr.approve':
return this.approvePullRequestMessage(message);
case 'pr.request-changes':
Expand Down Expand Up @@ -649,6 +651,33 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
});
}

private async setReadyForReviewAndMerge(message: IRequestMessage<{ mergeMethod: MergeMethod }>): Promise<void> {
try {
const readyResult = await this._item.setReadyForReview();

try {
await this._item.approve(this._folderRepositoryManager.repository, '');
} catch (e) {
vscode.window.showErrorMessage(`Pull request marked as ready for review, but failed to approve. ${formatError(e)}`);
this._replyMessage(message, readyResult);
return;
}

try {
await this._item.enableAutoMerge(message.args.mergeMethod);
} catch (e) {
vscode.window.showErrorMessage(`Pull request marked as ready and approved, but failed to enable auto-merge. ${formatError(e)}`);
this._replyMessage(message, readyResult);
return;
}

this._replyMessage(message, readyResult);
} catch (e) {
vscode.window.showErrorMessage(`Unable to mark pull request as ready for review. ${formatError(e)}`);
this._throwError(message, '');
}
}

private async checkoutDefaultBranch(message: IRequestMessage<string>): Promise<void> {
try {
const prBranch = this._folderRepositoryManager.repository.state.HEAD?.name;
Expand Down
2 changes: 2 additions & 0 deletions webviews/common/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export class PRContext {

public readyForReview = (): Promise<ReadyForReview> => this.postMessage({ command: 'pr.readyForReview' });

public readyForReviewAndMerge = (args: { mergeMethod: MergeMethod }): Promise<ReadyForReview> => this.postMessage({ command: 'pr.readyForReviewAndMerge', args });

public addReviewers = () => this.postMessage({ command: 'pr.change-reviewers' });
public changeProjects = (): Promise<ProjectItemsReply> => this.postMessage({ command: 'pr.change-projects' });
public removeProject = (project: IProjectItem) => this.postMessage({ command: 'pr.remove-project', args: project });
Expand Down
1 change: 1 addition & 0 deletions webviews/components/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default Icon;
export const accountIcon = <Icon src={require('../../resources/icons/codicons/account.svg')} />;
export const addIcon = <Icon src={require('../../resources/icons/codicons/add.svg')} />;
export const checkIcon = <Icon src={require('../../resources/icons/codicons/check.svg')} className='check' />;
export const checkAllIcon = <Icon src={require('../../resources/icons/codicons/check-all.svg')} />;
export const chevronDownIcon = <Icon src={require('../../resources/icons/codicons/chevron-down.svg')} />;
export const circleFilledIcon = <Icon src={require('../../resources/icons/codicons/circle-filled.svg')} className='pending' />;
export const closeIcon = <Icon src={require('../../resources/icons/codicons/close.svg')} className='close' />;
Expand Down
38 changes: 33 additions & 5 deletions webviews/components/merge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import React, {
} from 'react';
import { AutoMerge, QueuedToMerge } from './automergeSelect';
import { Dropdown } from './dropdown';
import { checkIcon, circleFilledIcon, closeIcon, gitMergeIcon, requestChangesIcon, skipIcon, warningIcon } from './icon';
import { checkAllIcon, checkIcon, circleFilledIcon, closeIcon, gitMergeIcon, loadingIcon, requestChangesIcon, skipIcon, warningIcon } from './icon';
import { nbsp } from './space';
import { Avatar } from './user';
import { EventType, ReviewEvent } from '../../src/common/timelineEvent';
Expand Down Expand Up @@ -283,9 +283,10 @@ export const OfferToUpdate = ({ mergeable, isSimple, isCurrentlyCheckedOut, canU

};

export const ReadyForReview = ({ isSimple }: { isSimple: boolean }) => {
export const ReadyForReview = ({ isSimple, isCopilotOnMyBehalf, mergeMethod }: { isSimple: boolean; isCopilotOnMyBehalf?: boolean; mergeMethod: MergeMethod }) => {
const [isBusy, setBusy] = useState(false);
const { readyForReview, updatePR } = useContext(PullRequestContext);
const [isMergeBusy, setMergeBusy] = useState(false);
const { readyForReview, readyForReviewAndMerge, updatePR } = useContext(PullRequestContext);

const markReadyForReview = useCallback(async () => {
try {
Expand All @@ -297,6 +298,18 @@ export const ReadyForReview = ({ isSimple }: { isSimple: boolean }) => {
}
}, [setBusy, readyForReview, updatePR]);

const markReadyAndMerge = useCallback(async () => {
try {
setBusy(true);
setMergeBusy(true);
const result = await readyForReviewAndMerge({ mergeMethod: mergeMethod });
updatePR(result);
} finally {
setBusy(false);
setMergeBusy(false);
}
}, [readyForReviewAndMerge, updatePR, mergeMethod]);

return (
<div className="ready-for-review-container">
<div className='ready-for-review-text-wrapper'>
Expand All @@ -307,6 +320,17 @@ export const ReadyForReview = ({ isSimple }: { isSimple: boolean }) => {
</div>
</div>
<div className='button-container'>
{isCopilotOnMyBehalf && (
<button
className="icon-button"
disabled={isBusy}
onClick={markReadyAndMerge}
title="Mark as ready for review, approve, and enable auto-merge with default merge method"
aria-label="Ready for Review, Approve, and Auto-Merge"
>
{isMergeBusy ? loadingIcon : checkAllIcon}
</button>
)}
<button disabled={isBusy} onClick={markReadyForReview}>Ready for Review</button>
</div>
</div>
Expand Down Expand Up @@ -340,10 +364,14 @@ export const Merge = (pr: PullRequest) => {
};

export const PrActions = ({ pr, isSimple }: { pr: PullRequest; isSimple: boolean }) => {
const { hasWritePermission, canEdit, isDraft, mergeable } = pr;
const { hasWritePermission, canEdit, isDraft, mergeable, isCopilotOnMyBehalf, defaultMergeMethod } = pr;
if (isDraft) {
// Only PR author and users with push rights can mark draft as ready for review
return canEdit ? <ReadyForReview isSimple={isSimple} /> : null;
if (!canEdit) {
return null;
}

return <ReadyForReview isSimple={isSimple} isCopilotOnMyBehalf={isCopilotOnMyBehalf} mergeMethod={defaultMergeMethod} />;
}

if (mergeable === PullRequestMergeability.Mergeable && hasWritePermission && !pr.mergeQueueEntry) {
Expand Down
6 changes: 6 additions & 0 deletions webviews/editorWebview/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ button.input-box {
align-items: center;
}

.ready-for-review-container .button-container {
flex-direction: row;
display: flex;
align-items: center;
}

.ready-for-review-icon {
width: 16px;
height: 16px;
Expand Down