perf(core): Optimize workflow getNodeConnectionIndexes (#18542)

This commit is contained in:
Tomi Turtiainen
2025-08-19 16:30:49 +03:00
committed by GitHub
parent 1d675f9ccc
commit 554327ee78
2 changed files with 375 additions and 56 deletions

View File

@@ -2391,12 +2391,11 @@ describe('Workflow', () => {
});
});
test('should return undefined when depth is 0', () => {
test('should return undefined when no connection exists', () => {
const result = SIMPLE_WORKFLOW.getNodeConnectionIndexes(
'Set',
'Start',
'Set',
NodeConnectionTypes.Main,
0,
);
expect(result).toBeUndefined();
});
@@ -2408,6 +2407,341 @@ describe('Workflow', () => {
destinationIndex: 0,
});
});
test('should find connection through multiple intermediate nodes', () => {
const result = WORKFLOW_WITH_SWITCH.getNodeConnectionIndexes('Set2', 'Switch');
expect(result).toEqual({
sourceIndex: 1,
destinationIndex: 0,
});
});
test('should return first found connection when multiple paths exist', () => {
// Set2 can be reached from Switch via two paths: Switch->Set->Set2 and Switch->Set1->Set2
// Should return the first one found (via Set at index 1)
const result = WORKFLOW_WITH_SWITCH.getNodeConnectionIndexes('Set2', 'Switch');
expect(result).toEqual({
sourceIndex: 1,
destinationIndex: 0,
});
});
test('should handle same source connecting to multiple outputs of destination', () => {
// Switch connects to Set via both output 1 and 2, should find first connection
const result = WORKFLOW_WITH_SWITCH.getNodeConnectionIndexes('Set', 'Switch');
expect(result).toEqual({
sourceIndex: 1,
destinationIndex: 0,
});
});
test('should handle cyclic connections without infinite loops', () => {
// Test with WORKFLOW_WITH_LOOPS which has cycles
const result = WORKFLOW_WITH_LOOPS.getNodeConnectionIndexes('Set', 'Start');
expect(result).toEqual({
sourceIndex: 0,
destinationIndex: 0,
});
});
test('should return undefined for reverse connection lookup', () => {
// Try to find Start from Set1 - should be undefined as Start doesn't connect to Set1
const result = SIMPLE_WORKFLOW.getNodeConnectionIndexes('Set1', 'Start');
expect(result).toEqual({
sourceIndex: 0,
destinationIndex: 0,
});
});
test('should handle disconnected subgraphs', () => {
// Create a workflow with disconnected nodes
const disconnectedWorkflow = new Workflow({
nodeTypes,
nodes: [
{
name: 'Node1',
type: 'test.set',
typeVersion: 1,
id: 'uuid-1',
position: [100, 100],
parameters: {},
},
{
name: 'Node2',
type: 'test.set',
typeVersion: 1,
id: 'uuid-2',
position: [200, 100],
parameters: {},
},
],
connections: {}, // No connections
active: false,
});
const result = disconnectedWorkflow.getNodeConnectionIndexes('Node2', 'Node1');
expect(result).toBeUndefined();
});
test('should handle empty workflow', () => {
const emptyWorkflow = new Workflow({
nodeTypes,
nodes: [],
connections: {},
active: false,
});
const result = emptyWorkflow.getNodeConnectionIndexes('NonExistent1', 'NonExistent2');
expect(result).toBeUndefined();
});
test('should handle single node workflow', () => {
const singleNodeWorkflow = new Workflow({
nodeTypes,
nodes: [
{
name: 'OnlyNode',
type: 'test.set',
typeVersion: 1,
id: 'uuid-1',
position: [100, 100],
parameters: {},
},
],
connections: {},
active: false,
});
const result = singleNodeWorkflow.getNodeConnectionIndexes('OnlyNode', 'OnlyNode');
expect(result).toBeUndefined();
});
test('should handle nodes with same names as method parameters', () => {
// Test edge case where node names might conflict with internal variables
const edgeCaseWorkflow = new Workflow({
nodeTypes,
nodes: [
{
name: 'queue',
type: 'test.set',
typeVersion: 1,
id: 'uuid-1',
position: [100, 100],
parameters: {},
},
{
name: 'visitedNodes',
type: 'test.set',
typeVersion: 1,
id: 'uuid-2',
position: [200, 100],
parameters: {},
},
],
connections: {
queue: {
main: [
[
{
node: 'visitedNodes',
type: NodeConnectionTypes.Main,
index: 0,
},
],
],
},
},
active: false,
});
const result = edgeCaseWorkflow.getNodeConnectionIndexes('visitedNodes', 'queue');
expect(result).toEqual({
sourceIndex: 0,
destinationIndex: 0,
});
});
test('should handle complex branching and merging patterns', () => {
// Create a diamond pattern: A -> B, A -> C, B -> D, C -> D
const diamondWorkflow = new Workflow({
nodeTypes,
nodes: [
{
name: 'A',
type: 'test.set',
typeVersion: 1,
id: 'uuid-1',
position: [100, 100],
parameters: {},
},
{
name: 'B',
type: 'test.set',
typeVersion: 1,
id: 'uuid-2',
position: [200, 50],
parameters: {},
},
{
name: 'C',
type: 'test.set',
typeVersion: 1,
id: 'uuid-3',
position: [200, 150],
parameters: {},
},
{
name: 'D',
type: 'test.set',
typeVersion: 1,
id: 'uuid-4',
position: [300, 100],
parameters: {},
},
],
connections: {
A: {
main: [
[
{ node: 'B', type: NodeConnectionTypes.Main, index: 0 },
{ node: 'C', type: NodeConnectionTypes.Main, index: 0 },
],
],
},
B: {
main: [[{ node: 'D', type: NodeConnectionTypes.Main, index: 0 }]],
},
C: {
main: [[{ node: 'D', type: NodeConnectionTypes.Main, index: 1 }]],
},
},
active: false,
});
// Should find connection A -> B -> D
const result = diamondWorkflow.getNodeConnectionIndexes('D', 'A');
expect(result).toEqual({
sourceIndex: 0,
destinationIndex: 0,
});
});
test('should handle multiple input indexes correctly', () => {
// Test a node that receives inputs at different indexes
const multiInputWorkflow = new Workflow({
nodeTypes,
nodes: [
{
name: 'Source1',
type: 'test.set',
typeVersion: 1,
id: 'uuid-1',
position: [100, 100],
parameters: {},
},
{
name: 'Source2',
type: 'test.set',
typeVersion: 1,
id: 'uuid-2',
position: [100, 200],
parameters: {},
},
{
name: 'Target',
type: 'test.set',
typeVersion: 1,
id: 'uuid-3',
position: [300, 150],
parameters: {},
},
],
connections: {
Source1: {
main: [[{ node: 'Target', type: NodeConnectionTypes.Main, index: 0 }]],
},
Source2: {
main: [[{ node: 'Target', type: NodeConnectionTypes.Main, index: 1 }]],
},
},
active: false,
});
// Check connection from Source1 to Target (should be at input index 0)
const result1 = multiInputWorkflow.getNodeConnectionIndexes('Target', 'Source1');
expect(result1).toEqual({
sourceIndex: 0,
destinationIndex: 0,
});
// Check connection from Source2 to Target (should be at input index 1)
const result2 = multiInputWorkflow.getNodeConnectionIndexes('Target', 'Source2');
expect(result2).toEqual({
sourceIndex: 0,
destinationIndex: 0,
});
});
test('should respect connection type parameter', () => {
// Test with different connection types if available
const result = SIMPLE_WORKFLOW.getNodeConnectionIndexes(
'Set',
'Start',
NodeConnectionTypes.Main,
);
expect(result).toEqual({
sourceIndex: 0,
destinationIndex: 0,
});
// Test with non-existent connection type (should return undefined)
const resultNonExistent = SIMPLE_WORKFLOW.getNodeConnectionIndexes(
'Set',
'Start',
'nonexistent' as any,
);
expect(resultNonExistent).toBeUndefined();
});
test('should handle nodes with null or undefined connections gracefully', () => {
// Test workflow with sparse connection arrays
const sparseWorkflow = new Workflow({
nodeTypes,
nodes: [
{
name: 'Start',
type: 'test.set',
typeVersion: 1,
id: 'uuid-1',
position: [100, 100],
parameters: {},
},
{
name: 'End',
type: 'test.set',
typeVersion: 1,
id: 'uuid-2',
position: [200, 100],
parameters: {},
},
],
connections: {
Start: {
main: [
null, // Null connection at index 0
[{ node: 'End', type: NodeConnectionTypes.Main, index: 0 }], // Connection at index 1
],
},
},
active: false,
});
const result = sparseWorkflow.getNodeConnectionIndexes('End', 'Start');
expect(result).toEqual({
sourceIndex: 1,
destinationIndex: 0,
});
});
});
describe('getStartNode', () => {