Skip to content

Commit

Permalink
Hook up reviewers, assignees, and milestones in create view
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 committed Jul 4, 2023
1 parent ed07c81 commit 9d98980
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 9d98980

Please sign in to comment.