From 99f102705d78840c9bfed05b31f97da69df83de1 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Fri, 18 Dec 2020 10:38:15 +0000 Subject: [PATCH] ui: Fuzzy and Regex searching (#9424) Moves search things around to match an interface that can be switched in and out of fuzzy searching using fuse.js. We add both fuzzy searching and regex based searching to the codebase here, but it is not yet compiled in. --- .../app/components/data-collection/index.js | 79 ++++-- .../consul-ui/app/search/predicates/acl.js | 4 +- .../app/search/predicates/health-check.js | 35 +-- .../app/search/predicates/intention.js | 12 +- .../consul-ui/app/search/predicates/kv.js | 5 +- .../consul-ui/app/search/predicates/node.js | 9 +- .../consul-ui/app/search/predicates/nspace.js | 12 +- .../consul-ui/app/search/predicates/policy.js | 4 +- .../consul-ui/app/search/predicates/role.js | 19 +- .../app/search/predicates/service-instance.js | 31 +-- .../app/search/predicates/service.js | 4 +- .../consul-ui/app/search/predicates/token.js | 24 +- ui/packages/consul-ui/app/services/search.js | 48 ++-- .../consul-ui/app/utils/search/exact.js | 12 + .../consul-ui/app/utils/search/fuzzy.js | 20 ++ .../consul-ui/app/utils/search/predicate.js | 22 ++ .../consul-ui/app/utils/search/regexp.js | 15 ++ .../tests/unit/search/predicates/acl-test.js | 52 ++-- .../unit/search/predicates/intention-test.js | 96 ++++--- .../tests/unit/search/predicates/kv-test.js | 50 ++-- .../tests/unit/search/predicates/node-test.js | 74 ++++-- .../unit/search/predicates/policy-test.js | 44 ++-- .../tests/unit/search/predicates/role-test.js | 139 +++++----- .../unit/search/predicates/service-test.js | 93 +++---- .../unit/search/predicates/token-test.js | 237 ++++++++++-------- 25 files changed, 639 insertions(+), 501 deletions(-) create mode 100644 ui/packages/consul-ui/app/utils/search/exact.js create mode 100644 ui/packages/consul-ui/app/utils/search/fuzzy.js create mode 100644 ui/packages/consul-ui/app/utils/search/predicate.js create mode 100644 ui/packages/consul-ui/app/utils/search/regexp.js diff --git a/ui/packages/consul-ui/app/components/data-collection/index.js b/ui/packages/consul-ui/app/components/data-collection/index.js index 23d6bb3eb9..bf9e905af9 100644 --- a/ui/packages/consul-ui/app/components/data-collection/index.js +++ b/ui/packages/consul-ui/app/components/data-collection/index.js @@ -1,6 +1,7 @@ import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; -import { computed, get, action } from '@ember/object'; +import { computed, action } from '@ember/object'; +import { alias } from '@ember/object/computed'; import { tracked } from '@glimmer/tracking'; import { sort } from '@ember/object/computed'; import { defineProperty } from '@ember/object'; @@ -12,30 +13,48 @@ export default class DataCollectionComponent extends Component { @tracked term = ''; + @alias('searchService.searchables') searchableMap; + get type() { return this.args.type; } + get searchMethod() { + return this.args.searchable || 'exact'; + } + + get searchProperties() { + return this.args.filters.searchproperties; + } + @computed('term', 'args.search') get searchTerm() { return this.term || this.args.search || ''; } - @action - search(term) { - this.term = term; - return this.items; + @computed('type', 'searchMethod', 'filtered', 'searchProperties') + get searchable() { + const Searchable = + typeof this.searchMethod === 'string' + ? this.searchableMap[this.searchMethod] + : this.args.searchable; + return new Searchable(this.filtered, { + finders: Object.fromEntries( + Object.entries(this.searchService.predicate(this.type)).filter(([key, value]) => { + return typeof this.searchProperties === 'undefined' + ? true + : this.searchProperties.includes(key); + }) + ), + }); } - @computed('args{items,.items.content}') - get content() { - // TODO: Temporary little hack to ensure we detect DataSource proxy - // objects but not any other special Ember Proxy object like ember-data - // things. Remove this once we no longer need the Proxies - if (this.args.items.dispatchEvent === 'function') { - return this.args.items.content; + @computed('type', 'args.sort') + get comparator() { + if (typeof this.args.sort === 'undefined') { + return []; } - return this.args.items; + return this.sort.comparator(this.type)(this.args.sort); } @computed('comparator', 'searched') @@ -51,36 +70,42 @@ export default class DataCollectionComponent extends Component { return this.sorted; } - @computed('type', 'filtered', 'args.filters.searchproperties', 'searchTerm') + @computed('searchTerm', 'searchable', 'filtered') get searched() { if (this.searchTerm === '') { return this.filtered; } - const predicate = this.searchService.predicate(this.type); - const options = {}; - if (typeof get(this, 'args.filters.searchproperties') !== 'undefined') { - options.properties = this.args.filters.searchproperties; - } - return this.filtered.filter(predicate(this.searchTerm, options)); + return this.searchable.search(this.searchTerm); } @computed('type', 'content', 'args.filters') get filtered() { + // if we don't filter, return a copy of the content so we end up with what + // filter will return when filtering ED recordsets if (typeof this.args.filters === 'undefined') { - return this.content; + return this.content.slice(); } const predicate = this.filter.predicate(this.type); if (typeof predicate === 'undefined') { - return this.content; + return this.content.slice(); } return this.content.filter(predicate(this.args.filters)); } - @computed('type', 'args.sort') - get comparator() { - if (typeof this.args.sort === 'undefined') { - return []; + @computed('args.{items.[],items.content.[]}') + get content() { + // TODO: Temporary little hack to ensure we detect DataSource proxy + // objects but not any other special Ember Proxy object like ember-data + // things. Remove this once we no longer need the Proxies + if (this.args.items.dispatchEvent === 'function') { + return this.args.items.content; } - return this.sort.comparator(this.type)(this.args.sort); + return this.args.items; + } + + @action + search(term) { + this.term = term; + return this.items; } } diff --git a/ui/packages/consul-ui/app/search/predicates/acl.js b/ui/packages/consul-ui/app/search/predicates/acl.js index b4ec13d6c4..6e3992e014 100644 --- a/ui/packages/consul-ui/app/search/predicates/acl.js +++ b/ui/packages/consul-ui/app/search/predicates/acl.js @@ -1,4 +1,4 @@ export default { - ID: (item, value) => item.ID.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Name: (item, value) => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1, + ID: item => item.ID, + Name: item => item.Name, }; diff --git a/ui/packages/consul-ui/app/search/predicates/health-check.js b/ui/packages/consul-ui/app/search/predicates/health-check.js index a74d93445a..e644b52cb4 100644 --- a/ui/packages/consul-ui/app/search/predicates/health-check.js +++ b/ui/packages/consul-ui/app/search/predicates/health-check.js @@ -2,31 +2,12 @@ const asArray = function(arr) { return Array.isArray(arr) ? arr : arr.toArray(); }; export default { - Name: (item, value) => { - return item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1; - }, - Node: (item, value) => { - return item.Node.toLowerCase().indexOf(value.toLowerCase()) !== -1; - }, - Service: (item, value) => { - const lower = value.toLowerCase(); - return ( - item.ServiceName.toLowerCase().indexOf(lower) !== -1 || - item.ServiceID.toLowerCase().indexOf(lower) !== -1 - ); - }, - CheckID: (item, value) => (item.CheckID || '').toLowerCase().indexOf(value.toLowerCase()) !== -1, - Notes: (item, value) => - item.Notes.toString() - .toLowerCase() - .indexOf(value.toLowerCase()) !== -1, - Output: (item, value) => - item.Output.toString() - .toLowerCase() - .indexOf(value.toLowerCase()) !== -1, - ServiceTags: (item, value) => { - return asArray(item.ServiceTags || []).some( - item => item.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ); - }, + Name: (item, value) => item.Name, + Node: (item, value) => item.Node, + Service: (item, value) => item.ServiceName, + CheckID: (item, value) => item.CheckID || '', + ID: (item, value) => item.Service.ID || '', + Notes: (item, value) => item.Notes, + Output: (item, value) => item.Output, + ServiceTags: (item, value) => asArray(item.ServiceTags || []), }; diff --git a/ui/packages/consul-ui/app/search/predicates/intention.js b/ui/packages/consul-ui/app/search/predicates/intention.js index daf2e19814..b4f2f3e516 100644 --- a/ui/packages/consul-ui/app/search/predicates/intention.js +++ b/ui/packages/consul-ui/app/search/predicates/intention.js @@ -1,9 +1,7 @@ -const allLabel = 'All Services (*)'.toLowerCase(); +const allLabel = 'All Services (*)'; export default { - SourceName: (item, value) => - item.SourceName.toLowerCase().indexOf(value.toLowerCase()) !== -1 || - (item.SourceName === '*' && allLabel.indexOf(value.toLowerCase()) !== -1), - DestinationName: (item, value) => - item.DestinationName.toLowerCase().indexOf(value.toLowerCase()) !== -1 || - (item.DestinationName === '*' && allLabel.indexOf(value.toLowerCase()) !== -1), + SourceName: item => + [item.SourceName, item.SourceName === '*' ? allLabel : undefined].filter(Boolean), + DestinationName: item => + [item.DestinationName, item.DestinationName === '*' ? allLabel : undefined].filter(Boolean), }; diff --git a/ui/packages/consul-ui/app/search/predicates/kv.js b/ui/packages/consul-ui/app/search/predicates/kv.js index 09ea963317..76d5fe5a38 100644 --- a/ui/packages/consul-ui/app/search/predicates/kv.js +++ b/ui/packages/consul-ui/app/search/predicates/kv.js @@ -1,9 +1,8 @@ import rightTrim from 'consul-ui/utils/right-trim'; export default { - Key: (item, value) => + Key: item => rightTrim(item.Key.toLowerCase()) .split('/') .filter(item => Boolean(item)) - .pop() - .indexOf(value.toLowerCase()) !== -1, + .pop(), }; diff --git a/ui/packages/consul-ui/app/search/predicates/node.js b/ui/packages/consul-ui/app/search/predicates/node.js index d60b3a3e28..82dd3e731b 100644 --- a/ui/packages/consul-ui/app/search/predicates/node.js +++ b/ui/packages/consul-ui/app/search/predicates/node.js @@ -1,8 +1,5 @@ export default { - Node: (item, value) => item.Node.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Address: (item, value) => item.Address.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Meta: (item, value) => - Object.entries(item.Meta || {}).some(entry => - entry.some(item => item.toLowerCase().indexOf(value.toLowerCase()) !== -1) - ), + Node: item => item.Node, + Address: item => item.Address, + Meta: item => Object.entries(item.Meta || {}).reduce((prev, entry) => prev.concat(entry), []), }; diff --git a/ui/packages/consul-ui/app/search/predicates/nspace.js b/ui/packages/consul-ui/app/search/predicates/nspace.js index 6b202e46de..945b8d0a31 100644 --- a/ui/packages/consul-ui/app/search/predicates/nspace.js +++ b/ui/packages/consul-ui/app/search/predicates/nspace.js @@ -1,16 +1,12 @@ export default { - Name: (item, value) => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Description: (item, value) => item.Description.toLowerCase().indexOf(value.toLowerCase()) !== -1, + Name: (item, value) => item.Name, + Description: (item, value) => item.Description, Role: (item, value) => { const acls = item.ACLs || {}; - return (acls.RoleDefaults || []).some( - item => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ); + return (acls.RoleDefaults || []).map(item => item.Name); }, Policy: (item, value) => { const acls = item.ACLs || {}; - return (acls.PolicyDefaults || []).some( - item => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ); + return (acls.PolicyDefaults || []).map(item => item.Name); }, }; diff --git a/ui/packages/consul-ui/app/search/predicates/policy.js b/ui/packages/consul-ui/app/search/predicates/policy.js index 63d5ed8ab1..a9efe85829 100644 --- a/ui/packages/consul-ui/app/search/predicates/policy.js +++ b/ui/packages/consul-ui/app/search/predicates/policy.js @@ -1,4 +1,4 @@ export default { - Name: (item, value) => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Description: (item, value) => item.Description.toLowerCase().indexOf(value.toLowerCase()) !== -1, + Name: item => item.Name, + Description: item => item.Description, }; diff --git a/ui/packages/consul-ui/app/search/predicates/role.js b/ui/packages/consul-ui/app/search/predicates/role.js index e06644aa18..1cb534b76d 100644 --- a/ui/packages/consul-ui/app/search/predicates/role.js +++ b/ui/packages/consul-ui/app/search/predicates/role.js @@ -1,17 +1,10 @@ export default { - Name: (item, value) => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Description: (item, value) => item.Description.toLowerCase().indexOf(value.toLowerCase()) !== -1, + Name: (item, value) => item.Name, + Description: (item, value) => item.Description, Policy: (item, value) => { - return ( - (item.Policies || []).some( - item => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ) || - (item.ServiceIdentities || []).some( - item => item.ServiceName.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ) || - (item.NodeIdentities || []).some( - item => item.NodeName.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ) - ); + return (item.Policies || []) + .map(item => item.Name) + .concat((item.ServiceIdentities || []).map(item => item.ServiceName)) + .concat((item.NodeIdentities || []).map(item => item.NodeName)); }, }; diff --git a/ui/packages/consul-ui/app/search/predicates/service-instance.js b/ui/packages/consul-ui/app/search/predicates/service-instance.js index 42f7ef715f..e1d6b63d57 100644 --- a/ui/packages/consul-ui/app/search/predicates/service-instance.js +++ b/ui/packages/consul-ui/app/search/predicates/service-instance.js @@ -1,24 +1,11 @@ export default { - Name: (item, value) => { - return item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1; - }, - Tags: (item, value) => - (item.Service.Tags || []).some(item => item.toLowerCase().indexOf(value.toLowerCase()) !== -1), - ID: (item, value) => (item.Service.ID || '').toLowerCase().indexOf(value.toLowerCase()) !== -1, - Address: (item, value) => - item.Address.toString() - .toLowerCase() - .indexOf(value.toLowerCase()) !== -1, - Port: (item, value) => - item.Service.Port.toString() - .toLowerCase() - .indexOf(value.toLowerCase()) !== -1, - ['Service.Meta']: (item, value) => - Object.entries(item.Meta || item.Service.Meta || {}).some(entry => - entry.some(item => item.toLowerCase().indexOf(value.toLowerCase()) !== -1) - ), - ['Node.Meta']: (item, value) => - Object.entries(item.Node.Meta || {}).some(entry => - entry.some(item => item.toLowerCase().indexOf(value.toLowerCase()) !== -1) - ), + Name: item => item.Name, + Tags: item => item.Service.Tags || [], + ID: (item, value) => item.Service.ID || '', + Address: item => item.Address || '', + Port: item => (item.Service.Port || '').toString(), + ['Service.Meta']: item => + Object.entries(item.Service.Meta || {}).reduce((prev, entry) => prev.concat(entry), []), + ['Node.Meta']: item => + Object.entries(item.Node.Meta || {}).reduce((prev, entry) => prev.concat(entry), []), }; diff --git a/ui/packages/consul-ui/app/search/predicates/service.js b/ui/packages/consul-ui/app/search/predicates/service.js index e56b69c5db..7080801a3d 100644 --- a/ui/packages/consul-ui/app/search/predicates/service.js +++ b/ui/packages/consul-ui/app/search/predicates/service.js @@ -1,4 +1,4 @@ export default { - Name: (item, value) => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Tags: (item, value) => (item.Tags || []).some(item => item.toLowerCase().indexOf(value.toLowerCase()) !== -1) + Name: item => item.Name, + Tags: item => item.Tags || [], }; diff --git a/ui/packages/consul-ui/app/search/predicates/token.js b/ui/packages/consul-ui/app/search/predicates/token.js index 6fd7ae64c1..33a77df51a 100644 --- a/ui/packages/consul-ui/app/search/predicates/token.js +++ b/ui/packages/consul-ui/app/search/predicates/token.js @@ -1,20 +1,12 @@ export default { - Name: (item, value) => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Description: (item, value) => item.Description.toLowerCase().indexOf(value.toLowerCase()) !== -1, - AccessorID: (item, value) => item.AccessorID.toLowerCase().indexOf(value.toLowerCase()) !== -1, - Role: (item, value) => - (item.Roles || []).some(item => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1), + Name: (item, value) => item.Name, + Description: (item, value) => item.Description, + AccessorID: (item, value) => item.AccessorID, + Role: (item, value) => (item.Roles || []).map(item => item.Name), Policy: (item, value) => { - return ( - (item.Policies || []).some( - item => item.Name.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ) || - (item.ServiceIdentities || []).some( - item => item.ServiceName.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ) || - (item.NodeIdentities || []).some( - item => item.NodeName.toLowerCase().indexOf(value.toLowerCase()) !== -1 - ) - ); + return (item.Policies || []) + .map(item => item.Name) + .concat((item.ServiceIdentities || []).map(item => item.ServiceName)) + .concat((item.NodeIdentities || []).map(item => item.NodeName)); }, }; diff --git a/ui/packages/consul-ui/app/services/search.js b/ui/packages/consul-ui/app/services/search.js index 87393b841a..086bd43266 100644 --- a/ui/packages/consul-ui/app/services/search.js +++ b/ui/packages/consul-ui/app/services/search.js @@ -1,5 +1,6 @@ import Service from '@ember/service'; -import setHelpers from 'mnemonist/set'; + +import ExactSearch from 'consul-ui/utils/search/exact'; import intention from 'consul-ui/search/predicates/intention'; import upstreamInstance from 'consul-ui/search/predicates/upstream-instance'; @@ -14,36 +15,27 @@ import role from 'consul-ui/search/predicates/role'; import policy from 'consul-ui/search/predicates/policy'; import nspace from 'consul-ui/search/predicates/nspace'; -export const search = spec => { - let possible = Object.keys(spec); - return (term, options = {}) => { - const actual = [ - ...setHelpers.intersection(new Set(possible), new Set(options.properties || possible)), - ]; - return item => { - return ( - typeof actual.find(key => { - return spec[key](item, term); - }) !== 'undefined' - ); - }; - }; -}; const predicates = { - intention: search(intention), - service: search(service), - ['service-instance']: search(serviceInstance), - ['upstream-instance']: search(upstreamInstance), - ['health-check']: search(healthCheck), - node: search(node), - kv: search(kv), - acl: search(acl), - token: search(token), - role: search(role), - policy: search(policy), - nspace: search(nspace), + intention: intention, + service: service, + ['service-instance']: serviceInstance, + ['upstream-instance']: upstreamInstance, + ['health-check']: healthCheck, + node: node, + kv: kv, + acl: acl, + token: token, + role: role, + policy: policy, + nspace: nspace, }; + export default class SearchService extends Service { + searchables = { + exact: ExactSearch, + // regex: RegExpSearch, + // fuzzy: FuzzySearch, + }; predicate(name) { return predicates[name]; } diff --git a/ui/packages/consul-ui/app/utils/search/exact.js b/ui/packages/consul-ui/app/utils/search/exact.js new file mode 100644 index 0000000000..b39b18b70f --- /dev/null +++ b/ui/packages/consul-ui/app/utils/search/exact.js @@ -0,0 +1,12 @@ +import PredicateSearch from './predicate'; + +export default class ExactSearch extends PredicateSearch { + predicate(s) { + s = s.toLowerCase(); + return (item = '') => + item + .toString() + .toLowerCase() + .indexOf(s) !== -1; + } +} diff --git a/ui/packages/consul-ui/app/utils/search/fuzzy.js b/ui/packages/consul-ui/app/utils/search/fuzzy.js new file mode 100644 index 0000000000..892b284c8a --- /dev/null +++ b/ui/packages/consul-ui/app/utils/search/fuzzy.js @@ -0,0 +1,20 @@ +import Fuse from 'fuse.js'; + +export default class FuzzySearch { + constructor(items, options) { + this.fuse = new Fuse(items, { + includeMatches: true, + + shouldSort: false, // use our own sorting for the moment + threshold: 0.4, + keys: Object.keys(options.finders) || [], + getFn(item, key) { + return (options.finders[key[0]](item) || []).toString(); + }, + }); + } + + search(s) { + return this.fuse.search(s).map(item => item.item); + } +} diff --git a/ui/packages/consul-ui/app/utils/search/predicate.js b/ui/packages/consul-ui/app/utils/search/predicate.js new file mode 100644 index 0000000000..398821755f --- /dev/null +++ b/ui/packages/consul-ui/app/utils/search/predicate.js @@ -0,0 +1,22 @@ +export default class PredicateSearch { + constructor(items, options) { + this.items = items; + this.options = options; + } + + search(s) { + const predicate = this.predicate(s); + // Test the value of each key for each object against the regex + // All that match are returned. + return this.items.filter(item => { + return Object.entries(this.options.finders).some(([key, finder]) => { + const val = finder(item); + if (Array.isArray(val)) { + return val.some(predicate); + } else { + return predicate(val); + } + }); + }); + } +} diff --git a/ui/packages/consul-ui/app/utils/search/regexp.js b/ui/packages/consul-ui/app/utils/search/regexp.js new file mode 100644 index 0000000000..f71e13799b --- /dev/null +++ b/ui/packages/consul-ui/app/utils/search/regexp.js @@ -0,0 +1,15 @@ +import PredicateSearch from './predicate'; + +export default class RegExpSearch extends PredicateSearch { + predicate(s) { + let regex; + try { + regex = new RegExp(s, 'i'); + } catch (e) { + // Return a predicate that excludes everything; most likely due to an + // eager search of an incomplete regex + return () => false; + } + return item => regex.test(item); + } +} diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js index 96c863cdfd..dcadd5caed 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js @@ -1,33 +1,43 @@ -import predicates from 'consul-ui/search/predicates/acl'; -import { search as create } from 'consul-ui/services/search'; import { module, test } from 'qunit'; +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/acl'; + module('Unit | Search | Predicate | acl', function() { - const search = create(predicates); test('items are found by properties', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + ID: 'HIT-id', + Name: 'name', + }, + { + ID: 'id', + Name: 'name', + }, + { + ID: 'id', + Name: 'name-HIT', + }, + ], { - ID: 'HIT-id', - Name: 'name', - }, - { - ID: 'id', - Name: 'name', - }, - { - ID: 'id', - Name: 'name-HIT', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 2); }); test('items are not found', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + ID: 'id', + Name: 'name', + }, + ], { - ID: 'id', - Name: 'name', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); }); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/intention-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/intention-test.js index cfe09d7117..69506f876f 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/intention-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/intention-test.js @@ -1,51 +1,48 @@ -import predicates from 'consul-ui/search/predicates/intention'; -import { search as create } from 'consul-ui/services/search'; import { module, test } from 'qunit'; +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/intention'; + module('Unit | Search | Predicate | intention', function() { - const search = create(predicates); test('items are found by properties', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + SourceName: 'Hit', + DestinationName: 'destination', + }, + { + SourceName: 'source', + DestinationName: 'destination', + }, + { + SourceName: 'source', + DestinationName: 'hiT', + }, + ], { - SourceName: 'Hit', - DestinationName: 'destination', - }, - { - SourceName: 'source', - DestinationName: 'destination', - }, - { - SourceName: 'source', - DestinationName: 'hiT', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 2); }); test('items are not found', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + SourceName: 'source', + DestinationName: 'destination', + }, + ], { - SourceName: 'source', - DestinationName: 'destination', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); test('items are found by *', function(assert) { - const actual = [ - { - SourceName: '*', - DestinationName: 'destination', - }, - { - SourceName: 'source', - DestinationName: '*', - }, - ].filter(search('*')); - assert.equal(actual.length, 2); - }); - test("* items are found by searching anything in 'All Services (*)'", function(assert) { - ['All Services (*)', 'SerVices', '(*)', '*', 'vIces', 'lL Ser'].forEach(term => { - const actual = [ + const actual = new ExactSearch( + [ { SourceName: '*', DestinationName: 'destination', @@ -54,8 +51,31 @@ module('Unit | Search | Predicate | intention', function() { SourceName: 'source', DestinationName: '*', }, - ].filter(search(term)); - assert.equal(actual.length, 2); + ], + { + finders: predicates, + } + ).search('*'); + assert.equal(actual.length, 2); + }); + test("* items are found by searching anything in 'All Services (*)'", function(assert) { + const actual = new ExactSearch( + [ + { + SourceName: '*', + DestinationName: 'destination', + }, + { + SourceName: 'source', + DestinationName: '*', + }, + ], + { + finders: predicates, + } + ); + ['All Services (*)', 'SerVices', '(*)', '*', 'vIces', 'lL Ser'].forEach(term => { + assert.equal(actual.search(term).length, 2); }); }); }); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/kv-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/kv-test.js index f45b415b04..91085d38c6 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/kv-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/kv-test.js @@ -1,32 +1,42 @@ -import predicates from 'consul-ui/search/predicates/kv'; -import { search as create } from 'consul-ui/services/search'; import { module, test } from 'qunit'; +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/kv'; + module('Unit | Search | Predicate | kv', function() { - const search = create(predicates); test('items are found by properties', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Key: 'HIT-here', + }, + { + Key: 'folder-HIT/', + }, + { + Key: 'excluded', + }, + { + Key: 'really/long/path/HIT-here', + }, + ], { - Key: 'HIT-here', - }, - { - Key: 'folder-HIT/', - }, - { - Key: 'excluded', - }, - { - Key: 'really/long/path/HIT-here', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 3); }); test('items are not found', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Key: 'key', + }, + ], { - Key: 'key', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); }); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/node-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/node-test.js index 41da3adc6c..1a5bae1501 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/node-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/node-test.js @@ -1,47 +1,67 @@ -import predicates from 'consul-ui/search/predicates/node'; -import { search as create } from 'consul-ui/services/search'; import { module, test } from 'qunit'; +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/node'; + module('Unit | Search | Predicate | node', function() { - const search = create(predicates); test('items are found by name', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Node: 'node-HIT', + Address: '10.0.0.0', + }, + { + Node: 'node', + Address: '10.0.0.0', + }, + ], { - Node: 'node-HIT', - Address: '10.0.0.0', - }, - { - Node: 'node', - Address: '10.0.0.0', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 1); }); test('items are found by IP address', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Node: 'node-HIT', + Address: '10.0.0.0', + }, + ], { - Node: 'node-HIT', - Address: '10.0.0.0', - }, - ].filter(search('10')); + finders: predicates, + } + ).search('10'); assert.equal(actual.length, 1); }); test('items are not found by name', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Node: 'name', + Address: '10.0.0.0', + }, + ], { - Node: 'name', - Address: '10.0.0.0', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); test('items are not found by IP address', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Node: 'name', + Address: '10.0.0.0', + }, + ], { - Node: 'name', - Address: '10.0.0.0', - }, - ].filter(search('9')); + finders: predicates, + } + ).search('9'); assert.equal(actual.length, 0); }); }); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/policy-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/policy-test.js index 20e57522dd..2b83f6e3e1 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/policy-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/policy-test.js @@ -1,29 +1,39 @@ -import predicates from 'consul-ui/search/predicates/policy'; -import { search as create } from 'consul-ui/services/search'; import { module, test } from 'qunit'; +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/policy'; + module('Unit | Search | Predicate | policy', function() { - const search = create(predicates); test('items are found by properties', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Name: 'name-HIT', + Description: 'description', + }, + { + Name: 'name', + Description: 'desc-HIT-ription', + }, + ], { - Name: 'name-HIT', - Description: 'description', - }, - { - Name: 'name', - Description: 'desc-HIT-ription', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 2); }); test('items are not found', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Name: 'name', + Description: 'description', + }, + ], { - Name: 'name', - Description: 'description', - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); }); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/role-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/role-test.js index 638b945b7e..70a826b958 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/role-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/role-test.js @@ -1,78 +1,93 @@ -import predicates from 'consul-ui/search/predicates/role'; -import { search as create } from 'consul-ui/services/search'; import { module, test } from 'qunit'; +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/role'; + module('Unit | Search | Predicate | role', function() { - const search = create(predicates); test('items are found by properties', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Name: 'name-HIT', + Description: 'description', + Policies: [], + }, + { + Name: 'name', + Description: 'desc-HIT-ription', + Policies: [], + }, + { + Name: 'name', + Description: 'description', + Policies: [{ Name: 'policy' }, { Name: 'policy-HIT' }], + }, + { + Name: 'name', + Description: 'description', + ServiceIdentities: [ + { ServiceName: 'service-identity' }, + { ServiceName: 'service-identity-HIT' }, + ], + }, + ], { - Name: 'name-HIT', - Description: 'description', - Policies: [], - }, - { - Name: 'name', - Description: 'desc-HIT-ription', - Policies: [], - }, - { - Name: 'name', - Description: 'description', - Policies: [{ Name: 'policy' }, { Name: 'policy-HIT' }], - }, - { - Name: 'name', - Description: 'description', - ServiceIdentities: [ - { ServiceName: 'service-identity' }, - { ServiceName: 'service-identity-HIT' }, - ], - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 4); }); test('items are not found', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Name: 'name', + Description: 'description', + Policies: [], + }, + { + Name: 'name', + Description: 'description', + Policies: [{ Name: 'policy' }, { Name: 'policy-second' }], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + ServiceIdenitities: [{ ServiceName: 'si' }, { ServiceName: 'si-second' }], + }, + ], { - Name: 'name', - Description: 'description', - Policies: [], - }, - { - Name: 'name', - Description: 'description', - Policies: [{ Name: 'policy' }, { Name: 'policy-second' }], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - ServiceIdenitities: [{ ServiceName: 'si' }, { ServiceName: 'si-second' }], - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); test('arraylike things can be empty', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + Name: 'name', + Description: 'description', + }, + { + Name: 'name', + Description: 'description', + Policies: null, + ServiceIdentities: null, + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Policies: [], + ServiceIdentities: [], + }, + ], { - Name: 'name', - Description: 'description', - }, - { - Name: 'name', - Description: 'description', - Policies: null, - ServiceIdentities: null, - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Policies: [], - ServiceIdentities: [], - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); }); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/service-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/service-test.js index 75fdfdacbb..1f3bd0ade8 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/service-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/service-test.js @@ -1,54 +1,63 @@ -import { search } from 'consul-ui/services/search'; -import spec from 'consul-ui/search/predicates/service'; import { module, test } from 'qunit'; -module('Unit | Search | Filter | service', function() { - const predicate = search(spec); +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/service'; + +module('Unit | Search | Predicate | service', function() { test('items are found by properties', function(assert) { - [ + const actual = new ExactSearch( + [ + { + Name: 'name-HIT', + Tags: [], + }, + { + Name: 'name', + Tags: ['tag', 'tag-withHiT'], + }, + ], { - Name: 'name-HIT', - Tags: [], - }, - { - Name: 'name', - Tags: ['tag', 'tag-withHiT'], - }, - ].forEach(function(item) { - const actual = predicate('hit')(item); - assert.ok(actual); - }); + finders: predicates, + } + ).search('hit'); + assert.equal(actual.length, 2); }); test('items are not found', function(assert) { - [ + const actual = new ExactSearch( + [ + { + Name: 'name', + }, + { + Name: 'name', + Tags: ['one', 'two'], + }, + ], { - Name: 'name', - }, - { - Name: 'name', - Tags: ['one', 'two'], - }, - ].forEach(function(item) { - const actual = predicate('hit')(item); - assert.notOk(actual); - }); + finders: predicates, + } + ).search('hit'); + assert.equal(actual.length, 0); }); test('tags can be empty', function(assert) { - [ + const actual = new ExactSearch( + [ + { + Name: 'name', + }, + { + Name: 'name', + Tags: null, + }, + { + Name: 'name', + Tags: [], + }, + ], { - Name: 'name', - }, - { - Name: 'name', - Tags: null, - }, - { - Name: 'name', - Tags: [], - }, - ].forEach(function(item) { - const actual = predicate('hit')(item); - assert.notOk(actual); - }); + finders: predicates, + } + ).search('hit'); + assert.equal(actual.length, 0); }); }); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/token-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/token-test.js index 477f08073d..cfbacb216f 100644 --- a/ui/packages/consul-ui/tests/unit/search/predicates/token-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/token-test.js @@ -1,126 +1,141 @@ -import predicates from 'consul-ui/search/predicates/token'; -import { search as create } from 'consul-ui/services/search'; import { module, test } from 'qunit'; -module('Unit | Search | Filter | token', function() { - const search = create(predicates); +import ExactSearch from 'consul-ui/utils/search/exact'; +import predicates from 'consul-ui/search/predicates/token'; + +module('Unit | Search | Predicate | token', function() { test('items are found by properties', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Policies: [], + }, + { + AccessorID: 'HIT-id', + Name: 'name', + Description: 'description', + Policies: [], + }, + { + AccessorID: 'id', + Name: 'name-HIT', + Description: 'description', + Policies: [], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'desc-HIT-ription', + Policies: [], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Policies: [{ Name: 'policy' }, { Name: 'policy-HIT' }], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Roles: [{ Name: 'role' }, { Name: 'role-HIT' }], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + ServiceIdentities: [ + { ServiceName: 'service-identity' }, + { ServiceName: 'service-identity-HIT' }, + ], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + NodeIdentities: [{ NodeName: 'node-identity' }, { NodeName: 'node-identity-HIT' }], + }, + ], { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Policies: [], - }, - { - AccessorID: 'HIT-id', - Name: 'name', - Description: 'description', - Policies: [], - }, - { - AccessorID: 'id', - Name: 'name-HIT', - Description: 'description', - Policies: [], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'desc-HIT-ription', - Policies: [], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Policies: [{ Name: 'policy' }, { Name: 'policy-HIT' }], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Roles: [{ Name: 'role' }, { Name: 'role-HIT' }], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - ServiceIdentities: [ - { ServiceName: 'service-identity' }, - { ServiceName: 'service-identity-HIT' }, - ], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - NodeIdentities: [{ NodeName: 'node-identity' }, { NodeName: 'node-identity-HIT' }], - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 7); }); test('items are not found', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Policies: [], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Policies: [{ Name: 'policy' }, { Name: 'policy-second' }], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Roles: [{ Name: 'role' }, { Name: 'role-second' }], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + ServiceIdentities: [{ ServiceName: 'si' }, { ServiceName: 'si-second' }], + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + NodeIdentities: [{ NodeName: 'si' }, { NodeName: 'si-second' }], + }, + ], { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Policies: [], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Policies: [{ Name: 'policy' }, { Name: 'policy-second' }], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Roles: [{ Name: 'role' }, { Name: 'role-second' }], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - ServiceIdentities: [{ ServiceName: 'si' }, { ServiceName: 'si-second' }], - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - NodeIdentities: [{ NodeName: 'si' }, { NodeName: 'si-second' }], - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); test('arraylike things can be empty', function(assert) { - const actual = [ + const actual = new ExactSearch( + [ + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Policies: null, + Roles: null, + ServiceIdentities: null, + NodeIdentities: null, + }, + { + AccessorID: 'id', + Name: 'name', + Description: 'description', + Policies: [], + Roles: [], + ServiceIdentities: [], + NodeIdentities: [], + }, + ], { - AccessorID: 'id', - Name: 'name', - Description: 'description', - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Policies: null, - Roles: null, - ServiceIdentities: null, - NodeIdentities: null, - }, - { - AccessorID: 'id', - Name: 'name', - Description: 'description', - Policies: [], - Roles: [], - ServiceIdentities: [], - NodeIdentities: [], - }, - ].filter(search('hit')); + finders: predicates, + } + ).search('hit'); assert.equal(actual.length, 0); }); });