mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2026-01-09 13:41:56 +00:00
UI: [VAULT-19560] Add empty states and tests (#22690)
This commit is contained in:
@@ -101,13 +101,13 @@ export default class DashboardQuickActionsCard extends Component {
|
||||
}
|
||||
|
||||
get filteredSecretEngines() {
|
||||
return this.args.secretsEngines.filter(
|
||||
return this.args.secretsEngines?.filter(
|
||||
(engine) => (engine.type === 'kv' && engine.version == 2) || QUICK_ACTION_ENGINES.includes(engine.type)
|
||||
);
|
||||
}
|
||||
|
||||
get mountOptions() {
|
||||
return this.filteredSecretEngines.map((engine) => {
|
||||
return this.filteredSecretEngines?.map((engine) => {
|
||||
const { id, type } = engine;
|
||||
|
||||
return { name: id, type, id };
|
||||
|
||||
@@ -18,14 +18,10 @@ import Component from '@glimmer/component';
|
||||
|
||||
export default class DashboardSecretsEnginesCard extends Component {
|
||||
get filteredSecretsEngines() {
|
||||
const filteredEngines = this.args.secretsEngines.filter(
|
||||
(secretEngine) => secretEngine.shouldIncludeInList
|
||||
);
|
||||
|
||||
return filteredEngines;
|
||||
return this.args.secretsEngines?.filter((secretEngine) => secretEngine.shouldIncludeInList);
|
||||
}
|
||||
|
||||
get firstFiveSecretsEngines() {
|
||||
return this.filteredSecretsEngines.slice(0, 5);
|
||||
return this.filteredSecretsEngines?.slice(0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,87 @@
|
||||
<Hds::Card::Container @hasBorder={{true}} class="has-padding-l" data-test-card="quick-actions">
|
||||
<h3 class="title is-4">Quick actions</h3>
|
||||
<div class="has-top-margin-m has-bottom-margin-m">
|
||||
<h4 class="title is-marginless is-6">Secrets engines</h4>
|
||||
<p class="is-size-8 has-top-margin-xxs has-bottom-margin-s has-text-grey">Supported engines include databases, KV version
|
||||
2, and PKI.</p>
|
||||
<SearchSelect
|
||||
@id="secrets-engines-select"
|
||||
@options={{this.mountOptions}}
|
||||
@selectLimit="1"
|
||||
@disallowNewItems={{true}}
|
||||
@fallbackComponent="input-search"
|
||||
@onChange={{this.handleSearchEngineSelect}}
|
||||
@placeholder="Type to select a mount"
|
||||
@displayInherit={{true}}
|
||||
@shouldRenderName={{true}}
|
||||
@passObject={{true}}
|
||||
@objectKeys={{array "type" "version"}}
|
||||
class="is-marginless"
|
||||
data-test-secrets-engines-select
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if this.selectedEngine}}
|
||||
<h4 class="title is-6">Action</h4>
|
||||
<Select
|
||||
@name="action-select"
|
||||
@options={{this.actionOptions}}
|
||||
@isFullwidth={{true}}
|
||||
@selectedValue={{this.selectedAction}}
|
||||
@onChange={{this.setSelectedAction}}
|
||||
@noDefault={{true}}
|
||||
/>
|
||||
|
||||
{{#if this.searchSelectParams.model}}
|
||||
<h4 class="title is-6" data-test-search-select-params-title>{{this.searchSelectParams.title}}</h4>
|
||||
|
||||
{{#if this.filteredSecretEngines}}
|
||||
<div class="has-top-margin-m has-bottom-margin-m">
|
||||
<h4 class="title is-marginless is-6">Secrets engines</h4>
|
||||
<p class="is-size-8 has-top-margin-xxs has-bottom-margin-s has-text-grey">Supported engines include databases, KV
|
||||
version 2, and PKI.</p>
|
||||
<SearchSelect
|
||||
class="is-flex-grow-1"
|
||||
@id="secrets-engines-select"
|
||||
@options={{this.mountOptions}}
|
||||
@selectLimit="1"
|
||||
@models={{array this.searchSelectParams.model}}
|
||||
@placeholder={{this.searchSelectParams.placeholder}}
|
||||
@disallowNewItems={{true}}
|
||||
@onChange={{this.handleActionSelect}}
|
||||
@fallbackComponent="input-search"
|
||||
@disabled={{not this.searchSelectParams.model}}
|
||||
@nameKey={{this.searchSelectParams.nameKey}}
|
||||
@queryObject={{this.searchSelectParams.queryObject}}
|
||||
@objectKeys={{this.searchSelectParams.objectKeys}}
|
||||
@onChange={{this.handleSearchEngineSelect}}
|
||||
@placeholder="Type to select a mount"
|
||||
@displayInherit={{true}}
|
||||
@shouldRenderName={{true}}
|
||||
@passObject={{true}}
|
||||
@shouldRenderName={{this.searchSelectParams.nameKey}}
|
||||
data-test-param-select
|
||||
@objectKeys={{array "type" "version"}}
|
||||
class="is-marginless"
|
||||
data-test-secrets-engines-select
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if this.selectedEngine}}
|
||||
<h4 class="title is-6">Action</h4>
|
||||
<Select
|
||||
@name="action-select"
|
||||
@options={{this.actionOptions}}
|
||||
@isFullwidth={{true}}
|
||||
@selectedValue={{this.selectedAction}}
|
||||
@onChange={{this.setSelectedAction}}
|
||||
@noDefault={{true}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary has-top-margin-m"
|
||||
disabled={{(not (and this.selectedAction this.selectedEngine this.paramValue))}}
|
||||
{{on "click" this.navigateToPage}}
|
||||
data-test-button={{this.searchSelectParams.buttonText}}
|
||||
>
|
||||
{{this.searchSelectParams.buttonText}}
|
||||
</button>
|
||||
</div>
|
||||
{{#if this.searchSelectParams.model}}
|
||||
<h4 class="title is-6" data-test-search-select-params-title>{{this.searchSelectParams.title}}</h4>
|
||||
|
||||
<SearchSelect
|
||||
class="is-flex-grow-1"
|
||||
@selectLimit="1"
|
||||
@models={{array this.searchSelectParams.model}}
|
||||
@placeholder={{this.searchSelectParams.placeholder}}
|
||||
@disallowNewItems={{true}}
|
||||
@onChange={{this.handleActionSelect}}
|
||||
@fallbackComponent="input-search"
|
||||
@disabled={{not this.searchSelectParams.model}}
|
||||
@nameKey={{this.searchSelectParams.nameKey}}
|
||||
@queryObject={{this.searchSelectParams.queryObject}}
|
||||
@objectKeys={{this.searchSelectParams.objectKeys}}
|
||||
@passObject={{true}}
|
||||
@shouldRenderName={{this.searchSelectParams.nameKey}}
|
||||
data-test-param-select
|
||||
/>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary has-top-margin-m"
|
||||
disabled={{(not (and this.selectedAction this.selectedEngine this.paramValue))}}
|
||||
{{on "click" this.navigateToPage}}
|
||||
data-test-button={{this.searchSelectParams.buttonText}}
|
||||
>
|
||||
{{this.searchSelectParams.buttonText}}
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="No mount selected"
|
||||
@message="Select a mount above to get started."
|
||||
data-test-no-mount-selected-empty
|
||||
/>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
|
||||
<EmptyState
|
||||
@title="No mount selected"
|
||||
@message="Select a mount above to get started."
|
||||
data-test-no-mount-selected-empty
|
||||
/>
|
||||
@title="Welcome to quick actions"
|
||||
@message="Access secret engine actions easily. Enable a compatible secret engine (such as database, KV version 2, or PKI) to get started."
|
||||
data-test-empty-state="quick-actions"
|
||||
>
|
||||
<div>
|
||||
<LinkTo @route="vault.cluster.settings.mount-secret-backend">Enable a secret engine</LinkTo>
|
||||
</div>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
</Hds::Card::Container>
|
||||
@@ -1,80 +1,100 @@
|
||||
<Hds::Card::Container @hasBorder={{true}} class="has-padding-l secrets-engines-card" data-test-card="secrets-engines">
|
||||
<Hds::Card::Container
|
||||
@hasBorder={{true}}
|
||||
class="has-padding-l {{if this.filteredSecretsEngines 'secrets-engines-card'}}"
|
||||
data-test-card="secrets-engines"
|
||||
>
|
||||
<div class="is-flex-between">
|
||||
<h3 class="title is-4 has-left-margin-xxs" data-test-dashboard-secrets-engines-header>Secrets engines</h3>
|
||||
|
||||
<LinkTo class="is-no-underline has-right-margin-xxs" @route="vault.cluster.secrets.backends">
|
||||
Details
|
||||
</LinkTo>
|
||||
{{#if this.filteredSecretsEngines}}
|
||||
<LinkTo class="is-no-underline has-right-margin-xxs" @route="vault.cluster.secrets.backends">
|
||||
Details
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<Hds::Table @caption="Five secrets engines" class="is-border-spacing-revert" data-test-dashboard-secrets-engines-table>
|
||||
<:body as |B|>
|
||||
{{#each this.firstFiveSecretsEngines as |backend|}}
|
||||
<B.Tr data-test-secrets-engines-row={{backend.id}}>
|
||||
<B.Td class="is-flex-between is-flex-center has-gap-m">
|
||||
<div>
|
||||
<div class="is-flex-center">
|
||||
{{#if backend.icon}}
|
||||
<ToolTip @horizontalPosition="left" as |T|>
|
||||
<T.Trigger>
|
||||
<Icon @name={{backend.icon}} class={{unless backend.isSupportedBackend "has-text-grey"}} />
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box">
|
||||
{{or backend.engineType backend.path}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
{{/if}}
|
||||
{{#if backend.path}}
|
||||
{{#if backend.isSupportedBackend}}
|
||||
<LinkTo
|
||||
@route={{backend.backendLink}}
|
||||
@model={{backend.id}}
|
||||
class="has-text-black has-text-weight-semibold"
|
||||
data-test-secret-path
|
||||
>
|
||||
{{backend.path}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<span class="has-text-grey" data-test-secret-path>{{backend.path}}</span>
|
||||
{{#if this.filteredSecretsEngines}}
|
||||
<Hds::Table @caption="Five secrets engines" class="is-border-spacing-revert" data-test-dashboard-secrets-engines-table>
|
||||
<:body as |B|>
|
||||
{{#each this.firstFiveSecretsEngines as |backend|}}
|
||||
<B.Tr data-test-secrets-engines-row={{backend.id}}>
|
||||
<B.Td class="is-flex-between is-flex-center has-gap-m">
|
||||
<div>
|
||||
<div class="is-flex-center">
|
||||
{{#if backend.icon}}
|
||||
<ToolTip @horizontalPosition="left" as |T|>
|
||||
<T.Trigger>
|
||||
<Icon @name={{backend.icon}} class={{unless backend.isSupportedBackend "has-text-grey"}} />
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box">
|
||||
{{or backend.engineType backend.path}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
{{/if}}
|
||||
{{#if backend.path}}
|
||||
{{#if backend.isSupportedBackend}}
|
||||
<LinkTo
|
||||
@route={{backend.backendLink}}
|
||||
@model={{backend.id}}
|
||||
class="has-text-black has-text-weight-semibold"
|
||||
data-test-secret-path
|
||||
>
|
||||
{{backend.path}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<span class="has-text-grey" data-test-secret-path>{{backend.path}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if backend.accessor}}
|
||||
<code class="has-text-grey is-size-8" data-test-accessor>
|
||||
{{backend.accessor}}
|
||||
</code>
|
||||
{{/if}}
|
||||
{{#if backend.description}}
|
||||
<div data-test-description class="truncate-first-line">
|
||||
{{backend.description}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if backend.accessor}}
|
||||
<code class="has-text-grey is-size-8" data-test-accessor>
|
||||
{{backend.accessor}}
|
||||
</code>
|
||||
{{#if backend.isSupportedBackend}}
|
||||
<LinkTo
|
||||
@route={{backend.backendLink}}
|
||||
@model={{backend.id}}
|
||||
class="has-text-weight-semibold is-no-underline"
|
||||
data-test-view
|
||||
>
|
||||
View
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
{{#if backend.description}}
|
||||
<div data-test-description class="truncate-first-line">
|
||||
{{backend.description}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if backend.isSupportedBackend}}
|
||||
<LinkTo
|
||||
@route={{backend.backendLink}}
|
||||
@model={{backend.id}}
|
||||
class="has-text-weight-semibold is-no-underline"
|
||||
data-test-view
|
||||
>
|
||||
View
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
{{/each}}
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
{{/each}}
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
|
||||
{{#if (gt this.filteredSecretsEngines.length 5)}}
|
||||
<p class="is-size-9 has-top-margin-xs has-text-grey" data-test-secrets-engine-total-help-text>
|
||||
Showing 5 out of
|
||||
{{this.filteredSecretsEngines.length}}
|
||||
secret engines. Navigate to
|
||||
<Hds::Link::Inline @route="vault.cluster.secrets.backends">details</Hds::Link::Inline>
|
||||
to view more.
|
||||
</p>
|
||||
{{#if (gt this.filteredSecretsEngines.length 5)}}
|
||||
<p class="is-size-9 has-top-margin-xs has-text-grey" data-test-secrets-engine-total-help-text>
|
||||
Showing 5 out of
|
||||
{{this.filteredSecretsEngines.length}}
|
||||
secret engines. Navigate to
|
||||
<Hds::Link::Inline @route="vault.cluster.secrets.backends">details</Hds::Link::Inline>
|
||||
to view more.
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="No secrets engines enabled"
|
||||
@message="Secret engines will be listed here. Enable a secret engine to get started."
|
||||
class="has-top-margin-m"
|
||||
data-test-empty-state="secrets-engines"
|
||||
>
|
||||
<div>
|
||||
<LinkTo @route="vault.cluster.settings.mount-secret-backend">Enable a secret engine</LinkTo>
|
||||
</div>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
</Hds::Card::Container>
|
||||
@@ -1,3 +1,4 @@
|
||||
export const SELECTORS = {
|
||||
cardName: (name) => `[data-test-card="${name}"]`,
|
||||
emptyState: (name) => `[data-test-empty-state="${name}"]`,
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ module('Integration | Component | dashboard/overview', function (hooks) {
|
||||
performance: {
|
||||
clusterId: 'abc-1',
|
||||
state: 'running',
|
||||
isPrimary: true,
|
||||
},
|
||||
};
|
||||
this.store.pushPayload('secret-engine', {
|
||||
@@ -61,6 +62,29 @@ module('Integration | Component | dashboard/overview', function (hooks) {
|
||||
this.refreshModel = () => {};
|
||||
});
|
||||
|
||||
test('it should show dashboard empty states', async function (assert) {
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.version.version = '1.13.1';
|
||||
this.isRootNamespace = true;
|
||||
await render(
|
||||
hbs`
|
||||
<Dashboard::Overview
|
||||
@version={{this.version}}
|
||||
@isRootNamespace={{this.isRootNamespace}}
|
||||
@refreshModel={{this.refreshModel}} />
|
||||
`
|
||||
);
|
||||
assert.dom('[data-test-dashboard-version-header]').exists();
|
||||
assert.dom(SELECTORS.cardName('secrets-engines')).exists();
|
||||
assert.dom(SELECTORS.emptyState('secrets-engines')).exists();
|
||||
assert.dom(SELECTORS.cardName('learn-more')).exists();
|
||||
assert.dom(SELECTORS.cardName('quick-actions')).exists();
|
||||
assert.dom(SELECTORS.emptyState('quick-actions')).exists();
|
||||
assert.dom(SELECTORS.cardName('configuration-details')).doesNotExist();
|
||||
assert.dom(SELECTORS.cardName('replication')).doesNotExist();
|
||||
assert.dom(SELECTORS.cardName('client-count')).doesNotExist();
|
||||
});
|
||||
|
||||
test('it should hide client count and replication card on community', async function (assert) {
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.version.version = '1.13.1';
|
||||
@@ -135,6 +159,7 @@ module('Integration | Component | dashboard/overview', function (hooks) {
|
||||
);
|
||||
|
||||
assert.dom('[data-test-dashboard-version-header]').exists();
|
||||
assert.dom('[data-test-badge-namespace]').exists();
|
||||
assert.dom(SELECTORS.cardName('secrets-engines')).exists();
|
||||
assert.dom(SELECTORS.cardName('learn-more')).exists();
|
||||
assert.dom(SELECTORS.cardName('quick-actions')).exists();
|
||||
@@ -166,6 +191,7 @@ module('Integration | Component | dashboard/overview', function (hooks) {
|
||||
);
|
||||
|
||||
assert.dom('[data-test-dashboard-version-header]').exists();
|
||||
assert.dom('[data-test-badge-namespace]').exists();
|
||||
assert.dom(SELECTORS.cardName('secrets-engines')).exists();
|
||||
assert.dom(SELECTORS.cardName('learn-more')).exists();
|
||||
assert.dom(SELECTORS.cardName('quick-actions')).exists();
|
||||
|
||||
Reference in New Issue
Block a user