summaryrefslogtreecommitdiffstats
path: root/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
diff options
context:
space:
mode:
Diffstat (limited to 'polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts')
-rw-r--r--polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts246
1 files changed, 175 insertions, 71 deletions
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 002ac5668e..a3ef937da0 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
@@ -19,18 +19,30 @@ import '../../shared/gr-label-info/gr-label-info';
import {customElement, property} from 'lit/decorators';
import {
AccountInfo,
+ ChangeStatus,
+ isDetailedLabelInfo,
SubmitRequirementExpressionInfo,
SubmitRequirementResultInfo,
+ SubmitRequirementStatus,
} from '../../../api/rest-api';
import {
+ canVote,
extractAssociatedLabels,
+ getApprovalInfo,
+ hasVotes,
iconForStatus,
} from '../../../utils/label-util';
import {ParsedChangeInfo} from '../../../types/types';
-import {Label} from '../gr-change-requirements/gr-change-requirements';
import {css, html, LitElement} from 'lit';
import {HovercardMixin} from '../../../mixins/hovercard-mixin/hovercard-mixin';
import {fontStyles} from '../../../styles/gr-font-styles';
+import {DraftsAction} from '../../../constants/constants';
+import {ReviewInput} from '../../../types/common';
+import {getAppContext} from '../../../services/app-context';
+import {assertIsDefined} from '../../../utils/common-util';
+import {CURRENT} from '../../../utils/patch-set-util';
+import {fireReload} from '../../../utils/event-util';
+import {submitRequirementsStyles} from '../../../styles/gr-submit-requirements-styles';
// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
const base = HovercardMixin(LitElement);
@@ -52,9 +64,12 @@ export class GrSubmitRequirementHovercard extends base {
@property({type: Boolean})
expanded = false;
+ private readonly restApiService = getAppContext().restApiService;
+
static override get styles() {
return [
fontStyles,
+ submitRequirementsStyles,
base.styles || [],
css`
#container {
@@ -97,28 +112,28 @@ export class GrSubmitRequirementHovercard extends base {
width: 20px;
height: 20px;
}
- .condition {
+ .section.condition > .sectionContent {
background-color: var(--gray-background);
padding: var(--spacing-m);
flex-grow: 1;
}
+ .button ~ .condition {
+ margin-top: var(--spacing-m);
+ }
.expression {
color: var(--gray-foreground);
}
- iron-icon.check {
- color: var(--success-foreground);
- }
- iron-icon.close {
- color: var(--warning-foreground);
- }
- .showConditions iron-icon {
+ .button iron-icon {
color: inherit;
}
- div.showConditions {
+ div.button {
border-top: 1px solid var(--border-color);
margin-top: var(--spacing-m);
padding: var(--spacing-m) var(--spacing-xl) 0;
}
+ .section.description > .sectionContent {
+ white-space: pre-wrap;
+ }
`,
];
}
@@ -129,7 +144,7 @@ export class GrSubmitRequirementHovercard extends base {
return html` <div id="container" role="tooltip" tabindex="-1">
<div class="section">
<div class="sectionIcon">
- <iron-icon class="${icon}" icon="gr-icons:${icon}"></iron-icon>
+ <iron-icon class=${icon} icon="gr-icons:${icon}"></iron-icon>
</div>
<div class="sectionContent">
<h3 class="name heading-3">
@@ -148,12 +163,49 @@ export class GrSubmitRequirementHovercard extends base {
</div>
</div>
</div>
- ${this.renderLabelSection()} ${this.renderConditionSection()}
+ ${this.renderLabelSection()}${this.renderDescription()}
+ ${this.renderShowHideConditionButton()}${this.renderConditionSection()}
+ ${this.renderVotingButtons()}
+ </div>`;
+ }
+
+ private renderDescription() {
+ let description = this.requirement?.description;
+ if (this.requirement?.status === SubmitRequirementStatus.ERROR) {
+ const submitRecord = this.change?.submit_records?.filter(
+ record => record.rule_name === this.requirement?.name
+ );
+ if (submitRecord?.length === 1 && submitRecord[0].error_message) {
+ description = submitRecord[0].error_message;
+ }
+ }
+ if (!description) return;
+ return html`<div class="section description">
+ <div class="sectionIcon">
+ <iron-icon icon="gr-icons:description"></iron-icon>
+ </div>
+ <div class="sectionContent">${description}</div>
</div>`;
}
private renderLabelSection() {
- const labels = this.computeLabels();
+ if (!this.requirement) return;
+ const requirementLabels = extractAssociatedLabels(this.requirement);
+ const allLabels = this.change?.labels ?? {};
+ const labels: string[] = [];
+ for (const label of Object.keys(allLabels)) {
+ if (requirementLabels.includes(label)) {
+ const labelInfo = allLabels[label];
+ const canSomeoneVote = (this.change?.reviewers['REVIEWER'] ?? []).some(
+ reviewer => canVote(labelInfo, reviewer)
+ );
+ if (hasVotes(labelInfo) || canSomeoneVote) {
+ labels.push(label);
+ }
+ }
+ }
+
+ if (labels.length === 0) return;
const showLabelName = labels.length >= 2;
return html` <div class="section">
<div class="sectionIcon"></div>
@@ -163,84 +215,136 @@ export class GrSubmitRequirementHovercard extends base {
</div>`;
}
- private renderLabel(label: Label, showLabelName: boolean) {
+ private renderLabel(labelName: string, showLabelName: boolean) {
+ const labels = this.change?.labels ?? {};
return html`
- ${showLabelName ? html`<div>${label.labelName} votes</div>` : ''}
+ ${showLabelName ? html`<div>${labelName} votes</div>` : ''}
<gr-label-info
.change=${this.change}
.account=${this.account}
.mutable=${this.mutable}
- .label="${label.labelName}"
- .labelInfo="${label.labelInfo}"
+ .label=${labelName}
+ .labelInfo=${labels[labelName]}
></gr-label-info>
`;
}
- private renderConditionSection() {
- if (!this.expanded) {
- return html` <div class="showConditions">
- <gr-button
- link=""
- class="showConditions"
- @click="${(_: MouseEvent) => this.handleShowConditions()}"
- >
- View condition
- <iron-icon icon="gr-icons:expand-more"></iron-icon
- ></gr-button>
- </div>`;
- } else {
- return html`
- <div class="section">
- <div class="sectionIcon">
- <iron-icon icon="gr-icons:description"></iron-icon>
- </div>
- <div class="sectionContent">${this.requirement?.description}</div>
- </div>
- ${this.renderCondition(
- 'Blocking condition',
- this.requirement?.submittability_expression_result
- )}
- ${this.renderCondition(
- 'Application condition',
- this.requirement?.applicability_expression_result
- )}
- ${this.renderCondition(
- 'Override condition',
- this.requirement?.override_expression_result
- )}
- `;
- }
+ private renderShowHideConditionButton() {
+ const buttonText = this.expanded ? 'Hide conditions' : 'View conditions';
+ const icon = this.expanded ? 'expand-less' : 'expand-more';
+
+ return html` <div class="button">
+ <gr-button
+ link=""
+ id="toggleConditionsButton"
+ @click=${(_: MouseEvent) => this.toggleConditionsVisibility()}
+ >
+ ${buttonText}
+ <iron-icon icon="gr-icons:${icon}"></iron-icon
+ ></gr-button>
+ </div>`;
}
- private computeLabels() {
- if (!this.requirement) return [];
- const requirementLabels = extractAssociatedLabels(this.requirement);
+ private renderVotingButtons() {
+ if (!this.requirement) return;
+ if (!this.account) return;
+ if (this.change?.status === ChangeStatus.MERGED) return;
+
+ const submittabilityLabels = extractAssociatedLabels(
+ this.requirement,
+ 'onlySubmittability'
+ );
+ const submittabilityVotes = submittabilityLabels.map(labelName =>
+ this.renderLabelVote(labelName, 'submittability')
+ );
+
+ const overrideLabels = extractAssociatedLabels(
+ this.requirement,
+ 'onlyOverride'
+ );
+ const overrideVotes = overrideLabels.map(labelName =>
+ this.renderLabelVote(labelName, 'override')
+ );
+
+ return submittabilityVotes.concat(overrideVotes);
+ }
+
+ private renderLabelVote(
+ labelName: string,
+ type: 'override' | 'submittability'
+ ) {
const labels = this.change?.labels ?? {};
+ const labelInfo = labels[labelName];
+ if (!labelInfo || !isDetailedLabelInfo(labelInfo)) return;
+ if (!this.account || !canVote(labelInfo, this.account)) return;
- const allLabels: Label[] = [];
+ const approvalInfo = getApprovalInfo(labelInfo, this.account);
+ const maxVote = approvalInfo?.permitted_voting_range?.max;
+ if (!maxVote || maxVote <= 0) return;
+ if (approvalInfo?.value === maxVote) return; // Already voted maxVote
+ return html` <div class="button quickApprove">
+ <gr-button
+ link=""
+ @click=${(_: MouseEvent) => this.quickApprove(labelName, maxVote)}
+ >
+ ${this.computeVoteButtonName(labelName, maxVote, type)}
+ </gr-button>
+ </div>`;
+ }
- for (const label of Object.keys(labels)) {
- if (requirementLabels.includes(label)) {
- allLabels.push({
- labelName: label,
- icon: '',
- style: '',
- labelInfo: labels[label],
- });
- }
+ private computeVoteButtonName(
+ labelName: string,
+ maxVote: number,
+ type: 'override' | 'submittability'
+ ) {
+ if (type === 'override') {
+ return `Override (${labelName})`;
+ } else {
+ return `Vote ${labelName} +${maxVote}`;
}
- return allLabels;
+ }
+
+ private quickApprove(label: string, score: number) {
+ assertIsDefined(this.change, 'change');
+ const review: ReviewInput = {
+ drafts: DraftsAction.PUBLISH_ALL_REVISIONS,
+ labels: {
+ [label]: score,
+ },
+ };
+ return this.restApiService
+ .saveChangeReview(this.change._number, CURRENT, review)
+ .then(() => {
+ fireReload(this, true);
+ });
+ }
+
+ private renderConditionSection() {
+ if (!this.expanded) return;
+ return html`
+ ${this.renderCondition(
+ 'Submit condition',
+ this.requirement?.submittability_expression_result
+ )}
+ ${this.renderCondition(
+ 'Application condition',
+ this.requirement?.applicability_expression_result
+ )}
+ ${this.renderCondition(
+ 'Override condition',
+ this.requirement?.override_expression_result
+ )}
+ `;
}
private renderCondition(
name: string,
expression?: SubmitRequirementExpressionInfo
) {
- if (!expression) return '';
+ if (!expression?.expression) return '';
return html`
- <div class="section">
- <div class="sectionIcon"></div>
- <div class="sectionContent condition">
+ <div class="section condition">
+ <div class="sectionContent">
${name}:<br />
<span class="expression"> ${expression.expression} </span>
</div>
@@ -248,8 +352,8 @@ export class GrSubmitRequirementHovercard extends base {
`;
}
- private handleShowConditions() {
- this.expanded = true;
+ private toggleConditionsVisibility() {
+ this.expanded = !this.expanded;
}
}