Merge "Fix current patchset not updated after Edit Publish"
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index 52e0b3f..d00fa40 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -20,6 +20,7 @@
import {GrButton} from '../../shared/gr-button/gr-button';
import {GrAccessSection} from '../gr-access-section/gr-access-section';
import {
+ AutocompleteCommitEvent,
AutocompleteQuery,
AutocompleteSuggestion,
} from '../../shared/gr-autocomplete/gr-autocomplete';
@@ -194,7 +195,7 @@
id="editInheritFromInput"
.text=${this.inheritFromFilter}
.query=${this.query}
- @commit=${(e: ValueChangedEvent) => {
+ @commit=${(e: AutocompleteCommitEvent) => {
this.handleUpdateInheritFrom(e);
}}
@bind-value-changed=${(e: ValueChangedEvent) => {
@@ -388,7 +389,7 @@
}
// private but used in test
- handleUpdateInheritFrom(e: ValueChangedEvent) {
+ handleUpdateInheritFrom(e: AutocompleteCommitEvent) {
this.inheritsFrom = {
...(this.inheritsFrom ?? {}),
id: e.detail.value as UrlEncodedRepoName,
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-abandon-flow/gr-change-list-bulk-abandon-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-abandon-flow/gr-change-list-bulk-abandon-flow.ts
index 11de37e..df63780 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-abandon-flow/gr-change-list-bulk-abandon-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-abandon-flow/gr-change-list-bulk-abandon-flow.ts
@@ -56,7 +56,7 @@
<dialog id="actionModal" tabindex="-1">
<gr-dialog
.disableCancel=${!this.isCancelEnabled()}
- .disabled=${!this.isConfirmEnabled()}
+ .disabled=${this.isDisabled()}
@confirm=${() => this.handleConfirm()}
@cancel=${() => this.handleClose()}
.cancelLabel=${'Close'}
@@ -104,13 +104,13 @@
);
}
- private isConfirmEnabled() {
+ private isDisabled() {
// Action is allowed if none of the changes have any bulk action performed
// on them. In case an error happens then we keep the button disabled.
for (const status of this.progress.values()) {
- if (status !== ProgressStatus.NOT_STARTED) return false;
+ if (status !== ProgressStatus.NOT_STARTED) return true;
}
- return true;
+ return false;
}
private isCancelEnabled() {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts
index a9b8028..db82523 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts
@@ -161,7 +161,9 @@
<dialog id="actionModal" tabindex="-1">
<gr-dialog
.disableCancel=${!this.isCancelEnabled()}
- .disabled=${!this.isConfirmEnabled()}
+ .disabled=${this.isDisabled(
+ triggerLabels.length + nonTriggerLabels.length
+ )}
?loading=${this.isLoading()}
.loadingLabel=${'Voting in progress...'}
@confirm=${() => this.handleConfirm()}
@@ -289,11 +291,12 @@
return getOverallStatus(this.progressByChange) === ProgressStatus.RUNNING;
}
- private isConfirmEnabled() {
+ private isDisabled(permittedLabelsCount: number) {
// Action is allowed if none of the changes have any bulk action performed
// on them. In case an error happens then we keep the button disabled.
- return (
- getOverallStatus(this.progressByChange) === ProgressStatus.NOT_STARTED
+ return !(
+ getOverallStatus(this.progressByChange) === ProgressStatus.NOT_STARTED &&
+ permittedLabelsCount > 0
);
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
index 654ed91..8a5bf47 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow_test.ts
@@ -307,17 +307,18 @@
);
// No common label with change1 so button is disabled
- change2.labels = {
+ const c2 = {...change2}; // create copy so other tests are not affected
+ c2.labels = {
x: {value: null} as LabelInfo,
y: {value: null} as LabelInfo,
z: {value: null} as LabelInfo,
};
- change2.submit_requirements = [
+ c2.submit_requirements = [
createSubmitRequirementResultInfo('label:x=MAX'),
createSubmitRequirementResultInfo('label:y=MAX'),
createSubmitRequirementResultInfo('label:z=MAX'),
];
- changes.push({...change2});
+ changes.push({...c2});
getChangesStub.restore();
getChangesStub.returns(Promise.resolve(changes));
model.sync(changes);
@@ -484,6 +485,45 @@
assert.equal(dispatchEventStub.lastCall.args[0].type, 'reload');
});
+ test('button is disabled if no votes are possible', async () => {
+ const c2 = {...change2}; // create copy so other tests are not affected
+ c2.labels = {
+ x: {value: null} as LabelInfo,
+ y: {value: null} as LabelInfo,
+ z: {value: null} as LabelInfo,
+ };
+ c2.submit_requirements = [
+ createSubmitRequirementResultInfo('label:x=MAX'),
+ createSubmitRequirementResultInfo('label:y=MAX'),
+ createSubmitRequirementResultInfo('label:z=MAX'),
+ ];
+
+ const changes: ChangeInfo[] = [change1, c2];
+ getChangesStub.returns(Promise.resolve(changes));
+
+ stubRestApi('saveChangeReview').callsFake(
+ (_changeNum, _patchNum, _review, errFn) =>
+ Promise.resolve(new Response()).then(res => {
+ errFn && errFn();
+ return res;
+ })
+ );
+
+ model.sync(changes);
+ await waitUntilObserved(
+ model.loadingState$,
+ state => state === LoadingState.LOADED
+ );
+ await selectChange(change1);
+ await selectChange(c2);
+ await element.updateComplete;
+
+ assert.isTrue(
+ queryAndAssert<GrButton>(query(element, 'gr-dialog'), '#confirm')
+ .disabled
+ );
+ });
+
test('closing dialog does not trigger reload if no request made', async () => {
const changes: ChangeInfo[] = [change1, change2];
getChangesStub.returns(Promise.resolve(changes));
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 5792230..f3be5c4 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -329,9 +329,9 @@
private computeIsExpandable() {
const hasSummary = !!this.result?.summary;
const hasMessage = !!this.result?.message;
- const hasLinks = (this.result?.links ?? []).length > 0;
+ const hasMultipleLinks = (this.result?.links ?? []).length > 1;
const hasPointers = (this.result?.codePointers ?? []).length > 0;
- return hasSummary && (hasMessage || hasLinks || hasPointers);
+ return hasSummary && (hasMessage || hasMultipleLinks || hasPointers);
}
override focus() {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
index 113470c..385bde7 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
@@ -117,6 +117,7 @@
aria-checked="false"
aria-label="Expand result row"
class="show-hide"
+ hidden
role="switch"
tabindex="0"
>
@@ -261,7 +262,6 @@
</h3>
<gr-result-row
class="FAKEErrorFinderFinderFinderFinderFinderFinderFinder"
- isexpandable
>
</gr-result-row>
<gr-result-row
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 314e126..6d8dc2b 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -7,6 +7,7 @@
import '../../shared/gr-icon/gr-icon';
import {ServerInfo} from '../../../types/common';
import {
+ AutocompleteCommitEvent,
AutocompleteQuery,
AutocompleteSuggestion,
GrAutocomplete,
@@ -227,7 +228,7 @@
.threshold=${this.threshold}
tab-complete
.verticalOffset=${30}
- @commit=${(e: Event) => {
+ @commit=${(e: AutocompleteCommitEvent) => {
this.handleInputCommit(e);
}}
@text-changed=${(e: CustomEvent) => {
@@ -285,7 +286,7 @@
return `${baseUrl}/user-search.html`;
}
- private handleInputCommit(e: Event) {
+ private handleInputCommit(e: AutocompleteCommitEvent) {
this.preventDefaultAndNavigateToInputVal(e);
}
@@ -295,7 +296,7 @@
* - e.target is the gr-autocomplete widget (#searchInput)
* - e.target is the input element wrapped within #searchInput
*/
- private preventDefaultAndNavigateToInputVal(e: Event) {
+ private preventDefaultAndNavigateToInputVal(e: AutocompleteCommitEvent) {
e.preventDefault();
if (!this.inputVal) return;
const trimmedInput = this.inputVal.trim();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
index 0509925..08f7c93 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
@@ -5,6 +5,7 @@
*/
import '../gr-autocomplete/gr-autocomplete';
import {
+ AutocompleteCommitEvent,
AutocompleteQuery,
GrAutocomplete,
} from '../gr-autocomplete/gr-autocomplete';
@@ -110,7 +111,7 @@
return this.input!.text;
}
- private handleInputCommit(e: CustomEvent) {
+ private handleInputCommit(e: AutocompleteCommitEvent) {
this.dispatchEvent(
new CustomEvent('add', {
detail: {value: e.detail.value},
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
index 65d8859..9034259 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
@@ -38,18 +38,17 @@
import {ReviewerState} from '../../../api/rest-api';
const VALID_EMAIL_ALERT = 'Please input a valid email.';
+const VALID_USER_GROUP_ALERT = 'Please input a valid user or group.';
declare global {
interface HTMLElementEventMap {
'accounts-changed': ValueChangedEvent<(AccountInfo | GroupInfo)[]>;
'pending-confirmation-changed': ValueChangedEvent<SuggestedReviewerGroupInfo | null>;
+ 'account-added': CustomEvent<{account: AccountInfo | GroupInfo}>;
}
interface HTMLElementTagNameMap {
'gr-account-list': GrAccountList;
}
- interface HTMLElementEventMap {
- 'account-added': CustomEvent<AccountInputDetail>;
- }
}
export interface AccountInputDetail {
account: AccountInput;
@@ -264,24 +263,28 @@
this.addAccountItem(item);
}
- addAccountItem(item: RawAccountInput) {
+ /**
+ * Check if account or group is valid and add it.
+ *
+ * @return true if account or group is added.
+ */
+ addAccountItem(item: RawAccountInput): boolean {
// Append new account or group to the accounts property. We add our own
// internal properties to the account/group here, so we clone the object
// to avoid cluttering up the shared change object.
- let account;
- let group;
+ let accountOrGroup: AccountInfo | GroupInfo | undefined;
let itemTypeAdded = 'unknown';
if (isAccountObject(item)) {
- account = {...item.account};
- this.accounts.push(account);
+ accountOrGroup = {...item.account};
+ this.accounts.push(accountOrGroup);
itemTypeAdded = 'account';
} else if (isSuggestedReviewerGroupInfo(item)) {
if (item.confirm) {
this.pendingConfirmation = item;
- return;
+ return false;
}
- group = {...item.group};
- this.accounts.push(group);
+ accountOrGroup = {...item.group};
+ this.accounts.push(accountOrGroup);
itemTypeAdded = 'group';
} else if (this.allowAnyInput) {
if (!item.includes('@')) {
@@ -291,13 +294,17 @@
fireAlert(this, VALID_EMAIL_ALERT);
return false;
} else {
- account = {email: item as EmailAddress};
- this.accounts.push(account);
+ accountOrGroup = {email: item as EmailAddress};
+ this.accounts.push(accountOrGroup);
itemTypeAdded = 'email';
}
}
+ if (!accountOrGroup) {
+ fireAlert(this, VALID_USER_GROUP_ALERT);
+ return false;
+ }
fire(this, 'accounts-changed', {value: this.accounts.slice()});
- fire(this, 'account-added', {account: (account ?? group)! as AccountInput});
+ fire(this, 'account-added', {account: accountOrGroup});
this.reporting.reportInteraction(`Add to ${this.id}`, {itemTypeAdded});
this.pendingConfirmation = null;
this.requestUpdate();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
index cc2723e..814b623 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
@@ -289,6 +289,15 @@
assert.isFalse(element.computeRemovable(newAccount));
});
+ test('addAccountItem with invalid item', () => {
+ const toastHandler = sinon.stub();
+ element.allowAnyInput = false;
+ element.addEventListener(EventType.SHOW_ALERT, toastHandler);
+ const result = element.addAccountItem('test');
+ assert.isFalse(result);
+ assert.isTrue(toastHandler.called);
+ });
+
test('submitEntryText', async () => {
element.allowAnyInput = true;
await element.updateComplete;
diff --git a/polygerrit-ui/app/utils/async-util.ts b/polygerrit-ui/app/utils/async-util.ts
index 905b16c..6ac3211 100644
--- a/polygerrit-ui/app/utils/async-util.ts
+++ b/polygerrit-ui/app/utils/async-util.ts
@@ -75,15 +75,18 @@
let callbackResult;
if (this.callback) callbackResult = this.callback();
if (callbackResult instanceof Promise) {
- callbackResult.finally(() =>
- this.resolvePromise!(ResolvedDelayedTaskStatus.CALLBACK_EXECUTED)
- );
+ callbackResult.finally(() => {
+ this.resolvePromise!(ResolvedDelayedTaskStatus.CALLBACK_EXECUTED);
+ });
} else {
this.resolvePromise!(ResolvedDelayedTaskStatus.CALLBACK_EXECUTED);
}
}
- constructor(private callback: () => void | Promise<void>, waitMs = 0) {
+ constructor(
+ private readonly callback: () => void | Promise<void>,
+ waitMs = 0
+ ) {
this.promise = new Promise(resolve => {
this.resolvePromise = resolve;
this.timerId = window.setTimeout(() => {