UI: Update Namespaces Page Link Component Behavior (#30861)

* namespace link using router.transitionTo and doesn't re-auth to match namespace-picker

* remove namespace link component and use interactive dd item

* update fn name + test

* trigger build

* add test

* update test

* test updates

* remove unneeded var
This commit is contained in:
lane-wetmore 2025-06-13 11:07:12 -05:00 committed by GitHub
parent fe9aaca5f4
commit e64ce286e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 39 additions and 86 deletions

View File

@ -1,51 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { service } from '@ember/service';
import { alias } from '@ember/object/computed';
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
namespaceService: service('namespace'),
currentNamespace: alias('namespaceService.path'),
tagName: '',
//public api
targetNamespace: null,
showLastSegment: false,
// set to true if targetNamespace is passed in unmodified
// otherwise, this assumes it is parsed as in namespace-picker
unparsed: false,
normalizedNamespace: computed('targetNamespace', 'unparsed', function () {
const ns = this.targetNamespace || '';
return this.unparsed ? ns : ns.replace(/\.+/g, '/').replace(/☃/g, '.');
}),
namespaceDisplay: computed('normalizedNamespace', 'showLastSegment', function () {
const ns = this.normalizedNamespace;
if (!ns) return 'root';
const showLastSegment = this.showLastSegment;
const parts = ns?.split('/');
return showLastSegment ? parts[parts.length - 1] : ns;
}),
isCurrentNamespace: computed('targetNamespace', 'currentNamespace', function () {
return this.currentNamespace === this.targetNamespace;
}),
get namespaceLink() {
const origin =
window.location.protocol +
'//' +
window.location.hostname +
(window.location.port ? ':' + window.location.port : '');
if (!this.normalizedNamespace) return `${origin}/ui/vault/dashboard`;
// The full URL/origin is required so that the page is reloaded.
return `${origin}/ui/vault/dashboard?namespace=${encodeURIComponent(this.normalizedNamespace)}`;
},
});

View File

@ -90,4 +90,11 @@ export default class ManageNamespacesController extends Controller {
this.flashMessages.danger('There was an error refreshing the namespace list.');
}
}
@action
switchNamespace(targetNamespace) {
this.router.transitionTo('vault.cluster.dashboard', {
queryParams: { namespace: targetNamespace },
});
}
}

View File

@ -1,21 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<ExternalLink
@href={{this.namespaceLink}}
@sameTab={{true}}
class={{concat "is-block " this.class}}
data-test-namespace-link={{this.normalizedNamespace}}
>
{{#if (has-block)}}
{{yield}}
{{else}}
<div class="level is-mobile">
<span class="level-left">{{this.namespaceDisplay}}</span>
<span class="level-right">
<Hds::Button @text="Namespace link" @icon="chevron-right" @isIconOnly={{true}} @color="tertiary" />
</span>
</div>
{{/if}}
</ExternalLink>

View File

@ -64,11 +64,8 @@
as |targetNamespace|
}}
{{#if (includes targetNamespace this.namespaceService.accessibleNamespaces)}}
<dd.Generic>
<NamespaceLink @targetNamespace={{targetNamespace}} @unparsed={{true}} @class="ns-dropdown-item">
Switch to namespace
</NamespaceLink>
</dd.Generic>
<dd.Interactive {{on "click" (fn this.switchNamespace targetNamespace)}} data-test-popup-menu="switch">Switch
to namespace</dd.Interactive>
{{/if}}
{{/let}}
<dd.Interactive @color="critical" {{on "click" (fn (mut this.nsToDelete) list.item)}}>Delete</dd.Interactive>

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import { currentRouteName, visit, click, fillIn, currentURL, findAll } from '@ember/test-helpers';
import { currentRouteName, visit, click, fillIn, currentURL, findAll, waitFor } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { login } from 'vault/tests/helpers/auth/auth-helpers';
@ -223,7 +223,7 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
assert.dom('[data-test-edit-form-submit]').exists('Save button is displayed');
await click('[data-test-edit-form-submit]');
// Verify test-create-ns does not exist in the Manage Namespace page
// Verify test-create-ns exists in the Manage Namespace page
await fillIn(GENERAL.filterInputExplicit, testNS);
await click(GENERAL.filterInputExplicitSearch);
assert.dom('.list-item-row').exists({ count: 1 }, `"${testNS}" namespace is displayed on the page`);
@ -255,6 +255,7 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
// Verify test-create-ns does not exist in the Namespace Picker
await click(GENERAL.toggleInput('namespace-id'));
await waitFor(NAMESPACE_PICKER_SELECTORS.searchInput);
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
assert.strictEqual(
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
@ -337,13 +338,6 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
assert
.dom(switchNamespaceButton)
.hasText('Switch to namespace', 'Allow users to switch to different namespace');
assert
.dom(`${switchNamespaceButton} a`)
.hasAttribute(
'href',
`http://localhost:7357/ui/vault/dashboard?namespace=${namespace}`,
'Switch namespace button has the correct href attribute'
);
// Verify that the user can delete the namespace
assert
@ -353,4 +347,29 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
// Cleanup: Delete namespace(s) via the CLI
await deleteNSFromPaths([namespace]);
});
test('it should render updated namespace after switching from access page', async function (assert) {
// Setup: Create namespace(s) via the CLI
const testNS = 'test-create-ns';
await createNSFromPaths([testNS]);
// Go to the manage namespaces page
await visit('/vault/access/namespaces');
// Hack: Trigger refresh internal namespaces endpoint
await click(GENERAL.toggleInput('namespace-id'));
await click(GENERAL.button('Refresh list'));
// Switch namespace
await click(GENERAL.menuTrigger);
await click(GENERAL.menuItem('switch'));
// Verify that we switched namespaces
await click(GENERAL.toggleInput('namespace-id'));
assert.dom('[data-test-badge-namespace]').hasText(testNS);
assert.strictEqual(currentRouteName(), 'vault.cluster.dashboard', 'navigates to the correct route');
// Cleanup: Delete namespace(s) via the CLI
await deleteNSFromPaths([testNS]);
});
});

View File

@ -12,6 +12,7 @@ import {
findAll,
triggerKeyEvent,
find,
waitFor,
} from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
@ -346,6 +347,7 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
// Verify that the namespace does not exist in the namespace picker
await click(GENERAL.toggleInput('namespace-id'));
await waitFor(GENERAL.button('Refresh list'));
await click(GENERAL.button('Refresh list'));
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, namespace);
assert