Skip to content

Commit 9e20e05

Browse files
[VAULT-35046] replace Hds::SuperSelect with Hds::Dropdown, show namespace count label (#30183)
* [VAULT-35046] Show namespace count label; use Hds::Dropdown instead of Hds::Superselect * address misc pr comments * update tests * address PR comment * Update ui/app/components/namespace-picker.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/components/namespace-picker.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * address PR comments: update tests * use getter for namespace label --------- Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
1 parent 35b9338 commit 9e20e05

File tree

7 files changed

+83
-88
lines changed

7 files changed

+83
-88
lines changed
Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
{{!
2-
Copyright (c) HashiCorp, Inc.
3-
SPDX-License-Identifier: BUSL-1.1
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: BUSL-1.1
44
}}
55

66
<div class="namespace-picker" ...attributes>
7-
<Hds::Form::SuperSelect::Single::Field
8-
@afterOptionsComponent={{component "namespace-picker/after-options" loadOptions=this.loadOptions}}
9-
@ariaLabel="Namespace"
10-
@onChange={{this.onChange}}
11-
@options={{this.options}}
12-
@placeholder="Search"
13-
@searchEnabled={{true}}
14-
@selected={{this.selected}}
15-
@selectedItemComponent={{component "namespace-picker/selected-option"}}
16-
@showAfterOptions={{this.showAfterOptions}}
17-
@verticalPosition="above"
18-
data-test-namespace-toggle
19-
as |F|
20-
>
21-
<F.Options>{{F.options.label}}</F.Options>
22-
</Hds::Form::SuperSelect::Single::Field>
7+
<Hds::Dropdown @enableCollisionDetection={{true}} as |D|>
8+
9+
<D.ToggleButton @icon="org" @text={{or this.selected.id "-"}} data-test-namespace-toggle />
10+
11+
<D.Generic>
12+
{{this.namespaceLabel}}
13+
<Hds::BadgeCount @text={{or this.options.length 0}} />
14+
</D.Generic>
15+
16+
{{#each this.options as |option|}}
17+
<D.Checkmark
18+
@selected={{eq option.id this.selected.id}}
19+
{{on "click" (fn this.onChange option)}}
20+
data-test-namespace-link={{option.path}}
21+
>
22+
{{option.label}}
23+
</D.Checkmark>
24+
{{/each}}
25+
26+
{{! Check if the user has permissions to list namespaces. If true, display additional footer options like "Refresh list" and "Manage". }}
27+
{{#if this.hasListPermissions}}
28+
<D.Footer @hasDivider={{true}}>
29+
<Hds::Button @color="secondary" @text="Refresh list" {{on "click" this.loadOptions}} data-test-refresh-namespaces />
30+
<Hds::Button @color="secondary" @text="Manage" @icon="settings" @route="vault.cluster.access.namespaces" />
31+
</D.Footer>
32+
{{/if}}
33+
34+
</Hds::Dropdown>
2335
</div>

ui/app/components/namespace-picker.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ export default class NamespacePicker extends Component {
2525
@service store;
2626

2727
// Show/hide refresh & manage namespaces buttons
28-
@tracked showAfterOptions = false;
28+
@tracked hasListPermissions = false;
2929

3030
@tracked selected = {};
3131
@tracked options = [];
32+
@tracked searchInput = '';
3233

3334
constructor() {
3435
super(...arguments);
@@ -72,18 +73,22 @@ export default class NamespacePicker extends Component {
7273
];
7374
}
7475

76+
get namespaceLabel() {
77+
return this.searchInput === '' ? 'All namespaces' : 'Matching namespaces';
78+
}
79+
7580
@action
7681
async fetchListCapability() {
7782
// TODO: Revist. This logic was carried over from previous component implmenetation.
7883
// When the user doesn't have this capability, shouldn't we just hide the "Manage" button,
7984
// instead of hiding both the "Manage" and "Refresh List" buttons?
8085
try {
8186
await this.store.findRecord('capabilities', 'sys/namespaces/');
82-
this.showAfterOptions = true;
87+
this.hasListPermissions = true;
8388
} catch (e) {
8489
// If error out on findRecord call it's because you don't have permissions
8590
// and therefore don't have permission to manage namespaces
86-
this.showAfterOptions = false;
91+
this.hasListPermissions = false;
8792
}
8893
}
8994

ui/app/components/namespace-picker/after-options.hbs

Lines changed: 0 additions & 13 deletions
This file was deleted.

ui/app/components/namespace-picker/after-options.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

ui/app/components/namespace-picker/selected-option.hbs

Lines changed: 0 additions & 11 deletions
This file was deleted.

ui/tests/acceptance/enterprise-namespaces-test.js

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { setupApplicationTest } from 'ember-qunit';
99
import { runCmd, createNS } from 'vault/tests/helpers/commands';
1010
import { login, loginNs, logout } from 'vault/tests/helpers/auth/auth-helpers';
1111
import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors';
12+
import { GENERAL } from '../helpers/general-selectors';
13+
import { NAMESPACE_PICKER_SELECTORS } from '../helpers/namespace-picker';
1214

1315
module('Acceptance | Enterprise | namespaces', function (hooks) {
1416
setupApplicationTest(hooks);
@@ -22,15 +24,18 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
2224
await runCmd(createNS(ns), false);
2325
const token = await runCmd(`write -field=client_token auth/token/create policies=default`);
2426
await login(token);
25-
await click('[data-test-namespace-toggle]');
26-
assert.dom('[aria-selected="true"]').hasText('root', 'root renders as current namespace');
27-
assert.dom('[data-option-index]').exists({ count: 1 }, 'Only the root namespace exists');
27+
await click(NAMESPACE_PICKER_SELECTORS.toggle);
28+
assert.dom(NAMESPACE_PICKER_SELECTORS.link()).hasText('root', 'root renders as current namespace');
29+
assert
30+
.dom(`${NAMESPACE_PICKER_SELECTORS.link()} svg${GENERAL.icon('check')}`)
31+
.exists('The root namespace is selected');
2832
});
2933

34+
// TODO: Is this test description still accurate?
3035
test('it shows nested namespaces if you log in with a namespace starting with a /', async function (assert) {
31-
assert.expect(5);
36+
assert.expect(6);
3237

33-
await click('[data-test-namespace-toggle]');
38+
await click(NAMESPACE_PICKER_SELECTORS.toggle);
3439

3540
const nses = ['beep', 'boop', 'bop'];
3641
for (const [i, ns] of nses.entries()) {
@@ -40,25 +45,40 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
4045
const targetNamespace = nses.slice(0, i + 1).join('/');
4146
const url = `/vault/secrets?namespace=${targetNamespace}`;
4247
// this is usually triggered when creating a ns in the form -- trigger a reload of the namespaces manually
43-
await click('[data-test-namespace-toggle]');
44-
await click('[data-test-refresh-namespaces]');
45-
await waitFor(`[data-option-index="${i + 1}"]`);
48+
await click(NAMESPACE_PICKER_SELECTORS.toggle);
49+
await click(NAMESPACE_PICKER_SELECTORS.refreshList);
50+
await waitFor(NAMESPACE_PICKER_SELECTORS.link(targetNamespace));
4651
// check that the full namespace path, like "beep/boop", shows in the toggle display
4752
assert
48-
.dom(`[data-option-index="${i + 1}"]`)
53+
.dom(NAMESPACE_PICKER_SELECTORS.link(targetNamespace))
4954
.hasText(targetNamespace, `shows the namespace ${targetNamespace} in the toggle component`);
5055
// because quint does not like page reloads, visiting url directly instead of clicking on namespace in toggle
5156
await visit(url);
5257
}
5358

5459
await loginNs('/beep/boop');
5560
await settled();
56-
await click('[data-test-namespace-toggle]');
57-
await waitFor('[data-test-current-namespace]');
58-
assert.dom('[data-test-current-namespace]').hasText('beep/boop/');
59-
assert
60-
.dom('[data-option-index="3"]')
61-
.hasText('beep/boop/bop', 'shows the full namespace path in the toggle');
61+
62+
// Open the namespace picker & wait for it to render
63+
await click(NAMESPACE_PICKER_SELECTORS.toggle);
64+
await waitFor(`svg${GENERAL.icon('check')}`);
65+
66+
// Find the selected element with the check icon & ensure it exists
67+
const checkIcon = document.querySelector(
68+
`${NAMESPACE_PICKER_SELECTORS.link()} svg${GENERAL.icon('check')}`
69+
);
70+
assert.ok(checkIcon, 'A selected namespace link with the check icon exists');
71+
72+
// Get the selected namespace with the data-test-namespace-link attribute & ensure it exists
73+
const selectedNamespace = checkIcon.closest(NAMESPACE_PICKER_SELECTORS.link());
74+
assert.ok(selectedNamespace, 'The selected namespace link exists');
75+
76+
// Verify that the selected namespace has the correct data-test-namespace-link attribute and path value
77+
assert.strictEqual(
78+
selectedNamespace.getAttribute('data-test-namespace-link'),
79+
'beep/boop',
80+
'The current namespace does not begin or end with /'
81+
);
6282
});
6383

6484
test('it shows the regular namespace toolbar when not managed', async function (assert) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
export const NAMESPACE_PICKER_SELECTORS = {
7+
link: (link) => (link ? `[data-test-namespace-link="${link}"]` : '[data-test-namespace-link]'),
8+
refreshList: '[data-test-refresh-namespaces]',
9+
toggle: '[data-test-namespace-toggle]',
10+
};

0 commit comments

Comments
 (0)