From 8945430cf9d20309cb81f3ab57fc5859d8b62431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 15 Jan 2018 15:33:14 +0100 Subject: [PATCH] Replace fuzzy with fuse and stricter options (#6132) * Replace fuzzy with fuse and stricter options * Fix performance issue on jquery-ui autocompleter https://bugs.jqueryui.com/ticket/15082 [ci skip] --- .../lazyloaded/lazyloaded-autocompleter.ts | 24 +++++++++++++++---- .../project-menu-autocomplete.component.ts | 1 + frontend/app/helpers/debug_output.ts | 8 ++++--- frontend/app/vendors.js | 2 +- frontend/npm-shrinkwrap.json | 15 ++++++------ frontend/package.json | 4 ++-- .../projects/project_autocomplete_spec.rb | 4 ++-- 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/frontend/app/components/common/autocomplete/lazyloaded/lazyloaded-autocompleter.ts b/frontend/app/components/common/autocomplete/lazyloaded/lazyloaded-autocompleter.ts index d1ab324ccc..25a40cdc4c 100644 --- a/frontend/app/components/common/autocomplete/lazyloaded/lazyloaded-autocompleter.ts +++ b/frontend/app/components/common/autocomplete/lazyloaded/lazyloaded-autocompleter.ts @@ -1,4 +1,5 @@ -import * as fuzzy from 'fuzzy'; +import * as Fuse from 'fuse.js'; +import {timeOutput} from '../../../../helpers/debug_output'; export interface IAutocompleteItem { label:string; @@ -13,6 +14,9 @@ export abstract class ILazyAutocompleterBridge { // Input autocomplete element public input:JQuery; + // Fuzzy instance for the results + public fuseInstance:any; + public constructor(public widgetName:string) { LazyLoadedAutocompleter.register(widgetName, this); } @@ -55,9 +59,7 @@ export abstract class ILazyAutocompleterBridge { return items; } - const options = { extract: (i:IAutocompleteItem) => i.label }; - const results = fuzzy.filter(term, items, options); - return results.map(el => el.original); + return this.fuseInstance.search(term) as any; } /** @@ -85,13 +87,25 @@ export abstract class ILazyAutocompleterBridge { this.currentPage = 0; this.input = input; this.input[this.widgetName].call(this.input, this.setupParams(items)); + const options = { + shouldSort: true, + tokenize: true, + threshold: 0.2, + location: 0, + distance: 100, + maxPatternLength: 16, + minMatchCharLength: 2, + keys: ['label'] + }; + + this.fuseInstance = new Fuse(items, options); } protected setupParams(autocompleteValues:IAutocompleteItem[]) { const ctrl = this; return { - delay: 0, + delay: 50, source: function (request:any, response:any) { const fuzzyResults = ctrl.fuzzySearch(autocompleteValues, request.term); response(ctrl.augmentedResultSet(autocompleteValues, fuzzyResults)); diff --git a/frontend/app/components/projects/project-menu-autocomplete/project-menu-autocomplete.component.ts b/frontend/app/components/projects/project-menu-autocomplete/project-menu-autocomplete.component.ts index 1278932bee..2ec996c904 100644 --- a/frontend/app/components/projects/project-menu-autocomplete/project-menu-autocomplete.component.ts +++ b/frontend/app/components/projects/project-menu-autocomplete/project-menu-autocomplete.component.ts @@ -180,6 +180,7 @@ export class ProjectMenuAutocompleteController extends ILazyAutocompleterBridge< */ protected augmentedResultSet(items:ProjectAutocompleteItem[], matched:ProjectAutocompleteItem[]) { const matches = matched.map(el => el.object.identifier); + console.log(matches); const matchedParents = _.flatten(matched.map(el => el.object.parents)); const results:ProjectAutocompleteItem[] = []; diff --git a/frontend/app/helpers/debug_output.ts b/frontend/app/helpers/debug_output.ts index f43b983d86..c1d460e950 100644 --- a/frontend/app/helpers/debug_output.ts +++ b/frontend/app/helpers/debug_output.ts @@ -16,15 +16,17 @@ export function debugLog(...args:any[]) { whenDebugging(() => console.log('[DEBUG] ', args.join('-- '))); } -export function timeOutput(msg:string, cb:() => void) { +export function timeOutput(msg:string, cb:() => void):any { if (DEBUG) { var t0 = performance.now(); - cb(); + var results = cb(); var t1 = performance.now(); console.log(`%c${msg} [completed in ${(t1 - t0)} milliseconds.`, 'color:#00A093;'); + + return results; } else { - cb(); + return cb(); } } diff --git a/frontend/app/vendors.js b/frontend/app/vendors.js index e9b89c9c2a..6876d0fa86 100644 --- a/frontend/app/vendors.js +++ b/frontend/app/vendors.js @@ -81,7 +81,7 @@ require('jquery.caret'); // Text highlight for autocompleter require('mark.js/dist/jquery.mark.min.js'); // Micro Text fuzzy search library -require('fuzzy'); +require('fuse.js'); require('at.js/jquery.atwho.min.js'); diff --git a/frontend/npm-shrinkwrap.json b/frontend/npm-shrinkwrap.json index b07a4e648a..cb00d09491 100644 --- a/frontend/npm-shrinkwrap.json +++ b/frontend/npm-shrinkwrap.json @@ -3178,10 +3178,10 @@ } } }, - "fuzzy": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz", - "integrity": "sha1-THbsL/CsGjap3M+aAN+GIweNTtg=" + "fuse.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz", + "integrity": "sha1-8ESOgGmFW/Kj5oPNwdMg5+KgfvQ=" }, "generate-function": { "version": "2.0.0", @@ -3742,9 +3742,10 @@ } }, "jquery-ui": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" + "version": "git://github.com/jquery/jquery-ui.git#74f8a0ac952f6f45f773312292baef1c26d81300", + "requires": { + "jquery": "3.1.1" + } }, "jquery-ujs": { "version": "1.2.2", diff --git a/frontend/package.json b/frontend/package.json index 29d66bff0f..c5dc7cd425 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -81,12 +81,12 @@ "file-loader": "^0.8.1", "fork-ts-checker-webpack-plugin": "^0.2.8", "foundation-apps": "1.1.0", - "fuzzy": "^0.1.3", + "fuse.js": "^3.2.0", "glob": "^4.5.3", "happypack": "^4.0.0", "html-loader": "^0.2.3", "jquery": "^3.1.1", - "jquery-ui": "^1.12.1", + "jquery-ui": "git://github.com/jquery/jquery-ui#74f8a0ac952f6f45f773312292baef1c26d81300", "jquery-ujs": "^1.2.2", "jquery.caret": "^0.3.1", "json5": "^0.5.0", diff --git a/spec/features/projects/project_autocomplete_spec.rb b/spec/features/projects/project_autocomplete_spec.rb index 34671bd2e1..f87d6670fb 100644 --- a/spec/features/projects/project_autocomplete_spec.rb +++ b/spec/features/projects/project_autocomplete_spec.rb @@ -91,9 +91,9 @@ describe 'Projects autocomplete page', type: :feature, js: true do top_menu.search 'Plain other project' within(top_menu.search_results) do - expect(page).to have_no_selector('.ui-menu-item-wrapper', text: 'Plain project') expect(page).to have_selector('.ui-state-disabled .ui-menu-item-wrapper', text: 'foobar') - expect(page).to have_selector('.ui-menu-item-wrapper', text: '» Plain other project') + expect(page).to have_selector('.ui-menu-item-wrapper.ui-state-active', text: '» Plain other project') + expect(page).to have_selector('.ui-menu-item-wrapper', text: 'Plain project') end # Visit a project