blob: 28ca5fdbdffd9e05a0e7606868ee701e3bedb65c [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 */
Ben Rohlfs1da030b2023-01-31 13:09:53 +01006import {Page, PageOptions, PageContext, PageNextCallback} from './gr-page';
Ben Rohlfs54934de2022-09-22 12:44:33 +02007import {NavigationService} from '../gr-navigation/gr-navigation';
Chris Poucetc6e880b2021-11-15 19:57:06 +01008import {getAppContext} from '../../../services/app-context';
Ben Rohlfs196dc722022-12-20 18:01:33 +01009import {
10 computeAllPatchSets,
11 computeLatestPatchNum,
12 convertToPatchSetNum,
13} from '../../../utils/patch-set-util';
Ben Rohlfsf6657362023-04-12 14:01:35 +020014import {assert, assertIsDefined} from '../../../utils/common-util';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020015import {
Dhruv Srivastava591b4902021-03-10 11:48:12 +010016 BasePatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020017 GroupId,
18 NumericChangeId,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +020019 RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020020 RepoName,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020021 UrlEncodedCommentId,
Ben Rohlfs58267b72022-05-27 15:59:18 +020022 PARENT,
Ben Rohlfsa1d2c0c2022-09-29 17:03:26 +020023 PatchSetNumber,
Ben Rohlfsc35ed182022-11-25 12:02:52 +000024 BranchName,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020025} from '../../../types/common';
Ben Rohlfs25d24312022-09-12 15:08:07 +020026import {AppElement, AppElementParams} from '../../gr-app-types';
Dmitrii Filippov6a038002020-10-14 18:50:07 +020027import {LocationChangeEventDetail} from '../../../types/events';
Ben Rohlfs2586e572022-09-16 12:55:37 +020028import {GerritView, RouterModel} from '../../../services/router/router-model';
Ben Rohlfs6bb90532023-02-17 18:55:56 +010029import {fire, fireAlert, firePageError} from '../../../utils/event-util';
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +010030import {windowLocationReload} from '../../../utils/dom-util';
Milutin Kristofice366b932021-03-10 17:09:52 +010031import {
Ben Rohlfsc02facb2023-01-27 18:46:02 +010032 encodeURL,
Milutin Kristofice366b932021-03-10 17:09:52 +010033 getBaseUrl,
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +020034 PatchRangeParams,
Milutin Kristofice366b932021-03-10 17:09:52 +010035 toPath,
36 toPathname,
37 toSearchParams,
38} from '../../../utils/url-util';
Ben Rohlfscf5109c2022-09-20 08:44:28 +020039import {LifeCycle, Timing} from '../../../constants/reporting';
Ben Rohlfsebef2e12022-09-01 17:22:24 +020040import {
41 LATEST_ATTEMPT,
42 stringToAttemptChoice,
43} from '../../../models/checks/checks-util';
Ben Rohlfs2586e572022-09-16 12:55:37 +020044import {
45 AdminChildView,
46 AdminViewModel,
47 AdminViewState,
Ben Rohlfs2e47ef72023-01-23 17:11:38 +010048 PLUGIN_LIST_ROUTE,
Edwin Kempinc2fce752024-09-17 14:39:13 +000049 SERVER_INFO_ROUTE,
Ben Rohlfs2586e572022-09-16 12:55:37 +020050} from '../../../models/views/admin';
51import {
52 AgreementViewModel,
53 AgreementViewState,
54} from '../../../models/views/agreement';
55import {
56 RepoDetailView,
57 RepoViewModel,
58 RepoViewState,
59} from '../../../models/views/repo';
60import {
Ben Rohlfs678e19d2023-01-13 14:26:14 +000061 createGroupUrl,
Ben Rohlfs2586e572022-09-16 12:55:37 +020062 GroupDetailView,
63 GroupViewModel,
64 GroupViewState,
65} from '../../../models/views/group';
Ben Rohlfs7b22a292022-09-29 12:52:01 +020066import {
Ben Rohlfsecc67992022-12-16 10:10:16 +010067 ChangeChildView,
Ben Rohlfs7b22a292022-09-29 12:52:01 +020068 ChangeViewModel,
69 ChangeViewState,
Ben Rohlfsecc67992022-12-16 10:10:16 +010070 createChangeViewUrl,
Ben Rohlfsb91a6a42023-01-13 09:29:31 +010071 createDiffUrl,
Ben Rohlfs7b22a292022-09-29 12:52:01 +020072} from '../../../models/views/change';
Ben Rohlfs2586e572022-09-16 12:55:37 +020073import {
Kamil Musin3e3da5a2023-10-18 16:19:13 +020074 DashboardType,
Ben Rohlfs2586e572022-09-16 12:55:37 +020075 DashboardViewModel,
76 DashboardViewState,
Ben Rohlfs4237f292023-02-11 13:06:01 +010077 PROJECT_DASHBOARD_ROUTE,
Ben Rohlfs2586e572022-09-16 12:55:37 +020078} from '../../../models/views/dashboard';
79import {
80 SettingsViewModel,
81 SettingsViewState,
82} from '../../../models/views/settings';
83import {define} from '../../../models/dependency';
Ben Rohlfs2586e572022-09-16 12:55:37 +020084import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
85import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
86import {
87 DocumentationViewModel,
88 DocumentationViewState,
89} from '../../../models/views/documentation';
90import {PluginViewModel, PluginViewState} from '../../../models/views/plugin';
91import {SearchViewModel, SearchViewState} from '../../../models/views/search';
Ben Rohlfs82b46492022-09-20 09:13:56 +020092import {DashboardSection} from '../../../utils/dashboard-util';
Ben Rohlfs7b22a292022-09-29 12:52:01 +020093import {Subscription} from 'rxjs';
Ben Rohlfs196dc722022-12-20 18:01:33 +010094import {
95 addPath,
96 findComment,
97 getPatchRangeForCommentUrl,
98 isInBaseOfPatchRange,
99} from '../../../utils/comment-util';
Ben Rohlfs4132fa72023-05-12 17:24:24 +0200100import {isFileUnchanged} from '../../../utils/diff-util';
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100101import {Route, ViewState} from '../../../models/views/base';
Dhruv Srivastavab26f09b2023-09-28 10:04:19 +0200102import {Model} from '../../../models/base/model';
Ben Rohlfsf6657362023-04-12 14:01:35 +0200103import {
104 InteractivePromise,
105 interactivePromise,
Chris Poucetf367bb92023-08-16 10:10:36 +0200106 noAwait,
Ben Rohlfsf6657362023-04-12 14:01:35 +0200107 timeoutPromise,
108} from '../../../utils/async-util';
Dhruv Srivastava7cfec742023-09-28 10:25:47 +0200109import {Finalizable} from '../../../types/types';
Ben Rohlfs0015dec2024-03-13 13:08:03 +0100110import {assign} from '../../../utils/location-util';
Wyatt Allenee2016c2017-10-31 13:41:52 -0700111
Ben Rohlfs0173d2a2023-01-27 09:10:40 +0100112// TODO: Move all patterns to view model files and use the `Route` interface,
113// which will enforce using `RegExp` in its `urlPattern` property.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100114const RoutePattern = {
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100115 ROOT: /^\/$/,
Wyatt Allenee2016c2017-10-31 13:41:52 -0700116
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100117 DASHBOARD: /^\/dashboard\/(.+)$/,
118 CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200119 LEGACY_PROJECT_DASHBOARD: /^\/projects\/(.+),dashboards\/(.+)/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700120
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100121 AGREEMENTS: /^\/settings\/agreements\/?/,
122 NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
123 REGISTER: /^\/register(\/.*)?$/,
Wyatt Allen6376e7a2017-08-31 10:11:59 -0700124
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100125 // Pattern for a catchall route when no other pattern is matched.
126 DEFAULT: /.*/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700127
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100128 // Matches /admin/groups/[uuid-]<group>
129 GROUP: /^\/admin\/groups\/(?:uuid-)?([^,]+)$/,
Paladox nonef7303f72019-06-27 14:57:11 +0000130
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100131 // Redirects /groups/self to /settings/#Groups for GWT compatibility
132 GROUP_SELF: /^\/groups\/self/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700133
Frank Borden5ea7d652022-05-10 16:52:11 +0200134 // Matches /admin/groups/[uuid-]<group>,info (backwards compat with gwtui)
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100135 // Redirects to /admin/groups/[uuid-]<group>
136 GROUP_INFO: /^\/admin\/groups\/(?:uuid-)?(.+),info$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700137
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100138 // Matches /admin/groups/<group>,audit-log
139 GROUP_AUDIT_LOG: /^\/admin\/groups\/(?:uuid-)?(.+),audit-log$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700140
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100141 // Matches /admin/groups/[uuid-]<group>,members
142 GROUP_MEMBERS: /^\/admin\/groups\/(?:uuid-)?(.+),members$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700143
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100144 // Matches /admin/create-project
145 LEGACY_CREATE_PROJECT: /^\/admin\/create-project\/?$/,
Becky Siegel28d1bf62017-09-01 12:07:09 -0700146
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100147 // Matches /admin/create-project
148 LEGACY_CREATE_GROUP: /^\/admin\/create-group\/?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700149
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100150 PROJECT_OLD: /^\/admin\/(projects)\/?(.+)?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700151
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100152 // Matches /admin/repos/<repo>
153 REPO: /^\/admin\/repos\/([^,]+)$/,
Becky Siegel6db432f2017-08-25 09:17:42 -0700154
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100155 // Matches /admin/repos/<repo>,commands.
156 REPO_COMMANDS: /^\/admin\/repos\/(.+),commands$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700157
Ben Rohlfsc35ed182022-11-25 12:02:52 +0000158 // For creating a change, and going directly into editing mode for one file.
159 REPO_EDIT_FILE:
160 /^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$/,
161
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +0200162 REPO_GENERAL: /^\/admin\/repos\/(.+),general$/,
163
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100164 // Matches /admin/repos/<repos>,access.
165 REPO_ACCESS: /^\/admin\/repos\/(.+),access$/,
Becky Siegelc588ab72018-01-16 17:43:10 -0800166
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100167 // Matches /admin/repos/<repos>,access.
168 REPO_DASHBOARDS: /^\/admin\/repos\/(.+),dashboards$/,
Paladox none2bd5c212017-11-16 18:54:02 +0000169
Ben Rohlfs4591b042023-01-27 09:39:06 +0100170 // Matches /admin/plugins with optional filter and offset.
Ben Rohlfsc33a8642023-02-13 11:45:15 +0100171 PLUGIN_LIST: /^\/admin\/plugins\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Ben Rohlfs8f064482023-01-27 09:52:51 +0100172 // Matches /admin/groups with optional filter and offset.
Ben Rohlfsc33a8642023-02-13 11:45:15 +0100173 GROUP_LIST: /^\/admin\/groups\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Ben Rohlfs26306a42023-01-27 12:13:50 +0100174 // Matches /admin/repos with optional filter and offset.
Ben Rohlfsc33a8642023-02-13 11:45:15 +0100175 REPO_LIST: /^\/admin\/repos\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100176 // Matches /admin/repos/$REPO,branches with optional filter and offset.
177 BRANCH_LIST:
178 /^\/admin\/repos\/(.+),branches\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
179 // Matches /admin/repos/$REPO,tags with optional filter and offset.
180 TAG_LIST: /^\/admin\/repos\/(.+),tags\/?(?:\/q\/filter:(.*?))?(?:,(\d+))?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700181
Edwin Kempinc2fce752024-09-17 14:39:13 +0000182 // Matches /admin/server-info.
183 SERVER_INFO: /^\/admin\/server-info$/,
184
Ben Rohlfsd47f9b72023-02-01 15:46:09 +0100185 QUERY: /^\/q\/(.+?)(,(\d+))?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700186
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700187 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100188 * Support vestigial params from GWT UI.
Tao Zhou9a076812019-12-17 09:59:28 +0100189 *
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100190 * @see Issue 7673.
191 * @type {!RegExp}
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700192 */
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100193 QUERY_LEGACY_SUFFIX: /^\/q\/.+,n,z$/,
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700194
Peter Collingbourne8cab9332020-08-18 14:42:44 -0700195 CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
196
Milutin Kristofic602145d2023-10-25 07:18:08 +0000197 // Matches /c/<changeNum>/[*][/].
198 CHANGE_LEGACY: /^\/c\/(\d+)\/?(.*)$/,
199 CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
200
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100201 // Matches
202 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..][<patchNum|edit>].
203 // TODO(kaspern): Migrate completely to project based URLs, with backwards
204 // compatibility for change-only.
205 CHANGE: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
Logan Hanksaa501d02017-09-30 04:07:34 -0700206
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100207 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>],edit
208 CHANGE_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(\d+))?,edit\/?$/,
Wyatt Allen84f0a572017-11-06 15:45:58 -0800209
Dhruv Srivastava90240452020-07-02 15:51:53 +0200210 // Matches /c/<project>/+/<changeNum>/comment/<commentId>/
211 // Navigates to the diff view
212 // This route is needed to resolve to patchNum vs latestPatchNum used in the
213 // links generated in the emails.
214 COMMENT: /^\/c\/(.+)\/\+\/(\d+)\/comment\/(\w+)\/?$/,
215
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +0200216 // Matches /c/<project>/+/<changeNum>/comments/<commentId>/
217 // Navigates to the commentId inside the Comments Tab
218 COMMENTS_TAB: /^\/c\/(.+)\/\+\/(\d+)\/comments(?:\/)?(\w+)?\/?$/,
219
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100220 // Matches
221 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..]<patchNum|edit>/<path>.
222 // TODO(kaspern): Migrate completely to project based URLs, with backwards
223 // compatibility for change-only.
224 // eslint-disable-next-line max-len
225 DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))))\/?$/,
Wyatt Allen52d76132017-11-06 16:58:52 -0800226
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100227 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>]/<path>,edit[#lineNum]
David Ostrovskycfe65302020-05-07 22:10:38 +0200228 DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
Wyatt Allen02add882018-08-13 19:06:52 -0700229
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100230 // Matches diff routes using @\d+ to specify a file name (whether or not
231 // the project name is included).
232 // eslint-disable-next-line max-len
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000233 DIFF_LEGACY_LINENUM:
234 /^\/c\/((.+)\/\+\/)?(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?\/(.+))?)@[ab]?\d+$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100235
236 SETTINGS: /^\/settings\/?/,
237 SETTINGS_LEGACY: /^\/settings\/VE\/(\S+)/,
238
239 // Matches /c/<changeNum>/ /<URL tail>
240 // Catches improperly encoded URLs (context: Issue 7100)
David Ostrovskycfe65302020-05-07 22:10:38 +0200241 IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/ \/(.+)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100242
243 PLUGIN_SCREEN: /^\/x\/([\w-]+)\/([\w-]+)\/?/,
244
Ben Rohlfs0173d2a2023-01-27 09:10:40 +0100245 DOCUMENTATION_SEARCH_FILTER: /^\/Documentation\/q\/filter:(.*)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100246 DOCUMENTATION_SEARCH: /^\/Documentation\/q\/(.*)$/,
247 DOCUMENTATION: /^\/Documentation(\/)?(.+)?/,
248};
249
250/**
251 * Pattern to recognize and parse the diff line locations as they appear in
252 * the hash of diff URLs. In this format, a number on its own indicates that
253 * line number in the revision of the diff. A number prefixed by either an 'a'
254 * or a 'b' indicates that line number of the base of the diff.
255 *
256 * @type {RegExp}
257 */
258const LINE_ADDRESS_PATTERN = /^([ab]?)(\d+)$/;
259
260/**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100261 * GWT UI would use @\d+ at the end of a path to indicate linenum.
262 */
263const LEGACY_LINENUM_PATTERN = /@([ab]?\d+)$/;
264
265const LEGACY_QUERY_SUFFIX_PATTERN = /,n,z$/;
266
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100267// Polymer makes `app` intrinsically defined on the window by virtue of the
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100268// custom element having the id "pg-app", but it is made explicit here.
Dmitrii Filippov6dbb1712020-03-19 12:11:50 +0100269// If you move this code to other place, please update comment about
270// gr-router and gr-app in the PolyGerritIndexHtml.soy file if needed
Chris Poucet36879972022-01-31 14:20:12 +0100271const app = document.querySelector('gr-app');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100272if (!app) {
Tao Zhoucd01d8f2020-07-22 12:35:31 +0200273 console.info('No gr-app found (running tests)');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100274}
275
276// Setup listeners outside of the router component initialization.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200277(function () {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100278 window.addEventListener('WebComponentsReady', () => {
Chris Poucetc6e880b2021-11-15 19:57:06 +0100279 getAppContext().reportingService.timeEnd(Timing.WEB_COMPONENTS_READY);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100280 });
281})();
282
Ben Rohlfs2586e572022-09-16 12:55:37 +0200283export const routerToken = define<GrRouter>('router');
284
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200285export class GrRouter implements Finalizable, NavigationService {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200286 readonly _app = app;
287
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200288 _isRedirecting?: boolean;
289
290 // This variable is to differentiate between internal navigation (false)
291 // and for first navigation in app after loaded from server (true).
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200292 _isInitialLoad = true;
293
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200294 private subscriptions: Subscription[] = [];
295
296 private view?: GerritView;
297
Ben Rohlfsf6657362023-04-12 14:01:35 +0200298 // While this set is not empty, the router will refuse to navigate to
299 // other pages, but instead show an alert. It will also install a
300 // `beforeUnload` handler that prevents the browser from closing the tab.
301 private navigationBlockers: Set<string> = new Set<string>();
302
303 // While navigationBlockers is not empty, this promise will continuously
304 // check for navigationBlockers to become empty again.
305 // This is undefined, iff navigationBlockers is empty.
306 private navigationBlockerPromise?: InteractivePromise<void>;
307
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100308 readonly page = new Page();
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100309
Ben Rohlfs2586e572022-09-16 12:55:37 +0200310 constructor(
311 private readonly reporting: ReportingService,
312 private readonly routerModel: RouterModel,
313 private readonly restApiService: RestApiService,
314 private readonly adminViewModel: AdminViewModel,
315 private readonly agreementViewModel: AgreementViewModel,
316 private readonly changeViewModel: ChangeViewModel,
317 private readonly dashboardViewModel: DashboardViewModel,
Ben Rohlfs2586e572022-09-16 12:55:37 +0200318 private readonly documentationViewModel: DocumentationViewModel,
Ben Rohlfs2586e572022-09-16 12:55:37 +0200319 private readonly groupViewModel: GroupViewModel,
320 private readonly pluginViewModel: PluginViewModel,
321 private readonly repoViewModel: RepoViewModel,
322 private readonly searchViewModel: SearchViewModel,
323 private readonly settingsViewModel: SettingsViewModel
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200324 ) {
325 this.subscriptions = [
326 // TODO: Do the same for other view models.
327 // We want to make sure that the current view model state is always
328 // reflected back into the URL bar.
329 this.changeViewModel.state$.subscribe(state => {
330 if (!state) return;
331 // Note that router model view must be updated before view model state.
332 // So this check is slightly fragile, but should work.
333 if (this.view !== GerritView.CHANGE) return;
Chris Poucet8f898592022-10-05 10:58:44 +0200334 const browserUrl = new URL(window.location.toString());
Ben Rohlfsecc67992022-12-16 10:10:16 +0100335 const stateUrl = new URL(createChangeViewUrl(state), browserUrl);
Ben Rohlfsae28e7c2022-10-17 12:33:36 +0200336
337 // Keeping the hash and certain parameters are stop-gap solution. We
338 // should find better ways of maintaining an overall consistent URL
339 // state.
Chris Poucet8f898592022-10-05 10:58:44 +0200340 stateUrl.hash = browserUrl.hash;
Ben Rohlfsae28e7c2022-10-17 12:33:36 +0200341 for (const p of browserUrl.searchParams.entries()) {
342 if (p[0] === 'experiment') stateUrl.searchParams.append(p[0], p[1]);
343 }
344
Chris Poucet8f898592022-10-05 10:58:44 +0200345 if (browserUrl.toString() !== stateUrl.toString()) {
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100346 this.page.replace(stateUrl.toString(), {}, /* dispatch: */ false);
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200347 }
348 }),
349 this.routerModel.routerView$.subscribe(view => (this.view = view)),
350 ];
351 }
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200352
Ben Rohlfsf6657362023-04-12 14:01:35 +0200353 blockNavigation(reason: string): void {
354 assert(!!reason, 'empty reason is not allowed');
355 this.navigationBlockers.add(reason);
356 if (this.navigationBlockers.size === 1) {
357 this.navigationBlockerPromise = interactivePromise();
358 window.addEventListener('beforeunload', this.beforeUnloadHandler);
359 }
360 }
361
362 releaseNavigation(reason: string): void {
363 assert(!!reason, 'empty reason is not allowed');
364 this.navigationBlockers.delete(reason);
365 if (this.navigationBlockers.size === 0) {
366 window.removeEventListener('beforeunload', this.beforeUnloadHandler);
367 this.navigationBlockerPromise?.resolve();
368 }
369 }
370
371 private beforeUnloadHandler = (event: BeforeUnloadEvent) => {
372 const reason = [...this.navigationBlockers][0];
373 if (!reason) return;
374
375 event.preventDefault(); // Cancel the event (per the standard).
376 event.returnValue = reason; // Chrome requires returnValue to be set.
377 // Note that we could as well just use '' instead of `reason`. Browsers will
378 // just show a generic message anyway.
379 return reason;
380 };
381
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200382 finalize(): void {
383 for (const subscription of this.subscriptions) {
384 subscription.unsubscribe();
385 }
Chris Poucetc8e992a2022-10-24 13:52:34 +0200386 this.subscriptions = [];
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100387 this.page.stop();
Ben Rohlfsf6657362023-04-12 14:01:35 +0200388 window.removeEventListener('beforeunload', this.beforeUnloadHandler);
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200389 }
Ben Rohlfs43935a42020-12-01 19:14:09 +0100390
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100391 start() {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200392 if (!this._app) {
393 return;
394 }
Frank Borden79448112022-04-12 16:59:32 +0200395 this.startRouter();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100396 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800397
Ben Rohlfs2586e572022-09-16 12:55:37 +0200398 setState(state: AppElementParams) {
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100399 // TODO: Move this logic into the change model.
Luca Milanesiocb6b6652022-10-26 22:41:56 +0100400 if ('repo' in state && state.repo !== undefined && 'changeNum' in state)
Kamil Musin48677cb2024-02-27 17:06:12 +0100401 this.restApiService.addRepoNameToCache(state.changeNum, state.repo);
Luca Milanesio79da1cd2022-10-24 22:10:12 +0100402
Ben Rohlfsd1009f82022-12-16 12:19:13 +0100403 this.routerModel.setState({view: state.view});
404 // We are trying to reset the change (view) model when navigating to other
405 // views, because we don't trust our reset logic at the moment. The models
406 // singletons and might unintentionally keep state from one change to
407 // another. TODO: Let's find some way to avoid that.
408 if (state.view !== GerritView.CHANGE) {
409 this.changeViewModel.setState(undefined);
410 }
Ben Rohlfs2586e572022-09-16 12:55:37 +0200411 this.appElement().params = state;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100412 }
413
Frank Borden79448112022-04-12 16:59:32 +0200414 private appElement(): AppElement {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100415 // In Polymer2 you have to reach through the shadow root of the app
416 // element. This obviously breaks encapsulation.
417 // TODO(brohlfs): Make this more elegant, e.g. by exposing app-element
418 // explicitly in app, or by delegating to it.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200419
420 // It is expected that application has a GrAppElement(id=='app-element')
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100421 // at the document level or inside the shadow root of the GrApp ('gr-app')
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200422 // element.
423 return (document.getElementById('app-element') ||
424 document
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100425 .querySelector('gr-app')!
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200426 .shadowRoot!.getElementById('app-element')!) as AppElement;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100427 }
428
Frank Borden79448112022-04-12 16:59:32 +0200429 redirect(url: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100430 this._isRedirecting = true;
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100431 this.page.redirect(url);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100432 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800433
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100434 /**
Ben Rohlfs2a431342022-09-16 10:47:01 +0200435 * Normalizes the patchset numbers of the params object.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100436 */
Frank Borden79448112022-04-12 16:59:32 +0200437 normalizePatchRangeParams(params: PatchRangeParams) {
Ben Rohlfs2a431342022-09-16 10:47:01 +0200438 if (params.basePatchNum === undefined) return;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100439
440 // Diffing a patch against itself is invalid, so if the base and revision
441 // patches are equal clear the base.
Dhruv Srivastavad1da4582021-01-11 16:34:19 +0100442 if (params.patchNum && params.basePatchNum === params.patchNum) {
Ben Rohlfs58267b72022-05-27 15:59:18 +0200443 params.basePatchNum = PARENT;
Ben Rohlfs2a431342022-09-16 10:47:01 +0200444 return;
445 }
446 // Regexes set basePatchNum instead of patchNum when only one is
447 // specified.
448 if (params.patchNum === undefined) {
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200449 params.patchNum = params.basePatchNum as RevisionPatchSetNum;
Ben Rohlfs58267b72022-05-27 15:59:18 +0200450 params.basePatchNum = PARENT;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100451 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100452 }
453
454 /**
455 * Redirect the user to login using the given return-URL for redirection
456 * after authentication success.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100457 */
Frank Borden79448112022-04-12 16:59:32 +0200458 redirectToLogin(returnUrl: string) {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200459 const basePath = getBaseUrl() || '';
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +0100460 // We are not using `this.getNavigation().setUrl()`, because the login
461 // page is served directly from the backend and is not part of the web
462 // app.
Ben Rohlfs0015dec2024-03-13 13:08:03 +0100463 assign(
464 window.location,
Paladox none9100b1b2024-04-18 18:18:59 +0000465 `${basePath}/login/${encodeURIComponent(
466 returnUrl.substring(basePath.length)
467 )}`
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100468 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100469 }
470
471 /**
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100472 * Hashes parsed by gr-page exclude "inner" hashes, so a URL like "/a#b#c"
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100473 * is parsed to have a hash of "b" rather than "b#c". Instead, this method
474 * parses hashes correctly. Will return an empty string if there is no hash.
475 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200476 * @return Everything after the first '#' ("a#b#c" -> "b#c").
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100477 */
Frank Borden79448112022-04-12 16:59:32 +0200478 getHashFromCanonicalPath(canonicalPath: string) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200479 return canonicalPath.split('#').slice(1).join('#');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100480 }
481
Frank Borden79448112022-04-12 16:59:32 +0200482 parseLineAddress(hash: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100483 const match = hash.match(LINE_ADDRESS_PATTERN);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200484 if (!match) {
485 return null;
486 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100487 return {
488 leftSide: !!match[1],
Dhruv Srivastavab8edee92020-10-19 10:20:07 +0200489 lineNum: Number(match[2]),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100490 };
491 }
492
493 /**
494 * Check to see if the user is logged in and return a promise that only
495 * resolves if the user is logged in. If the user us not logged in, the
496 * promise is rejected and the page is redirected to the login flow.
497 *
Ben Rohlfs5940c532022-09-20 09:19:50 +0200498 * @return A promise yielding the original route ctx
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200499 * (if it resolves).
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100500 */
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100501 redirectIfNotLoggedIn(path: string) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100502 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100503 if (loggedIn) {
504 return Promise.resolve();
Wyatt Allen797480f2017-06-08 09:20:46 -0700505 } else {
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100506 this.redirectToLogin(path);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100507 return Promise.reject(new Error());
Wyatt Allen797480f2017-06-08 09:20:46 -0700508 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100509 });
510 }
Wyatt Allen797480f2017-06-08 09:20:46 -0700511
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100512 /** gr-page middleware that warms the REST API's logged-in cache line. */
Frank Borden79448112022-04-12 16:59:32 +0200513 private loadUserMiddleware(_: PageContext, next: PageNextCallback) {
Chris Poucetf367bb92023-08-16 10:10:36 +0200514 noAwait(this.restApiService.getLoggedIn());
515 next();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100516 }
517
518 /**
519 * Map a route to a method on the router.
520 *
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100521 * @param pattern The regex pattern for the route.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200522 * @param handlerName The method name for the handler. If the
523 * route is matched, the handler will be executed with `this` referring
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100524 * to the component. Its return value will be discarded.
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100525 * TODO: Get rid of this parameter. This is really not something that the
526 * router wants to be concerned with. The reporting service and the view
527 * models should figure that out between themselves.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200528 * @param authRedirect If true, then auth is checked before
529 * executing the handler. If the user is not logged in, it will redirect
530 * to the login flow and the handler will not be executed. The login
Frank Borden5ea7d652022-05-10 16:52:11 +0200531 * redirect specifies the matched URL to be used after successful auth.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100532 */
Frank Borden79448112022-04-12 16:59:32 +0200533 mapRoute(
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100534 pattern: RegExp,
Frank Borden79448112022-04-12 16:59:32 +0200535 handlerName: string,
Ben Rohlfscf5109c2022-09-20 08:44:28 +0200536 handler: (ctx: PageContext) => void,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200537 authRedirect?: boolean
538 ) {
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100539 this.page.registerRoute(pattern, (ctx, next) =>
540 this.loadUserMiddleware(ctx, next)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200541 );
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100542 this.page.registerRoute(pattern, ctx => {
543 this.reporting.locationChanged(handlerName);
544 const promise = authRedirect
545 ? this.redirectIfNotLoggedIn(ctx.canonicalPath)
546 : Promise.resolve();
547 promise.then(() => {
548 handler(ctx);
549 });
550 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100551 }
552
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200553 /**
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100554 * Convenience wrapper of `mapRoute()` for when you have a `Route` object that
555 * can deal with state creation. Takes care of setting the view model state,
556 * which is currently duplicated lots of times for direct callers of
557 * `mapRoute()`.
558 */
559 mapRouteState<T extends ViewState>(
560 route: Route<T>,
561 viewModel: Model<T | undefined>,
562 handlerName: string,
563 authRedirect?: boolean
564 ) {
565 const handler = (ctx: PageContext) => {
566 const state = route.createState(ctx);
567 // Note that order is important: `this.setState()` must be called before
568 // `viewModel.setState()`. Otherwise the chain of model subscriptions
569 // would be very different. Some views may want app element to swap the
570 // top level view first. Also, `this.setState()` has some special change
571 // view model resetting logic. Eventually the order might not be important
572 // anymore, but be careful! :-)
573 this.setState(state as AppElementParams);
574 viewModel.setState(state);
575 };
576 this.mapRoute(route.urlPattern, handlerName, handler, authRedirect);
577 }
578
579 /**
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200580 * This is similar to letting the browser navigate to this URL when the user
581 * clicks it, or to just setting `window.location.href` directly.
582 *
583 * This adds a new entry to the browser location history. Consier using
584 * `replaceUrl()`, if you want to avoid that.
585 *
586 * page.show() eventually just calls `window.history.pushState()`.
587 */
588 setUrl(url: string) {
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +0100589 // TODO: Use window.location.assign() instead of page.show(), if the URL is
590 // external, i.e. not handled by the router.
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100591 this.page.show(url);
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200592 }
593
594 /**
595 * Navigate to this URL, but replace the current URL in the history instead of
596 * adding a new one (which is what `setUrl()` would do).
597 *
Ben Rohlfs0d174e02023-01-24 18:18:28 +0100598 * this.page.redirect() eventually just calls `window.history.replaceState()`.
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200599 */
600 replaceUrl(url: string) {
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +0100601 // TODO: Use window.location.replace() instead of page.redirect(), if the
602 // URL is external, i.e. not handled by the router.
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200603 this.redirect(url);
604 }
605
sergeyfac4cab2022-11-11 22:19:46 +0400606 private dispatchLocationChangeEvent() {
607 const detail: LocationChangeEventDetail = {
608 hash: window.location.hash,
609 pathname: window.location.pathname,
610 };
Ben Rohlfs6bb90532023-02-17 18:55:56 +0100611 fire(document, 'location-change', detail);
sergeyfac4cab2022-11-11 22:19:46 +0400612 }
613
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100614 _testOnly_startRouter() {
Ben Rohlfsdc22ff42023-02-06 10:21:28 +0100615 this.startRouter({dispatch: false, base: getBaseUrl()});
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100616 }
617
Ben Rohlfsdc22ff42023-02-06 10:21:28 +0100618 startRouter(opts: PageOptions = {dispatch: true, base: getBaseUrl()}) {
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100619 this.page.registerExitRoute(/(.*)/, (_, next) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100620 if (!this._isRedirecting) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100621 this.reporting.beforeLocationChanged();
Viktar Donicha28dee062017-11-10 16:03:13 -0800622 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100623 this._isRedirecting = false;
624 this._isInitialLoad = false;
625 next();
626 });
Viktar Donicha28dee062017-11-10 16:03:13 -0800627
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100628 // Remove the tracking param 'usp' (User Source Parameter) from the URL,
629 // just to have users look at cleaner URLs.
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100630 this.page.registerRoute(/(.*)/, (ctx, next) => {
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100631 if (window.URLSearchParams) {
632 const pathname = toPathname(ctx.canonicalPath);
633 const searchParams = toSearchParams(ctx.canonicalPath);
634 if (searchParams.has('usp')) {
Ben Rohlfs6fb09dd2021-03-12 09:24:26 +0100635 const usp = searchParams.get('usp');
636 this.reporting.reportLifeCycle(LifeCycle.USER_REFERRED_FROM, {usp});
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100637 searchParams.delete('usp');
Frank Borden79448112022-04-12 16:59:32 +0200638 this.redirect(toPath(pathname, searchParams));
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100639 return;
640 }
641 }
642 next();
643 });
644
Ben Rohlfsf6657362023-04-12 14:01:35 +0200645 // Block navigation while navigationBlockers exist. But wait 1 second for
646 // those blockers to resolve. If they do, then still navigate. We don't want
647 // to annoy users by forcing them to navigate twice only because it took
648 // another 200ms for a comment to save or something similar.
649 this.page.registerRoute(/(.*)/, (_, next) => {
650 if (this.navigationBlockers.size === 0) {
651 next();
652 return;
653 }
654
655 const msg = 'Waiting 1 second for navigation blockers to resolve ...';
656 fireAlert(document, msg);
657 Promise.race([this.navigationBlockerPromise, timeoutPromise(1000)]).then(
658 () => {
659 if (this.navigationBlockers.size === 0) {
660 next();
661 } else {
662 const reason = [...this.navigationBlockers][0];
663 fireAlert(document, `Navigation is blocked by: ${reason}`);
664 }
665 }
666 );
667 });
668
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100669 // Middleware
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100670 this.page.registerRoute(/(.*)/, (ctx, next) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100671 document.body.scrollTop = 0;
Viktar Donicha28dee062017-11-10 16:03:13 -0800672
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100673 if (ctx.hash.match(RoutePattern.PLUGIN_SCREEN)) {
674 // Redirect all urls using hash #/x/plugin/screen to /x/plugin/screen
675 // This is needed to allow plugins to add basic #/x/ screen links to
676 // any location.
Frank Borden79448112022-04-12 16:59:32 +0200677 this.redirect(ctx.hash);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700678 return;
679 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700680
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100681 // Fire asynchronously so that the URL is changed by the time the event
682 // is processed.
Ben Rohlfs6b078932021-03-10 15:20:03 +0100683 setTimeout(() => {
sergeyfac4cab2022-11-11 22:19:46 +0400684 this.dispatchLocationChangeEvent();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100685 }, 1);
686 next();
687 });
688
Frank Borden5ea7d652022-05-10 16:52:11 +0200689 this.mapRoute(RoutePattern.ROOT, 'handleRootRoute', ctx =>
690 this.handleRootRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200691 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100692
Frank Borden5ea7d652022-05-10 16:52:11 +0200693 this.mapRoute(RoutePattern.DASHBOARD, 'handleDashboardRoute', ctx =>
694 this.handleDashboardRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200695 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100696
Frank Borden79448112022-04-12 16:59:32 +0200697 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200698 RoutePattern.CUSTOM_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200699 'handleCustomDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200700 ctx => this.handleCustomDashboardRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200701 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100702
Ben Rohlfs4237f292023-02-11 13:06:01 +0100703 this.mapRouteState(
704 PROJECT_DASHBOARD_ROUTE,
705 this.dashboardViewModel,
706 'handleProjectDashboardRoute'
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200707 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100708
Frank Borden79448112022-04-12 16:59:32 +0200709 this.mapRoute(
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200710 RoutePattern.LEGACY_PROJECT_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200711 'handleLegacyProjectDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200712 ctx => this.handleLegacyProjectDashboardRoute(ctx)
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200713 );
714
Frank Borden79448112022-04-12 16:59:32 +0200715 this.mapRoute(
716 RoutePattern.GROUP_INFO,
717 'handleGroupInfoRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200718 ctx => this.handleGroupInfoRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200719 true
720 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100721
Frank Borden79448112022-04-12 16:59:32 +0200722 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200723 RoutePattern.GROUP_AUDIT_LOG,
Frank Borden79448112022-04-12 16:59:32 +0200724 'handleGroupAuditLogRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200725 ctx => this.handleGroupAuditLogRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200726 true
727 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100728
Frank Borden79448112022-04-12 16:59:32 +0200729 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200730 RoutePattern.GROUP_MEMBERS,
Frank Borden79448112022-04-12 16:59:32 +0200731 'handleGroupMembersRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200732 ctx => this.handleGroupMembersRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200733 true
734 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100735
Frank Borden79448112022-04-12 16:59:32 +0200736 this.mapRoute(
Ben Rohlfs8f064482023-01-27 09:52:51 +0100737 RoutePattern.GROUP_LIST,
738 'handleGroupListRoute',
739 ctx => this.handleGroupListRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200740 true
741 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100742
Frank Borden79448112022-04-12 16:59:32 +0200743 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200744 RoutePattern.GROUP_SELF,
Frank Borden79448112022-04-12 16:59:32 +0200745 'handleGroupSelfRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200746 ctx => this.handleGroupSelfRedirectRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200747 true
748 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100749
Frank Borden79448112022-04-12 16:59:32 +0200750 this.mapRoute(
751 RoutePattern.GROUP,
752 'handleGroupRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200753 ctx => this.handleGroupRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200754 true
755 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100756
Frank Borden5ea7d652022-05-10 16:52:11 +0200757 this.mapRoute(RoutePattern.PROJECT_OLD, 'handleProjectsOldRoute', ctx =>
758 this.handleProjectsOldRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200759 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100760
Frank Borden79448112022-04-12 16:59:32 +0200761 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200762 RoutePattern.REPO_COMMANDS,
Frank Borden79448112022-04-12 16:59:32 +0200763 'handleRepoCommandsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200764 ctx => this.handleRepoCommandsRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200765 true
766 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100767
Ben Rohlfsc35ed182022-11-25 12:02:52 +0000768 this.mapRoute(
769 RoutePattern.REPO_EDIT_FILE,
770 'handleRepoEditFileRoute',
771 ctx => this.handleRepoEditFileRoute(ctx),
772 true
773 );
774
Frank Borden5ea7d652022-05-10 16:52:11 +0200775 this.mapRoute(RoutePattern.REPO_GENERAL, 'handleRepoGeneralRoute', ctx =>
776 this.handleRepoGeneralRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200777 );
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +0200778
Frank Borden5ea7d652022-05-10 16:52:11 +0200779 this.mapRoute(RoutePattern.REPO_ACCESS, 'handleRepoAccessRoute', ctx =>
780 this.handleRepoAccessRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200781 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100782
Frank Borden79448112022-04-12 16:59:32 +0200783 this.mapRoute(
784 RoutePattern.REPO_DASHBOARDS,
785 'handleRepoDashboardsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200786 ctx => this.handleRepoDashboardsRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200787 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100788
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100789 this.mapRoute(RoutePattern.BRANCH_LIST, 'handleBranchListRoute', ctx =>
790 this.handleBranchListRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200791 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100792
Ben Rohlfsb57102f2023-01-27 12:26:56 +0100793 this.mapRoute(RoutePattern.TAG_LIST, 'handleTagListRoute', ctx =>
794 this.handleTagListRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200795 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100796
Frank Borden79448112022-04-12 16:59:32 +0200797 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200798 RoutePattern.LEGACY_CREATE_GROUP,
Frank Borden79448112022-04-12 16:59:32 +0200799 'handleCreateGroupRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200800 ctx => this.handleCreateGroupRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200801 true
802 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100803
Frank Borden79448112022-04-12 16:59:32 +0200804 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200805 RoutePattern.LEGACY_CREATE_PROJECT,
Frank Borden79448112022-04-12 16:59:32 +0200806 'handleCreateProjectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200807 ctx => this.handleCreateProjectRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200808 true
809 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100810
Ben Rohlfs26306a42023-01-27 12:13:50 +0100811 this.mapRoute(RoutePattern.REPO_LIST, 'handleRepoListRoute', ctx =>
812 this.handleRepoListRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200813 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100814
Frank Borden5ea7d652022-05-10 16:52:11 +0200815 this.mapRoute(RoutePattern.REPO, 'handleRepoRoute', ctx =>
816 this.handleRepoRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200817 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100818
Frank Borden79448112022-04-12 16:59:32 +0200819 this.mapRoute(
Ben Rohlfs4591b042023-01-27 09:39:06 +0100820 RoutePattern.PLUGIN_LIST,
Frank Borden79448112022-04-12 16:59:32 +0200821 'handlePluginListFilterRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200822 ctx => this.handlePluginListFilterRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200823 true
824 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100825
Ben Rohlfs2e47ef72023-01-23 17:11:38 +0100826 this.mapRouteState(
827 PLUGIN_LIST_ROUTE,
828 this.adminViewModel,
Frank Borden79448112022-04-12 16:59:32 +0200829 'handlePluginListRoute',
Frank Borden79448112022-04-12 16:59:32 +0200830 true
831 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100832
Frank Borden79448112022-04-12 16:59:32 +0200833 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200834 RoutePattern.QUERY_LEGACY_SUFFIX,
Frank Borden79448112022-04-12 16:59:32 +0200835 'handleQueryLegacySuffixRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200836 ctx => this.handleQueryLegacySuffixRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200837 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100838
Frank Borden5ea7d652022-05-10 16:52:11 +0200839 this.mapRoute(RoutePattern.QUERY, 'handleQueryRoute', ctx =>
840 this.handleQueryRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200841 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100842
Frank Borden79448112022-04-12 16:59:32 +0200843 this.mapRoute(
844 RoutePattern.CHANGE_ID_QUERY,
845 'handleChangeIdQueryRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200846 ctx => this.handleChangeIdQueryRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200847 );
Peter Collingbourne8cab9332020-08-18 14:42:44 -0700848
Frank Borden79448112022-04-12 16:59:32 +0200849 this.mapRoute(
850 RoutePattern.DIFF_LEGACY_LINENUM,
851 'handleLegacyLinenum',
Frank Borden5ea7d652022-05-10 16:52:11 +0200852 ctx => this.handleLegacyLinenum(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200853 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100854
Frank Borden79448112022-04-12 16:59:32 +0200855 this.mapRoute(
Milutin Kristofic602145d2023-10-25 07:18:08 +0000856 RoutePattern.CHANGE_NUMBER_LEGACY,
857 'handleChangeNumberLegacyRoute',
858 ctx => this.handleChangeNumberLegacyRoute(ctx)
859 );
860
861 this.mapRoute(
Frank Borden79448112022-04-12 16:59:32 +0200862 RoutePattern.DIFF_EDIT,
863 'handleDiffEditRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200864 ctx => this.handleDiffEditRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200865 true
866 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100867
Frank Borden79448112022-04-12 16:59:32 +0200868 this.mapRoute(
869 RoutePattern.CHANGE_EDIT,
870 'handleChangeEditRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200871 ctx => this.handleChangeEditRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200872 true
873 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100874
Frank Borden5ea7d652022-05-10 16:52:11 +0200875 this.mapRoute(RoutePattern.COMMENT, 'handleCommentRoute', ctx =>
876 this.handleCommentRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200877 );
Dhruv Srivastava90240452020-07-02 15:51:53 +0200878
Frank Borden5ea7d652022-05-10 16:52:11 +0200879 this.mapRoute(RoutePattern.COMMENTS_TAB, 'handleCommentsRoute', ctx =>
880 this.handleCommentsRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200881 );
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +0200882
Frank Borden5ea7d652022-05-10 16:52:11 +0200883 this.mapRoute(RoutePattern.DIFF, 'handleDiffRoute', ctx =>
884 this.handleDiffRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200885 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100886
Frank Borden5ea7d652022-05-10 16:52:11 +0200887 this.mapRoute(RoutePattern.CHANGE, 'handleChangeRoute', ctx =>
888 this.handleChangeRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200889 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100890
Milutin Kristofic602145d2023-10-25 07:18:08 +0000891 this.mapRoute(RoutePattern.CHANGE_LEGACY, 'handleChangeLegacyRoute', ctx =>
892 this.handleChangeLegacyRoute(ctx)
893 );
894
Frank Borden79448112022-04-12 16:59:32 +0200895 this.mapRoute(
896 RoutePattern.AGREEMENTS,
897 'handleAgreementsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200898 () => this.handleAgreementsRoute(),
Frank Borden79448112022-04-12 16:59:32 +0200899 true
900 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100901
Frank Borden79448112022-04-12 16:59:32 +0200902 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200903 RoutePattern.NEW_AGREEMENTS,
Frank Borden79448112022-04-12 16:59:32 +0200904 'handleNewAgreementsRoute',
Ben Rohlfs2586e572022-09-16 12:55:37 +0200905 () => this.handleNewAgreementsRoute(),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200906 true
907 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100908
Frank Borden79448112022-04-12 16:59:32 +0200909 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200910 RoutePattern.SETTINGS_LEGACY,
Frank Borden79448112022-04-12 16:59:32 +0200911 'handleSettingsLegacyRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200912 ctx => this.handleSettingsLegacyRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200913 true
914 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100915
Frank Borden79448112022-04-12 16:59:32 +0200916 this.mapRoute(
917 RoutePattern.SETTINGS,
918 'handleSettingsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200919 ctx => this.handleSettingsRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200920 true
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200921 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100922
Frank Borden5ea7d652022-05-10 16:52:11 +0200923 this.mapRoute(RoutePattern.REGISTER, 'handleRegisterRoute', ctx =>
924 this.handleRegisterRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200925 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100926
Frank Borden79448112022-04-12 16:59:32 +0200927 this.mapRoute(
928 RoutePattern.IMPROPERLY_ENCODED_PLUS,
929 'handleImproperlyEncodedPlusRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200930 ctx => this.handleImproperlyEncodedPlusRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200931 );
932
Frank Borden5ea7d652022-05-10 16:52:11 +0200933 this.mapRoute(RoutePattern.PLUGIN_SCREEN, 'handlePluginScreen', ctx =>
934 this.handlePluginScreen(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200935 );
936
Edwin Kempinc2fce752024-09-17 14:39:13 +0000937 this.mapRouteState(
938 SERVER_INFO_ROUTE,
939 this.adminViewModel,
940 'handleServerInfoRoute',
941 true
942 );
943
Frank Borden79448112022-04-12 16:59:32 +0200944 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200945 RoutePattern.DOCUMENTATION_SEARCH_FILTER,
Frank Borden79448112022-04-12 16:59:32 +0200946 'handleDocumentationSearchRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200947 ctx => this.handleDocumentationSearchRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200948 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100949
950 // redirects /Documentation/q/* to /Documentation/q/filter:*
Frank Borden79448112022-04-12 16:59:32 +0200951 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200952 RoutePattern.DOCUMENTATION_SEARCH,
Frank Borden79448112022-04-12 16:59:32 +0200953 'handleDocumentationSearchRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200954 ctx => this.handleDocumentationSearchRedirectRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200955 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100956
Frank Borden5ea7d652022-05-10 16:52:11 +0200957 // Makes sure /Documentation/* links work (don't return 404)
Frank Borden79448112022-04-12 16:59:32 +0200958 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200959 RoutePattern.DOCUMENTATION,
Frank Borden79448112022-04-12 16:59:32 +0200960 'handleDocumentationRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200961 ctx => this.handleDocumentationRedirectRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200962 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100963
964 // Note: this route should appear last so it only catches URLs unmatched
965 // by other patterns.
Frank Borden5ea7d652022-05-10 16:52:11 +0200966 this.mapRoute(RoutePattern.DEFAULT, 'handleDefaultRoute', () =>
967 this.handleDefaultRoute()
Frank Borden79448112022-04-12 16:59:32 +0200968 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100969
Ben Rohlfsf27798d2023-01-26 12:45:21 +0100970 this.page.start(opts);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100971 }
972
973 /**
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200974 * @return if handling the route involves asynchrony, then a
975 * promise is returned. Otherwise, synchronous handling returns null.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100976 */
Ben Rohlfs5940c532022-09-20 09:19:50 +0200977 handleRootRoute(ctx: PageContext) {
978 if (ctx.querystring.match(/^closeAfterLogin/)) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100979 // Close child window on redirect after login.
980 window.close();
981 return null;
982 }
Ben Rohlfs5940c532022-09-20 09:19:50 +0200983 let hash = this.getHashFromCanonicalPath(ctx.canonicalPath);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100984 // For backward compatibility with GWT links.
985 if (hash) {
986 // In certain login flows the server may redirect to a hash without
Ben Rohlfs1da030b2023-01-31 13:09:53 +0100987 // a leading slash, which gr-page doesn't handle correctly.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100988 if (hash[0] !== '/') {
989 hash = '/' + hash;
990 }
Ben Rohlfs5940c532022-09-20 09:19:50 +0200991 if (hash.includes('/ /') && ctx.canonicalPath.includes('/+/')) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100992 // Path decodes all '+' to ' ' -- this breaks project-based URLs.
993 // See Issue 6888.
994 hash = hash.replace('/ /', '/+/');
995 }
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200996 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100997 let newUrl = base + hash;
998 if (hash.startsWith('/VE/')) {
999 newUrl = base + '/settings' + hash;
Wyatt Allen84505ea2017-08-24 10:53:05 -07001000 }
Frank Borden79448112022-04-12 16:59:32 +02001001 this.redirect(newUrl);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001002 return null;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001003 }
Ben Rohlfs43935a42020-12-01 19:14:09 +01001004 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001005 if (loggedIn) {
Frank Borden79448112022-04-12 16:59:32 +02001006 this.redirect('/dashboard/self');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001007 } else {
Frank Borden79448112022-04-12 16:59:32 +02001008 this.redirect('/q/status:open+-is:wip');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001009 }
1010 });
1011 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001012
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001013 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001014 * Handle dashboard routes. These may be user, or project dashboards.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001015 */
Ben Rohlfs5940c532022-09-20 09:19:50 +02001016 handleDashboardRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001017 // User dashboard. We require viewing user to be logged in, else we
1018 // redirect to login for self dashboard or simple owner search for
1019 // other user dashboard.
Ben Rohlfs43935a42020-12-01 19:14:09 +01001020 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001021 if (!loggedIn) {
Ben Rohlfs5940c532022-09-20 09:19:50 +02001022 if (ctx.params[0].toLowerCase() === 'self') {
1023 this.redirectToLogin(ctx.canonicalPath);
Wyatt Allen089dfb32017-08-24 17:55:38 -07001024 } else {
Ben Rohlfsc02facb2023-01-27 18:46:02 +01001025 this.redirect('/q/owner:' + encodeURL(ctx.params[0]));
Wyatt Allen089dfb32017-08-24 17:55:38 -07001026 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001027 } else {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001028 const state: DashboardViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001029 view: GerritView.DASHBOARD,
Kamil Musin3e3da5a2023-10-18 16:19:13 +02001030 type: DashboardType.USER,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001031 user: ctx.params[0],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001032 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001033 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001034 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001035 this.dashboardViewModel.setState(state);
Wyatt Allen089dfb32017-08-24 17:55:38 -07001036 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001037 });
1038 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001039
Ben Rohlfs82b46492022-09-20 09:13:56 +02001040 handleCustomDashboardRoute(ctx: PageContext) {
1041 const queryParams = new URLSearchParams(ctx.querystring);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001042
Ben Rohlfs82b46492022-09-20 09:13:56 +02001043 let title = 'Custom Dashboard';
1044 const titleParam = queryParams.get('title');
1045 if (titleParam) title = titleParam;
1046 queryParams.delete('title');
1047
1048 let forEachQuery = '';
1049 const forEachParam = queryParams.get('foreach');
1050 if (forEachParam) forEachQuery = forEachParam + ' ';
1051 queryParams.delete('foreach');
1052
1053 const sections: DashboardSection[] = [];
1054 for (const [name, query] of queryParams) {
1055 if (!name || !query) continue;
1056 sections.push({name, query: `${forEachQuery}${query}`});
1057 }
1058
1059 if (sections.length === 0) {
1060 this.redirect('/dashboard/self');
Wyatt Allenee2016c2017-10-31 13:41:52 -07001061 return Promise.resolve();
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001062 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001063
Ben Rohlfs82b46492022-09-20 09:13:56 +02001064 const state: DashboardViewState = {
1065 view: GerritView.DASHBOARD,
Kamil Musin3e3da5a2023-10-18 16:19:13 +02001066 type: DashboardType.CUSTOM,
Ben Rohlfs82b46492022-09-20 09:13:56 +02001067 user: 'self',
1068 sections,
1069 title,
1070 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001071 // Note that router model view must be updated before view models.
Ben Rohlfs82b46492022-09-20 09:13:56 +02001072 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001073 this.dashboardViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001074 return Promise.resolve();
1075 }
Logan Hanksc34e0b62017-10-03 01:40:54 -07001076
Ben Rohlfs5940c532022-09-20 09:19:50 +02001077 handleLegacyProjectDashboardRoute(ctx: PageContext) {
1078 this.redirect('/p/' + ctx.params[0] + '/+/dashboard/' + ctx.params[1]);
Jacek Centkowskid322bd42020-09-16 10:34:55 +02001079 }
1080
Ben Rohlfs5940c532022-09-20 09:19:50 +02001081 handleGroupInfoRoute(ctx: PageContext) {
Ben Rohlfs678e19d2023-01-13 14:26:14 +00001082 const groupId = ctx.params[0] as GroupId;
1083 this.redirect(createGroupUrl({groupId}));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001084 }
Paladox nonef7303f72019-06-27 14:57:11 +00001085
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001086 handleGroupSelfRedirectRoute(_: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001087 this.redirect('/settings/#Groups');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001088 }
Wyatt Allenc1727672017-10-19 15:06:54 -07001089
Ben Rohlfs5940c532022-09-20 09:19:50 +02001090 handleGroupRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001091 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001092 view: GerritView.GROUP,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001093 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001094 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001095 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001096 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001097 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001098 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001099
Ben Rohlfs5940c532022-09-20 09:19:50 +02001100 handleGroupAuditLogRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001101 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001102 view: GerritView.GROUP,
1103 detail: GroupDetailView.LOG,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001104 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001105 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001106 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001107 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001108 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001109 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001110
Ben Rohlfs5940c532022-09-20 09:19:50 +02001111 handleGroupMembersRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001112 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001113 view: GerritView.GROUP,
1114 detail: GroupDetailView.MEMBERS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001115 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001116 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001117 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001118 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001119 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001120 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001121
Ben Rohlfs8f064482023-01-27 09:52:51 +01001122 handleGroupListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001123 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001124 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001125 adminView: AdminChildView.GROUPS,
Ben Rohlfsc33a8642023-02-13 11:45:15 +01001126 offset: ctx.params[1] || '0',
1127 filter: ctx.params[0] ?? null,
Ben Rohlfs8f064482023-01-27 09:52:51 +01001128 openCreateModal:
Ben Rohlfsc33a8642023-02-13 11:45:15 +01001129 !ctx.params[0] && !ctx.params[1] && ctx.hash === 'create',
Ben Rohlfs2586e572022-09-16 12:55:37 +02001130 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001131 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001132 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001133 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001134 }
Paladox none87f99602019-07-05 12:58:23 +00001135
Ben Rohlfs5940c532022-09-20 09:19:50 +02001136 handleProjectsOldRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001137 let params = '';
Ben Rohlfs5940c532022-09-20 09:19:50 +02001138 if (ctx.params[1]) {
1139 params = encodeURIComponent(ctx.params[1]);
1140 if (ctx.params[1].includes(',')) {
1141 params = encodeURIComponent(ctx.params[1]).replace('%2C', ',');
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001142 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001143 }
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001144
Ben Rohlfs678e19d2023-01-13 14:26:14 +00001145 // TODO: Change the route pattern to match `repo` and `detailView`
1146 // separately, and then use `createRepoUrl()` here.
Frank Borden79448112022-04-12 16:59:32 +02001147 this.redirect(`/admin/repos/${params}`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001148 }
1149
Ben Rohlfs5940c532022-09-20 09:19:50 +02001150 handleRepoCommandsRoute(ctx: PageContext) {
1151 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001152 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001153 view: GerritView.REPO,
1154 detail: RepoDetailView.COMMANDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001155 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001156 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001157 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001158 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001159 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001160 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001161 }
1162
Ben Rohlfsc35ed182022-11-25 12:02:52 +00001163 handleRepoEditFileRoute(ctx: PageContext) {
1164 const repo = ctx.params[0] as RepoName;
1165 const branch = ctx.params[1] as BranchName;
1166 const path = ctx.params[2];
1167 const state: RepoViewState = {
1168 view: GerritView.REPO,
1169 detail: RepoDetailView.COMMANDS,
1170 repo,
1171 createEdit: {branch, path},
1172 };
1173 // Note that router model view must be updated before view models.
1174 this.setState(state);
1175 this.repoViewModel.setState(state);
1176 this.reporting.setRepoName(repo);
1177 }
1178
Ben Rohlfs5940c532022-09-20 09:19:50 +02001179 handleRepoGeneralRoute(ctx: PageContext) {
1180 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001181 const state: RepoViewState = {
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +02001182 view: GerritView.REPO,
1183 detail: RepoDetailView.GENERAL,
1184 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001185 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001186 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001187 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001188 this.repoViewModel.setState(state);
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +02001189 this.reporting.setRepoName(repo);
1190 }
1191
Ben Rohlfs5940c532022-09-20 09:19:50 +02001192 handleRepoAccessRoute(ctx: PageContext) {
1193 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001194 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001195 view: GerritView.REPO,
1196 detail: RepoDetailView.ACCESS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001197 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001198 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001199 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001200 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001201 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001202 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001203 }
1204
Ben Rohlfs5940c532022-09-20 09:19:50 +02001205 handleRepoDashboardsRoute(ctx: PageContext) {
1206 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001207 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001208 view: GerritView.REPO,
1209 detail: RepoDetailView.DASHBOARDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001210 repo,
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.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001215 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001216 }
1217
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001218 handleBranchListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001219 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001220 view: GerritView.REPO,
1221 detail: RepoDetailView.BRANCHES,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001222 repo: ctx.params[0] as RepoName,
Ben Rohlfs1da030b2023-01-31 13:09:53 +01001223 offset: ctx.params[2] || '0',
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001224 filter: ctx.params[1] ?? null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001225 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001226 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001227 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001228 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001229 }
1230
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001231 handleTagListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001232 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001233 view: GerritView.REPO,
1234 detail: RepoDetailView.TAGS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001235 repo: ctx.params[0] as RepoName,
Ben Rohlfs1da030b2023-01-31 13:09:53 +01001236 offset: ctx.params[2] || '0',
Ben Rohlfsb57102f2023-01-27 12:26:56 +01001237 filter: ctx.params[1] ?? null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001238 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001239 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001240 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001241 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001242 }
1243
Ben Rohlfs26306a42023-01-27 12:13:50 +01001244 handleRepoListRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001245 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001246 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001247 adminView: AdminChildView.REPOS,
Ben Rohlfsc33a8642023-02-13 11:45:15 +01001248 offset: ctx.params[1] || '0',
1249 filter: ctx.params[0] ?? null,
Ben Rohlfs26306a42023-01-27 12:13:50 +01001250 openCreateModal:
Ben Rohlfsc33a8642023-02-13 11:45:15 +01001251 !ctx.params[0] && !ctx.params[1] && ctx.hash === 'create',
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 Rohlfs2586e572022-09-16 12:55:37 +02001254 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001255 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001256 }
1257
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001258 handleCreateProjectRoute(_: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001259 // Redirects the legacy route to the new route, which displays the project
1260 // list with a hash 'create'.
Frank Borden79448112022-04-12 16:59:32 +02001261 this.redirect('/admin/repos#create');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001262 }
1263
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001264 handleCreateGroupRoute(_: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001265 // Redirects the legacy route to the new route, which displays the group
1266 // list with a hash 'create'.
Frank Borden79448112022-04-12 16:59:32 +02001267 this.redirect('/admin/groups#create');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001268 }
1269
Ben Rohlfs5940c532022-09-20 09:19:50 +02001270 handleRepoRoute(ctx: PageContext) {
1271 this.redirect(ctx.path + ',general');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001272 }
1273
Ben Rohlfs5940c532022-09-20 09:19:50 +02001274 handlePluginListFilterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001275 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001276 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001277 adminView: AdminChildView.PLUGINS,
Ben Rohlfsc33a8642023-02-13 11:45:15 +01001278 offset: ctx.params[1] || '0',
1279 filter: ctx.params[0] ?? null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001280 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001281 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001282 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001283 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001284 }
1285
Ben Rohlfs5940c532022-09-20 09:19:50 +02001286 handleQueryRoute(ctx: PageContext) {
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001287 const state: SearchViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001288 view: GerritView.SEARCH,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001289 query: ctx.params[0],
Ben Rohlfs1da030b2023-01-31 13:09:53 +01001290 offset: ctx.params[2] || '0',
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001291 loading: false,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001292 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001293 // Note that router model view must be updated before view models.
Ben Rohlfs64a17892022-10-18 10:05:46 +02001294 this.setState(state as AppElementParams);
1295 this.searchViewModel.updateState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001296 }
1297
Ben Rohlfs5940c532022-09-20 09:19:50 +02001298 handleChangeIdQueryRoute(ctx: PageContext) {
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001299 // TODO(pcc): This will need to indicate that this was a change ID query if
1300 // standard queries gain the ability to search places like commit messages
1301 // for change IDs.
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001302 const state: SearchViewState = {
Ben Rohlfs8163cd42022-08-18 16:04:36 +02001303 view: GerritView.SEARCH,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001304 query: ctx.params[0],
Ben Rohlfs1da030b2023-01-31 13:09:53 +01001305 offset: '0',
Ben Rohlfsf27798d2023-01-26 12:45:21 +01001306 loading: false,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001307 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001308 // Note that router model view must be updated before view models.
Ben Rohlfs64a17892022-10-18 10:05:46 +02001309 this.setState(state as AppElementParams);
1310 this.searchViewModel.updateState(state);
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001311 }
1312
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001313 handleQueryLegacySuffixRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001314 this.redirect(ctx.path.replace(LEGACY_QUERY_SUFFIX_PATTERN, ''));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001315 }
1316
Milutin Kristofic602145d2023-10-25 07:18:08 +00001317 handleChangeNumberLegacyRoute(ctx: PageContext) {
1318 this.redirect(
1319 '/c/' +
1320 ctx.params[0] +
1321 (ctx.querystring.length > 0 ? `?${ctx.querystring}` : '')
1322 );
1323 }
1324
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001325 handleChangeRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001326 // Parameter order is based on the regex group number matched.
Milutin Kristofic3a844522021-01-21 10:01:55 +01001327 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001328 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001329 repo: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001330 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001331 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001332 patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001333 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001334 childView: ChangeChildView.OVERVIEW,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001335 };
1336
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001337 const queryMap = new URLSearchParams(ctx.querystring);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001338 if (queryMap.has('openReplyDialog')) state.openReplyDialog = true;
Dhruv2cc210c2022-06-07 14:18:03 +02001339
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001340 const tab = queryMap.get('tab');
Ben Rohlfsd49e8332023-04-20 22:40:04 +02001341 if (queryMap.has('forceReload')) state.forceReload = true;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001342 if (tab) state.tab = tab;
Ben Rohlfsa1d2c0c2022-09-29 17:03:26 +02001343 const checksPatchset = Number(queryMap.get('checksPatchset'));
1344 if (Number.isInteger(checksPatchset) && checksPatchset > 0) {
1345 state.checksPatchset = checksPatchset as PatchSetNumber;
1346 }
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001347 const filter = queryMap.get('filter');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001348 if (filter) state.filter = filter;
Ben Rohlfs209f1412022-09-30 12:25:46 +02001349 const checksResultsFilter = queryMap.get('checksResultsFilter');
1350 if (checksResultsFilter) state.checksResultsFilter = checksResultsFilter;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001351 const attempt = stringToAttemptChoice(queryMap.get('attempt'));
Ben Rohlfs2586e572022-09-16 12:55:37 +02001352 if (attempt && attempt !== LATEST_ATTEMPT) state.attempt = attempt;
Ben Rohlfs6485eb82022-09-30 11:26:08 +02001353 const selected = queryMap.get('checksRunsSelected');
Ben Rohlfs20fe8e82022-10-14 11:13:10 +02001354 if (selected) state.checksRunsSelected = new Set(selected.split(','));
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001355
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001356 assertIsDefined(state.repo, 'project');
1357 this.reporting.setRepoName(state.repo);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001358 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001359 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001360 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001361 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001362 this.changeViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001363 }
1364
Ben Rohlfs196dc722022-12-20 18:01:33 +01001365 async handleCommentRoute(ctx: PageContext) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001366 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs196dc722022-12-20 18:01:33 +01001367 const repo = ctx.params[0] as RepoName;
1368 const commentId = ctx.params[2] as UrlEncodedCommentId;
1369
Kamil Musin48677cb2024-02-27 17:06:12 +01001370 this.restApiService.addRepoNameToCache(changeNum, repo);
Ben Rohlfs7bee1262023-02-08 09:19:17 +01001371 const [comments, robotComments, drafts, change] = await Promise.all([
Ben Rohlfsc5576542023-01-31 13:16:59 +01001372 this.restApiService.getDiffComments(changeNum),
1373 this.restApiService.getDiffRobotComments(changeNum),
Ben Rohlfs7bee1262023-02-08 09:19:17 +01001374 this.restApiService.getDiffDrafts(changeNum),
Ben Rohlfsc5576542023-01-31 13:16:59 +01001375 this.restApiService.getChangeDetail(changeNum),
1376 ]);
Ben Rohlfs196dc722022-12-20 18:01:33 +01001377
Ben Rohlfsc5576542023-01-31 13:16:59 +01001378 const comment =
1379 findComment(addPath(comments), commentId) ??
Ben Rohlfs7bee1262023-02-08 09:19:17 +01001380 findComment(addPath(robotComments), commentId) ??
1381 findComment(addPath(drafts), commentId);
Ben Rohlfs196dc722022-12-20 18:01:33 +01001382 const path = comment?.path;
1383 const patchsets = computeAllPatchSets(change);
1384 const latestPatchNum = computeLatestPatchNum(patchsets);
1385 if (!comment || !path || !latestPatchNum) {
1386 this.show404();
1387 return;
1388 }
1389 let {basePatchNum, patchNum} = getPatchRangeForCommentUrl(
1390 comment,
1391 latestPatchNum
1392 );
1393
1394 if (basePatchNum !== PARENT) {
1395 const diff = await this.restApiService.getDiff(
1396 changeNum,
1397 basePatchNum,
1398 patchNum,
1399 path
1400 );
1401 if (diff && isFileUnchanged(diff)) {
1402 fireAlert(
1403 document,
1404 `File is unchanged between Patchset ${basePatchNum} and ${patchNum}.
1405 Showing diff of Base vs ${basePatchNum}.`
1406 );
1407 patchNum = basePatchNum as RevisionPatchSetNum;
1408 basePatchNum = PARENT;
1409 }
1410 }
1411
1412 const diffUrl = createDiffUrl({
Milutin Kristofic3a844522021-01-21 10:01:55 +01001413 changeNum,
Ben Rohlfs196dc722022-12-20 18:01:33 +01001414 repo,
1415 patchNum,
1416 basePatchNum,
1417 diffView: {
1418 path,
1419 lineNum: comment.line,
1420 leftSide: isInBaseOfPatchRange(comment, {basePatchNum, patchNum}),
1421 },
1422 });
1423 this.redirect(diffUrl);
Dhruv Srivastava90240452020-07-02 15:51:53 +02001424 }
1425
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001426 handleCommentsRoute(ctx: PageContext) {
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001427 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001428 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001429 repo: ctx.params[0] as RepoName,
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001430 changeNum,
1431 commentId: ctx.params[2] as UrlEncodedCommentId,
1432 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001433 childView: ChangeChildView.OVERVIEW,
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001434 };
Ben Rohlfsd49e8332023-04-20 22:40:04 +02001435 const queryMap = new URLSearchParams(ctx.querystring);
1436 if (queryMap.has('forceReload')) state.forceReload = true;
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001437 assertIsDefined(state.repo);
1438 this.reporting.setRepoName(state.repo);
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001439 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001440 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001441 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001442 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001443 this.changeViewModel.setState(state);
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001444 }
1445
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001446 handleDiffRoute(ctx: PageContext) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001447 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001448 // Parameter order is based on the regex group number matched.
Ben Rohlfsecc67992022-12-16 10:10:16 +01001449 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001450 repo: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001451 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001452 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001453 patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001454 view: GerritView.CHANGE,
1455 childView: ChangeChildView.DIFF,
1456 diffView: {path: ctx.params[8]},
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001457 };
Ben Rohlfsd49e8332023-04-20 22:40:04 +02001458 const queryMap = new URLSearchParams(ctx.querystring);
Ben Rohlfs05234f52023-05-17 09:40:47 +02001459 const checksPatchset = Number(queryMap.get('checksPatchset'));
1460 if (Number.isInteger(checksPatchset) && checksPatchset > 0) {
1461 state.checksPatchset = checksPatchset as PatchSetNumber;
1462 }
Ben Rohlfsd49e8332023-04-20 22:40:04 +02001463 if (queryMap.has('forceReload')) state.forceReload = true;
Frank Borden79448112022-04-12 16:59:32 +02001464 const address = this.parseLineAddress(ctx.hash);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001465 if (address) {
Ben Rohlfsecc67992022-12-16 10:10:16 +01001466 state.diffView!.leftSide = address.leftSide;
1467 state.diffView!.lineNum = address.lineNum;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001468 }
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001469 this.reporting.setRepoName(state.repo ?? '');
Milutin Kristofic3a844522021-01-21 10:01:55 +01001470 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001471 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001472 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001473 this.setState(state);
Ben Rohlfsecc67992022-12-16 10:10:16 +01001474 this.changeViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001475 }
1476
Milutin Kristofic602145d2023-10-25 07:18:08 +00001477 handleChangeLegacyRoute(ctx: PageContext) {
1478 const changeNum = Number(ctx.params[0]) as NumericChangeId;
1479 if (!changeNum) {
1480 this.show404();
1481 return;
1482 }
Kamil Musin48677cb2024-02-27 17:06:12 +01001483 this.restApiService.getRepoName(changeNum).then(project => {
Milutin Kristofic602145d2023-10-25 07:18:08 +00001484 // Show a 404 and terminate if the lookup request failed. Attempting
1485 // to redirect after failing to get the project loops infinitely.
1486 if (!project) {
1487 this.show404();
1488 return;
1489 }
1490 this.redirect(
1491 `/c/${project}/+/${changeNum}/${ctx.params[1]}` +
1492 (ctx.querystring.length > 0 ? `?${ctx.querystring}` : '')
1493 );
1494 });
1495 }
1496
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001497 handleLegacyLinenum(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001498 this.redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001499 }
1500
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001501 handleDiffEditRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001502 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001503 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001504 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfsecc67992022-12-16 10:10:16 +01001505 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001506 repo: project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001507 changeNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001508 // for edit view params, patchNum cannot be undefined
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001509 patchNum: convertToPatchSetNum(ctx.params[2]) as RevisionPatchSetNum,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001510 view: GerritView.CHANGE,
1511 childView: ChangeChildView.EDIT,
1512 editView: {path: ctx.params[3], lineNum: Number(ctx.hash)},
Ben Rohlfs2a431342022-09-16 10:47:01 +02001513 };
Ben Rohlfsd49e8332023-04-20 22:40:04 +02001514 const queryMap = new URLSearchParams(ctx.querystring);
1515 if (queryMap.has('forceReload')) state.forceReload = true;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001516 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001517 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001518 this.setState(state);
Ben Rohlfsecc67992022-12-16 10:10:16 +01001519 this.changeViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001520 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001521 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001522 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001523
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001524 handleChangeEditRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001525 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001526 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001527 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001528 const queryMap = new URLSearchParams(ctx.querystring);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001529 const state: ChangeViewState = {
Ben Rohlfsbfc688b2022-10-21 12:38:37 +02001530 repo: project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001531 changeNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001532 patchNum: convertToPatchSetNum(ctx.params[3]) as RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001533 view: GerritView.CHANGE,
Ben Rohlfsecc67992022-12-16 10:10:16 +01001534 childView: ChangeChildView.OVERVIEW,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001535 edit: true,
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001536 };
Ben Rohlfs15da9b72022-10-18 09:41:50 +02001537 const tab = queryMap.get('tab');
1538 if (tab) state.tab = tab;
Ben Rohlfsd49e8332023-04-20 22:40:04 +02001539 if (queryMap.has('forceReload')) state.forceReload = true;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001540 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001541 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001542 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001543 this.changeViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001544 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001545 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001546 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001547
Frank Borden79448112022-04-12 16:59:32 +02001548 handleAgreementsRoute() {
1549 this.redirect('/settings/#Agreements');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001550 }
1551
Ben Rohlfs2586e572022-09-16 12:55:37 +02001552 handleNewAgreementsRoute() {
1553 const state: AgreementViewState = {
1554 view: GerritView.AGREEMENTS,
1555 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001556 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001557 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001558 this.agreementViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001559 }
1560
Ben Rohlfs5940c532022-09-20 09:19:50 +02001561 handleSettingsLegacyRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001562 // email tokens may contain '+' but no space.
1563 // The parameter parsing replaces all '+' with a space,
1564 // undo that to have valid tokens.
Ben Rohlfs5940c532022-09-20 09:19:50 +02001565 const token = ctx.params[0].replace(/ /g, '+');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001566 const state: SettingsViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001567 view: GerritView.SETTINGS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001568 emailToken: token,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001569 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001570 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001571 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001572 this.settingsViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001573 }
1574
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001575 handleSettingsRoute(_: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001576 const state: SettingsViewState = {view: GerritView.SETTINGS};
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001577 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001578 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001579 this.settingsViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001580 }
1581
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001582 handleRegisterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001583 this.setState({justRegistered: true});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001584 let path = ctx.params[0] || '/';
1585
1586 // Prevent redirect looping.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001587 if (path.startsWith('/register')) {
1588 path = '/';
1589 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001590
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001591 if (path[0] !== '/') {
1592 return;
1593 }
Frank Borden79448112022-04-12 16:59:32 +02001594 this.redirect(getBaseUrl() + path);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001595 }
1596
1597 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001598 * URL may sometimes have /+/ encoded to / /.
1599 * Context: Issue 6888, Issue 7100
1600 */
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001601 handleImproperlyEncodedPlusRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001602 let hash = this.getHashFromCanonicalPath(ctx.canonicalPath);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001603 if (hash.length) {
1604 hash = '#' + hash;
1605 }
Frank Borden79448112022-04-12 16:59:32 +02001606 this.redirect(`/c/${ctx.params[0]}/+/${ctx.params[1]}${hash}`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001607 }
1608
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001609 handlePluginScreen(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001610 const state: PluginViewState = {
1611 view: GerritView.PLUGIN_SCREEN,
1612 plugin: ctx.params[0],
1613 screen: ctx.params[1],
1614 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001615 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001616 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001617 this.pluginViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001618 }
1619
Ben Rohlfs5940c532022-09-20 09:19:50 +02001620 handleDocumentationSearchRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001621 const state: DocumentationViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001622 view: GerritView.DOCUMENTATION_SEARCH,
Ben Rohlfs0173d2a2023-01-27 09:10:40 +01001623 filter: ctx.params[0] ?? '',
Ben Rohlfs2586e572022-09-16 12:55:37 +02001624 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001625 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001626 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001627 this.documentationViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001628 }
1629
Ben Rohlfs5940c532022-09-20 09:19:50 +02001630 handleDocumentationSearchRedirectRoute(ctx: PageContext) {
Ben Rohlfsc02facb2023-01-27 18:46:02 +01001631 this.redirect('/Documentation/q/filter:' + encodeURL(ctx.params[0]));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001632 }
1633
Ben Rohlfs5940c532022-09-20 09:19:50 +02001634 handleDocumentationRedirectRoute(ctx: PageContext) {
1635 if (ctx.params[1]) {
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +01001636 windowLocationReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001637 } else {
1638 // Redirect /Documentation to /Documentation/index.html
Frank Borden79448112022-04-12 16:59:32 +02001639 this.redirect('/Documentation/index.html');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001640 }
1641 }
1642
1643 /**
1644 * Catchall route for when no other route is matched.
1645 */
Frank Borden79448112022-04-12 16:59:32 +02001646 handleDefaultRoute() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001647 if (this._isInitialLoad) {
1648 // Server recognized this route as polygerrit, so we show 404.
Frank Borden79448112022-04-12 16:59:32 +02001649 this.show404();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001650 } else {
1651 // Route can be recognized by server, so we pass it to server.
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +01001652 this.windowReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001653 }
1654 }
1655
Ben Rohlfs90b2b7d2024-03-13 08:42:32 +01001656 // Allows stubbing in tests.
1657 windowReload() {
1658 windowLocationReload();
1659 }
1660
Frank Borden79448112022-04-12 16:59:32 +02001661 private show404() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001662 // Note: the app's 404 display is tightly-coupled with catching 404
1663 // network responses, so we simulate a 404 response status to display it.
1664 // TODO: Decouple the gr-app error view from network responses.
Ben Rohlfsa76c82f2021-01-22 22:22:32 +01001665 firePageError(new Response('', {status: 404}));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001666 }
1667}