fix(editor): Improve handling of trailing 'Trigger' in NodeCreator search (#14612)

This commit is contained in:
Charlie Kolb
2025-04-16 13:43:14 +02:00
committed by GitHub
parent a8fee9a4f3
commit 8b3b4749ea
4 changed files with 68 additions and 25 deletions

View File

@@ -2,15 +2,14 @@ import topLevel from './snapshots/toplevel.snapshot.json';
import { sublimeSearch } from './sublimeSearch'; import { sublimeSearch } from './sublimeSearch';
describe('sublimeSearch', () => { describe('sublimeSearch', () => {
const testCases = [{ filter: 'agent', expectedOrder: ['Magento 2', 'AI Agent'] }]; describe('search finds at least one match', () => {
const testCases: Array<[string, string[]]> = [['agent', ['Magento 2', 'AI Agent']]];
test.each(testCases)( test.each(testCases)(
'should return results in the correct order for filter "$filter"', 'should return at least "$expectedOrder" for filter "$filter"',
({ filter, expectedOrder }) => { (filter, expectedOrder) => {
const results = sublimeSearch(filter, topLevel, [ // These match the weights in the production use case
{ key: 'properties.displayName', weight: 1.3 }, const results = sublimeSearch(filter, topLevel);
{ key: 'properties.codex.alias', weight: 1 },
]);
const resultNames = results.map((result) => result.item.properties.displayName); const resultNames = results.map((result) => result.item.properties.displayName);
expectedOrder.forEach((expectedName, index) => { expectedOrder.forEach((expectedName, index) => {
@@ -18,4 +17,5 @@ describe('sublimeSearch', () => {
}); });
}, },
); );
});
}); });

View File

@@ -12,6 +12,11 @@ const LEADING_LETTER_PENALTY = -20; // penalty applied for every letter in str b
const MAX_LEADING_LETTER_PENALTY = -200; // maximum penalty for leading letters const MAX_LEADING_LETTER_PENALTY = -200; // maximum penalty for leading letters
const UNMATCHED_LETTER_PENALTY = -5; const UNMATCHED_LETTER_PENALTY = -5;
export const DEFAULT_KEYS = [
{ key: 'properties.displayName', weight: 1.3 },
{ key: 'properties.codex.alias', weight: 1 },
];
/** /**
* Returns true if each character in pattern is found sequentially within target * Returns true if each character in pattern is found sequentially within target
* @param {*} pattern string * @param {*} pattern string
@@ -217,7 +222,7 @@ function getValue<T extends object>(obj: T, prop: string): unknown {
export function sublimeSearch<T extends object>( export function sublimeSearch<T extends object>(
filter: string, filter: string,
data: Readonly<T[]>, data: Readonly<T[]>,
keys: Array<{ key: string; weight: number }>, keys: Array<{ key: string; weight: number }> = DEFAULT_KEYS,
): Array<{ score: number; item: T }> { ): Array<{ score: number; item: T }> {
const results = data.reduce((accu: Array<{ score: number; item: T }>, item: T) => { const results = data.reduce((accu: Array<{ score: number; item: T }>, item: T) => {
let values: Array<{ value: string; weight: number }> = []; let values: Array<{ value: string; weight: number }> = [];

View File

@@ -1,5 +1,10 @@
import type { SectionCreateElement } from '@/Interface'; import type { SectionCreateElement } from '@/Interface';
import { formatTriggerActionName, groupItemsInSections, sortNodeCreateElements } from './utils'; import {
formatTriggerActionName,
groupItemsInSections,
removeTrailingTrigger,
sortNodeCreateElements,
} from './utils';
import { import {
mockActionCreateElement, mockActionCreateElement,
mockNodeCreateElement, mockNodeCreateElement,
@@ -77,4 +82,25 @@ describe('NodeCreator - utils', () => {
expect(formatTriggerActionName(actionName)).toEqual(expected); expect(formatTriggerActionName(actionName)).toEqual(expected);
}); });
}); });
describe('removeTrailingTrigger', () => {
test.each([
['Telegram Trigger', 'Telegram'],
['Trigger Telegram', 'Trigger Telegram'],
['Telegram Tri', 'Telegram'],
['Telegram Bot', 'Telegram Bot'],
['Tri', 'Tri'],
['Trigger', 'Trigger'],
['Telegram', 'Telegram'],
['Telegram Trigger Bot', 'Telegram Trigger Bot'],
['Telegram Trig', 'Telegram'],
['Telegram Bot trigger', 'Telegram Bot'],
['Telegram TRIGGER', 'Telegram'],
['', ''],
['Telegram Trigger', 'Telegram Trigger'], // full-width space,
['Telegram Trigger ', 'Telegram Trigger'],
['Telegram Trigger', 'Telegram'],
])('should transform "%s" to "%s"', (input, expected) => {
expect(removeTrailingTrigger(input)).toEqual(expected);
});
});
}); });

View File

@@ -90,22 +90,34 @@ export function sortNodeCreateElements(nodes: INodeCreateElement[]) {
}); });
} }
// We remove `Trigger` from e.g. `Telegram Trigger` to show it as part of the `Telegram` group,
// but still want to show matching results when the user types `Telegram Tri` or `Telegram Trigger`
// Ideally this would be handled via metadata, but that is a larger refactor.
export function removeTrailingTrigger(searchFilter: string) {
const parts = searchFilter.split(' ');
if (parts.length > 1 && 'trigger'.startsWith(parts.slice(-1)[0].toLowerCase())) {
return parts
.slice(0, -1)
.filter((x) => x)
.join(' ')
.trimEnd();
}
return searchFilter;
}
export function searchNodes(searchFilter: string, items: INodeCreateElement[]) { export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
const askAiEnabled = useSettingsStore().isAskAiEnabled; const askAiEnabled = useSettingsStore().isAskAiEnabled;
if (!askAiEnabled) { if (!askAiEnabled) {
items = items.filter((item) => item.key !== AI_TRANSFORM_NODE_TYPE); items = items.filter((item) => item.key !== AI_TRANSFORM_NODE_TYPE);
} }
// In order to support the old search we need to remove the 'trigger' part const trimmedFilter = removeTrailingTrigger(searchFilter).toLowerCase();
const trimmedFilter = searchFilter.toLowerCase().replace('trigger', '').trimEnd();
// We have a snapshot of this call in sublimeSearch.test.ts to assert practical order for some cases // We have a snapshot of this call in sublimeSearch.test.ts to assert practical order for some cases
// Please kindly update the test call if you modify the weights here, and ideally regenerate the snapshot as described in its file. // Please update the snapshots per the README next to the the snapshots if you modify items significantly.
const result = ( const result = (sublimeSearch<INodeCreateElement>(trimmedFilter, items) || []).map(
sublimeSearch<INodeCreateElement>(trimmedFilter, items, [ ({ item }) => item,
{ key: 'properties.displayName', weight: 1.3 }, );
{ key: 'properties.codex.alias', weight: 1 },
]) || []
).map(({ item }) => item);
return result; return result;
} }