Skip to content

Commit

Permalink
ui: Fuzzy and Regex searching (#9424)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
johncowen authored Dec 18, 2020
1 parent 9ca4e43 commit 99f1027
Show file tree
Hide file tree
Showing 25 changed files with 638 additions and 500 deletions.
79 changes: 52 additions & 27 deletions ui/packages/consul-ui/app/components/data-collection/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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')
Expand All @@ -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;
}
}
4 changes: 2 additions & 2 deletions ui/packages/consul-ui/app/search/predicates/acl.js
Original file line number Diff line number Diff line change
@@ -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,
};
35 changes: 8 additions & 27 deletions ui/packages/consul-ui/app/search/predicates/health-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || []),
};
12 changes: 5 additions & 7 deletions ui/packages/consul-ui/app/search/predicates/intention.js
Original file line number Diff line number Diff line change
@@ -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),
};
5 changes: 2 additions & 3 deletions ui/packages/consul-ui/app/search/predicates/kv.js
Original file line number Diff line number Diff line change
@@ -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(),
};
9 changes: 3 additions & 6 deletions ui/packages/consul-ui/app/search/predicates/node.js
Original file line number Diff line number Diff line change
@@ -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), []),
};
12 changes: 4 additions & 8 deletions ui/packages/consul-ui/app/search/predicates/nspace.js
Original file line number Diff line number Diff line change
@@ -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);
},
};
4 changes: 2 additions & 2 deletions ui/packages/consul-ui/app/search/predicates/policy.js
Original file line number Diff line number Diff line change
@@ -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,
};
19 changes: 6 additions & 13 deletions ui/packages/consul-ui/app/search/predicates/role.js
Original file line number Diff line number Diff line change
@@ -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));
},
};
31 changes: 9 additions & 22 deletions ui/packages/consul-ui/app/search/predicates/service-instance.js
Original file line number Diff line number Diff line change
@@ -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), []),
};
4 changes: 2 additions & 2 deletions ui/packages/consul-ui/app/search/predicates/service.js
Original file line number Diff line number Diff line change
@@ -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 || [],
};
24 changes: 8 additions & 16 deletions ui/packages/consul-ui/app/search/predicates/token.js
Original file line number Diff line number Diff line change
@@ -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));
},
};
48 changes: 20 additions & 28 deletions ui/packages/consul-ui/app/services/search.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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];
}
Expand Down
Loading

0 comments on commit 99f1027

Please sign in to comment.