diff --git a/changelog/20747.txt b/changelog/20747.txt new file mode 100644 index 000000000..4c600d203 --- /dev/null +++ b/changelog/20747.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Add filtering by auth type and auth name to the Authentication Method list view. +``` diff --git a/ui/app/controllers/vault/cluster/access/methods.js b/ui/app/controllers/vault/cluster/access/methods.js index b05c40d06..9e0fa3a98 100644 --- a/ui/app/controllers/vault/cluster/access/methods.js +++ b/ui/app/controllers/vault/cluster/access/methods.js @@ -4,22 +4,74 @@ */ import Controller from '@ember/controller'; -import { task } from 'ember-concurrency'; +import { dropTask } from 'ember-concurrency'; import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; -export default Controller.extend({ - flashMessages: service(), +export default class VaultClusterAccessMethodsController extends Controller { + @service flashMessages; - queryParams: { - page: 'page', - pageFilter: 'pageFilter', - }, + @tracked authMethodOptions = []; + @tracked selectedAuthType = null; + @tracked selectedAuthName = null; - page: 1, - pageFilter: null, - filter: null, + queryParams = ['page, pageFilter']; - disableMethod: task(function* (method) { + page = 1; + pageFilter = null; + filter = null; + + get authMethodList() { + // return an options list to filter by engine type, ex: 'kv' + if (this.selectedAuthType) { + // check first if the user has also filtered by name. + // names are individualized across type so you can't have the same name for an aws auth method and userpass. + // this means it's fine to filter by first type and then name or just name. + if (this.selectedAuthName) { + return this.model.filter((method) => this.selectedAuthName === method.id); + } + // otherwise filter by auth type + return this.model.filter((method) => this.selectedAuthType === method.type); + } + // return an options list to filter by auth name, ex: 'my-userpass' + if (this.selectedAuthName) { + return this.model.filter((method) => this.selectedAuthName === method.id); + } + // no filters, return full sorted list. + return this.model; + } + + get authMethodArrayByType() { + const arrayOfAllAuthTypes = this.authMethodList.map((modelObject) => modelObject.type); + // filter out repeated auth types (e.g. [userpass, userpass] => [userpass]) + const arrayOfUniqueAuthTypes = [...new Set(arrayOfAllAuthTypes)]; + + return arrayOfUniqueAuthTypes.map((authType) => ({ + name: authType, + id: authType, + })); + } + + get authMethodArrayByName() { + return this.authMethodList.map((modelObject) => ({ + name: modelObject.id, + id: modelObject.id, + })); + } + + @action + filterAuthType([type]) { + this.selectedAuthType = type; + } + + @action + filterAuthName([name]) { + this.selectedAuthName = name; + } + + @dropTask + *disableMethod(method) { const { type, path } = method; try { yield method.destroyRecord(); @@ -29,5 +81,5 @@ export default Controller.extend({ `There was an error disabling Auth Method at ${path}: ${err.errors.join(' ')}.` ); } - }).drop(), -}); + } +} diff --git a/ui/app/routes/vault/cluster/access/methods.js b/ui/app/routes/vault/cluster/access/methods.js index e776b2dd4..7d573c254 100644 --- a/ui/app/routes/vault/cluster/access/methods.js +++ b/ui/app/routes/vault/cluster/access/methods.js @@ -6,19 +6,19 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -export default Route.extend({ - store: service(), +export default class VaultClusterAccessMethodsRoute extends Route { + @service store; - queryParams: { + queryParams = { page: { refreshModel: true, }, pageFilter: { refreshModel: true, }, - }, + }; model() { return this.store.findAll('auth-method'); - }, -}); + } +} diff --git a/ui/app/templates/vault/cluster/access/methods.hbs b/ui/app/templates/vault/cluster/access/methods.hbs index 7486c1002..c04c4c7f3 100644 --- a/ui/app/templates/vault/cluster/access/methods.hbs +++ b/ui/app/templates/vault/cluster/access/methods.hbs @@ -7,6 +7,33 @@ + + + + Enable new method @@ -14,7 +41,7 @@ -{{#each (sort-by "path" this.model) as |method|}} +{{#each (sort-by "path" this.authMethodList) as |method|}} row.innerText.includes('userpass')); + + assert.strictEqual(rows.length, rowsUserpass.length, 'all rows returned are userpass'); + + // filter by name + await clickTrigger('#filter-by-auth-name'); + const firstItemToSelect = searchSelect.options.objectAt(0).text; + await searchSelect.options.objectAt(0).click(); + const singleRow = document.querySelectorAll('[data-test-auth-backend-link]'); + + assert.strictEqual(singleRow.length, 1, 'returns only one row'); + assert.dom(singleRow[0]).includesText(firstItemToSelect, 'shows the filtered by auth name'); + // 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 consoleComponent.runCommands([`delete sys/auth/${authPath1}`]); + await consoleComponent.runCommands([`delete sys/auth/${authPath2}`]); + }); }); diff --git a/ui/tests/acceptance/secrets/backend/engines-test.js b/ui/tests/acceptance/secrets/backend/engines-test.js index db2ab3cad..60fd9a2a8 100644 --- a/ui/tests/acceptance/secrets/backend/engines-test.js +++ b/ui/tests/acceptance/secrets/backend/engines-test.js @@ -7,7 +7,7 @@ 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 consoleClass from 'vault/tests/pages/components/console/ui-panel'; import { setupApplicationTest } from 'ember-qunit'; import { v4 as uuidv4 } from 'uuid'; @@ -16,6 +16,7 @@ import backendsPage from 'vault/tests/pages/secrets/backends'; import authPage from 'vault/tests/pages/auth'; import ss from 'vault/tests/pages/components/search-select'; +const consoleComponent = create(consoleClass); const searchSelect = create(ss); module('Acceptance | secret-engine list view', function (hooks) { @@ -76,7 +77,7 @@ module('Acceptance | secret-engine list view', function (hooks) { assert.dom(rowSupported[0]).hasClass('linked-block', `linked-block class is added to supported engines.`); // cleanup - await runCommands([`delete sys/mounts/${enginePath}`]); + await consoleComponent.runCommands([`delete sys/mounts/${enginePath}`]); }); test('it filters by name and engine type', async function (assert) { @@ -109,7 +110,7 @@ module('Acceptance | secret-engine list view', function (hooks) { assert.ok(rowsAgain.length > 1, 'filter has been removed'); // cleanup - await runCommands([`delete sys/mounts/${enginePath1}`]); - await runCommands([`delete sys/mounts/${enginePath2}`]); + await consoleComponent.runCommands([`delete sys/mounts/${enginePath1}`]); + await consoleComponent.runCommands([`delete sys/mounts/${enginePath2}`]); }); });