diff --git a/ui/app/controllers/jobs/index.js b/ui/app/controllers/jobs/index.js index 4df1f084c3f..769b48115bd 100644 --- a/ui/app/controllers/jobs/index.js +++ b/ui/app/controllers/jobs/index.js @@ -25,6 +25,8 @@ export default Controller.extend(Sortable, Searchable, { sortDescending: true, searchProps: computed(() => ['id', 'name']), + fuzzySearchProps: computed(() => ['name']), + fuzzySearchEnabled: true, /** Filtered jobs are those that match the selected namespace and aren't children diff --git a/ui/app/mixins/searchable.js b/ui/app/mixins/searchable.js index 26557f75a46..815956c694c 100644 --- a/ui/app/mixins/searchable.js +++ b/ui/app/mixins/searchable.js @@ -1,5 +1,7 @@ import Mixin from '@ember/object/mixin'; import { get, computed } from '@ember/object'; +import { reads } from '@ember/object/computed'; +import Fuse from 'npm:fuse.js'; /** Searchable mixin @@ -9,6 +11,12 @@ import { get, computed } from '@ember/object'; Properties to override: - searchTerm: the string to use as a query - searchProps: the props on each object to search + -- exactMatchSearchProps: the props for exact search when props are different per search type + -- regexSearchProps: the props for regex search when props are different per search type + -- fuzzySearchProps: the props for fuzzy search when props are different per search type + - exactMatchEnabled: (true) disable to not use the exact match search type + - fuzzySearchEnabled: (false) enable to use the fuzzy search type + - regexEnabled: (true) disable to disable the regex search type - listToSearch: the list of objects to search Properties provided: @@ -17,17 +25,83 @@ import { get, computed } from '@ember/object'; export default Mixin.create({ searchTerm: '', listToSearch: computed(() => []), + searchProps: null, + exactMatchSearchProps: reads('searchProps'), + regexSearchProps: reads('searchProps'), + fuzzySearchProps: reads('searchProps'), - listSearched: computed('searchTerm', 'listToSearch.[]', 'searchProps.[]', function() { - const searchTerm = this.get('searchTerm'); - if (searchTerm && searchTerm.length) { - return regexSearch(searchTerm, this.get('listToSearch'), this.get('searchProps')); - } - return this.get('listToSearch'); + // Three search modes + exactMatchEnabled: true, + fuzzySearchEnabled: false, + regexEnabled: true, + + fuse: computed('listToSearch.[]', 'fuzzySearchProps.[]', function() { + return new Fuse(this.get('listToSearch'), { + shouldSort: true, + threshold: 0.4, + location: 0, + distance: 100, + tokenize: true, + matchAllTokens: true, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: this.get('fuzzySearchProps') || [], + getFn(item, key) { + return get(item, key); + }, + }); }), + + listSearched: computed( + 'searchTerm', + 'listToSearch.[]', + 'exactMatchEnabled', + 'fuzzySearchEnabled', + 'regexEnabled', + 'exactMatchSearchProps.[]', + 'fuzzySearchProps.[]', + 'regexSearchProps.[]', + function() { + const searchTerm = this.get('searchTerm').trim(); + + if (!searchTerm || !searchTerm.length) { + return this.get('listToSearch'); + } + + const results = []; + + if (this.get('exactMatchEnabled')) { + results.push( + ...exactMatchSearch( + searchTerm, + this.get('listToSearch'), + this.get('exactMatchSearchProps') + ) + ); + } + + if (this.get('fuzzySearchEnabled')) { + results.push(...this.get('fuse').search(searchTerm)); + } + + if (this.get('regexEnabled')) { + results.push( + ...regexSearch(searchTerm, this.get('listToSearch'), this.get('regexSearchProps')) + ); + } + + return results.uniq(); + } + ), }); +function exactMatchSearch(term, list, keys) { + if (term.length) { + return list.filter(item => keys.some(key => get(item, key) === term)); + } +} + function regexSearch(term, list, keys) { if (term.length) { try { @@ -38,5 +112,6 @@ function regexSearch(term, list, keys) { } catch (e) { // Swallow the error; most likely due to an eager search of an incomplete regex } + return []; } } diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 34e24c33a22..8767c2355ab 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -38,6 +38,9 @@