diff options
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.ts | 246 |
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; } } |