Skip to content

Commit

Permalink
Glimmer Navigate Input component (#19517)
Browse files Browse the repository at this point in the history
* wip

* wip

* todo hackeweek remove

* clean up

* add documetnation and fix test failure

* pr review changes

* spelling

* remove unused method
  • Loading branch information
Monkeychip authored and raymonstah committed Mar 17, 2023
1 parent 0102e61 commit 5d4f9b8
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 140 deletions.
2 changes: 1 addition & 1 deletion ui/app/templates/vault/cluster/policies/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
{{#if this.model.meta.total}}
<ToolbarFilters>
<NavigateInput
filterFocusDidChange={{action "setFilterFocus"}}
@filterFocusDidChange={{action "setFilterFocus"}}
@filterDidChange={{action "setFilter"}}
@filter={{this.filter}}
@filterMatchesKey={{this.filterMatchesKey}}
Expand Down
20 changes: 20 additions & 0 deletions ui/lib/core/addon/components/navigate-input.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div class="navigate-filter">
<div class="field" data-test-nav-input>
<p class="control has-icons-left">
{{! template-lint-disable no-down-event-binding }}
<input
class="filter input"
value={{@filter}}
placeholder={{or @placeholder "Filter items"}}
type="text"
data-test-component="navigate-input"
{{on "input" this.handleInput}}
{{on "keyup" this.handleKeyUp}}
{{on "keydown" this.handleKeyPress}}
{{on "focus" (fn this.setFilterFocused true)}}
{{on "blur" (fn this.setFilterFocused false)}}
/>
<Icon @name="search" class="search-icon has-text-grey-light" />
</p>
</div>
</div>
186 changes: 93 additions & 93 deletions ui/lib/core/addon/components/navigate-input.js
Original file line number Diff line number Diff line change
@@ -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
* <NavigateInput @filter={@roleFiltered} @placeholder="placeholder text" urls="{{hash list="vault.cluster.secrets.backend.kubernetes.roles"}}"/>
* ```
*
* @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 = {
Expand All @@ -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) => {
Expand All @@ -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),
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -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);
}
}
}
20 changes: 0 additions & 20 deletions ui/lib/core/addon/templates/components/navigate-input.hbs

This file was deleted.

2 changes: 1 addition & 1 deletion ui/lib/pki/addon/templates/certificates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<Toolbar>
{{#if this.model.certificates.length}}
<ToolbarFilters>
{{! ARG TODO glimmerize the NavigateInput and refactor so you can use it in an engine }}
{{! TODO add NavigateInput component }}
</ToolbarFilters>
{{/if}}
</Toolbar>
Expand Down
14 changes: 6 additions & 8 deletions ui/tests/acceptance/aws-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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`);
});
});
2 changes: 1 addition & 1 deletion ui/tests/acceptance/leases-test.js
Original file line number Diff line number Diff line change
@@ -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
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down
Loading

0 comments on commit 5d4f9b8

Please sign in to comment.