From c854eb9ada9c64f4cc80e0410418d17466a2306a Mon Sep 17 00:00:00 2001 From: Phil Renaud Date: Thu, 20 Feb 2025 10:27:44 -0500 Subject: [PATCH] /csi routes to /storage routes and a routeRedirector util --- ui/app/components/global-search/control.js | 2 +- .../controllers/{csi => storage}/plugins.js | 0 .../{csi => storage}/plugins/index.js | 4 +- .../{csi => storage}/plugins/plugin.js | 4 +- .../plugins/plugin/allocations.js | 0 .../{csi => storage}/plugins/plugin/index.js | 0 .../controllers/{csi => storage}/volumes.js | 0 .../{csi => storage}/volumes/index.js | 4 +- .../{csi => storage}/volumes/volume.js | 4 +- ui/app/router.js | 5 +- ui/app/routes/application.js | 7 ++ ui/app/routes/{csi => storage}/index.js | 2 +- ui/app/routes/{csi => storage}/plugins.js | 0 .../routes/{csi => storage}/plugins/index.js | 0 .../routes/{csi => storage}/plugins/plugin.js | 0 .../plugins/plugin/allocations.js | 0 .../{csi => storage}/plugins/plugin/index.js | 0 ui/app/routes/{csi => storage}/volumes.js | 0 .../routes/{csi => storage}/volumes/index.js | 0 .../routes/{csi => storage}/volumes/volume.js | 0 ui/app/services/keyboard.js | 2 +- .../allocations/allocation/task/index.hbs | 2 +- ui/app/templates/components/gutter-menu.hbs | 2 +- ui/app/templates/components/plugin-subnav.hbs | 4 +- .../templates/components/storage-subnav.hbs | 4 +- ui/app/templates/components/task-row.hbs | 4 +- ui/app/templates/csi/plugins.hbs | 6 - ui/app/templates/csi/volumes.hbs | 6 - ui/app/templates/jobs/job/task-group.hbs | 4 +- ui/app/templates/{csi.hbs => storage.hbs} | 0 ui/app/templates/storage/plugins.hbs | 6 + .../{csi => storage}/plugins/index.hbs | 2 +- .../{csi => storage}/plugins/plugin.hbs | 0 .../plugins/plugin/allocations.hbs | 0 .../{csi => storage}/plugins/plugin/index.hbs | 4 +- ui/app/templates/storage/volumes.hbs | 6 + .../{csi => storage}/volumes/index.hbs | 2 +- .../{csi => storage}/volumes/volume.hbs | 0 ui/app/utils/route-redirector.js | 43 +++++++ ui/app/utils/route-redirects.js | 36 ++++++ ui/tests/unit/utils/route-redirector-test.js | 116 ++++++++++++++++++ 41 files changed, 243 insertions(+), 38 deletions(-) rename ui/app/controllers/{csi => storage}/plugins.js (100%) rename ui/app/controllers/{csi => storage}/plugins/index.js (91%) rename ui/app/controllers/{csi => storage}/plugins/plugin.js (82%) rename ui/app/controllers/{csi => storage}/plugins/plugin/allocations.js (100%) rename ui/app/controllers/{csi => storage}/plugins/plugin/index.js (100%) rename ui/app/controllers/{csi => storage}/volumes.js (100%) rename ui/app/controllers/{csi => storage}/volumes/index.js (97%) rename ui/app/controllers/{csi => storage}/volumes/volume.js (95%) rename ui/app/routes/{csi => storage}/index.js (81%) rename ui/app/routes/{csi => storage}/plugins.js (100%) rename ui/app/routes/{csi => storage}/plugins/index.js (100%) rename ui/app/routes/{csi => storage}/plugins/plugin.js (100%) rename ui/app/routes/{csi => storage}/plugins/plugin/allocations.js (100%) rename ui/app/routes/{csi => storage}/plugins/plugin/index.js (100%) rename ui/app/routes/{csi => storage}/volumes.js (100%) rename ui/app/routes/{csi => storage}/volumes/index.js (100%) rename ui/app/routes/{csi => storage}/volumes/volume.js (100%) delete mode 100644 ui/app/templates/csi/plugins.hbs delete mode 100644 ui/app/templates/csi/volumes.hbs rename ui/app/templates/{csi.hbs => storage.hbs} (100%) create mode 100644 ui/app/templates/storage/plugins.hbs rename ui/app/templates/{csi => storage}/plugins/index.hbs (96%) rename ui/app/templates/{csi => storage}/plugins/plugin.hbs (100%) rename ui/app/templates/{csi => storage}/plugins/plugin/allocations.hbs (100%) rename ui/app/templates/{csi => storage}/plugins/plugin/index.hbs (98%) create mode 100644 ui/app/templates/storage/volumes.hbs rename ui/app/templates/{csi => storage}/volumes/index.hbs (99%) rename ui/app/templates/{csi => storage}/volumes/volume.hbs (100%) create mode 100644 ui/app/utils/route-redirector.js create mode 100644 ui/app/utils/route-redirects.js create mode 100644 ui/tests/unit/utils/route-redirector-test.js diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index f39b98cf248..dfafceb7eb3 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -202,7 +202,7 @@ export default class GlobalSearchControl extends Component { ); }); } else if (model.type === 'plugin') { - this.router.transitionTo('csi.plugins.plugin', model.id); + this.router.transitionTo('storage.plugins.plugin', model.id); } else if (model.type === 'allocation') { this.router.transitionTo('allocations.allocation', model.id); } diff --git a/ui/app/controllers/csi/plugins.js b/ui/app/controllers/storage/plugins.js similarity index 100% rename from ui/app/controllers/csi/plugins.js rename to ui/app/controllers/storage/plugins.js diff --git a/ui/app/controllers/csi/plugins/index.js b/ui/app/controllers/storage/plugins/index.js similarity index 91% rename from ui/app/controllers/csi/plugins/index.js rename to ui/app/controllers/storage/plugins/index.js index 5afa2f231b1..2fb73db9a0c 100644 --- a/ui/app/controllers/csi/plugins/index.js +++ b/ui/app/controllers/storage/plugins/index.js @@ -23,7 +23,7 @@ export default class IndexController extends Controller.extend( Searchable ) { @service userSettings; - @controller('csi/plugins') pluginsController; + @controller('storage/plugins') pluginsController; @alias('pluginsController.isForbidden') isForbidden; @@ -65,7 +65,7 @@ export default class IndexController extends Controller.extend( @action gotoPlugin(plugin, event) { lazyClick([ - () => this.transitionToRoute('csi.plugins.plugin', plugin.plainId), + () => this.transitionToRoute('storage.plugins.plugin', plugin.plainId), event, ]); } diff --git a/ui/app/controllers/csi/plugins/plugin.js b/ui/app/controllers/storage/plugins/plugin.js similarity index 82% rename from ui/app/controllers/csi/plugins/plugin.js rename to ui/app/controllers/storage/plugins/plugin.js index 632d48841ab..0a347e5c012 100644 --- a/ui/app/controllers/csi/plugins/plugin.js +++ b/ui/app/controllers/storage/plugins/plugin.js @@ -15,11 +15,11 @@ export default class CsiPluginsPluginController extends Controller { return [ { label: 'Plugins', - args: ['csi.plugins'], + args: ['storage.plugins'], }, { label: plainId, - args: ['csi.plugins.plugin', plainId], + args: ['storage.plugins.plugin', plainId], }, ]; } diff --git a/ui/app/controllers/csi/plugins/plugin/allocations.js b/ui/app/controllers/storage/plugins/plugin/allocations.js similarity index 100% rename from ui/app/controllers/csi/plugins/plugin/allocations.js rename to ui/app/controllers/storage/plugins/plugin/allocations.js diff --git a/ui/app/controllers/csi/plugins/plugin/index.js b/ui/app/controllers/storage/plugins/plugin/index.js similarity index 100% rename from ui/app/controllers/csi/plugins/plugin/index.js rename to ui/app/controllers/storage/plugins/plugin/index.js diff --git a/ui/app/controllers/csi/volumes.js b/ui/app/controllers/storage/volumes.js similarity index 100% rename from ui/app/controllers/csi/volumes.js rename to ui/app/controllers/storage/volumes.js diff --git a/ui/app/controllers/csi/volumes/index.js b/ui/app/controllers/storage/volumes/index.js similarity index 97% rename from ui/app/controllers/csi/volumes/index.js rename to ui/app/controllers/storage/volumes/index.js index 92750aaf858..db90a4a624f 100644 --- a/ui/app/controllers/csi/volumes/index.js +++ b/ui/app/controllers/storage/volumes/index.js @@ -28,7 +28,7 @@ export default class IndexController extends Controller.extend( @service system; @service userSettings; @service keyboard; - @controller('csi/volumes') volumesController; + @controller('storage/volumes') volumesController; @alias('volumesController.isForbidden') isForbidden; @@ -115,7 +115,7 @@ export default class IndexController extends Controller.extend( lazyClick([ () => this.transitionToRoute( - 'csi.volumes.volume', + 'storage.volumes.volume', volume.get('idWithNamespace') ), event, diff --git a/ui/app/controllers/csi/volumes/volume.js b/ui/app/controllers/storage/volumes/volume.js similarity index 95% rename from ui/app/controllers/csi/volumes/volume.js rename to ui/app/controllers/storage/volumes/volume.js index 74fb18ed9eb..9271479e0ad 100644 --- a/ui/app/controllers/csi/volumes/volume.js +++ b/ui/app/controllers/storage/volumes/volume.js @@ -31,12 +31,12 @@ export default class VolumeController extends Controller { return [ { label: 'Volumes', - args: ['csi.volumes'], + args: ['storage.volumes'], }, { label: volume.name, args: [ - 'csi.volumes.volume', + 'storage.volumes.volume', volume.plainId, qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default', diff --git a/ui/app/router.js b/ui/app/router.js index 174c5c3c4e8..c771310c579 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -60,7 +60,10 @@ Router.map(function () { this.route('topology'); - this.route('csi', function () { + // Only serves as a redirect to storage + this.route('csi'); + + this.route('storage', function () { this.route('volumes', function () { this.route('volume', { path: '/:volume_name' }); }); diff --git a/ui/app/routes/application.js b/ui/app/routes/application.js index d45418bc12c..b749de20fba 100644 --- a/ui/app/routes/application.js +++ b/ui/app/routes/application.js @@ -3,6 +3,8 @@ * SPDX-License-Identifier: BUSL-1.1 */ +// @ts-check + /* eslint-disable ember/no-controller-access-in-routes */ import { inject as service } from '@ember/service'; import { later, next } from '@ember/runloop'; @@ -11,6 +13,7 @@ import { AbortError } from '@ember-data/adapter/error'; import RSVP from 'rsvp'; import { action } from '@ember/object'; import classic from 'ember-classic-decorator'; +import { handleRouteRedirects } from '../utils/route-redirector'; @classic export default class ApplicationRoute extends Route { @@ -33,6 +36,10 @@ export default class ApplicationRoute extends Route { } async beforeModel(transition) { + if (handleRouteRedirects(transition, this.router)) { + return; + } + let promises; // service:router#transitionTo can cause this to rerun because of refreshModel on diff --git a/ui/app/routes/csi/index.js b/ui/app/routes/storage/index.js similarity index 81% rename from ui/app/routes/csi/index.js rename to ui/app/routes/storage/index.js index 23b74f6f00d..3f17adab6f1 100644 --- a/ui/app/routes/csi/index.js +++ b/ui/app/routes/storage/index.js @@ -7,6 +7,6 @@ import Route from '@ember/routing/route'; export default class IndexRoute extends Route { redirect() { - this.transitionTo('csi.volumes'); + this.transitionTo('storage.volumes'); } } diff --git a/ui/app/routes/csi/plugins.js b/ui/app/routes/storage/plugins.js similarity index 100% rename from ui/app/routes/csi/plugins.js rename to ui/app/routes/storage/plugins.js diff --git a/ui/app/routes/csi/plugins/index.js b/ui/app/routes/storage/plugins/index.js similarity index 100% rename from ui/app/routes/csi/plugins/index.js rename to ui/app/routes/storage/plugins/index.js diff --git a/ui/app/routes/csi/plugins/plugin.js b/ui/app/routes/storage/plugins/plugin.js similarity index 100% rename from ui/app/routes/csi/plugins/plugin.js rename to ui/app/routes/storage/plugins/plugin.js diff --git a/ui/app/routes/csi/plugins/plugin/allocations.js b/ui/app/routes/storage/plugins/plugin/allocations.js similarity index 100% rename from ui/app/routes/csi/plugins/plugin/allocations.js rename to ui/app/routes/storage/plugins/plugin/allocations.js diff --git a/ui/app/routes/csi/plugins/plugin/index.js b/ui/app/routes/storage/plugins/plugin/index.js similarity index 100% rename from ui/app/routes/csi/plugins/plugin/index.js rename to ui/app/routes/storage/plugins/plugin/index.js diff --git a/ui/app/routes/csi/volumes.js b/ui/app/routes/storage/volumes.js similarity index 100% rename from ui/app/routes/csi/volumes.js rename to ui/app/routes/storage/volumes.js diff --git a/ui/app/routes/csi/volumes/index.js b/ui/app/routes/storage/volumes/index.js similarity index 100% rename from ui/app/routes/csi/volumes/index.js rename to ui/app/routes/storage/volumes/index.js diff --git a/ui/app/routes/csi/volumes/volume.js b/ui/app/routes/storage/volumes/volume.js similarity index 100% rename from ui/app/routes/csi/volumes/volume.js rename to ui/app/routes/storage/volumes/volume.js diff --git a/ui/app/services/keyboard.js b/ui/app/services/keyboard.js index 3ed4f78fccf..d45c14ba0ba 100644 --- a/ui/app/services/keyboard.js +++ b/ui/app/services/keyboard.js @@ -105,7 +105,7 @@ export default class KeyboardService extends Service { }, { label: 'Go to Storage', - action: () => this.router.transitionTo('csi.volumes'), + action: () => this.router.transitionTo('storage.volumes'), rebindable: true, }, { diff --git a/ui/app/templates/allocations/allocation/task/index.hbs b/ui/app/templates/allocations/allocation/task/index.hbs index 578827da545..9d6598f52db 100644 --- a/ui/app/templates/allocations/allocation/task/index.hbs +++ b/ui/app/templates/allocations/allocation/task/index.hbs @@ -220,7 +220,7 @@ {{#if row.model.isCSI}} diff --git a/ui/app/templates/components/plugin-subnav.hbs b/ui/app/templates/components/plugin-subnav.hbs index 2a84d1dfda2..05a49648048 100644 --- a/ui/app/templates/components/plugin-subnav.hbs +++ b/ui/app/templates/components/plugin-subnav.hbs @@ -5,7 +5,7 @@
    -
  • Overview
  • -
  • Allocations
  • +
  • Overview
  • +
  • Allocations
diff --git a/ui/app/templates/components/storage-subnav.hbs b/ui/app/templates/components/storage-subnav.hbs index e14c97aa66f..eabb9367ae4 100644 --- a/ui/app/templates/components/storage-subnav.hbs +++ b/ui/app/templates/components/storage-subnav.hbs @@ -6,12 +6,12 @@
  • - + Volumes
  • - + Plugins
  • diff --git a/ui/app/templates/components/task-row.hbs b/ui/app/templates/components/task-row.hbs index 838297a68e4..78d863b2363 100644 --- a/ui/app/templates/components/task-row.hbs +++ b/ui/app/templates/components/task-row.hbs @@ -47,7 +47,7 @@ {{#if volume.isCSI}} {{/if}} {{/if}} - \ No newline at end of file + diff --git a/ui/app/templates/csi/plugins.hbs b/ui/app/templates/csi/plugins.hbs deleted file mode 100644 index f2c6f7ee976..00000000000 --- a/ui/app/templates/csi/plugins.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -~}} - -{{outlet}} \ No newline at end of file diff --git a/ui/app/templates/csi/volumes.hbs b/ui/app/templates/csi/volumes.hbs deleted file mode 100644 index f2c6f7ee976..00000000000 --- a/ui/app/templates/csi/volumes.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -~}} - -{{outlet}} \ No newline at end of file diff --git a/ui/app/templates/jobs/job/task-group.hbs b/ui/app/templates/jobs/job/task-group.hbs index 88fdb6e226c..4b85aee571e 100644 --- a/ui/app/templates/jobs/job/task-group.hbs +++ b/ui/app/templates/jobs/job/task-group.hbs @@ -332,10 +332,10 @@ {{#if row.model.isCSI}} {{!-- if volume is per_alloc=true, there's no one specific volume. So, link to the volumes index with an active query --}} {{#if row.model.perAlloc}} - {{row.model.name}} + {{row.model.name}} {{else}} {{row.model.name}} diff --git a/ui/app/templates/csi.hbs b/ui/app/templates/storage.hbs similarity index 100% rename from ui/app/templates/csi.hbs rename to ui/app/templates/storage.hbs diff --git a/ui/app/templates/storage/plugins.hbs b/ui/app/templates/storage/plugins.hbs new file mode 100644 index 00000000000..01b9ee821d6 --- /dev/null +++ b/ui/app/templates/storage/plugins.hbs @@ -0,0 +1,6 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +~}} + +{{outlet}} diff --git a/ui/app/templates/csi/plugins/index.hbs b/ui/app/templates/storage/plugins/index.hbs similarity index 96% rename from ui/app/templates/csi/plugins/index.hbs rename to ui/app/templates/storage/plugins/index.hbs index 082b41a5133..545e91dbb20 100644 --- a/ui/app/templates/csi/plugins/index.hbs +++ b/ui/app/templates/storage/plugins/index.hbs @@ -44,7 +44,7 @@ action=(action "gotoPlugin" row.model) }} > - {{row.model.plainId}} + {{row.model.plainId}} {{#if row.model.controllerRequired}} diff --git a/ui/app/templates/csi/plugins/plugin.hbs b/ui/app/templates/storage/plugins/plugin.hbs similarity index 100% rename from ui/app/templates/csi/plugins/plugin.hbs rename to ui/app/templates/storage/plugins/plugin.hbs diff --git a/ui/app/templates/csi/plugins/plugin/allocations.hbs b/ui/app/templates/storage/plugins/plugin/allocations.hbs similarity index 100% rename from ui/app/templates/csi/plugins/plugin/allocations.hbs rename to ui/app/templates/storage/plugins/plugin/allocations.hbs diff --git a/ui/app/templates/csi/plugins/plugin/index.hbs b/ui/app/templates/storage/plugins/plugin/index.hbs similarity index 98% rename from ui/app/templates/csi/plugins/plugin/index.hbs rename to ui/app/templates/storage/plugins/plugin/index.hbs index 1c2b7748e37..8b64fab1437 100644 --- a/ui/app/templates/csi/plugins/plugin/index.hbs +++ b/ui/app/templates/storage/plugins/plugin/index.hbs @@ -129,7 +129,7 @@

    @@ -180,7 +180,7 @@

    diff --git a/ui/app/templates/storage/volumes.hbs b/ui/app/templates/storage/volumes.hbs new file mode 100644 index 00000000000..01b9ee821d6 --- /dev/null +++ b/ui/app/templates/storage/volumes.hbs @@ -0,0 +1,6 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +~}} + +{{outlet}} diff --git a/ui/app/templates/csi/volumes/index.hbs b/ui/app/templates/storage/volumes/index.hbs similarity index 99% rename from ui/app/templates/csi/volumes/index.hbs rename to ui/app/templates/storage/volumes/index.hbs index bb50661d2a5..31c31cba020 100644 --- a/ui/app/templates/csi/volumes/index.hbs +++ b/ui/app/templates/storage/volumes/index.hbs @@ -84,7 +84,7 @@ > diff --git a/ui/app/templates/csi/volumes/volume.hbs b/ui/app/templates/storage/volumes/volume.hbs similarity index 100% rename from ui/app/templates/csi/volumes/volume.hbs rename to ui/app/templates/storage/volumes/volume.hbs diff --git a/ui/app/utils/route-redirector.js b/ui/app/utils/route-redirector.js new file mode 100644 index 00000000000..f8eac3784ae --- /dev/null +++ b/ui/app/utils/route-redirector.js @@ -0,0 +1,43 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import routeRedirects from './route-redirects'; + +export function handleRouteRedirects(transition, router) { + const currentPath = transition.intent.url || transition.targetName; + + for (const redirect of routeRedirects) { + let shouldRedirect = false; + let targetPath = + typeof redirect.to === 'function' + ? redirect.to(currentPath) + : redirect.to; + + switch (redirect.method) { + case 'startsWith': + shouldRedirect = currentPath.startsWith(redirect.from); + break; + case 'exact': + shouldRedirect = currentPath === redirect.from; + break; + case 'pattern': + if (redirect.pattern && redirect.pattern.test(currentPath)) { + shouldRedirect = true; + } + break; + } + + if (shouldRedirect) { + console.warn( + `This URL has changed. Please update your bookmark from ${currentPath} to ${targetPath}` + ); + + router.replaceWith(targetPath, { + queryParams: transition.to.queryParams, + }); + return true; + } + } +} diff --git a/ui/app/utils/route-redirects.js b/ui/app/utils/route-redirects.js new file mode 100644 index 00000000000..be42c1b960d --- /dev/null +++ b/ui/app/utils/route-redirects.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +// This serves as a lit of routes in the UI that we change over time, +// but still want to respect users' bookmarks and habits. + +/** + * @typedef {Object} RouteRedirect + * @property {string} from - The path to match against + * @property {(string|function(string): string)} to - Either a static path or a function to compute the new path + * @property {'startsWith'|'exact'|'pattern'} method - The matching strategy to use + * @property {RegExp} [pattern] - Optional regex pattern if method is 'pattern' + */ +export default [ + { + from: '/csi/volumes/', + to: (path) => { + const volumeName = path.split('/csi/volumes/')[1]; + return `/storage/volumes/${volumeName}`; + }, + method: 'pattern', + pattern: /^\/csi\/volumes\/(.+)$/, + }, + { + from: '/csi/volumes', + to: '/storage/volumes', + method: 'exact', + }, + { + from: '/csi', + to: '/storage', + method: 'startsWith', + }, +]; diff --git a/ui/tests/unit/utils/route-redirector-test.js b/ui/tests/unit/utils/route-redirector-test.js new file mode 100644 index 00000000000..e121cadb23e --- /dev/null +++ b/ui/tests/unit/utils/route-redirector-test.js @@ -0,0 +1,116 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { handleRouteRedirects } from 'nomad-ui/utils/route-redirector'; +import sinon from 'sinon'; + +module('Unit | Utility | handle-route-redirects', function () { + test('it handles different types of redirects correctly', function (assert) { + assert.expect(7); + + const router = { + replaceWith: sinon.spy(), + }; + + const testCases = [ + { + name: 'exact match redirect', + transition: { + intent: { url: '/csi/volumes' }, + to: { queryParams: { region: 'global' } }, + }, + expectedPath: '/storage/volumes', + expectedQueryParams: { region: 'global' }, + }, + { + name: 'pattern match redirect', + transition: { + intent: { url: '/csi/volumes/my-volume' }, + to: { queryParams: { region: 'us-east' } }, + }, + expectedPath: '/storage/volumes/my-volume', + expectedQueryParams: { region: 'us-east' }, + }, + { + name: 'startsWith redirect', + transition: { + intent: { url: '/csi' }, + to: { queryParams: {} }, + }, + expectedPath: '/storage', + expectedQueryParams: {}, + }, + { + name: 'no redirect needed', + transition: { + intent: { url: '/jobs' }, + to: { queryParams: {} }, + }, + expectedCalls: 0, + }, + ]; + + testCases + .filter((testCase) => testCase.expectedCalls !== 0) + .forEach((testCase) => { + router.replaceWith.resetHistory(); + + handleRouteRedirects(testCase.transition, router); + assert.ok( + router.replaceWith.calledOnce, + `${testCase.name}: redirect occurred` + ); + assert.ok( + router.replaceWith.calledWith(testCase.expectedPath, { + queryParams: testCase.expectedQueryParams, + }), + `${testCase.name}: redirected to correct path with query params` + ); + }); + + testCases + .filter((testCase) => testCase.expectedCalls === 0) + .forEach((testCase) => { + router.replaceWith.resetHistory(); + + handleRouteRedirects(testCase.transition, router); + assert.notOk( + router.replaceWith.called, + `${testCase.name}: no redirect occurred` + ); + }); + }); + + test('it preserves query parameters during redirects', function (assert) { + const router = { + replaceWith: sinon.spy(), + }; + + const transition = { + intent: { url: '/csi/volumes' }, + to: { + queryParams: { + region: 'global', + namespace: 'default', + foo: 'bar', + }, + }, + }; + + handleRouteRedirects(transition, router); + + assert.ok( + router.replaceWith.calledWith('/storage/volumes', { + queryParams: { + region: 'global', + namespace: 'default', + foo: 'bar', + }, + }), + 'All query parameters were preserved in the redirect' + ); + }); +});