blob: 66569bbba13588217396f6094e4dabe4a8a802e6 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2015 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04005 */
Dmitrii Filippov8814de12020-10-10 16:31:21 +02006import '../gr-change-list/gr-change-list';
7import '../gr-repo-header/gr-repo-header';
8import '../gr-user-header/gr-user-header';
Dmitrii Filippov8814de12020-10-10 16:31:21 +02009import {page} from '../../../utils/page-wrapper-utils';
Ben Rohlfsebe4acc2020-12-11 21:16:10 +010010import {GerritNav} from '../../core/gr-navigation/gr-navigation';
Dmitrii Filippov8814de12020-10-10 16:31:21 +020011import {AppElementParams} from '../../gr-app-types';
12import {
13 AccountDetailInfo,
14 AccountId,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020015 ChangeInfo,
16 EmailAddress,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020017 PreferencesInput,
Paladox none5554d952021-08-11 03:56:51 +000018 RepoName,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020019} from '../../../types/common';
Dmitrii Filippov8814de12020-10-10 16:31:21 +020020import {ChangeStarToggleStarDetail} from '../../shared/gr-change-star/gr-change-star';
Ben Rohlfs5a1bd472022-09-09 13:27:22 +020021import {fireAlert, fireEvent, fireTitleChange} from '../../../utils/event-util';
Chris Poucetc6e880b2021-11-15 19:57:06 +010022import {getAppContext} from '../../../services/app-context';
Ben Rohlfsebe4acc2020-12-11 21:16:10 +010023import {GerritView} from '../../../services/router/router-model';
Paladox none142add02021-12-21 21:06:09 +000024import {sharedStyles} from '../../../styles/shared-styles';
25import {LitElement, PropertyValues, html, css} from 'lit';
Frank Borden42c1a452022-08-11 16:27:20 +020026import {customElement, property, state, query} from 'lit/decorators.js';
Dhruv7c72da52022-06-09 11:43:15 +020027import {ValueChangedEvent} from '../../../types/events';
Ben Rohlfs39aa0572022-09-14 22:57:17 +020028import {createSearchUrl} from '../../../models/views/search';
Kasper Nilssona81a8a32017-03-13 14:46:44 -070029
Ben Rohlfs7b71b112021-02-12 10:36:08 +010030const LOOKUP_QUERY_PATTERNS: RegExp[] = [
31 /^\s*i?[0-9a-f]{7,40}\s*$/i, // CHANGE_ID
32 /^\s*[1-9][0-9]*\s*$/g, // CHANGE_NUM
33 /[0-9a-f]{40}/, // COMMIT
34];
Dhruv Srivastava6f8c9e32020-08-26 12:01:49 +000035
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010036const USER_QUERY_PATTERN = /^owner:\s?("[^"]+"|[^ ]+)$/;
Becky Siegel20789302018-01-05 14:18:05 -080037
Chris Poucetcaeea1b2021-08-19 22:12:56 +000038const REPO_QUERY_PATTERN =
39 /^project:\s?("[^"]+"|[^ ]+)(\sstatus\s?:(open|"open"))?$/;
Logan Hanks29693ac2017-07-12 10:11:21 -070040
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010041const LIMIT_OPERATOR_PATTERN = /\blimit:(\d+)/i;
42
Dmitrii Filippov8814de12020-10-10 16:31:21 +020043@customElement('gr-change-list-view')
Paladox none142add02021-12-21 21:06:09 +000044export class GrChangeListView extends LitElement {
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +010045 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010046 * Fired when the title of the page should change.
47 *
48 * @event title-change
Tao Zhou9a076812019-12-17 09:59:28 +010049 */
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010050
Paladox none142add02021-12-21 21:06:09 +000051 @query('#prevArrow') protected prevArrow?: HTMLAnchorElement;
Andrew Bonventre78792e82016-03-04 17:48:22 -050052
Paladox none142add02021-12-21 21:06:09 +000053 @query('#nextArrow') protected nextArrow?: HTMLAnchorElement;
54
55 @property({type: Object})
56 params?: AppElementParams;
Andrew Bonventre78792e82016-03-04 17:48:22 -050057
Dmitrii Filippov8814de12020-10-10 16:31:21 +020058 @property({type: Object})
59 account: AccountDetailInfo | null = null;
Wyatt Allenfc0c84f2018-03-02 10:52:46 -080060
Paladox none142add02021-12-21 21:06:09 +000061 @property({type: Object})
Dmitrii Filippov8814de12020-10-10 16:31:21 +020062 preferences?: PreferencesInput;
Andrew Bonventre78792e82016-03-04 17:48:22 -050063
Paladox none142add02021-12-21 21:06:09 +000064 // private but used in test
65 @state() changesPerPage?: number;
Kasper Nilsson8e6a64d2018-04-04 15:33:19 -070066
Paladox none142add02021-12-21 21:06:09 +000067 // private but used in test
68 @state() query = '';
Urs Wolfer54a5a462016-03-09 21:04:22 +010069
Paladox none142add02021-12-21 21:06:09 +000070 // private but used in test
71 @state() offset?: number;
Andrew Bonventre78792e82016-03-04 17:48:22 -050072
Paladox none142add02021-12-21 21:06:09 +000073 // private but used in test
74 @state() changes?: ChangeInfo[];
Andrew Bonventre78792e82016-03-04 17:48:22 -050075
Paladox none142add02021-12-21 21:06:09 +000076 // private but used in test
77 @state() loading = true;
Andrew Bonventre78792e82016-03-04 17:48:22 -050078
Paladox none142add02021-12-21 21:06:09 +000079 // private but used in test
80 @state() userId: AccountId | EmailAddress | null = null;
Wyatt Allen5c3a3cd2017-08-30 13:43:37 -070081
Paladox none142add02021-12-21 21:06:09 +000082 // private but used in test
83 @state() repo: RepoName | null = null;
Becky Siegel20789302018-01-05 14:18:05 -080084
Dhruv7c72da52022-06-09 11:43:15 +020085 @state() selectedIndex = 0;
86
Chris Poucetc6e880b2021-11-15 19:57:06 +010087 private readonly restApiService = getAppContext().restApiService;
Ben Rohlfs43935a42020-12-01 19:14:09 +010088
Chris Poucetc6e880b2021-11-15 19:57:06 +010089 private reporting = getAppContext().reportingService;
Dhruv Srivastava5841dd82021-05-17 11:03:19 +020090
Ben Rohlfsf7f1e8e2021-03-12 14:36:40 +010091 constructor() {
92 super();
Paladox none142add02021-12-21 21:06:09 +000093 this.addEventListener('next-page', () => this.handleNextPage());
94 this.addEventListener('previous-page', () => this.handlePreviousPage());
Dhruv Srivastava36ff29d2021-09-22 18:31:02 +020095 this.addEventListener('reload', () => this.reload());
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010096 }
97
Chris Poucet59dad572021-08-20 15:25:36 +000098 override connectedCallback() {
Ben Rohlfs5f520da2021-03-10 14:58:43 +010099 super.connectedCallback();
Paladox none142add02021-12-21 21:06:09 +0000100 this.loadPreferences();
Ben Rohlfs85666c82022-03-21 10:03:17 +0100101 }
102
103 override disconnectedCallback() {
Ben Rohlfs85666c82022-03-21 10:03:17 +0100104 super.disconnectedCallback();
Paladox none142add02021-12-21 21:06:09 +0000105 }
106
107 static override get styles() {
108 return [
109 sharedStyles,
110 css`
111 :host {
112 display: block;
113 }
114 .loading {
115 color: var(--deemphasized-text-color);
116 padding: var(--spacing-l);
117 }
118 gr-change-list {
119 width: 100%;
120 }
121 gr-user-header,
122 gr-repo-header {
123 border-bottom: 1px solid var(--border-color);
124 }
125 nav {
126 align-items: center;
127 display: flex;
128 height: 3rem;
129 justify-content: flex-end;
130 margin-right: 20px;
Paladox none142add02021-12-21 21:06:09 +0000131 color: var(--deemphasized-text-color);
132 }
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200133 gr-icon {
Chris Poucetcf512542022-07-12 09:03:42 +0200134 font-size: 1.85rem;
Paladox none142add02021-12-21 21:06:09 +0000135 margin-left: 16px;
Paladox none142add02021-12-21 21:06:09 +0000136 }
137 .hide {
138 display: none;
139 }
140 @media only screen and (max-width: 50em) {
141 .loading,
142 .error {
143 padding: 0 var(--spacing-l);
144 }
145 }
146 `,
147 ];
148 }
149
150 override render() {
Paladox none142add02021-12-21 21:06:09 +0000151 const loggedIn = !!(this.account && Object.keys(this.account).length > 0);
Dhruv Srivastava45166a12022-03-02 15:30:35 +0100152 // In case of an internal reload we want the ChangeList section components
153 // to remain in the DOM so that the Bulk Actions Model associated with them
154 // is not recreated after the reload resulting in user selections being lost
Paladox none142add02021-12-21 21:06:09 +0000155 return html`
Dhruv Srivastava45166a12022-03-02 15:30:35 +0100156 <div class="loading" ?hidden=${!this.loading}>Loading...</div>
157 <div ?hidden=${this.loading}>
Paladox none142add02021-12-21 21:06:09 +0000158 ${this.renderRepoHeader()} ${this.renderUserHeader(loggedIn)}
159 <gr-change-list
160 .account=${this.account}
161 .changes=${this.changes}
162 .preferences=${this.preferences}
Paladox none142add02021-12-21 21:06:09 +0000163 .showStar=${loggedIn}
Dhruv7c72da52022-06-09 11:43:15 +0200164 .selectedIndex=${this.selectedIndex}
165 @selected-index-changed=${(e: ValueChangedEvent<number>) => {
166 this.selectedIndex = e.detail.value;
167 }}
Paladox none142add02021-12-21 21:06:09 +0000168 @toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => {
169 this.handleToggleStar(e);
170 }}
Chris Poucet12100f12022-07-06 15:11:27 +0200171 .usp=${'search'}
Paladox none142add02021-12-21 21:06:09 +0000172 ></gr-change-list>
173 ${this.renderChangeListViewNav()}
174 </div>
175 `;
176 }
177
178 private renderRepoHeader() {
179 if (!this.repo) return;
180
181 return html` <gr-repo-header .repo=${this.repo}></gr-repo-header> `;
182 }
183
184 private renderUserHeader(loggedIn: boolean) {
185 if (!this.userId) return;
186
187 return html`
188 <gr-user-header
189 .userId=${this.userId}
190 showDashboardLink
191 .loggedIn=${loggedIn}
192 ></gr-user-header>
193 `;
194 }
195
196 private renderChangeListViewNav() {
197 if (this.loading || !this.changes || !this.changes.length) return;
198
199 return html`
200 <nav>
201 Page ${this.computePage()} ${this.renderPrevArrow()}
202 ${this.renderNextArrow()}
203 </nav>
204 `;
205 }
206
207 private renderPrevArrow() {
208 if (this.offset === 0) return;
209
210 return html`
Ben Rohlfs8003bd32022-04-05 18:24:42 +0200211 <a id="prevArrow" href=${this.computeNavLink(-1)}>
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200212 <gr-icon icon="chevron_left" aria-label="Older"></gr-icon>
Paladox none142add02021-12-21 21:06:09 +0000213 </a>
214 `;
215 }
216
217 private renderNextArrow() {
218 if (
219 !(
220 this.changes?.length &&
221 this.changes[this.changes.length - 1]._more_changes
222 )
223 )
224 return;
225
226 return html`
Ben Rohlfs8003bd32022-04-05 18:24:42 +0200227 <a id="nextArrow" href=${this.computeNavLink(1)}>
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200228 <gr-icon icon="chevron_right" aria-label="Newer"></gr-icon>
Paladox none142add02021-12-21 21:06:09 +0000229 </a>
230 `;
231 }
232
233 override willUpdate(changedProperties: PropertyValues) {
234 if (changedProperties.has('params')) {
235 this.paramsChanged();
236 }
237
238 if (changedProperties.has('changes')) {
239 this.changesChanged();
240 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100241 }
242
Dhruv Srivastava36ff29d2021-09-22 18:31:02 +0200243 reload() {
Paladox none142add02021-12-21 21:06:09 +0000244 if (this.loading) return;
245 this.loading = true;
246 this.getChanges().then(changes => {
247 this.changes = changes || [];
248 this.loading = false;
Dhruv Srivastava36ff29d2021-09-22 18:31:02 +0200249 });
250 }
251
Paladox none142add02021-12-21 21:06:09 +0000252 private paramsChanged() {
253 const value = this.params;
254 if (!value || value.view !== GerritView.SEARCH) return;
Dhruv1347c2d2022-06-07 13:05:46 +0200255 const offset = isNaN(Number(value.offset)) ? 0 : Number(value.offset);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100256
Ben Rohlfsa58c5962022-09-12 22:26:14 +0200257 if (this.query !== (value.query ?? '')) {
Dhruv7c72da52022-06-09 11:43:15 +0200258 this.selectedIndex = 0;
259 }
260
Paladox none142add02021-12-21 21:06:09 +0000261 this.loading = true;
Ben Rohlfsa58c5962022-09-12 22:26:14 +0200262 this.query = value.query ?? '';
Dhruv1347c2d2022-06-07 13:05:46 +0200263 this.offset = offset;
Andrew Bonventre78792e82016-03-04 17:48:22 -0500264
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100265 // NOTE: This method may be called before attachment. Fire title-change
266 // in an async so that attachment to the DOM can take place first.
Paladox none142add02021-12-21 21:06:09 +0000267 setTimeout(() => fireTitleChange(this, this.query));
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200268
Ben Rohlfs43935a42020-12-01 19:14:09 +0100269 this.restApiService
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200270 .getPreferences()
271 .then(prefs => {
272 if (!prefs) {
273 throw new Error('getPreferences returned undefined');
274 }
Paladox none142add02021-12-21 21:06:09 +0000275 this.changesPerPage = prefs.changes_per_page;
276 return this.getChanges();
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200277 })
278 .then(changes => {
279 changes = changes || [];
Paladox none142add02021-12-21 21:06:09 +0000280 if (this.query && changes.length === 1) {
Ben Rohlfs7b71b112021-02-12 10:36:08 +0100281 for (const queryPattern of LOOKUP_QUERY_PATTERNS) {
Paladox none142add02021-12-21 21:06:09 +0000282 if (this.query.match(queryPattern)) {
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200283 // "Back"/"Forward" buttons work correctly only with
284 // opt_redirect options
Dhruv Srivastava0633ae12021-11-11 15:16:47 +0100285 GerritNav.navigateToChange(changes[0], {
286 redirect: true,
287 });
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200288 return;
Dhruv Srivastava6f8c9e32020-08-26 12:01:49 +0000289 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100290 }
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200291 }
Paladox none142add02021-12-21 21:06:09 +0000292 this.changes = changes;
293 this.loading = false;
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200294 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100295 }
Andrew Bonventre78792e82016-03-04 17:48:22 -0500296
Paladox none142add02021-12-21 21:06:09 +0000297 private loadPreferences() {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100298 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100299 if (loggedIn) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100300 this.restApiService.getPreferences().then(preferences => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100301 this.preferences = preferences;
302 });
303 } else {
304 this.preferences = {};
Logan Hanks29693ac2017-07-12 10:11:21 -0700305 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100306 });
307 }
Logan Hanks29693ac2017-07-12 10:11:21 -0700308
Paladox none142add02021-12-21 21:06:09 +0000309 // private but used in test
310 getChanges() {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100311 return this.restApiService.getChanges(
Paladox none142add02021-12-21 21:06:09 +0000312 this.changesPerPage,
313 this.query,
314 this.offset
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200315 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100316 }
Andrew Bonventre78792e82016-03-04 17:48:22 -0500317
Paladox none142add02021-12-21 21:06:09 +0000318 // private but used in test
319 limitFor(query: string, defaultLimit?: number) {
320 if (defaultLimit === undefined) return 0;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100321 const match = query.match(LIMIT_OPERATOR_PATTERN);
322 if (!match) {
323 return defaultLimit;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100324 }
Dhruv Srivastavab8edee92020-10-19 10:20:07 +0200325 return Number(match[1]);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100326 }
Kasper Nilsson97240922018-02-08 14:57:27 -0800327
Paladox none142add02021-12-21 21:06:09 +0000328 // private but used in test
329 computeNavLink(direction: number) {
330 const offset = this.offset ?? 0;
331 const limit = this.limitFor(this.query, this.changesPerPage);
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200332 const newOffset = Math.max(0, offset + limit * direction);
Ben Rohlfs39aa0572022-09-14 22:57:17 +0200333 return createSearchUrl({query: this.query, offset: newOffset});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100334 }
335
Paladox none142add02021-12-21 21:06:09 +0000336 // private but used in test
337 handleNextPage() {
338 if (!this.nextArrow || !this.changesPerPage) return;
339 page.show(this.computeNavLink(1));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100340 }
341
Paladox none142add02021-12-21 21:06:09 +0000342 // private but used in test
343 handlePreviousPage() {
344 if (!this.prevArrow || !this.changesPerPage) return;
345 page.show(this.computeNavLink(-1));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100346 }
347
Paladox none142add02021-12-21 21:06:09 +0000348 private changesChanged() {
349 this.userId = null;
350 this.repo = null;
351 const changes = this.changes;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100352 if (!changes || !changes.length) {
353 return;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100354 }
Paladox none142add02021-12-21 21:06:09 +0000355 if (USER_QUERY_PATTERN.test(this.query)) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100356 const owner = changes[0].owner;
357 const userId = owner._account_id ? owner._account_id : owner.email;
358 if (userId) {
Paladox none142add02021-12-21 21:06:09 +0000359 this.userId = userId;
Wyatt Allen5c3a3cd2017-08-30 13:43:37 -0700360 return;
361 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100362 }
Paladox none142add02021-12-21 21:06:09 +0000363 if (REPO_QUERY_PATTERN.test(this.query)) {
364 this.repo = changes[0].project;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100365 }
366 }
367
Paladox none142add02021-12-21 21:06:09 +0000368 // private but used in test
369 computePage() {
370 if (this.offset === undefined || this.changesPerPage === undefined) return;
371 return this.offset / this.changesPerPage + 1;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100372 }
373
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200374 private async handleToggleStar(e: CustomEvent<ChangeStarToggleStarDetail>) {
Dhruv Srivastava5841dd82021-05-17 11:03:19 +0200375 if (e.detail.starred) {
376 this.reporting.reportInteraction('change-starred-from-change-list');
377 }
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200378 const msg = e.detail.starred
379 ? 'Starring change...'
380 : 'Unstarring change...';
381 fireAlert(this, msg);
382 await this.restApiService.saveChangeStarred(
Ben Rohlfs43935a42020-12-01 19:14:09 +0100383 e.detail.change._number,
384 e.detail.starred
385 );
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200386 fireEvent(this, 'hide-alert');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100387 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100388}
389
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200390declare global {
391 interface HTMLElementTagNameMap {
392 'gr-change-list-view': GrChangeListView;
393 }
394}