Skip to content
Draft
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
10 changes: 10 additions & 0 deletions src/github/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,16 @@ export interface MarkPullRequestReadyForReviewResponse {
};
}

export interface ConvertPullRequestToDraftResponse {
convertPullRequestToDraft: {
pullRequest: {
isDraft: boolean;
mergeable: 'MERGEABLE' | 'CONFLICTING' | 'UNKNOWN';
mergeStateStatus: 'BEHIND' | 'BLOCKED' | 'CLEAN' | 'DIRTY' | 'HAS_HOOKS' | 'UNKNOWN' | 'UNSTABLE';
};
};
}

export interface MergeQueueForBranchResponse {
repository: {
mergeQueue?: {
Expand Down
5 changes: 5 additions & 0 deletions src/github/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export interface ReadyForReview {
allowAutoMerge: boolean;
}

export interface ConvertToDraft {
isDraft: boolean;
mergeable: PullRequestMergeability;
}

export interface IActor {
login: string;
avatarUrl?: string;
Expand Down
40 changes: 40 additions & 0 deletions src/github/pullRequestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
AddCommentResponse,
AddReactionResponse,
AddReviewThreadResponse,
ConvertPullRequestToDraftResponse,
DeleteReactionResponse,
DeleteReviewResponse,
DequeuePullRequestResponse,
Expand All @@ -44,6 +45,7 @@ import {
} from './graphql';
import {
AccountType,
ConvertToDraft,
GithubItemStateEnum,
IAccount,
IGitTreeItem,
Expand Down Expand Up @@ -1837,6 +1839,44 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
}
}

/**
* Convert a pull request to draft.
*/
async convertToDraft(): Promise<ConvertToDraft> {
try {
const { mutate, schema } = await this.githubRepository.ensure();

const { data } = await mutate<ConvertPullRequestToDraftResponse>({
mutation: schema.ConvertToDraft,
variables: {
input: {
pullRequestId: this.graphNodeId,
},
},
});

/* __GDPR__
"pr.convertToDraft.success" : {}
*/
this._telemetry.sendTelemetryEvent('pr.convertToDraft.success');

const result: ConvertToDraft = {
isDraft: data!.convertPullRequestToDraft.pullRequest.isDraft,
mergeable: parseMergeability(data!.convertPullRequestToDraft.pullRequest.mergeable, data!.convertPullRequestToDraft.pullRequest.mergeStateStatus),
};
this.item.isDraft = result.isDraft;
this.item.mergeable = result.mergeable;
this._onDidChange.fire({ draft: true });
return result;
} catch (e) {
/* __GDPR__
"pr.convertToDraft.failure" : {}
*/
this._telemetry.sendTelemetryErrorEvent('pr.convertToDraft.failure');
throw e;
}
}

private updateCommentReactions(graphNodeId: string, reactionGroups: ReactionGroup[]) {
const reviewThread = this._reviewThreadsCache?.find(thread =>
thread.comments.some(c => c.graphNodeId === graphNodeId),
Expand Down
10 changes: 10 additions & 0 deletions src/github/pullRequestOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
return this.setReadyForReview(message);
case 'pr.readyForReviewAndMerge':
return this.setReadyForReviewAndMerge(message);
case 'pr.convertToDraft':
return this.setConvertToDraft(message);
case 'pr.approve':
return this.approvePullRequestMessage(message);
case 'pr.request-changes':
Expand Down Expand Up @@ -666,6 +668,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
return PullRequestReviewCommon.setReadyForReviewAndMerge(this.getReviewContext(), message);
}

private async setConvertToDraft(message: IRequestMessage<{}>): Promise<void> {
return PullRequestReviewCommon.setConvertToDraft(this.getReviewContext(), message);
}

private async readyForReviewCommand(): Promise<void> {
return PullRequestReviewCommon.readyForReviewCommand(this.getReviewContext());
}
Expand All @@ -674,6 +680,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
return PullRequestReviewCommon.readyForReviewAndMergeCommand(this.getReviewContext(), context);
}

private async convertToDraftCommand(): Promise<void> {
return PullRequestReviewCommon.convertToDraftCommand(this.getReviewContext());
}

private async checkoutDefaultBranch(message: IRequestMessage<string>): Promise<void> {
return PullRequestReviewCommon.checkoutDefaultBranch(this.getReviewContext(), message);
}
Expand Down
30 changes: 30 additions & 0 deletions src/github/pullRequestReviewCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,16 @@ export namespace PullRequestReviewCommon {
}
}

export async function setConvertToDraft(ctx: ReviewContext, _message: IRequestMessage<{}>): Promise<void> {
try {
const result = await ctx.item.convertToDraft();
ctx.replyMessage(_message, result);
} catch (e) {
vscode.window.showErrorMessage(vscode.l10n.t('Unable to convert pull request to draft. {0}', formatError(e)));
ctx.throwError(_message, '');
}
}

export async function readyForReviewCommand(ctx: ReviewContext): Promise<void> {
ctx.postMessage({
command: 'pr.readying-for-review'
Expand Down Expand Up @@ -274,6 +284,26 @@ export namespace PullRequestReviewCommon {
}
}

export async function convertToDraftCommand(ctx: ReviewContext): Promise<void> {
ctx.postMessage({
command: 'pr.converting-to-draft'
});
try {
const result = await ctx.item.convertToDraft();

const convertedResult = {
isDraft: result.isDraft
};
await ctx.postMessage({
command: 'pr.converted-to-draft',
result: convertedResult
});
} catch (e) {
vscode.window.showErrorMessage(`Unable to convert pull request to draft. ${formatError(e)}`);
ctx.throwError(undefined, e.message);
}
}

export async function deleteBranch(folderRepositoryManager: FolderRepositoryManager, item: PullRequestModel): Promise<{ isReply: boolean, message: any }> {
const branchInfo = await folderRepositoryManager.getBranchNameForPullRequest(item);
const actions: (vscode.QuickPickItem & { type: 'remoteHead' | 'local' | 'remote' | 'suspend' })[] = [];
Expand Down
10 changes: 10 additions & 0 deletions src/github/queriesShared.gql
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,16 @@ mutation ReadyForReview($input: MarkPullRequestReadyForReviewInput!) {
}
}

mutation ConvertToDraft($input: ConvertPullRequestToDraftInput!) {
convertPullRequestToDraft(input: $input) {
pullRequest {
isDraft
mergeable
mergeStateStatus
}
}
}

mutation StartReview($input: AddPullRequestReviewInput!) {
addPullRequestReview(input: $input) {
pullRequestReview {
Expand Down
4 changes: 4 additions & 0 deletions src/github/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ export interface ReadyForReviewReply {
autoMerge?: boolean;
}

export interface ConvertToDraftReply {
isDraft: boolean;
}

export interface MergeArguments {
title: string | undefined;
description: string | undefined;
Expand Down
4 changes: 3 additions & 1 deletion webviews/common/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getMessageHandler, MessageHandler } from './message';
import { CloseResult, OpenCommitChangesArgs } from '../../common/views';
import { IComment } from '../../src/common/comment';
import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../../src/common/timelineEvent';
import { IProjectItem, MergeMethod, ReadyForReview } from '../../src/github/interface';
import { ConvertToDraft, IProjectItem, MergeMethod, ReadyForReview } from '../../src/github/interface';
import { CancelCodingAgentReply, ChangeAssigneesReply, DeleteReviewResult, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, ReadyForReviewReply, SubmitReviewReply } from '../../src/github/views';

export class PRContext {
Expand Down Expand Up @@ -91,6 +91,8 @@ export class PRContext {

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

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

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
33 changes: 33 additions & 0 deletions webviews/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
) : (
<div className="section-placeholder">None yet</div>
)}
{!pr!.isDraft && (hasWritePermission || pr!.isAuthor) && (
<ConvertToDraft />
)}
</Section>
)}

Expand Down Expand Up @@ -507,3 +510,33 @@ function Project(project: IProjectItem & { canDelete: boolean }) {
</div>
);
}

function ConvertToDraft() {
const { convertToDraft, updatePR, pr } = useContext(PullRequestContext);
const [isBusy, setBusy] = useState(false);

const handleConvertToDraft = async () => {
try {
setBusy(true);
const result = await convertToDraft();
updatePR({ isDraft: result.isDraft });
} catch (e) {
// Error handling is done in the backend
} finally {
setBusy(false);
}
};

return (
<div className="section-item">
<button
className="secondary"
onClick={handleConvertToDraft}
disabled={isBusy || pr?.busy}
style={{ width: '100%', marginTop: '8px' }}
>
Convert to draft
</button>
</div>
);
}