mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-11 01:42:06 -04:00
Change tab completion in the UI to prefer common prefix (#6759)
* add common-prefix util and use it in the list controller * add test * browser js for in-repo dirs * address PR feedback
This commit is contained in:
parent
1a72f14201
commit
8cdba298cd
4 changed files with 75 additions and 12 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { computed } from '@ember/object';
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
import commonPrefix from 'vault/utils/common-prefix';
|
||||
|
||||
export default Mixin.create({
|
||||
queryParams: {
|
||||
|
|
@ -16,21 +17,27 @@ export default Mixin.create({
|
|||
isLoading: false,
|
||||
|
||||
filterMatchesKey: computed('filter', 'model', 'model.[]', function() {
|
||||
var filter = this.get('filter');
|
||||
var content = this.get('model');
|
||||
let { filter, model: content } = this;
|
||||
return !!(content.length && content.findBy('id', filter));
|
||||
}),
|
||||
|
||||
firstPartialMatch: computed('filter', 'model', 'model.[]', 'filterMatchesKey', function() {
|
||||
var filter = this.get('filter');
|
||||
var content = this.get('model');
|
||||
var filterMatchesKey = this.get('filterMatchesKey');
|
||||
var re = new RegExp('^' + escapeStringRegexp(filter));
|
||||
return filterMatchesKey
|
||||
? null
|
||||
: content.find(function(key) {
|
||||
return re.test(key.get('id'));
|
||||
});
|
||||
let { filter, filterMatchesKey, model: content } = this;
|
||||
let re = new RegExp('^' + escapeStringRegexp(filter));
|
||||
let matchSet = content.filter(key => re.test(key.id));
|
||||
let match = matchSet[0];
|
||||
|
||||
if (filterMatchesKey || !match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let sharedPrefix = commonPrefix(content);
|
||||
// if we already are filtering the prefix, then next we want
|
||||
// the exact match
|
||||
if (filter === sharedPrefix || matchSet.length === 1) {
|
||||
return match;
|
||||
}
|
||||
return { id: sharedPrefix };
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
|
|
|||
24
ui/app/utils/common-prefix.js
Normal file
24
ui/app/utils/common-prefix.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export default function(arr = [], attribute = 'id') {
|
||||
if (!arr.length) {
|
||||
return '';
|
||||
}
|
||||
// this assumes an already sorted array
|
||||
// if the array is sorted, we want to compare the first and last
|
||||
// item in the array - if they share a prefix, all of the items do
|
||||
let firstString = arr[0][attribute];
|
||||
let lastString = arr[arr.length - 1][attribute];
|
||||
|
||||
// the longest the shared prefix could be is the length of the match
|
||||
let targetLength = firstString.length;
|
||||
let prefixLength = 0;
|
||||
// walk the two strings, and if they match at the current length,
|
||||
// increment the prefixLength and try again
|
||||
while (
|
||||
prefixLength < targetLength &&
|
||||
firstString.charAt(prefixLength) === lastString.charAt(prefixLength)
|
||||
) {
|
||||
prefixLength++;
|
||||
}
|
||||
// slice the prefix from the first item
|
||||
return firstString.substring(0, prefixLength);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
browser: false,
|
||||
browser: true,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
32
ui/tests/unit/utils/common-prefix-test.js
Normal file
32
ui/tests/unit/utils/common-prefix-test.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import commonPrefix from 'vault/utils/common-prefix';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Util | common prefix', function() {
|
||||
test('it returns empty string if called with no args or an empty array', function(assert) {
|
||||
let returned = commonPrefix();
|
||||
assert.equal(returned, '', 'returns an empty string');
|
||||
returned = commonPrefix([]);
|
||||
assert.equal(returned, '', 'returns an empty string for an empty array');
|
||||
});
|
||||
|
||||
test('it returns empty string if there are no common prefixes', function(assert) {
|
||||
let secrets = ['asecret', 'secret2', 'secret3'].map(s => ({ id: s }));
|
||||
let returned = commonPrefix(secrets);
|
||||
assert.equal(returned, '', 'returns an empty string');
|
||||
});
|
||||
|
||||
test('it returns the longest prefix', function(assert) {
|
||||
let secrets = ['secret1', 'secret2', 'secret3'].map(s => ({ id: s }));
|
||||
let returned = commonPrefix(secrets);
|
||||
assert.equal(returned, 'secret', 'finds secret prefix');
|
||||
let greetings = ['hello-there', 'hello-hi', 'hello-howdy'].map(s => ({ id: s }));
|
||||
returned = commonPrefix(greetings);
|
||||
assert.equal(returned, 'hello-', 'finds hello- prefix');
|
||||
});
|
||||
|
||||
test('it can compare an attribute that is not "id" to calculate the longest prefix', function(assert) {
|
||||
let secrets = ['secret1', 'secret2', 'secret3'].map(s => ({ name: s }));
|
||||
let returned = commonPrefix(secrets, 'name');
|
||||
assert.equal(returned, 'secret', 'finds secret prefix from name attribute');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue