Add CC with bulk action
https://imgur.com/a/6qR0E9J
Google-Bug-Id: b/216478677
Release-Notes: skip
Change-Id: I376709c5db4d3e8c61d073c5ad9878d95ea35532
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
index 227115f..7029ad6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
@@ -5,7 +5,7 @@
*/
import {html, LitElement} from 'lit';
import {customElement, query, state} from 'lit/decorators';
-import {ProgressStatus} from '../../../constants/constants';
+import {ProgressStatus, ReviewerState} from '../../../constants/constants';
import {bulkActionsModelToken} from '../../../models/bulk-actions/bulk-actions-model';
import {resolve} from '../../../models/dependency';
import {AccountInfo, ChangeInfo} from '../../../types/common';
@@ -21,12 +21,29 @@
} from '../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider';
import '../../shared/gr-account-list/gr-account-list';
+const SUGGESTIONS_PROVIDERS_USERS_TYPES_BY_REVIEWER_STATE: Record<
+ ReviewerState,
+ SUGGESTIONS_PROVIDERS_USERS_TYPES
+> = {
+ REVIEWER: SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER,
+ CC: SUGGESTIONS_PROVIDERS_USERS_TYPES.CC,
+ REMOVED: SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY,
+};
+
@customElement('gr-change-list-reviewer-flow')
export class GrChangeListReviewerFlow extends LitElement {
@state() private selectedChanges: ChangeInfo[] = [];
- // given to gr-account-list to mutate
- @state() private updatedReviewers: AccountInfo[] = [];
+ // contents are given to gr-account-lists to mutate
+ @state() private updatedAccountsByReviewerState: Map<
+ ReviewerState,
+ AccountInfo[]
+ > = new Map();
+
+ @state() private suggestionsProviderByReviewerState: Map<
+ ReviewerState,
+ ReviewerSuggestionsProvider
+ > = new Map();
@state() private progressByChange = new Map<ChangeInfo, ProgressStatus>();
@@ -36,8 +53,6 @@
private restApiService = getAppContext().restApiService;
- private suggestionsProvider?: ReviewerSuggestionsProvider;
-
override connectedCallback(): void {
super.connectedCallback();
subscribe(
@@ -71,14 +86,16 @@
<div slot="header">Add Reviewer / CC</div>
<div slot="main">
<div>
- <span>Reviewers:</span>
- <gr-account-list
- .accounts=${this.updatedReviewers}
- .removableValues=${[]}
- .suggestionsProvider=${this.suggestionsProvider}
- .placeholder=${'Add reviewer...'}
- >
- </gr-account-list>
+ <span>Reviewers</span>
+ ${this.renderAccountList(
+ ReviewerState.REVIEWER,
+ 'reviewer-list',
+ 'Add reviewer'
+ )}
+ </div>
+ <div>
+ <span>CC</span>
+ ${this.renderAccountList(ReviewerState.CC, 'cc-list', 'Add CC')}
</div>
</div>
</gr-dialog>
@@ -86,27 +103,53 @@
`;
}
+ private renderAccountList(
+ reviewerState: ReviewerState,
+ id: string,
+ placeholder: string
+ ) {
+ const updatedAccounts =
+ this.updatedAccountsByReviewerState.get(reviewerState);
+ const suggestionsProvider =
+ this.suggestionsProviderByReviewerState.get(reviewerState);
+ if (!updatedAccounts || !suggestionsProvider) {
+ return;
+ }
+ return html`
+ <gr-account-list
+ id=${id}
+ .accounts=${updatedAccounts}
+ .removableValues=${[]}
+ .suggestionsProvider=${suggestionsProvider}
+ .placeholder=${placeholder}
+ >
+ </gr-account-list>
+ `;
+ }
+
private resetFlow() {
this.progressByChange = new Map(
this.selectedChanges.map(change => [change, ProgressStatus.NOT_STARTED])
);
- this.updatedReviewers = this.getCurrentReviewers();
- if (this.selectedChanges.length === 0) {
- return;
+ for (const state of [ReviewerState.REVIEWER, ReviewerState.CC]) {
+ this.updatedAccountsByReviewerState.set(
+ state,
+ this.getCurrentAccounts(state)
+ );
+ if (this.selectedChanges.length > 0) {
+ this.suggestionsProviderByReviewerState.set(
+ state,
+ this.createSuggestionsProvider(state)
+ );
+ }
}
- this.suggestionsProvider = GrReviewerSuggestionsProvider.create(
- this.restApiService,
- // TODO: fan out and get suggestions allowed by all changes
- this.selectedChanges[0]._number,
- SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER
- );
- this.suggestionsProvider.init();
+ this.requestUpdate();
}
private onConfirm(overallStatus: ProgressStatus) {
switch (overallStatus) {
case ProgressStatus.NOT_STARTED:
- this.saveReviewers();
+ this.saveChanges();
break;
case ProgressStatus.SUCCESSFUL:
this.overlay.close();
@@ -114,12 +157,12 @@
}
}
- private saveReviewers() {
+ private saveChanges() {
this.progressByChange = new Map(
this.selectedChanges.map(change => [change, ProgressStatus.RUNNING])
);
const inFlightActions = this.getBulkActionsModel().addReviewers(
- this.getAddedReviewers()
+ this.updatedAccountsByReviewerState
);
for (let index = 0; index < this.selectedChanges.length; index++) {
const change = this.selectedChanges[index];
@@ -149,9 +192,9 @@
: 'Close';
}
- private getCurrentReviewers() {
- const reviewersPerChange = this.selectedChanges.map(change =>
- Object.values(change.reviewers).flat()
+ private getCurrentAccounts(reviewerState: ReviewerState) {
+ const reviewersPerChange = this.selectedChanges.map(
+ change => change.reviewers[reviewerState] ?? []
);
if (reviewersPerChange.length === 0) {
return [];
@@ -162,11 +205,17 @@
);
}
- private getAddedReviewers(): AccountInfo[] {
- const oldReviewers = this.getCurrentReviewers();
- return this.updatedReviewers.filter(
- reviewer => !oldReviewers.includes(reviewer)
+ private createSuggestionsProvider(
+ state: ReviewerState
+ ): ReviewerSuggestionsProvider {
+ const suggestionsProvider = GrReviewerSuggestionsProvider.create(
+ this.restApiService,
+ // TODO: fan out and get suggestions allowed by all changes
+ this.selectedChanges[0]._number,
+ SUGGESTIONS_PROVIDERS_USERS_TYPES_BY_REVIEWER_STATE[state]
);
+ suggestionsProvider.init();
+ return suggestionsProvider;
}
private getOverallStatus() {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts
index 131b823..4e9e790 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts
@@ -35,19 +35,25 @@
createAccountWithIdNameAndEmail(0),
createAccountWithIdNameAndEmail(1),
createAccountWithIdNameAndEmail(2),
+ createAccountWithIdNameAndEmail(3),
+ createAccountWithIdNameAndEmail(4),
+ createAccountWithIdNameAndEmail(5),
];
const changes: ChangeInfo[] = [
{
...createChange(),
_number: 1 as NumericChangeId,
subject: 'Subject 1',
- reviewers: {REVIEWER: [accounts[0], accounts[1]]},
+ reviewers: {
+ REVIEWER: [accounts[0], accounts[1]],
+ CC: [accounts[3], accounts[4]],
+ },
},
{
...createChange(),
_number: 2 as NumericChangeId,
subject: 'Subject 2',
- reviewers: {REVIEWER: [accounts[0]]},
+ reviewers: {REVIEWER: [accounts[0]], CC: [accounts[3]]},
},
];
@@ -103,8 +109,12 @@
<div slot="header">Add Reviewer / CC</div>
<div slot="main">
<div>
- <span>Reviewers:</span>
- <gr-account-list></gr-account-list>
+ <span>Reviewers</span>
+ <gr-account-list id="reviewer-list"></gr-account-list>
+ </div>
+ <div>
+ <span>CC</span>
+ <gr-account-list id="cc-list"></gr-account-list>
</div>
</div>
</gr-dialog>
@@ -141,23 +151,23 @@
assert.isTrue(overlay.opened);
});
- suite('dialog', () => {
- let markActivePromises: MockPromise<Response>[];
+ suite('dialog flow', () => {
+ let saveChangesPromises: MockPromise<Response>[];
let saveChangeReviewStub: sinon.SinonStub;
let dialog: GrDialog;
async function resolvePromises() {
- markActivePromises[0].resolve(new Response());
- markActivePromises[1].resolve(new Response());
+ saveChangesPromises[0].resolve(new Response());
+ saveChangesPromises[1].resolve(new Response());
await element.updateComplete;
}
setup(async () => {
- markActivePromises = [];
+ saveChangesPromises = [];
saveChangeReviewStub = stubRestApi('saveChangeReview');
for (let i = 0; i < changes.length; i++) {
const promise = mockPromise<Response>();
- markActivePromises.push(promise);
+ saveChangesPromises.push(promise);
saveChangeReviewStub
.withArgs(changes[i]._number, sinon.match.any, sinon.match.any)
.returns(promise);
@@ -169,21 +179,32 @@
await dialog.updateComplete;
});
- test('only lists reviewers shared by all changes', async () => {
- const accountList = queryAndAssert<GrAccountList>(
+ test('only lists reviewers/CCs shared by all changes', async () => {
+ const reviewerList = queryAndAssert<GrAccountList>(
dialog,
- 'gr-account-list'
+ 'gr-account-list#reviewer-list'
+ );
+ const ccList = queryAndAssert<GrAccountList>(
+ dialog,
+ 'gr-account-list#cc-list'
);
// does not include account 1
- assert.sameMembers(accountList.accounts, [accounts[0]]);
+ assert.sameMembers(reviewerList.accounts, [accounts[0]]);
+ // does not include account 4
+ assert.sameMembers(ccList.accounts, [accounts[3]]);
});
- test('adds reviewer', async () => {
- const accountList = queryAndAssert<GrAccountList>(
+ test('adds reviewer & CC', async () => {
+ const reviewerList = queryAndAssert<GrAccountList>(
dialog,
- 'gr-account-list'
+ 'gr-account-list#reviewer-list'
);
- accountList.accounts.push(accounts[2]);
+ const ccList = queryAndAssert<GrAccountList>(
+ dialog,
+ 'gr-account-list#cc-list'
+ );
+ reviewerList.accounts.push(accounts[2]);
+ ccList.accounts.push(accounts[5]);
await flush();
dialog.confirmButton!.click();
await element.updateComplete;
@@ -195,6 +216,7 @@
{
reviewers: [
{reviewer: accounts[2]._account_id, state: ReviewerState.REVIEWER},
+ {reviewer: accounts[5]._account_id, state: ReviewerState.CC},
],
},
]);
@@ -204,6 +226,7 @@
{
reviewers: [
{reviewer: accounts[2]._account_id, state: ReviewerState.REVIEWER},
+ {reviewer: accounts[5]._account_id, state: ReviewerState.CC},
],
},
]);
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
index b4fba63..a3bd530 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
@@ -16,7 +16,7 @@
import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
import {define} from '../dependency';
import {select} from '../../utils/observable-util';
-import {ReviewInput} from '../../types/common';
+import {ReviewInput, ReviewerInput} from '../../types/common';
export const bulkActionsModelToken =
define<BulkActionsModel>('bulk-actions-model');
@@ -129,25 +129,25 @@
});
}
- addReviewers(addedReviewers: AccountInfo[]): Promise<Response>[] {
+ addReviewers(
+ changedReviewers: Map<ReviewerState, AccountInfo[]>
+ ): Promise<Response>[] {
const current = this.subject$.getValue();
const changes = current.selectedChangeNums.map(
changeNum => current.allChanges.get(changeNum)!
);
return changes.map(change => {
- const reviewersNewToChange = addedReviewers.filter(
- account => !change.reviewers[ReviewerState.REVIEWER]?.includes(account)
+ const reviewersNewToChange = [
+ ReviewerState.REVIEWER,
+ ReviewerState.CC,
+ ].flatMap(state =>
+ this.getNewReviewersToChange(change, state, changedReviewers)
);
if (reviewersNewToChange.length === 0) {
return Promise.resolve(new Response());
}
const reviewInput: ReviewInput = {
- reviewers: reviewersNewToChange.map(account => {
- return {
- state: ReviewerState.REVIEWER,
- reviewer: account._account_id!,
- };
- }),
+ reviewers: reviewersNewToChange,
};
return this.restApiService.saveChangeReview(
change._number,
@@ -212,5 +212,20 @@
};
}
+ private getNewReviewersToChange(
+ change: ChangeInfo,
+ state: ReviewerState,
+ changedReviewers: Map<ReviewerState, AccountInfo[]>
+ ): ReviewerInput[] {
+ return (
+ changedReviewers
+ .get(state)
+ ?.filter(account => !change.reviewers[state]?.includes(account))
+ .map(account => {
+ return {state, reviewer: account._account_id!};
+ }) ?? []
+ );
+ }
+
finalize() {}
}
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
index 763df9b..8f2330d 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
@@ -158,22 +158,21 @@
const accounts: AccountInfo[] = [
createAccountWithIdNameAndEmail(0),
createAccountWithIdNameAndEmail(1),
- createAccountWithIdNameAndEmail(2),
];
const changes: ChangeInfo[] = [
{
...createChange(),
_number: 1 as NumericChangeId,
subject: 'Subject 1',
- reviewers: {REVIEWER: [accounts[0], accounts[1]]},
- removable_reviewers: [accounts[0]],
+ reviewers: {
+ REVIEWER: [accounts[0]],
+ CC: [accounts[1]],
+ },
},
{
...createChange(),
_number: 2 as NumericChangeId,
subject: 'Subject 2',
- reviewers: {REVIEWER: [accounts[0]]},
- removable_reviewers: [accounts[0]],
},
];
let saveChangeReviewStub: sinon.SinonStub;
@@ -191,18 +190,23 @@
bulkActionsModel.addSelectedChangeNum(changes[1]._number);
});
- test('adds reviewers only to changes that need it', async () => {
- bulkActionsModel.addReviewers([accounts[1]]);
+ test('adds reviewers/cc only to changes that need it', async () => {
+ bulkActionsModel.addReviewers(
+ new Map([
+ [ReviewerState.REVIEWER, [accounts[0]]],
+ [ReviewerState.CC, [accounts[1]]],
+ ])
+ );
- // changes[0] is not updated since it already has accounts[1]
- // as a reviewer
+ // changes[0] is not updated since it already has the reviewer & CC
assert.isTrue(saveChangeReviewStub.calledOnce);
assert.sameDeepOrderedMembers(saveChangeReviewStub.firstCall.args, [
changes[1]._number,
'current',
{
reviewers: [
- {reviewer: accounts[1]._account_id, state: ReviewerState.REVIEWER},
+ {reviewer: accounts[0]._account_id, state: ReviewerState.REVIEWER},
+ {reviewer: accounts[1]._account_id, state: ReviewerState.CC},
],
},
]);