blob: 2992923186a68f35aa951edb3c2e3d9e72ffec6a [file] [log] [blame]
Milutin Kristoficc8909722021-08-04 15:21:40 +02001/**
2 * @license
3 * Copyright (C) 2021 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
Milutin Kristofica837d522021-10-15 16:08:20 +020017import '../../shared/gr-label-info/gr-label-info';
Milutin Kristofic0950fd72021-08-26 17:05:09 +020018import '../gr-submit-requirement-hovercard/gr-submit-requirement-hovercard';
Milutin Kristofica837d522021-10-15 16:08:20 +020019import '../gr-trigger-vote-hovercard/gr-trigger-vote-hovercard';
Ben Rohlfs0ca140a2021-09-03 10:43:37 +020020import {LitElement, css, html} from 'lit';
Milutin Kristofic1641c262021-09-28 10:46:16 +020021import {customElement, property, state} from 'lit/decorators';
Milutin Kristofic3424f6e2021-08-05 13:02:16 +020022import {ParsedChangeInfo} from '../../../types/types';
Milutin Kristofic8fc3d8f82021-08-10 20:25:21 +020023import {
24 AccountInfo,
Milutin Kristofic0950fd72021-08-26 17:05:09 +020025 isDetailedLabelInfo,
Milutin Kristoficeee74762021-10-11 15:30:33 +020026 isQuickLabelInfo,
Milutin Kristofic12b00dc2021-10-07 12:42:00 +020027 LabelInfo,
Milutin Kristofic0950fd72021-08-26 17:05:09 +020028 LabelNameToInfoMap,
Milutin Kristofic8fc3d8f82021-08-10 20:25:21 +020029 SubmitRequirementResultInfo,
30 SubmitRequirementStatus,
31} from '../../../api/rest-api';
Milutin Kristofic9f50e762021-08-27 11:41:41 +020032import {
33 extractAssociatedLabels,
Milutin Kristofic12b00dc2021-10-07 12:42:00 +020034 getAllUniqueApprovals,
Milutin Kristoficf611a5f2021-10-25 16:51:51 +020035 getRequirements,
Milutin Kristofic8768a5d2021-10-26 11:02:38 +020036 getTriggerVotes,
Milutin Kristofic2b366b62021-10-13 16:44:41 +020037 hasNeutralStatus,
Milutin Kristoficd55e0302021-09-27 12:19:00 +020038 hasVotes,
Milutin Kristofic9f50e762021-08-27 11:41:41 +020039 iconForStatus,
Milutin Kristoficbd445802021-10-20 21:00:21 +020040 orderSubmitRequirements,
Milutin Kristofic9f50e762021-08-27 11:41:41 +020041} from '../../../utils/label-util';
Milutin Kristofic758711b2021-09-16 14:44:10 +020042import {fontStyles} from '../../../styles/gr-font-styles';
Milutin Kristofic1641c262021-09-28 10:46:16 +020043import {charsOnly, pluralize} from '../../../utils/string-util';
44import {subscribe} from '../../lit/subscription-controller';
45import {
46 allRunsLatestPatchsetLatestAttempt$,
47 CheckRun,
48} from '../../../services/checks/checks-model';
49import {getResultsOf, hasResultsOf} from '../../../services/checks/checks-util';
50import {Category} from '../../../api/checks';
Ben Rohlfsece23f92021-09-30 14:58:37 +020051import '../../shared/gr-vote-chip/gr-vote-chip';
Milutin Kristoficc8909722021-08-04 15:21:40 +020052
Milutin Kristofic395c5462021-10-25 15:28:15 +020053/**
54 * @attr {Boolean} suppress-title - hide titles, currently for hovercard view
55 */
Milutin Kristoficc8909722021-08-04 15:21:40 +020056@customElement('gr-submit-requirements')
Ben Rohlfs0ca140a2021-09-03 10:43:37 +020057export class GrSubmitRequirements extends LitElement {
Milutin Kristofic3424f6e2021-08-05 13:02:16 +020058 @property({type: Object})
59 change?: ParsedChangeInfo;
60
Milutin Kristofic8fc3d8f82021-08-10 20:25:21 +020061 @property({type: Object})
62 account?: AccountInfo;
63
64 @property({type: Boolean})
65 mutable?: boolean;
66
Milutin Kristofic1641c262021-09-28 10:46:16 +020067 @state()
68 runs: CheckRun[] = [];
69
Gerrit Code Review09637ae2021-08-19 14:34:39 +000070 static override get styles() {
Milutin Kristoficc8909722021-08-04 15:21:40 +020071 return [
Milutin Kristofic758711b2021-09-16 14:44:10 +020072 fontStyles,
Milutin Kristoficc8909722021-08-04 15:21:40 +020073 css`
Milutin Kristofic395c5462021-10-25 15:28:15 +020074 :host([suppress-title]) .metadata-title {
Milutin Kristoficbe900172021-10-25 13:42:58 +020075 display: none;
76 }
Milutin Kristoficc8909722021-08-04 15:21:40 +020077 .metadata-title {
Milutin Kristoficc8909722021-08-04 15:21:40 +020078 color: var(--deemphasized-text-color);
79 padding-left: var(--metadata-horizontal-padding);
Milutin Kristofic758711b2021-09-16 14:44:10 +020080 margin: 0 0 var(--spacing-s);
81 border-top: 1px solid var(--border-color);
82 padding-top: var(--spacing-s);
Milutin Kristofic58526822021-08-06 12:18:36 +020083 }
84 iron-icon {
Milutin Kristofic758711b2021-09-16 14:44:10 +020085 width: var(--line-height-normal, 20px);
86 height: var(--line-height-normal, 20px);
Milutin Kristofic58526822021-08-06 12:18:36 +020087 }
Milutin Kristofic225270a2021-10-20 22:39:47 +020088 iron-icon.check,
89 iron-icon.overridden {
Milutin Kristofic58526822021-08-06 12:18:36 +020090 color: var(--success-foreground);
91 }
Milutin Kristofic9f50e762021-08-27 11:41:41 +020092 iron-icon.close {
Milutin Kristofica7b15bf2021-10-19 16:16:54 +020093 color: var(--error-foreground);
Milutin Kristofic58526822021-08-06 12:18:36 +020094 }
Milutin Kristofic758711b2021-09-16 14:44:10 +020095 .requirements,
Milutin Kristofic12b00dc2021-10-07 12:42:00 +020096 section.trigger-votes {
Milutin Kristofic758711b2021-09-16 14:44:10 +020097 margin-left: var(--spacing-l);
98 }
Milutin Kristofic12b00dc2021-10-07 12:42:00 +020099 .trigger-votes {
100 padding-top: var(--spacing-s);
101 display: flex;
102 flex-wrap: wrap;
103 gap: var(--spacing-s);
104 /* Setting max-width as defined in Submit Requirements design,
105 * to wrap overflowed items to next row.
106 */
107 max-width: 390px;
108 }
Milutin Kristofic758711b2021-09-16 14:44:10 +0200109 gr-limited-text.name {
110 font-weight: var(--font-weight-bold);
111 }
112 table {
113 border-collapse: collapse;
114 border-spacing: 0;
115 }
116 td {
117 padding: var(--spacing-s);
Milutin Kristoficbe900172021-10-25 13:42:58 +0200118 white-space: nowrap;
Milutin Kristofic758711b2021-09-16 14:44:10 +0200119 }
Milutin Kristofic1641c262021-09-28 10:46:16 +0200120 .votes-cell {
121 display: flex;
122 }
123 .check-error {
124 margin-right: var(--spacing-l);
125 }
126 .check-error iron-icon {
127 color: var(--error-foreground);
128 vertical-align: top;
129 }
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200130 gr-vote-chip {
131 margin-right: var(--spacing-s);
132 }
Milutin Kristoficc8909722021-08-04 15:21:40 +0200133 `,
134 ];
135 }
136
Milutin Kristofic1641c262021-09-28 10:46:16 +0200137 constructor() {
138 super();
139 subscribe(this, allRunsLatestPatchsetLatestAttempt$, x => (this.runs = x));
140 }
141
Gerrit Code Review09637ae2021-08-19 14:34:39 +0000142 override render() {
Milutin Kristoficf611a5f2021-10-25 16:51:51 +0200143 const submit_requirements = orderSubmitRequirements(
144 getRequirements(this.change)
Milutin Kristofic7e65e992021-10-21 20:55:05 +0200145 );
Milutin Kristofic7e65e992021-10-21 20:55:05 +0200146
Milutin Kristofice865e912021-09-23 17:08:41 +0200147 return html` <h3
Milutin Kristofic758711b2021-09-16 14:44:10 +0200148 class="metadata-title heading-3"
149 id="submit-requirements-caption"
150 >
151 Submit Requirements
Milutin Kristofice865e912021-09-23 17:08:41 +0200152 </h3>
Milutin Kristofic758711b2021-09-16 14:44:10 +0200153 <table class="requirements" aria-labelledby="submit-requirements-caption">
154 <thead hidden>
155 <tr>
156 <th>Status</th>
157 <th>Name</th>
158 <th>Votes</th>
159 </tr>
160 </thead>
161 <tbody>
162 ${submit_requirements.map(
Milutin Kristofic5eec84e2021-09-27 12:28:10 +0200163 requirement => html`<tr
164 id="requirement-${charsOnly(requirement.name)}"
165 >
Milutin Kristofic758711b2021-09-16 14:44:10 +0200166 <td>${this.renderStatus(requirement.status)}</td>
167 <td class="name">
168 <gr-limited-text
169 class="name"
170 limit="25"
171 .text="${requirement.name}"
172 ></gr-limited-text>
173 </td>
Milutin Kristofic1641c262021-09-28 10:46:16 +0200174 <td>
175 <div class="votes-cell">
176 ${this.renderVotes(requirement)}
177 ${this.renderChecks(requirement)}
178 </div>
179 </td>
Milutin Kristofic758711b2021-09-16 14:44:10 +0200180 </tr>`
181 )}
182 </tbody>
183 </table>
Milutin Kristofic3424f6e2021-08-05 13:02:16 +0200184 ${submit_requirements.map(
Milutin Kristofic758711b2021-09-16 14:44:10 +0200185 requirement => html`
Milutin Kristofic0950fd72021-08-26 17:05:09 +0200186 <gr-submit-requirement-hovercard
Milutin Kristofic5eec84e2021-09-27 12:28:10 +0200187 for="requirement-${charsOnly(requirement.name)}"
Milutin Kristofic9f50e762021-08-27 11:41:41 +0200188 .requirement="${requirement}"
189 .change="${this.change}"
190 .account="${this.account}"
Ben Rohlfsece23f92021-09-30 14:58:37 +0200191 .mutable="${this.mutable ?? false}"
Milutin Kristofic9f50e762021-08-27 11:41:41 +0200192 ></gr-submit-requirement-hovercard>
Milutin Kristofic758711b2021-09-16 14:44:10 +0200193 `
Milutin Kristofic40826c12021-08-26 13:16:11 +0200194 )}
Milutin Kristofic8768a5d2021-10-26 11:02:38 +0200195 ${this.renderTriggerVotes()}`;
Milutin Kristoficc8909722021-08-04 15:21:40 +0200196 }
Milutin Kristofic58526822021-08-06 12:18:36 +0200197
198 renderStatus(status: SubmitRequirementStatus) {
Milutin Kristofic9f50e762021-08-27 11:41:41 +0200199 const icon = iconForStatus(status);
Milutin Kristofic58526822021-08-06 12:18:36 +0200200 return html`<iron-icon
Milutin Kristofic9f50e762021-08-27 11:41:41 +0200201 class="${icon}"
202 icon="gr-icons:${icon}"
Milutin Kristofic758711b2021-09-16 14:44:10 +0200203 role="img"
204 aria-label="${status.toLowerCase()}"
Milutin Kristofic58526822021-08-06 12:18:36 +0200205 ></iron-icon>`;
206 }
Milutin Kristofic8fc3d8f82021-08-10 20:25:21 +0200207
Milutin Kristofic0950fd72021-08-26 17:05:09 +0200208 renderVotes(requirement: SubmitRequirementResultInfo) {
209 const requirementLabels = extractAssociatedLabels(requirement);
Milutin Kristoficd55e0302021-09-27 12:19:00 +0200210 const allLabels = this.change?.labels ?? {};
211 const associatedLabels = Object.keys(allLabels).filter(label =>
212 requirementLabels.includes(label)
213 );
Milutin Kristofic0950fd72021-08-26 17:05:09 +0200214
Milutin Kristoficd55e0302021-09-27 12:19:00 +0200215 const everyAssociatedLabelsIsWithoutVotes = associatedLabels.every(
Milutin Kristofic7e59b682021-09-27 16:15:01 +0200216 label => !hasVotes(allLabels[label])
Milutin Kristoficd55e0302021-09-27 12:19:00 +0200217 );
218 if (everyAssociatedLabelsIsWithoutVotes) return html`No votes`;
219
220 return associatedLabels.map(label =>
221 this.renderLabelVote(label, allLabels)
222 );
Milutin Kristofic0950fd72021-08-26 17:05:09 +0200223 }
224
225 renderLabelVote(label: string, labels: LabelNameToInfoMap) {
226 const labelInfo = labels[label];
Milutin Kristoficeee74762021-10-11 15:30:33 +0200227 if (isDetailedLabelInfo(labelInfo)) {
Milutin Kristofic2b366b62021-10-13 16:44:41 +0200228 const uniqueApprovals = getAllUniqueApprovals(labelInfo).filter(
229 approval => !hasNeutralStatus(labelInfo, approval)
230 );
Milutin Kristoficeee74762021-10-11 15:30:33 +0200231 return uniqueApprovals.map(
232 approvalInfo =>
233 html`<gr-vote-chip
234 .vote="${approvalInfo}"
235 .label="${labelInfo}"
236 .more="${(labelInfo.all ?? []).filter(
237 other => other.value === approvalInfo.value
238 ).length > 1}"
239 ></gr-vote-chip>`
240 );
241 } else if (isQuickLabelInfo(labelInfo)) {
242 return [html`<gr-vote-chip .label="${labelInfo}"></gr-vote-chip>`];
243 } else {
244 return html``;
245 }
Milutin Kristofic0950fd72021-08-26 17:05:09 +0200246 }
247
Milutin Kristofic1641c262021-09-28 10:46:16 +0200248 renderChecks(requirement: SubmitRequirementResultInfo) {
249 const requirementLabels = extractAssociatedLabels(requirement);
250 const requirementRuns = this.runs
251 .filter(run => hasResultsOf(run, Category.ERROR))
252 .filter(
253 run => run.labelName && requirementLabels.includes(run.labelName)
254 );
255 const runsCount = requirementRuns.reduce(
256 (sum, run) => sum + getResultsOf(run, Category.ERROR).length,
257 0
258 );
259 if (runsCount > 0) {
260 return html`<span class="check-error"
261 ><iron-icon icon="gr-icons:error"></iron-icon>${pluralize(
262 runsCount,
263 'error'
264 )}</span
265 >`;
266 }
267 return;
268 }
269
Milutin Kristofic8768a5d2021-10-26 11:02:38 +0200270 renderTriggerVotes() {
Milutin Kristofic40826c12021-08-26 13:16:11 +0200271 const labels = this.change?.labels ?? {};
Milutin Kristofic8768a5d2021-10-26 11:02:38 +0200272 const triggerVotes = getTriggerVotes(this.change).filter(label =>
273 hasVotes(labels[label])
274 );
Milutin Kristofic40826c12021-08-26 13:16:11 +0200275 if (!triggerVotes.length) return;
Milutin Kristofic758711b2021-09-16 14:44:10 +0200276 return html`<h3 class="metadata-title heading-3">Trigger Votes</h3>
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200277 <section class="trigger-votes">
Milutin Kristofic758711b2021-09-16 14:44:10 +0200278 ${triggerVotes.map(
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200279 label =>
280 html`<gr-trigger-vote
281 .label="${label}"
Milutin Kristofic758711b2021-09-16 14:44:10 +0200282 .labelInfo="${labels[label]}"
Milutin Kristofica837d522021-10-15 16:08:20 +0200283 .change="${this.change}"
284 .account="${this.account}"
285 .mutable="${this.mutable ?? false}"
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200286 ></gr-trigger-vote>`
Milutin Kristofic758711b2021-09-16 14:44:10 +0200287 )}
288 </section>`;
Milutin Kristofic40826c12021-08-26 13:16:11 +0200289 }
Milutin Kristoficc8909722021-08-04 15:21:40 +0200290}
291
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200292@customElement('gr-trigger-vote')
293export class GrTriggerVote extends LitElement {
294 @property()
295 label?: string;
296
297 @property({type: Object})
298 labelInfo?: LabelInfo;
299
Milutin Kristofica837d522021-10-15 16:08:20 +0200300 @property({type: Object})
301 change?: ParsedChangeInfo;
302
303 @property({type: Object})
304 account?: AccountInfo;
305
306 @property({type: Boolean})
307 mutable?: boolean;
308
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200309 static override get styles() {
310 return css`
311 :host {
312 display: block;
313 }
314 .container {
315 box-sizing: border-box;
316 border: 1px solid var(--border-color);
317 border-radius: calc(var(--border-radius) + 2px);
318 background-color: var(--background-color-primary);
319 display: flex;
320 padding: 0;
321 padding-left: var(--spacing-s);
322 padding-right: var(--spacing-xxs);
323 align-items: center;
324 }
325 .label {
326 padding-right: var(--spacing-s);
327 font-weight: var(--font-weight-bold);
328 }
329 gr-vote-chip {
330 --gr-vote-chip-width: 14px;
331 --gr-vote-chip-height: 14px;
332 margin-right: 0px;
Milutin Kristofica837d522021-10-15 16:08:20 +0200333 margin-left: var(--spacing-xs);
334 }
335 gr-vote-chip:first-of-type {
336 margin-left: 0px;
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200337 }
338 `;
339 }
340
341 override render() {
Milutin Kristoficeee74762021-10-11 15:30:33 +0200342 if (!this.labelInfo) return;
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200343 return html`
344 <div class="container">
Milutin Kristofica837d522021-10-15 16:08:20 +0200345 <gr-trigger-vote-hovercard .labelName=${this.label}>
346 <gr-label-info
347 slot="label-info"
348 .change=${this.change}
349 .account=${this.account}
350 .mutable=${this.mutable}
351 .label=${this.label}
352 .labelInfo=${this.labelInfo}
353 .showAllReviewers=${false}
354 ></gr-label-info>
355 </gr-trigger-vote-hovercard>
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200356 <span class="label">${this.label}</span>
Milutin Kristoficeee74762021-10-11 15:30:33 +0200357 ${this.renderVotes()}
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200358 </div>
359 `;
360 }
Milutin Kristoficeee74762021-10-11 15:30:33 +0200361
362 private renderVotes() {
Milutin Kristofic2b366b62021-10-13 16:44:41 +0200363 const {labelInfo} = this;
364 if (!labelInfo) return;
365 if (isDetailedLabelInfo(labelInfo)) {
366 const approvals = getAllUniqueApprovals(labelInfo).filter(
367 approval => !hasNeutralStatus(labelInfo, approval)
368 );
369 return approvals.map(
Milutin Kristoficeee74762021-10-11 15:30:33 +0200370 approvalInfo => html`<gr-vote-chip
371 .vote="${approvalInfo}"
Milutin Kristofic2b366b62021-10-13 16:44:41 +0200372 .label="${labelInfo}"
Milutin Kristoficeee74762021-10-11 15:30:33 +0200373 ></gr-vote-chip>`
374 );
Milutin Kristofic2b366b62021-10-13 16:44:41 +0200375 } else if (isQuickLabelInfo(labelInfo)) {
Milutin Kristoficeee74762021-10-11 15:30:33 +0200376 return [html`<gr-vote-chip .label="${this.labelInfo}"></gr-vote-chip>`];
377 } else {
378 return html``;
379 }
380 }
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200381}
382
Milutin Kristoficc8909722021-08-04 15:21:40 +0200383declare global {
384 interface HTMLElementTagNameMap {
385 'gr-submit-requirements': GrSubmitRequirements;
Milutin Kristofic12b00dc2021-10-07 12:42:00 +0200386 'gr-trigger-vote': GrTriggerVote;
Milutin Kristoficc8909722021-08-04 15:21:40 +0200387 }
388}