Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Allow navigate to list from View Secret card #22502

Merged
merged 10 commits into from
Aug 23, 2023
3 changes: 3 additions & 0 deletions changelog/22502.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: KV View Secret card will link to list view if input ends in "/"
```
23 changes: 22 additions & 1 deletion ui/app/components/get-credentials-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* @param {string} backend - Passed to SearchSelect query method to fetch dropdown options
*/

// TODO: kv engine cleanup remove secrets related logic
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
Expand All @@ -32,18 +33,38 @@ export default class GetCredentialsCard extends Component {
@tracked role = '';
@tracked secret = '';

constructor() {
super(...arguments);
this.secret = this.args?.initialValue || '';
}

get buttonText() {
if (this.args.type === 'secret') {
if (this.secret.endsWith('/')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know endsWith() was a thing!! So cool 🤩

return 'View list';
}
return 'View secret';
}
return 'Get credentials';
}

get buttonDisabled() {
return !this.role && !this.secret;
}

@action
transitionToCredential() {
transitionToCredential(evt) {
evt.preventDefault();
const role = this.role;
const secret = this.secret;
if (role) {
this.router.transitionTo('vault.cluster.secrets.backend.credentials', role);
}
if (secret) {
if (secret.endsWith('/')) {
this.router.transitionTo('vault.cluster.secrets.backend.list', secret);
return;
}
this.router.transitionTo('vault.cluster.secrets.backend.show', secret);
}
}
Expand Down
13 changes: 4 additions & 9 deletions ui/app/templates/components/get-credentials-card.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
SPDX-License-Identifier: BUSL-1.1
~}}

<form class="selectable-card is-rounded no-flex data-test-get-credentials-card">
<form {{on "submit" this.transitionToCredential}} class="selectable-card is-rounded no-flex data-test-get-credentials-card">
<div class="is-flex-between is-fullwidth card-details">
<h3 class="title is-5">{{@title}}</h3>
</div>
Expand All @@ -16,6 +16,7 @@
@id="search-input-{{@type}}"
@onChange={{this.handleInput}}
@placeholder={{@placeholder}}
@initialValue={{@initialValue}}
data-test-search-roles
/>
{{else}}
Expand All @@ -30,13 +31,7 @@
data-test-search-roles
/>
{{/if}}
<button
type="button"
class="button is-secondary"
disabled={{this.buttonDisabled}}
{{on "click" this.transitionToCredential}}
data-test-get-credentials
>
{{@title}}
<button type="submit" class="button is-secondary" disabled={{this.buttonDisabled}} data-test-get-credentials>
{{this.buttonText}}
</button>
</form>
6 changes: 3 additions & 3 deletions ui/app/templates/vault/cluster/secrets/backend/list.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
@renderInputSearch={{true}}
@title="View secret"
@searchLabel="Secret path"
@subText="Type the path of the secret you want to read"
@subText="Type the path of the secret you want to view. Include a trailing slash to navigate to the list view."
@placeholder="secret/"
@backend="kv"
@type="secret"
@initialValue={{this.baseKey.id}}
/>
</div>
</div>
Expand Down Expand Up @@ -148,7 +148,7 @@
<EmptyState
@title="No {{pluralize options.item}} in this backend"
@message="{{pluralize (capitalize options.item)}}
in this backend will be listed here.
in this backend will be listed here.
{{or options.message (concat 'Add a ' options.item ' to get started.')}}"
>
<SecretLink
Expand Down
6 changes: 6 additions & 0 deletions ui/lib/core/addon/components/input-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export default class inputSelect extends Component {
*
*/
@tracked searchInput = '';

constructor() {
super(...arguments);
this.searchInput = this.args?.initialValue;
}

@action
inputChanged() {
this.args.onChange(this.searchInput);
Expand Down
42 changes: 42 additions & 0 deletions ui/tests/integration/components/get-credentials-card-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ module('Integration | Component | get-credentials-card', function (hooks) {
test('it shows a disabled button when no item is selected', async function (assert) {
await render(hbs`<GetCredentialsCard @title={{this.title}} @searchLabel={{this.searchLabel}}/>`);
assert.dom('[data-test-get-credentials]').isDisabled();
assert.dom('[data-test-get-credentials]').hasText('Get credentials', 'Button has default text');
});

test('it shows button that can be clicked to credentials route when an item is selected', async function (assert) {
Expand Down Expand Up @@ -89,11 +90,52 @@ module('Integration | Component | get-credentials-card', function (hooks) {
);
await typeIn('[data-test-search-roles] input', 'test');
assert.dom('[data-test-get-credentials]').isEnabled('submit button enables after typing input text');
assert.dom('[data-test-get-credentials]').hasText('View secret', 'Button has view secret CTA');
await click('[data-test-get-credentials]');
assert.propEqual(
this.router.transitionTo.lastCall.args,
['vault.cluster.secrets.backend.show', 'test'],
'transitionTo is called with correct route and secret name'
);
});

test('it prefills input if initialValue has value', async function (assert) {
await render(
hbs`<GetCredentialsCard @title={{this.title}} @renderInputSearch={{true}} @placeholder="secret/" @backend="kv" @type="secret" @initialValue="hello/"/>`
);
assert
.dom('[data-test-component="search-select"]')
.doesNotExist('does not render search select component');
assert.dom('[data-test-search-roles] input').hasValue('hello/', 'pre-fills search input');
assert.dom('[data-test-get-credentials]').isEnabled('submit button is enabled at render');
assert.dom('[data-test-get-credentials]').hasText('View list', 'Button has list CTA');
await typeIn('[data-test-search-roles] input', 'test');
assert
.dom('[data-test-get-credentials]')
.hasText('View secret', 'Button has view secret CTA after input');
await click('[data-test-get-credentials]');
assert.propEqual(
this.router.transitionTo.lastCall.args,
['vault.cluster.secrets.backend.show', 'hello/test'],
'transitionTo is called with correct route and secret name'
);
});

test('it goes to list route if input ends in / and type=secret', async function (assert) {
await render(
hbs`<GetCredentialsCard @title={{this.title}} @renderInputSearch={{true}} @placeholder="secret/" @backend="kv" @type="secret" />`
);
assert
.dom('[data-test-component="search-select"]')
.doesNotExist('does not render search select component');
await typeIn('[data-test-search-roles] input', 'test/');
assert.dom('[data-test-get-credentials]').hasText('View list', 'submit button has list CTA');
assert.dom('[data-test-get-credentials]').isEnabled('submit button is enabled at render');
await click('[data-test-get-credentials]');
assert.propEqual(
this.router.transitionTo.lastCall.args,
['vault.cluster.secrets.backend.list', 'test/'],
'transitionTo is called with correct route and secret name'
);
});
});