Skip to content

Commit

Permalink
feat(search): integrate with unified search
Browse files Browse the repository at this point in the history
This ensures we build upon the new search abstraction,
built via bpmn-io/diagram-js#916.
  • Loading branch information
nikku committed Nov 5, 2024
1 parent 8e019cf commit 8383966
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 171 deletions.
192 changes: 40 additions & 152 deletions lib/features/search/BpmnSearchProvider.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { omit } from 'min-dash';

import {
getLabel,
isLabel
Expand All @@ -11,7 +9,11 @@ import {
* @typedef {import('diagram-js/lib/features/search-pad/SearchPad').default} SearchPad
*
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').default} SearchPadProvider
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').SearchResult} SearchResult
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').SearchResult} SearchPadResult
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').Token} SearchPadToken
* @typedef {import('diagram-js/lib/features/search/search').default} Search
* @typedef {import('diagram-js/lib/features/search/search').SearchResult} SearchResult
* @typedef {import('diagram-js/lib/features/search/search').Token} SearchToken
*/

/**
Expand All @@ -22,24 +24,27 @@ import {
* @param {ElementRegistry} elementRegistry
* @param {SearchPad} searchPad
* @param {Canvas} canvas
* @param {Search} search
*/
export default function BpmnSearchProvider(elementRegistry, searchPad, canvas) {
export default function BpmnSearchProvider(elementRegistry, searchPad, canvas, search) {
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._search = search;

searchPad.registerProvider(this);
}

BpmnSearchProvider.$inject = [
'elementRegistry',
'searchPad',
'canvas'
'canvas',
'search'
];

/**
* @param {string} pattern
*
* @return {SearchResult[]}
* @return {SearchPadResult[]}
*/
BpmnSearchProvider.prototype.find = function(pattern) {
var rootElements = this._canvas.getRootElements();
Expand All @@ -48,157 +53,40 @@ BpmnSearchProvider.prototype.find = function(pattern) {
return !isLabel(element) && !rootElements.includes(element);
});

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 this._search(
elements.map(element => {
return {
element: result.element,
primaryTokens: result.primaryTokens.map(function(token) {
return omit(token, [ 'index' ]);
}),
secondaryTokens: result.secondaryTokens.map(function(token) {
return omit(token, [ 'index' ]);
})
element,
label: getLabel(element),
id: element.id
};
});
}),
pattern, {
keys: [
'label',
'id'
]
}
).map(toSearchPadResult);
};

/**
* @param {Token} token
*
* @return {boolean}
*/
function isMatch(token) {
return 'matched' in token;
}

/**
* @param {Token[]} tokens
*
* @return {boolean}
*/
function hasMatch(tokens) {
return tokens.find(isMatch);
}

/**
* 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);
}

/**
* @param {string} text
* @param {string} pattern
* @param {SearchResult} token
*
* @return {Token[]}
* @return {SearchPadResult}
*/
function findMatches(text, pattern) {
var tokens = [],
originalText = text;

if (!text) {
return tokens;
}

text = text.toLowerCase();
pattern = pattern.toLowerCase();

var index = text.indexOf(pattern);

if (index > -1) {
if (index !== 0) {
tokens.push({
normal: originalText.slice(0, index),
index: 0
});
}

tokens.push({
matched: originalText.slice(index, index + pattern.length),
index: index
});

if (pattern.length + index < text.length) {
tokens.push({
normal: originalText.slice(index + pattern.length),
index: index + pattern.length
});
}
} else {
tokens.push({
normal: originalText,
index: 0
});
}

return tokens;
function toSearchPadResult(result) {

const {
item: {
element
},
tokens
} = result;

return {
element,
primaryTokens: tokens.label,
secondaryTokens: tokens.id
};
}
4 changes: 3 additions & 1 deletion lib/features/search/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import SearchPadModule from 'diagram-js/lib/features/search-pad';
import SearchModule from 'diagram-js/lib/features/search';

import BpmnSearchProvider from './BpmnSearchProvider';


export default {
__depends__: [
SearchPadModule
SearchPadModule,
SearchModule
],
__init__: [ 'bpmnSearch' ],
bpmnSearch: [ 'type', BpmnSearchProvider ]
Expand Down
53 changes: 35 additions & 18 deletions test/spec/features/search/BpmnSearchProviderSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import {
inject
} from 'test/TestHelper';

import {
pick
} from 'min-dash';

import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import bpmnSearchModule from 'lib/features/search';
Expand Down Expand Up @@ -72,13 +76,13 @@ describe('features - BPMN search provider', function() {
var elements = bpmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ normal: 'has matched ID' }
expectTokens(elements[0].primaryTokens, [
{ value: 'has matched ID' }
]);
expect(elements[0].secondaryTokens).to.eql([
{ normal: 'some_' },
{ matched: 'DataStore' },
{ normal: '_123456_id' }
expectTokens(elements[0].secondaryTokens, [
{ value: 'some_' },
{ value: 'DataStore', match: true },
{ value: '_123456_id' }
]);
}));

Expand Down Expand Up @@ -122,8 +126,10 @@ describe('features - BPMN search provider', function() {
var elements = bpmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ matched: 'all matched' }
expectTokens(elements[0].primaryTokens, [
{ value: 'all', match: true },
{ value: ' ' },
{ value: 'matched', match: true }
]);
}));

Expand All @@ -137,9 +143,9 @@ describe('features - BPMN search provider', function() {
var elements = bpmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ matched: 'before' },
{ normal: ' 321' }
expectTokens(elements[0].primaryTokens, [
{ value: 'before', match: true },
{ value: ' 321' }
]);
}));

Expand All @@ -153,10 +159,10 @@ describe('features - BPMN search provider', function() {
var elements = bpmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ normal: '123 ' },
{ matched: 'middle' },
{ normal: ' 321' }
expectTokens(elements[0].primaryTokens, [
{ value: '123 ' },
{ value: 'middle', match: true },
{ value: ' 321' }
]);
}));

Expand All @@ -170,9 +176,9 @@ describe('features - BPMN search provider', function() {
var elements = bpmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ normal: '123 ' },
{ matched: 'after' }
expectTokens(elements[0].primaryTokens, [
{ value: '123 ' },
{ value: 'after', match: true }
]);
}));

Expand Down Expand Up @@ -224,3 +230,14 @@ describe('features - BPMN search provider', function() {
});

});


// helpers ///////////////

function expectTokens(tokens, expectedTokens) {
const cleanTokens = tokens.map(
token => pick(token, [ 'value', 'match' ])
);

expect(cleanTokens).to.eql(expectedTokens);
}

0 comments on commit 8383966

Please sign in to comment.