fix(editor): Fix partial chat executions (#15379)

This commit is contained in:
oleg
2025-05-15 17:12:08 +02:00
committed by GitHub
parent 726438d95e
commit b6370fb2ec
8 changed files with 452 additions and 32 deletions

View File

@@ -359,6 +359,64 @@ describe('useRunWorkflow({ router })', () => {
expect(result).toEqual(mockExecutionResponse);
});
it('should exclude destinationNode from startNodes when provided', async () => {
// ARRANGE
const mockExecutionResponse = { executionId: '123' };
const { runWorkflow } = useRunWorkflow({ router });
const dataCaptor = captor<IStartRunData>();
const parentNodeName = 'parentNode';
const destinationNodeName = 'destinationNode';
// Mock workflow with parent-child relationship
const workflow = {
name: 'Test Workflow',
id: 'workflowId',
getParentNodes: vi.fn().mockImplementation((nodeName: string) => {
if (nodeName === destinationNodeName) {
return [parentNodeName];
}
return [];
}),
nodes: {
[parentNodeName]: createTestNode({ name: parentNodeName }),
[destinationNodeName]: createTestNode({ name: destinationNodeName }),
},
} as unknown as Workflow;
vi.mocked(pushConnectionStore).isConnected = true;
vi.mocked(workflowsStore).runWorkflow.mockResolvedValue(mockExecutionResponse);
vi.mocked(workflowsStore).nodesIssuesExist = false;
vi.mocked(workflowHelpers).getCurrentWorkflow.mockReturnValue(workflow);
vi.mocked(workflowHelpers).getWorkflowDataToSave.mockResolvedValue({
id: 'workflowId',
nodes: [],
} as unknown as IWorkflowData);
vi.mocked(workflowsStore).getWorkflowRunData = {
[parentNodeName]: [
{
startTime: 1,
executionTime: 0,
source: [],
data: { main: [[{ json: { test: 'data' } }]] },
},
],
} as unknown as IRunData;
// ACT
await runWorkflow({ destinationNode: destinationNodeName });
// ASSERT
expect(workflowsStore.runWorkflow).toHaveBeenCalledTimes(1);
expect(workflowsStore.runWorkflow).toHaveBeenCalledWith(dataCaptor);
const startNodes = dataCaptor.value.startNodes ?? [];
const destinationInStartNodes = startNodes.some((node) => node.name === destinationNodeName);
expect(destinationInStartNodes).toBe(false);
});
it('should send dirty nodes for partial executions v2', async () => {
vi.mocked(settingsStore).partialExecutionVersion = 2;
const composable = useRunWorkflow({ router });

View File

@@ -163,6 +163,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
let triggerToStartFrom: IStartRunData['triggerToStartFrom'];
if (
startNodeNames.length === 0 &&
directParentNodes.length === 0 &&
'destinationNode' in options &&
options.destinationNode !== undefined
) {
@@ -174,6 +175,8 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
);
newRunData = { [options.triggerNode]: [options.nodeData] };
executedNode = options.triggerNode;
} else if (options.destinationNode) {
executedNode = options.destinationNode;
}
if (options.triggerNode) {
@@ -237,24 +240,35 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
const version = settingsStore.partialExecutionVersion;
// TODO: this will be redundant once we cleanup the partial execution v1
const startNodes: StartNodeData[] = sortNodesByYPosition(startNodeNames).map((name) => {
// Find for each start node the source data
let sourceData = get(runData, [name, 0, 'source', 0], null);
if (sourceData === null) {
const parentNodes = workflow.getParentNodes(name, NodeConnectionTypes.Main, 1);
const executeData = workflowHelpers.executeData(
parentNodes,
const startNodes: StartNodeData[] = sortNodesByYPosition(startNodeNames)
.map((name) => {
// Find for each start node the source data
let sourceData = get(runData, [name, 0, 'source', 0], null);
if (sourceData === null) {
const parentNodes = workflow.getParentNodes(name, NodeConnectionTypes.Main, 1);
const executeData = workflowHelpers.executeData(
parentNodes,
name,
NodeConnectionTypes.Main,
0,
);
sourceData = get(executeData, ['source', NodeConnectionTypes.Main, 0], null);
}
return {
name,
NodeConnectionTypes.Main,
0,
);
sourceData = get(executeData, ['source', NodeConnectionTypes.Main, 0], null);
}
return {
name,
sourceData,
};
});
sourceData,
};
})
// If a destination node is specified and it has chat parent, we don't want to include it in the start nodes
.filter((node) => {
if (
options.destinationNode &&
workflowsStore.checkIfNodeHasChatParent(options.destinationNode)
) {
return node.name !== options.destinationNode;
}
return true;
});
const singleWebhookTrigger = triggers.find((node) =>
SINGLE_WEBHOOK_TRIGGERS.includes(node.type),