Skip to content

Commit

Permalink
feat(search): sort matches by location
Browse files Browse the repository at this point in the history
* simplify implementation
* take location into account when sorting
  • Loading branch information
philippfromme authored and nikku committed Sep 12, 2024
1 parent f7ce94b commit 8ddb068
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 49 deletions.
161 changes: 114 additions & 47 deletions lib/features/search/BpmnSearchProvider.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import {
map,
filter,
sortBy
} from 'min-dash';
import { omit } from 'min-dash';

import {
getLabel
getLabel,
isLabel
} from '../../util/LabelUtil';

/**
Expand Down Expand Up @@ -48,48 +45,114 @@ BpmnSearchProvider.prototype.find = function(pattern) {
var rootElements = this._canvas.getRootElements();

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 !rootElements.includes(element);
});

elements = map(elements, function(element) {
return {
primaryTokens: matchAndSplit(getLabel(element), pattern),
secondaryTokens: matchAndSplit(element.id, pattern),
element: element
};
return !isLabel(element) && !rootElements.includes(element);
});

// 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)
|| compareTokens(a.secondaryTokens, b.secondaryTokens)
|| 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);
}

/**
Expand All @@ -98,7 +161,7 @@ function hasMatched(tokens) {
*
* @return {Token[]}
*/
function matchAndSplit(text, pattern) {
function findMatches(text, pattern) {
var tokens = [],
originalText = text;

Expand All @@ -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
});
}

Expand Down
32 changes: 30 additions & 2 deletions test/spec/features/search/BpmnSearchProviderSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
Expand All @@ -38,7 +38,8 @@ describe('features - BPMN search provider', function() {
});


describe(' - with process as root - ', function() {
describe('process', function() {

var diagramXML = require('./bpmn-search.bpmn');

beforeEach(bootstrapViewer(diagramXML, { modules: testModules }));
Expand Down Expand Up @@ -177,6 +178,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');
}));

});

Expand Down
39 changes: 39 additions & 0 deletions test/spec/features/search/bpmn-search-sorting.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0ikirpg" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.23.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.5.0">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:task id="foo_3" name="foo bar" />
<bpmn:task id="baz" name="bar foo" />
<bpmn:task id="bar" name="foo bar" />
<bpmn:task id="foo_2" name="foo bar" />
<bpmn:task id="foo_1" name="baz" />
<bpmn:task id="foo_0" name="bar" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Activity_1sd3etr_di" bpmnElement="foo_3">
<dc:Bounds x="160" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0tpx833_di" bpmnElement="baz">
<dc:Bounds x="290" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_03mqj66_di" bpmnElement="bar">
<dc:Bounds x="420" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1pc6qua_di" bpmnElement="foo_2">
<dc:Bounds x="550" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0g0alrr_di" bpmnElement="foo_1">
<dc:Bounds x="680" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0mbe5y4_di" bpmnElement="foo_0">
<dc:Bounds x="810" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

0 comments on commit 8ddb068

Please sign in to comment.