diff --git a/ui/app/templates/vault/cluster/policies/index.hbs b/ui/app/templates/vault/cluster/policies/index.hbs index 78102f2146e5..925e27e50688 100644 --- a/ui/app/templates/vault/cluster/policies/index.hbs +++ b/ui/app/templates/vault/cluster/policies/index.hbs @@ -16,7 +16,7 @@ {{#if this.model.meta.total}} +
+

+ {{! template-lint-disable no-down-event-binding }} + + +

+
+ \ No newline at end of file diff --git a/ui/lib/core/addon/components/navigate-input.js b/ui/lib/core/addon/components/navigate-input.js index 9cf9d80b23e0..f45ff217a508 100644 --- a/ui/lib/core/addon/components/navigate-input.js +++ b/ui/lib/core/addon/components/navigate-input.js @@ -1,14 +1,34 @@ -import { schedule, debounce } from '@ember/runloop'; +import { debounce } from '@ember/runloop'; import { inject as service } from '@ember/service'; -import Component from '@ember/component'; +import Component from '@glimmer/component'; +import { action } from '@ember/object'; -//TODO MOVE THESE TO THE ADDON +// TODO MOVE THESE TO THE ADDON import utils from 'vault/lib/key-utils'; import keys from 'vault/lib/keycodes'; -import FocusOnInsertMixin from 'vault/mixins/focus-on-insert'; import { encodePath } from 'vault/utils/path-encoding-helpers'; -import layout from '../templates/components/navigate-input'; +/** + * @module NavigateInput + * `NavigateInput` components are used to filter list data. + * + * @example + * ```js + * + * ``` + * + * @param {String} filter=null - The filtered string. + * @param {String} [placeholder="Filter items"] - The message inside the input to indicate what the user should enter into the space. + * @param {Object} [urls=null] - An object containing list=route url. + * @param {Function} [filterFocusDidChange=null] - A function called when the focus changes. + * @param {Function} [filterDidChange=null] - A function called when the filter string changes. + * @param {Function} [filterMatchesKey=null] - A function used to match to a specific key, such as an Id. + * @param {Function} [filterPartialMatch=null] - A function used to filter through a partial match. Such as "oo" of "root". + * @param {String} [baseKey=""] - A string to transition by Id. + * @param {Boolean} [shouldNavigateTree=false] - If true, navigate a larger tree, such as when you're navigating leases under access. + * @param {String} [mode="secrets"] - Mode which plays into navigation type. + * @param {String} [extraNavParams=""] - A string used in route transition when necessary. + */ const routeFor = function (type, mode, urls) { const MODES = { @@ -34,25 +54,12 @@ const routeFor = function (type, mode, urls) { return useSuffix ? modeVal + '.' + typeVal : modeVal; }; -export default Component.extend(FocusOnInsertMixin, { - layout, - router: service(), +export default class NavigateInput extends Component { + @service router; - classNames: ['navigate-filter'], - urls: null, - - // these get passed in from the outside - // actions that get passed in - filterFocusDidChange: null, - filterDidChange: null, - mode: 'secrets', - shouldNavigateTree: false, - extraNavParams: null, - - baseKey: null, - filter: null, - filterMatchesKey: null, - firstPartialMatch: null, + get mode() { + return this.args.mode || 'secrets'; + } transitionToRoute(...args) { const params = args.map((param, index) => { @@ -63,43 +70,37 @@ export default Component.extend(FocusOnInsertMixin, { }); this.router.transitionTo(...params); - }, - - shouldFocus: false, - - didReceiveAttrs() { - this._super(...arguments); - if (!this.filter) return; - schedule('afterRender', this, 'forceFocus'); - }, + } keyForNav(key) { if (this.mode !== 'secrets-cert') { return key; } return `cert/${key}`; - }, - onEnter: function (val) { - const { baseKey, mode } = this; - const extraParams = this.extraNavParams; + } + + onEnter(val) { + const mode = this.mode; + const baseKey = this.args.baseKey; + const extraParams = this.args.extraNavParams; if (mode.startsWith('secrets') && (!val || val === baseKey)) { return; } - if (this.filterMatchesKey && !utils.keyIsFolder(val)) { - const params = [routeFor('show', mode, this.urls), extraParams, this.keyForNav(val)].compact(); + if (this.args.filterMatchesKey && !utils.keyIsFolder(val)) { + const params = [routeFor('show', mode, this.args.urls), extraParams, this.keyForNav(val)].compact(); this.transitionToRoute(...params); } else { if (mode === 'policies') { return; } - const route = routeFor('create', mode, this.urls); + const route = routeFor('create', mode, this.args.urls); if (baseKey) { this.transitionToRoute(route, this.keyForNav(baseKey), { queryParams: { initialKey: val, }, }); - } else if (this.urls) { + } else if (this.args.urls) { this.transitionToRoute(route, { queryParams: { initialKey: this.keyForNav(val), @@ -113,35 +114,35 @@ export default Component.extend(FocusOnInsertMixin, { }); } } - }, + } // pop to the nearest parentKey or to the root - onEscape: function (val) { - var key = utils.parentKeyForKey(val) || ''; - this.filterDidChange(key); + onEscape(val) { + const key = utils.parentKeyForKey(val) || ''; + this.args.filterDidChange(key); this.filterUpdated(key); - }, + } - onTab: function (event) { - var firstPartialMatch = this.firstPartialMatch.id; + onTab(event) { + const firstPartialMatch = this.args.firstPartialMatch.id; if (!firstPartialMatch) { return; } event.preventDefault(); - this.filterDidChange(firstPartialMatch); + this.args.filterDidChange(firstPartialMatch); this.filterUpdated(firstPartialMatch); - }, + } // as you type, navigates through the k/v tree - filterUpdated: function (val) { - var mode = this.mode; - if (mode === 'policies' || !this.shouldNavigateTree) { + filterUpdated(val) { + const mode = this.mode; + if (mode === 'policies' || !this.args.shouldNavigateTree) { this.filterUpdatedNoNav(val, mode); return; } // select the key to nav to, assumed to be a folder - var key = val ? val.trim() : ''; - var isFolder = utils.keyIsFolder(key); + let key = val ? val.trim() : ''; + const isFolder = utils.keyIsFolder(key); if (!isFolder) { // nav to the closest parentKey (or the root) @@ -150,10 +151,10 @@ export default Component.extend(FocusOnInsertMixin, { const pageFilter = val.replace(key, ''); this.navigate(this.keyForNav(key), mode, pageFilter); - }, + } navigate(key, mode, pageFilter) { - const route = routeFor(key ? 'list' : 'list-root', mode, this.urls); + const route = routeFor(key ? 'list' : 'list-root', mode, this.args.urls); const args = [route]; if (key) { args.push(key); @@ -174,47 +175,46 @@ export default Component.extend(FocusOnInsertMixin, { }); } this.transitionToRoute(...args); - }, + } - filterUpdatedNoNav: function (val, mode) { - var key = val ? val.trim() : null; - this.transitionToRoute(routeFor('list-root', mode, this.urls), { + filterUpdatedNoNav(val, mode) { + const key = val ? val.trim() : null; + this.transitionToRoute(routeFor('list-root', mode, this.args.urls), { queryParams: { pageFilter: key, page: 1, }, }); - }, - - actions: { - handleInput: function (filter) { - if (this.filterDidChange) { - this.filterDidChange(filter); - } - debounce(this, 'filterUpdated', filter, 200); - }, - - setFilterFocused: function (isFocused) { - if (this.filterFocusDidChange) { - this.filterFocusDidChange(isFocused); - } - }, - - handleKeyPress: function (event) { - if (event.keyCode === keys.TAB) { - this.onTab(event); - } - }, + } - handleKeyUp: function (event) { - var keyCode = event.keyCode; - const val = event.target.value; - if (keyCode === keys.ENTER) { - this.onEnter(val); - } - if (keyCode === keys.ESC) { - this.onEscape(val); - } - }, - }, -}); + @action + handleInput(filter) { + if (this.args.filterDidChange) { + this.args.filterDidChange(filter.target.value); + } + debounce(this, this.filterUpdated, filter.target.value, 200); + } + @action + setFilterFocused(isFocused) { + if (this.args.filterFocusDidChange) { + this.args.filterFocusDidChange(isFocused); + } + } + @action + handleKeyPress(event) { + if (event.keyCode === keys.TAB) { + this.onTab(event); + } + } + @action + handleKeyUp(event) { + const keyCode = event.keyCode; + const val = event.target.value; + if (keyCode === keys.ENTER) { + this.onEnter(val); + } + if (keyCode === keys.ESC) { + this.onEscape(val); + } + } +} diff --git a/ui/lib/core/addon/templates/components/navigate-input.hbs b/ui/lib/core/addon/templates/components/navigate-input.hbs deleted file mode 100644 index eeaa8b02d6cb..000000000000 --- a/ui/lib/core/addon/templates/components/navigate-input.hbs +++ /dev/null @@ -1,20 +0,0 @@ -
-

- {{! template-lint-disable no-down-event-binding }} - - {{! template-lint-enable no-down-event-binding }} - -

-
\ No newline at end of file diff --git a/ui/lib/pki/addon/templates/certificates/index.hbs b/ui/lib/pki/addon/templates/certificates/index.hbs index f7a571bc8dfc..95e366b3440d 100644 --- a/ui/lib/pki/addon/templates/certificates/index.hbs +++ b/ui/lib/pki/addon/templates/certificates/index.hbs @@ -12,7 +12,7 @@ {{#if this.model.certificates.length}} - {{! ARG TODO glimmerize the NavigateInput and refactor so you can use it in an engine }} + {{! TODO add NavigateInput component }} {{/if}} diff --git a/ui/tests/acceptance/aws-test.js b/ui/tests/acceptance/aws-test.js index 61f50fa6ffb6..d2532860a98f 100644 --- a/ui/tests/acceptance/aws-test.js +++ b/ui/tests/acceptance/aws-test.js @@ -27,6 +27,7 @@ module('Acceptance | aws secret backend', function (hooks) { ], }; test('aws backend', async function (assert) { + assert.expect(12); const now = new Date().getTime(); const path = `aws-${now}`; const roleName = 'awsrole'; @@ -91,13 +92,10 @@ module('Acceptance | aws secret backend', function (hooks) { assert.ok(findAll(`[data-test-secret-link="${roleName}"]`).length, `aws: role shows in the list`); //and delete - // TODO the button does not populate quickly enough. - // await click(`[data-test-secret-link="${roleName}"] [data-test-popup-menu-trigger]`); - // await settled(); - // await click(`[data-test-aws-role-delete="${roleName}"]`); - - // await click(`[data-test-confirm-button]`); - // await settled(); - // assert.dom(`[data-test-secret-link="${roleName}"]`).doesNotExist(`aws: role is no longer in the list`); + await click(`[data-test-secret-link="${roleName}"] [data-test-popup-menu-trigger]`); + await waitUntil(() => find(`[data-test-aws-role-delete="${roleName}"]`)); // flaky without + await click(`[data-test-aws-role-delete="${roleName}"]`); + await click(`[data-test-confirm-button]`); + assert.dom(`[data-test-secret-link="${roleName}"]`).doesNotExist(`aws: role is no longer in the list`); }); }); diff --git a/ui/tests/acceptance/leases-test.js b/ui/tests/acceptance/leases-test.js index 48c921ad91a0..26111641b242 100644 --- a/ui/tests/acceptance/leases-test.js +++ b/ui/tests/acceptance/leases-test.js @@ -1,5 +1,5 @@ import { click, currentRouteName, visit } from '@ember/test-helpers'; -// TESTS HERE ARE SKPPED +// TESTS HERE ARE SKIPPED // running vault with -dev-leased-kv flag lets you run some of these tests // but generating leases programmatically is currently difficult // diff --git a/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js b/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js index 3736e2d530fb..ac50bfc8484d 100644 --- a/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js +++ b/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js @@ -30,7 +30,7 @@ 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-comoponent="navigate-input"]', '1'); + await fillIn('[data-test-component="navigate-input"]', '1'); 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/acceptance/ssh-test.js b/ui/tests/acceptance/ssh-test.js index 07a5d9fee0f4..a3a6270c547a 100644 --- a/ui/tests/acceptance/ssh-test.js +++ b/ui/tests/acceptance/ssh-test.js @@ -75,7 +75,7 @@ module('Acceptance | ssh secret backend', function (hooks) { }, ]; test('ssh backend', async function (assert) { - assert.expect(26); + assert.expect(28); const now = new Date().getTime(); const sshPath = `ssh-${now}`; @@ -157,17 +157,13 @@ module('Acceptance | ssh secret backend', function (hooks) { ); //and delete - // TODO confirmed functionality works, but it can not find the data-test-ssh-role-delete in time. - // await click(`[data-test-secret-link="${role.name}"] [data-test-popup-menu-trigger]`); - // await settled(); - // await click(`[data-test-ssh-role-delete]`); - // await settled(); - // await click(`[data-test-confirm-button]`); - - // await settled(); - // assert - // .dom(`[data-test-secret-link="${role.name}"]`) - // .doesNotExist(`${role.type}: role is no longer in the list`); + await click(`[data-test-secret-link="${role.name}"] [data-test-popup-menu-trigger]`); + await waitUntil(() => find('[data-test-ssh-role-delete]')); // flaky without + await click(`[data-test-ssh-role-delete]`); + await click(`[data-test-confirm-button]`); + assert + .dom(`[data-test-secret-link="${role.name}"]`) + .doesNotExist(`${role.type}: role is no longer in the list`); } }); }); diff --git a/ui/tests/integration/components/auth-form-test.js b/ui/tests/integration/components/auth-form-test.js index d09283138b45..af5df8b7834c 100644 --- a/ui/tests/integration/components/auth-form-test.js +++ b/ui/tests/integration/components/auth-form-test.js @@ -88,7 +88,7 @@ module('Integration | Component | auth form', function (hooks) { this.set('cluster', EmberObject.create({})); this.set('selectedAuth', 'token'); await render(hbs`{{auth-form cluster=this.cluster selectedAuth=this.selectedAuth}}`); - // ARG TODO research and see if adapter errors changed, but null used to be Bad Request + // returns null because test does not return details of failed network request. On the app it will return the details of the error instead of null. return component.login().then(() => { assert.strictEqual(component.errorText, 'Error Authentication failed: null'); server.shutdown(); diff --git a/ui/tests/pages/secrets/backend/kmip/credentials.js b/ui/tests/pages/secrets/backend/kmip/credentials.js index a1938a0f27f3..baa03aa8adba 100644 --- a/ui/tests/pages/secrets/backend/kmip/credentials.js +++ b/ui/tests/pages/secrets/backend/kmip/credentials.js @@ -6,9 +6,7 @@ export default create({ visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials'), visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials/:serial'), create: clickable('[data-test-role-create]'), - credentialsLink: clickable('[data-test-kmip-link-credentials]'), generateCredentialsLink: clickable('[data-test-kmip-link-generate-credentials]'), - roleDetailsLink: clickable('[data-test-kmip-link-role-details]'), backToRoleLink: clickable('[data-test-kmip-link-back-to-role]'), submit: clickable('[data-test-edit-form-submit]'), });