blob: a841958716b7179c6328146623f2372a0efd3d1d [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2016 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04005 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02006import {
Ben Rohlfsf27798d2023-01-26 12:45:21 +01007 Options,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02008 page,
9 PageContext,
10 PageNextCallback,
11} from '../../../utils/page-wrapper-utils';
Ben Rohlfs54934de2022-09-22 12:44:33 +020012import {NavigationService} from '../gr-navigation/gr-navigation';
Chris Poucetc6e880b2021-11-15 19:57:06 +010013import {getAppContext} from '../../../services/app-context';
Ben Rohlfs196dc722022-12-20 18:01:33 +010014import {
15 computeAllPatchSets,
16 computeLatestPatchNum,
17 convertToPatchSetNum,
18} from '../../../utils/patch-set-util';
Ben Rohlfsc2a8f292022-09-19 14:54:42 +020019import {assertIsDefined} from '../../../utils/common-util';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020020import {
Dhruv Srivastava591b4902021-03-10 11:48:12 +010021 BasePatchSetNum,
Dmitrii Filippov12f22a92020-10-12 16:16:30 +020022 DashboardId,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020023 GroupId,
24 NumericChangeId,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +020025 RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020026 RepoName,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020027 UrlEncodedCommentId,
Ben Rohlfs58267b72022-05-27 15:59:18 +020028 PARENT,
Ben Rohlfsa1d2c0c2022-09-29 17:03:26 +020029 PatchSetNumber,
Ben Rohlfsc35ed182022-11-25 12:02:52 +000030 BranchName,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020031} from '../../../types/common';
Ben Rohlfs25d24312022-09-12 15:08:07 +020032import {AppElement, AppElementParams} from '../../gr-app-types';
Dmitrii Filippov6a038002020-10-14 18:50:07 +020033import {LocationChangeEventDetail} from '../../../types/events';
Ben Rohlfs2586e572022-09-16 12:55:37 +020034import {GerritView, RouterModel} from '../../../services/router/router-model';
Ben Rohlfs196dc722022-12-20 18:01:33 +010035import {fireAlert, firePageError} from '../../../utils/event-util';
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +010036import {windowLocationReload} from '../../../utils/dom-util';
Milutin Kristofice366b932021-03-10 17:09:52 +010037import {
Ben Rohlfsc02facb2023-01-27 18:46:02 +010038 encodeURL,
Milutin Kristofice366b932021-03-10 17:09:52 +010039 getBaseUrl,
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +020040 PatchRangeParams,
Milutin Kristofice366b932021-03-10 17:09:52 +010041 toPath,
42 toPathname,
43 toSearchParams,
44} from '../../../utils/url-util';
Ben Rohlfscf5109c2022-09-20 08:44:28 +020045import {LifeCycle, Timing} from '../../../constants/reporting';
Ben Rohlfsebef2e12022-09-01 17:22:24 +020046import {
47 LATEST_ATTEMPT,
48 stringToAttemptChoice,
49} from '../../../models/checks/checks-util';
Ben Rohlfs2586e572022-09-16 12:55:37 +020050import {
51 AdminChildView,
52 AdminViewModel,
53 AdminViewState,
Ben Rohlfs2e47ef72023-01-23 17:11:38 +010054 PLUGIN_LIST_ROUTE,
Ben Rohlfs2586e572022-09-16 12:55:37 +020055} from '../../../models/views/admin';
56import {
57 AgreementViewModel,
58 AgreementViewState,
59} from '../../../models/views/agreement';
60import {
61 RepoDetailView,
62 RepoViewModel,
63 RepoViewState,
64} from '../../../models/views/repo';
65import {
Ben Rohlfs678e19d2023-01-13 14:26:14 +000066 createGroupUrl,
Ben Rohlfs2586e572022-09-16 12:55:37 +020067 GroupDetailView,
68 GroupViewModel,
69 GroupViewState,
70} from '../../../models/views/group';
Ben Rohlfs7b22a292022-09-29 12:52:01 +020071import {
Ben Rohlfsecc67992022-12-16 10:10:16 +010072 ChangeChildView,
Ben Rohlfs7b22a292022-09-29 12:52:01 +020073 ChangeViewModel,
74 ChangeViewState,
Ben Rohlfsecc67992022-12-16 10:10:16 +010075 createChangeViewUrl,
Ben Rohlfsb91a6a42023-01-13 09:29:31 +010076 createDiffUrl,
Ben Rohlfs7b22a292022-09-29 12:52:01 +020077} from '../../../models/views/change';
Ben Rohlfs2586e572022-09-16 12:55:37 +020078import {
79 DashboardViewModel,
80 DashboardViewState,
81} from '../../../models/views/dashboard';
82import {
83 SettingsViewModel,
84 SettingsViewState,
85} from '../../../models/views/settings';
86import {define} from '../../../models/dependency';
87import {Finalizable} from '../../../services/registry';
88import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
89import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
90import {
91 DocumentationViewModel,
92 DocumentationViewState,
93} from '../../../models/views/documentation';
94import {PluginViewModel, PluginViewState} from '../../../models/views/plugin';
95import {SearchViewModel, SearchViewState} from '../../../models/views/search';
Ben Rohlfs82b46492022-09-20 09:13:56 +020096import {DashboardSection} from '../../../utils/dashboard-util';
Ben Rohlfs7b22a292022-09-29 12:52:01 +020097import {Subscription} from 'rxjs';
Ben Rohlfs196dc722022-12-20 18:01:33 +010098import {
99 addPath,
100 findComment,
101 getPatchRangeForCommentUrl,
102 isInBaseOfPatchRange,
103} from '../../../utils/comment-util';
Ben Rohlfs196dc722022-12-20 18:01:33 +0100104import {isFileUnchanged} from '../../../embed/diff/gr-diff/gr-diff-utils';
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100105import {Route, ViewState} from '../../../models/views/base';
106import {Model} from '../../../models/model';
Wyatt Allenee2016c2017-10-31 13:41:52 -0700107
Ben Rohlfs0173d2a2023-01-27 09:10:40 +0100108// TODO: Move all patterns to view model files and use the `Route` interface,
109// which will enforce using `RegExp` in its `urlPattern` property.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100110const RoutePattern = {
111 ROOT: '/',
Wyatt Allenee2016c2017-10-31 13:41:52 -0700112
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100113 DASHBOARD: /^\/dashboard\/(.+)$/,
114 CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
115 PROJECT_DASHBOARD: /^\/p\/(.+)\/\+\/dashboard\/(.+)/,
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200116 LEGACY_PROJECT_DASHBOARD: /^\/projects\/(.+),dashboards\/(.+)/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700117
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100118 AGREEMENTS: /^\/settings\/agreements\/?/,
119 NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
120 REGISTER: /^\/register(\/.*)?$/,
Wyatt Allen6376e7a2017-08-31 10:11:59 -0700121
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100122 // Pattern for login and logout URLs intended to be passed-through. May
123 // include a return URL.
Ben Rohlfs9f085ff2023-01-27 08:23:33 +0100124 // TODO: Maybe this pattern and its handler can just be removed, because
125 // passing through is what the default router would eventually do anyway.
126 LOG_IN_OR_OUT: /^\/log(in|out)(\/(.+))?$/,
Wyatt Allenf1971382017-08-29 13:22:15 -0700127
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100128 // Pattern for a catchall route when no other pattern is matched.
129 DEFAULT: /.*/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700130
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100131 // Matches /admin/groups/[uuid-]<group>
132 GROUP: /^\/admin\/groups\/(?:uuid-)?([^,]+)$/,
Paladox nonef7303f72019-06-27 14:57:11 +0000133
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100134 // Redirects /groups/self to /settings/#Groups for GWT compatibility
135 GROUP_SELF: /^\/groups\/self/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700136
Frank Borden5ea7d652022-05-10 16:52:11 +0200137 // Matches /admin/groups/[uuid-]<group>,info (backwards compat with gwtui)
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100138 // Redirects to /admin/groups/[uuid-]<group>
139 GROUP_INFO: /^\/admin\/groups\/(?:uuid-)?(.+),info$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700140
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100141 // Matches /admin/groups/<group>,audit-log
142 GROUP_AUDIT_LOG: /^\/admin\/groups\/(?:uuid-)?(.+),audit-log$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700143
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100144 // Matches /admin/groups/[uuid-]<group>,members
145 GROUP_MEMBERS: /^\/admin\/groups\/(?:uuid-)?(.+),members$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700146
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100147 // Matches /admin/create-project
148 LEGACY_CREATE_PROJECT: /^\/admin\/create-project\/?$/,
Becky Siegel28d1bf62017-09-01 12:07:09 -0700149
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100150 // Matches /admin/create-project
151 LEGACY_CREATE_GROUP: /^\/admin\/create-group\/?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700152
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100153 PROJECT_OLD: /^\/admin\/(projects)\/?(.+)?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700154
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100155 // Matches /admin/repos/<repo>
156 REPO: /^\/admin\/repos\/([^,]+)$/,
Becky Siegel6db432f2017-08-25 09:17:42 -0700157
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100158 // Matches /admin/repos/<repo>,commands.
159 REPO_COMMANDS: /^\/admin\/repos\/(.+),commands$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700160
Ben Rohlfsc35ed182022-11-25 12:02:52 +0000161 // For creating a change, and going directly into editing mode for one file.
162 REPO_EDIT_FILE:
163 /^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$/,
164
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +0200165 REPO_GENERAL: /^\/admin\/repos\/(.+),general$/,
166
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100167 // Matches /admin/repos/<repos>,access.
168 REPO_ACCESS: /^\/admin\/repos\/(.+),access$/,
Becky Siegelc588ab72018-01-16 17:43:10 -0800169
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100170 // Matches /admin/repos/<repos>,access.
171 REPO_DASHBOARDS: /^\/admin\/repos\/(.+),dashboards$/,
Paladox none2bd5c212017-11-16 18:54:02 +0000172
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100173 PLUGINS: /^\/plugins\/(.+)$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700174
Ben Rohlfs4591b042023-01-27 09:39:06 +0100175 // Matches /admin/plugins with optional filter and offset.
176 PLUGIN_LIST: /^\/admin\/plugins\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100177 // Matches /admin/groups with optional filter and offset.
178 GROUP_LIST: /^\/admin\/groups\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Ben Rohlfs26306a42023-01-27 12:13:50 +0100179 // Matches /admin/repos with optional filter and offset.
180 REPO_LIST: /^\/admin\/repos\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100181 // Matches /admin/repos/$REPO,branches with optional filter and offset.
182 BRANCH_LIST:
183 /^\/admin\/repos\/(.+),branches\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
184 // Matches /admin/repos/$REPO,tags with optional filter and offset.
185 TAG_LIST: /^\/admin\/repos\/(.+),tags\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700186
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100187 QUERY: /^\/q\/([^,]+)(,(\d+))?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700188
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700189 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100190 * Support vestigial params from GWT UI.
Tao Zhou9a076812019-12-17 09:59:28 +0100191 *
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100192 * @see Issue 7673.
193 * @type {!RegExp}
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700194 */
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100195 QUERY_LEGACY_SUFFIX: /^\/q\/.+,n,z$/,
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700196
Peter Collingbourne8cab9332020-08-18 14:42:44 -0700197 CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
198
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +0100199 // Matches /c/<changeNum>/[*][/].
Ben Rohlfsd9032422021-10-06 19:05:12 +0200200 CHANGE_LEGACY: /^\/c\/(\d+)\/?(.*)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100201 CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
Logan Hanksaa501d02017-09-30 04:07:34 -0700202
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100203 // Matches
204 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..][<patchNum|edit>].
205 // TODO(kaspern): Migrate completely to project based URLs, with backwards
206 // compatibility for change-only.
207 CHANGE: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
Logan Hanksaa501d02017-09-30 04:07:34 -0700208
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100209 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>],edit
210 CHANGE_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(\d+))?,edit\/?$/,
Wyatt Allen84f0a572017-11-06 15:45:58 -0800211
Dhruv Srivastava90240452020-07-02 15:51:53 +0200212 // Matches /c/<project>/+/<changeNum>/comment/<commentId>/
213 // Navigates to the diff view
214 // This route is needed to resolve to patchNum vs latestPatchNum used in the
215 // links generated in the emails.
216 COMMENT: /^\/c\/(.+)\/\+\/(\d+)\/comment\/(\w+)\/?$/,
217
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +0200218 // Matches /c/<project>/+/<changeNum>/comments/<commentId>/
219 // Navigates to the commentId inside the Comments Tab
220 COMMENTS_TAB: /^\/c\/(.+)\/\+\/(\d+)\/comments(?:\/)?(\w+)?\/?$/,
221
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100222 // Matches
223 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..]<patchNum|edit>/<path>.
224 // TODO(kaspern): Migrate completely to project based URLs, with backwards
225 // compatibility for change-only.
226 // eslint-disable-next-line max-len
227 DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))))\/?$/,
Wyatt Allen52d76132017-11-06 16:58:52 -0800228
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100229 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>]/<path>,edit[#lineNum]
David Ostrovskycfe65302020-05-07 22:10:38 +0200230 DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
Wyatt Allen02add882018-08-13 19:06:52 -0700231
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100232 // Matches diff routes using @\d+ to specify a file name (whether or not
233 // the project name is included).
234 // eslint-disable-next-line max-len
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000235 DIFF_LEGACY_LINENUM:
236 /^\/c\/((.+)\/\+\/)?(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?\/(.+))?)@[ab]?\d+$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100237
238 SETTINGS: /^\/settings\/?/,
239 SETTINGS_LEGACY: /^\/settings\/VE\/(\S+)/,
240
241 // Matches /c/<changeNum>/ /<URL tail>
242 // Catches improperly encoded URLs (context: Issue 7100)
David Ostrovskycfe65302020-05-07 22:10:38 +0200243 IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/ \/(.+)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100244
245 PLUGIN_SCREEN: /^\/x\/([\w-]+)\/([\w-]+)\/?/,
246
Ben Rohlfs0173d2a2023-01-27 09:10:40 +0100247 DOCUMENTATION_SEARCH_FILTER: /^\/Documentation\/q\/filter:(.*)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100248 DOCUMENTATION_SEARCH: /^\/Documentation\/q\/(.*)$/,
249 DOCUMENTATION: /^\/Documentation(\/)?(.+)?/,
250};
251
252/**
253 * Pattern to recognize and parse the diff line locations as they appear in
254 * the hash of diff URLs. In this format, a number on its own indicates that
255 * line number in the revision of the diff. A number prefixed by either an 'a'
256 * or a 'b' indicates that line number of the base of the diff.
257 *
258 * @type {RegExp}
259 */
260const LINE_ADDRESS_PATTERN = /^([ab]?)(\d+)$/;
261
262/**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100263 * GWT UI would use @\d+ at the end of a path to indicate linenum.
264 */
265const LEGACY_LINENUM_PATTERN = /@([ab]?\d+)$/;
266
267const LEGACY_QUERY_SUFFIX_PATTERN = /,n,z$/;
268
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100269// Polymer makes `app` intrinsically defined on the window by virtue of the
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100270// custom element having the id "pg-app", but it is made explicit here.
Dmitrii Filippov6dbb1712020-03-19 12:11:50 +0100271// If you move this code to other place, please update comment about
272// gr-router and gr-app in the PolyGerritIndexHtml.soy file if needed
Chris Poucet36879972022-01-31 14:20:12 +0100273const app = document.querySelector('gr-app');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100274if (!app) {
Tao Zhoucd01d8f2020-07-22 12:35:31 +0200275 console.info('No gr-app found (running tests)');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100276}
277
278// Setup listeners outside of the router component initialization.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200279(function () {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100280 window.addEventListener('WebComponentsReady', () => {
Chris Poucetc6e880b2021-11-15 19:57:06 +0100281 getAppContext().reportingService.timeEnd(Timing.WEB_COMPONENTS_READY);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100282 });
283})();
284
Ben Rohlfs2586e572022-09-16 12:55:37 +0200285export const routerToken = define<GrRouter>('router');
286
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200287export class GrRouter implements Finalizable, NavigationService {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200288 readonly _app = app;
289
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200290 _isRedirecting?: boolean;
291
292 // This variable is to differentiate between internal navigation (false)
293 // and for first navigation in app after loaded from server (true).
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200294 _isInitialLoad = true;
295
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200296 private subscriptions: Subscription[] = [];
297
298 private view?: GerritView;
299
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100300 readonly page = page.create();
301
Ben Rohlfs2586e572022-09-16 12:55:37 +0200302 constructor(
303 private readonly reporting: ReportingService,
304 private readonly routerModel: RouterModel,
305 private readonly restApiService: RestApiService,
306 private readonly adminViewModel: AdminViewModel,
307 private readonly agreementViewModel: AgreementViewModel,
308 private readonly changeViewModel: ChangeViewModel,
309 private readonly dashboardViewModel: DashboardViewModel,
Ben Rohlfs2586e572022-09-16 12:55:37 +0200310 private readonly documentationViewModel: DocumentationViewModel,
Ben Rohlfs2586e572022-09-16 12:55:37 +0200311 private readonly groupViewModel: GroupViewModel,
312 private readonly pluginViewModel: PluginViewModel,
313 private readonly repoViewModel: RepoViewModel,
314 private readonly searchViewModel: SearchViewModel,
315 private readonly settingsViewModel: SettingsViewModel
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200316 ) {
317 this.subscriptions = [
318 // TODO: Do the same for other view models.
319 // We want to make sure that the current view model state is always
320 // reflected back into the URL bar.
321 this.changeViewModel.state$.subscribe(state => {
322 if (!state) return;
323 // Note that router model view must be updated before view model state.
324 // So this check is slightly fragile, but should work.
325 if (this.view !== GerritView.CHANGE) return;
Chris Poucet8f898592022-10-05 10:58:44 +0200326 const browserUrl = new URL(window.location.toString());
Ben Rohlfsecc67992022-12-16 10:10:16 +0100327 const stateUrl = new URL(createChangeViewUrl(state), browserUrl);
Ben Rohlfsae28e7c2022-10-17 12:33:36 +0200328
329 // Keeping the hash and certain parameters are stop-gap solution. We
330 // should find better ways of maintaining an overall consistent URL
331 // state.
Chris Poucet8f898592022-10-05 10:58:44 +0200332 stateUrl.hash = browserUrl.hash;
Ben Rohlfsae28e7c2022-10-17 12:33:36 +0200333 for (const p of browserUrl.searchParams.entries()) {
334 if (p[0] === 'experiment') stateUrl.searchParams.append(p[0], p[1]);
335 }
336
Chris Poucet8f898592022-10-05 10:58:44 +0200337 if (browserUrl.toString() !== stateUrl.toString()) {
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100338 this.page.replace(
Chris Poucet8f898592022-10-05 10:58:44 +0200339 stateUrl.toString(),
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200340 null,
341 /* init: */ false,
342 /* dispatch: */ false
343 );
344 }
345 }),
346 this.routerModel.routerView$.subscribe(view => (this.view = view)),
347 ];
348 }
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200349
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200350 finalize(): void {
351 for (const subscription of this.subscriptions) {
352 subscription.unsubscribe();
353 }
Chris Poucetc8e992a2022-10-24 13:52:34 +0200354 this.subscriptions = [];
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100355 this.page.stop();
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200356 }
Ben Rohlfs43935a42020-12-01 19:14:09 +0100357
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100358 start() {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200359 if (!this._app) {
360 return;
361 }
Frank Borden79448112022-04-12 16:59:32 +0200362 this.startRouter();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100363 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800364
Ben Rohlfs2586e572022-09-16 12:55:37 +0200365 setState(state: AppElementParams) {
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100366 // TODO: Move this logic into the change model.
Luca Milanesiocb6b6652022-10-26 22:41:56 +0100367 if ('repo' in state && state.repo !== undefined && 'changeNum' in state)
368 this.restApiService.setInProjectLookup(state.changeNum, state.repo);
Luca Milanesio79da1cd2022-10-24 22:10:12 +0100369
Ben Rohlfsd1009f82022-12-16 12:19:13 +0100370 this.routerModel.setState({view: state.view});
371 // We are trying to reset the change (view) model when navigating to other
372 // views, because we don't trust our reset logic at the moment. The models
373 // singletons and might unintentionally keep state from one change to
374 // another. TODO: Let's find some way to avoid that.
375 if (state.view !== GerritView.CHANGE) {
376 this.changeViewModel.setState(undefined);
377 }
Ben Rohlfs2586e572022-09-16 12:55:37 +0200378 this.appElement().params = state;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100379 }
380
Frank Borden79448112022-04-12 16:59:32 +0200381 private appElement(): AppElement {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100382 // In Polymer2 you have to reach through the shadow root of the app
383 // element. This obviously breaks encapsulation.
384 // TODO(brohlfs): Make this more elegant, e.g. by exposing app-element
385 // explicitly in app, or by delegating to it.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200386
387 // It is expected that application has a GrAppElement(id=='app-element')
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100388 // at the document level or inside the shadow root of the GrApp ('gr-app')
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200389 // element.
390 return (document.getElementById('app-element') ||
391 document
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100392 .querySelector('gr-app')!
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200393 .shadowRoot!.getElementById('app-element')!) as AppElement;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100394 }
395
Frank Borden79448112022-04-12 16:59:32 +0200396 redirect(url: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100397 this._isRedirecting = true;
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100398 this.page.redirect(url);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100399 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800400
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100401 /**
Ben Rohlfs2a431342022-09-16 10:47:01 +0200402 * Normalizes the patchset numbers of the params object.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100403 */
Frank Borden79448112022-04-12 16:59:32 +0200404 normalizePatchRangeParams(params: PatchRangeParams) {
Ben Rohlfs2a431342022-09-16 10:47:01 +0200405 if (params.basePatchNum === undefined) return;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100406
407 // Diffing a patch against itself is invalid, so if the base and revision
408 // patches are equal clear the base.
Dhruv Srivastavad1da4582021-01-11 16:34:19 +0100409 if (params.patchNum && params.basePatchNum === params.patchNum) {
Ben Rohlfs58267b72022-05-27 15:59:18 +0200410 params.basePatchNum = PARENT;
Ben Rohlfs2a431342022-09-16 10:47:01 +0200411 return;
412 }
413 // Regexes set basePatchNum instead of patchNum when only one is
414 // specified.
415 if (params.patchNum === undefined) {
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200416 params.patchNum = params.basePatchNum as RevisionPatchSetNum;
Ben Rohlfs58267b72022-05-27 15:59:18 +0200417 params.basePatchNum = PARENT;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100418 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100419 }
420
421 /**
422 * Redirect the user to login using the given return-URL for redirection
423 * after authentication success.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100424 */
Frank Borden79448112022-04-12 16:59:32 +0200425 redirectToLogin(returnUrl: string) {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200426 const basePath = getBaseUrl() || '';
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100427 this.page(
428 '/login/' + encodeURIComponent(returnUrl.substring(basePath.length))
429 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100430 }
431
432 /**
433 * Hashes parsed by page.js exclude "inner" hashes, so a URL like "/a#b#c"
434 * is parsed to have a hash of "b" rather than "b#c". Instead, this method
435 * parses hashes correctly. Will return an empty string if there is no hash.
436 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200437 * @return Everything after the first '#' ("a#b#c" -> "b#c").
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100438 */
Frank Borden79448112022-04-12 16:59:32 +0200439 getHashFromCanonicalPath(canonicalPath: string) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200440 return canonicalPath.split('#').slice(1).join('#');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100441 }
442
Frank Borden79448112022-04-12 16:59:32 +0200443 parseLineAddress(hash: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100444 const match = hash.match(LINE_ADDRESS_PATTERN);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200445 if (!match) {
446 return null;
447 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100448 return {
449 leftSide: !!match[1],
Dhruv Srivastavab8edee92020-10-19 10:20:07 +0200450 lineNum: Number(match[2]),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100451 };
452 }
453
454 /**
455 * Check to see if the user is logged in and return a promise that only
456 * resolves if the user is logged in. If the user us not logged in, the
457 * promise is rejected and the page is redirected to the login flow.
458 *
Ben Rohlfs5940c532022-09-20 09:19:50 +0200459 * @return A promise yielding the original route ctx
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200460 * (if it resolves).
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100461 */
Ben Rohlfs5940c532022-09-20 09:19:50 +0200462 redirectIfNotLoggedIn(ctx: PageContext) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100463 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100464 if (loggedIn) {
465 return Promise.resolve();
Wyatt Allen797480f2017-06-08 09:20:46 -0700466 } else {
Ben Rohlfs5940c532022-09-20 09:19:50 +0200467 this.redirectToLogin(ctx.canonicalPath);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100468 return Promise.reject(new Error());
Wyatt Allen797480f2017-06-08 09:20:46 -0700469 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100470 });
471 }
Wyatt Allen797480f2017-06-08 09:20:46 -0700472
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100473 /** Page.js middleware that warms the REST API's logged-in cache line. */
Frank Borden79448112022-04-12 16:59:32 +0200474 private loadUserMiddleware(_: PageContext, next: PageNextCallback) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100475 this.restApiService.getLoggedIn().then(() => {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200476 next();
477 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100478 }
479
480 /**
481 * Map a route to a method on the router.
482 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200483 * @param pattern The page.js pattern for the route.
484 * @param handlerName The method name for the handler. If the
485 * route is matched, the handler will be executed with `this` referring
486 * to the component. Its return value will be discarded so that it does
487 * not interfere with page.js.
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100488 * TODO: Get rid of this parameter. This is really not something that the
489 * router wants to be concerned with. The reporting service and the view
490 * models should figure that out between themselves.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200491 * @param authRedirect If true, then auth is checked before
492 * executing the handler. If the user is not logged in, it will redirect
493 * to the login flow and the handler will not be executed. The login
Frank Borden5ea7d652022-05-10 16:52:11 +0200494 * redirect specifies the matched URL to be used after successful auth.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100495 */
Frank Borden79448112022-04-12 16:59:32 +0200496 mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200497 pattern: string | RegExp,
Frank Borden79448112022-04-12 16:59:32 +0200498 handlerName: string,
Ben Rohlfscf5109c2022-09-20 08:44:28 +0200499 handler: (ctx: PageContext) => void,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200500 authRedirect?: boolean
501 ) {
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100502 this.page(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200503 pattern,
Frank Borden79448112022-04-12 16:59:32 +0200504 (ctx, next) => this.loadUserMiddleware(ctx, next),
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100505 ctx => {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200506 this.reporting.locationChanged(handlerName);
507 const promise = authRedirect
Frank Borden79448112022-04-12 16:59:32 +0200508 ? this.redirectIfNotLoggedIn(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200509 : Promise.resolve();
510 promise.then(() => {
Ben Rohlfscf5109c2022-09-20 08:44:28 +0200511 handler(ctx);
Tao Zhou4fd32d52020-04-06 17:23:10 +0200512 });
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200513 }
514 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100515 }
516
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200517 /**
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100518 * Convenience wrapper of `mapRoute()` for when you have a `Route` object that
519 * can deal with state creation. Takes care of setting the view model state,
520 * which is currently duplicated lots of times for direct callers of
521 * `mapRoute()`.
522 */
523 mapRouteState<T extends ViewState>(
524 route: Route<T>,
525 viewModel: Model<T | undefined>,
526 handlerName: string,
527 authRedirect?: boolean
528 ) {
529 const handler = (ctx: PageContext) => {
530 const state = route.createState(ctx);
531 // Note that order is important: `this.setState()` must be called before
532 // `viewModel.setState()`. Otherwise the chain of model subscriptions
533 // would be very different. Some views may want app element to swap the
534 // top level view first. Also, `this.setState()` has some special change
535 // view model resetting logic. Eventually the order might not be important
536 // anymore, but be careful! :-)
537 this.setState(state as AppElementParams);
538 viewModel.setState(state);
539 };
540 this.mapRoute(route.urlPattern, handlerName, handler, authRedirect);
541 }
542
543 /**
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200544 * This is similar to letting the browser navigate to this URL when the user
545 * clicks it, or to just setting `window.location.href` directly.
546 *
547 * This adds a new entry to the browser location history. Consier using
548 * `replaceUrl()`, if you want to avoid that.
549 *
550 * page.show() eventually just calls `window.history.pushState()`.
551 */
552 setUrl(url: string) {
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100553 this.page.show(url);
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200554 }
555
556 /**
557 * Navigate to this URL, but replace the current URL in the history instead of
558 * adding a new one (which is what `setUrl()` would do).
559 *
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100560 * this.page.redirect() eventually just calls `window.history.replaceState()`.
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200561 */
562 replaceUrl(url: string) {
563 this.redirect(url);
564 }
565
sergeyfac4cab2022-11-11 22:19:46 +0400566 private dispatchLocationChangeEvent() {
567 const detail: LocationChangeEventDetail = {
568 hash: window.location.hash,
569 pathname: window.location.pathname,
570 };
571 document.dispatchEvent(
572 new CustomEvent('location-change', {
573 detail,
574 composed: true,
575 bubbles: true,
576 })
577 );
578 }
579
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100580 _testOnly_startRouter() {
581 this.startRouter({dispatch: false, popstate: false});
582 }
583
584 startRouter(opts: Options = {}) {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200585 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100586 if (base) {
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100587 this.page.base(base);
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100588 }
Wyatt Allen4fd17cc2017-06-23 09:32:47 -0700589
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100590 this.page.exit('*', (_, next) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100591 if (!this._isRedirecting) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100592 this.reporting.beforeLocationChanged();
Viktar Donicha28dee062017-11-10 16:03:13 -0800593 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100594 this._isRedirecting = false;
595 this._isInitialLoad = false;
596 next();
597 });
Viktar Donicha28dee062017-11-10 16:03:13 -0800598
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100599 // Remove the tracking param 'usp' (User Source Parameter) from the URL,
600 // just to have users look at cleaner URLs.
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100601 this.page((ctx, next) => {
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100602 if (window.URLSearchParams) {
603 const pathname = toPathname(ctx.canonicalPath);
604 const searchParams = toSearchParams(ctx.canonicalPath);
605 if (searchParams.has('usp')) {
Ben Rohlfs6fb09dd2021-03-12 09:24:26 +0100606 const usp = searchParams.get('usp');
607 this.reporting.reportLifeCycle(LifeCycle.USER_REFERRED_FROM, {usp});
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100608 searchParams.delete('usp');
Frank Borden79448112022-04-12 16:59:32 +0200609 this.redirect(toPath(pathname, searchParams));
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100610 return;
611 }
612 }
613 next();
614 });
615
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100616 // Middleware
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100617 this.page((ctx, next) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100618 document.body.scrollTop = 0;
Viktar Donicha28dee062017-11-10 16:03:13 -0800619
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100620 if (ctx.hash.match(RoutePattern.PLUGIN_SCREEN)) {
621 // Redirect all urls using hash #/x/plugin/screen to /x/plugin/screen
622 // This is needed to allow plugins to add basic #/x/ screen links to
623 // any location.
Frank Borden79448112022-04-12 16:59:32 +0200624 this.redirect(ctx.hash);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700625 return;
626 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700627
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100628 // Fire asynchronously so that the URL is changed by the time the event
629 // is processed.
Ben Rohlfs6b078932021-03-10 15:20:03 +0100630 setTimeout(() => {
sergeyfac4cab2022-11-11 22:19:46 +0400631 this.dispatchLocationChangeEvent();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100632 }, 1);
633 next();
634 });
635
Frank Borden5ea7d652022-05-10 16:52:11 +0200636 this.mapRoute(RoutePattern.ROOT, 'handleRootRoute', ctx =>
637 this.handleRootRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200638 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100639
Frank Borden5ea7d652022-05-10 16:52:11 +0200640 this.mapRoute(RoutePattern.DASHBOARD, 'handleDashboardRoute', ctx =>
641 this.handleDashboardRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200642 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100643
Frank Borden79448112022-04-12 16:59:32 +0200644 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200645 RoutePattern.CUSTOM_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200646 'handleCustomDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200647 ctx => this.handleCustomDashboardRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200648 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100649
Frank Borden79448112022-04-12 16:59:32 +0200650 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200651 RoutePattern.PROJECT_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200652 'handleProjectDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200653 ctx => this.handleProjectDashboardRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200654 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100655
Frank Borden79448112022-04-12 16:59:32 +0200656 this.mapRoute(
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200657 RoutePattern.LEGACY_PROJECT_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200658 'handleLegacyProjectDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200659 ctx => this.handleLegacyProjectDashboardRoute(ctx)
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200660 );
661
Frank Borden79448112022-04-12 16:59:32 +0200662 this.mapRoute(
663 RoutePattern.GROUP_INFO,
664 'handleGroupInfoRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200665 ctx => this.handleGroupInfoRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200666 true
667 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100668
Frank Borden79448112022-04-12 16:59:32 +0200669 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200670 RoutePattern.GROUP_AUDIT_LOG,
Frank Borden79448112022-04-12 16:59:32 +0200671 'handleGroupAuditLogRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200672 ctx => this.handleGroupAuditLogRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200673 true
674 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100675
Frank Borden79448112022-04-12 16:59:32 +0200676 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200677 RoutePattern.GROUP_MEMBERS,
Frank Borden79448112022-04-12 16:59:32 +0200678 'handleGroupMembersRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200679 ctx => this.handleGroupMembersRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200680 true
681 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100682
Frank Borden79448112022-04-12 16:59:32 +0200683 this.mapRoute(
Ben Rohlfs8f064482023-01-27 09:52:51 +0100684 RoutePattern.GROUP_LIST,
685 'handleGroupListRoute',
686 ctx => this.handleGroupListRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200687 true
688 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100689
Frank Borden79448112022-04-12 16:59:32 +0200690 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200691 RoutePattern.GROUP_SELF,
Frank Borden79448112022-04-12 16:59:32 +0200692 'handleGroupSelfRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200693 ctx => this.handleGroupSelfRedirectRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200694 true
695 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100696
Frank Borden79448112022-04-12 16:59:32 +0200697 this.mapRoute(
698 RoutePattern.GROUP,
699 'handleGroupRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200700 ctx => this.handleGroupRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200701 true
702 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100703
Frank Borden5ea7d652022-05-10 16:52:11 +0200704 this.mapRoute(RoutePattern.PROJECT_OLD, 'handleProjectsOldRoute', ctx =>
705 this.handleProjectsOldRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200706 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100707
Frank Borden79448112022-04-12 16:59:32 +0200708 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200709 RoutePattern.REPO_COMMANDS,
Frank Borden79448112022-04-12 16:59:32 +0200710 'handleRepoCommandsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200711 ctx => this.handleRepoCommandsRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200712 true
713 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100714
Ben Rohlfsc35ed182022-11-25 12:02:52 +0000715 this.mapRoute(
716 RoutePattern.REPO_EDIT_FILE,
717 'handleRepoEditFileRoute',
718 ctx => this.handleRepoEditFileRoute(ctx),
719 true
720 );
721
Frank Borden5ea7d652022-05-10 16:52:11 +0200722 this.mapRoute(RoutePattern.REPO_GENERAL, 'handleRepoGeneralRoute', ctx =>
723 this.handleRepoGeneralRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200724 );
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +0200725
Frank Borden5ea7d652022-05-10 16:52:11 +0200726 this.mapRoute(RoutePattern.REPO_ACCESS, 'handleRepoAccessRoute', ctx =>
727 this.handleRepoAccessRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200728 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100729
Frank Borden79448112022-04-12 16:59:32 +0200730 this.mapRoute(
731 RoutePattern.REPO_DASHBOARDS,
732 'handleRepoDashboardsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200733 ctx => this.handleRepoDashboardsRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200734 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100735
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100736 this.mapRoute(RoutePattern.BRANCH_LIST, 'handleBranchListRoute', ctx =>
737 this.handleBranchListRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200738 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100739
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100740 this.mapRoute(RoutePattern.TAG_LIST, 'handleTagListRoute', ctx =>
741 this.handleTagListRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200742 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100743
Frank Borden79448112022-04-12 16:59:32 +0200744 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200745 RoutePattern.LEGACY_CREATE_GROUP,
Frank Borden79448112022-04-12 16:59:32 +0200746 'handleCreateGroupRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200747 ctx => this.handleCreateGroupRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200748 true
749 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100750
Frank Borden79448112022-04-12 16:59:32 +0200751 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200752 RoutePattern.LEGACY_CREATE_PROJECT,
Frank Borden79448112022-04-12 16:59:32 +0200753 'handleCreateProjectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200754 ctx => this.handleCreateProjectRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200755 true
756 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100757
Ben Rohlfs26306a42023-01-27 12:13:50 +0100758 this.mapRoute(RoutePattern.REPO_LIST, 'handleRepoListRoute', ctx =>
759 this.handleRepoListRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200760 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100761
Frank Borden5ea7d652022-05-10 16:52:11 +0200762 this.mapRoute(RoutePattern.REPO, 'handleRepoRoute', ctx =>
763 this.handleRepoRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200764 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100765
Frank Borden5ea7d652022-05-10 16:52:11 +0200766 this.mapRoute(RoutePattern.PLUGINS, 'handlePassThroughRoute', () =>
767 this.handlePassThroughRoute()
Frank Borden79448112022-04-12 16:59:32 +0200768 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100769
Frank Borden79448112022-04-12 16:59:32 +0200770 this.mapRoute(
Ben Rohlfs4591b042023-01-27 09:39:06 +0100771 RoutePattern.PLUGIN_LIST,
Frank Borden79448112022-04-12 16:59:32 +0200772 'handlePluginListFilterRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200773 ctx => this.handlePluginListFilterRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200774 true
775 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100776
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100777 this.mapRouteState(
778 PLUGIN_LIST_ROUTE,
779 this.adminViewModel,
Frank Borden79448112022-04-12 16:59:32 +0200780 'handlePluginListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200781 true
782 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100783
Frank Borden79448112022-04-12 16:59:32 +0200784 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200785 RoutePattern.QUERY_LEGACY_SUFFIX,
Frank Borden79448112022-04-12 16:59:32 +0200786 'handleQueryLegacySuffixRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200787 ctx => this.handleQueryLegacySuffixRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200788 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100789
Frank Borden5ea7d652022-05-10 16:52:11 +0200790 this.mapRoute(RoutePattern.QUERY, 'handleQueryRoute', ctx =>
791 this.handleQueryRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200792 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100793
Frank Borden79448112022-04-12 16:59:32 +0200794 this.mapRoute(
795 RoutePattern.CHANGE_ID_QUERY,
796 'handleChangeIdQueryRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200797 ctx => this.handleChangeIdQueryRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200798 );
Peter Collingbourne8cab9332020-08-18 14:42:44 -0700799
Frank Borden79448112022-04-12 16:59:32 +0200800 this.mapRoute(
801 RoutePattern.DIFF_LEGACY_LINENUM,
802 'handleLegacyLinenum',
Frank Borden5ea7d652022-05-10 16:52:11 +0200803 ctx => this.handleLegacyLinenum(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200804 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100805
Frank Borden79448112022-04-12 16:59:32 +0200806 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200807 RoutePattern.CHANGE_NUMBER_LEGACY,
Frank Borden79448112022-04-12 16:59:32 +0200808 'handleChangeNumberLegacyRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200809 ctx => this.handleChangeNumberLegacyRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200810 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100811
Frank Borden79448112022-04-12 16:59:32 +0200812 this.mapRoute(
813 RoutePattern.DIFF_EDIT,
814 'handleDiffEditRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200815 ctx => this.handleDiffEditRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200816 true
817 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100818
Frank Borden79448112022-04-12 16:59:32 +0200819 this.mapRoute(
820 RoutePattern.CHANGE_EDIT,
821 'handleChangeEditRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200822 ctx => this.handleChangeEditRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200823 true
824 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100825
Frank Borden5ea7d652022-05-10 16:52:11 +0200826 this.mapRoute(RoutePattern.COMMENT, 'handleCommentRoute', ctx =>
827 this.handleCommentRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200828 );
Dhruv Srivastava90240452020-07-02 15:51:53 +0200829
Frank Borden5ea7d652022-05-10 16:52:11 +0200830 this.mapRoute(RoutePattern.COMMENTS_TAB, 'handleCommentsRoute', ctx =>
831 this.handleCommentsRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200832 );
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +0200833
Frank Borden5ea7d652022-05-10 16:52:11 +0200834 this.mapRoute(RoutePattern.DIFF, 'handleDiffRoute', ctx =>
835 this.handleDiffRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200836 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100837
Frank Borden5ea7d652022-05-10 16:52:11 +0200838 this.mapRoute(RoutePattern.CHANGE, 'handleChangeRoute', ctx =>
839 this.handleChangeRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200840 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100841
Frank Borden5ea7d652022-05-10 16:52:11 +0200842 this.mapRoute(RoutePattern.CHANGE_LEGACY, 'handleChangeLegacyRoute', ctx =>
843 this.handleChangeLegacyRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200844 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100845
Frank Borden79448112022-04-12 16:59:32 +0200846 this.mapRoute(
847 RoutePattern.AGREEMENTS,
848 'handleAgreementsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200849 () => this.handleAgreementsRoute(),
Frank Borden79448112022-04-12 16:59:32 +0200850 true
851 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100852
Frank Borden79448112022-04-12 16:59:32 +0200853 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200854 RoutePattern.NEW_AGREEMENTS,
Frank Borden79448112022-04-12 16:59:32 +0200855 'handleNewAgreementsRoute',
Ben Rohlfs2586e572022-09-16 12:55:37 +0200856 () => this.handleNewAgreementsRoute(),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200857 true
858 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100859
Frank Borden79448112022-04-12 16:59:32 +0200860 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200861 RoutePattern.SETTINGS_LEGACY,
Frank Borden79448112022-04-12 16:59:32 +0200862 'handleSettingsLegacyRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200863 ctx => this.handleSettingsLegacyRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200864 true
865 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100866
Frank Borden79448112022-04-12 16:59:32 +0200867 this.mapRoute(
868 RoutePattern.SETTINGS,
869 'handleSettingsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200870 ctx => this.handleSettingsRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200871 true
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200872 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100873
Frank Borden5ea7d652022-05-10 16:52:11 +0200874 this.mapRoute(RoutePattern.REGISTER, 'handleRegisterRoute', ctx =>
875 this.handleRegisterRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200876 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100877
Frank Borden5ea7d652022-05-10 16:52:11 +0200878 this.mapRoute(RoutePattern.LOG_IN_OR_OUT, 'handlePassThroughRoute', () =>
879 this.handlePassThroughRoute()
Frank Borden79448112022-04-12 16:59:32 +0200880 );
881
882 this.mapRoute(
883 RoutePattern.IMPROPERLY_ENCODED_PLUS,
884 'handleImproperlyEncodedPlusRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200885 ctx => this.handleImproperlyEncodedPlusRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200886 );
887
Frank Borden5ea7d652022-05-10 16:52:11 +0200888 this.mapRoute(RoutePattern.PLUGIN_SCREEN, 'handlePluginScreen', ctx =>
889 this.handlePluginScreen(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200890 );
891
892 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200893 RoutePattern.DOCUMENTATION_SEARCH_FILTER,
Frank Borden79448112022-04-12 16:59:32 +0200894 'handleDocumentationSearchRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200895 ctx => this.handleDocumentationSearchRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200896 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100897
898 // redirects /Documentation/q/* to /Documentation/q/filter:*
Frank Borden79448112022-04-12 16:59:32 +0200899 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200900 RoutePattern.DOCUMENTATION_SEARCH,
Frank Borden79448112022-04-12 16:59:32 +0200901 'handleDocumentationSearchRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200902 ctx => this.handleDocumentationSearchRedirectRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200903 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100904
Frank Borden5ea7d652022-05-10 16:52:11 +0200905 // Makes sure /Documentation/* links work (don't return 404)
Frank Borden79448112022-04-12 16:59:32 +0200906 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200907 RoutePattern.DOCUMENTATION,
Frank Borden79448112022-04-12 16:59:32 +0200908 'handleDocumentationRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200909 ctx => this.handleDocumentationRedirectRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200910 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100911
912 // Note: this route should appear last so it only catches URLs unmatched
913 // by other patterns.
Frank Borden5ea7d652022-05-10 16:52:11 +0200914 this.mapRoute(RoutePattern.DEFAULT, 'handleDefaultRoute', () =>
915 this.handleDefaultRoute()
Frank Borden79448112022-04-12 16:59:32 +0200916 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100917
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100918 this.page.start(opts);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100919 }
920
921 /**
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200922 * @return if handling the route involves asynchrony, then a
923 * promise is returned. Otherwise, synchronous handling returns null.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100924 */
Ben Rohlfs5940c532022-09-20 09:19:50 +0200925 handleRootRoute(ctx: PageContext) {
926 if (ctx.querystring.match(/^closeAfterLogin/)) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100927 // Close child window on redirect after login.
928 window.close();
929 return null;
930 }
Ben Rohlfs5940c532022-09-20 09:19:50 +0200931 let hash = this.getHashFromCanonicalPath(ctx.canonicalPath);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100932 // For backward compatibility with GWT links.
933 if (hash) {
934 // In certain login flows the server may redirect to a hash without
935 // a leading slash, which page.js doesn't handle correctly.
936 if (hash[0] !== '/') {
937 hash = '/' + hash;
938 }
Ben Rohlfs5940c532022-09-20 09:19:50 +0200939 if (hash.includes('/ /') && ctx.canonicalPath.includes('/+/')) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100940 // Path decodes all '+' to ' ' -- this breaks project-based URLs.
941 // See Issue 6888.
942 hash = hash.replace('/ /', '/+/');
943 }
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200944 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100945 let newUrl = base + hash;
946 if (hash.startsWith('/VE/')) {
947 newUrl = base + '/settings' + hash;
Wyatt Allen84505ea2017-08-24 10:53:05 -0700948 }
Frank Borden79448112022-04-12 16:59:32 +0200949 this.redirect(newUrl);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100950 return null;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100951 }
Ben Rohlfs43935a42020-12-01 19:14:09 +0100952 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100953 if (loggedIn) {
Frank Borden79448112022-04-12 16:59:32 +0200954 this.redirect('/dashboard/self');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100955 } else {
Frank Borden79448112022-04-12 16:59:32 +0200956 this.redirect('/q/status:open+-is:wip');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100957 }
958 });
959 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700960
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100961 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100962 * Handle dashboard routes. These may be user, or project dashboards.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100963 */
Ben Rohlfs5940c532022-09-20 09:19:50 +0200964 handleDashboardRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100965 // User dashboard. We require viewing user to be logged in, else we
966 // redirect to login for self dashboard or simple owner search for
967 // other user dashboard.
Ben Rohlfs43935a42020-12-01 19:14:09 +0100968 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100969 if (!loggedIn) {
Ben Rohlfs5940c532022-09-20 09:19:50 +0200970 if (ctx.params[0].toLowerCase() === 'self') {
971 this.redirectToLogin(ctx.canonicalPath);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700972 } else {
Ben Rohlfsc02facb2023-01-27 18:46:02 +0100973 this.redirect('/q/owner:' + encodeURL(ctx.params[0]));
Wyatt Allen089dfb32017-08-24 17:55:38 -0700974 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100975 } else {
Ben Rohlfs2586e572022-09-16 12:55:37 +0200976 const state: DashboardViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200977 view: GerritView.DASHBOARD,
Ben Rohlfs5940c532022-09-20 09:19:50 +0200978 user: ctx.params[0],
Ben Rohlfs2586e572022-09-16 12:55:37 +0200979 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200980 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +0200981 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +0200982 this.dashboardViewModel.setState(state);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700983 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100984 });
985 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700986
Ben Rohlfs82b46492022-09-20 09:13:56 +0200987 handleCustomDashboardRoute(ctx: PageContext) {
988 const queryParams = new URLSearchParams(ctx.querystring);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100989
Ben Rohlfs82b46492022-09-20 09:13:56 +0200990 let title = 'Custom Dashboard';
991 const titleParam = queryParams.get('title');
992 if (titleParam) title = titleParam;
993 queryParams.delete('title');
994
995 let forEachQuery = '';
996 const forEachParam = queryParams.get('foreach');
997 if (forEachParam) forEachQuery = forEachParam + ' ';
998 queryParams.delete('foreach');
999
1000 const sections: DashboardSection[] = [];
1001 for (const [name, query] of queryParams) {
1002 if (!name || !query) continue;
1003 sections.push({name, query: `${forEachQuery}${query}`});
1004 }
1005
1006 if (sections.length === 0) {
1007 this.redirect('/dashboard/self');
Wyatt Allenee2016c2017-10-31 13:41:52 -07001008 return Promise.resolve();
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001009 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001010
Ben Rohlfs82b46492022-09-20 09:13:56 +02001011 const state: DashboardViewState = {
1012 view: GerritView.DASHBOARD,
1013 user: 'self',
1014 sections,
1015 title,
1016 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001017 // Note that router model view must be updated before view models.
Ben Rohlfs82b46492022-09-20 09:13:56 +02001018 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001019 this.dashboardViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001020 return Promise.resolve();
1021 }
Logan Hanksc34e0b62017-10-03 01:40:54 -07001022
Ben Rohlfs5940c532022-09-20 09:19:50 +02001023 handleProjectDashboardRoute(ctx: PageContext) {
1024 const project = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001025 const state: DashboardViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001026 view: GerritView.DASHBOARD,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001027 project,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001028 dashboard: decodeURIComponent(ctx.params[1]) as DashboardId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001029 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001030 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001031 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001032 this.dashboardViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001033 this.reporting.setRepoName(project);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001034 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001035
Ben Rohlfs5940c532022-09-20 09:19:50 +02001036 handleLegacyProjectDashboardRoute(ctx: PageContext) {
1037 this.redirect('/p/' + ctx.params[0] + '/+/dashboard/' + ctx.params[1]);
Jacek Centkowskid322bd42020-09-16 10:34:55 +02001038 }
1039
Ben Rohlfs5940c532022-09-20 09:19:50 +02001040 handleGroupInfoRoute(ctx: PageContext) {
Ben Rohlfs678e19d2023-01-13 14:26:14 +00001041 const groupId = ctx.params[0] as GroupId;
1042 this.redirect(createGroupUrl({groupId}));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001043 }
Paladox nonef7303f72019-06-27 14:57:11 +00001044
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001045 handleGroupSelfRedirectRoute(_: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001046 this.redirect('/settings/#Groups');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001047 }
Wyatt Allenc1727672017-10-19 15:06:54 -07001048
Ben Rohlfs5940c532022-09-20 09:19:50 +02001049 handleGroupRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001050 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001051 view: GerritView.GROUP,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001052 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001053 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001054 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001055 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001056 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001057 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001058
Ben Rohlfs5940c532022-09-20 09:19:50 +02001059 handleGroupAuditLogRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001060 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001061 view: GerritView.GROUP,
1062 detail: GroupDetailView.LOG,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001063 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001064 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001065 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001066 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001067 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001068 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001069
Ben Rohlfs5940c532022-09-20 09:19:50 +02001070 handleGroupMembersRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001071 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001072 view: GerritView.GROUP,
1073 detail: GroupDetailView.MEMBERS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001074 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001075 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001076 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001077 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001078 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001079 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001080
Ben Rohlfs8f064482023-01-27 09:52:51 +01001081 handleGroupListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001082 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001083 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001084 adminView: AdminChildView.GROUPS,
Ben Rohlfsadbde152023-01-24 19:23:32 +01001085 offset: ctx.params[1] ?? '0',
Ben Rohlfs8f064482023-01-27 09:52:51 +01001086 filter: ctx.params[0] ?? null,
1087 openCreateModal:
1088 !ctx.params[0] && !ctx.params[1] && ctx.hash === 'create',
Ben Rohlfs2586e572022-09-16 12:55:37 +02001089 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001090 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001091 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001092 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001093 }
Paladox none87f99602019-07-05 12:58:23 +00001094
Ben Rohlfs5940c532022-09-20 09:19:50 +02001095 handleProjectsOldRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001096 let params = '';
Ben Rohlfs5940c532022-09-20 09:19:50 +02001097 if (ctx.params[1]) {
1098 params = encodeURIComponent(ctx.params[1]);
1099 if (ctx.params[1].includes(',')) {
1100 params = encodeURIComponent(ctx.params[1]).replace('%2C', ',');
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001101 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001102 }
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001103
Ben Rohlfs678e19d2023-01-13 14:26:14 +00001104 // TODO: Change the route pattern to match `repo` and `detailView`
1105 // separately, and then use `createRepoUrl()` here.
Frank Borden79448112022-04-12 16:59:32 +02001106 this.redirect(`/admin/repos/${params}`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001107 }
1108
Ben Rohlfs5940c532022-09-20 09:19:50 +02001109 handleRepoCommandsRoute(ctx: PageContext) {
1110 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001111 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001112 view: GerritView.REPO,
1113 detail: RepoDetailView.COMMANDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001114 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001115 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001116 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001117 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001118 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001119 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001120 }
1121
Ben Rohlfsc35ed182022-11-25 12:02:52 +00001122 handleRepoEditFileRoute(ctx: PageContext) {
1123 const repo = ctx.params[0] as RepoName;
1124 const branch = ctx.params[1] as BranchName;
1125 const path = ctx.params[2];
1126 const state: RepoViewState = {
1127 view: GerritView.REPO,
1128 detail: RepoDetailView.COMMANDS,
1129 repo,
1130 createEdit: {branch, path},
1131 };
1132 // Note that router model view must be updated before view models.
1133 this.setState(state);
1134 this.repoViewModel.setState(state);
1135 this.reporting.setRepoName(repo);
1136 }
1137
Ben Rohlfs5940c532022-09-20 09:19:50 +02001138 handleRepoGeneralRoute(ctx: PageContext) {
1139 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001140 const state: RepoViewState = {
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +02001141 view: GerritView.REPO,
1142 detail: RepoDetailView.GENERAL,
1143 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001144 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001145 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001146 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001147 this.repoViewModel.setState(state);
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +02001148 this.reporting.setRepoName(repo);
1149 }
1150
Ben Rohlfs5940c532022-09-20 09:19:50 +02001151 handleRepoAccessRoute(ctx: PageContext) {
1152 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001153 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001154 view: GerritView.REPO,
1155 detail: RepoDetailView.ACCESS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001156 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001157 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001158 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001159 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001160 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001161 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001162 }
1163
Ben Rohlfs5940c532022-09-20 09:19:50 +02001164 handleRepoDashboardsRoute(ctx: PageContext) {
1165 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001166 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001167 view: GerritView.REPO,
1168 detail: RepoDetailView.DASHBOARDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001169 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001170 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001171 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001172 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001173 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001174 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001175 }
1176
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001177 handleBranchListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001178 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001179 view: GerritView.REPO,
1180 detail: RepoDetailView.BRANCHES,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001181 repo: ctx.params[0] as RepoName,
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001182 offset: ctx.params[2] ?? '0',
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001183 filter: ctx.params[1] ?? null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001184 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001185 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001186 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001187 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001188 }
1189
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001190 handleTagListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001191 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001192 view: GerritView.REPO,
1193 detail: RepoDetailView.TAGS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001194 repo: ctx.params[0] as RepoName,
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001195 offset: ctx.params[2] ?? '0',
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001196 filter: ctx.params[1] ?? null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001197 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001198 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001199 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001200 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001201 }
1202
Ben Rohlfs26306a42023-01-27 12:13:50 +01001203 handleRepoListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001204 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001205 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001206 adminView: AdminChildView.REPOS,
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001207 offset: ctx.params[1] ?? '0',
Ben Rohlfs26306a42023-01-27 12:13:50 +01001208 filter: ctx.params[0] ?? null,
1209 openCreateModal:
1210 !ctx.params[0] && !ctx.params[1] && ctx.hash === 'create',
Ben Rohlfs2586e572022-09-16 12:55:37 +02001211 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001212 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001213 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001214 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001215 }
1216
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001217 handleCreateProjectRoute(_: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001218 // Redirects the legacy route to the new route, which displays the project
1219 // list with a hash 'create'.
Frank Borden79448112022-04-12 16:59:32 +02001220 this.redirect('/admin/repos#create');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001221 }
1222
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001223 handleCreateGroupRoute(_: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001224 // Redirects the legacy route to the new route, which displays the group
1225 // list with a hash 'create'.
Frank Borden79448112022-04-12 16:59:32 +02001226 this.redirect('/admin/groups#create');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001227 }
1228
Ben Rohlfs5940c532022-09-20 09:19:50 +02001229 handleRepoRoute(ctx: PageContext) {
1230 this.redirect(ctx.path + ',general');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001231 }
1232
Ben Rohlfs5940c532022-09-20 09:19:50 +02001233 handlePluginListFilterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001234 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001235 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001236 adminView: AdminChildView.PLUGINS,
Ben Rohlfs4591b042023-01-27 09:39:06 +01001237 offset: ctx.params[1] ?? '0',
1238 filter: ctx.params[0] ?? null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001239 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001240 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001241 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001242 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001243 }
1244
Ben Rohlfs5940c532022-09-20 09:19:50 +02001245 handleQueryRoute(ctx: PageContext) {
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001246 const state: SearchViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001247 view: GerritView.SEARCH,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001248 query: ctx.params[0],
1249 offset: ctx.params[2],
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001250 loading: false,
1251 changes: [],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001252 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001253 // Note that router model view must be updated before view models.
Ben Rohlfs64a17892022-10-18 10:05:46 +02001254 this.setState(state as AppElementParams);
1255 this.searchViewModel.updateState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001256 }
1257
Ben Rohlfs5940c532022-09-20 09:19:50 +02001258 handleChangeIdQueryRoute(ctx: PageContext) {
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001259 // TODO(pcc): This will need to indicate that this was a change ID query if
1260 // standard queries gain the ability to search places like commit messages
1261 // for change IDs.
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001262 const state: SearchViewState = {
Ben Rohlfs8163cd42022-08-18 16:04:36 +02001263 view: GerritView.SEARCH,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001264 query: ctx.params[0],
Ben Rohlfs64a17892022-10-18 10:05:46 +02001265 offset: undefined,
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001266 loading: false,
1267 changes: [],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001268 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001269 // Note that router model view must be updated before view models.
Ben Rohlfs64a17892022-10-18 10:05:46 +02001270 this.setState(state as AppElementParams);
1271 this.searchViewModel.updateState(state);
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001272 }
1273
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001274 handleQueryLegacySuffixRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001275 this.redirect(ctx.path.replace(LEGACY_QUERY_SUFFIX_PATTERN, ''));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001276 }
1277
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001278 handleChangeNumberLegacyRoute(ctx: PageContext) {
Ben Rohlfsc02facb2023-01-27 18:46:02 +01001279 this.redirect('/c/' + ctx.params[0]);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001280 }
1281
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001282 handleChangeRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001283 // Parameter order is based on the regex group number matched.
Milutin Kristofic3a844522021-01-21 10:01:55 +01001284 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001285 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001286 repo: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001287 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001288 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001289 patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001290 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001291 childView: ChangeChildView.OVERVIEW,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001292 };
1293
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001294 const queryMap = new URLSearchParams(ctx.querystring);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001295 if (queryMap.has('forceReload')) state.forceReload = true;
1296 if (queryMap.has('openReplyDialog')) state.openReplyDialog = true;
Dhruv2cc210c2022-06-07 14:18:03 +02001297
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001298 const tab = queryMap.get('tab');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001299 if (tab) state.tab = tab;
Ben Rohlfsa1d2c0c2022-09-29 17:03:26 +02001300 const checksPatchset = Number(queryMap.get('checksPatchset'));
1301 if (Number.isInteger(checksPatchset) && checksPatchset > 0) {
1302 state.checksPatchset = checksPatchset as PatchSetNumber;
1303 }
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001304 const filter = queryMap.get('filter');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001305 if (filter) state.filter = filter;
Ben Rohlfs209f1412022-09-30 12:25:46 +02001306 const checksResultsFilter = queryMap.get('checksResultsFilter');
1307 if (checksResultsFilter) state.checksResultsFilter = checksResultsFilter;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001308 const attempt = stringToAttemptChoice(queryMap.get('attempt'));
Ben Rohlfs2586e572022-09-16 12:55:37 +02001309 if (attempt && attempt !== LATEST_ATTEMPT) state.attempt = attempt;
Ben Rohlfs6485eb82022-09-30 11:26:08 +02001310 const selected = queryMap.get('checksRunsSelected');
Ben Rohlfs20fe8e82022-10-14 11:13:10 +02001311 if (selected) state.checksRunsSelected = new Set(selected.split(','));
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001312
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001313 assertIsDefined(state.repo, 'project');
1314 this.reporting.setRepoName(state.repo);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001315 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001316 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001317 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001318 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001319 this.changeViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001320 }
1321
Ben Rohlfs196dc722022-12-20 18:01:33 +01001322 async handleCommentRoute(ctx: PageContext) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001323 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs196dc722022-12-20 18:01:33 +01001324 const repo = ctx.params[0] as RepoName;
1325 const commentId = ctx.params[2] as UrlEncodedCommentId;
1326
1327 const comments = await this.restApiService.getDiffComments(changeNum);
1328 const change = await this.restApiService.getChangeDetail(changeNum);
1329
1330 const comment = findComment(addPath(comments), commentId);
1331 const path = comment?.path;
1332 const patchsets = computeAllPatchSets(change);
1333 const latestPatchNum = computeLatestPatchNum(patchsets);
1334 if (!comment || !path || !latestPatchNum) {
1335 this.show404();
1336 return;
1337 }
1338 let {basePatchNum, patchNum} = getPatchRangeForCommentUrl(
1339 comment,
1340 latestPatchNum
1341 );
1342
1343 if (basePatchNum !== PARENT) {
1344 const diff = await this.restApiService.getDiff(
1345 changeNum,
1346 basePatchNum,
1347 patchNum,
1348 path
1349 );
1350 if (diff && isFileUnchanged(diff)) {
1351 fireAlert(
1352 document,
1353 `File is unchanged between Patchset ${basePatchNum} and ${patchNum}.
1354 Showing diff of Base vs ${basePatchNum}.`
1355 );
1356 patchNum = basePatchNum as RevisionPatchSetNum;
1357 basePatchNum = PARENT;
1358 }
1359 }
1360
1361 const diffUrl = createDiffUrl({
Milutin Kristofic3a844522021-01-21 10:01:55 +01001362 changeNum,
Ben Rohlfs196dc722022-12-20 18:01:33 +01001363 repo,
1364 patchNum,
1365 basePatchNum,
1366 diffView: {
1367 path,
1368 lineNum: comment.line,
1369 leftSide: isInBaseOfPatchRange(comment, {basePatchNum, patchNum}),
1370 },
1371 });
1372 this.redirect(diffUrl);
Dhruv Srivastava90240452020-07-02 15:51:53 +02001373 }
1374
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001375 handleCommentsRoute(ctx: PageContext) {
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001376 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001377 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001378 repo: ctx.params[0] as RepoName,
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001379 changeNum,
1380 commentId: ctx.params[2] as UrlEncodedCommentId,
1381 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001382 childView: ChangeChildView.OVERVIEW,
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001383 };
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001384 assertIsDefined(state.repo);
1385 this.reporting.setRepoName(state.repo);
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001386 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001387 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001388 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001389 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001390 this.changeViewModel.setState(state);
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001391 }
1392
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001393 handleDiffRoute(ctx: PageContext) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001394 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001395 // Parameter order is based on the regex group number matched.
Ben Rohlfsecc67992022-12-16 10:10:16 +01001396 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001397 repo: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001398 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001399 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001400 patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001401 view: GerritView.CHANGE,
1402 childView: ChangeChildView.DIFF,
1403 diffView: {path: ctx.params[8]},
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001404 };
Frank Borden79448112022-04-12 16:59:32 +02001405 const address = this.parseLineAddress(ctx.hash);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001406 if (address) {
Ben Rohlfsecc67992022-12-16 10:10:16 +01001407 state.diffView!.leftSide = address.leftSide;
1408 state.diffView!.lineNum = address.lineNum;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001409 }
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001410 this.reporting.setRepoName(state.repo ?? '');
Milutin Kristofic3a844522021-01-21 10:01:55 +01001411 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001412 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001413 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001414 this.setState(state);
Ben Rohlfsecc67992022-12-16 10:10:16 +01001415 this.changeViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001416 }
1417
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001418 handleChangeLegacyRoute(ctx: PageContext) {
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001419 const changeNum = Number(ctx.params[0]) as NumericChangeId;
1420 if (!changeNum) {
Frank Borden79448112022-04-12 16:59:32 +02001421 this.show404();
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001422 return;
1423 }
1424 this.restApiService.getFromProjectLookup(changeNum).then(project => {
1425 // Show a 404 and terminate if the lookup request failed. Attempting
1426 // to redirect after failing to get the project loops infinitely.
1427 if (!project) {
Frank Borden79448112022-04-12 16:59:32 +02001428 this.show404();
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001429 return;
1430 }
Frank Borden79448112022-04-12 16:59:32 +02001431 this.redirect(`/c/${project}/+/${changeNum}/${ctx.params[1]}`);
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001432 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001433 }
1434
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001435 handleLegacyLinenum(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001436 this.redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001437 }
1438
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001439 handleDiffEditRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001440 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001441 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001442 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfsecc67992022-12-16 10:10:16 +01001443 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001444 repo: project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001445 changeNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001446 // for edit view params, patchNum cannot be undefined
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001447 patchNum: convertToPatchSetNum(ctx.params[2]) as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001448 view: GerritView.CHANGE,
1449 childView: ChangeChildView.EDIT,
1450 editView: {path: ctx.params[3], lineNum: Number(ctx.hash)},
Ben Rohlfs2a431342022-09-16 10:47:01 +02001451 };
Ben Rohlfs2586e572022-09-16 12:55:37 +02001452 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001453 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001454 this.setState(state);
Ben Rohlfsecc67992022-12-16 10:10:16 +01001455 this.changeViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001456 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001457 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001458 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001459
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001460 handleChangeEditRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001461 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001462 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001463 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001464 const queryMap = new URLSearchParams(ctx.querystring);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001465 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001466 repo: project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001467 changeNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001468 patchNum: convertToPatchSetNum(ctx.params[3]) as RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001469 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001470 childView: ChangeChildView.OVERVIEW,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001471 edit: true,
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001472 };
Ben Rohlfs15da9b72022-10-18 09:41:50 +02001473 const tab = queryMap.get('tab');
1474 if (tab) state.tab = tab;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001475 if (queryMap.has('forceReload')) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001476 state.forceReload = true;
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001477 history.replaceState(
1478 null,
1479 '',
1480 location.href.replace(/[?&]forceReload=true/, '')
1481 );
1482 }
Ben Rohlfs2586e572022-09-16 12:55:37 +02001483 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001484 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001485 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001486 this.changeViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001487 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001488 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001489 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001490
Frank Borden79448112022-04-12 16:59:32 +02001491 handleAgreementsRoute() {
1492 this.redirect('/settings/#Agreements');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001493 }
1494
Ben Rohlfs2586e572022-09-16 12:55:37 +02001495 handleNewAgreementsRoute() {
1496 const state: AgreementViewState = {
1497 view: GerritView.AGREEMENTS,
1498 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001499 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001500 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001501 this.agreementViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001502 }
1503
Ben Rohlfs5940c532022-09-20 09:19:50 +02001504 handleSettingsLegacyRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001505 // email tokens may contain '+' but no space.
1506 // The parameter parsing replaces all '+' with a space,
1507 // undo that to have valid tokens.
Ben Rohlfs5940c532022-09-20 09:19:50 +02001508 const token = ctx.params[0].replace(/ /g, '+');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001509 const state: SettingsViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001510 view: GerritView.SETTINGS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001511 emailToken: token,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001512 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001513 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001514 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001515 this.settingsViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001516 }
1517
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001518 handleSettingsRoute(_: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001519 const state: SettingsViewState = {view: GerritView.SETTINGS};
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001520 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001521 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001522 this.settingsViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001523 }
1524
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001525 handleRegisterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001526 this.setState({justRegistered: true});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001527 let path = ctx.params[0] || '/';
1528
1529 // Prevent redirect looping.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001530 if (path.startsWith('/register')) {
1531 path = '/';
1532 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001533
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001534 if (path[0] !== '/') {
1535 return;
1536 }
Frank Borden79448112022-04-12 16:59:32 +02001537 this.redirect(getBaseUrl() + path);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001538 }
1539
1540 /**
1541 * Handler for routes that should pass through the router and not be caught
1542 * by the catchall _handleDefaultRoute handler.
1543 */
Frank Borden79448112022-04-12 16:59:32 +02001544 handlePassThroughRoute() {
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +01001545 windowLocationReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001546 }
1547
1548 /**
1549 * URL may sometimes have /+/ encoded to / /.
1550 * Context: Issue 6888, Issue 7100
1551 */
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001552 handleImproperlyEncodedPlusRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001553 let hash = this.getHashFromCanonicalPath(ctx.canonicalPath);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001554 if (hash.length) {
1555 hash = '#' + hash;
1556 }
Frank Borden79448112022-04-12 16:59:32 +02001557 this.redirect(`/c/${ctx.params[0]}/+/${ctx.params[1]}${hash}`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001558 }
1559
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001560 handlePluginScreen(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001561 const state: PluginViewState = {
1562 view: GerritView.PLUGIN_SCREEN,
1563 plugin: ctx.params[0],
1564 screen: ctx.params[1],
1565 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001566 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001567 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001568 this.pluginViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001569 }
1570
Ben Rohlfs5940c532022-09-20 09:19:50 +02001571 handleDocumentationSearchRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001572 const state: DocumentationViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001573 view: GerritView.DOCUMENTATION_SEARCH,
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001574 filter: ctx.params[0] ?? '',
Ben Rohlfs2586e572022-09-16 12:55:37 +02001575 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001576 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001577 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001578 this.documentationViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001579 }
1580
Ben Rohlfs5940c532022-09-20 09:19:50 +02001581 handleDocumentationSearchRedirectRoute(ctx: PageContext) {
Ben Rohlfsc02facb2023-01-27 18:46:02 +01001582 this.redirect('/Documentation/q/filter:' + encodeURL(ctx.params[0]));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001583 }
1584
Ben Rohlfs5940c532022-09-20 09:19:50 +02001585 handleDocumentationRedirectRoute(ctx: PageContext) {
1586 if (ctx.params[1]) {
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +01001587 windowLocationReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001588 } else {
1589 // Redirect /Documentation to /Documentation/index.html
Frank Borden79448112022-04-12 16:59:32 +02001590 this.redirect('/Documentation/index.html');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001591 }
1592 }
1593
1594 /**
1595 * Catchall route for when no other route is matched.
1596 */
Frank Borden79448112022-04-12 16:59:32 +02001597 handleDefaultRoute() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001598 if (this._isInitialLoad) {
1599 // Server recognized this route as polygerrit, so we show 404.
Frank Borden79448112022-04-12 16:59:32 +02001600 this.show404();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001601 } else {
1602 // Route can be recognized by server, so we pass it to server.
Frank Borden79448112022-04-12 16:59:32 +02001603 this.handlePassThroughRoute();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001604 }
1605 }
1606
Frank Borden79448112022-04-12 16:59:32 +02001607 private show404() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001608 // Note: the app's 404 display is tightly-coupled with catching 404
1609 // network responses, so we simulate a 404 response status to display it.
1610 // TODO: Decouple the gr-app error view from network responses.
Ben Rohlfsa76c82f2021-01-22 22:22:32 +01001611 firePageError(new Response('', {status: 404}));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001612 }
1613}