Add error handling for bulk vote flow
Screenshot: https://imgur.com/a/wYLUbMZ
Google-bug-id: b/216478680
Release-Notes: skip
Change-Id: I7eed4945eab17953bb3a96165da48acbed99dbab
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 eb2a71d..77c9382 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
@@ -24,6 +24,7 @@
import {getAppContext} from '../../../services/app-context';
import {fontStyles} from '../../../styles/gr-font-styles';
import {queryAndAssert} from '../../../utils/common-util';
+import '@polymer/iron-icon/iron-icon';
import {
LabelNameToValuesMap,
ReviewInput,
@@ -36,6 +37,7 @@
import '../../change/gr-label-score-row/gr-label-score-row';
import {getOverallStatus} from '../../../utils/bulk-flow-util';
import {allSettled} from '../../../utils/async-util';
+import {pluralize} from '../../../utils/string-util';
@customElement('gr-change-list-bulk-vote-flow')
export class GrChangeListBulkVoteFlow extends LitElement {
@@ -85,6 +87,20 @@
margin-bottom: var(--spacing-m);
font-weight: var(--font-weight-h2);
}
+ .error-container {
+ background-color: var(--red-50);
+ margin-top: var(--spacing-l);
+ }
+ .error-container iron-icon {
+ padding: 10px var(--spacing-xl);
+ color: var(--red-700);
+ --iron-icon-height: 20px;
+ --iron-icon-width: 20px;
+ }
+ .error-container span {
+ position: relative;
+ top: 1px;
+ }
`,
];
}
@@ -145,13 +161,33 @@
'Trigger Votes',
permittedLabels
)}
+ ${this.renderErrors()}
</div>
- <!-- TODO: Add error handling status if something fails -->
</gr-dialog>
</gr-overlay>
`;
}
+ private renderErrors() {
+ if (getOverallStatus(this.progressByChange) !== ProgressStatus.FAILED) {
+ return nothing;
+ }
+ return html`
+ <div class="error-container">
+ <iron-icon icon="gr-icons:error"></iron-icon>
+ <span>
+ <!-- prettier-ignore -->
+ Failed to vote on ${pluralize(
+ Array.from(this.progressByChange.values()).filter(
+ status => status === ProgressStatus.FAILED
+ ).length,
+ 'change'
+ )}
+ </span>
+ </div>
+ `;
+ }
+
private renderLabels(
labels: Label[],
heading: string,
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 1372eb8..e2cbaf0 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
@@ -174,6 +174,75 @@
</gr-overlay> `);
});
+ test('renders with errors', async () => {
+ const changes: ChangeInfo[] = [change1];
+ getChangesStub.returns(Promise.resolve(changes));
+ model.sync(changes);
+ await waitUntilObserved(
+ model.loadingState$,
+ state => state === LoadingState.LOADED
+ );
+ stubRestApi('saveChangeReview').callsFake(
+ (_changeNum, _patchNum, _review, errFn) =>
+ Promise.resolve(new Response()).then(res => {
+ errFn && errFn();
+ return res;
+ })
+ );
+ await selectChange(change1);
+ await element.updateComplete;
+
+ queryAndAssert<GrButton>(query(element, 'gr-dialog'), '#confirm').click();
+
+ await waitUntil(
+ () =>
+ element.progressByChange.get(1 as NumericChangeId) ===
+ ProgressStatus.FAILED
+ );
+
+ expect(element).shadowDom.to.equal(/* HTML */ `<gr-button
+ aria-disabled="false"
+ flatten=""
+ id="voteFlowButton"
+ role="button"
+ tabindex="0"
+ >
+ Vote
+ </gr-button>
+ <gr-overlay
+ aria-hidden="true"
+ id="actionOverlay"
+ style="outline: none; display: none;"
+ tabindex="-1"
+ with-backdrop=""
+ >
+ <gr-dialog role="dialog">
+ <div slot="header">
+ <span class="main-heading"> Vote on selected changes </span>
+ </div>
+ <div slot="main">
+ <div class="newSubmitRequirements scoresTable">
+ <h3 class="heading-4 vote-type">Submit requirements votes</h3>
+ <gr-label-score-row name="A"> </gr-label-score-row>
+ <gr-label-score-row name="B"> </gr-label-score-row>
+ <gr-label-score-row name="C"> </gr-label-score-row>
+ <gr-label-score-row name="change1OnlyLabelD">
+ </gr-label-score-row>
+ </div>
+ <div class="newSubmitRequirements scoresTable">
+ <h3 class="heading-4 vote-type">Trigger Votes</h3>
+ <gr-label-score-row name="change1OnlyTriggerLabelE">
+ </gr-label-score-row>
+ </div>
+ <div class="error-container">
+ <iron-icon icon="gr-icons:error"> </iron-icon>
+ <span> Failed to vote on 1 change </span>
+ </div>
+ </div>
+ </gr-dialog>
+ </gr-overlay> `);
+ });
+
test('button state updates as changes are updated', async () => {
const changes: ChangeInfo[] = [change1];
getChangesStub.returns(Promise.resolve(changes));