diff --git a/changelog/27178.txt b/changelog/27178.txt
new file mode 100644
index 0000000000..c84c67f34e
--- /dev/null
+++ b/changelog/27178.txt
@@ -0,0 +1,3 @@
+```release-note:change
+ui/kubernetes: Update the roles filter-input to use explicit search.
+```
diff --git a/ui/lib/core/addon/components/filter-input-explicit.hbs b/ui/lib/core/addon/components/filter-input-explicit.hbs
new file mode 100644
index 0000000000..2cf1f2ed29
--- /dev/null
+++ b/ui/lib/core/addon/components/filter-input-explicit.hbs
@@ -0,0 +1,19 @@
+{{!
+ Copyright (c) HashiCorp, Inc.
+ SPDX-License-Identifier: BUSL-1.1
+~}}
+
+
\ No newline at end of file
diff --git a/ui/lib/core/app/components/filter-input-explicit.js b/ui/lib/core/app/components/filter-input-explicit.js
new file mode 100644
index 0000000000..1327ecdafc
--- /dev/null
+++ b/ui/lib/core/app/components/filter-input-explicit.js
@@ -0,0 +1,6 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+export { default } from 'core/components/filter-input-explicit';
diff --git a/ui/lib/kubernetes/addon/components/page/roles.hbs b/ui/lib/kubernetes/addon/components/page/roles.hbs
index e1ade3fa61..810b45ae5f 100644
--- a/ui/lib/kubernetes/addon/components/page/roles.hbs
+++ b/ui/lib/kubernetes/addon/components/page/roles.hbs
@@ -6,8 +6,11 @@
{{#unless @promptConfig}}
diff --git a/ui/lib/kubernetes/addon/components/page/roles.js b/ui/lib/kubernetes/addon/components/page/roles.js
index e3ef2eca90..6de4188d12 100644
--- a/ui/lib/kubernetes/addon/components/page/roles.js
+++ b/ui/lib/kubernetes/addon/components/page/roles.js
@@ -9,24 +9,58 @@ import { action } from '@ember/object';
import { getOwner } from '@ember/application';
import errorMessage from 'vault/utils/error-message';
import { tracked } from '@glimmer/tracking';
+import keys from 'core/utils/key-codes';
/**
* @module Roles
- * RolesPage component is a child component to show list of roles
+ * RolesPage component is a child component to show list of roles.
+ * It also handles the filtering actions of roles.
*
* @param {array} roles - array of roles
* @param {boolean} promptConfig - whether or not to display config cta
- * @param {array} pageFilter - array of filtered roles
+ * @param {string} filterValue - value of queryParam pageFilter
* @param {array} breadcrumbs - breadcrumbs as an array of objects that contain label and route
*/
export default class RolesPageComponent extends Component {
@service flashMessages;
+ @service router;
+ @tracked query;
@tracked roleToDelete = null;
+ constructor() {
+ super(...arguments);
+ this.query = this.args.filterValue;
+ }
+
get mountPoint() {
return getOwner(this).mountPoint;
}
+ navigate(pageFilter) {
+ const route = `${this.mountPoint}.roles.index`;
+ const args = [route, { queryParams: { pageFilter: pageFilter || null } }];
+ this.router.transitionTo(...args);
+ }
+
+ @action
+ handleKeyDown(event) {
+ if (event.keyCode === keys.ESC) {
+ // On escape, transition to roles index route.
+ this.navigate();
+ }
+ // ignore all other key events
+ }
+
+ @action handleInput(evt) {
+ this.query = evt.target.value;
+ }
+
+ @action
+ handleSearch(evt) {
+ evt.preventDefault();
+ this.navigate(this.query);
+ }
+
@action
async onDelete(model) {
try {
diff --git a/ui/lib/kubernetes/addon/components/tab-page-header.hbs b/ui/lib/kubernetes/addon/components/tab-page-header.hbs
index 6b6fc74a31..cc765170ab 100644
--- a/ui/lib/kubernetes/addon/components/tab-page-header.hbs
+++ b/ui/lib/kubernetes/addon/components/tab-page-header.hbs
@@ -28,10 +28,12 @@
{{#if @filterRoles}}
-
{{/if}}
diff --git a/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js b/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js
index 4a8dcdbdf6..23af404a13 100644
--- a/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js
+++ b/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js
@@ -10,6 +10,7 @@ import kubernetesScenario from 'vault/mirage/scenarios/kubernetes';
import kubernetesHandlers from 'vault/mirage/handlers/kubernetes';
import authPage from 'vault/tests/pages/auth';
import { fillIn, visit, currentURL, click, currentRouteName } from '@ember/test-helpers';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
module('Acceptance | kubernetes | roles', function (hooks) {
setupApplicationTest(hooks);
@@ -30,7 +31,8 @@ module('Acceptance | kubernetes | roles', function (hooks) {
test('it should filter roles', async function (assert) {
await this.visitRoles();
assert.dom('[data-test-list-item-link]').exists({ count: 3 }, 'Roles list renders');
- await fillIn('[data-test-component="navigate-input"]', '1');
+ await fillIn(GENERAL.filterInputExplicit, '1');
+ await click(GENERAL.filterInputExplicitSearch);
assert.dom('[data-test-list-item-link]').exists({ count: 1 }, 'Filtered roles list renders');
assert.ok(currentURL().includes('pageFilter=1'), 'pageFilter query param value is set');
});
diff --git a/ui/tests/helpers/general-selectors.ts b/ui/tests/helpers/general-selectors.ts
index 49260cb17b..674a5bb51a 100644
--- a/ui/tests/helpers/general-selectors.ts
+++ b/ui/tests/helpers/general-selectors.ts
@@ -20,6 +20,8 @@ export const GENERAL = {
filter: (name: string) => `[data-test-filter="${name}"]`,
filterInput: '[data-test-filter-input]',
+ filterInputExplicit: '[data-test-filter-input-explicit]',
+ filterInputExplicitSearch: '[data-test-filter-input-explicit-search]',
confirmModalInput: '[data-test-confirmation-modal-input]',
confirmButton: '[data-test-confirm-button]',
confirmTrigger: '[data-test-confirm-action-trigger]',
diff --git a/ui/tests/integration/components/filter-input-explicit-test.js b/ui/tests/integration/components/filter-input-explicit-test.js
new file mode 100644
index 0000000000..3dff54736c
--- /dev/null
+++ b/ui/tests/integration/components/filter-input-explicit-test.js
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, typeIn, click } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import sinon from 'sinon';
+
+const handler = (e) => {
+ // required because filter-input-explicit passes handleSearch on form submit
+ if (e && e.preventDefault) e.preventDefault();
+ return;
+};
+
+module('Integration | Component | filter-input-explicit', function (hooks) {
+ setupRenderingTest(hooks);
+
+ hooks.beforeEach(function () {
+ this.handleSearch = sinon.spy(handler);
+ this.handleInput = sinon.spy();
+ this.handleKeyDown = sinon.spy();
+ this.query = '';
+ this.placeholder = 'Filter roles';
+
+ this.renderComponent = () => {
+ return render(
+ hbs``
+ );
+ };
+ });
+
+ test('it renders', async function (assert) {
+ this.query = 'foo';
+ await this.renderComponent();
+
+ assert
+ .dom(GENERAL.filterInputExplicit)
+ .hasAttribute('placeholder', 'Filter roles', 'Placeholder passed to input element');
+ assert.dom(GENERAL.filterInputExplicit).hasValue('foo', 'Value passed to input element');
+ });
+
+ test('it should call handleSearch on submit', async function (assert) {
+ await this.renderComponent();
+ await typeIn(GENERAL.filterInputExplicit, 'bar');
+ await click(GENERAL.filterInputExplicitSearch);
+ assert.ok(this.handleSearch.calledOnce, 'handleSearch was called once');
+ });
+
+ test('it should send keydown event on keydown', async function (assert) {
+ await this.renderComponent();
+ await typeIn(GENERAL.filterInputExplicit, 'a');
+ await typeIn(GENERAL.filterInputExplicit, 'b');
+
+ assert.ok(this.handleKeyDown.calledTwice, 'handle keydown was called twice');
+ assert.ok(this.handleSearch.notCalled, 'handleSearch was not called on a keydown event');
+ });
+});
diff --git a/ui/tests/integration/components/kubernetes/page/roles-test.js b/ui/tests/integration/components/kubernetes/page/roles-test.js
index 70562e2adf..bf557e2d39 100644
--- a/ui/tests/integration/components/kubernetes/page/roles-test.js
+++ b/ui/tests/integration/components/kubernetes/page/roles-test.js
@@ -10,6 +10,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import { render, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
module('Integration | Component | kubernetes | Page::Roles', function (hooks) {
setupRenderingTest(hooks);
@@ -58,7 +59,7 @@ module('Integration | Component | kubernetes | Page::Roles', function (hooks) {
.dom('[data-test-toolbar-roles-action]')
.doesNotExist('Create role', 'Toolbar action does not render when not configured');
assert
- .dom('[data-test-nav-input]')
+ .dom(GENERAL.filterInputExplicit)
.doesNotExist('Roles filter input does not render when not configured');
assert.dom('[data-test-config-cta]').exists('Config cta renders');
});
@@ -70,7 +71,7 @@ module('Integration | Component | kubernetes | Page::Roles', function (hooks) {
assert
.dom('[data-test-toolbar-roles-action] svg')
.hasClass('flight-icon-plus', 'Toolbar action has correct icon');
- assert.dom('[data-test-nav-input]').exists('Roles filter input renders');
+ assert.dom(GENERAL.filterInputExplicit).exists('Roles filter input renders');
assert.dom('[data-test-empty-state-title]').hasText('No roles yet', 'Title renders');
assert
.dom('[data-test-empty-state-message]')
diff --git a/ui/tests/integration/components/kubernetes/tab-page-header-test.js b/ui/tests/integration/components/kubernetes/tab-page-header-test.js
index e4e24ab6cf..e203d1e753 100644
--- a/ui/tests/integration/components/kubernetes/tab-page-header-test.js
+++ b/ui/tests/integration/components/kubernetes/tab-page-header-test.js
@@ -9,6 +9,8 @@ import { setupEngine } from 'ember-engines/test-support';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import sinon from 'sinon';
module('Integration | Component | kubernetes | TabPageHeader', function (hooks) {
setupRenderingTest(hooks);
@@ -28,12 +30,18 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks)
this.model = this.store.peekRecord('secret-engine', 'kubernetes-test');
this.mount = this.model.path.slice(0, -1);
this.breadcrumbs = [{ label: 'Secrets', route: 'secrets', linkExternal: true }, { label: this.mount }];
+ this.handleSearch = sinon.spy();
+ this.handleInput = sinon.spy();
+ this.handleKeyDown = sinon.spy();
});
test('it should render breadcrumbs', async function (assert) {
- await render(hbs``, {
- owner: this.engine,
- });
+ await render(
+ hbs``,
+ {
+ owner: this.engine,
+ }
+ );
assert.dom('[data-test-breadcrumbs] li:nth-child(1) a').hasText('Secrets', 'Secrets breadcrumb renders');
assert
@@ -42,9 +50,12 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks)
});
test('it should render title', async function (assert) {
- await render(hbs``, {
- owner: this.engine,
- });
+ await render(
+ hbs``,
+ {
+ owner: this.engine,
+ }
+ );
assert
.dom('[data-test-header-title] svg')
.hasClass('flight-icon-kubernetes-color', 'Correct icon renders in title');
@@ -52,9 +63,12 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks)
});
test('it should render tabs', async function (assert) {
- await render(hbs``, {
- owner: this.engine,
- });
+ await render(
+ hbs``,
+ {
+ owner: this.engine,
+ }
+ );
assert.dom('[data-test-tab="overview"]').hasText('Overview', 'Overview tab renders');
assert.dom('[data-test-tab="roles"]').hasText('Roles', 'Roles tab renders');
assert.dom('[data-test-tab="config"]').hasText('Configuration', 'Configuration tab renders');
@@ -62,16 +76,16 @@ module('Integration | Component | kubernetes | TabPageHeader', function (hooks)
test('it should render filter for roles', async function (assert) {
await render(
- hbs``,
+ hbs``,
{ owner: this.engine }
);
- assert.dom('[data-test-nav-input] input').hasValue('test', 'Filter renders with provided value');
+ assert.dom(GENERAL.filterInputExplicit).hasValue('test', 'Filter renders with provided value');
});
test('it should yield block for toolbar actions', async function (assert) {
await render(
hbs`
-
+
It yields!
`,