From 5eb0aa29ac97e3c456e633d1fb7c7323ac905fdf Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Mon, 20 May 2019 12:03:10 -0500 Subject: [PATCH 1/4] add common-prefix util and use it in the list controller --- ui/app/mixins/list-controller.js | 20 +++++++++++++++----- ui/app/utils/common-prefix.js | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 ui/app/utils/common-prefix.js diff --git a/ui/app/mixins/list-controller.js b/ui/app/mixins/list-controller.js index 423b42a12e21..5d27f7a4abde 100644 --- a/ui/app/mixins/list-controller.js +++ b/ui/app/mixins/list-controller.js @@ -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: { @@ -26,11 +27,20 @@ export default Mixin.create({ 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 matchSet = content.filter(key => re.test(key.id)); + let match = matchSet.firstObject; + + 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: { diff --git a/ui/app/utils/common-prefix.js b/ui/app/utils/common-prefix.js new file mode 100644 index 000000000000..b5e4fda6fdbf --- /dev/null +++ b/ui/app/utils/common-prefix.js @@ -0,0 +1,23 @@ +import { get } from '@ember/object'; + +export default function(arr, attribute = 'id') { + let content = arr || []; + // this assumes an already sorted array, if we have a match, + // it will have matched the first one so we also want the last one + let firstString = get(content[0], attribute); + let lastString = get(content[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 prefix and try again + while ( + prefixLength < targetLength && + firstString.charAt(prefixLength) === lastString.charAt(prefixLength) + ) { + prefixLength++; + } + // slice the prefix from the match + return firstString.substring(0, prefixLength); +} From b88a88c4edffbb474c6f54e58cd49664879beb57 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Mon, 20 May 2019 15:36:44 -0500 Subject: [PATCH 2/4] add test --- ui/app/utils/common-prefix.js | 9 ++++---- ui/tests/unit/utils/common-prefix-test.js | 27 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 ui/tests/unit/utils/common-prefix-test.js diff --git a/ui/app/utils/common-prefix.js b/ui/app/utils/common-prefix.js index b5e4fda6fdbf..e77d0569af74 100644 --- a/ui/app/utils/common-prefix.js +++ b/ui/app/utils/common-prefix.js @@ -2,8 +2,9 @@ import { get } from '@ember/object'; export default function(arr, attribute = 'id') { let content = arr || []; - // this assumes an already sorted array, if we have a match, - // it will have matched the first one so we also want the last one + // 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 = get(content[0], attribute); let lastString = get(content[arr.length - 1], attribute); @@ -11,13 +12,13 @@ export default function(arr, attribute = 'id') { let targetLength = firstString.length; let prefixLength = 0; // walk the two strings, and if they match at the current length, - // increment the prefix and try again + // increment the prefixLength and try again while ( prefixLength < targetLength && firstString.charAt(prefixLength) === lastString.charAt(prefixLength) ) { prefixLength++; } - // slice the prefix from the match + // slice the prefix from the first item return firstString.substring(0, prefixLength); } diff --git a/ui/tests/unit/utils/common-prefix-test.js b/ui/tests/unit/utils/common-prefix-test.js new file mode 100644 index 000000000000..0acc63ad76d1 --- /dev/null +++ b/ui/tests/unit/utils/common-prefix-test.js @@ -0,0 +1,27 @@ +import commonPrefix from 'vault/utils/common-prefix'; +import { module, test } from 'qunit'; + +module('Unit | Util | common prefix', function() { + 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'); + }); +}); + From d9b4bc445c5642d2138050868ab7eca33154799f Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 21 May 2019 14:28:43 -0500 Subject: [PATCH 3/4] browser js for in-repo dirs --- ui/lib/.eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/.eslintrc.js b/ui/lib/.eslintrc.js index 548ea343c9f0..02fab21e154c 100644 --- a/ui/lib/.eslintrc.js +++ b/ui/lib/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { env: { node: true, - browser: false, + browser: true, }, }; From e0b4c7e9861bd3737aa55bdcfcc89d9d5cd2af55 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 21 May 2019 14:29:15 -0500 Subject: [PATCH 4/4] address PR feedback --- ui/app/mixins/list-controller.js | 11 ++++------- ui/app/utils/common-prefix.js | 12 ++++++------ ui/tests/unit/utils/common-prefix-test.js | 17 +++++++++++------ 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/ui/app/mixins/list-controller.js b/ui/app/mixins/list-controller.js index 5d27f7a4abde..dbe234cf5e13 100644 --- a/ui/app/mixins/list-controller.js +++ b/ui/app/mixins/list-controller.js @@ -17,18 +17,15 @@ 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)); + let { filter, filterMatchesKey, model: content } = this; + let re = new RegExp('^' + escapeStringRegexp(filter)); let matchSet = content.filter(key => re.test(key.id)); - let match = matchSet.firstObject; + let match = matchSet[0]; if (filterMatchesKey || !match) { return null; diff --git a/ui/app/utils/common-prefix.js b/ui/app/utils/common-prefix.js index e77d0569af74..59d27b8d6318 100644 --- a/ui/app/utils/common-prefix.js +++ b/ui/app/utils/common-prefix.js @@ -1,12 +1,12 @@ -import { get } from '@ember/object'; - -export default function(arr, attribute = 'id') { - let content = arr || []; +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 = get(content[0], attribute); - let lastString = get(content[arr.length - 1], attribute); + 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; diff --git a/ui/tests/unit/utils/common-prefix-test.js b/ui/tests/unit/utils/common-prefix-test.js index 0acc63ad76d1..5b437330c5ed 100644 --- a/ui/tests/unit/utils/common-prefix-test.js +++ b/ui/tests/unit/utils/common-prefix-test.js @@ -2,26 +2,31 @@ 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 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 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})); + 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 secrets = ['secret1', 'secret2', 'secret3'].map(s => ({ name: s })); let returned = commonPrefix(secrets, 'name'); assert.equal(returned, 'secret', 'finds secret prefix from name attribute'); }); }); -