diff --git a/resources/icons/codicons/check-all.svg b/resources/icons/codicons/check-all.svg new file mode 100644 index 0000000000..3028a6c57e --- /dev/null +++ b/resources/icons/codicons/check-all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index de64cf068d..e7fb80cd68 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -364,6 +364,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel): Promise { + 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): Promise { try { const prBranch = this._folderRepositoryManager.repository.state.HEAD?.name; diff --git a/webviews/common/context.tsx b/webviews/common/context.tsx index df94d45c06..dd00c8076f 100644 --- a/webviews/common/context.tsx +++ b/webviews/common/context.tsx @@ -89,6 +89,8 @@ export class PRContext { public readyForReview = (): Promise => this.postMessage({ command: 'pr.readyForReview' }); + public readyForReviewAndMerge = (args: { mergeMethod: MergeMethod }): Promise => this.postMessage({ command: 'pr.readyForReviewAndMerge', args }); + public addReviewers = () => this.postMessage({ command: 'pr.change-reviewers' }); public changeProjects = (): Promise => this.postMessage({ command: 'pr.change-projects' }); public removeProject = (project: IProjectItem) => this.postMessage({ command: 'pr.remove-project', args: project }); diff --git a/webviews/components/icon.tsx b/webviews/components/icon.tsx index 45f2bb938e..67c7ee53aa 100644 --- a/webviews/components/icon.tsx +++ b/webviews/components/icon.tsx @@ -15,6 +15,7 @@ export default Icon; export const accountIcon = ; export const addIcon = ; export const checkIcon = ; +export const checkAllIcon = ; export const chevronDownIcon = ; export const circleFilledIcon = ; export const closeIcon = ; diff --git a/webviews/components/merge.tsx b/webviews/components/merge.tsx index 95851207f4..46d5d758cf 100644 --- a/webviews/components/merge.tsx +++ b/webviews/components/merge.tsx @@ -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'; @@ -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 { @@ -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 (
@@ -307,6 +320,17 @@ export const ReadyForReview = ({ isSimple }: { isSimple: boolean }) => {
+ {isCopilotOnMyBehalf && ( + + )}
@@ -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 ? : null; + if (!canEdit) { + return null; + } + + return ; } if (mergeable === PullRequestMergeability.Mergeable && hasWritePermission && !pr.mergeQueueEntry) { diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index f73c8c39c3..4716e0152e 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -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;