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 23d6bb3eb..bf9e905af 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 b4ec13d6c..6e3992e01 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 a74d93445..e644b52cb 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 daf2e1981..b4f2f3e51 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 09ea96331..76d5fe5a3 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 d60b3a3e2..82dd3e731 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 6b202e46d..945b8d0a3 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 63d5ed8ab..a9efe8582 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 e06644aa1..1cb534b76 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 42f7ef715..e1d6b63d5 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 e56b69c5d..7080801a3 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 6fd7ae64c..33a77df51 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 87393b841..086bd4326 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 000000000..b39b18b70 --- /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 000000000..892b284c8 --- /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 000000000..398821755 --- /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 000000000..f71e13799 --- /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 96c863cdf..dcadd5cae 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 cfe09d711..69506f876 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 f45b415b0..91085d38c 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 41da3adc6..1a5bae150 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 20e57522d..2b83f6e3e 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 638b945b7..70a826b95 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 75fdfdacb..1f3bd0ade 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 477f08073..cfbacb216 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); }); });