Add support to cherry-pick using secondary email in UI
Add an email drop down to the cherry-pick dialog box for single change
cherry-picks so that users can select one of their secondary emails to
be used as the committer email in the new cherry-picked patch-set. This
drop-down is visible only when user has more than one email registered
on their account.
Release-Notes: UI supports cherry-pick using a registered secondary email
Change-Id: If8f52bca76320af8da79a5855daf3ecb53779ea6
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 39929e1..3f50afe 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -280,9 +280,14 @@
** [[cherry-pick]]`Cherry-Pick`:
+
-Allows to cherry-pick the change to another branch. The destination
-branch can be selected from a dialog. Cherry-picking a change creates a
-new open change on the selected destination branch.
+Allows to cherry-pick the change to another branch. The destination branch
+can be selected from a dialog. Cherry-picking a change creates a new open
+change on the selected destination branch. 'Cherry-pick committer email'
+drop-down is visible for single change cherry-picks when user has more than
+one email registered to their account. It is possible to select any of the
+registered emails to be used as the cherry-pick committer email. It defaults
+to source commit's committer email if it is a registered email of the calling
+user, else defaults to calling user's preferred email.
+
It is also possible to cherry-pick a change to the same branch. This is
effectively the same as rebasing it to the current tip of the
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 0be80bb..8f835de 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -1610,6 +1610,7 @@
base: el.baseCommit ? el.baseCommit : null,
message: el.message,
allow_conflicts: conflicts,
+ committer_email: el.committerEmail ? el.committerEmail : null,
}
);
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index 631d946..15500de 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -1000,6 +1000,7 @@
base: null,
message: 'foo message',
allow_conflicts: false,
+ committer_email: null,
},
]);
});
@@ -1048,6 +1049,7 @@
base: null,
message: 'foo message',
allow_conflicts: true,
+ committer_email: null,
},
]);
});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index b66b2cf..df3db29 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -18,6 +18,8 @@
ChangeInfoId,
TopicName,
ChangeActionDialog,
+ EmailInfo,
+ GitPersonInfo,
} from '../../../types/common';
import {customElement, property, query, state} from 'lit/decorators.js';
import {
@@ -30,6 +32,7 @@
ChangeStatus,
ProgressStatus,
} from '../../../constants/constants';
+import {subscribe} from '../../lit/subscription-controller';
import {fire, fireNoBubble} from '../../../utils/event-util';
import {css, html, LitElement, PropertyValues} from 'lit';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -43,6 +46,7 @@
import {ParsedChangeInfo} from '../../../types/types';
import {formStyles} from '../../../styles/form-styles';
import {branchName} from '../../../utils/patch-set-util';
+import {changeModelToken} from '../../../models/change/change-model';
const SUGGESTIONS_LIMIT = 15;
const CHANGE_SUBJECT_LIMIT = 50;
@@ -127,13 +131,24 @@
@state()
private invalidBranch = false;
+ @state()
+ emails: EmailInfo[] = [];
+
@query('#branchInput')
branchInput!: GrTypedAutocomplete<BranchName>;
+ @state()
+ committerEmail?: string;
+
+ @state()
+ latestCommitter?: GitPersonInfo;
+
private selectedChangeIds = new Set<ChangeInfoId>();
private readonly restApiService = getAppContext().restApiService;
+ private readonly getChangeModel = resolve(this, changeModelToken);
+
private readonly reporting = getAppContext().reportingService;
private readonly getNavigation = resolve(this, navigationToken);
@@ -142,6 +157,16 @@
super();
this.statuses = {};
this.query = (text: string) => this.getProjectBranchesSuggestions(text);
+ subscribe(
+ this,
+ () => this.getChangeModel().latestCommitter$,
+ x => (this.latestCommitter = x)
+ );
+ }
+
+ override connectedCallback() {
+ super.connectedCallback();
+ this.loadEmails();
}
override willUpdate(changedProperties: PropertyValues) {
@@ -341,6 +366,17 @@
@bind-value-changed=${(e: BindValueChangeEvent) =>
(this.message = e.detail.value ?? '')}
></iron-autogrow-textarea>
+ ${when(
+ this.canShowEmailDropdown(),
+ () => html`<div id="cherryPickEmailDropdown">Cherry Pick Committer Email
+ <gr-dropdown-list
+ .items=${this.getEmailDropdownItems()}
+ .value=${this.committerEmail}
+ @value-change=${this.setCommitterEmail}
+ >
+ </gr-dropdown-list>
+ <span></div>`
+ )}
`;
}
@@ -580,6 +616,7 @@
topic,
allow_conflicts: true,
allow_empty: true,
+ committer_email: this.committerEmail ? this.committerEmail : null,
};
const handleError = (response?: Response | null) => {
this.handleCherryPickFailed(change, response);
@@ -654,4 +691,40 @@
return branches;
});
}
+
+ async loadEmails() {
+ let accountEmails: EmailInfo[] = [];
+ await this.restApiService.getAccountEmails().then(emails => {
+ accountEmails = emails ?? [];
+ });
+ let selectedEmail: string | undefined;
+ accountEmails.forEach(e => {
+ if (e.preferred) {
+ selectedEmail = e.email;
+ }
+ });
+
+ if (accountEmails.some(e => e.email === this.latestCommitter?.email)) {
+ selectedEmail = this.latestCommitter?.email;
+ }
+ this.emails = accountEmails;
+ this.committerEmail = selectedEmail;
+ }
+
+ private canShowEmailDropdown() {
+ return this.emails.length > 1;
+ }
+
+ private getEmailDropdownItems() {
+ return this.emails.map(e => {
+ return {
+ text: e.email,
+ value: e.email,
+ };
+ });
+ }
+
+ private setCommitterEmail(e: CustomEvent<{value: string}>) {
+ this.committerEmail = e.detail.value;
+ }
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
index dc8dba9..83e6f9e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
@@ -5,7 +5,12 @@
*/
import '../../../test/common-test-setup';
import './gr-confirm-cherrypick-dialog';
-import {queryAll, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {
+ query,
+ queryAll,
+ queryAndAssert,
+ stubRestApi,
+} from '../../../test/test-utils';
import {GrConfirmCherrypickDialog} from './gr-confirm-cherrypick-dialog';
import {
BranchName,
@@ -24,11 +29,53 @@
import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
import {ProgressStatus} from '../../../constants/constants';
import {fixture, html, assert} from '@open-wc/testing';
+import {GrDropdownList} from '../../shared/gr-dropdown-list/gr-dropdown-list';
const CHERRY_PICK_TYPES = {
SINGLE_CHANGE: 1,
TOPIC: 2,
};
+
+const changes: ChangeInfo[] = [
+ {
+ ...createChange(),
+ id: '1234' as ChangeInfoId,
+ change_id: '12345678901234' as ChangeId,
+ topic: 'T' as TopicName,
+ subject: 'random',
+ project: 'A' as RepoName,
+ _number: 1 as NumericChangeId,
+ revisions: {
+ a: createRevision(),
+ },
+ current_revision: 'a' as CommitId,
+ },
+ {
+ ...createChange(),
+ id: '5678' as ChangeInfoId,
+ change_id: '23456' as ChangeId,
+ topic: 'T' as TopicName,
+ subject: 'a'.repeat(100),
+ project: 'B' as RepoName,
+ _number: 2 as NumericChangeId,
+ revisions: {
+ a: createRevision(),
+ },
+ current_revision: 'a' as CommitId,
+ },
+];
+
+const emails = [
+ {
+ email: 'primary@email.com',
+ preferred: true,
+ },
+ {
+ email: 'secondary@email.com',
+ preferred: false,
+ },
+];
+
suite('gr-confirm-cherrypick-dialog tests', () => {
let element: GrConfirmCherrypickDialog;
@@ -149,34 +196,6 @@
});
suite('cherry pick topic', () => {
- const changes: ChangeInfo[] = [
- {
- ...createChange(),
- id: '1234' as ChangeInfoId,
- change_id: '12345678901234' as ChangeId,
- topic: 'T' as TopicName,
- subject: 'random',
- project: 'A' as RepoName,
- _number: 1 as NumericChangeId,
- revisions: {
- a: createRevision(),
- },
- current_revision: 'a' as CommitId,
- },
- {
- ...createChange(),
- id: '5678' as ChangeInfoId,
- change_id: '23456' as ChangeId,
- topic: 'T' as TopicName,
- subject: 'a'.repeat(100),
- project: 'B' as RepoName,
- _number: 2 as NumericChangeId,
- revisions: {
- a: createRevision(),
- },
- current_revision: 'a' as CommitId,
- },
- ];
setup(async () => {
element.updateChanges(changes);
element.cherryPickType = CHERRY_PICK_TYPES.TOPIC;
@@ -290,4 +309,36 @@
assert.equal(branches.length, 1);
assert.equal(branches[0].name, 'test-branch');
});
+
+ suite('cherry pick single change with committer email', () => {
+ test('hide email dropdown when user has one email', async () => {
+ element.emails = emails.slice(0, 1);
+ await element.updateComplete;
+ assert.notExists(query(element, '#cherryPickEmailDropdown'));
+ });
+
+ test('show email dropdown when user has more than one email', async () => {
+ element.emails = emails;
+ await element.updateComplete;
+ const cherryPickEmailDropdown = queryAndAssert(
+ element,
+ '#cherryPickEmailDropdown'
+ );
+ assert.dom.equal(
+ cherryPickEmailDropdown,
+ `<div id="cherryPickEmailDropdown">Cherry Pick Committer Email
+ <gr-dropdown-list></gr-dropdown-list>
+ <span></span>
+ </div>`
+ );
+ const emailDropdown = queryAndAssert<GrDropdownList>(
+ cherryPickEmailDropdown,
+ 'gr-dropdown-list'
+ );
+ assert.deepEqual(
+ emailDropdown.items?.map(e => e.value),
+ emails.map(e => e.email)
+ );
+ });
+ });
});
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index e6f8512..dedbe9d 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -255,6 +255,11 @@
change => change?.revisions[change.current_revision]?.uploader
);
+ public readonly latestCommitter$ = select(
+ this.change$,
+ change => change?.revisions[change.current_revision]?.commit?.committer
+ );
+
/**
* Emits the current patchset number. If the route does not define the current
* patchset num, then this selector waits for the change to be defined and