Skip to content

Commit

Permalink
Hook up reviewers, assignees, and milestones in create view (#4998)
Browse files Browse the repository at this point in the history
and reuse quickpicks from the PR description
Part of #4403
  • Loading branch information
alexr00 authored Jul 4, 2023
1 parent ed07c81 commit 7237de6
Show file tree
Hide file tree
Showing 7 changed files with 543 additions and 382 deletions.
23 changes: 22 additions & 1 deletion common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ILabel, MergeMethod, MergeMethodsAvailability } from '../src/github/interface';
import { IAccount, ILabel, IMilestone, ITeam, MergeMethod, MergeMethodsAvailability } from '../src/github/interface';

export interface RemoteInfo {
owner: string;
Expand Down Expand Up @@ -66,6 +66,24 @@ export interface CreatePullRequest {
labels: ILabel[];
}

export interface CreatePullRequestNew {
title: string;
body: string;
owner: string;
repo: string;
base: string
compareBranch: string;
compareOwner: string;
compareRepo: string;
draft: boolean;
autoMerge: boolean;
autoMergeMethod?: MergeMethod;
labels: ILabel[];
assignees: IAccount[];
reviewers: (IAccount | ITeam)[];
milestone?: IMilestone;
}

// #region new create view

export interface CreateParamsNew {
Expand All @@ -85,6 +103,9 @@ export interface CreateParamsNew {
isDraftDefault: boolean;
isDraft?: boolean;
labels?: ILabel[];
assignees?: IAccount[];
reviewers?: (IAccount | ITeam)[];
milestone?: IMilestone;
isDarkTheme?: boolean;

validate?: boolean;
Expand Down
116 changes: 108 additions & 8 deletions src/github/createPRViewProviderNew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { ChooseBaseRemoteAndBranchResult, ChooseCompareRemoteAndBranchResult, ChooseRemoteAndBranchArgs, CreateParamsNew, CreatePullRequest, RemoteInfo } from '../../common/views';
import { ChooseBaseRemoteAndBranchResult, ChooseCompareRemoteAndBranchResult, ChooseRemoteAndBranchArgs, CreateParamsNew, CreatePullRequestNew, RemoteInfo } from '../../common/views';
import type { Branch, Ref } from '../api/api';
import { GitHubServerType } from '../common/authentication';
import { commands, contexts } from '../common/executeCommands';
Expand All @@ -19,6 +19,7 @@ import {
PUSH_BRANCH,
SET_AUTO_MERGE,
} from '../common/settingKeys';
import { asPromise, compareIgnoreCase, formatError } from '../common/utils';
import { getNonce, IRequestMessage, WebviewViewBase } from '../common/webview';
import {
byRemoteName,
Expand All @@ -28,10 +29,11 @@ import {
titleAndBodyFrom,
} from './folderRepositoryManager';
import { GitHubRepository } from './githubRepository';
import { ILabel, MergeMethod, RepoAccessAndMergeMethods } from './interface';
import { IAccount, ILabel, IMilestone, isTeam, ITeam, MergeMethod, RepoAccessAndMergeMethods } from './interface';
import { PullRequestGitHelper } from './pullRequestGitHelper';
import { PullRequestModel } from './pullRequestModel';
import { getDefaultMergeMethod } from './pullRequestOverview';
import { getAssigneesQuickPickItems, getMilestoneFromQuickPick, reviewersQuickPick } from './quickPicks';
import { ISSUE_EXPRESSION, parseIssueExpressionOutput, variableSubstitution } from './utils';

const ISSUE_CLOSING_KEYWORDS = new RegExp('closes|closed|close|fixes|fixed|fix|resolves|resolved|resolve\s$', 'i'); // https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
Expand Down Expand Up @@ -497,6 +499,102 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements
}
}

private async setAssignees(pr: PullRequestModel, assignees: IAccount[]): Promise<void> {
if (assignees.length) {
await pr.addAssignees(assignees.map(assignee => assignee.login));
} else {
await this.autoAssign(pr);
}
}

private async setReviewers(pr: PullRequestModel, reviewers: (IAccount | ITeam)[]): Promise<void> {
if (reviewers.length) {
const users: string[] = [];
const teams: string[] = [];
for (const reviewer of reviewers) {
if (isTeam(reviewer)) {
teams.push(reviewer.id);
} else {
users.push(reviewer.id);
}
}
await pr.requestReview(users, teams);
}
}

private setMilestone(pr: PullRequestModel, milestone: IMilestone | undefined): void {
if (milestone) {
pr.updateMilestone(milestone.id);
}
}

private async getRemote(): Promise<GitHubRemote> {
return (await this._folderRepositoryManager.getGitHubRemotes()).find(remote => compareIgnoreCase(remote.owner, this._baseRemote.owner) === 0 && compareIgnoreCase(remote.repositoryName, this._baseRemote.repositoryName) === 0)!;
}

private milestone: IMilestone | undefined;
public async addMilestone(): Promise<void> {
const remote = await this.getRemote();
const repo = this._folderRepositoryManager.gitHubRepositories.find(repo => repo.remote.remoteName === remote.remoteName)!;

return getMilestoneFromQuickPick(this._folderRepositoryManager, repo, (milestone) => {
this.milestone = milestone;
return this._postMessage({
command: 'set-milestone',
params: { milestone: this.milestone }
});
});
}

private reviewers: (IAccount | ITeam)[] = [];
public async addReviewers(): Promise<void> {
let quickPick: vscode.QuickPick<vscode.QuickPickItem & {
reviewer?: IAccount | ITeam | undefined;
}> | undefined;
const remote = await this.getRemote();
try {
const repo = this._folderRepositoryManager.gitHubRepositories.find(repo => repo.remote.remoteName === remote.remoteName)!;
const [metadata, author, teamsCount] = await Promise.all([repo?.getMetadata(), this._folderRepositoryManager.getCurrentUser(), this._folderRepositoryManager.getOrgTeamsCount(repo)]);
quickPick = await reviewersQuickPick(this._folderRepositoryManager, remote.remoteName, !!metadata?.organization, teamsCount, author, this.reviewers.map(reviewer => { return { reviewer, state: 'REQUESTED' }; }), []);
quickPick.busy = false;
const acceptPromise = asPromise<void>(quickPick.onDidAccept).then(() => {
return quickPick!.selectedItems.filter(item => item.reviewer) as (vscode.QuickPickItem & { reviewer: IAccount | ITeam })[] | undefined;
});
const hidePromise = asPromise<void>(quickPick.onDidHide);
const allReviewers = await Promise.race<(vscode.QuickPickItem & { reviewer: IAccount | ITeam })[] | void>([acceptPromise, hidePromise]);
quickPick.busy = true;

if (allReviewers) {
this.reviewers = allReviewers.map(item => item.reviewer);
this._postMessage({
command: 'set-reviewers',
params: { reviewers: this.reviewers }
});
}
} catch (e) {
Logger.error(formatError(e));
vscode.window.showErrorMessage(formatError(e));
} finally {
quickPick?.hide();
quickPick?.dispose();
}
}

private assignees: IAccount[] = [];
public async addAssignees(): Promise<void> {
const remote = await this.getRemote();
const assigneesToAdd = await vscode.window.showQuickPick(getAssigneesQuickPickItems(this._folderRepositoryManager, remote.remoteName, this.assignees),
{ canPickMany: true, placeHolder: vscode.l10n.t('Add Assignees') });
if (assigneesToAdd) {
const addedAssignees = assigneesToAdd.map(assignee => assignee.assignee).filter<IAccount>((assignee): assignee is IAccount => !!assignee);
this.assignees = addedAssignees;
this._postMessage({
command: 'set-assignees',
params: { assignees: this.assignees }
});
}
}

private labels: ILabel[] = [];
public async addLabels(): Promise<void> {
let newLabels: ILabel[] = [];
Expand All @@ -518,7 +616,7 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements

const labelsToAdd = await vscode.window.showQuickPick(
getLabelOptions(this._folderRepositoryManager, this.labels, this._baseRemote),
{ canPickMany: true, placeHolder: 'Apply labels' },
{ canPickMany: true, placeHolder: vscode.l10n.t('Apply labels') },
);

if (labelsToAdd) {
Expand Down Expand Up @@ -568,15 +666,17 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements
}
}

private async create(message: IRequestMessage<CreatePullRequest>): Promise<void> {
private async create(message: IRequestMessage<CreatePullRequestNew>): Promise<void> {
Logger.debug(`Creating pull request with args ${JSON.stringify(message.args)}`, CreatePullRequestViewProviderNew.ID);

function postCreate(createdPR: PullRequestModel) {
const postCreate = (createdPR: PullRequestModel) => {
return Promise.all([
this.setLabels(createdPR, message.args.labels),
this.enableAutoMerge(createdPR, message.args.autoMerge, message.args.autoMergeMethod),
this.autoAssign(createdPR)]);
}
this.setAssignees(createdPR, message.args.assignees),
this.setReviewers(createdPR, message.args.reviewers),
this.setMilestone(createdPR, message.args.milestone)]);
};

vscode.window.withProgress({ location: { viewId: 'github:createPullRequest' } }, () => {
return vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, async progress => {
Expand Down Expand Up @@ -689,7 +789,7 @@ export class CreatePullRequestViewProviderNew extends WebviewViewBase implements
return this.getTitleAndDescription(compareBranch, this._baseBranch);
}

private async cancel(message: IRequestMessage<CreatePullRequest>) {
private async cancel(message: IRequestMessage<CreatePullRequestNew>) {
vscode.commands.executeCommand('setContext', 'github:createPullRequest', false);
this._onDone.fire(undefined);
// Re-fetch the automerge info so that it's updated for next time.
Expand Down
Loading

0 comments on commit 7237de6

Please sign in to comment.