blob: 885827ac7f720b5b102027044831947222fe0aa2 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
3 * Copyright (C) 2016 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 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020017import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
18import {PolymerElement} from '@polymer/polymer/polymer-element';
19import {
20 page,
21 PageContext,
22 PageNextCallback,
23} from '../../../utils/page-wrapper-utils';
24import {htmlTemplate} from './gr-router_html';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020025import {
26 DashboardSection,
Ben Rohlfsebe4acc2020-12-11 21:16:10 +010027 GeneratedWebLink,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020028 GenerateUrlChangeViewParameters,
29 GenerateUrlDashboardViewParameters,
30 GenerateUrlDiffViewParameters,
31 GenerateUrlEditViewParameters,
32 GenerateUrlGroupViewParameters,
33 GenerateUrlParameters,
34 GenerateUrlRepoViewParameters,
35 GenerateUrlSearchViewParameters,
36 GenerateWebLinksChangeParameters,
37 GenerateWebLinksFileParameters,
38 GenerateWebLinksParameters,
39 GenerateWebLinksPatchsetParameters,
Ben Rohlfsebe4acc2020-12-11 21:16:10 +010040 GerritNav,
41 GroupDetailView,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020042 isGenerateUrlDiffViewParameters,
43 RepoDetailView,
44 WeblinkType,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020045} from '../gr-navigation/gr-navigation';
46import {appContext} from '../../../services/app-context';
Dhruv Srivastavad1da4582021-01-11 16:34:19 +010047import {convertToPatchSetNum} from '../../../utils/patch-set-util';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020048import {customElement, property} from '@polymer/decorators';
49import {assertNever} from '../../../utils/common-util';
50import {
Dhruv Srivastava591b4902021-03-10 11:48:12 +010051 BasePatchSetNum,
Dmitrii Filippov12f22a92020-10-12 16:16:30 +020052 DashboardId,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020053 GroupId,
54 NumericChangeId,
55 PatchSetNum,
56 RepoName,
57 ServerInfo,
58 UrlEncodedCommentId,
59} from '../../../types/common';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020060import {
61 AppElement,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020062 AppElementAgreementParam,
Ben Rohlfsebe4acc2020-12-11 21:16:10 +010063 AppElementParams,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020064} from '../../gr-app-types';
Dmitrii Filippov6a038002020-10-14 18:50:07 +020065import {LocationChangeEventDetail} from '../../../types/events';
Ben Rohlfsebe4acc2020-12-11 21:16:10 +010066import {GerritView, updateState} from '../../../services/router/router-model';
Ben Rohlfsa76c82f2021-01-22 22:22:32 +010067import {firePageError} from '../../../utils/event-util';
Milutin Kristoficdfb781d2021-01-29 13:24:43 +010068import {addQuotesWhen} from '../../../utils/string-util';
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +010069import {windowLocationReload} from '../../../utils/dom-util';
Milutin Kristofice366b932021-03-10 17:09:52 +010070import {
71 encodeURL,
72 getBaseUrl,
73 toPath,
74 toPathname,
75 toSearchParams,
76} from '../../../utils/url-util';
Ben Rohlfs6fb09dd2021-03-12 09:24:26 +010077import {Execution, LifeCycle} from '../../../constants/reporting';
Wyatt Allenee2016c2017-10-31 13:41:52 -070078
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010079const RoutePattern = {
80 ROOT: '/',
Wyatt Allenee2016c2017-10-31 13:41:52 -070081
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010082 DASHBOARD: /^\/dashboard\/(.+)$/,
83 CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
84 PROJECT_DASHBOARD: /^\/p\/(.+)\/\+\/dashboard\/(.+)/,
Jacek Centkowskid322bd42020-09-16 10:34:55 +020085 LEGACY_PROJECT_DASHBOARD: /^\/projects\/(.+),dashboards\/(.+)/,
Wyatt Allen089dfb32017-08-24 17:55:38 -070086
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010087 AGREEMENTS: /^\/settings\/agreements\/?/,
88 NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
89 REGISTER: /^\/register(\/.*)?$/,
Wyatt Allen6376e7a2017-08-31 10:11:59 -070090
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010091 // Pattern for login and logout URLs intended to be passed-through. May
92 // include a return URL.
93 LOG_IN_OR_OUT: /\/log(in|out)(\/(.+))?$/,
Wyatt Allenf1971382017-08-29 13:22:15 -070094
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010095 // Pattern for a catchall route when no other pattern is matched.
96 DEFAULT: /.*/,
Wyatt Allen089dfb32017-08-24 17:55:38 -070097
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010098 // Matches /admin/groups/[uuid-]<group>
99 GROUP: /^\/admin\/groups\/(?:uuid-)?([^,]+)$/,
Paladox nonef7303f72019-06-27 14:57:11 +0000100
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100101 // Redirects /groups/self to /settings/#Groups for GWT compatibility
102 GROUP_SELF: /^\/groups\/self/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700103
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100104 // Matches /admin/groups/[uuid-]<group>,info (backwords compat with gwtui)
105 // Redirects to /admin/groups/[uuid-]<group>
106 GROUP_INFO: /^\/admin\/groups\/(?:uuid-)?(.+),info$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700107
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100108 // Matches /admin/groups/<group>,audit-log
109 GROUP_AUDIT_LOG: /^\/admin\/groups\/(?:uuid-)?(.+),audit-log$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700110
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100111 // Matches /admin/groups/[uuid-]<group>,members
112 GROUP_MEMBERS: /^\/admin\/groups\/(?:uuid-)?(.+),members$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700113
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100114 // Matches /admin/groups[,<offset>][/].
115 GROUP_LIST_OFFSET: /^\/admin\/groups(,(\d+))?(\/)?$/,
116 GROUP_LIST_FILTER: '/admin/groups/q/filter::filter',
117 GROUP_LIST_FILTER_OFFSET: '/admin/groups/q/filter::filter,:offset',
Becky Siegel28d1bf62017-09-01 12:07:09 -0700118
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100119 // Matches /admin/create-project
120 LEGACY_CREATE_PROJECT: /^\/admin\/create-project\/?$/,
Becky Siegel28d1bf62017-09-01 12:07:09 -0700121
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100122 // Matches /admin/create-project
123 LEGACY_CREATE_GROUP: /^\/admin\/create-group\/?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700124
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100125 PROJECT_OLD: /^\/admin\/(projects)\/?(.+)?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700126
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100127 // Matches /admin/repos/<repo>
128 REPO: /^\/admin\/repos\/([^,]+)$/,
Becky Siegel6db432f2017-08-25 09:17:42 -0700129
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100130 // Matches /admin/repos/<repo>,commands.
131 REPO_COMMANDS: /^\/admin\/repos\/(.+),commands$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700132
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100133 // Matches /admin/repos/<repos>,access.
134 REPO_ACCESS: /^\/admin\/repos\/(.+),access$/,
Becky Siegelc588ab72018-01-16 17:43:10 -0800135
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100136 // Matches /admin/repos/<repos>,access.
137 REPO_DASHBOARDS: /^\/admin\/repos\/(.+),dashboards$/,
Paladox none2bd5c212017-11-16 18:54:02 +0000138
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100139 // Matches /admin/repos[,<offset>][/].
140 REPO_LIST_OFFSET: /^\/admin\/repos(,(\d+))?(\/)?$/,
141 REPO_LIST_FILTER: '/admin/repos/q/filter::filter',
142 REPO_LIST_FILTER_OFFSET: '/admin/repos/q/filter::filter,:offset',
Wyatt Allen089dfb32017-08-24 17:55:38 -0700143
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100144 // Matches /admin/repos/<repo>,branches[,<offset>].
145 BRANCH_LIST_OFFSET: /^\/admin\/repos\/(.+),branches(,(.+))?$/,
146 BRANCH_LIST_FILTER: '/admin/repos/:repo,branches/q/filter::filter',
147 BRANCH_LIST_FILTER_OFFSET:
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200148 '/admin/repos/:repo,branches/q/filter::filter,:offset',
Wyatt Allen089dfb32017-08-24 17:55:38 -0700149
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100150 // Matches /admin/repos/<repo>,tags[,<offset>].
151 TAG_LIST_OFFSET: /^\/admin\/repos\/(.+),tags(,(.+))?$/,
152 TAG_LIST_FILTER: '/admin/repos/:repo,tags/q/filter::filter',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200153 TAG_LIST_FILTER_OFFSET: '/admin/repos/:repo,tags/q/filter::filter,:offset',
Paladox none3bfada82017-09-01 09:29:21 +0000154
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100155 PLUGINS: /^\/plugins\/(.+)$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700156
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100157 PLUGIN_LIST: /^\/admin\/plugins(\/)?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700158
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100159 // Matches /admin/plugins[,<offset>][/].
160 PLUGIN_LIST_OFFSET: /^\/admin\/plugins(,(\d+))?(\/)?$/,
161 PLUGIN_LIST_FILTER: '/admin/plugins/q/filter::filter',
162 PLUGIN_LIST_FILTER_OFFSET: '/admin/plugins/q/filter::filter,:offset',
Wyatt Allen089dfb32017-08-24 17:55:38 -0700163
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100164 QUERY: /^\/q\/([^,]+)(,(\d+))?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700165
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700166 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100167 * Support vestigial params from GWT UI.
Tao Zhou9a076812019-12-17 09:59:28 +0100168 *
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100169 * @see Issue 7673.
170 * @type {!RegExp}
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700171 */
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100172 QUERY_LEGACY_SUFFIX: /^\/q\/.+,n,z$/,
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700173
Peter Collingbourne8cab9332020-08-18 14:42:44 -0700174 CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
175
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100176 // Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
177 CHANGE_LEGACY: /^\/c\/(\d+)\/?(((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
178 CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
Logan Hanksaa501d02017-09-30 04:07:34 -0700179
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100180 // Matches
181 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..][<patchNum|edit>].
182 // TODO(kaspern): Migrate completely to project based URLs, with backwards
183 // compatibility for change-only.
184 CHANGE: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
Logan Hanksaa501d02017-09-30 04:07:34 -0700185
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100186 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>],edit
187 CHANGE_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(\d+))?,edit\/?$/,
Wyatt Allen84f0a572017-11-06 15:45:58 -0800188
Dhruv Srivastava90240452020-07-02 15:51:53 +0200189 // Matches /c/<project>/+/<changeNum>/comment/<commentId>/
190 // Navigates to the diff view
191 // This route is needed to resolve to patchNum vs latestPatchNum used in the
192 // links generated in the emails.
193 COMMENT: /^\/c\/(.+)\/\+\/(\d+)\/comment\/(\w+)\/?$/,
194
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100195 // Matches
196 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..]<patchNum|edit>/<path>.
197 // TODO(kaspern): Migrate completely to project based URLs, with backwards
198 // compatibility for change-only.
199 // eslint-disable-next-line max-len
200 DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))))\/?$/,
Wyatt Allen52d76132017-11-06 16:58:52 -0800201
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100202 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>]/<path>,edit[#lineNum]
David Ostrovskycfe65302020-05-07 22:10:38 +0200203 DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
Wyatt Allen02add882018-08-13 19:06:52 -0700204
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100205 // Matches non-project-relative
206 // /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
207 DIFF_LEGACY: /^\/c\/(\d+)\/((-?\d+|edit)(\.\.(\d+|edit))?)\/(.+)/,
208
209 // Matches diff routes using @\d+ to specify a file name (whether or not
210 // the project name is included).
211 // eslint-disable-next-line max-len
212 DIFF_LEGACY_LINENUM: /^\/c\/((.+)\/\+\/)?(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?\/(.+))?)@[ab]?\d+$/,
213
214 SETTINGS: /^\/settings\/?/,
215 SETTINGS_LEGACY: /^\/settings\/VE\/(\S+)/,
216
217 // Matches /c/<changeNum>/ /<URL tail>
218 // Catches improperly encoded URLs (context: Issue 7100)
David Ostrovskycfe65302020-05-07 22:10:38 +0200219 IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/ \/(.+)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100220
221 PLUGIN_SCREEN: /^\/x\/([\w-]+)\/([\w-]+)\/?/,
222
223 DOCUMENTATION_SEARCH_FILTER: '/Documentation/q/filter::filter',
224 DOCUMENTATION_SEARCH: /^\/Documentation\/q\/(.*)$/,
225 DOCUMENTATION: /^\/Documentation(\/)?(.+)?/,
226};
227
Dhruv Srivastava90240452020-07-02 15:51:53 +0200228export const _testOnly_RoutePattern = RoutePattern;
229
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100230/**
231 * Pattern to recognize and parse the diff line locations as they appear in
232 * the hash of diff URLs. In this format, a number on its own indicates that
233 * line number in the revision of the diff. A number prefixed by either an 'a'
234 * or a 'b' indicates that line number of the base of the diff.
235 *
236 * @type {RegExp}
237 */
238const LINE_ADDRESS_PATTERN = /^([ab]?)(\d+)$/;
239
240/**
241 * Pattern to recognize '+' in url-encoded strings for replacement with ' '.
242 */
243const PLUS_PATTERN = /\+/g;
244
245/**
246 * Pattern to recognize leading '?' in window.location.search, for stripping.
247 */
248const QUESTION_PATTERN = /^\?*/;
249
250/**
251 * GWT UI would use @\d+ at the end of a path to indicate linenum.
252 */
253const LEGACY_LINENUM_PATTERN = /@([ab]?\d+)$/;
254
255const LEGACY_QUERY_SUFFIX_PATTERN = /,n,z$/;
256
David Ostrovskycfe65302020-05-07 22:10:38 +0200257const REPO_TOKEN_PATTERN = /\${(project|repo)}/g;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100258
259// Polymer makes `app` intrinsically defined on the window by virtue of the
260// custom element having the id "app", but it is made explicit here.
Dmitrii Filippov6dbb1712020-03-19 12:11:50 +0100261// If you move this code to other place, please update comment about
262// gr-router and gr-app in the PolyGerritIndexHtml.soy file if needed
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100263const app = document.querySelector('#app');
264if (!app) {
Tao Zhoucd01d8f2020-07-22 12:35:31 +0200265 console.info('No gr-app found (running tests)');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100266}
267
268// Setup listeners outside of the router component initialization.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200269(function () {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100270 window.addEventListener('WebComponentsReady', () => {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100271 appContext.reportingService.timeEnd('WebComponentsReady');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100272 });
273})();
274
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200275export interface PageContextWithQueryMap extends PageContext {
276 queryMap: Map<string, string> | URLSearchParams;
277}
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100278
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200279type QueryStringItem = [string, string]; // [key, value]
280
281type GenerateUrlLegacyChangeViewParameters = Omit<
282 GenerateUrlChangeViewParameters,
283 'project'
284>;
285type GenerateUrlLegacyDiffViewParameters = Omit<
286 GenerateUrlDiffViewParameters,
287 'project'
288>;
289
290interface PatchRangeParams {
291 patchNum?: PatchSetNum | null;
Dhruv Srivastava591b4902021-03-10 11:48:12 +0100292 basePatchNum?: BasePatchSetNum | null;
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200293}
294
295@customElement('gr-router')
Ben Rohlfsd94011e2021-03-08 23:37:58 +0100296export class GrRouter extends LegacyElementMixin(PolymerElement) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200297 static get template() {
298 return htmlTemplate;
Viktar Donich66067042016-08-25 15:54:19 -0700299 }
Andrew Bonventref3b33a02016-03-29 13:30:10 -0400300
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200301 @property({type: Object})
302 readonly _app = app;
303
304 @property({type: Boolean})
305 _isRedirecting?: boolean;
306
307 // This variable is to differentiate between internal navigation (false)
308 // and for first navigation in app after loaded from server (true).
309 @property({type: Boolean})
310 _isInitialLoad = true;
311
312 private readonly reporting = appContext.reportingService;
313
Ben Rohlfs43935a42020-12-01 19:14:09 +0100314 private readonly restApiService = appContext.restApiService;
315
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100316 start() {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200317 if (!this._app) {
318 return;
319 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100320 this._startRouter();
321 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800322
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200323 _setParams(params: AppElementParams | GenerateUrlParameters) {
Ben Rohlfsebe4acc2020-12-11 21:16:10 +0100324 updateState(
325 params.view,
326 'changeNum' in params ? params.changeNum : undefined,
327 'patchNum' in params ? params.patchNum ?? undefined : undefined
328 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100329 this._appElement().params = params;
330 }
331
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200332 _appElement(): AppElement {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100333 // In Polymer2 you have to reach through the shadow root of the app
334 // element. This obviously breaks encapsulation.
335 // TODO(brohlfs): Make this more elegant, e.g. by exposing app-element
336 // explicitly in app, or by delegating to it.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200337
338 // It is expected that application has a GrAppElement(id=='app-element')
339 // at the document level or inside the shadow root of the GrApp (id='app')
340 // element.
341 return (document.getElementById('app-element') ||
342 document
343 .getElementById('app')!
344 .shadowRoot!.getElementById('app-element')!) as AppElement;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100345 }
346
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200347 _redirect(url: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100348 this._isRedirecting = true;
349 page.redirect(url);
350 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800351
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200352 _generateUrl(params: GenerateUrlParameters) {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200353 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100354 let url = '';
Wyatt Allen84505ea2017-08-24 10:53:05 -0700355
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200356 if (params.view === GerritView.SEARCH) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100357 url = this._generateSearchUrl(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200358 } else if (params.view === GerritView.CHANGE) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100359 url = this._generateChangeUrl(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200360 } else if (params.view === GerritView.DASHBOARD) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100361 url = this._generateDashboardUrl(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200362 } else if (
363 params.view === GerritView.DIFF ||
364 params.view === GerritView.EDIT
365 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100366 url = this._generateDiffOrEditUrl(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200367 } else if (params.view === GerritView.GROUP) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100368 url = this._generateGroupUrl(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200369 } else if (params.view === GerritView.REPO) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100370 url = this._generateRepoUrl(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200371 } else if (params.view === GerritView.ROOT) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100372 url = '/';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200373 } else if (params.view === GerritView.SETTINGS) {
374 url = this._generateSettingsUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100375 } else {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200376 assertNever(params, "Can't generate");
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100377 }
Wyatt Allen84505ea2017-08-24 10:53:05 -0700378
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100379 return base + url;
380 }
381
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200382 _generateWeblinks(
383 params: GenerateWebLinksParameters
384 ): GeneratedWebLink[] | GeneratedWebLink {
385 switch (params.type) {
386 case WeblinkType.FILE:
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100387 return this._getFileWebLinks(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200388 case WeblinkType.CHANGE:
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100389 return this._getChangeWeblinks(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200390 case WeblinkType.PATCHSET:
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100391 return this._getPatchSetWeblink(params);
392 default:
Dhruv Srivastavaf97b0552020-11-30 14:14:09 +0100393 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Dhruv Srivastavab68c85f2020-11-30 19:04:34 +0100394 assertNever(params, `Unsupported weblink ${(params as any).type}!`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100395 }
396 }
397
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200398 _getPatchSetWeblink(
399 params: GenerateWebLinksPatchsetParameters
400 ): GeneratedWebLink {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100401 const {commit, options} = params;
402 const {weblinks, config} = options || {};
403 const name = commit && commit.slice(0, 7);
404 const weblink = this._getBrowseCommitWeblink(weblinks, config);
405 if (!weblink || !weblink.url) {
406 return {name};
407 } else {
408 return {name, url: weblink.url};
409 }
410 }
411
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200412 _firstCodeBrowserWeblink(weblinks: GeneratedWebLink[]) {
Tao Zhou9a8951d2020-06-30 11:18:09 +0200413 // This is an ordered allowed list of web link types that provide direct
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100414 // links to the commit in the url property.
415 const codeBrowserLinks = ['gitiles', 'browse', 'gitweb'];
416 for (let i = 0; i < codeBrowserLinks.length; i++) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200417 const weblink = weblinks.find(
418 weblink => weblink.name === codeBrowserLinks[i]
419 );
420 if (weblink) {
421 return weblink;
422 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100423 }
424 return null;
425 }
426
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200427 _getBrowseCommitWeblink(weblinks?: GeneratedWebLink[], config?: ServerInfo) {
428 if (!weblinks) {
429 return null;
430 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100431 let weblink;
432 // Use primary weblink if configured and exists.
Tao Zhou34b0f122020-08-31 13:44:50 +0200433 if (config?.gerrit?.primary_weblink_name) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200434 const primaryWeblinkName = config.gerrit.primary_weblink_name;
435 weblink = weblinks.find(weblink => weblink.name === primaryWeblinkName);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100436 }
437 if (!weblink) {
438 weblink = this._firstCodeBrowserWeblink(weblinks);
439 }
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200440 if (!weblink) {
441 return null;
442 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100443 return weblink;
444 }
445
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200446 _getChangeWeblinks(
447 params: GenerateWebLinksChangeParameters
448 ): GeneratedWebLink[] {
449 const weblinks = params.options?.weblinks;
450 const config = params.options?.config;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100451 if (!weblinks || !weblinks.length) return [];
452 const commitWeblink = this._getBrowseCommitWeblink(weblinks, config);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200453 return weblinks.filter(
454 weblink =>
455 !commitWeblink ||
456 !commitWeblink.name ||
457 weblink.name !== commitWeblink.name
458 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100459 }
460
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200461 _getFileWebLinks(params: GenerateWebLinksFileParameters): GeneratedWebLink[] {
462 return params.options?.weblinks || [];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100463 }
464
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200465 _generateSearchUrl(params: GenerateUrlSearchViewParameters) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100466 let offsetExpr = '';
467 if (params.offset && params.offset > 0) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200468 offsetExpr = `,${params.offset}`;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100469 }
Wyatt Allen84505ea2017-08-24 10:53:05 -0700470
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100471 if (params.query) {
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +0200472 return '/q/' + encodeURL(params.query, true) + offsetExpr;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100473 }
Ben Rohlfse3b45422019-06-07 09:36:34 +0200474
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200475 const operators: string[] = [];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100476 if (params.owner) {
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +0200477 operators.push('owner:' + encodeURL(params.owner, false));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100478 }
479 if (params.project) {
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +0200480 operators.push('project:' + encodeURL(params.project, false));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100481 }
482 if (params.branch) {
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +0200483 operators.push('branch:' + encodeURL(params.branch, false));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100484 }
485 if (params.topic) {
Milutin Kristoficdfb781d2021-01-29 13:24:43 +0100486 operators.push(
487 'topic:' +
488 addQuotesWhen(encodeURL(params.topic, false), /\s/.test(params.topic))
489 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100490 }
491 if (params.hashtag) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200492 operators.push(
Milutin Kristoficdfb781d2021-01-29 13:24:43 +0100493 'hashtag:' +
494 addQuotesWhen(
495 encodeURL(params.hashtag.toLowerCase(), false),
496 /\s/.test(params.hashtag)
497 )
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200498 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100499 }
500 if (params.statuses) {
501 if (params.statuses.length === 1) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200502 operators.push('status:' + encodeURL(params.statuses[0], false));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100503 } else if (params.statuses.length > 1) {
504 operators.push(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200505 '(' +
506 params.statuses
507 .map(s => `status:${encodeURL(s, false)}`)
508 .join(' OR ') +
509 ')'
510 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100511 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100512 }
Wyatt Allen84505ea2017-08-24 10:53:05 -0700513
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100514 return '/q/' + operators.join('+') + offsetExpr;
515 }
516
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200517 _generateChangeUrl(params: GenerateUrlChangeViewParameters) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100518 let range = this._getPatchRangeExpression(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200519 if (range.length) {
520 range = '/' + range;
521 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100522 let suffix = `${range}`;
523 if (params.querystring) {
524 suffix += '?' + params.querystring;
525 } else if (params.edit) {
526 suffix += ',edit';
527 }
528 if (params.messageHash) {
529 suffix += params.messageHash;
530 }
531 if (params.project) {
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +0200532 const encodedProject = encodeURL(params.project, true);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100533 return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
534 } else {
535 return `/c/${params.changeNum}${suffix}`;
536 }
537 }
538
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200539 _generateDashboardUrl(params: GenerateUrlDashboardViewParameters) {
540 const repoName = params.repo || params.project || undefined;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100541 if (params.sections) {
542 // Custom dashboard.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200543 const queryParams = this._sectionsToEncodedParams(
544 params.sections,
545 repoName
546 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100547 if (params.title) {
548 queryParams.push('title=' + encodeURIComponent(params.title));
549 }
550 const user = params.user ? params.user : '';
551 return `/dashboard/${user}?${queryParams.join('&')}`;
552 } else if (repoName) {
553 // Project dashboard.
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +0200554 const encodedRepo = encodeURL(repoName, true);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100555 return `/p/${encodedRepo}/+/dashboard/${params.dashboard}`;
556 } else {
557 // User dashboard.
558 return `/dashboard/${params.user || 'self'}`;
559 }
560 }
561
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200562 _sectionsToEncodedParams(sections: DashboardSection[], repoName?: RepoName) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100563 return sections.map(section => {
564 // If there is a repo name provided, make sure to substitute it into the
565 // ${repo} (or legacy ${project}) query tokens.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200566 const query = repoName
567 ? section.query.replace(REPO_TOKEN_PATTERN, repoName)
568 : section.query;
569 return encodeURIComponent(section.name) + '=' + encodeURIComponent(query);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100570 });
571 }
572
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200573 _generateDiffOrEditUrl(
574 params: GenerateUrlDiffViewParameters | GenerateUrlEditViewParameters
575 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100576 let range = this._getPatchRangeExpression(params);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200577 if (range.length) {
578 range = '/' + range;
579 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100580
Dhruv Srivastava2c35afe2020-04-28 12:16:56 +0200581 let suffix = `${range}/${encodeURL(params.path || '', true)}`;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100582
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200583 if (params.view === GerritView.EDIT) {
584 suffix += ',edit';
585 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100586
587 if (params.lineNum) {
588 suffix += '#';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200589 if (isGenerateUrlDiffViewParameters(params) && params.leftSide) {
590 suffix += 'b';
591 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100592 suffix += params.lineNum;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100593 }
Wyatt Allen797480f2017-06-08 09:20:46 -0700594
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200595 if (isGenerateUrlDiffViewParameters(params) && params.commentId) {
Dhruv Srivastava2c35afe2020-04-28 12:16:56 +0200596 suffix = `/comment/${params.commentId}` + suffix;
597 }
598
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100599 if (params.project) {
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +0200600 const encodedProject = encodeURL(params.project, true);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100601 return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
602 } else {
603 return `/c/${params.changeNum}${suffix}`;
604 }
605 }
Wyatt Allen797480f2017-06-08 09:20:46 -0700606
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200607 _generateGroupUrl(params: GenerateUrlGroupViewParameters) {
608 let url = `/admin/groups/${encodeURL(`${params.groupId}`, true)}`;
609 if (params.detail === GroupDetailView.MEMBERS) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100610 url += ',members';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200611 } else if (params.detail === GroupDetailView.LOG) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100612 url += ',audit-log';
613 }
614 return url;
615 }
616
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200617 _generateRepoUrl(params: GenerateUrlRepoViewParameters) {
618 let url = `/admin/repos/${encodeURL(`${params.repoName}`, true)}`;
619 if (params.detail === RepoDetailView.ACCESS) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100620 url += ',access';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200621 } else if (params.detail === RepoDetailView.BRANCHES) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100622 url += ',branches';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200623 } else if (params.detail === RepoDetailView.TAGS) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100624 url += ',tags';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200625 } else if (params.detail === RepoDetailView.COMMANDS) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100626 url += ',commands';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200627 } else if (params.detail === RepoDetailView.DASHBOARDS) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100628 url += ',dashboards';
629 }
630 return url;
631 }
632
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200633 _generateSettingsUrl() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100634 return '/settings';
635 }
636
637 /**
638 * Given an object of parameters, potentially including a `patchNum` or a
639 * `basePatchNum` or both, return a string representation of that range. If
640 * no range is indicated in the params, the empty string is returned.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100641 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200642 _getPatchRangeExpression(params: PatchRangeParams) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100643 let range = '';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200644 if (params.patchNum) {
645 range = `${params.patchNum}`;
646 }
647 if (params.basePatchNum) {
648 range = `${params.basePatchNum}..${range}`;
649 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100650 return range;
651 }
652
653 /**
654 * Given a set of params without a project, gets the project from the rest
655 * API project lookup and then sets the app params.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100656 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200657 _normalizeLegacyRouteParams(
658 params: Readonly<
659 | GenerateUrlLegacyChangeViewParameters
660 | GenerateUrlLegacyDiffViewParameters
661 >
662 ) {
663 if (!params.changeNum) {
664 return Promise.resolve();
665 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100666
Ben Rohlfs43935a42020-12-01 19:14:09 +0100667 return this.restApiService
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200668 .getFromProjectLookup(params.changeNum)
669 .then(project => {
670 // Show a 404 and terminate if the lookup request failed. Attempting
671 // to redirect after failing to get the project loops infinitely.
672 if (!project) {
673 this._show404();
674 return;
675 }
676 const updatedParams:
677 | GenerateUrlChangeViewParameters
678 | GenerateUrlDiffViewParameters = {...params, project};
679 this._normalizePatchRangeParams(updatedParams);
680 this._redirect(this._generateUrl(updatedParams));
681 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100682 }
683
684 /**
685 * Normalizes the params object, and determines if the URL needs to be
686 * modified to fit the proper schema.
687 *
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100688 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200689 _normalizePatchRangeParams(params: PatchRangeParams) {
690 if (params.basePatchNum === null || params.basePatchNum === undefined) {
691 return false;
692 }
693 const hasPatchNum =
694 params.patchNum !== null && params.patchNum !== undefined;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100695 let needsRedirect = false;
696
697 // Diffing a patch against itself is invalid, so if the base and revision
698 // patches are equal clear the base.
Dhruv Srivastavad1da4582021-01-11 16:34:19 +0100699 if (params.patchNum && params.basePatchNum === params.patchNum) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100700 needsRedirect = true;
701 params.basePatchNum = null;
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200702 } else if (!hasPatchNum) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100703 // Regexes set basePatchNum instead of patchNum when only one is
704 // specified. Redirect is not needed in this case.
705 params.patchNum = params.basePatchNum;
706 params.basePatchNum = null;
707 }
708 return needsRedirect;
709 }
710
711 /**
712 * Redirect the user to login using the given return-URL for redirection
713 * after authentication success.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100714 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200715 _redirectToLogin(returnUrl: string) {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200716 const basePath = getBaseUrl() || '';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200717 page('/login/' + encodeURIComponent(returnUrl.substring(basePath.length)));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100718 }
719
720 /**
721 * Hashes parsed by page.js exclude "inner" hashes, so a URL like "/a#b#c"
722 * is parsed to have a hash of "b" rather than "b#c". Instead, this method
723 * parses hashes correctly. Will return an empty string if there is no hash.
724 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200725 * @return Everything after the first '#' ("a#b#c" -> "b#c").
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100726 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200727 _getHashFromCanonicalPath(canonicalPath: string) {
728 return canonicalPath.split('#').slice(1).join('#');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100729 }
730
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200731 _parseLineAddress(hash: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100732 const match = hash.match(LINE_ADDRESS_PATTERN);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200733 if (!match) {
734 return null;
735 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100736 return {
737 leftSide: !!match[1],
Dhruv Srivastavab8edee92020-10-19 10:20:07 +0200738 lineNum: Number(match[2]),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100739 };
740 }
741
742 /**
743 * Check to see if the user is logged in and return a promise that only
744 * resolves if the user is logged in. If the user us not logged in, the
745 * promise is rejected and the page is redirected to the login flow.
746 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200747 * @return A promise yielding the original route data
748 * (if it resolves).
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100749 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200750 _redirectIfNotLoggedIn(data: PageContext) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100751 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100752 if (loggedIn) {
753 return Promise.resolve();
Wyatt Allen797480f2017-06-08 09:20:46 -0700754 } else {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100755 this._redirectToLogin(data.canonicalPath);
756 return Promise.reject(new Error());
Wyatt Allen797480f2017-06-08 09:20:46 -0700757 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100758 });
759 }
Wyatt Allen797480f2017-06-08 09:20:46 -0700760
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100761 /** Page.js middleware that warms the REST API's logged-in cache line. */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200762 _loadUserMiddleware(_: PageContext, next: PageNextCallback) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100763 this.restApiService.getLoggedIn().then(() => {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200764 next();
765 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100766 }
767
Tao Zhou4fd32d52020-04-06 17:23:10 +0200768 /** Page.js middleware that try parse the querystring into queryMap. */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200769 _queryStringMiddleware(ctx: PageContext, next: PageNextCallback) {
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100770 (ctx as PageContextWithQueryMap).queryMap = this.createQueryMap(ctx);
771 next();
772 }
773
774 private createQueryMap(ctx: PageContext) {
Tao Zhou4fd32d52020-04-06 17:23:10 +0200775 if (ctx.querystring) {
776 // https://caniuse.com/#search=URLSearchParams
777 if (window.URLSearchParams) {
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100778 return new URLSearchParams(ctx.querystring);
Tao Zhou4fd32d52020-04-06 17:23:10 +0200779 } else {
Milutin Kristofic2fddf922021-03-11 10:35:19 +0100780 this.reporting.reportExecution(Execution.REACHABLE_CODE, {
781 id: 'noURLSearchParams',
782 });
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100783 return new Map(this._parseQueryString(ctx.querystring));
Tao Zhou4fd32d52020-04-06 17:23:10 +0200784 }
785 }
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100786 return new Map<string, string>();
Tao Zhou4fd32d52020-04-06 17:23:10 +0200787 }
788
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100789 /**
790 * Map a route to a method on the router.
791 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200792 * @param pattern The page.js pattern for the route.
793 * @param handlerName The method name for the handler. If the
794 * route is matched, the handler will be executed with `this` referring
795 * to the component. Its return value will be discarded so that it does
796 * not interfere with page.js.
797 * @param authRedirect If true, then auth is checked before
798 * executing the handler. If the user is not logged in, it will redirect
799 * to the login flow and the handler will not be executed. The login
800 * redirect specifies the matched URL to be used after successfull auth.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100801 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200802 _mapRoute(
803 pattern: string | RegExp,
804 handlerName: keyof GrRouter,
805 authRedirect?: boolean
806 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100807 if (!this[handlerName]) {
Milutin Kristofic380b9f62020-12-03 09:24:17 +0100808 this.reporting.error(
809 new Error(`Attempted to map route to unknown method: ${handlerName}`)
810 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100811 return;
812 }
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200813 page(
814 pattern,
815 (ctx, next) => this._loadUserMiddleware(ctx, next),
816 (ctx, next) => this._queryStringMiddleware(ctx, next),
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100817 ctx => {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200818 this.reporting.locationChanged(handlerName);
819 const promise = authRedirect
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100820 ? this._redirectIfNotLoggedIn(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200821 : Promise.resolve();
822 promise.then(() => {
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100823 this[handlerName](ctx as PageContextWithQueryMap);
Tao Zhou4fd32d52020-04-06 17:23:10 +0200824 });
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200825 }
826 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100827 }
828
829 _startRouter() {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200830 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100831 if (base) {
832 page.base(base);
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100833 }
Wyatt Allen4fd17cc2017-06-23 09:32:47 -0700834
Dmitrii Filippoveb8b2692020-04-06 18:02:35 +0200835 GerritNav.setup(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200836 (url, redirect?) => {
837 if (redirect) {
838 page.redirect(url);
839 } else {
840 page.show(url);
841 }
842 },
843 params => this._generateUrl(params),
844 params => this._generateWeblinks(params),
845 x => x
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100846 );
847
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200848 page.exit('*', (_, next) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100849 if (!this._isRedirecting) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100850 this.reporting.beforeLocationChanged();
Viktar Donicha28dee062017-11-10 16:03:13 -0800851 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100852 this._isRedirecting = false;
853 this._isInitialLoad = false;
854 next();
855 });
Viktar Donicha28dee062017-11-10 16:03:13 -0800856
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100857 // Remove the tracking param 'usp' (User Source Parameter) from the URL,
858 // just to have users look at cleaner URLs.
859 page((ctx, next) => {
860 if (window.URLSearchParams) {
861 const pathname = toPathname(ctx.canonicalPath);
862 const searchParams = toSearchParams(ctx.canonicalPath);
863 if (searchParams.has('usp')) {
Ben Rohlfs6fb09dd2021-03-12 09:24:26 +0100864 const usp = searchParams.get('usp');
865 this.reporting.reportLifeCycle(LifeCycle.USER_REFERRED_FROM, {usp});
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100866 searchParams.delete('usp');
867 this._redirect(toPath(pathname, searchParams));
868 return;
869 }
870 }
871 next();
872 });
873
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100874 // Middleware
875 page((ctx, next) => {
876 document.body.scrollTop = 0;
Viktar Donicha28dee062017-11-10 16:03:13 -0800877
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100878 if (ctx.hash.match(RoutePattern.PLUGIN_SCREEN)) {
879 // Redirect all urls using hash #/x/plugin/screen to /x/plugin/screen
880 // This is needed to allow plugins to add basic #/x/ screen links to
881 // any location.
882 this._redirect(ctx.hash);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700883 return;
884 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700885
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100886 // Fire asynchronously so that the URL is changed by the time the event
887 // is processed.
Ben Rohlfs6b078932021-03-10 15:20:03 +0100888 setTimeout(() => {
Dmitrii Filippov6a038002020-10-14 18:50:07 +0200889 const detail: LocationChangeEventDetail = {
890 hash: window.location.hash,
891 pathname: window.location.pathname,
892 };
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200893 this.dispatchEvent(
894 new CustomEvent('location-change', {
Dmitrii Filippov6a038002020-10-14 18:50:07 +0200895 detail,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200896 composed: true,
897 bubbles: true,
898 })
899 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100900 }, 1);
901 next();
902 });
903
904 this._mapRoute(RoutePattern.ROOT, '_handleRootRoute');
905
906 this._mapRoute(RoutePattern.DASHBOARD, '_handleDashboardRoute');
907
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200908 this._mapRoute(
909 RoutePattern.CUSTOM_DASHBOARD,
910 '_handleCustomDashboardRoute'
911 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100912
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200913 this._mapRoute(
914 RoutePattern.PROJECT_DASHBOARD,
915 '_handleProjectDashboardRoute'
916 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100917
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200918 this._mapRoute(
919 RoutePattern.LEGACY_PROJECT_DASHBOARD,
920 '_handleLegacyProjectDashboardRoute'
921 );
922
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100923 this._mapRoute(RoutePattern.GROUP_INFO, '_handleGroupInfoRoute', true);
924
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200925 this._mapRoute(
926 RoutePattern.GROUP_AUDIT_LOG,
927 '_handleGroupAuditLogRoute',
928 true
929 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100930
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200931 this._mapRoute(
932 RoutePattern.GROUP_MEMBERS,
933 '_handleGroupMembersRoute',
934 true
935 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100936
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200937 this._mapRoute(
938 RoutePattern.GROUP_LIST_OFFSET,
939 '_handleGroupListOffsetRoute',
940 true
941 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100942
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200943 this._mapRoute(
944 RoutePattern.GROUP_LIST_FILTER_OFFSET,
945 '_handleGroupListFilterOffsetRoute',
946 true
947 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100948
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200949 this._mapRoute(
950 RoutePattern.GROUP_LIST_FILTER,
951 '_handleGroupListFilterRoute',
952 true
953 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100954
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200955 this._mapRoute(
956 RoutePattern.GROUP_SELF,
957 '_handleGroupSelfRedirectRoute',
958 true
959 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100960
961 this._mapRoute(RoutePattern.GROUP, '_handleGroupRoute', true);
962
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200963 this._mapRoute(RoutePattern.PROJECT_OLD, '_handleProjectsOldRoute');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100964
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200965 this._mapRoute(
966 RoutePattern.REPO_COMMANDS,
967 '_handleRepoCommandsRoute',
968 true
969 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100970
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200971 this._mapRoute(RoutePattern.REPO_ACCESS, '_handleRepoAccessRoute');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100972
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200973 this._mapRoute(RoutePattern.REPO_DASHBOARDS, '_handleRepoDashboardsRoute');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100974
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200975 this._mapRoute(
976 RoutePattern.BRANCH_LIST_OFFSET,
977 '_handleBranchListOffsetRoute'
978 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100979
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200980 this._mapRoute(
981 RoutePattern.BRANCH_LIST_FILTER_OFFSET,
982 '_handleBranchListFilterOffsetRoute'
983 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100984
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200985 this._mapRoute(
986 RoutePattern.BRANCH_LIST_FILTER,
987 '_handleBranchListFilterRoute'
988 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100989
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200990 this._mapRoute(RoutePattern.TAG_LIST_OFFSET, '_handleTagListOffsetRoute');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100991
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200992 this._mapRoute(
993 RoutePattern.TAG_LIST_FILTER_OFFSET,
994 '_handleTagListFilterOffsetRoute'
995 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100996
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200997 this._mapRoute(RoutePattern.TAG_LIST_FILTER, '_handleTagListFilterRoute');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100998
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200999 this._mapRoute(
1000 RoutePattern.LEGACY_CREATE_GROUP,
1001 '_handleCreateGroupRoute',
1002 true
1003 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001004
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001005 this._mapRoute(
1006 RoutePattern.LEGACY_CREATE_PROJECT,
1007 '_handleCreateProjectRoute',
1008 true
1009 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001010
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001011 this._mapRoute(RoutePattern.REPO_LIST_OFFSET, '_handleRepoListOffsetRoute');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001012
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001013 this._mapRoute(
1014 RoutePattern.REPO_LIST_FILTER_OFFSET,
1015 '_handleRepoListFilterOffsetRoute'
1016 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001017
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001018 this._mapRoute(RoutePattern.REPO_LIST_FILTER, '_handleRepoListFilterRoute');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001019
1020 this._mapRoute(RoutePattern.REPO, '_handleRepoRoute');
1021
1022 this._mapRoute(RoutePattern.PLUGINS, '_handlePassThroughRoute');
1023
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001024 this._mapRoute(
1025 RoutePattern.PLUGIN_LIST_OFFSET,
1026 '_handlePluginListOffsetRoute',
1027 true
1028 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001029
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001030 this._mapRoute(
1031 RoutePattern.PLUGIN_LIST_FILTER_OFFSET,
1032 '_handlePluginListFilterOffsetRoute',
1033 true
1034 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001035
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001036 this._mapRoute(
1037 RoutePattern.PLUGIN_LIST_FILTER,
1038 '_handlePluginListFilterRoute',
1039 true
1040 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001041
1042 this._mapRoute(RoutePattern.PLUGIN_LIST, '_handlePluginListRoute', true);
1043
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001044 this._mapRoute(
1045 RoutePattern.QUERY_LEGACY_SUFFIX,
1046 '_handleQueryLegacySuffixRoute'
1047 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001048
1049 this._mapRoute(RoutePattern.QUERY, '_handleQueryRoute');
1050
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001051 this._mapRoute(RoutePattern.CHANGE_ID_QUERY, '_handleChangeIdQueryRoute');
1052
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001053 this._mapRoute(RoutePattern.DIFF_LEGACY_LINENUM, '_handleLegacyLinenum');
1054
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001055 this._mapRoute(
1056 RoutePattern.CHANGE_NUMBER_LEGACY,
1057 '_handleChangeNumberLegacyRoute'
1058 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001059
1060 this._mapRoute(RoutePattern.DIFF_EDIT, '_handleDiffEditRoute', true);
1061
1062 this._mapRoute(RoutePattern.CHANGE_EDIT, '_handleChangeEditRoute', true);
1063
Dhruv Srivastava90240452020-07-02 15:51:53 +02001064 this._mapRoute(RoutePattern.COMMENT, '_handleCommentRoute');
1065
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001066 this._mapRoute(RoutePattern.DIFF, '_handleDiffRoute');
1067
1068 this._mapRoute(RoutePattern.CHANGE, '_handleChangeRoute');
1069
1070 this._mapRoute(RoutePattern.CHANGE_LEGACY, '_handleChangeLegacyRoute');
1071
1072 this._mapRoute(RoutePattern.DIFF_LEGACY, '_handleDiffLegacyRoute');
1073
1074 this._mapRoute(RoutePattern.AGREEMENTS, '_handleAgreementsRoute', true);
1075
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001076 this._mapRoute(
1077 RoutePattern.NEW_AGREEMENTS,
1078 '_handleNewAgreementsRoute',
1079 true
1080 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001081
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001082 this._mapRoute(
1083 RoutePattern.SETTINGS_LEGACY,
1084 '_handleSettingsLegacyRoute',
1085 true
1086 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001087
1088 this._mapRoute(RoutePattern.SETTINGS, '_handleSettingsRoute', true);
1089
1090 this._mapRoute(RoutePattern.REGISTER, '_handleRegisterRoute');
1091
1092 this._mapRoute(RoutePattern.LOG_IN_OR_OUT, '_handlePassThroughRoute');
1093
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001094 this._mapRoute(
1095 RoutePattern.IMPROPERLY_ENCODED_PLUS,
1096 '_handleImproperlyEncodedPlusRoute'
1097 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001098
1099 this._mapRoute(RoutePattern.PLUGIN_SCREEN, '_handlePluginScreen');
1100
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001101 this._mapRoute(
1102 RoutePattern.DOCUMENTATION_SEARCH_FILTER,
1103 '_handleDocumentationSearchRoute'
1104 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001105
1106 // redirects /Documentation/q/* to /Documentation/q/filter:*
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001107 this._mapRoute(
1108 RoutePattern.DOCUMENTATION_SEARCH,
1109 '_handleDocumentationSearchRedirectRoute'
1110 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001111
1112 // Makes sure /Documentation/* links work (doin't return 404)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001113 this._mapRoute(
1114 RoutePattern.DOCUMENTATION,
1115 '_handleDocumentationRedirectRoute'
1116 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001117
1118 // Note: this route should appear last so it only catches URLs unmatched
1119 // by other patterns.
1120 this._mapRoute(RoutePattern.DEFAULT, '_handleDefaultRoute');
1121
1122 page.start();
1123 }
1124
1125 /**
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001126 * @return if handling the route involves asynchrony, then a
1127 * promise is returned. Otherwise, synchronous handling returns null.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001128 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001129 _handleRootRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001130 if (data.querystring.match(/^closeAfterLogin/)) {
1131 // Close child window on redirect after login.
1132 window.close();
1133 return null;
1134 }
1135 let hash = this._getHashFromCanonicalPath(data.canonicalPath);
1136 // For backward compatibility with GWT links.
1137 if (hash) {
1138 // In certain login flows the server may redirect to a hash without
1139 // a leading slash, which page.js doesn't handle correctly.
1140 if (hash[0] !== '/') {
1141 hash = '/' + hash;
1142 }
1143 if (hash.includes('/ /') && data.canonicalPath.includes('/+/')) {
1144 // Path decodes all '+' to ' ' -- this breaks project-based URLs.
1145 // See Issue 6888.
1146 hash = hash.replace('/ /', '/+/');
1147 }
Dmitrii Filippov0049afe2020-07-08 14:08:17 +02001148 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001149 let newUrl = base + hash;
1150 if (hash.startsWith('/VE/')) {
1151 newUrl = base + '/settings' + hash;
Wyatt Allen84505ea2017-08-24 10:53:05 -07001152 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001153 this._redirect(newUrl);
1154 return null;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001155 }
Ben Rohlfs43935a42020-12-01 19:14:09 +01001156 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001157 if (loggedIn) {
1158 this._redirect('/dashboard/self');
1159 } else {
Ben Rohlfs718957b2020-04-15 13:59:17 +02001160 this._redirect('/q/status:open+-is:wip');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001161 }
1162 });
1163 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001164
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001165 /**
1166 * Decode an application/x-www-form-urlencoded string.
1167 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001168 * @param qs The application/x-www-form-urlencoded string.
1169 * @return The decoded string.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001170 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001171 _decodeQueryString(qs: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001172 return decodeURIComponent(qs.replace(PLUS_PATTERN, ' '));
1173 }
1174
1175 /**
1176 * Parse a query string (e.g. window.location.search) into an array of
1177 * name/value pairs.
1178 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001179 * @param qs The application/x-www-form-urlencoded query string.
1180 * @return An array of name/value pairs, where each
1181 * element is a 2-element array.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001182 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001183 _parseQueryString(qs: string): Array<QueryStringItem> {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001184 qs = qs.replace(QUESTION_PATTERN, '');
1185 if (!qs) {
1186 return [];
1187 }
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001188 const params: Array<[string, string]> = [];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001189 qs.split('&').forEach(param => {
1190 const idx = param.indexOf('=');
1191 let name;
1192 let value;
1193 if (idx < 0) {
1194 name = this._decodeQueryString(param);
1195 value = '';
1196 } else {
1197 name = this._decodeQueryString(param.substring(0, idx));
1198 value = this._decodeQueryString(param.substring(idx + 1));
Wyatt Allen089dfb32017-08-24 17:55:38 -07001199 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001200 if (name) {
1201 params.push([name, value]);
Wyatt Allen089dfb32017-08-24 17:55:38 -07001202 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001203 });
1204 return params;
1205 }
1206
1207 /**
1208 * Handle dashboard routes. These may be user, or project dashboards.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001209 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001210 _handleDashboardRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001211 // User dashboard. We require viewing user to be logged in, else we
1212 // redirect to login for self dashboard or simple owner search for
1213 // other user dashboard.
Ben Rohlfs43935a42020-12-01 19:14:09 +01001214 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001215 if (!loggedIn) {
1216 if (data.params[0].toLowerCase() === 'self') {
1217 this._redirectToLogin(data.canonicalPath);
Wyatt Allen089dfb32017-08-24 17:55:38 -07001218 } else {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001219 this._redirect('/q/owner:' + encodeURIComponent(data.params[0]));
Wyatt Allen089dfb32017-08-24 17:55:38 -07001220 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001221 } else {
Logan Hanksaa501d02017-09-30 04:07:34 -07001222 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001223 view: GerritView.DASHBOARD,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001224 user: data.params[0],
Logan Hanksaa501d02017-09-30 04:07:34 -07001225 });
Wyatt Allen089dfb32017-08-24 17:55:38 -07001226 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001227 });
1228 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001229
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001230 /**
1231 * Handle custom dashboard routes.
1232 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001233 * @param qs Optional query string associated with the route.
1234 * If not given, window.location.search is used. (Used by tests).
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001235 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001236 _handleCustomDashboardRoute(
1237 _: PageContextWithQueryMap,
1238 qs: string = window.location.search
1239 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001240 const queryParams = this._parseQueryString(qs);
1241 let title = 'Custom Dashboard';
1242 const titleParam = queryParams.find(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001243 elem => elem[0].toLowerCase() === 'title'
1244 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001245 if (titleParam) {
1246 title = titleParam[1];
1247 }
1248 // Dashboards support a foreach param which adds a base query to any
1249 // additional query.
1250 const forEachParam = queryParams.find(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001251 elem => elem[0].toLowerCase() === 'foreach'
1252 );
1253 let forEachQuery: string | null = null;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001254 if (forEachParam) {
1255 forEachQuery = forEachParam[1];
1256 }
1257 const sectionParams = queryParams.filter(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001258 elem =>
1259 elem[0] &&
1260 elem[1] &&
1261 elem[0].toLowerCase() !== 'title' &&
1262 elem[0].toLowerCase() !== 'foreach'
1263 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001264 const sections = sectionParams.map(elem => {
1265 const query = forEachQuery ? `${forEachQuery} ${elem[1]}` : elem[1];
1266 return {
1267 name: elem[0],
1268 query,
1269 };
1270 });
1271
1272 if (sections.length > 0) {
1273 // Custom dashboard view.
1274 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001275 view: GerritView.DASHBOARD,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001276 user: 'self',
1277 sections,
1278 title,
1279 });
Wyatt Allenee2016c2017-10-31 13:41:52 -07001280 return Promise.resolve();
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001281 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001282
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001283 // Redirect /dashboard/ -> /dashboard/self.
1284 this._redirect('/dashboard/self');
1285 return Promise.resolve();
1286 }
Logan Hanksc34e0b62017-10-03 01:40:54 -07001287
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001288 _handleProjectDashboardRoute(data: PageContextWithQueryMap) {
1289 const project = data.params[0] as RepoName;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001290 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001291 view: GerritView.DASHBOARD,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001292 project,
Dmitrii Filippov12f22a92020-10-12 16:16:30 +02001293 dashboard: decodeURIComponent(data.params[1]) as DashboardId,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001294 });
Milutin Kristoficda88b332020-03-24 10:19:12 +01001295 this.reporting.setRepoName(project);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001296 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001297
Jacek Centkowskid322bd42020-09-16 10:34:55 +02001298 _handleLegacyProjectDashboardRoute(data: PageContextWithQueryMap) {
1299 this._redirect('/p/' + data.params[0] + '/+/dashboard/' + data.params[1]);
1300 }
1301
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001302 _handleGroupInfoRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001303 this._redirect('/admin/groups/' + encodeURIComponent(data.params[0]));
1304 }
Paladox nonef7303f72019-06-27 14:57:11 +00001305
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001306 _handleGroupSelfRedirectRoute(_: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001307 this._redirect('/settings/#Groups');
1308 }
Wyatt Allenc1727672017-10-19 15:06:54 -07001309
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001310 _handleGroupRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001311 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001312 view: GerritView.GROUP,
1313 groupId: data.params[0] as GroupId,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001314 });
1315 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001316
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001317 _handleGroupAuditLogRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001318 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001319 view: GerritView.GROUP,
1320 detail: GroupDetailView.LOG,
1321 groupId: data.params[0] as GroupId,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001322 });
1323 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001324
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001325 _handleGroupMembersRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001326 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001327 view: GerritView.GROUP,
1328 detail: GroupDetailView.MEMBERS,
1329 groupId: data.params[0] as GroupId,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001330 });
1331 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001332
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001333 _handleGroupListOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001334 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001335 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001336 adminView: 'gr-admin-group-list',
1337 offset: data.params[1] || 0,
1338 filter: null,
1339 openCreateModal: data.hash === 'create',
1340 });
1341 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001342
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001343 _handleGroupListFilterOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001344 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001345 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001346 adminView: 'gr-admin-group-list',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001347 offset: data.params['offset'],
1348 filter: data.params['filter'],
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001349 });
1350 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001351
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001352 _handleGroupListFilterRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001353 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001354 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001355 adminView: 'gr-admin-group-list',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001356 filter: data.params['filter'] || null,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001357 });
1358 }
Paladox none87f99602019-07-05 12:58:23 +00001359
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001360 _handleProjectsOldRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001361 let params = '';
1362 if (data.params[1]) {
1363 params = encodeURIComponent(data.params[1]);
1364 if (data.params[1].includes(',')) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001365 params = encodeURIComponent(data.params[1]).replace('%2C', ',');
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001366 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001367 }
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001368
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001369 this._redirect(`/admin/repos/${params}`);
1370 }
1371
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001372 _handleRepoCommandsRoute(data: PageContextWithQueryMap) {
1373 const repo = data.params[0] as RepoName;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001374 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001375 view: GerritView.REPO,
1376 detail: RepoDetailView.COMMANDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001377 repo,
1378 });
Milutin Kristoficda88b332020-03-24 10:19:12 +01001379 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001380 }
1381
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001382 _handleRepoAccessRoute(data: PageContextWithQueryMap) {
1383 const repo = data.params[0] as RepoName;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001384 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001385 view: GerritView.REPO,
1386 detail: RepoDetailView.ACCESS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001387 repo,
1388 });
Milutin Kristoficda88b332020-03-24 10:19:12 +01001389 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001390 }
1391
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001392 _handleRepoDashboardsRoute(data: PageContextWithQueryMap) {
1393 const repo = data.params[0] as RepoName;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001394 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001395 view: GerritView.REPO,
1396 detail: RepoDetailView.DASHBOARDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001397 repo,
1398 });
Milutin Kristoficda88b332020-03-24 10:19:12 +01001399 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001400 }
1401
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001402 _handleBranchListOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001403 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001404 view: GerritView.REPO,
1405 detail: RepoDetailView.BRANCHES,
1406 repo: data.params[0] as RepoName,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001407 offset: data.params[2] || 0,
1408 filter: null,
1409 });
1410 }
1411
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001412 _handleBranchListFilterOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001413 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001414 view: GerritView.REPO,
1415 detail: RepoDetailView.BRANCHES,
1416 repo: data.params['repo'] as RepoName,
1417 offset: data.params['offset'],
1418 filter: data.params['filter'],
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001419 });
1420 }
1421
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001422 _handleBranchListFilterRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001423 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001424 view: GerritView.REPO,
1425 detail: RepoDetailView.BRANCHES,
1426 repo: data.params['repo'] as RepoName,
1427 filter: data.params['filter'] || null,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001428 });
1429 }
1430
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001431 _handleTagListOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001432 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001433 view: GerritView.REPO,
1434 detail: RepoDetailView.TAGS,
1435 repo: data.params[0] as RepoName,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001436 offset: data.params[2] || 0,
1437 filter: null,
1438 });
1439 }
1440
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001441 _handleTagListFilterOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001442 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001443 view: GerritView.REPO,
1444 detail: RepoDetailView.TAGS,
1445 repo: data.params['repo'] as RepoName,
1446 offset: data.params['offset'],
1447 filter: data.params['filter'],
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001448 });
1449 }
1450
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001451 _handleTagListFilterRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001452 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001453 view: GerritView.REPO,
1454 detail: RepoDetailView.TAGS,
1455 repo: data.params['repo'] as RepoName,
1456 filter: data.params['filter'] || null,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001457 });
1458 }
1459
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001460 _handleRepoListOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001461 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001462 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001463 adminView: 'gr-repo-list',
1464 offset: data.params[1] || 0,
1465 filter: null,
1466 openCreateModal: data.hash === 'create',
1467 });
1468 }
1469
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001470 _handleRepoListFilterOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001471 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001472 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001473 adminView: 'gr-repo-list',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001474 offset: data.params['offset'],
1475 filter: data.params['filter'],
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001476 });
1477 }
1478
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001479 _handleRepoListFilterRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001480 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001481 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001482 adminView: 'gr-repo-list',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001483 filter: data.params['filter'] || null,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001484 });
1485 }
1486
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001487 _handleCreateProjectRoute(_: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001488 // Redirects the legacy route to the new route, which displays the project
1489 // list with a hash 'create'.
1490 this._redirect('/admin/repos#create');
1491 }
1492
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001493 _handleCreateGroupRoute(_: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001494 // Redirects the legacy route to the new route, which displays the group
1495 // list with a hash 'create'.
1496 this._redirect('/admin/groups#create');
1497 }
1498
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001499 _handleRepoRoute(data: PageContextWithQueryMap) {
1500 const repo = data.params[0] as RepoName;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001501 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001502 view: GerritView.REPO,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001503 repo,
1504 });
Milutin Kristoficda88b332020-03-24 10:19:12 +01001505 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001506 }
1507
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001508 _handlePluginListOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001509 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001510 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001511 adminView: 'gr-plugin-list',
1512 offset: data.params[1] || 0,
1513 filter: null,
1514 });
1515 }
1516
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001517 _handlePluginListFilterOffsetRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001518 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001519 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001520 adminView: 'gr-plugin-list',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001521 offset: data.params['offset'],
1522 filter: data.params['filter'],
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001523 });
1524 }
1525
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001526 _handlePluginListFilterRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001527 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001528 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001529 adminView: 'gr-plugin-list',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001530 filter: data.params['filter'] || null,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001531 });
1532 }
1533
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001534 _handlePluginListRoute(_: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001535 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001536 view: GerritView.ADMIN,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001537 adminView: 'gr-plugin-list',
1538 });
1539 }
1540
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001541 _handleQueryRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001542 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001543 view: GerritView.SEARCH,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001544 query: data.params[0],
1545 offset: data.params[2],
1546 });
1547 }
1548
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001549 _handleChangeIdQueryRoute(data: PageContextWithQueryMap) {
1550 // TODO(pcc): This will need to indicate that this was a change ID query if
1551 // standard queries gain the ability to search places like commit messages
1552 // for change IDs.
1553 this._setParams({
1554 view: GerritNav.View.SEARCH,
1555 query: data.params[0],
1556 });
1557 }
1558
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001559 _handleQueryLegacySuffixRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001560 this._redirect(ctx.path.replace(LEGACY_QUERY_SUFFIX_PATTERN, ''));
1561 }
1562
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001563 _handleChangeNumberLegacyRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001564 this._redirect('/c/' + encodeURIComponent(ctx.params[0]));
1565 }
1566
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001567 _handleChangeRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001568 // Parameter order is based on the regex group number matched.
Milutin Kristofic3a844522021-01-21 10:01:55 +01001569 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001570 const params: GenerateUrlChangeViewParameters = {
1571 project: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001572 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001573 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001574 patchNum: convertToPatchSetNum(ctx.params[6]),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001575 view: GerritView.CHANGE,
Tao Zhou4fd32d52020-04-06 17:23:10 +02001576 queryMap: ctx.queryMap,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001577 };
1578
Milutin Kristoficda88b332020-03-24 10:19:12 +01001579 this.reporting.setRepoName(params.project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001580 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001581 this._redirectOrNavigate(params);
1582 }
1583
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001584 _handleCommentRoute(ctx: PageContextWithQueryMap) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001585 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001586 const params: GenerateUrlDiffViewParameters = {
1587 project: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001588 changeNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001589 commentId: ctx.params[2] as UrlEncodedCommentId,
1590 view: GerritView.DIFF,
Dhruv Srivastava90240452020-07-02 15:51:53 +02001591 commentLink: true,
1592 };
1593 this.reporting.setRepoName(params.project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001594 this.reporting.setChangeId(changeNum);
Dhruv Srivastava90240452020-07-02 15:51:53 +02001595 this._redirectOrNavigate(params);
1596 }
1597
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001598 _handleDiffRoute(ctx: PageContextWithQueryMap) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001599 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001600 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001601 const params: GenerateUrlDiffViewParameters = {
1602 project: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001603 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001604 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001605 patchNum: convertToPatchSetNum(ctx.params[6]),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001606 path: ctx.params[8],
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001607 view: GerritView.DIFF,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001608 };
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001609 const address = this._parseLineAddress(ctx.hash);
1610 if (address) {
1611 params.leftSide = address.leftSide;
1612 params.lineNum = address.lineNum;
1613 }
Milutin Kristoficda88b332020-03-24 10:19:12 +01001614 this.reporting.setRepoName(params.project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001615 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001616 this._redirectOrNavigate(params);
1617 }
1618
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001619 _handleChangeLegacyRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001620 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001621 const params: GenerateUrlLegacyChangeViewParameters = {
Ben Rohlfsebe4acc2020-12-11 21:16:10 +01001622 changeNum: Number(ctx.params[0]) as NumericChangeId,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001623 basePatchNum: convertToPatchSetNum(ctx.params[3]) as BasePatchSetNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001624 patchNum: convertToPatchSetNum(ctx.params[5]),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001625 view: GerritView.CHANGE,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001626 querystring: ctx.querystring,
1627 };
1628
1629 this._normalizeLegacyRouteParams(params);
1630 }
1631
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001632 _handleLegacyLinenum(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001633 this._redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
1634 }
1635
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001636 _handleDiffLegacyRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001637 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001638 const params: GenerateUrlLegacyDiffViewParameters = {
Ben Rohlfsebe4acc2020-12-11 21:16:10 +01001639 changeNum: Number(ctx.params[0]) as NumericChangeId,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001640 basePatchNum: convertToPatchSetNum(ctx.params[2]) as BasePatchSetNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001641 patchNum: convertToPatchSetNum(ctx.params[4]),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001642 path: ctx.params[5],
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001643 view: GerritView.DIFF,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001644 };
1645
1646 const address = this._parseLineAddress(ctx.hash);
1647 if (address) {
1648 params.leftSide = address.leftSide;
1649 params.lineNum = address.lineNum;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001650 }
Paladox none2be2d2a2017-07-27 16:36:58 +00001651
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001652 this._normalizeLegacyRouteParams(params);
1653 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001654
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001655 _handleDiffEditRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001656 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001657 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001658 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001659 this._redirectOrNavigate({
1660 project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001661 changeNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001662 // for edit view params, patchNum cannot be undefined
1663 patchNum: convertToPatchSetNum(ctx.params[2])!,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001664 path: ctx.params[3],
1665 lineNum: ctx.hash,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001666 view: GerritView.EDIT,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001667 });
Milutin Kristoficda88b332020-03-24 10:19:12 +01001668 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001669 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001670 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001671
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001672 _handleChangeEditRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001673 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001674 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001675 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001676 this._redirectOrNavigate({
1677 project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001678 changeNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001679 patchNum: convertToPatchSetNum(ctx.params[3]),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001680 view: GerritView.CHANGE,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001681 edit: true,
1682 });
Milutin Kristoficda88b332020-03-24 10:19:12 +01001683 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001684 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001685 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001686
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001687 /**
1688 * Normalize the patch range params for a the change or diff view and
1689 * redirect if URL upgrade is needed.
1690 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001691 _redirectOrNavigate(params: GenerateUrlParameters & PatchRangeParams) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001692 const needsRedirect = this._normalizePatchRangeParams(params);
1693 if (needsRedirect) {
1694 this._redirect(this._generateUrl(params));
1695 } else {
1696 this._setParams(params);
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001697 }
1698 }
1699
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001700 _handleAgreementsRoute() {
1701 this._redirect('/settings/#Agreements');
1702 }
1703
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001704 _handleNewAgreementsRoute(data: PageContextWithQueryMap) {
1705 data.params['view'] = GerritView.AGREEMENTS;
1706 // TODO(TS): create valid object
1707 this._setParams((data.params as unknown) as AppElementAgreementParam);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001708 }
1709
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001710 _handleSettingsLegacyRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001711 // email tokens may contain '+' but no space.
1712 // The parameter parsing replaces all '+' with a space,
1713 // undo that to have valid tokens.
1714 const token = data.params[0].replace(/ /g, '+');
1715 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001716 view: GerritView.SETTINGS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001717 emailToken: token,
1718 });
1719 }
1720
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001721 _handleSettingsRoute(_: PageContextWithQueryMap) {
1722 this._setParams({view: GerritView.SETTINGS});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001723 }
1724
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001725 _handleRegisterRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001726 this._setParams({justRegistered: true});
1727 let path = ctx.params[0] || '/';
1728
1729 // Prevent redirect looping.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001730 if (path.startsWith('/register')) {
1731 path = '/';
1732 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001733
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001734 if (path[0] !== '/') {
1735 return;
1736 }
Dmitrii Filippov0049afe2020-07-08 14:08:17 +02001737 this._redirect(getBaseUrl() + path);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001738 }
1739
1740 /**
1741 * Handler for routes that should pass through the router and not be caught
1742 * by the catchall _handleDefaultRoute handler.
1743 */
1744 _handlePassThroughRoute() {
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +01001745 windowLocationReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001746 }
1747
1748 /**
1749 * URL may sometimes have /+/ encoded to / /.
1750 * Context: Issue 6888, Issue 7100
1751 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001752 _handleImproperlyEncodedPlusRoute(ctx: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001753 let hash = this._getHashFromCanonicalPath(ctx.canonicalPath);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001754 if (hash.length) {
1755 hash = '#' + hash;
1756 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001757 this._redirect(`/c/${ctx.params[0]}/+/${ctx.params[1]}${hash}`);
1758 }
1759
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001760 _handlePluginScreen(ctx: PageContextWithQueryMap) {
1761 const view = GerritView.PLUGIN_SCREEN;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001762 const plugin = ctx.params[0];
1763 const screen = ctx.params[1];
1764 this._setParams({view, plugin, screen});
1765 }
1766
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001767 _handleDocumentationSearchRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001768 this._setParams({
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001769 view: GerritView.DOCUMENTATION_SEARCH,
1770 filter: data.params['filter'] || null,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001771 });
1772 }
1773
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001774 _handleDocumentationSearchRedirectRoute(data: PageContextWithQueryMap) {
1775 this._redirect(
1776 '/Documentation/q/filter:' + encodeURIComponent(data.params[0])
1777 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001778 }
1779
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001780 _handleDocumentationRedirectRoute(data: PageContextWithQueryMap) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001781 if (data.params[1]) {
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +01001782 windowLocationReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001783 } else {
1784 // Redirect /Documentation to /Documentation/index.html
1785 this._redirect('/Documentation/index.html');
1786 }
1787 }
1788
1789 /**
1790 * Catchall route for when no other route is matched.
1791 */
1792 _handleDefaultRoute() {
1793 if (this._isInitialLoad) {
1794 // Server recognized this route as polygerrit, so we show 404.
1795 this._show404();
1796 } else {
1797 // Route can be recognized by server, so we pass it to server.
1798 this._handlePassThroughRoute();
1799 }
1800 }
1801
1802 _show404() {
1803 // Note: the app's 404 display is tightly-coupled with catching 404
1804 // network responses, so we simulate a 404 response status to display it.
1805 // TODO: Decouple the gr-app error view from network responses.
Ben Rohlfsa76c82f2021-01-22 22:22:32 +01001806 firePageError(new Response('', {status: 404}));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001807 }
1808}
1809
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001810declare global {
1811 interface HTMLElementTagNameMap {
1812 'gr-router': GrRouter;
1813 }
1814}