diff --git a/lib/features/search/BpmnSearchProvider.js b/lib/features/search/BpmnSearchProvider.js index cbd05c5adb..ab98a2d214 100644 --- a/lib/features/search/BpmnSearchProvider.js +++ b/lib/features/search/BpmnSearchProvider.js @@ -1,11 +1,8 @@ -import { - map, - filter, - sortBy -} from 'min-dash'; +import { omit } from 'min-dash'; import { - getLabel + getLabel, + isLabel } from '../../util/LabelUtil'; /** @@ -48,48 +45,114 @@ BpmnSearchProvider.prototype.find = function(pattern) { var rootElement = this._canvas.getRootElement(); var elements = this._elementRegistry.filter(function(element) { - if (element.labelTarget) { - return false; - } - return true; - }); - - // do not include root element - elements = filter(elements, function(element) { - return element !== rootElement; - }); - - elements = map(elements, function(element) { - return { - primaryTokens: matchAndSplit(getLabel(element), pattern), - secondaryTokens: matchAndSplit(element.id, pattern), - element: element - }; + return !isLabel(element) && element !== rootElement; }); - // exclude non-matched elements - elements = filter(elements, function(element) { - return hasMatched(element.primaryTokens) || hasMatched(element.secondaryTokens); - }); - - elements = sortBy(elements, function(element) { - return getLabel(element.element) + element.element.id; - }); - - return elements; + return elements + .reduce(function(results, element) { + var label = getLabel(element); + + var primaryTokens = findMatches(label, pattern), + secondaryTokens = findMatches(element.id, pattern); + + if (hasMatch(primaryTokens) || hasMatch(secondaryTokens)) { + return [ + ...results, + { + primaryTokens, + secondaryTokens, + element + } + ]; + } + + return results; + }, []) + .sort(function(a, b) { + return compareTokens(a.primaryTokens, b.primaryTokens, getLabel(a.element), getLabel(b.element)) + || compareTokens(a.secondaryTokens, b.secondaryTokens, a.element.id, b.element.id) + || compareStrings(getLabel(a.element), getLabel(b.element)) + || compareStrings(a.element.id, b.element.id); + }) + .map(function(result) { + return { + element: result.element, + primaryTokens: result.primaryTokens.map(function(token) { + return omit(token, [ 'index' ]); + }), + secondaryTokens: result.secondaryTokens.map(function(token) { + return omit(token, [ 'index' ]); + }) + }; + }); }; +/** + * @param {Token} token + * + * @return {boolean} + */ +function isMatch(token) { + return 'matched' in token; +} + /** * @param {Token[]} tokens * * @return {boolean} */ -function hasMatched(tokens) { - var matched = filter(tokens, function(token) { - return !!token.matched; - }); +function hasMatch(tokens) { + return tokens.find(isMatch); +} - return matched.length > 0; +/** + * Compares two token arrays. + * + * @param {Token[]} tokensA + * @param {Token[]} tokensB + * + * @returns {number} + */ +function compareTokens(tokensA, tokensB) { + const tokensAHasMatch = hasMatch(tokensA), + tokensBHasMatch = hasMatch(tokensB); + + if (tokensAHasMatch && !tokensBHasMatch) { + return -1; + } + + if (!tokensAHasMatch && tokensBHasMatch) { + return 1; + } + + if (!tokensAHasMatch && !tokensBHasMatch) { + return 0; + } + + const tokensAFirstMatch = tokensA.find(isMatch), + tokensBFirstMatch = tokensB.find(isMatch); + + if (tokensAFirstMatch.index < tokensBFirstMatch.index) { + return -1; + } + + if (tokensAFirstMatch.index > tokensBFirstMatch.index) { + return 1; + } + + return 0; +} + +/** + * Compares two strings. + * + * @param {string} a + * @param {string} b + * + * @returns {number} + */ +function compareStrings(a, b) { + return a.localeCompare(b); } /** @@ -98,7 +161,7 @@ function hasMatched(tokens) { * * @return {Token[]} */ -function matchAndSplit(text, pattern) { +function findMatches(text, pattern) { var tokens = [], originalText = text; @@ -109,27 +172,31 @@ function matchAndSplit(text, pattern) { text = text.toLowerCase(); pattern = pattern.toLowerCase(); - var i = text.indexOf(pattern); + var index = text.indexOf(pattern); - if (i > -1) { - if (i !== 0) { + if (index > -1) { + if (index !== 0) { tokens.push({ - normal: originalText.slice(0, i) + normal: originalText.slice(0, index), + index: 0 }); } tokens.push({ - matched: originalText.slice(i, pattern.length) + matched: originalText.slice(index, index + pattern.length), + index: index }); - if (pattern.length + i < text.length) { + if (pattern.length + index < text.length) { tokens.push({ - normal: originalText.slice(pattern.length + i, text.length) + normal: originalText.slice(index + pattern.length), + index: index + pattern.length }); } } else { tokens.push({ - normal: originalText + normal: originalText, + index: 0 }); } diff --git a/test/spec/features/search/BpmnSearchProviderSpec.js b/test/spec/features/search/BpmnSearchProviderSpec.js index c1e82fced8..ab6099db15 100644 --- a/test/spec/features/search/BpmnSearchProviderSpec.js +++ b/test/spec/features/search/BpmnSearchProviderSpec.js @@ -17,7 +17,7 @@ describe('features - BPMN search provider', function() { ]; - describe(' - with collaboration as root - ', function() { + describe('collaboration', function() { var diagramXML = require('./bpmn-search-collaboration.bpmn'); beforeEach(bootstrapViewer(diagramXML, { modules: testModules })); @@ -34,10 +34,12 @@ describe('features - BPMN search provider', function() { // then expect(elements).to.have.length(0); })); + }); - describe(' - with process as root - ', function() { + describe('process', function() { + var diagramXML = require('./bpmn-search.bpmn'); beforeEach(bootstrapViewer(diagramXML, { modules: testModules })); @@ -161,7 +163,33 @@ describe('features - BPMN search provider', function() { }); + }); + + + describe('sorting', function() { + + var diagramXML = require('./bpmn-search-sorting.bpmn'); + + beforeEach(bootstrapViewer(diagramXML, { modules: testModules })); + + it('should sort', inject(function(bpmnSearch) { + + // given + var pattern = 'foo'; + + // when + var elements = bpmnSearch.find(pattern); + + // then + expect(elements).length(6); + expect(elements[0].element.id).to.eql('foo_2'); + expect(elements[1].element.id).to.eql('foo_3'); + expect(elements[2].element.id).to.eql('bar'); + expect(elements[3].element.id).to.eql('baz'); + expect(elements[4].element.id).to.eql('foo_0'); + expect(elements[5].element.id).to.eql('foo_1'); + })); }); diff --git a/test/spec/features/search/bpmn-search-sorting.bpmn b/test/spec/features/search/bpmn-search-sorting.bpmn new file mode 100644 index 0000000000..1fcf91c266 --- /dev/null +++ b/test/spec/features/search/bpmn-search-sorting.bpmn @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +