blob: cef2e0b3607bd56170e7d4a824d2c1cea356c805 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Milutin Kristoficf8192162021-04-21 22:12:59 +02003 * Copyright (C) 2021 The Android Open Source Project
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04004 *
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 Kristoficf8192162021-04-21 22:12:59 +020017import './gr-related-change';
Dmitrii Filippov1d607912020-09-21 13:21:14 +020018import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
19import '../../plugins/gr-endpoint-param/gr-endpoint-param';
20import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
Ben Rohlfse2b2f592021-09-07 08:47:53 +000021import {classMap} from 'lit/directives/class-map';
Milutin Kristoficf8192162021-04-21 22:12:59 +020022import {GrLitElement} from '../../lit/gr-lit-element';
Ben Rohlfse2b2f592021-09-07 08:47:53 +000023import {css, html, nothing, TemplateResult} from 'lit';
24import {customElement, property, state} from 'lit/decorators';
Milutin Kristoficf8192162021-04-21 22:12:59 +020025import {sharedStyles} from '../../../styles/shared-styles';
26import {
27 SubmittedTogetherInfo,
28 ChangeInfo,
29 RelatedChangeAndCommitInfo,
30 RelatedChangesInfo,
31 PatchSetNum,
32 CommitId,
33} from '../../../types/common';
34import {appContext} from '../../../services/app-context';
35import {ParsedChangeInfo} from '../../../types/types';
Dmitrii Filippov1d607912020-09-21 13:21:14 +020036import {GerritNav} from '../../core/gr-navigation/gr-navigation';
Milutin Kristoficf8192162021-04-21 22:12:59 +020037import {pluralize} from '../../../utils/string-util';
Milutin Kristofic41de8172021-02-17 12:52:04 +010038import {
39 changeIsOpen,
40 getRevisionKey,
41 isChangeInfo,
42} from '../../../utils/change-util';
Milutin Kristofica7c24c12021-06-07 23:09:26 +020043import {Interaction} from '../../../constants/reporting';
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010044
Milutin Kristoficf8192162021-04-21 22:12:59 +020045/** What is the maximum number of shown changes in collapsed list? */
46const DEFALT_NUM_CHANGES_WHEN_COLLAPSED = 3;
47
48export interface ChangeMarkersInList {
49 showCurrentChangeArrow: boolean;
50 showWhenCollapsed: boolean;
51 showTopArrow: boolean;
52 showBottomArrow: boolean;
53}
54
55export enum Section {
56 RELATED_CHANGES = 'related changes',
57 SUBMITTED_TOGETHER = 'submitted together',
58 SAME_TOPIC = 'same topic',
59 MERGE_CONFLICTS = 'merge conflicts',
60 CHERRY_PICKS = 'cherry picks',
Dmitrii Filippov1d607912020-09-21 13:21:14 +020061}
62
Dmitrii Filippov1d607912020-09-21 13:21:14 +020063@customElement('gr-related-changes-list')
Milutin Kristoficf8192162021-04-21 22:12:59 +020064export class GrRelatedChangesList extends GrLitElement {
65 @property()
Dmitrii Filippov1d607912020-09-21 13:21:14 +020066 change?: ParsedChangeInfo;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +010067
Dmitrii Filippov1d607912020-09-21 13:21:14 +020068 @property({type: String})
69 patchNum?: PatchSetNum;
70
Milutin Kristoficf8192162021-04-21 22:12:59 +020071 @property()
Dmitrii Filippov1d607912020-09-21 13:21:14 +020072 mergeable?: boolean;
73
Frank Bordenf19f2b72021-05-12 11:21:34 +020074 @state()
Milutin Kristoficf8192162021-04-21 22:12:59 +020075 submittedTogether?: SubmittedTogetherInfo = {
76 changes: [],
77 non_visible_changes: 0,
78 };
Dmitrii Filippov1d607912020-09-21 13:21:14 +020079
Frank Bordenf19f2b72021-05-12 11:21:34 +020080 @state()
Milutin Kristoficf8192162021-04-21 22:12:59 +020081 relatedChanges: RelatedChangeAndCommitInfo[] = [];
Dmitrii Filippov1d607912020-09-21 13:21:14 +020082
Frank Bordenf19f2b72021-05-12 11:21:34 +020083 @state()
Milutin Kristoficf8192162021-04-21 22:12:59 +020084 conflictingChanges: ChangeInfo[] = [];
Dmitrii Filippov1d607912020-09-21 13:21:14 +020085
Frank Bordenf19f2b72021-05-12 11:21:34 +020086 @state()
Milutin Kristoficf8192162021-04-21 22:12:59 +020087 cherryPickChanges: ChangeInfo[] = [];
Dmitrii Filippov1d607912020-09-21 13:21:14 +020088
Frank Bordenf19f2b72021-05-12 11:21:34 +020089 @state()
Milutin Kristoficf8192162021-04-21 22:12:59 +020090 sameTopicChanges: ChangeInfo[] = [];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010091
Ben Rohlfs43935a42020-12-01 19:14:09 +010092 private readonly restApiService = appContext.restApiService;
93
Gerrit Code Review79031232021-08-19 14:32:41 +000094 static override get styles() {
Milutin Kristoficf8192162021-04-21 22:12:59 +020095 return [
96 sharedStyles,
97 css`
98 .note {
99 color: var(--error-text-color);
100 margin-left: 1.2em;
101 }
102 section {
103 margin-bottom: var(--spacing-l);
104 }
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200105 .relatedChangeLine {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200106 display: flex;
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200107 visibility: visible;
108 height: auto;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200109 }
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200110 .marker.arrow {
111 visibility: hidden;
112 min-width: 20px;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200113 }
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200114 .marker.arrowToCurrentChange {
115 min-width: 20px;
116 text-align: center;
117 }
118 .marker.space {
119 height: 1px;
120 min-width: 20px;
121 }
122 gr-related-collapse[collapsed] .marker.arrow {
123 visibility: visible;
124 min-width: auto;
125 }
126 gr-related-collapse[collapsed] .relatedChangeLine.show-when-collapsed {
127 visibility: visible;
128 height: auto;
129 }
130 /* keep width, so width of section and position of show all button
131 * are set according to width of all (even hidden) elements
132 */
133 gr-related-collapse[collapsed] .relatedChangeLine {
134 visibility: hidden;
135 height: 0px;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200136 }
137 `,
138 ];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100139 }
140
Gerrit Code Review79031232021-08-19 14:32:41 +0000141 override render() {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200142 const sectionSize = this.sectionSizeFactory(
143 this.relatedChanges.length,
144 this.submittedTogether?.changes.length || 0,
145 this.sameTopicChanges.length,
146 this.conflictingChanges.length,
147 this.cherryPickChanges.length
148 );
149 const relatedChangesMarkersPredicate = this.markersPredicateFactory(
150 this.relatedChanges.length,
151 this.relatedChanges.findIndex(relatedChange =>
152 this._changesEqual(relatedChange, this.change)
153 ),
154 sectionSize(Section.RELATED_CHANGES)
155 );
156 const connectedRevisions = this._computeConnectedRevisions(
157 this.change,
158 this.patchNum,
159 this.relatedChanges
160 );
161 let firstNonEmptySectionFound = false;
162 let isFirstNonEmpty =
163 !firstNonEmptySectionFound && !!this.relatedChanges.length;
164 firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
165 const relatedChangeSection = html` <section
166 id="relatedChanges"
167 ?hidden=${!this.relatedChanges.length}
168 >
169 <gr-related-collapse
170 title="Relation chain"
171 class="${classMap({first: isFirstNonEmpty})}"
172 .length=${this.relatedChanges.length}
173 .numChangesWhenCollapsed=${sectionSize(Section.RELATED_CHANGES)}
174 >
175 ${this.relatedChanges.map(
176 (change, index) =>
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200177 html`<div
178 class="${classMap({
179 ['relatedChangeLine']: true,
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000180 ['show-when-collapsed']:
181 relatedChangesMarkersPredicate(index).showWhenCollapsed,
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200182 })}"
183 >
184 ${this.renderMarkers(
Milutin Kristoficf8192162021-04-21 22:12:59 +0200185 relatedChangesMarkersPredicate(index)
186 )}<gr-related-change
Milutin Kristoficf8192162021-04-21 22:12:59 +0200187 .change="${change}"
188 .connectedRevisions="${connectedRevisions}"
189 .href="${change?._change_number
190 ? GerritNav.getUrlForChangeById(
191 change._change_number,
192 change.project,
193 change._revision_number as PatchSetNum
194 )
195 : ''}"
196 .showChangeStatus=${true}
197 >${change.commit.subject}</gr-related-change
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200198 >
199 </div>`
Milutin Kristoficf8192162021-04-21 22:12:59 +0200200 )}
201 </gr-related-collapse>
202 </section>`;
203
204 const submittedTogetherChanges = this.submittedTogether?.changes ?? [];
205 const countNonVisibleChanges =
206 this.submittedTogether?.non_visible_changes ?? 0;
207 const submittedTogetherMarkersPredicate = this.markersPredicateFactory(
208 submittedTogetherChanges.length,
209 submittedTogetherChanges.findIndex(relatedChange =>
210 this._changesEqual(relatedChange, this.change)
211 ),
212 sectionSize(Section.SUBMITTED_TOGETHER)
213 );
214 isFirstNonEmpty =
215 !firstNonEmptySectionFound &&
216 (!!submittedTogetherChanges?.length ||
217 !!this.submittedTogether?.non_visible_changes);
218 firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
219 const submittedTogetherSection = html`<section
220 id="submittedTogether"
221 ?hidden=${!submittedTogetherChanges?.length &&
222 !this.submittedTogether?.non_visible_changes}
223 >
224 <gr-related-collapse
225 title="Submitted together"
226 class="${classMap({first: isFirstNonEmpty})}"
227 .length=${submittedTogetherChanges.length}
228 .numChangesWhenCollapsed=${sectionSize(Section.SUBMITTED_TOGETHER)}
229 >
230 ${submittedTogetherChanges.map(
231 (change, index) =>
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200232 html`<div
233 class="${classMap({
234 ['relatedChangeLine']: true,
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000235 ['show-when-collapsed']:
236 submittedTogetherMarkersPredicate(index).showWhenCollapsed,
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200237 })}"
238 >
239 ${this.renderMarkers(
Milutin Kristoficf8192162021-04-21 22:12:59 +0200240 submittedTogetherMarkersPredicate(index)
241 )}<gr-related-change
Milutin Kristoficf8192162021-04-21 22:12:59 +0200242 .change="${change}"
243 .href="${GerritNav.getUrlForChangeById(
244 change._number,
245 change.project
246 )}"
247 .showSubmittableCheck=${true}
248 >${change.project}: ${change.branch}:
249 ${change.subject}</gr-related-change
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200250 >
251 </div>`
Milutin Kristoficf8192162021-04-21 22:12:59 +0200252 )}
253 </gr-related-collapse>
254 <div class="note" ?hidden=${!countNonVisibleChanges}>
255 (+ ${pluralize(countNonVisibleChanges, 'non-visible change')})
256 </div>
257 </section>`;
258
259 const sameTopicMarkersPredicate = this.markersPredicateFactory(
260 this.sameTopicChanges.length,
261 -1,
262 sectionSize(Section.SAME_TOPIC)
263 );
264 isFirstNonEmpty =
265 !firstNonEmptySectionFound && !!this.sameTopicChanges?.length;
266 firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
267 const sameTopicSection = html`<section
268 id="sameTopic"
269 ?hidden=${!this.sameTopicChanges?.length}
270 >
271 <gr-related-collapse
272 title="Same topic"
273 class="${classMap({first: isFirstNonEmpty})}"
274 .length=${this.sameTopicChanges.length}
275 .numChangesWhenCollapsed=${sectionSize(Section.SAME_TOPIC)}
276 >
277 ${this.sameTopicChanges.map(
278 (change, index) =>
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200279 html`<div
280 class="${classMap({
281 ['relatedChangeLine']: true,
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000282 ['show-when-collapsed']:
283 sameTopicMarkersPredicate(index).showWhenCollapsed,
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200284 })}"
285 >
286 ${this.renderMarkers(
Milutin Kristoficf8192162021-04-21 22:12:59 +0200287 sameTopicMarkersPredicate(index)
288 )}<gr-related-change
Milutin Kristoficf8192162021-04-21 22:12:59 +0200289 .change="${change}"
290 .href="${GerritNav.getUrlForChangeById(
291 change._number,
292 change.project
293 )}"
294 >${change.project}: ${change.branch}:
295 ${change.subject}</gr-related-change
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200296 >
297 </div>`
Milutin Kristoficf8192162021-04-21 22:12:59 +0200298 )}
299 </gr-related-collapse>
300 </section>`;
301
302 const mergeConflictsMarkersPredicate = this.markersPredicateFactory(
303 this.conflictingChanges.length,
304 -1,
305 sectionSize(Section.MERGE_CONFLICTS)
306 );
307 isFirstNonEmpty =
308 !firstNonEmptySectionFound && !!this.conflictingChanges?.length;
309 firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
310 const mergeConflictsSection = html`<section
311 id="mergeConflicts"
312 ?hidden=${!this.conflictingChanges?.length}
313 >
314 <gr-related-collapse
315 title="Merge conflicts"
316 class="${classMap({first: isFirstNonEmpty})}"
317 .length=${this.conflictingChanges.length}
318 .numChangesWhenCollapsed=${sectionSize(Section.MERGE_CONFLICTS)}
319 >
320 ${this.conflictingChanges.map(
321 (change, index) =>
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200322 html`<div
323 class="${classMap({
324 ['relatedChangeLine']: true,
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000325 ['show-when-collapsed']:
326 mergeConflictsMarkersPredicate(index).showWhenCollapsed,
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200327 })}"
328 >
329 ${this.renderMarkers(
Milutin Kristoficf8192162021-04-21 22:12:59 +0200330 mergeConflictsMarkersPredicate(index)
331 )}<gr-related-change
Milutin Kristoficf8192162021-04-21 22:12:59 +0200332 .change="${change}"
333 .href="${GerritNav.getUrlForChangeById(
334 change._number,
335 change.project
336 )}"
337 >${change.subject}</gr-related-change
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200338 >
339 </div>`
Milutin Kristoficf8192162021-04-21 22:12:59 +0200340 )}
341 </gr-related-collapse>
342 </section>`;
343
344 const cherryPicksMarkersPredicate = this.markersPredicateFactory(
345 this.cherryPickChanges.length,
346 -1,
347 sectionSize(Section.CHERRY_PICKS)
348 );
349 isFirstNonEmpty =
350 !firstNonEmptySectionFound && !!this.cherryPickChanges?.length;
351 firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
352 const cherryPicksSection = html`<section
353 id="cherryPicks"
354 ?hidden=${!this.cherryPickChanges?.length}
355 >
356 <gr-related-collapse
357 title="Cherry picks"
358 class="${classMap({first: isFirstNonEmpty})}"
359 .length=${this.cherryPickChanges.length}
360 .numChangesWhenCollapsed=${sectionSize(Section.CHERRY_PICKS)}
361 >
362 ${this.cherryPickChanges.map(
363 (change, index) =>
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200364 html`<div
365 class="${classMap({
366 ['relatedChangeLine']: true,
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000367 ['show-when-collapsed']:
368 cherryPicksMarkersPredicate(index).showWhenCollapsed,
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200369 })}"
370 >
371 ${this.renderMarkers(
Milutin Kristoficf8192162021-04-21 22:12:59 +0200372 cherryPicksMarkersPredicate(index)
373 )}<gr-related-change
Milutin Kristoficf8192162021-04-21 22:12:59 +0200374 .change="${change}"
375 .href="${GerritNav.getUrlForChangeById(
376 change._number,
377 change.project
378 )}"
379 >${change.branch}: ${change.subject}</gr-related-change
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200380 >
381 </div>`
Milutin Kristoficf8192162021-04-21 22:12:59 +0200382 )}
383 </gr-related-collapse>
384 </section>`;
385
386 return html`<gr-endpoint-decorator name="related-changes-section">
387 <gr-endpoint-param
388 name="change"
389 .value=${this.change}
390 ></gr-endpoint-param>
391 <gr-endpoint-slot name="top"></gr-endpoint-slot>
392 ${relatedChangeSection} ${submittedTogetherSection} ${sameTopicSection}
393 ${mergeConflictsSection} ${cherryPicksSection}
394 <gr-endpoint-slot name="bottom"></gr-endpoint-slot>
395 </gr-endpoint-decorator>`;
396 }
397
398 sectionSizeFactory(
399 relatedChangesLen: number,
400 submittedTogetherLen: number,
401 sameTopicLen: number,
402 mergeConflictsLen: number,
403 cherryPicksLen: number
404 ) {
405 const calcDefaultSize = (length: number) =>
406 Math.min(length, DEFALT_NUM_CHANGES_WHEN_COLLAPSED);
407
408 const sectionSizes = [
409 {
410 section: Section.RELATED_CHANGES,
411 size: calcDefaultSize(relatedChangesLen),
412 len: relatedChangesLen,
413 },
414 {
415 section: Section.SUBMITTED_TOGETHER,
416 size: calcDefaultSize(submittedTogetherLen),
417 len: submittedTogetherLen,
418 },
419 {
420 section: Section.SAME_TOPIC,
421 size: calcDefaultSize(sameTopicLen),
422 len: sameTopicLen,
423 },
424 {
425 section: Section.MERGE_CONFLICTS,
426 size: calcDefaultSize(mergeConflictsLen),
427 len: mergeConflictsLen,
428 },
429 {
430 section: Section.CHERRY_PICKS,
431 size: calcDefaultSize(cherryPicksLen),
432 len: cherryPicksLen,
433 },
434 ];
435
436 const FILLER = 1; // space for header
437 let totalSize = sectionSizes.reduce(
438 (acc, val) => acc + val.size + (val.size !== 0 ? FILLER : 0),
439 0
440 );
441
442 const MAX_SIZE = 16;
443 for (let i = 0; i < sectionSizes.length; i++) {
444 if (totalSize >= MAX_SIZE) break;
445 const sizeObj = sectionSizes[i];
446 if (sizeObj.size === sizeObj.len) continue;
447 const newSize = Math.min(
448 MAX_SIZE - totalSize + sizeObj.size,
449 sizeObj.len
450 );
451 totalSize += newSize - sizeObj.size;
452 sizeObj.size = newSize;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100453 }
Milutin Kristoficf8192162021-04-21 22:12:59 +0200454
455 return (section: Section) => {
456 const sizeObj = sectionSizes.find(sizeObj => sizeObj.section === section);
457 if (sizeObj) return sizeObj.size;
458 return DEFALT_NUM_CHANGES_WHEN_COLLAPSED;
459 };
460 }
461
462 markersPredicateFactory(
463 length: number,
464 highlightIndex: number,
465 numChangesShownWhenCollapsed = DEFALT_NUM_CHANGES_WHEN_COLLAPSED
466 ): (index: number) => ChangeMarkersInList {
467 const showWhenCollapsedPredicate = (index: number) => {
468 if (highlightIndex === -1) return index < numChangesShownWhenCollapsed;
469 if (highlightIndex === 0)
470 return index <= numChangesShownWhenCollapsed - 1;
471 if (highlightIndex === length - 1)
472 return index >= length - numChangesShownWhenCollapsed;
473 let numBeforeHighlight = Math.floor(numChangesShownWhenCollapsed / 2);
474 let numAfterHighlight =
475 Math.floor(numChangesShownWhenCollapsed / 2) -
476 (numChangesShownWhenCollapsed % 2 ? 0 : 1);
477 numBeforeHighlight += Math.max(
478 highlightIndex + numAfterHighlight - length + 1,
479 0
480 );
481 numAfterHighlight -= Math.min(0, highlightIndex - numBeforeHighlight);
482 return (
483 highlightIndex - numBeforeHighlight <= index &&
484 index <= highlightIndex + numAfterHighlight
485 );
486 };
487 return (index: number) => {
488 return {
489 showCurrentChangeArrow:
490 highlightIndex !== -1 && index === highlightIndex,
491 showWhenCollapsed: showWhenCollapsedPredicate(index),
492 showTopArrow:
493 index >= 1 &&
494 index !== highlightIndex &&
495 showWhenCollapsedPredicate(index) &&
496 !showWhenCollapsedPredicate(index - 1),
497 showBottomArrow:
498 index <= length - 2 &&
499 index !== highlightIndex &&
500 showWhenCollapsedPredicate(index) &&
501 !showWhenCollapsedPredicate(index + 1),
502 };
503 };
504 }
505
506 renderMarkers(changeMarkers: ChangeMarkersInList) {
507 if (changeMarkers.showCurrentChangeArrow) {
508 return html`<span
509 role="img"
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200510 class="marker arrowToCurrentChange"
Milutin Kristoficf8192162021-04-21 22:12:59 +0200511 aria-label="Arrow marking current change"
512 >âž”</span
513 >`;
514 }
515 if (changeMarkers.showTopArrow) {
516 return html`<span
517 role="img"
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200518 class="marker arrow"
Milutin Kristoficf8192162021-04-21 22:12:59 +0200519 aria-label="Arrow marking change has collapsed ancestors"
520 ><iron-icon icon="gr-icons:arrowDropUp"></iron-icon
521 ></span> `;
522 }
523 if (changeMarkers.showBottomArrow) {
524 return html`<span
525 role="img"
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200526 class="marker arrow"
Milutin Kristoficf8192162021-04-21 22:12:59 +0200527 aria-label="Arrow marking change has collapsed descendants"
528 ><iron-icon icon="gr-icons:arrowDropDown"></iron-icon
529 ></span> `;
530 }
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200531 return html`<span class="marker space"></span>`;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200532 }
533
534 reload(getRelatedChanges?: Promise<RelatedChangesInfo | undefined>) {
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200535 const change = this.change;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200536 if (!change) return Promise.reject(new Error('change missing'));
537 if (!this.patchNum) return Promise.reject(new Error('patchNum missing'));
538 if (!getRelatedChanges) {
539 getRelatedChanges = this.restApiService.getRelatedChanges(
540 change._number,
541 this.patchNum
542 );
543 }
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200544 const promises: Array<Promise<void>> = [
Milutin Kristoficf8192162021-04-21 22:12:59 +0200545 getRelatedChanges.then(response => {
546 if (!response) {
547 throw new Error('getRelatedChanges returned undefined response');
548 }
549 this.relatedChanges = response?.changes ?? [];
550 }),
Ben Rohlfs43935a42020-12-01 19:14:09 +0100551 this.restApiService
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200552 .getChangesSubmittedTogether(change._number)
553 .then(response => {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200554 this.submittedTogether = response;
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200555 }),
Ben Rohlfs43935a42020-12-01 19:14:09 +0100556 this.restApiService
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200557 .getChangeCherryPicks(change.project, change.change_id, change._number)
558 .then(response => {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200559 this.cherryPickChanges = response || [];
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200560 }),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100561 ];
562
563 // Get conflicts if change is open and is mergeable.
Milutin Kristoficf8192162021-04-21 22:12:59 +0200564 // Mergeable is output of restApiServict.getMergeable from gr-change-view
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200565 if (changeIsOpen(change) && this.mergeable) {
566 promises.push(
Ben Rohlfs43935a42020-12-01 19:14:09 +0100567 this.restApiService
568 .getChangeConflicts(change._number)
569 .then(response => {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200570 this.conflictingChanges = response ?? [];
Ben Rohlfs43935a42020-12-01 19:14:09 +0100571 })
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200572 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100573 }
Milutin Kristoficf8192162021-04-21 22:12:59 +0200574 if (change.topic) {
575 const changeTopic = change.topic;
576 promises.push(
577 this.restApiService.getConfig().then(config => {
578 if (config && !config.change.submit_whole_topic) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100579 return this.restApiService
Milutin Kristoficf8192162021-04-21 22:12:59 +0200580 .getChangesWithSameTopic(changeTopic, change._number)
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200581 .then(response => {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200582 if (changeTopic === this.change?.topic) {
583 this.sameTopicChanges = response ?? [];
584 }
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200585 });
586 }
Milutin Kristoficf8192162021-04-21 22:12:59 +0200587 this.sameTopicChanges = [];
588 return Promise.resolve();
589 })
590 );
591 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100592
Milutin Kristoficf8192162021-04-21 22:12:59 +0200593 return Promise.all(promises);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100594 }
595
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100596 /**
597 * Do the given objects describe the same change? Compares the changes by
598 * their numbers.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100599 */
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200600 _changesEqual(
Milutin Kristoficf8192162021-04-21 22:12:59 +0200601 a?: ChangeInfo | RelatedChangeAndCommitInfo,
602 b?: ChangeInfo | ParsedChangeInfo | RelatedChangeAndCommitInfo
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200603 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100604 const aNum = this._getChangeNumber(a);
605 const bNum = this._getChangeNumber(b);
606 return aNum === bNum;
607 }
608
609 /**
610 * Get the change number from either a ChangeInfo (such as those included in
611 * SubmittedTogetherInfo responses) or get the change number from a
612 * RelatedChangeAndCommitInfo (such as those included in a
613 * RelatedChangesInfo response).
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100614 */
Milutin Kristoficf8192162021-04-21 22:12:59 +0200615 _getChangeNumber(
616 change?: ChangeInfo | ParsedChangeInfo | RelatedChangeAndCommitInfo
617 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100618 // Default to 0 if change property is not defined.
619 if (!change) return 0;
620
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200621 if (isChangeInfo(change)) {
622 return change._number;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100623 }
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200624 return change._change_number;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100625 }
626
Milutin Kristoficf8192162021-04-21 22:12:59 +0200627 /*
628 * A list of commit ids connected to change to understand if other change
629 * is direct or indirect ancestor / descendant.
630 */
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200631 _computeConnectedRevisions(
632 change?: ParsedChangeInfo,
633 patchNum?: PatchSetNum,
634 relatedChanges?: RelatedChangeAndCommitInfo[]
635 ) {
Milutin Kristofic02c50032021-02-10 08:49:33 +0100636 if (!patchNum || !relatedChanges || !change) {
637 return [];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100638 }
639
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200640 const connected: CommitId[] = [];
Milutin Kristofic02c50032021-02-10 08:49:33 +0100641 const changeRevision = getRevisionKey(change, patchNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100642 const commits = relatedChanges.map(c => c.commit);
643 let pos = commits.length - 1;
644
645 while (pos >= 0) {
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200646 const commit: CommitId = commits[pos].commit;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100647 connected.push(commit);
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200648 // TODO(TS): Ensure that both (commit and changeRevision) are string and use === instead
649 // eslint-disable-next-line eqeqeq
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100650 if (commit == changeRevision) {
651 break;
652 }
653 pos--;
654 }
655 while (pos >= 0) {
656 for (let i = 0; i < commits[pos].parents.length; i++) {
657 if (connected.includes(commits[pos].parents[i].commit)) {
658 connected.push(commits[pos].commit);
659 break;
660 }
661 }
662 --pos;
663 }
664 return connected;
665 }
Milutin Kristoficf8192162021-04-21 22:12:59 +0200666}
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100667
Milutin Kristoficf8192162021-04-21 22:12:59 +0200668@customElement('gr-related-collapse')
669export class GrRelatedCollapse extends GrLitElement {
670 @property()
Gerrit Code Review79031232021-08-19 14:32:41 +0000671 override title = '';
Milutin Kristoficf8192162021-04-21 22:12:59 +0200672
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200673 @property({type: Boolean})
Milutin Kristoficf8192162021-04-21 22:12:59 +0200674 showAll = false;
675
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200676 @property({type: Boolean, reflect: true})
677 collapsed = true;
678
Milutin Kristoficf8192162021-04-21 22:12:59 +0200679 @property()
680 length = 0;
681
682 @property()
683 numChangesWhenCollapsed = DEFALT_NUM_CHANGES_WHEN_COLLAPSED;
684
685 private readonly reporting = appContext.reportingService;
686
Gerrit Code Review79031232021-08-19 14:32:41 +0000687 static override get styles() {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200688 return [
689 sharedStyles,
690 css`
691 .title {
692 font-weight: var(--font-weight-bold);
693 color: var(--deemphasized-text-color);
694 padding-left: var(--metadata-horizontal-padding);
695 }
696 h4 {
697 display: flex;
698 align-self: flex-end;
699 }
700 gr-button {
701 display: flex;
702 }
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200703 h4 {
704 margin-left: 20px;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200705 }
706 gr-button iron-icon {
707 color: inherit;
708 --iron-icon-height: 18px;
709 --iron-icon-width: 18px;
710 }
711 .container {
712 justify-content: space-between;
713 display: flex;
714 margin-bottom: var(--spacing-s);
715 }
716 :host(.first) .container {
717 margin-bottom: var(--spacing-m);
718 }
719 `,
720 ];
721 }
722
Gerrit Code Review79031232021-08-19 14:32:41 +0000723 override render() {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200724 const title = html`<h4 class="title">${this.title}</h4>`;
725
726 const collapsible = this.length > this.numChangesWhenCollapsed;
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200727 this.collapsed = !this.showAll && collapsible;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200728
729 let button: TemplateResult | typeof nothing = nothing;
730 if (collapsible) {
731 let buttonText = 'Show less';
732 let buttonIcon = 'expand-less';
733 if (!this.showAll) {
734 buttonText = `Show all (${this.length})`;
735 buttonIcon = 'expand-more';
736 }
737 button = html`<gr-button link="" @click="${this.toggle}"
738 >${buttonText}<iron-icon icon="gr-icons:${buttonIcon}"></iron-icon
739 ></gr-button>`;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100740 }
Milutin Kristoficf8192162021-04-21 22:12:59 +0200741
742 return html`<div class="container">${title}${button}</div>
Milutin Kristofic505f0ab2021-05-27 13:20:44 +0200743 <div><slot></slot></div>`;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100744 }
745
Milutin Kristoficf8192162021-04-21 22:12:59 +0200746 private toggle(e: MouseEvent) {
747 e.stopPropagation();
748 this.showAll = !this.showAll;
Milutin Kristofica7c24c12021-06-07 23:09:26 +0200749 this.reporting.reportInteraction(Interaction.TOGGLE_SHOW_ALL_BUTTON, {
Milutin Kristoficf8192162021-04-21 22:12:59 +0200750 sectionName: this.title,
751 toState: this.showAll ? 'Show all' : 'Show less',
Milutin Kristoficc866e602021-02-17 00:03:47 +0100752 });
753 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100754}
755
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200756declare global {
757 interface HTMLElementTagNameMap {
758 'gr-related-changes-list': GrRelatedChangesList;
Milutin Kristoficf8192162021-04-21 22:12:59 +0200759 'gr-related-collapse': GrRelatedCollapse;
Dmitrii Filippov1d607912020-09-21 13:21:14 +0200760 }
761}