blob: ec7ef5224800d488f66888f5ab0fac7f21a36bc6 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2016 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04005 */
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02006import {
7 page,
8 PageContext,
9 PageNextCallback,
10} from '../../../utils/page-wrapper-utils';
Ben Rohlfs54934de2022-09-22 12:44:33 +020011import {NavigationService} from '../gr-navigation/gr-navigation';
Chris Poucetc6e880b2021-11-15 19:57:06 +010012import {getAppContext} from '../../../services/app-context';
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +020013import {convertToPatchSetNum} from '../../../utils/patch-set-util';
Ben Rohlfsc2a8f292022-09-19 14:54:42 +020014import {assertIsDefined} from '../../../utils/common-util';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020015import {
Dhruv Srivastava591b4902021-03-10 11:48:12 +010016 BasePatchSetNum,
Dmitrii Filippov12f22a92020-10-12 16:16:30 +020017 DashboardId,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020018 GroupId,
19 NumericChangeId,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +020020 RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020021 RepoName,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +020022 UrlEncodedCommentId,
Ben Rohlfs58267b72022-05-27 15:59:18 +020023 PARENT,
Ben Rohlfsa1d2c0c2022-09-29 17:03:26 +020024 PatchSetNumber,
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 Rohlfsa76c82f2021-01-22 22:22:32 +010029import {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 {
Milutin Kristofice366b932021-03-10 17:09:52 +010032 getBaseUrl,
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +020033 PatchRangeParams,
Milutin Kristofice366b932021-03-10 17:09:52 +010034 toPath,
35 toPathname,
36 toSearchParams,
37} from '../../../utils/url-util';
Ben Rohlfscf5109c2022-09-20 08:44:28 +020038import {LifeCycle, Timing} from '../../../constants/reporting';
Ben Rohlfsebef2e12022-09-01 17:22:24 +020039import {
40 LATEST_ATTEMPT,
41 stringToAttemptChoice,
42} from '../../../models/checks/checks-util';
Ben Rohlfs2586e572022-09-16 12:55:37 +020043import {
44 AdminChildView,
45 AdminViewModel,
46 AdminViewState,
47} from '../../../models/views/admin';
48import {
49 AgreementViewModel,
50 AgreementViewState,
51} from '../../../models/views/agreement';
52import {
53 RepoDetailView,
54 RepoViewModel,
55 RepoViewState,
56} from '../../../models/views/repo';
57import {
58 GroupDetailView,
59 GroupViewModel,
60 GroupViewState,
61} from '../../../models/views/group';
62import {DiffViewModel, DiffViewState} from '../../../models/views/diff';
Ben Rohlfs7b22a292022-09-29 12:52:01 +020063import {
64 ChangeViewModel,
65 ChangeViewState,
66 createChangeUrl,
67} from '../../../models/views/change';
Ben Rohlfs2586e572022-09-16 12:55:37 +020068import {EditViewModel, EditViewState} from '../../../models/views/edit';
69import {
70 DashboardViewModel,
71 DashboardViewState,
72} from '../../../models/views/dashboard';
73import {
74 SettingsViewModel,
75 SettingsViewState,
76} from '../../../models/views/settings';
77import {define} from '../../../models/dependency';
78import {Finalizable} from '../../../services/registry';
79import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
80import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
81import {
82 DocumentationViewModel,
83 DocumentationViewState,
84} from '../../../models/views/documentation';
85import {PluginViewModel, PluginViewState} from '../../../models/views/plugin';
86import {SearchViewModel, SearchViewState} from '../../../models/views/search';
Ben Rohlfs82b46492022-09-20 09:13:56 +020087import {DashboardSection} from '../../../utils/dashboard-util';
Ben Rohlfs7b22a292022-09-29 12:52:01 +020088import {Subscription} from 'rxjs';
Wyatt Allenee2016c2017-10-31 13:41:52 -070089
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010090const RoutePattern = {
91 ROOT: '/',
Wyatt Allenee2016c2017-10-31 13:41:52 -070092
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010093 DASHBOARD: /^\/dashboard\/(.+)$/,
94 CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
95 PROJECT_DASHBOARD: /^\/p\/(.+)\/\+\/dashboard\/(.+)/,
Jacek Centkowskid322bd42020-09-16 10:34:55 +020096 LEGACY_PROJECT_DASHBOARD: /^\/projects\/(.+),dashboards\/(.+)/,
Wyatt Allen089dfb32017-08-24 17:55:38 -070097
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010098 AGREEMENTS: /^\/settings\/agreements\/?/,
99 NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
100 REGISTER: /^\/register(\/.*)?$/,
Wyatt Allen6376e7a2017-08-31 10:11:59 -0700101
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100102 // Pattern for login and logout URLs intended to be passed-through. May
103 // include a return URL.
104 LOG_IN_OR_OUT: /\/log(in|out)(\/(.+))?$/,
Wyatt Allenf1971382017-08-29 13:22:15 -0700105
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100106 // Pattern for a catchall route when no other pattern is matched.
107 DEFAULT: /.*/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700108
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100109 // Matches /admin/groups/[uuid-]<group>
110 GROUP: /^\/admin\/groups\/(?:uuid-)?([^,]+)$/,
Paladox nonef7303f72019-06-27 14:57:11 +0000111
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100112 // Redirects /groups/self to /settings/#Groups for GWT compatibility
113 GROUP_SELF: /^\/groups\/self/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700114
Frank Borden5ea7d652022-05-10 16:52:11 +0200115 // Matches /admin/groups/[uuid-]<group>,info (backwards compat with gwtui)
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100116 // Redirects to /admin/groups/[uuid-]<group>
117 GROUP_INFO: /^\/admin\/groups\/(?:uuid-)?(.+),info$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700118
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100119 // Matches /admin/groups/<group>,audit-log
120 GROUP_AUDIT_LOG: /^\/admin\/groups\/(?:uuid-)?(.+),audit-log$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700121
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100122 // Matches /admin/groups/[uuid-]<group>,members
123 GROUP_MEMBERS: /^\/admin\/groups\/(?:uuid-)?(.+),members$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700124
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100125 // Matches /admin/groups[,<offset>][/].
126 GROUP_LIST_OFFSET: /^\/admin\/groups(,(\d+))?(\/)?$/,
127 GROUP_LIST_FILTER: '/admin/groups/q/filter::filter',
128 GROUP_LIST_FILTER_OFFSET: '/admin/groups/q/filter::filter,:offset',
Becky Siegel28d1bf62017-09-01 12:07:09 -0700129
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100130 // Matches /admin/create-project
131 LEGACY_CREATE_PROJECT: /^\/admin\/create-project\/?$/,
Becky Siegel28d1bf62017-09-01 12:07:09 -0700132
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100133 // Matches /admin/create-project
134 LEGACY_CREATE_GROUP: /^\/admin\/create-group\/?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700135
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100136 PROJECT_OLD: /^\/admin\/(projects)\/?(.+)?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700137
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100138 // Matches /admin/repos/<repo>
139 REPO: /^\/admin\/repos\/([^,]+)$/,
Becky Siegel6db432f2017-08-25 09:17:42 -0700140
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100141 // Matches /admin/repos/<repo>,commands.
142 REPO_COMMANDS: /^\/admin\/repos\/(.+),commands$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700143
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +0200144 REPO_GENERAL: /^\/admin\/repos\/(.+),general$/,
145
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100146 // Matches /admin/repos/<repos>,access.
147 REPO_ACCESS: /^\/admin\/repos\/(.+),access$/,
Becky Siegelc588ab72018-01-16 17:43:10 -0800148
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100149 // Matches /admin/repos/<repos>,access.
150 REPO_DASHBOARDS: /^\/admin\/repos\/(.+),dashboards$/,
Paladox none2bd5c212017-11-16 18:54:02 +0000151
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100152 // Matches /admin/repos[,<offset>][/].
153 REPO_LIST_OFFSET: /^\/admin\/repos(,(\d+))?(\/)?$/,
154 REPO_LIST_FILTER: '/admin/repos/q/filter::filter',
155 REPO_LIST_FILTER_OFFSET: '/admin/repos/q/filter::filter,:offset',
Wyatt Allen089dfb32017-08-24 17:55:38 -0700156
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100157 // Matches /admin/repos/<repo>,branches[,<offset>].
158 BRANCH_LIST_OFFSET: /^\/admin\/repos\/(.+),branches(,(.+))?$/,
159 BRANCH_LIST_FILTER: '/admin/repos/:repo,branches/q/filter::filter',
160 BRANCH_LIST_FILTER_OFFSET:
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200161 '/admin/repos/:repo,branches/q/filter::filter,:offset',
Wyatt Allen089dfb32017-08-24 17:55:38 -0700162
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100163 // Matches /admin/repos/<repo>,tags[,<offset>].
164 TAG_LIST_OFFSET: /^\/admin\/repos\/(.+),tags(,(.+))?$/,
165 TAG_LIST_FILTER: '/admin/repos/:repo,tags/q/filter::filter',
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200166 TAG_LIST_FILTER_OFFSET: '/admin/repos/:repo,tags/q/filter::filter,:offset',
Paladox none3bfada82017-09-01 09:29:21 +0000167
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100168 PLUGINS: /^\/plugins\/(.+)$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700169
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100170 PLUGIN_LIST: /^\/admin\/plugins(\/)?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700171
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100172 // Matches /admin/plugins[,<offset>][/].
173 PLUGIN_LIST_OFFSET: /^\/admin\/plugins(,(\d+))?(\/)?$/,
174 PLUGIN_LIST_FILTER: '/admin/plugins/q/filter::filter',
175 PLUGIN_LIST_FILTER_OFFSET: '/admin/plugins/q/filter::filter,:offset',
Wyatt Allen089dfb32017-08-24 17:55:38 -0700176
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100177 QUERY: /^\/q\/([^,]+)(,(\d+))?$/,
Wyatt Allen089dfb32017-08-24 17:55:38 -0700178
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700179 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100180 * Support vestigial params from GWT UI.
Tao Zhou9a076812019-12-17 09:59:28 +0100181 *
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100182 * @see Issue 7673.
183 * @type {!RegExp}
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700184 */
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100185 QUERY_LEGACY_SUFFIX: /^\/q\/.+,n,z$/,
Wyatt Allenfd6a9472017-08-28 16:42:40 -0700186
Peter Collingbourne8cab9332020-08-18 14:42:44 -0700187 CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
188
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +0100189 // Matches /c/<changeNum>/[*][/].
Ben Rohlfsd9032422021-10-06 19:05:12 +0200190 CHANGE_LEGACY: /^\/c\/(\d+)\/?(.*)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100191 CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
Logan Hanksaa501d02017-09-30 04:07:34 -0700192
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100193 // Matches
194 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..][<patchNum|edit>].
195 // TODO(kaspern): Migrate completely to project based URLs, with backwards
196 // compatibility for change-only.
197 CHANGE: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
Logan Hanksaa501d02017-09-30 04:07:34 -0700198
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100199 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>],edit
200 CHANGE_EDIT: /^\/c\/(.+)\/\+\/(\d+)(\/(\d+))?,edit\/?$/,
Wyatt Allen84f0a572017-11-06 15:45:58 -0800201
Dhruv Srivastava90240452020-07-02 15:51:53 +0200202 // Matches /c/<project>/+/<changeNum>/comment/<commentId>/
203 // Navigates to the diff view
204 // This route is needed to resolve to patchNum vs latestPatchNum used in the
205 // links generated in the emails.
206 COMMENT: /^\/c\/(.+)\/\+\/(\d+)\/comment\/(\w+)\/?$/,
207
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +0200208 // Matches /c/<project>/+/<changeNum>/comments/<commentId>/
209 // Navigates to the commentId inside the Comments Tab
210 COMMENTS_TAB: /^\/c\/(.+)\/\+\/(\d+)\/comments(?:\/)?(\w+)?\/?$/,
211
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100212 // Matches
213 // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..]<patchNum|edit>/<path>.
214 // TODO(kaspern): Migrate completely to project based URLs, with backwards
215 // compatibility for change-only.
216 // eslint-disable-next-line max-len
217 DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))))\/?$/,
Wyatt Allen52d76132017-11-06 16:58:52 -0800218
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100219 // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>]/<path>,edit[#lineNum]
David Ostrovskycfe65302020-05-07 22:10:38 +0200220 DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
Wyatt Allen02add882018-08-13 19:06:52 -0700221
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100222 // Matches diff routes using @\d+ to specify a file name (whether or not
223 // the project name is included).
224 // eslint-disable-next-line max-len
Chris Poucetcaeea1b2021-08-19 22:12:56 +0000225 DIFF_LEGACY_LINENUM:
226 /^\/c\/((.+)\/\+\/)?(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?\/(.+))?)@[ab]?\d+$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100227
228 SETTINGS: /^\/settings\/?/,
229 SETTINGS_LEGACY: /^\/settings\/VE\/(\S+)/,
230
231 // Matches /c/<changeNum>/ /<URL tail>
232 // Catches improperly encoded URLs (context: Issue 7100)
David Ostrovskycfe65302020-05-07 22:10:38 +0200233 IMPROPERLY_ENCODED_PLUS: /^\/c\/(.+)\/ \/(.+)$/,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100234
235 PLUGIN_SCREEN: /^\/x\/([\w-]+)\/([\w-]+)\/?/,
236
237 DOCUMENTATION_SEARCH_FILTER: '/Documentation/q/filter::filter',
238 DOCUMENTATION_SEARCH: /^\/Documentation\/q\/(.*)$/,
239 DOCUMENTATION: /^\/Documentation(\/)?(.+)?/,
240};
241
Dhruv Srivastava90240452020-07-02 15:51:53 +0200242export const _testOnly_RoutePattern = RoutePattern;
243
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100244/**
245 * Pattern to recognize and parse the diff line locations as they appear in
246 * the hash of diff URLs. In this format, a number on its own indicates that
247 * line number in the revision of the diff. A number prefixed by either an 'a'
248 * or a 'b' indicates that line number of the base of the diff.
249 *
250 * @type {RegExp}
251 */
252const LINE_ADDRESS_PATTERN = /^([ab]?)(\d+)$/;
253
254/**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100255 * GWT UI would use @\d+ at the end of a path to indicate linenum.
256 */
257const LEGACY_LINENUM_PATTERN = /@([ab]?\d+)$/;
258
259const LEGACY_QUERY_SUFFIX_PATTERN = /,n,z$/;
260
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100261// Polymer makes `app` intrinsically defined on the window by virtue of the
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100262// custom element having the id "pg-app", but it is made explicit here.
Dmitrii Filippov6dbb1712020-03-19 12:11:50 +0100263// If you move this code to other place, please update comment about
264// gr-router and gr-app in the PolyGerritIndexHtml.soy file if needed
Chris Poucet36879972022-01-31 14:20:12 +0100265const app = document.querySelector('gr-app');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100266if (!app) {
Tao Zhoucd01d8f2020-07-22 12:35:31 +0200267 console.info('No gr-app found (running tests)');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100268}
269
270// Setup listeners outside of the router component initialization.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200271(function () {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100272 window.addEventListener('WebComponentsReady', () => {
Chris Poucetc6e880b2021-11-15 19:57:06 +0100273 getAppContext().reportingService.timeEnd(Timing.WEB_COMPONENTS_READY);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100274 });
275})();
276
Ben Rohlfs2586e572022-09-16 12:55:37 +0200277export const routerToken = define<GrRouter>('router');
278
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200279export class GrRouter implements Finalizable, NavigationService {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200280 readonly _app = app;
281
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200282 _isRedirecting?: boolean;
283
284 // This variable is to differentiate between internal navigation (false)
285 // and for first navigation in app after loaded from server (true).
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200286 _isInitialLoad = true;
287
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200288 private subscriptions: Subscription[] = [];
289
290 private view?: GerritView;
291
Ben Rohlfs2586e572022-09-16 12:55:37 +0200292 constructor(
293 private readonly reporting: ReportingService,
294 private readonly routerModel: RouterModel,
295 private readonly restApiService: RestApiService,
296 private readonly adminViewModel: AdminViewModel,
297 private readonly agreementViewModel: AgreementViewModel,
298 private readonly changeViewModel: ChangeViewModel,
299 private readonly dashboardViewModel: DashboardViewModel,
300 private readonly diffViewModel: DiffViewModel,
301 private readonly documentationViewModel: DocumentationViewModel,
302 private readonly editViewModel: EditViewModel,
303 private readonly groupViewModel: GroupViewModel,
304 private readonly pluginViewModel: PluginViewModel,
305 private readonly repoViewModel: RepoViewModel,
306 private readonly searchViewModel: SearchViewModel,
307 private readonly settingsViewModel: SettingsViewModel
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200308 ) {
309 this.subscriptions = [
310 // TODO: Do the same for other view models.
311 // We want to make sure that the current view model state is always
312 // reflected back into the URL bar.
313 this.changeViewModel.state$.subscribe(state => {
314 if (!state) return;
315 // Note that router model view must be updated before view model state.
316 // So this check is slightly fragile, but should work.
317 if (this.view !== GerritView.CHANGE) return;
Chris Poucet8f898592022-10-05 10:58:44 +0200318 const browserUrl = new URL(window.location.toString());
319 const stateUrl = new URL(createChangeUrl(state), browserUrl);
Ben Rohlfsae28e7c2022-10-17 12:33:36 +0200320
321 // Keeping the hash and certain parameters are stop-gap solution. We
322 // should find better ways of maintaining an overall consistent URL
323 // state.
Chris Poucet8f898592022-10-05 10:58:44 +0200324 stateUrl.hash = browserUrl.hash;
Ben Rohlfsae28e7c2022-10-17 12:33:36 +0200325 for (const p of browserUrl.searchParams.entries()) {
326 if (p[0] === 'experiment') stateUrl.searchParams.append(p[0], p[1]);
327 }
328
Chris Poucet8f898592022-10-05 10:58:44 +0200329 if (browserUrl.toString() !== stateUrl.toString()) {
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200330 page.replace(
Chris Poucet8f898592022-10-05 10:58:44 +0200331 stateUrl.toString(),
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200332 null,
333 /* init: */ false,
334 /* dispatch: */ false
335 );
336 }
337 }),
338 this.routerModel.routerView$.subscribe(view => (this.view = view)),
339 ];
340 }
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200341
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200342 finalize(): void {
343 for (const subscription of this.subscriptions) {
344 subscription.unsubscribe();
345 }
346 }
Ben Rohlfs43935a42020-12-01 19:14:09 +0100347
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100348 start() {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200349 if (!this._app) {
350 return;
351 }
Frank Borden79448112022-04-12 16:59:32 +0200352 this.startRouter();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100353 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800354
Ben Rohlfs2586e572022-09-16 12:55:37 +0200355 setState(state: AppElementParams) {
Ben Rohlfs5fa82932022-09-21 09:29:31 +0200356 this.routerModel.setState({
Ben Rohlfs2586e572022-09-16 12:55:37 +0200357 view: state.view,
358 changeNum: 'changeNum' in state ? state.changeNum : undefined,
359 patchNum: 'patchNum' in state ? state.patchNum ?? undefined : undefined,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200360 basePatchNum:
Ben Rohlfs2586e572022-09-16 12:55:37 +0200361 'basePatchNum' in state ? state.basePatchNum ?? undefined : undefined,
Chris Poucet455f8fc2021-11-23 02:00:39 +0100362 });
Ben Rohlfs2586e572022-09-16 12:55:37 +0200363 this.appElement().params = state;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100364 }
365
Frank Borden79448112022-04-12 16:59:32 +0200366 private appElement(): AppElement {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100367 // In Polymer2 you have to reach through the shadow root of the app
368 // element. This obviously breaks encapsulation.
369 // TODO(brohlfs): Make this more elegant, e.g. by exposing app-element
370 // explicitly in app, or by delegating to it.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200371
372 // It is expected that application has a GrAppElement(id=='app-element')
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100373 // at the document level or inside the shadow root of the GrApp ('gr-app')
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200374 // element.
375 return (document.getElementById('app-element') ||
376 document
David Ostrovsky8584b3d2022-02-12 09:19:02 +0100377 .querySelector('gr-app')!
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200378 .shadowRoot!.getElementById('app-element')!) as AppElement;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100379 }
380
Frank Borden79448112022-04-12 16:59:32 +0200381 redirect(url: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100382 this._isRedirecting = true;
383 page.redirect(url);
384 }
Wyatt Allen3a69d822017-02-14 15:50:01 -0800385
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100386 /**
Ben Rohlfs2a431342022-09-16 10:47:01 +0200387 * Normalizes the patchset numbers of the params object.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100388 */
Frank Borden79448112022-04-12 16:59:32 +0200389 normalizePatchRangeParams(params: PatchRangeParams) {
Ben Rohlfs2a431342022-09-16 10:47:01 +0200390 if (params.basePatchNum === undefined) return;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100391
392 // Diffing a patch against itself is invalid, so if the base and revision
393 // patches are equal clear the base.
Dhruv Srivastavad1da4582021-01-11 16:34:19 +0100394 if (params.patchNum && params.basePatchNum === params.patchNum) {
Ben Rohlfs58267b72022-05-27 15:59:18 +0200395 params.basePatchNum = PARENT;
Ben Rohlfs2a431342022-09-16 10:47:01 +0200396 return;
397 }
398 // Regexes set basePatchNum instead of patchNum when only one is
399 // specified.
400 if (params.patchNum === undefined) {
Ben Rohlfsabaeacf2022-05-27 15:24:22 +0200401 params.patchNum = params.basePatchNum as RevisionPatchSetNum;
Ben Rohlfs58267b72022-05-27 15:59:18 +0200402 params.basePatchNum = PARENT;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100403 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100404 }
405
406 /**
407 * Redirect the user to login using the given return-URL for redirection
408 * after authentication success.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100409 */
Frank Borden79448112022-04-12 16:59:32 +0200410 redirectToLogin(returnUrl: string) {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200411 const basePath = getBaseUrl() || '';
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200412 page('/login/' + encodeURIComponent(returnUrl.substring(basePath.length)));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100413 }
414
415 /**
416 * Hashes parsed by page.js exclude "inner" hashes, so a URL like "/a#b#c"
417 * is parsed to have a hash of "b" rather than "b#c". Instead, this method
418 * parses hashes correctly. Will return an empty string if there is no hash.
419 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200420 * @return Everything after the first '#' ("a#b#c" -> "b#c").
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100421 */
Frank Borden79448112022-04-12 16:59:32 +0200422 getHashFromCanonicalPath(canonicalPath: string) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200423 return canonicalPath.split('#').slice(1).join('#');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100424 }
425
Frank Borden79448112022-04-12 16:59:32 +0200426 parseLineAddress(hash: string) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100427 const match = hash.match(LINE_ADDRESS_PATTERN);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200428 if (!match) {
429 return null;
430 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100431 return {
432 leftSide: !!match[1],
Dhruv Srivastavab8edee92020-10-19 10:20:07 +0200433 lineNum: Number(match[2]),
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100434 };
435 }
436
437 /**
438 * Check to see if the user is logged in and return a promise that only
439 * resolves if the user is logged in. If the user us not logged in, the
440 * promise is rejected and the page is redirected to the login flow.
441 *
Ben Rohlfs5940c532022-09-20 09:19:50 +0200442 * @return A promise yielding the original route ctx
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200443 * (if it resolves).
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100444 */
Ben Rohlfs5940c532022-09-20 09:19:50 +0200445 redirectIfNotLoggedIn(ctx: PageContext) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100446 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100447 if (loggedIn) {
448 return Promise.resolve();
Wyatt Allen797480f2017-06-08 09:20:46 -0700449 } else {
Ben Rohlfs5940c532022-09-20 09:19:50 +0200450 this.redirectToLogin(ctx.canonicalPath);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100451 return Promise.reject(new Error());
Wyatt Allen797480f2017-06-08 09:20:46 -0700452 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100453 });
454 }
Wyatt Allen797480f2017-06-08 09:20:46 -0700455
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100456 /** Page.js middleware that warms the REST API's logged-in cache line. */
Frank Borden79448112022-04-12 16:59:32 +0200457 private loadUserMiddleware(_: PageContext, next: PageNextCallback) {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100458 this.restApiService.getLoggedIn().then(() => {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200459 next();
460 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100461 }
462
463 /**
464 * Map a route to a method on the router.
465 *
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200466 * @param pattern The page.js pattern for the route.
467 * @param handlerName The method name for the handler. If the
468 * route is matched, the handler will be executed with `this` referring
469 * to the component. Its return value will be discarded so that it does
470 * not interfere with page.js.
471 * @param authRedirect If true, then auth is checked before
472 * executing the handler. If the user is not logged in, it will redirect
473 * to the login flow and the handler will not be executed. The login
Frank Borden5ea7d652022-05-10 16:52:11 +0200474 * redirect specifies the matched URL to be used after successful auth.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100475 */
Frank Borden79448112022-04-12 16:59:32 +0200476 mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200477 pattern: string | RegExp,
Frank Borden79448112022-04-12 16:59:32 +0200478 handlerName: string,
Ben Rohlfscf5109c2022-09-20 08:44:28 +0200479 handler: (ctx: PageContext) => void,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200480 authRedirect?: boolean
481 ) {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200482 page(
483 pattern,
Frank Borden79448112022-04-12 16:59:32 +0200484 (ctx, next) => this.loadUserMiddleware(ctx, next),
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100485 ctx => {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200486 this.reporting.locationChanged(handlerName);
487 const promise = authRedirect
Frank Borden79448112022-04-12 16:59:32 +0200488 ? this.redirectIfNotLoggedIn(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200489 : Promise.resolve();
490 promise.then(() => {
Ben Rohlfscf5109c2022-09-20 08:44:28 +0200491 handler(ctx);
Tao Zhou4fd32d52020-04-06 17:23:10 +0200492 });
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200493 }
494 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100495 }
496
Ben Rohlfs77c489a2022-09-21 14:25:56 +0200497 /**
498 * This is similar to letting the browser navigate to this URL when the user
499 * clicks it, or to just setting `window.location.href` directly.
500 *
501 * This adds a new entry to the browser location history. Consier using
502 * `replaceUrl()`, if you want to avoid that.
503 *
504 * page.show() eventually just calls `window.history.pushState()`.
505 */
506 setUrl(url: string) {
507 page.show(url);
508 }
509
510 /**
511 * Navigate to this URL, but replace the current URL in the history instead of
512 * adding a new one (which is what `setUrl()` would do).
513 *
514 * page.redirect() eventually just calls `window.history.replaceState()`.
515 */
516 replaceUrl(url: string) {
517 this.redirect(url);
518 }
519
Frank Borden79448112022-04-12 16:59:32 +0200520 startRouter() {
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200521 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100522 if (base) {
523 page.base(base);
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100524 }
Wyatt Allen4fd17cc2017-06-23 09:32:47 -0700525
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200526 page.exit('*', (_, next) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100527 if (!this._isRedirecting) {
Milutin Kristoficda88b332020-03-24 10:19:12 +0100528 this.reporting.beforeLocationChanged();
Viktar Donicha28dee062017-11-10 16:03:13 -0800529 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100530 this._isRedirecting = false;
531 this._isInitialLoad = false;
532 next();
533 });
Viktar Donicha28dee062017-11-10 16:03:13 -0800534
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100535 // Remove the tracking param 'usp' (User Source Parameter) from the URL,
536 // just to have users look at cleaner URLs.
537 page((ctx, next) => {
538 if (window.URLSearchParams) {
539 const pathname = toPathname(ctx.canonicalPath);
540 const searchParams = toSearchParams(ctx.canonicalPath);
541 if (searchParams.has('usp')) {
Ben Rohlfs6fb09dd2021-03-12 09:24:26 +0100542 const usp = searchParams.get('usp');
543 this.reporting.reportLifeCycle(LifeCycle.USER_REFERRED_FROM, {usp});
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100544 searchParams.delete('usp');
Frank Borden79448112022-04-12 16:59:32 +0200545 this.redirect(toPath(pathname, searchParams));
Ben Rohlfs7b16e9c2021-03-04 16:45:25 +0100546 return;
547 }
548 }
549 next();
550 });
551
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100552 // Middleware
553 page((ctx, next) => {
554 document.body.scrollTop = 0;
Viktar Donicha28dee062017-11-10 16:03:13 -0800555
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100556 if (ctx.hash.match(RoutePattern.PLUGIN_SCREEN)) {
557 // Redirect all urls using hash #/x/plugin/screen to /x/plugin/screen
558 // This is needed to allow plugins to add basic #/x/ screen links to
559 // any location.
Frank Borden79448112022-04-12 16:59:32 +0200560 this.redirect(ctx.hash);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700561 return;
562 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700563
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100564 // Fire asynchronously so that the URL is changed by the time the event
565 // is processed.
Ben Rohlfs6b078932021-03-10 15:20:03 +0100566 setTimeout(() => {
Dmitrii Filippov6a038002020-10-14 18:50:07 +0200567 const detail: LocationChangeEventDetail = {
568 hash: window.location.hash,
569 pathname: window.location.pathname,
570 };
Dhruv Srivastava75656e72022-08-24 13:58:31 +0200571 document.dispatchEvent(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200572 new CustomEvent('location-change', {
Dmitrii Filippov6a038002020-10-14 18:50:07 +0200573 detail,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200574 composed: true,
575 bubbles: true,
576 })
577 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100578 }, 1);
579 next();
580 });
581
Frank Borden5ea7d652022-05-10 16:52:11 +0200582 this.mapRoute(RoutePattern.ROOT, 'handleRootRoute', ctx =>
583 this.handleRootRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200584 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100585
Frank Borden5ea7d652022-05-10 16:52:11 +0200586 this.mapRoute(RoutePattern.DASHBOARD, 'handleDashboardRoute', ctx =>
587 this.handleDashboardRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200588 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100589
Frank Borden79448112022-04-12 16:59:32 +0200590 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200591 RoutePattern.CUSTOM_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200592 'handleCustomDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200593 ctx => this.handleCustomDashboardRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200594 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100595
Frank Borden79448112022-04-12 16:59:32 +0200596 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200597 RoutePattern.PROJECT_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200598 'handleProjectDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200599 ctx => this.handleProjectDashboardRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200600 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100601
Frank Borden79448112022-04-12 16:59:32 +0200602 this.mapRoute(
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200603 RoutePattern.LEGACY_PROJECT_DASHBOARD,
Frank Borden79448112022-04-12 16:59:32 +0200604 'handleLegacyProjectDashboardRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200605 ctx => this.handleLegacyProjectDashboardRoute(ctx)
Jacek Centkowskid322bd42020-09-16 10:34:55 +0200606 );
607
Frank Borden79448112022-04-12 16:59:32 +0200608 this.mapRoute(
609 RoutePattern.GROUP_INFO,
610 'handleGroupInfoRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200611 ctx => this.handleGroupInfoRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200612 true
613 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100614
Frank Borden79448112022-04-12 16:59:32 +0200615 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200616 RoutePattern.GROUP_AUDIT_LOG,
Frank Borden79448112022-04-12 16:59:32 +0200617 'handleGroupAuditLogRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200618 ctx => this.handleGroupAuditLogRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200619 true
620 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100621
Frank Borden79448112022-04-12 16:59:32 +0200622 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200623 RoutePattern.GROUP_MEMBERS,
Frank Borden79448112022-04-12 16:59:32 +0200624 'handleGroupMembersRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200625 ctx => this.handleGroupMembersRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200626 true
627 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100628
Frank Borden79448112022-04-12 16:59:32 +0200629 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200630 RoutePattern.GROUP_LIST_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200631 'handleGroupListOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200632 ctx => this.handleGroupListOffsetRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200633 true
634 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100635
Frank Borden79448112022-04-12 16:59:32 +0200636 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200637 RoutePattern.GROUP_LIST_FILTER_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200638 'handleGroupListFilterOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200639 ctx => this.handleGroupListFilterOffsetRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200640 true
641 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100642
Frank Borden79448112022-04-12 16:59:32 +0200643 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200644 RoutePattern.GROUP_LIST_FILTER,
Frank Borden79448112022-04-12 16:59:32 +0200645 'handleGroupListFilterRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200646 ctx => this.handleGroupListFilterRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200647 true
648 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100649
Frank Borden79448112022-04-12 16:59:32 +0200650 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200651 RoutePattern.GROUP_SELF,
Frank Borden79448112022-04-12 16:59:32 +0200652 'handleGroupSelfRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200653 ctx => this.handleGroupSelfRedirectRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200654 true
655 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100656
Frank Borden79448112022-04-12 16:59:32 +0200657 this.mapRoute(
658 RoutePattern.GROUP,
659 'handleGroupRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200660 ctx => this.handleGroupRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200661 true
662 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100663
Frank Borden5ea7d652022-05-10 16:52:11 +0200664 this.mapRoute(RoutePattern.PROJECT_OLD, 'handleProjectsOldRoute', ctx =>
665 this.handleProjectsOldRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200666 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100667
Frank Borden79448112022-04-12 16:59:32 +0200668 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200669 RoutePattern.REPO_COMMANDS,
Frank Borden79448112022-04-12 16:59:32 +0200670 'handleRepoCommandsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200671 ctx => this.handleRepoCommandsRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200672 true
673 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100674
Frank Borden5ea7d652022-05-10 16:52:11 +0200675 this.mapRoute(RoutePattern.REPO_GENERAL, 'handleRepoGeneralRoute', ctx =>
676 this.handleRepoGeneralRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200677 );
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +0200678
Frank Borden5ea7d652022-05-10 16:52:11 +0200679 this.mapRoute(RoutePattern.REPO_ACCESS, 'handleRepoAccessRoute', ctx =>
680 this.handleRepoAccessRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200681 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100682
Frank Borden79448112022-04-12 16:59:32 +0200683 this.mapRoute(
684 RoutePattern.REPO_DASHBOARDS,
685 'handleRepoDashboardsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200686 ctx => this.handleRepoDashboardsRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200687 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100688
Frank Borden79448112022-04-12 16:59:32 +0200689 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200690 RoutePattern.BRANCH_LIST_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200691 'handleBranchListOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200692 ctx => this.handleBranchListOffsetRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200693 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100694
Frank Borden79448112022-04-12 16:59:32 +0200695 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200696 RoutePattern.BRANCH_LIST_FILTER_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200697 'handleBranchListFilterOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200698 ctx => this.handleBranchListFilterOffsetRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200699 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100700
Frank Borden79448112022-04-12 16:59:32 +0200701 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200702 RoutePattern.BRANCH_LIST_FILTER,
Frank Borden79448112022-04-12 16:59:32 +0200703 'handleBranchListFilterRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200704 ctx => this.handleBranchListFilterRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200705 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100706
Frank Borden79448112022-04-12 16:59:32 +0200707 this.mapRoute(
708 RoutePattern.TAG_LIST_OFFSET,
709 'handleTagListOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200710 ctx => this.handleTagListOffsetRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200711 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100712
Frank Borden79448112022-04-12 16:59:32 +0200713 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200714 RoutePattern.TAG_LIST_FILTER_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200715 'handleTagListFilterOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200716 ctx => this.handleTagListFilterOffsetRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200717 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100718
Frank Borden79448112022-04-12 16:59:32 +0200719 this.mapRoute(
720 RoutePattern.TAG_LIST_FILTER,
721 'handleTagListFilterRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200722 ctx => this.handleTagListFilterRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200723 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100724
Frank Borden79448112022-04-12 16:59:32 +0200725 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200726 RoutePattern.LEGACY_CREATE_GROUP,
Frank Borden79448112022-04-12 16:59:32 +0200727 'handleCreateGroupRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200728 ctx => this.handleCreateGroupRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200729 true
730 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100731
Frank Borden79448112022-04-12 16:59:32 +0200732 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200733 RoutePattern.LEGACY_CREATE_PROJECT,
Frank Borden79448112022-04-12 16:59:32 +0200734 'handleCreateProjectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200735 ctx => this.handleCreateProjectRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200736 true
737 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100738
Frank Borden79448112022-04-12 16:59:32 +0200739 this.mapRoute(
740 RoutePattern.REPO_LIST_OFFSET,
741 'handleRepoListOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200742 ctx => this.handleRepoListOffsetRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200743 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100744
Frank Borden79448112022-04-12 16:59:32 +0200745 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200746 RoutePattern.REPO_LIST_FILTER_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200747 'handleRepoListFilterOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200748 ctx => this.handleRepoListFilterOffsetRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200749 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100750
Frank Borden79448112022-04-12 16:59:32 +0200751 this.mapRoute(
752 RoutePattern.REPO_LIST_FILTER,
753 'handleRepoListFilterRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200754 ctx => this.handleRepoListFilterRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200755 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100756
Frank Borden5ea7d652022-05-10 16:52:11 +0200757 this.mapRoute(RoutePattern.REPO, 'handleRepoRoute', ctx =>
758 this.handleRepoRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200759 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100760
Frank Borden5ea7d652022-05-10 16:52:11 +0200761 this.mapRoute(RoutePattern.PLUGINS, 'handlePassThroughRoute', () =>
762 this.handlePassThroughRoute()
Frank Borden79448112022-04-12 16:59:32 +0200763 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100764
Frank Borden79448112022-04-12 16:59:32 +0200765 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200766 RoutePattern.PLUGIN_LIST_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200767 'handlePluginListOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200768 ctx => this.handlePluginListOffsetRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200769 true
770 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100771
Frank Borden79448112022-04-12 16:59:32 +0200772 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200773 RoutePattern.PLUGIN_LIST_FILTER_OFFSET,
Frank Borden79448112022-04-12 16:59:32 +0200774 'handlePluginListFilterOffsetRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200775 ctx => this.handlePluginListFilterOffsetRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200776 true
777 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100778
Frank Borden79448112022-04-12 16:59:32 +0200779 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200780 RoutePattern.PLUGIN_LIST_FILTER,
Frank Borden79448112022-04-12 16:59:32 +0200781 'handlePluginListFilterRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200782 ctx => this.handlePluginListFilterRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200783 true
784 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100785
Frank Borden79448112022-04-12 16:59:32 +0200786 this.mapRoute(
787 RoutePattern.PLUGIN_LIST,
788 'handlePluginListRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200789 ctx => this.handlePluginListRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200790 true
791 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100792
Frank Borden79448112022-04-12 16:59:32 +0200793 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200794 RoutePattern.QUERY_LEGACY_SUFFIX,
Frank Borden79448112022-04-12 16:59:32 +0200795 'handleQueryLegacySuffixRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200796 ctx => this.handleQueryLegacySuffixRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200797 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100798
Frank Borden5ea7d652022-05-10 16:52:11 +0200799 this.mapRoute(RoutePattern.QUERY, 'handleQueryRoute', ctx =>
800 this.handleQueryRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200801 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100802
Frank Borden79448112022-04-12 16:59:32 +0200803 this.mapRoute(
804 RoutePattern.CHANGE_ID_QUERY,
805 'handleChangeIdQueryRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200806 ctx => this.handleChangeIdQueryRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200807 );
Peter Collingbourne8cab9332020-08-18 14:42:44 -0700808
Frank Borden79448112022-04-12 16:59:32 +0200809 this.mapRoute(
810 RoutePattern.DIFF_LEGACY_LINENUM,
811 'handleLegacyLinenum',
Frank Borden5ea7d652022-05-10 16:52:11 +0200812 ctx => this.handleLegacyLinenum(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200813 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100814
Frank Borden79448112022-04-12 16:59:32 +0200815 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200816 RoutePattern.CHANGE_NUMBER_LEGACY,
Frank Borden79448112022-04-12 16:59:32 +0200817 'handleChangeNumberLegacyRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200818 ctx => this.handleChangeNumberLegacyRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200819 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100820
Frank Borden79448112022-04-12 16:59:32 +0200821 this.mapRoute(
822 RoutePattern.DIFF_EDIT,
823 'handleDiffEditRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200824 ctx => this.handleDiffEditRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200825 true
826 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100827
Frank Borden79448112022-04-12 16:59:32 +0200828 this.mapRoute(
829 RoutePattern.CHANGE_EDIT,
830 'handleChangeEditRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200831 ctx => this.handleChangeEditRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200832 true
833 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100834
Frank Borden5ea7d652022-05-10 16:52:11 +0200835 this.mapRoute(RoutePattern.COMMENT, 'handleCommentRoute', ctx =>
836 this.handleCommentRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200837 );
Dhruv Srivastava90240452020-07-02 15:51:53 +0200838
Frank Borden5ea7d652022-05-10 16:52:11 +0200839 this.mapRoute(RoutePattern.COMMENTS_TAB, 'handleCommentsRoute', ctx =>
840 this.handleCommentsRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200841 );
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +0200842
Frank Borden5ea7d652022-05-10 16:52:11 +0200843 this.mapRoute(RoutePattern.DIFF, 'handleDiffRoute', ctx =>
844 this.handleDiffRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200845 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100846
Frank Borden5ea7d652022-05-10 16:52:11 +0200847 this.mapRoute(RoutePattern.CHANGE, 'handleChangeRoute', ctx =>
848 this.handleChangeRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200849 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100850
Frank Borden5ea7d652022-05-10 16:52:11 +0200851 this.mapRoute(RoutePattern.CHANGE_LEGACY, 'handleChangeLegacyRoute', ctx =>
852 this.handleChangeLegacyRoute(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(
856 RoutePattern.AGREEMENTS,
857 'handleAgreementsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200858 () => this.handleAgreementsRoute(),
Frank Borden79448112022-04-12 16:59:32 +0200859 true
860 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100861
Frank Borden79448112022-04-12 16:59:32 +0200862 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200863 RoutePattern.NEW_AGREEMENTS,
Frank Borden79448112022-04-12 16:59:32 +0200864 'handleNewAgreementsRoute',
Ben Rohlfs2586e572022-09-16 12:55:37 +0200865 () => this.handleNewAgreementsRoute(),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200866 true
867 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100868
Frank Borden79448112022-04-12 16:59:32 +0200869 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200870 RoutePattern.SETTINGS_LEGACY,
Frank Borden79448112022-04-12 16:59:32 +0200871 'handleSettingsLegacyRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200872 ctx => this.handleSettingsLegacyRoute(ctx),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200873 true
874 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100875
Frank Borden79448112022-04-12 16:59:32 +0200876 this.mapRoute(
877 RoutePattern.SETTINGS,
878 'handleSettingsRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200879 ctx => this.handleSettingsRoute(ctx),
Frank Borden79448112022-04-12 16:59:32 +0200880 true
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200881 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100882
Frank Borden5ea7d652022-05-10 16:52:11 +0200883 this.mapRoute(RoutePattern.REGISTER, 'handleRegisterRoute', ctx =>
884 this.handleRegisterRoute(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.LOG_IN_OR_OUT, 'handlePassThroughRoute', () =>
888 this.handlePassThroughRoute()
Frank Borden79448112022-04-12 16:59:32 +0200889 );
890
891 this.mapRoute(
892 RoutePattern.IMPROPERLY_ENCODED_PLUS,
893 'handleImproperlyEncodedPlusRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200894 ctx => this.handleImproperlyEncodedPlusRoute(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200895 );
896
Frank Borden5ea7d652022-05-10 16:52:11 +0200897 this.mapRoute(RoutePattern.PLUGIN_SCREEN, 'handlePluginScreen', ctx =>
898 this.handlePluginScreen(ctx)
Frank Borden79448112022-04-12 16:59:32 +0200899 );
900
901 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200902 RoutePattern.DOCUMENTATION_SEARCH_FILTER,
Frank Borden79448112022-04-12 16:59:32 +0200903 'handleDocumentationSearchRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200904 ctx => this.handleDocumentationSearchRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200905 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100906
907 // redirects /Documentation/q/* to /Documentation/q/filter:*
Frank Borden79448112022-04-12 16:59:32 +0200908 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200909 RoutePattern.DOCUMENTATION_SEARCH,
Frank Borden79448112022-04-12 16:59:32 +0200910 'handleDocumentationSearchRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200911 ctx => this.handleDocumentationSearchRedirectRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200912 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100913
Frank Borden5ea7d652022-05-10 16:52:11 +0200914 // Makes sure /Documentation/* links work (don't return 404)
Frank Borden79448112022-04-12 16:59:32 +0200915 this.mapRoute(
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200916 RoutePattern.DOCUMENTATION,
Frank Borden79448112022-04-12 16:59:32 +0200917 'handleDocumentationRedirectRoute',
Frank Borden5ea7d652022-05-10 16:52:11 +0200918 ctx => this.handleDocumentationRedirectRoute(ctx)
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200919 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100920
921 // Note: this route should appear last so it only catches URLs unmatched
922 // by other patterns.
Frank Borden5ea7d652022-05-10 16:52:11 +0200923 this.mapRoute(RoutePattern.DEFAULT, 'handleDefaultRoute', () =>
924 this.handleDefaultRoute()
Frank Borden79448112022-04-12 16:59:32 +0200925 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100926
927 page.start();
928 }
929
930 /**
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200931 * @return if handling the route involves asynchrony, then a
932 * promise is returned. Otherwise, synchronous handling returns null.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100933 */
Ben Rohlfs5940c532022-09-20 09:19:50 +0200934 handleRootRoute(ctx: PageContext) {
935 if (ctx.querystring.match(/^closeAfterLogin/)) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100936 // Close child window on redirect after login.
937 window.close();
938 return null;
939 }
Ben Rohlfs5940c532022-09-20 09:19:50 +0200940 let hash = this.getHashFromCanonicalPath(ctx.canonicalPath);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100941 // For backward compatibility with GWT links.
942 if (hash) {
943 // In certain login flows the server may redirect to a hash without
944 // a leading slash, which page.js doesn't handle correctly.
945 if (hash[0] !== '/') {
946 hash = '/' + hash;
947 }
Ben Rohlfs5940c532022-09-20 09:19:50 +0200948 if (hash.includes('/ /') && ctx.canonicalPath.includes('/+/')) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100949 // Path decodes all '+' to ' ' -- this breaks project-based URLs.
950 // See Issue 6888.
951 hash = hash.replace('/ /', '/+/');
952 }
Dmitrii Filippov0049afe2020-07-08 14:08:17 +0200953 const base = getBaseUrl();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100954 let newUrl = base + hash;
955 if (hash.startsWith('/VE/')) {
956 newUrl = base + '/settings' + hash;
Wyatt Allen84505ea2017-08-24 10:53:05 -0700957 }
Frank Borden79448112022-04-12 16:59:32 +0200958 this.redirect(newUrl);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100959 return null;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100960 }
Ben Rohlfs43935a42020-12-01 19:14:09 +0100961 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100962 if (loggedIn) {
Frank Borden79448112022-04-12 16:59:32 +0200963 this.redirect('/dashboard/self');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100964 } else {
Frank Borden79448112022-04-12 16:59:32 +0200965 this.redirect('/q/status:open+-is:wip');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100966 }
967 });
968 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700969
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100970 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100971 * Handle dashboard routes. These may be user, or project dashboards.
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100972 */
Ben Rohlfs5940c532022-09-20 09:19:50 +0200973 handleDashboardRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100974 // User dashboard. We require viewing user to be logged in, else we
975 // redirect to login for self dashboard or simple owner search for
976 // other user dashboard.
Ben Rohlfs43935a42020-12-01 19:14:09 +0100977 return this.restApiService.getLoggedIn().then(loggedIn => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100978 if (!loggedIn) {
Ben Rohlfs5940c532022-09-20 09:19:50 +0200979 if (ctx.params[0].toLowerCase() === 'self') {
980 this.redirectToLogin(ctx.canonicalPath);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700981 } else {
Ben Rohlfs5940c532022-09-20 09:19:50 +0200982 this.redirect('/q/owner:' + encodeURIComponent(ctx.params[0]));
Wyatt Allen089dfb32017-08-24 17:55:38 -0700983 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100984 } else {
Ben Rohlfs2586e572022-09-16 12:55:37 +0200985 const state: DashboardViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +0200986 view: GerritView.DASHBOARD,
Ben Rohlfs5940c532022-09-20 09:19:50 +0200987 user: ctx.params[0],
Ben Rohlfs2586e572022-09-16 12:55:37 +0200988 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +0200989 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +0200990 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +0200991 this.dashboardViewModel.setState(state);
Wyatt Allen089dfb32017-08-24 17:55:38 -0700992 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100993 });
994 }
Wyatt Allen089dfb32017-08-24 17:55:38 -0700995
Ben Rohlfs82b46492022-09-20 09:13:56 +0200996 handleCustomDashboardRoute(ctx: PageContext) {
997 const queryParams = new URLSearchParams(ctx.querystring);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100998
Ben Rohlfs82b46492022-09-20 09:13:56 +0200999 let title = 'Custom Dashboard';
1000 const titleParam = queryParams.get('title');
1001 if (titleParam) title = titleParam;
1002 queryParams.delete('title');
1003
1004 let forEachQuery = '';
1005 const forEachParam = queryParams.get('foreach');
1006 if (forEachParam) forEachQuery = forEachParam + ' ';
1007 queryParams.delete('foreach');
1008
1009 const sections: DashboardSection[] = [];
1010 for (const [name, query] of queryParams) {
1011 if (!name || !query) continue;
1012 sections.push({name, query: `${forEachQuery}${query}`});
1013 }
1014
1015 if (sections.length === 0) {
1016 this.redirect('/dashboard/self');
Wyatt Allenee2016c2017-10-31 13:41:52 -07001017 return Promise.resolve();
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001018 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001019
Ben Rohlfs82b46492022-09-20 09:13:56 +02001020 const state: DashboardViewState = {
1021 view: GerritView.DASHBOARD,
1022 user: 'self',
1023 sections,
1024 title,
1025 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001026 // Note that router model view must be updated before view models.
Ben Rohlfs82b46492022-09-20 09:13:56 +02001027 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001028 this.dashboardViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001029 return Promise.resolve();
1030 }
Logan Hanksc34e0b62017-10-03 01:40:54 -07001031
Ben Rohlfs5940c532022-09-20 09:19:50 +02001032 handleProjectDashboardRoute(ctx: PageContext) {
1033 const project = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001034 const state: DashboardViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001035 view: GerritView.DASHBOARD,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001036 project,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001037 dashboard: decodeURIComponent(ctx.params[1]) as DashboardId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001038 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001039 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001040 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001041 this.dashboardViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001042 this.reporting.setRepoName(project);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001043 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001044
Ben Rohlfs5940c532022-09-20 09:19:50 +02001045 handleLegacyProjectDashboardRoute(ctx: PageContext) {
1046 this.redirect('/p/' + ctx.params[0] + '/+/dashboard/' + ctx.params[1]);
Jacek Centkowskid322bd42020-09-16 10:34:55 +02001047 }
1048
Ben Rohlfs5940c532022-09-20 09:19:50 +02001049 handleGroupInfoRoute(ctx: PageContext) {
1050 this.redirect('/admin/groups/' + encodeURIComponent(ctx.params[0]));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001051 }
Paladox nonef7303f72019-06-27 14:57:11 +00001052
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001053 handleGroupSelfRedirectRoute(_: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001054 this.redirect('/settings/#Groups');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001055 }
Wyatt Allenc1727672017-10-19 15:06:54 -07001056
Ben Rohlfs5940c532022-09-20 09:19:50 +02001057 handleGroupRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001058 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001059 view: GerritView.GROUP,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001060 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001061 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001062 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001063 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001064 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001065 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001066
Ben Rohlfs5940c532022-09-20 09:19:50 +02001067 handleGroupAuditLogRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001068 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001069 view: GerritView.GROUP,
1070 detail: GroupDetailView.LOG,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001071 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001072 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001073 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001074 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001075 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001076 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001077
Ben Rohlfs5940c532022-09-20 09:19:50 +02001078 handleGroupMembersRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001079 const state: GroupViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001080 view: GerritView.GROUP,
1081 detail: GroupDetailView.MEMBERS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001082 groupId: ctx.params[0] as GroupId,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001083 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001084 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001085 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001086 this.groupViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001087 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001088
Ben Rohlfs5940c532022-09-20 09:19:50 +02001089 handleGroupListOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001090 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001091 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001092 adminView: AdminChildView.GROUPS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001093 offset: ctx.params[1] || 0,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001094 filter: null,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001095 openCreateModal: ctx.hash === 'create',
Ben Rohlfs2586e572022-09-16 12:55:37 +02001096 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001097 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001098 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001099 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001100 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001101
Ben Rohlfs5940c532022-09-20 09:19:50 +02001102 handleGroupListFilterOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001103 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001104 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001105 adminView: AdminChildView.GROUPS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001106 offset: ctx.params['offset'],
1107 filter: ctx.params['filter'],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001108 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001109 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001110 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001111 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001112 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001113
Ben Rohlfs5940c532022-09-20 09:19:50 +02001114 handleGroupListFilterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001115 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001116 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001117 adminView: AdminChildView.GROUPS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001118 filter: ctx.params['filter'] || null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001119 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001120 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001121 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001122 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001123 }
Paladox none87f99602019-07-05 12:58:23 +00001124
Ben Rohlfs5940c532022-09-20 09:19:50 +02001125 handleProjectsOldRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001126 let params = '';
Ben Rohlfs5940c532022-09-20 09:19:50 +02001127 if (ctx.params[1]) {
1128 params = encodeURIComponent(ctx.params[1]);
1129 if (ctx.params[1].includes(',')) {
1130 params = encodeURIComponent(ctx.params[1]).replace('%2C', ',');
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001131 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +01001132 }
Kasper Nilsson8d399e02017-08-29 11:19:04 -07001133
Frank Borden79448112022-04-12 16:59:32 +02001134 this.redirect(`/admin/repos/${params}`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001135 }
1136
Ben Rohlfs5940c532022-09-20 09:19:50 +02001137 handleRepoCommandsRoute(ctx: PageContext) {
1138 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001139 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001140 view: GerritView.REPO,
1141 detail: RepoDetailView.COMMANDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001142 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001143 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001144 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001145 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001146 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001147 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001148 }
1149
Ben Rohlfs5940c532022-09-20 09:19:50 +02001150 handleRepoGeneralRoute(ctx: PageContext) {
1151 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001152 const state: RepoViewState = {
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +02001153 view: GerritView.REPO,
1154 detail: RepoDetailView.GENERAL,
1155 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);
Youssef Elghareeb3b7740f2021-08-09 19:52:23 +02001160 this.reporting.setRepoName(repo);
1161 }
1162
Ben Rohlfs5940c532022-09-20 09:19:50 +02001163 handleRepoAccessRoute(ctx: PageContext) {
1164 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001165 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001166 view: GerritView.REPO,
1167 detail: RepoDetailView.ACCESS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001168 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001169 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001170 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001171 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001172 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001173 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001174 }
1175
Ben Rohlfs5940c532022-09-20 09:19:50 +02001176 handleRepoDashboardsRoute(ctx: PageContext) {
1177 const repo = ctx.params[0] as RepoName;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001178 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001179 view: GerritView.REPO,
1180 detail: RepoDetailView.DASHBOARDS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001181 repo,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001182 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001183 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001184 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001185 this.repoViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001186 this.reporting.setRepoName(repo);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001187 }
1188
Ben Rohlfs5940c532022-09-20 09:19:50 +02001189 handleBranchListOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001190 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001191 view: GerritView.REPO,
1192 detail: RepoDetailView.BRANCHES,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001193 repo: ctx.params[0] as RepoName,
1194 offset: ctx.params[2] || 0,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001195 filter: null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001196 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001197 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001198 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001199 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001200 }
1201
Ben Rohlfs5940c532022-09-20 09:19:50 +02001202 handleBranchListFilterOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001203 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001204 view: GerritView.REPO,
1205 detail: RepoDetailView.BRANCHES,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001206 repo: ctx.params['repo'] as RepoName,
1207 offset: ctx.params['offset'],
1208 filter: ctx.params['filter'],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001209 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001210 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001211 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001212 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001213 }
1214
Ben Rohlfs5940c532022-09-20 09:19:50 +02001215 handleBranchListFilterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001216 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001217 view: GerritView.REPO,
1218 detail: RepoDetailView.BRANCHES,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001219 repo: ctx.params['repo'] as RepoName,
1220 filter: ctx.params['filter'] || null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001221 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001222 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001223 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001224 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001225 }
1226
Ben Rohlfs5940c532022-09-20 09:19:50 +02001227 handleTagListOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001228 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001229 view: GerritView.REPO,
1230 detail: RepoDetailView.TAGS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001231 repo: ctx.params[0] as RepoName,
1232 offset: ctx.params[2] || 0,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001233 filter: null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001234 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001235 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001236 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001237 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001238 }
1239
Ben Rohlfs5940c532022-09-20 09:19:50 +02001240 handleTagListFilterOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001241 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001242 view: GerritView.REPO,
1243 detail: RepoDetailView.TAGS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001244 repo: ctx.params['repo'] as RepoName,
1245 offset: ctx.params['offset'],
1246 filter: ctx.params['filter'],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001247 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001248 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001249 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001250 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001251 }
1252
Ben Rohlfs5940c532022-09-20 09:19:50 +02001253 handleTagListFilterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001254 const state: RepoViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001255 view: GerritView.REPO,
1256 detail: RepoDetailView.TAGS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001257 repo: ctx.params['repo'] as RepoName,
1258 filter: ctx.params['filter'] || null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001259 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001260 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001261 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001262 this.repoViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001263 }
1264
Ben Rohlfs5940c532022-09-20 09:19:50 +02001265 handleRepoListOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001266 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001267 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001268 adminView: AdminChildView.REPOS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001269 offset: ctx.params[1] || 0,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001270 filter: null,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001271 openCreateModal: ctx.hash === 'create',
Ben Rohlfs2586e572022-09-16 12:55:37 +02001272 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001273 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001274 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001275 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001276 }
1277
Ben Rohlfs5940c532022-09-20 09:19:50 +02001278 handleRepoListFilterOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001279 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001280 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001281 adminView: AdminChildView.REPOS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001282 offset: ctx.params['offset'],
1283 filter: ctx.params['filter'],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001284 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001285 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001286 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001287 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001288 }
1289
Ben Rohlfs5940c532022-09-20 09:19:50 +02001290 handleRepoListFilterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001291 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001292 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001293 adminView: AdminChildView.REPOS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001294 filter: ctx.params['filter'] || null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001295 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001296 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001297 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001298 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001299 }
1300
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001301 handleCreateProjectRoute(_: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001302 // Redirects the legacy route to the new route, which displays the project
1303 // list with a hash 'create'.
Frank Borden79448112022-04-12 16:59:32 +02001304 this.redirect('/admin/repos#create');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001305 }
1306
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001307 handleCreateGroupRoute(_: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001308 // Redirects the legacy route to the new route, which displays the group
1309 // list with a hash 'create'.
Frank Borden79448112022-04-12 16:59:32 +02001310 this.redirect('/admin/groups#create');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001311 }
1312
Ben Rohlfs5940c532022-09-20 09:19:50 +02001313 handleRepoRoute(ctx: PageContext) {
1314 this.redirect(ctx.path + ',general');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001315 }
1316
Ben Rohlfs5940c532022-09-20 09:19:50 +02001317 handlePluginListOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001318 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001319 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001320 adminView: AdminChildView.PLUGINS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001321 offset: ctx.params[1] || 0,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001322 filter: null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001323 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001324 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001325 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001326 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001327 }
1328
Ben Rohlfs5940c532022-09-20 09:19:50 +02001329 handlePluginListFilterOffsetRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001330 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001331 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001332 adminView: AdminChildView.PLUGINS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001333 offset: ctx.params['offset'],
1334 filter: ctx.params['filter'],
Ben Rohlfs2586e572022-09-16 12:55:37 +02001335 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001336 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001337 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001338 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001339 }
1340
Ben Rohlfs5940c532022-09-20 09:19:50 +02001341 handlePluginListFilterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001342 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001343 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001344 adminView: AdminChildView.PLUGINS,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001345 filter: ctx.params['filter'] || null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001346 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001347 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001348 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001349 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001350 }
1351
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001352 handlePluginListRoute(_: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001353 const state: AdminViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001354 view: GerritView.ADMIN,
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +02001355 adminView: AdminChildView.PLUGINS,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001356 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001357 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001358 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001359 this.adminViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001360 }
1361
Ben Rohlfs5940c532022-09-20 09:19:50 +02001362 handleQueryRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001363 const state: SearchViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001364 view: GerritView.SEARCH,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001365 query: ctx.params[0],
1366 offset: ctx.params[2],
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +00001367 changes: [],
1368 loading: false,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001369 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001370 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001371 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001372 this.searchViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001373 }
1374
Ben Rohlfs5940c532022-09-20 09:19:50 +02001375 handleChangeIdQueryRoute(ctx: PageContext) {
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001376 // TODO(pcc): This will need to indicate that this was a change ID query if
1377 // standard queries gain the ability to search places like commit messages
1378 // for change IDs.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001379 const state: SearchViewState = {
Ben Rohlfs8163cd42022-08-18 16:04:36 +02001380 view: GerritView.SEARCH,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001381 query: ctx.params[0],
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +00001382 changes: [],
1383 loading: false,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001384 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001385 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001386 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001387 this.searchViewModel.setState(state);
Peter Collingbourne8cab9332020-08-18 14:42:44 -07001388 }
1389
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001390 handleQueryLegacySuffixRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001391 this.redirect(ctx.path.replace(LEGACY_QUERY_SUFFIX_PATTERN, ''));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001392 }
1393
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001394 handleChangeNumberLegacyRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001395 this.redirect('/c/' + encodeURIComponent(ctx.params[0]));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001396 }
1397
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001398 handleChangeRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001399 // Parameter order is based on the regex group number matched.
Milutin Kristofic3a844522021-01-21 10:01:55 +01001400 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001401 const state: ChangeViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001402 project: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001403 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001404 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001405 patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001406 view: GerritView.CHANGE,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001407 };
1408
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001409 const queryMap = new URLSearchParams(ctx.querystring);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001410 if (queryMap.has('forceReload')) state.forceReload = true;
1411 if (queryMap.has('openReplyDialog')) state.openReplyDialog = true;
Dhruv2cc210c2022-06-07 14:18:03 +02001412
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001413 const tab = queryMap.get('tab');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001414 if (tab) state.tab = tab;
Ben Rohlfsa1d2c0c2022-09-29 17:03:26 +02001415 const checksPatchset = Number(queryMap.get('checksPatchset'));
1416 if (Number.isInteger(checksPatchset) && checksPatchset > 0) {
1417 state.checksPatchset = checksPatchset as PatchSetNumber;
1418 }
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001419 const filter = queryMap.get('filter');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001420 if (filter) state.filter = filter;
Ben Rohlfs209f1412022-09-30 12:25:46 +02001421 const checksResultsFilter = queryMap.get('checksResultsFilter');
1422 if (checksResultsFilter) state.checksResultsFilter = checksResultsFilter;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001423 const attempt = stringToAttemptChoice(queryMap.get('attempt'));
Ben Rohlfs2586e572022-09-16 12:55:37 +02001424 if (attempt && attempt !== LATEST_ATTEMPT) state.attempt = attempt;
Ben Rohlfs6485eb82022-09-30 11:26:08 +02001425 const selected = queryMap.get('checksRunsSelected');
Ben Rohlfs20fe8e82022-10-14 11:13:10 +02001426 if (selected) state.checksRunsSelected = new Set(selected.split(','));
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001427
Ben Rohlfs2586e572022-09-16 12:55:37 +02001428 assertIsDefined(state.project, 'project');
1429 this.reporting.setRepoName(state.project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001430 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001431 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001432 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001433 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001434 this.changeViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001435 }
1436
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001437 handleCommentRoute(ctx: PageContext) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001438 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001439 const state: DiffViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001440 project: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001441 changeNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001442 commentId: ctx.params[2] as UrlEncodedCommentId,
1443 view: GerritView.DIFF,
Dhruv Srivastava90240452020-07-02 15:51:53 +02001444 commentLink: true,
1445 };
Ben Rohlfs2586e572022-09-16 12:55:37 +02001446 this.reporting.setRepoName(state.project ?? '');
Milutin Kristofic3a844522021-01-21 10:01:55 +01001447 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001448 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001449 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001450 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001451 this.diffViewModel.setState(state);
Dhruv Srivastava90240452020-07-02 15:51:53 +02001452 }
1453
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001454 handleCommentsRoute(ctx: PageContext) {
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001455 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001456 const state: ChangeViewState = {
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001457 project: ctx.params[0] as RepoName,
1458 changeNum,
1459 commentId: ctx.params[2] as UrlEncodedCommentId,
1460 view: GerritView.CHANGE,
1461 };
Ben Rohlfs2586e572022-09-16 12:55:37 +02001462 assertIsDefined(state.project);
1463 this.reporting.setRepoName(state.project);
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001464 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001465 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001466 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001467 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001468 this.changeViewModel.setState(state);
Dhruv Srivastava1bed4ae2021-05-25 21:58:12 +02001469 }
1470
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001471 handleDiffRoute(ctx: PageContext) {
Milutin Kristofic3a844522021-01-21 10:01:55 +01001472 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001473 // Parameter order is based on the regex group number matched.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001474 const state: DiffViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001475 project: ctx.params[0] as RepoName,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001476 changeNum,
Dhruv Srivastava591b4902021-03-10 11:48:12 +01001477 basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001478 patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001479 path: ctx.params[8],
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001480 view: GerritView.DIFF,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001481 };
Frank Borden79448112022-04-12 16:59:32 +02001482 const address = this.parseLineAddress(ctx.hash);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001483 if (address) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001484 state.leftSide = address.leftSide;
1485 state.lineNum = address.lineNum;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001486 }
Ben Rohlfs2586e572022-09-16 12:55:37 +02001487 this.reporting.setRepoName(state.project ?? '');
Milutin Kristofic3a844522021-01-21 10:01:55 +01001488 this.reporting.setChangeId(changeNum);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001489 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001490 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001491 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001492 this.diffViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001493 }
1494
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001495 handleChangeLegacyRoute(ctx: PageContext) {
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001496 const changeNum = Number(ctx.params[0]) as NumericChangeId;
1497 if (!changeNum) {
Frank Borden79448112022-04-12 16:59:32 +02001498 this.show404();
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001499 return;
1500 }
1501 this.restApiService.getFromProjectLookup(changeNum).then(project => {
1502 // Show a 404 and terminate if the lookup request failed. Attempting
1503 // to redirect after failing to get the project loops infinitely.
1504 if (!project) {
Frank Borden79448112022-04-12 16:59:32 +02001505 this.show404();
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001506 return;
1507 }
Frank Borden79448112022-04-12 16:59:32 +02001508 this.redirect(`/c/${project}/+/${changeNum}/${ctx.params[1]}`);
Dhruv Srivastavaebdf7542021-09-28 15:12:02 +01001509 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001510 }
1511
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001512 handleLegacyLinenum(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001513 this.redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001514 }
1515
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001516 handleDiffEditRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001517 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001518 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001519 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfs2586e572022-09-16 12:55:37 +02001520 const state: EditViewState = {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001521 project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001522 changeNum,
Dhruv Srivastavae3430e22020-10-21 22:32:39 +02001523 // for edit view params, patchNum cannot be undefined
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001524 patchNum: convertToPatchSetNum(ctx.params[2]) as RevisionPatchSetNum,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001525 path: ctx.params[3],
Ben Rohlfs6a0033a2022-09-15 11:32:55 +02001526 lineNum: Number(ctx.hash),
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001527 view: GerritView.EDIT,
Ben Rohlfs2a431342022-09-16 10:47:01 +02001528 };
Ben Rohlfs2586e572022-09-16 12:55:37 +02001529 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001530 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001531 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001532 this.editViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001533 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001534 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001535 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001536
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001537 handleChangeEditRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001538 // Parameter order is based on the regex group number matched.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001539 const project = ctx.params[0] as RepoName;
Milutin Kristofic3a844522021-01-21 10:01:55 +01001540 const changeNum = Number(ctx.params[1]) as NumericChangeId;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001541 const queryMap = new URLSearchParams(ctx.querystring);
Ben Rohlfs2586e572022-09-16 12:55:37 +02001542 const state: ChangeViewState = {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001543 project,
Milutin Kristofic3a844522021-01-21 10:01:55 +01001544 changeNum,
Ben Rohlfsabaeacf2022-05-27 15:24:22 +02001545 patchNum: convertToPatchSetNum(ctx.params[3]) as RevisionPatchSetNum,
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001546 view: GerritView.CHANGE,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001547 edit: true,
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001548 };
Ben Rohlfs15da9b72022-10-18 09:41:50 +02001549 const tab = queryMap.get('tab');
1550 if (tab) state.tab = tab;
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001551 if (queryMap.has('forceReload')) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001552 state.forceReload = true;
Dhruv Srivastava49f0a7a2021-10-06 12:03:01 +01001553 history.replaceState(
1554 null,
1555 '',
1556 location.href.replace(/[?&]forceReload=true/, '')
1557 );
1558 }
Ben Rohlfs2586e572022-09-16 12:55:37 +02001559 this.normalizePatchRangeParams(state);
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001560 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001561 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001562 this.changeViewModel.setState(state);
Milutin Kristoficda88b332020-03-24 10:19:12 +01001563 this.reporting.setRepoName(project);
Milutin Kristofic3a844522021-01-21 10:01:55 +01001564 this.reporting.setChangeId(changeNum);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001565 }
Wyatt Allen089dfb32017-08-24 17:55:38 -07001566
Frank Borden79448112022-04-12 16:59:32 +02001567 handleAgreementsRoute() {
1568 this.redirect('/settings/#Agreements');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001569 }
1570
Ben Rohlfs2586e572022-09-16 12:55:37 +02001571 handleNewAgreementsRoute() {
1572 const state: AgreementViewState = {
1573 view: GerritView.AGREEMENTS,
1574 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001575 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001576 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001577 this.agreementViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001578 }
1579
Ben Rohlfs5940c532022-09-20 09:19:50 +02001580 handleSettingsLegacyRoute(ctx: PageContext) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001581 // email tokens may contain '+' but no space.
1582 // The parameter parsing replaces all '+' with a space,
1583 // undo that to have valid tokens.
Ben Rohlfs5940c532022-09-20 09:19:50 +02001584 const token = ctx.params[0].replace(/ /g, '+');
Ben Rohlfs2586e572022-09-16 12:55:37 +02001585 const state: SettingsViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001586 view: GerritView.SETTINGS,
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001587 emailToken: token,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001588 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001589 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001590 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001591 this.settingsViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001592 }
1593
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001594 handleSettingsRoute(_: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001595 const state: SettingsViewState = {view: GerritView.SETTINGS};
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001596 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001597 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001598 this.settingsViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001599 }
1600
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001601 handleRegisterRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001602 this.setState({justRegistered: true});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001603 let path = ctx.params[0] || '/';
1604
1605 // Prevent redirect looping.
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001606 if (path.startsWith('/register')) {
1607 path = '/';
1608 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001609
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001610 if (path[0] !== '/') {
1611 return;
1612 }
Frank Borden79448112022-04-12 16:59:32 +02001613 this.redirect(getBaseUrl() + path);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001614 }
1615
1616 /**
1617 * Handler for routes that should pass through the router and not be caught
1618 * by the catchall _handleDefaultRoute handler.
1619 */
Frank Borden79448112022-04-12 16:59:32 +02001620 handlePassThroughRoute() {
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +01001621 windowLocationReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001622 }
1623
1624 /**
1625 * URL may sometimes have /+/ encoded to / /.
1626 * Context: Issue 6888, Issue 7100
1627 */
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001628 handleImproperlyEncodedPlusRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001629 let hash = this.getHashFromCanonicalPath(ctx.canonicalPath);
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001630 if (hash.length) {
1631 hash = '#' + hash;
1632 }
Frank Borden79448112022-04-12 16:59:32 +02001633 this.redirect(`/c/${ctx.params[0]}/+/${ctx.params[1]}${hash}`);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001634 }
1635
Ben Rohlfscf5109c2022-09-20 08:44:28 +02001636 handlePluginScreen(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001637 const state: PluginViewState = {
1638 view: GerritView.PLUGIN_SCREEN,
1639 plugin: ctx.params[0],
1640 screen: ctx.params[1],
1641 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001642 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001643 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001644 this.pluginViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001645 }
1646
Ben Rohlfs5940c532022-09-20 09:19:50 +02001647 handleDocumentationSearchRoute(ctx: PageContext) {
Ben Rohlfs2586e572022-09-16 12:55:37 +02001648 const state: DocumentationViewState = {
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001649 view: GerritView.DOCUMENTATION_SEARCH,
Ben Rohlfs5940c532022-09-20 09:19:50 +02001650 filter: ctx.params['filter'] || null,
Ben Rohlfs2586e572022-09-16 12:55:37 +02001651 };
Ben Rohlfs7b22a292022-09-29 12:52:01 +02001652 // Note that router model view must be updated before view models.
Ben Rohlfs2586e572022-09-16 12:55:37 +02001653 this.setState(state);
Ben Rohlfs5fa82932022-09-21 09:29:31 +02001654 this.documentationViewModel.setState(state);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001655 }
1656
Ben Rohlfs5940c532022-09-20 09:19:50 +02001657 handleDocumentationSearchRedirectRoute(ctx: PageContext) {
Frank Borden79448112022-04-12 16:59:32 +02001658 this.redirect(
Ben Rohlfs5940c532022-09-20 09:19:50 +02001659 '/Documentation/q/filter:' + encodeURIComponent(ctx.params[0])
Dmitrii Filippov252cddf2020-08-17 16:21:13 +02001660 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001661 }
1662
Ben Rohlfs5940c532022-09-20 09:19:50 +02001663 handleDocumentationRedirectRoute(ctx: PageContext) {
1664 if (ctx.params[1]) {
Ben Rohlfs5ae40eb2021-02-11 20:16:22 +01001665 windowLocationReload();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001666 } else {
1667 // Redirect /Documentation to /Documentation/index.html
Frank Borden79448112022-04-12 16:59:32 +02001668 this.redirect('/Documentation/index.html');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001669 }
1670 }
1671
1672 /**
1673 * Catchall route for when no other route is matched.
1674 */
Frank Borden79448112022-04-12 16:59:32 +02001675 handleDefaultRoute() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001676 if (this._isInitialLoad) {
1677 // Server recognized this route as polygerrit, so we show 404.
Frank Borden79448112022-04-12 16:59:32 +02001678 this.show404();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001679 } else {
1680 // Route can be recognized by server, so we pass it to server.
Frank Borden79448112022-04-12 16:59:32 +02001681 this.handlePassThroughRoute();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001682 }
1683 }
1684
Frank Borden79448112022-04-12 16:59:32 +02001685 private show404() {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001686 // Note: the app's 404 display is tightly-coupled with catching 404
1687 // network responses, so we simulate a 404 response status to display it.
1688 // TODO: Decouple the gr-app error view from network responses.
Ben Rohlfsa76c82f2021-01-22 22:22:32 +01001689 firePageError(new Response('', {status: 404}));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +01001690 }
1691}