Submit requirements - new votes in hovercard
Screenshot: https://imgur.com/a/eshQrqd
Change-Id: I92cc5b4658c2b512682cf7172a74aa9c9c8f296f
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
index 61785fc..e3edb5e 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
@@ -31,7 +31,6 @@
ApprovalInfo,
Reviewers,
AccountId,
- DetailedLabelInfo,
EmailAddress,
AccountDetailInfo,
isDetailedLabelInfo,
@@ -158,24 +157,19 @@
if (!change.labels) {
return NaN;
}
- const detailedLabel = change.labels[label] as DetailedLabelInfo;
- if (!detailedLabel.all) {
+ const detailedLabel = change.labels[label];
+ if (!isDetailedLabelInfo(detailedLabel) || !detailedLabel.all) {
return NaN;
}
- const detailed = detailedLabel.all
- .filter(
- (approval: ApprovalInfo) =>
- reviewer._account_id === approval._account_id
- )
- .pop();
- if (!detailed) {
+ const approvalInfo = getApprovalInfo(detailedLabel, reviewer);
+ if (!approvalInfo) {
return NaN;
}
- if (hasOwnProperty(detailed, 'permitted_voting_range')) {
- if (!detailed.permitted_voting_range) return NaN;
- return detailed.permitted_voting_range.max;
- } else if (hasOwnProperty(detailed, 'value')) {
- // If preset, user can vote on the label.
+ if (hasOwnProperty(approvalInfo, 'permitted_voting_range')) {
+ if (!approvalInfo.permitted_voting_range) return NaN;
+ return approvalInfo.permitted_voting_range.max;
+ } else if (hasOwnProperty(approvalInfo, 'value')) {
+ // If present, user can vote on the label.
return 0;
}
return NaN;
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts
index d3f23ad..bf15bb5 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.ts
@@ -26,6 +26,7 @@
import {
createAccountDetailWithId,
createChange,
+ createDetailedLabelInfo,
} from '../../../test/test-data-generators';
import {tap} from '@polymer/iron-test-helpers/mock-interactions';
import {GrButton} from '../../shared/gr-button/gr-button';
@@ -423,6 +424,7 @@
...createChange(),
labels: {
Foo: {
+ ...createDetailedLabelInfo(),
all: [
{
_account_id: 7 as AccountId,
@@ -431,6 +433,7 @@
],
},
Bar: {
+ ...createDetailedLabelInfo(),
all: [
{
...createAccountDetailWithId(1),
@@ -443,6 +446,7 @@
],
},
FooBar: {
+ ...createDetailedLabelInfo(),
all: [{_account_id: 7 as AccountId, value: 0}],
},
},
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
index 3c9f54c..d90d173 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
@@ -120,6 +120,9 @@
margin-top: var(--spacing-m);
padding: var(--spacing-m) var(--spacing-xl) 0;
}
+ .status-placeholder {
+ visibility: hidden;
+ }
`,
];
}
@@ -155,31 +158,27 @@
private renderLabelSection() {
const labels = this.computeLabels();
+ const showLabelName = labels.length >= 2;
return html` <div class="section">
- ${labels.map(l => this.renderLabel(l))}
+ <div class="sectionIcon"></div>
+ <div class="row">
+ <!-- Hidden placeholder to be aligned as Status line above -->
+ <div class="title status-placeholder">Status</div>
+ <div>${labels.map(l => this.renderLabel(l, showLabelName))}</div>
+ </div>
</div>`;
}
- private renderLabel(label: Label) {
+ private renderLabel(label: Label, showLabelName: boolean) {
return html`
- <section class="label">
- <div class="label-title">
- <gr-limited-text
- class="name"
- limit="25"
- text="${label.labelName}"
- ></gr-limited-text>
- </div>
- <div class="label-value">
- <gr-label-info
- .change=${this.change}
- .account=${this.account}
- .mutable=${this.mutable}
- .label="${label.labelName}"
- .labelInfo="${label.labelInfo}"
- ></gr-label-info>
- </div>
- </section>
+ ${showLabelName ? html`<div>${label.labelName} votes</div>` : ''}
+ <gr-label-info
+ .change=${this.change}
+ .account=${this.account}
+ .mutable=${this.mutable}
+ .label="${label.labelName}"
+ .labelInfo="${label.labelInfo}"
+ ></gr-label-info>
`;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index 2df2ccb..f60b9b6 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -17,8 +17,10 @@
import '../../../styles/gr-font-styles';
import '../../../styles/gr-voting-styles';
import '../../../styles/shared-styles';
+import '../gr-vote-chip/gr-vote-chip';
import '../gr-account-label/gr-account-label';
import '../gr-account-link/gr-account-link';
+import '../gr-account-chip/gr-account-chip';
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
import '../gr-label/gr-label';
@@ -36,7 +38,12 @@
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators';
import {GrButton} from '../gr-button/gr-button';
-import {getVotingRangeOrDefault} from '../../../utils/label-util';
+import {
+ canVote,
+ getApprovalInfo,
+ getVotingRangeOrDefault,
+ hasNeutralStatus,
+} from '../../../utils/label-util';
import {appContext} from '../../../services/app-context';
import {ParsedChangeInfo} from '../../../types/types';
import {fontStyles} from '../../../styles/gr-font-styles';
@@ -44,6 +51,7 @@
import {votingStyles} from '../../../styles/gr-voting-styles';
import {ifDefined} from 'lit/directives/if-defined';
import {fireReload} from '../../../utils/event-util';
+import {KnownExperimentId} from '../../../services/flags/flags';
declare global {
interface HTMLElementTagNameMap {
@@ -160,28 +168,83 @@
.labelValueContainer:not(:first-of-type) td {
padding-top: var(--spacing-s);
}
+ .reviewer-row {
+ padding-top: var(--spacing-s);
+ }
+ .reviewer-row:first-of-type {
+ padding-top: 0;
+ }
+ .reviewer-row gr-account-chip,
+ .reviewer-row gr-tooltip-content {
+ display: inline-block;
+ vertical-align: top;
+ }
+ .reviewer-row .no-votes {
+ color: var(--deemphasized-text-color);
+ margin-left: var(--spacing-xs);
+ }
`,
];
}
+ private readonly flagsService = appContext.flagsService;
+
override render() {
+ if (this.flagsService.isEnabled(KnownExperimentId.SUBMIT_REQUIREMENTS_UI)) {
+ return this.renderNewSubmitRequirements();
+ } else {
+ return this.renderOldSubmitRequirements();
+ }
+ }
+
+ private renderNewSubmitRequirements() {
+ const labelInfo = this.labelInfo;
+ if (!labelInfo) return;
+ const reviewers = (this.change?.reviewers['REVIEWER'] ?? []).filter(
+ reviewer => canVote(labelInfo, reviewer)
+ );
+ return html`<div>
+ ${reviewers.map(reviewer => this.renderReviewerVote(reviewer))}
+ </div>`;
+ }
+
+ private renderOldSubmitRequirements() {
+ const labelInfo = this.labelInfo;
return html` <p
class="placeholder ${this.computeShowPlaceholder(
- this.labelInfo,
+ labelInfo,
this.change?.labels
)}"
>
No votes
</p>
<table>
- ${this.mapLabelInfo(
- this.labelInfo,
- this.account,
- this.change?.labels
- ).map(mappedLabel => this.renderLabel(mappedLabel))}
+ ${this.mapLabelInfo(labelInfo, this.account, this.change?.labels).map(
+ mappedLabel => this.renderLabel(mappedLabel)
+ )}
</table>`;
}
+ renderReviewerVote(reviewer: AccountInfo) {
+ const labelInfo = this.labelInfo;
+ if (!labelInfo || !isDetailedLabelInfo(labelInfo)) return;
+ const approvalInfo = getApprovalInfo(labelInfo, reviewer);
+ const noVoteYet =
+ !approvalInfo || hasNeutralStatus(labelInfo, approvalInfo);
+ return html`<div class="reviewer-row">
+ <gr-account-chip .account="${reviewer}" .change="${this.change}">
+ <gr-vote-chip
+ slot="vote-chip"
+ .vote="${approvalInfo}"
+ .label="${labelInfo}"
+ ></gr-vote-chip
+ ></gr-account-chip>
+ ${noVoteYet
+ ? html`<span class="no-votes">No votes</span>`
+ : html`${this.renderRemoveVote(reviewer)}`}
+ </div>`;
+ }
+
renderLabel(mappedLabel: FormattedLabel) {
const {labelInfo, change} = this;
return html` <tr class="labelValueContainer">
@@ -201,26 +264,28 @@
.change="${change}"
></gr-account-link>
</td>
- <td>
- <gr-tooltip-content has-tooltip title="Remove vote">
- <gr-button
- link
- aria-label="Remove vote"
- @click="${this.onDeleteVote}"
- data-account-id="${ifDefined(mappedLabel.account._account_id)}"
- class="deleteBtn ${this.computeDeleteClass(
- mappedLabel.account,
- this.mutable,
- change
- )}"
- >
- <iron-icon icon="gr-icons:delete"></iron-icon>
- </gr-button>
- </gr-tooltip-content>
- </td>
+ <td>${this.renderRemoveVote(mappedLabel.account)}</td>
</tr>`;
}
+ private renderRemoveVote(reviewer: AccountInfo) {
+ return html`<gr-tooltip-content has-tooltip title="Remove vote">
+ <gr-button
+ link
+ aria-label="Remove vote"
+ @click="${this.onDeleteVote}"
+ data-account-id="${ifDefined(reviewer._account_id)}"
+ class="deleteBtn ${this.computeDeleteClass(
+ reviewer,
+ this.mutable,
+ this.change
+ )}"
+ >
+ <iron-icon icon="gr-icons:delete"></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>`;
+ }
+
/**
* This method also listens on change.labels.*,
* to trigger computation when a label is removed from the change.
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index db84043..351cc13 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -102,6 +102,7 @@
import {ChangeMessage} from '../elements/change/gr-message/gr-message';
import {GenerateUrlEditViewParameters} from '../elements/core/gr-navigation/gr-navigation';
import {
+ DetailedLabelInfo,
SubmitRequirementExpressionInfo,
SubmitRequirementResultInfo,
SubmitRequirementStatus,
@@ -706,3 +707,13 @@
message: 'This is the test message.',
};
}
+
+export function createDetailedLabelInfo(): DetailedLabelInfo {
+ return {
+ values: {
+ ' 0': 'No score',
+ '+1': 'Style Verified',
+ '-1': 'Wrong Style or Formatting',
+ },
+ };
+}
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 78c4132..960b02f 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -134,6 +134,16 @@
return label.all?.filter(x => x._account_id === account._account_id)[0];
}
+export function canVote(label: DetailedLabelInfo, account: AccountInfo) {
+ const approvalInfo = getApprovalInfo(label, account);
+ if (!approvalInfo) return false;
+ if (approvalInfo.permitted_voting_range) {
+ return approvalInfo.permitted_voting_range.max > 0;
+ }
+ // If value present, user can vote on the label.
+ return approvalInfo.value !== undefined;
+}
+
export function getAllUniqueApprovals(labelInfo?: LabelInfo) {
if (!labelInfo || !isDetailedLabelInfo(labelInfo)) return [];
const uniqueApprovals = (labelInfo.all ?? [])
@@ -148,8 +158,7 @@
export function hasVotes(labelInfo: LabelInfo): boolean {
if (isDetailedLabelInfo(labelInfo)) {
return (labelInfo.all ?? []).some(
- approval =>
- getLabelStatus(labelInfo, approval.value) !== LabelStatus.NEUTRAL
+ approval => !hasNeutralStatus(labelInfo, approval)
);
}
if (isQuickLabelInfo(labelInfo)) {