Skip to content

Commit 7131189

Browse files
committed
[VAULT-35044] UI: implement action buttons in updated namespace picker (#30140)
1 parent 467e1f4 commit 7131189

File tree

5 files changed

+97
-13
lines changed

5 files changed

+97
-13
lines changed

ui/app/components/namespace-picker.hbs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,19 @@ SPDX-License-Identifier: BUSL-1.1
55

66
<div class="namespace-picker" ...attributes>
77
<Hds::Form::SuperSelect::Single::Field
8+
@afterOptionsComponent={{component "namespace-picker/after-options" loadOptions=this.loadOptions}}
89
@ariaLabel="Namespace"
910
@onChange={{this.onChange}}
1011
@options={{this.options}}
1112
@placeholder="Search"
1213
@searchEnabled={{true}}
1314
@selected={{this.selected}}
1415
@selectedItemComponent={{component "namespace-picker/selected-option"}}
16+
@showAfterOptions={{this.showAfterOptions}}
1517
@verticalPosition="above"
1618
data-test-namespace-toggle
1719
as |F|
1820
>
19-
<F.Options>
20-
{{#let F.options as |option|}}
21-
<span data-test-namespace-link="{{option.label}}">{{option.label}}</span>
22-
{{/let}}
23-
</F.Options>
21+
<F.Options>{{F.options.label}}</F.Options>
2422
</Hds::Form::SuperSelect::Single::Field>
2523
</div>

ui/app/components/namespace-picker.js

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Component from '@glimmer/component';
77
import { action } from '@ember/object';
88
import { tracked } from '@glimmer/tracking';
99
import { service } from '@ember/service';
10+
import { isEmpty } from '@ember/utils';
1011

1112
/**
1213
* @module NamespacePicker
@@ -21,6 +22,11 @@ import { service } from '@ember/service';
2122

2223
export default class NamespacePicker extends Component {
2324
@service namespace;
25+
@service router;
26+
@service store;
27+
28+
// Show/hide refresh & manage namespaces buttons
29+
@tracked showAfterOptions = false;
2430

2531
@tracked selected = {};
2632
@tracked options = [];
@@ -30,17 +36,20 @@ export default class NamespacePicker extends Component {
3036
this.loadOptions();
3137
}
3238

39+
// TODO make private when converting from js to ts
3340
#matchesPath(option, currentNamespace) {
3441
// TODO: Revisit. A hardcoded check for "path" & "/path" seems hacky, but it fixes a breaking test:
3542
// "Acceptance | Enterprise | namespaces: it shows nested namespaces if you log in with a namespace starting with a /"
3643
// My assumption is that namespace shouldn't start with a "/", but is this a HVD thing? or is the test outdated?
3744
return option?.path === currentNamespace?.path || `/${option?.path}` === currentNamespace?.path;
3845
}
3946

47+
// TODO make private when converting from js to ts
4048
#getSelected(options, currentNamespace) {
4149
return options.find((option) => this.#matchesPath(option, currentNamespace));
4250
}
4351

52+
// TODO make private when converting from js to ts
4453
#getOptions(namespace) {
4554
/* Each namespace option has 3 properties: { id, path, and label }
4655
* - id: node / namespace name (displayed when the namespace picker is closed)
@@ -64,6 +73,40 @@ export default class NamespacePicker extends Component {
6473
];
6574
}
6675

76+
// TODO make private when converting from js to ts
77+
#getNamespaceLink(location, namespace) {
78+
const origin = this.#getOrigin(location);
79+
80+
let queryParams = '';
81+
if (!isEmpty(namespace.path)) {
82+
const encodedNamespace = encodeURIComponent(namespace.path);
83+
queryParams = `?namespace=${encodedNamespace}`;
84+
}
85+
86+
// The full URL/origin is required so that the page is reloaded.
87+
return `${origin}/ui/vault/dashboard${queryParams}`;
88+
}
89+
90+
// TODO make private when converting from js to ts
91+
#getOrigin(location) {
92+
return location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
93+
}
94+
95+
@action
96+
async fetchListCapability() {
97+
// TODO: Revist. This logic was carried over from previous component implmenetation.
98+
// When the user doesn't have this capability, shouldn't we just hide the "Manage" button,
99+
// instead of hiding both the "Manage" and "Refresh List" buttons?
100+
try {
101+
await this.store.findRecord('capabilities', 'sys/namespaces/');
102+
this.showAfterOptions = true;
103+
} catch (e) {
104+
// If error out on findRecord call it's because you don't have permissions
105+
// and therefore don't have permission to manage namespaces
106+
this.showAfterOptions = false;
107+
}
108+
}
109+
67110
@action
68111
async loadOptions() {
69112
// TODO: namespace service's findNamespacesForUser will never throw an error.
@@ -72,11 +115,13 @@ export default class NamespacePicker extends Component {
72115

73116
this.options = this.#getOptions(this.namespace);
74117
this.selected = this.#getSelected(this.options, this.namespace);
118+
119+
await this.fetchListCapability();
75120
}
76121

77122
@action
78-
async onChange(selectedOption) {
79-
// TODO: redirect to selected namespace
80-
this.selected = selectedOption;
123+
async onChange(selected) {
124+
this.selected = selected;
125+
this.router.transitionTo('vault.cluster.dashboard', { queryParams: { namespace: selected.path } });
81126
}
82127
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: BUSL-1.1
4+
}}
5+
<div>
6+
<Hds::Button
7+
@color="secondary"
8+
@text="Refresh List"
9+
{{on "click" (action "refreshNamespaceList")}}
10+
data-test-refresh-namespaces
11+
/>
12+
<Hds::Button @color="secondary" @text="Manage" @icon="settings" @route="vault.cluster.access.namespaces" />
13+
</div>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Component from '@glimmer/component';
7+
import { action } from '@ember/object';
8+
9+
interface AfterOptionsArgs {
10+
loadOptions: () => void;
11+
}
12+
13+
/**
14+
* @module AfterOptions
15+
* @description component is used to display action items inside the namespace picker dropdown.
16+
* The "Manage" button directs the user to the namespace management page.
17+
* The "Refresh List" button refrehes the list of namespaces in the dropdown.
18+
*
19+
* @example
20+
* @afterOptionsComponent={{component "namespace-picker/after-options" loadOptions=this.loadOptions}}
21+
*/
22+
23+
export default class AfterOptions extends Component<AfterOptionsArgs> {
24+
@action
25+
refreshNamespaceList() {
26+
this.args?.loadOptions();
27+
}
28+
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
2424
await login(token);
2525
await click('[data-test-namespace-toggle]');
2626
assert.dom('[aria-selected="true"]').hasText('root', 'root renders as current namespace');
27-
assert.dom('[data-test-namespace-link]').exists({ count: 1 }, 'Only the root namespace exists');
27+
assert.dom('[data-option-index]').exists({ count: 1 }, 'Only the root namespace exists');
2828
});
2929

3030
test('it shows nested namespaces if you log in with a namespace starting with a /', async function (assert) {
@@ -42,10 +42,10 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
4242
// this is usually triggered when creating a ns in the form -- trigger a reload of the namespaces manually
4343
await click('[data-test-namespace-toggle]');
4444
await click('[data-test-refresh-namespaces]');
45-
await waitFor(`[data-test-namespace-link="${targetNamespace}"]`);
45+
await waitFor(`[data-option-index="${i + 1}"]`);
4646
// check that the full namespace path, like "beep/boop", shows in the toggle display
4747
assert
48-
.dom(`[data-test-namespace-link="${targetNamespace}"]`)
48+
.dom(`[data-option-index="${i + 1}"]`)
4949
.hasText(targetNamespace, `shows the namespace ${targetNamespace} in the toggle component`);
5050
// because quint does not like page reloads, visiting url directly instead of clicking on namespace in toggle
5151
await visit(url);
@@ -57,8 +57,8 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
5757
await waitFor('[data-test-current-namespace]');
5858
assert.dom('[data-test-current-namespace]').hasText('beep/boop/');
5959
assert
60-
.dom('[data-test-namespace-link="beep/boop/bop"]')
61-
.exists('renders the link to the nested namespace');
60+
.dom('[data-option-index="3"]')
61+
.hasText('beep/boop/bop', 'shows the full namespace path in the toggle');
6262
});
6363

6464
test('it shows the regular namespace toolbar when not managed', async function (assert) {

0 commit comments

Comments
 (0)