| /** |
| * @license |
| * Copyright 2022 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import '../../change/gr-submit-requirement-dashboard-hovercard/gr-submit-requirement-dashboard-hovercard'; |
| import '../../shared/gr-change-status/gr-change-status'; |
| import '../../shared/gr-icon/gr-icon'; |
| import {LitElement, css, html} from 'lit'; |
| import {customElement, property} from 'lit/decorators.js'; |
| import { |
| ApprovalInfo, |
| ChangeInfo, |
| isDetailedLabelInfo, |
| isQuickLabelInfo, |
| LabelInfo, |
| SubmitRequirementResultInfo, |
| SubmitRequirementStatus, |
| } from '../../../api/rest-api'; |
| import {submitRequirementsStyles} from '../../../styles/gr-submit-requirements-styles'; |
| import { |
| extractAssociatedLabels, |
| getAllUniqueApprovals, |
| getRequirements, |
| getTriggerVotes, |
| hasNeutralStatus, |
| hasVotes, |
| iconForStatus, |
| } from '../../../utils/label-util'; |
| import {sharedStyles} from '../../../styles/shared-styles'; |
| import {ifDefined} from 'lit/directives/if-defined.js'; |
| import {capitalizeFirstLetter} from '../../../utils/string-util'; |
| |
| @customElement('gr-change-list-column-requirement') |
| export class GrChangeListColumnRequirement extends LitElement { |
| @property({type: Object}) |
| change?: ChangeInfo; |
| |
| @property() |
| labelName?: string; |
| |
| static override get styles() { |
| return [ |
| submitRequirementsStyles, |
| sharedStyles, |
| css` |
| .container { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| .container.not-applicable { |
| background-color: var(--table-header-background-color); |
| height: calc(var(--line-height-normal) + var(--spacing-m)); |
| } |
| `, |
| ]; |
| } |
| |
| override render() { |
| return html`<div |
| class="container ${this.computeClass()}" |
| title=${ifDefined(this.computeLabelTitle())} |
| > |
| ${this.renderContent()} |
| </div>`; |
| } |
| |
| private renderContent() { |
| if (!this.labelName) return; |
| const requirements = this.getRequirement(this.labelName); |
| if (requirements.length === 0) { |
| return this.renderTriggerVote(); |
| } |
| |
| const requirement = requirements[0]; |
| if (requirement.status === SubmitRequirementStatus.UNSATISFIED) { |
| return this.renderUnsatisfiedState(requirement); |
| } else { |
| return this.renderStatusIcon(requirement.status); |
| } |
| } |
| |
| private renderTriggerVote() { |
| if (!this.labelName || !this.isTriggerVote(this.labelName)) return; |
| const allLabels = this.change?.labels ?? {}; |
| const labelInfo = allLabels[this.labelName]; |
| if (isDetailedLabelInfo(labelInfo)) { |
| // votes sorted from best e.g +2 to worst e.g -2 |
| const votes = this.getSortedVotes(this.labelName); |
| if (votes.length > 0) { |
| const bestVote = votes[0]; |
| return html`<gr-vote-chip |
| .vote=${bestVote} |
| .label=${labelInfo} |
| tooltip-with-who-voted |
| ></gr-vote-chip>`; |
| } |
| } |
| if (isQuickLabelInfo(labelInfo)) { |
| return html`<gr-vote-chip .label=${labelInfo}></gr-vote-chip>`; |
| } |
| return; |
| } |
| |
| private renderUnsatisfiedState(requirement: SubmitRequirementResultInfo) { |
| const requirementLabels = extractAssociatedLabels( |
| requirement, |
| 'onlySubmittability' |
| ); |
| const allLabels = this.change?.labels ?? {}; |
| const associatedLabels = Object.keys(allLabels).filter(label => |
| requirementLabels.includes(label) |
| ); |
| |
| let worstVote: ApprovalInfo | undefined; |
| let labelInfo: LabelInfo | undefined; |
| for (const label of associatedLabels) { |
| // votes sorted from worst e.g -2 to best e.g +2 |
| const votes = this.getSortedVotes(label).sort( |
| (a, b) => (a.value ?? 0) - (b.value ?? 0) |
| ); |
| if (votes.length === 0) break; |
| if (!worstVote || (worstVote.value ?? 0) > (votes[0].value ?? 0)) { |
| worstVote = votes[0]; |
| labelInfo = allLabels[label]; |
| } |
| } |
| if (worstVote === undefined) { |
| return this.renderStatusIcon(requirement.status); |
| } else { |
| return html`<gr-vote-chip |
| .vote=${worstVote} |
| .label=${labelInfo} |
| tooltip-with-who-voted |
| ></gr-vote-chip>`; |
| } |
| } |
| |
| private renderStatusIcon(status: SubmitRequirementStatus) { |
| const icon = iconForStatus(status); |
| return html` |
| <gr-icon |
| class=${icon.icon} |
| icon=${icon.icon} |
| ?filled=${icon.filled} |
| ></gr-icon> |
| `; |
| } |
| |
| private computeClass(): string { |
| if (!this.labelName) return ''; |
| const requirements = this.getRequirement(this.labelName); |
| if (requirements.length === 0 && !this.isTriggerVote(this.labelName)) { |
| return 'not-applicable'; |
| } |
| return ''; |
| } |
| |
| private computeLabelTitle() { |
| if (!this.labelName) return; |
| const requirements = this.getRequirement(this.labelName); |
| if (requirements.length === 0) { |
| if (this.isTriggerVote(this.labelName)) { |
| return; |
| } else { |
| return 'Requirement not applicable'; |
| } |
| } |
| const requirement = requirements[0]; |
| if (requirement.status === SubmitRequirementStatus.UNSATISFIED) { |
| const requirementLabels = extractAssociatedLabels( |
| requirement, |
| 'onlySubmittability' |
| ); |
| const allLabels = this.change?.labels ?? {}; |
| const associatedLabels = Object.keys(allLabels).filter(label => |
| requirementLabels.includes(label) |
| ); |
| const requirementWithoutLabelToVoteOn = associatedLabels.length === 0; |
| if (requirementWithoutLabelToVoteOn) { |
| const status = capitalizeFirstLetter(requirement.status.toLowerCase()); |
| return status; |
| } |
| |
| const everyAssociatedLabelsIsWithoutVotes = associatedLabels.every( |
| label => !hasVotes(allLabels[label]) |
| ); |
| if (everyAssociatedLabelsIsWithoutVotes) { |
| return 'No votes'; |
| } else { |
| return; // there is a vote with tooltip, so undefined label title |
| } |
| } else { |
| return capitalizeFirstLetter(requirement.status.toLowerCase()); |
| } |
| } |
| |
| private getRequirement(labelName: string) { |
| const requirements = getRequirements(this.change).filter( |
| sr => sr.name === labelName |
| ); |
| // TODO(milutin): Remove this after migration from legacy requirements. |
| if (requirements.length > 1) { |
| return requirements.filter(sr => !sr.is_legacy); |
| } else { |
| return requirements; |
| } |
| } |
| |
| private getSortedVotes(label: string) { |
| const allLabels = this.change?.labels ?? {}; |
| const labelInfo = allLabels[label]; |
| if (isDetailedLabelInfo(labelInfo)) { |
| return getAllUniqueApprovals(labelInfo).filter( |
| approval => !hasNeutralStatus(labelInfo, approval) |
| ); |
| } |
| return []; |
| } |
| |
| private isTriggerVote(labelName: string) { |
| const triggerVotes = getTriggerVotes(this.change); |
| return triggerVotes.includes(labelName); |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-change-list-column-requirement': GrChangeListColumnRequirement; |
| } |
| } |