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]
pull/6134/head
Oliver Günther 7 years ago committed by GitHub
parent e1e5aa7ff1
commit 8945430cf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      frontend/app/components/common/autocomplete/lazyloaded/lazyloaded-autocompleter.ts
  2. 1
      frontend/app/components/projects/project-menu-autocomplete/project-menu-autocomplete.component.ts
  3. 8
      frontend/app/helpers/debug_output.ts
  4. 2
      frontend/app/vendors.js
  5. 15
      frontend/npm-shrinkwrap.json
  6. 4
      frontend/package.json
  7. 4
      spec/features/projects/project_autocomplete_spec.rb

@ -1,4 +1,5 @@
import * as fuzzy from 'fuzzy'; import * as Fuse from 'fuse.js';
import {timeOutput} from '../../../../helpers/debug_output';
export interface IAutocompleteItem<T> { export interface IAutocompleteItem<T> {
label:string; label:string;
@ -13,6 +14,9 @@ export abstract class ILazyAutocompleterBridge<T> {
// Input autocomplete element // Input autocomplete element
public input:JQuery; public input:JQuery;
// Fuzzy instance for the results
public fuseInstance:any;
public constructor(public widgetName:string) { public constructor(public widgetName:string) {
LazyLoadedAutocompleter.register(widgetName, this); LazyLoadedAutocompleter.register(widgetName, this);
} }
@ -55,9 +59,7 @@ export abstract class ILazyAutocompleterBridge<T> {
return items; return items;
} }
const options = { extract: (i:IAutocompleteItem<T>) => i.label }; return this.fuseInstance.search(term) as any;
const results = fuzzy.filter(term, items, options);
return results.map(el => el.original);
} }
/** /**
@ -85,13 +87,25 @@ export abstract class ILazyAutocompleterBridge<T> {
this.currentPage = 0; this.currentPage = 0;
this.input = input; this.input = input;
this.input[this.widgetName].call(this.input, this.setupParams(items)); 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<T>[]) { protected setupParams(autocompleteValues:IAutocompleteItem<T>[]) {
const ctrl = this; const ctrl = this;
return { return {
delay: 0, delay: 50,
source: function (request:any, response:any) { source: function (request:any, response:any) {
const fuzzyResults = ctrl.fuzzySearch(autocompleteValues, request.term); const fuzzyResults = ctrl.fuzzySearch(autocompleteValues, request.term);
response(ctrl.augmentedResultSet(autocompleteValues, fuzzyResults)); response(ctrl.augmentedResultSet(autocompleteValues, fuzzyResults));

@ -180,6 +180,7 @@ export class ProjectMenuAutocompleteController extends ILazyAutocompleterBridge<
*/ */
protected augmentedResultSet(items:ProjectAutocompleteItem[], matched:ProjectAutocompleteItem[]) { protected augmentedResultSet(items:ProjectAutocompleteItem[], matched:ProjectAutocompleteItem[]) {
const matches = matched.map(el => el.object.identifier); const matches = matched.map(el => el.object.identifier);
console.log(matches);
const matchedParents = _.flatten(matched.map(el => el.object.parents)); const matchedParents = _.flatten(matched.map(el => el.object.parents));
const results:ProjectAutocompleteItem[] = []; const results:ProjectAutocompleteItem[] = [];

@ -16,15 +16,17 @@ export function debugLog(...args:any[]) {
whenDebugging(() => console.log('[DEBUG] ', args.join('-- '))); whenDebugging(() => console.log('[DEBUG] ', args.join('-- ')));
} }
export function timeOutput(msg:string, cb:() => void) { export function timeOutput(msg:string, cb:() => void):any {
if (DEBUG) { if (DEBUG) {
var t0 = performance.now(); var t0 = performance.now();
cb(); var results = cb();
var t1 = performance.now(); var t1 = performance.now();
console.log(`%c${msg} [completed in ${(t1 - t0)} milliseconds.`, 'color:#00A093;'); console.log(`%c${msg} [completed in ${(t1 - t0)} milliseconds.`, 'color:#00A093;');
return results;
} else { } else {
cb(); return cb();
} }
} }

@ -81,7 +81,7 @@ require('jquery.caret');
// Text highlight for autocompleter // Text highlight for autocompleter
require('mark.js/dist/jquery.mark.min.js'); require('mark.js/dist/jquery.mark.min.js');
// Micro Text fuzzy search library // Micro Text fuzzy search library
require('fuzzy'); require('fuse.js');
require('at.js/jquery.atwho.min.js'); require('at.js/jquery.atwho.min.js');

@ -3178,10 +3178,10 @@
} }
} }
}, },
"fuzzy": { "fuse.js": {
"version": "0.1.3", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz", "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz",
"integrity": "sha1-THbsL/CsGjap3M+aAN+GIweNTtg=" "integrity": "sha1-8ESOgGmFW/Kj5oPNwdMg5+KgfvQ="
}, },
"generate-function": { "generate-function": {
"version": "2.0.0", "version": "2.0.0",
@ -3742,9 +3742,10 @@
} }
}, },
"jquery-ui": { "jquery-ui": {
"version": "1.12.1", "version": "git://github.com/jquery/jquery-ui.git#74f8a0ac952f6f45f773312292baef1c26d81300",
"resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", "requires": {
"integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" "jquery": "3.1.1"
}
}, },
"jquery-ujs": { "jquery-ujs": {
"version": "1.2.2", "version": "1.2.2",

@ -81,12 +81,12 @@
"file-loader": "^0.8.1", "file-loader": "^0.8.1",
"fork-ts-checker-webpack-plugin": "^0.2.8", "fork-ts-checker-webpack-plugin": "^0.2.8",
"foundation-apps": "1.1.0", "foundation-apps": "1.1.0",
"fuzzy": "^0.1.3", "fuse.js": "^3.2.0",
"glob": "^4.5.3", "glob": "^4.5.3",
"happypack": "^4.0.0", "happypack": "^4.0.0",
"html-loader": "^0.2.3", "html-loader": "^0.2.3",
"jquery": "^3.1.1", "jquery": "^3.1.1",
"jquery-ui": "^1.12.1", "jquery-ui": "git://github.com/jquery/jquery-ui#74f8a0ac952f6f45f773312292baef1c26d81300",
"jquery-ujs": "^1.2.2", "jquery-ujs": "^1.2.2",
"jquery.caret": "^0.3.1", "jquery.caret": "^0.3.1",
"json5": "^0.5.0", "json5": "^0.5.0",

@ -91,9 +91,9 @@ describe 'Projects autocomplete page', type: :feature, js: true do
top_menu.search 'Plain other project' top_menu.search 'Plain other project'
within(top_menu.search_results) do 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: '<strong>foobar</strong>') expect(page).to have_selector('.ui-state-disabled .ui-menu-item-wrapper', text: '<strong>foobar</strong>')
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 end
# Visit a project # Visit a project

Loading…
Cancel
Save