Filter Secret Engine List view by engineType and/or name (#20481)
* initial WIP glimmerize the controller
* wip got the filter engine type by supported backends working
* got filter by engine type working
* wip need to refactor but working ish for name
* wip working state with both filters, does not work if both fiters are set
* fixed when you have two selected filters, but broken for multiples of the same type with different names
* remove repeated engineTypes in filter list
* add disabled to power select
* fix bug of glimmer for the concurrency task.
* wording fix
* remove linkableItem and the nested contextual compnents to help with loading speed.
* add changelog
* fix some tests
* add test coverage
* Update 20481.txt
update changelog text
* test fixes 🤞
* test fix?
* address a pr comment and save
* address pr comment
This commit is contained in:
parent
57e2657c3e
commit
00e06301f1
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: Add filtering by engine type and engine name to the Secret Engine list view.
|
||||
```
|
|
@ -2,36 +2,76 @@
|
|||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { filterBy } from '@ember/object/computed';
|
||||
import { computed } from '@ember/object';
|
||||
/* eslint ember/no-computed-properties-in-native-classes: 'warn' */
|
||||
import Controller from '@ember/controller';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
|
||||
import { inject as service } from '@ember/service';
|
||||
const LINKED_BACKENDS = supportedSecretBackends();
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { filterBy } from '@ember/object/computed';
|
||||
import { dropTask } from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
flashMessages: service(),
|
||||
displayableBackends: filterBy('model', 'shouldIncludeInList'),
|
||||
export default class VaultClusterSecretsBackendController extends Controller {
|
||||
@service flashMessages;
|
||||
@filterBy('model', 'shouldIncludeInList') displayableBackends;
|
||||
|
||||
supportedBackends: computed('displayableBackends', 'displayableBackends.[]', function () {
|
||||
return (this.displayableBackends || [])
|
||||
.filter((backend) => LINKED_BACKENDS.includes(backend.get('engineType')))
|
||||
.sortBy('id');
|
||||
}),
|
||||
@tracked secretEngineOptions = [];
|
||||
@tracked selectedEngineType = null;
|
||||
@tracked selectedEngineName = null;
|
||||
|
||||
unsupportedBackends: computed(
|
||||
'displayableBackends',
|
||||
'displayableBackends.[]',
|
||||
'supportedBackends',
|
||||
'supportedBackends.[]',
|
||||
function () {
|
||||
return (this.displayableBackends || []).slice().removeObjects(this.supportedBackends).sortBy('id');
|
||||
get sortedDisplayableBackends() {
|
||||
// show supported secret engines first and then organize those by id.
|
||||
const sortedBackends = this.displayableBackends.sort(
|
||||
(a, b) => b.isSupportedBackend - a.isSupportedBackend || a.id - b.id
|
||||
);
|
||||
|
||||
// return an options list to filter by engine type, ex: 'kv'
|
||||
if (this.selectedEngineType) {
|
||||
// check first if the user has also filtered by name.
|
||||
if (this.selectedEngineName) {
|
||||
return sortedBackends.filter((backend) => this.selectedEngineName === backend.id);
|
||||
}
|
||||
// otherwise filter by engine type
|
||||
return sortedBackends.filter((backend) => this.selectedEngineType === backend.engineType);
|
||||
}
|
||||
),
|
||||
|
||||
disableEngine: task(function* (engine) {
|
||||
// return an options list to filter by engine name, ex: 'secret'
|
||||
if (this.selectedEngineName) {
|
||||
return sortedBackends.filter((backend) => this.selectedEngineName === backend.id);
|
||||
}
|
||||
// no filters, return full sorted list.
|
||||
return sortedBackends;
|
||||
}
|
||||
|
||||
get secretEngineArrayByType() {
|
||||
const arrayOfAllEngineTypes = this.sortedDisplayableBackends.map((modelObject) => modelObject.engineType);
|
||||
// filter out repeated engineTypes (e.g. [kv, kv] => [kv])
|
||||
const arrayOfUniqueEngineTypes = [...new Set(arrayOfAllEngineTypes)];
|
||||
|
||||
return arrayOfUniqueEngineTypes.map((engineType) => ({
|
||||
name: engineType,
|
||||
id: engineType,
|
||||
}));
|
||||
}
|
||||
|
||||
get secretEngineArrayByName() {
|
||||
return this.sortedDisplayableBackends.map((modelObject) => ({
|
||||
name: modelObject.id,
|
||||
id: modelObject.id,
|
||||
}));
|
||||
}
|
||||
|
||||
@action
|
||||
filterEngineType([type]) {
|
||||
this.selectedEngineType = type;
|
||||
}
|
||||
|
||||
@action
|
||||
filterEngineName([name]) {
|
||||
this.selectedEngineName = name;
|
||||
}
|
||||
|
||||
@dropTask
|
||||
*disableEngine(engine) {
|
||||
const { engineType, path } = engine;
|
||||
try {
|
||||
yield engine.destroyRecord();
|
||||
|
@ -41,5 +81,5 @@ export default Controller.extend({
|
|||
`There was an error disabling the ${engineType} Secrets Engine at ${path}: ${err.errors.join(' ')}.`
|
||||
);
|
||||
}
|
||||
}).drop(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import { computed } from '@ember/object'; // eslint-disable-line
|
|||
import { equal } from '@ember/object/computed'; // eslint-disable-line
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
|
||||
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
|
||||
|
||||
const LINKED_BACKENDS = supportedSecretBackends();
|
||||
|
||||
// identity will be managed separately and the inclusion
|
||||
// of the system backend is an implementation detail
|
||||
|
@ -143,6 +146,27 @@ export default class SecretEngineModel extends Model {
|
|||
return !LIST_EXCLUDED_BACKENDS.includes(this.engineType);
|
||||
}
|
||||
|
||||
get isSupportedBackend() {
|
||||
return LINKED_BACKENDS.includes(this.engineType);
|
||||
}
|
||||
|
||||
get backendLink() {
|
||||
if (this.engineType === 'kmip') {
|
||||
return 'vault.cluster.secrets.backend.kmip.scopes';
|
||||
}
|
||||
if (this.engineType === 'database') {
|
||||
return 'vault.cluster.secrets.backend.overview';
|
||||
}
|
||||
return 'vault.cluster.secrets.backend.list-root';
|
||||
}
|
||||
|
||||
get accessor() {
|
||||
if (this.version === 2) {
|
||||
return `v2 ${this.accessor}`;
|
||||
}
|
||||
return this.accessor;
|
||||
}
|
||||
|
||||
get localDisplay() {
|
||||
return this.local ? 'local' : 'replicated';
|
||||
}
|
||||
|
|
|
@ -7,106 +7,115 @@
|
|||
</PageHeader>
|
||||
|
||||
<Toolbar>
|
||||
<ToolbarFilters>
|
||||
<SearchSelect
|
||||
@id="filter-by-engine-type"
|
||||
@options={{this.secretEngineArrayByType}}
|
||||
@selectLimit="1"
|
||||
@disallowNewItems={{true}}
|
||||
@fallbackComponent="input-search"
|
||||
@onChange={{this.filterEngineType}}
|
||||
@placeholder={{"Filter by engine type"}}
|
||||
@displayInherit={{true}}
|
||||
@inputValue={{if this.selectedEngineType (array this.selectedEngineType)}}
|
||||
@disabled={{if this.selectedEngineName true false}}
|
||||
class="is-marginless"
|
||||
/>
|
||||
<SearchSelect
|
||||
@id="filter-by-engine-name"
|
||||
@options={{this.secretEngineArrayByName}}
|
||||
@selectLimit="1"
|
||||
@disallowNewItems={{true}}
|
||||
@fallbackComponent="input-search"
|
||||
@onChange={{this.filterEngineName}}
|
||||
@placeholder={{"Filter by engine name"}}
|
||||
@displayInherit={{true}}
|
||||
@inputValue={{if this.selectedEngineName (array this.selectedEngineName)}}
|
||||
class="is-marginless has-left-padding-s"
|
||||
/>
|
||||
</ToolbarFilters>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @route="vault.cluster.settings.mount-secret-backend" @type="add" data-test-enable-engine>
|
||||
Enable new engine
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
{{#each this.supportedBackends as |backend|}}
|
||||
{{#let
|
||||
(if
|
||||
(eq backend.engineType "kmip")
|
||||
"vault.cluster.secrets.backend.kmip.scopes"
|
||||
(if
|
||||
(eq backend.engineType "database") "vault.cluster.secrets.backend.overview" "vault.cluster.secrets.backend.list-root"
|
||||
)
|
||||
)
|
||||
as |backendLink|
|
||||
}}
|
||||
<LinkableItem data-test-secret-backend-row={{backend.id}} @link={{hash route=backendLink model=backend.id}} as |Li|>
|
||||
<Li.content
|
||||
@accessor={{if (eq backend.version 2) (concat "v2 " backend.accessor) backend.accessor}}
|
||||
@description={{backend.description}}
|
||||
@glyphText={{backend.engineType}}
|
||||
@glyph={{backend.icon}}
|
||||
@link={{hash route=backendLink model=backend.id}}
|
||||
@title={{backend.path}}
|
||||
/>
|
||||
<Li.menu>
|
||||
<PopupMenu @name="engine-menu">
|
||||
<Confirm as |c|>
|
||||
<nav class="menu" aria-label="supported secrets engine menu">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.secrets.backend.configuration" @model={{backend.id}}>
|
||||
View configuration
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if (not-eq backend.type "cubbyhole")}}
|
||||
<li class="action">
|
||||
<c.Message
|
||||
@id={{backend.id}}
|
||||
@triggerText="Disable"
|
||||
@message="Any data in this engine will be permanently deleted."
|
||||
@title="Disable engine?"
|
||||
@confirmButtonText="Disable"
|
||||
@onConfirm={{perform this.disableEngine backend}}
|
||||
data-test-engine-disable="true"
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if this.item.updatePath.isPending}}
|
||||
<li class="action">
|
||||
<button disabled type="button" class="link button is-loading is-transparent">
|
||||
loading
|
||||
</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</Confirm>
|
||||
</PopupMenu>
|
||||
</Li.menu>
|
||||
</LinkableItem>
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
|
||||
{{#each this.unsupportedBackends as |backend|}}
|
||||
<LinkableItem data-test-secret-backend-row={{backend.id}} @disabled={{true}} as |Li|>
|
||||
<Li.content
|
||||
@accessor={{if (eq backend.version 2) (concat "v2 " backend.accessor) backend.accessor}}
|
||||
@description={{backend.description}}
|
||||
@glyphText={{backend.engineType}}
|
||||
@glyph={{or (if (eq backend.engineType "kmip") "secrets" backend.engineType) "secrets"}}
|
||||
@title={{backend.path}}
|
||||
/>
|
||||
<Li.menu>
|
||||
<PopupMenu name="engine-menu">
|
||||
{{#each this.sortedDisplayableBackends as |backend|}}
|
||||
<LinkedBlock
|
||||
@params={{array backend.backendLink backend.id}}
|
||||
class="list-item-row linkable-item is-no-underline"
|
||||
data-test-auth-backend-link={{backend.id}}
|
||||
@disabled={{if backend.isSupportedBackend false true}}
|
||||
>
|
||||
<div class="linkable-item-content" data-test-linkable-item-content>
|
||||
<div class="has-text-grey">
|
||||
{{#if backend.icon}}
|
||||
<ToolTip @horizontalPosition="left" as |T|>
|
||||
<T.Trigger>
|
||||
<Icon @name={{backend.icon}} class="has-text-grey-light" data-test-linkable-item-glyph />
|
||||
</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 data-test-secret-path>{{backend.path}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if backend.accessor}}
|
||||
<code class="has-text-grey is-size-8" data-test-linkable-item-accessor>
|
||||
{{backend.accessor}}
|
||||
</code>
|
||||
{{/if}}
|
||||
{{#if backend.description}}
|
||||
<ReadMore data-test-linkable-item-description>
|
||||
{{backend.description}}
|
||||
</ReadMore>
|
||||
{{/if}}
|
||||
{{yield}}
|
||||
</div>
|
||||
{{! meatball sandwich menu }}
|
||||
<div class="linkable-item-menu" data-test-linkable-item-menu={{backend.path}}>
|
||||
<PopupMenu @name="engine-menu">
|
||||
<Confirm as |c|>
|
||||
<nav class="menu" aria-label="unsupported secrets engine menu">
|
||||
<nav class="menu" aria-label="{{if backend.isSupportedBackend 'supported' 'unsupported'}} secrets engine menu">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<LinkTo @route="vault.cluster.secrets.backend.configuration" @model={{backend.id}} data-test-engine-config>
|
||||
View configuration
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<c.Message
|
||||
@id={{backend.id}}
|
||||
@triggerText="Disable"
|
||||
@message="Any data in this engine will be permanently deleted."
|
||||
@title="Disable engine?"
|
||||
@confirmButtonText="Disable"
|
||||
@onConfirm={{perform this.disableEngine backend}}
|
||||
data-test-engine-disable="true"
|
||||
/>
|
||||
</li>
|
||||
{{#if (not-eq backend.type "cubbyhole")}}
|
||||
<li class="action">
|
||||
<c.Message
|
||||
@id={{backend.id}}
|
||||
@triggerText="Disable"
|
||||
@message="Any data in this engine will be permanently deleted."
|
||||
@title="Disable engine?"
|
||||
@confirmButtonText="Disable"
|
||||
@onConfirm={{perform this.disableEngine backend}}
|
||||
data-test-engine-disable="true"
|
||||
/>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</Confirm>
|
||||
</PopupMenu>
|
||||
</Li.menu>
|
||||
</LinkableItem>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
{{/each}}
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import layout from '../templates/components/linkable-item';
|
||||
import { setComponentTemplate } from '@ember/component';
|
||||
|
||||
/**
|
||||
* @module LinkableItem
|
||||
* LinkableItem components have two contextual components, a Content component used to show information on the left with a Menu component on the right, all aligned vertically centered. If passed a link, the block will be clickable.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <LinkableItem @link={{hash route='vault.backends' model='my-backend-path'}} data-test-row="my-backend-path">
|
||||
* // Use <LinkableItem.content> and <LinkableItem.menu> here
|
||||
* </LinkableItem>
|
||||
* ```
|
||||
*
|
||||
* @param {object} [link=null] - Link should have route and model
|
||||
* @param {boolean} [disabled=false] - If no link then should be given a disabled attribute equal to true
|
||||
*/
|
||||
|
||||
/* eslint ember/no-empty-glimmer-component-classes: 'warn' */
|
||||
class LinkableItemComponent extends Component {}
|
||||
|
||||
export default setComponentTemplate(layout, LinkableItemComponent);
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import layout from '../../templates/components/linkable-item/content';
|
||||
import { setComponentTemplate } from '@ember/component';
|
||||
|
||||
/**
|
||||
* @module Content
|
||||
* Content components are contextual components of LinkableItem, used to display content on the left side of a LinkableItem component.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <LinkableItem as |Li|>
|
||||
* <Li.content
|
||||
* @accessor="cubbyhole_e21f8ee6"
|
||||
* @description="per-token private secret storage"
|
||||
* @glyphText="tooltip text"
|
||||
* @glyph=glyph
|
||||
* @title="title"
|
||||
* />
|
||||
* </LinkableItem>
|
||||
* ```
|
||||
* @param {string} accessor=null - formatted as HTML <code> tag
|
||||
* @param {string} description=null - will truncate if wider than parent div
|
||||
* @param {string} glyphText=null - tooltip for glyph
|
||||
* @param {string} glyph=null - will display as icon beside the title
|
||||
* @param {string} title=null - if @link object is passed in then title will link to @link.route
|
||||
*/
|
||||
|
||||
/* eslint ember/no-empty-glimmer-component-classes: 'warn' */
|
||||
class ContentComponent extends Component {}
|
||||
|
||||
export default setComponentTemplate(layout, ContentComponent);
|
|
@ -1,27 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import layout from '../../templates/components/linkable-item/menu';
|
||||
import { setComponentTemplate } from '@ember/component';
|
||||
|
||||
/**
|
||||
* @module Menu
|
||||
* Menu components are contextual components of LinkableItem, used to display a menu on the right side of a LinkableItem component.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <LinkableItem as |Li|>
|
||||
* <Li.menu>
|
||||
* Some menu here
|
||||
* </Li.menu>
|
||||
* </LinkableItem>
|
||||
* ```
|
||||
*/
|
||||
|
||||
/* eslint ember/no-empty-glimmer-component-classes: 'warn' */
|
||||
class MenuComponent extends Component {}
|
||||
|
||||
export default setComponentTemplate(layout, MenuComponent);
|
|
@ -39,6 +39,7 @@
|
|||
@onChange={{this.selectOrCreate}}
|
||||
@placeholderComponent={{component "search-select-placeholder"}}
|
||||
@verticalPosition="below"
|
||||
@disabled={{@disabled}}
|
||||
as |option|
|
||||
>
|
||||
{{#if this.shouldRenderName}}
|
||||
|
|
|
@ -53,6 +53,7 @@ import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-ut
|
|||
* @param {string} [placeholder] - text you wish to replace the default "search" with
|
||||
* @param {boolean} [displayInherit=false] - if you need the search select component to display inherit instead of box.
|
||||
* @param {function} [renderInfoTooltip] - receives each inputValue string and list of dropdownOptions as args, so parent can determine when to render a tooltip beside a selectedOption and the tooltip text. see 'oidc/provider-form.js'
|
||||
* @param {boolean} [disabled] - if true sets the disabled property on the ember-power-select component and makes it unusable.
|
||||
*
|
||||
// * advanced customization
|
||||
* @param {Array} options - array of objects passed directly to the power-select component. If doing this, `models` should not also be passed as that will overwrite the
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<div ...attributes>
|
||||
{{#if @disabled}}
|
||||
<div class="list-item-row linkable-item is-no-underline">
|
||||
{{yield (hash content=(component "linkable-item/content"))}}
|
||||
{{yield (hash menu=(component "linkable-item/menu"))}}
|
||||
</div>
|
||||
{{else}}
|
||||
<LinkedBlock
|
||||
@params={{array @link.route @link.model}}
|
||||
class="list-item-row linkable-item is-no-underline"
|
||||
data-test-auth-backend-link={{@link.model}}
|
||||
>
|
||||
{{yield (hash content=(component "linkable-item/content"))}}
|
||||
{{yield (hash menu=(component "linkable-item/menu"))}}
|
||||
</LinkedBlock>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,44 +0,0 @@
|
|||
<div class="linkable-item-content" data-test-linkable-item-content ...attributes>
|
||||
<div class="has-text-grey">
|
||||
{{#if @glyph}}
|
||||
<ToolTip @horizontalPosition="left" as |T|>
|
||||
<T.Trigger>
|
||||
<Icon @name={{@glyph}} class="has-text-grey-light" data-test-linkable-item-glyph />
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box">
|
||||
{{or @glyphText @title}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
{{/if}}
|
||||
|
||||
{{#if @title}}
|
||||
{{#if @link}}
|
||||
<LinkTo
|
||||
@route={{@link.route}}
|
||||
@model={{@link.model}}
|
||||
class="has-text-black has-text-weight-semibold"
|
||||
data-test-secret-path
|
||||
>
|
||||
{{@title}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<span data-test-secret-path>{{@title}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if @accessor}}
|
||||
<code class="has-text-grey is-size-8" data-test-linkable-item-accessor>
|
||||
{{@accessor}}
|
||||
</code>
|
||||
{{/if}}
|
||||
|
||||
{{#if @description}}
|
||||
<ReadMore data-test-linkable-item-description>
|
||||
{{@description}}
|
||||
</ReadMore>
|
||||
{{/if}}
|
||||
|
||||
{{yield}}
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
<div class="linkable-item-menu" data-test-linkable-item-menu ...attributes>
|
||||
{{yield}}
|
||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export { default } from 'core/components/linkable-item';
|
|
@ -4,15 +4,21 @@
|
|||
*/
|
||||
|
||||
import { currentRouteName, settled } from '@ember/test-helpers';
|
||||
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import { module, test } from 'qunit';
|
||||
import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import backendsPage from 'vault/tests/pages/secrets/backends';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import ss from 'vault/tests/pages/components/search-select';
|
||||
|
||||
module('Acceptance | engine/disable', function (hooks) {
|
||||
const searchSelect = create(ss);
|
||||
|
||||
module('Acceptance | secret-engine list view', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
|
@ -20,7 +26,7 @@ module('Acceptance | engine/disable', function (hooks) {
|
|||
return authPage.login();
|
||||
});
|
||||
|
||||
test('disable engine', async function (assert) {
|
||||
test('it allows you to disable an engine', async function (assert) {
|
||||
// first mount an engine so we can disable it.
|
||||
const enginePath = `alicloud-disable-${this.uid}`;
|
||||
await mountSecrets.enable('alicloud', enginePath);
|
||||
|
@ -41,11 +47,68 @@ module('Acceptance | engine/disable', function (hooks) {
|
|||
'vault.cluster.secrets.backends',
|
||||
'redirects to the backends page'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
backendsPage.rows.filterBy('path', `${enginePath}/`).length,
|
||||
0,
|
||||
'does not show the disabled engine'
|
||||
);
|
||||
});
|
||||
|
||||
test('it adds disabled css styling to unsupported secret engines', async function (assert) {
|
||||
assert.expect(2);
|
||||
// first mount engine that is not supported
|
||||
const enginePath = `nomad-${this.uid}`;
|
||||
|
||||
await mountSecrets.enable('nomad', enginePath);
|
||||
await settled();
|
||||
await backendsPage.visit();
|
||||
await settled();
|
||||
|
||||
const rows = document.querySelectorAll('[data-test-auth-backend-link]');
|
||||
const rowUnsupported = Array.from(rows).filter((row) => row.innerText.includes('nomad'));
|
||||
const rowSupported = Array.from(rows).filter((row) => row.innerText.includes('cubbyhole'));
|
||||
assert
|
||||
.dom(rowUnsupported[0])
|
||||
.doesNotHaveClass(
|
||||
'linked-block',
|
||||
`the linked-block class is not added to unsupported engines, which effectively disables it.`
|
||||
);
|
||||
assert.dom(rowSupported[0]).hasClass('linked-block', `linked-block class is added to supported engines.`);
|
||||
|
||||
// cleanup
|
||||
await runCommands([`delete sys/mounts/${enginePath}`]);
|
||||
});
|
||||
|
||||
test('it filters by name and engine type', async function (assert) {
|
||||
assert.expect(3);
|
||||
const enginePath1 = `aws-1-${this.uid}`;
|
||||
const enginePath2 = `aws-2-${this.uid}`;
|
||||
|
||||
await mountSecrets.enable('aws', enginePath1);
|
||||
await mountSecrets.enable('aws', enginePath2);
|
||||
await backendsPage.visit();
|
||||
await settled();
|
||||
// filter by type
|
||||
await clickTrigger('#filter-by-engine-type');
|
||||
await searchSelect.options.objectAt(0).click();
|
||||
|
||||
const rows = document.querySelectorAll('[data-test-auth-backend-link]');
|
||||
const rowsAws = Array.from(rows).filter((row) => row.innerText.includes('aws'));
|
||||
|
||||
assert.strictEqual(rows.length, rowsAws.length, 'all rows returned are aws');
|
||||
// filter by name
|
||||
await clickTrigger('#filter-by-engine-name');
|
||||
await searchSelect.options.objectAt(1).click();
|
||||
const singleRow = document.querySelectorAll('[data-test-auth-backend-link]');
|
||||
|
||||
assert.dom(singleRow[0]).includesText('aws-2', 'shows the filtered by name engine');
|
||||
// clear filter by engine name
|
||||
await searchSelect.deleteButtons.objectAt(1).click();
|
||||
const rowsAgain = document.querySelectorAll('[data-test-auth-backend-link]');
|
||||
assert.ok(rowsAgain.length > 1, 'filter has been removed');
|
||||
|
||||
// cleanup
|
||||
await runCommands([`delete sys/mounts/${enginePath1}`]);
|
||||
await runCommands([`delete sys/mounts/${enginePath2}`]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -126,7 +126,7 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
|
|||
await page.secretList();
|
||||
await settled();
|
||||
assert
|
||||
.dom(`[data-test-secret-backend-row=${path}]`)
|
||||
.dom(`[data-test-auth-backend-link=${path}]`)
|
||||
.exists({ count: 1 }, 'renders only one instance of the engine');
|
||||
});
|
||||
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | linkable-item', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders anything passed in', async function (assert) {
|
||||
await render(hbs`<LinkableItem />`);
|
||||
assert.dom(this.element).hasText('', 'No content rendered');
|
||||
|
||||
await render(hbs`
|
||||
<LinkableItem as |Li|>
|
||||
<Li.content>
|
||||
stuff here
|
||||
</Li.content>
|
||||
<Li.menu>
|
||||
menu
|
||||
</Li.menu>
|
||||
</LinkableItem>
|
||||
`);
|
||||
assert.dom('[data-test-linkable-item-content]').hasText('stuff here');
|
||||
assert.dom('[data-test-linkable-item-menu]').hasText('menu');
|
||||
});
|
||||
|
||||
test('it is not wrapped in a linked block if disabled is true', async function (assert) {
|
||||
await render(hbs`
|
||||
<LinkableItem @disabled={{true}} as |Li|>
|
||||
<Li.content>
|
||||
stuff here
|
||||
</Li.content>
|
||||
</LinkableItem>
|
||||
`);
|
||||
assert.dom('.list-item-row').exists('List item row exists');
|
||||
assert.dom('.list-item-row.linked-block').doesNotExist('Does not render linked block');
|
||||
assert.dom('[data-test-secret-path]').doesNotExist('Title is not rendered');
|
||||
assert.dom('[data-test-linkable-item-accessor]').doesNotExist('Accessor is not rendered');
|
||||
assert.dom('[data-test-linkable-item-accessor]').doesNotExist('Accessor is not rendered');
|
||||
assert.dom('[data-test-linkable-item-glyph]').doesNotExist('Glyph is not rendered');
|
||||
});
|
||||
|
||||
test('it is wrapped in a linked block if a link is passed', async function (assert) {
|
||||
await render(hbs`
|
||||
<LinkableItem @link={{hash route="vault" model="modelId"}} as |Li|>
|
||||
<Li.content
|
||||
@title="A title"
|
||||
@link={{hash route="vault" model="modelId"}}
|
||||
>
|
||||
stuff here
|
||||
</Li.content>
|
||||
</LinkableItem>
|
||||
`);
|
||||
|
||||
assert.dom('.list-item-row.linked-block').exists('Renders linked block');
|
||||
});
|
||||
|
||||
test('it renders standard attributes on content', async function (assert) {
|
||||
this.set('title', 'A Title');
|
||||
this.set('accessor', 'my accessor');
|
||||
this.set('description', 'my description');
|
||||
this.set('glyph', 'key');
|
||||
this.set('glyphText', 'Here is some extra info');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<LinkableItem data-test-example as |Li|>
|
||||
<Li.content
|
||||
@accessor={{this.accessor}}
|
||||
@description={{this.description}}
|
||||
@glyph={{this.glyph}}
|
||||
@glyphText={{this.glyphText}}
|
||||
@title={{this.title}}
|
||||
/>
|
||||
</LinkableItem>
|
||||
`);
|
||||
assert.dom('.list-item-row').exists('List item row exists');
|
||||
assert.dom('[data-test-secret-path]').hasText(this.title, 'Title is rendered');
|
||||
assert.dom('[data-test-linkable-item-accessor]').hasText(this.accessor, 'Accessor is rendered');
|
||||
assert.dom('[data-test-linkable-item-description]').hasText(this.description, 'Description is rendered');
|
||||
assert.dom('[data-test-linkable-item-glyph]').exists('Glyph is rendered');
|
||||
});
|
||||
});
|
|
@ -9,7 +9,7 @@ import uiPanel from 'vault/tests/pages/components/console/ui-panel';
|
|||
export default create({
|
||||
consoleToggle: clickable('[data-test-console-toggle]'),
|
||||
visit: visitable('/vault/secrets'),
|
||||
rows: collection('[data-test-secret-backend-row]', {
|
||||
rows: collection('[data-test-auth-backend-link]', {
|
||||
path: text('[data-test-secret-path]'),
|
||||
menu: clickable('[data-test-popup-menu-trigger]'),
|
||||
}),
|
||||
|
|
Loading…
Reference in New Issue