feat(editor): Support partial executions of tool nodes (#14945)

This commit is contained in:
Benjamin Schroth
2025-05-01 08:32:47 +02:00
committed by GitHub
parent 5fa41bd73a
commit 54dcdedece
21 changed files with 1132 additions and 22 deletions

View File

@@ -10,6 +10,7 @@ import type {
IExecuteData,
ITaskData,
INodeConnections,
INode,
} from 'n8n-workflow';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
@@ -24,6 +25,7 @@ import { useSettingsStore } from '@/stores/settings.store';
import { usePushConnectionStore } from '@/stores/pushConnection.store';
import { createTestNode, createTestWorkflow } from '@/__tests__/mocks';
import { waitFor } from '@testing-library/vue';
import { useParameterOverridesStore } from '@/stores/parameterOverrides.store';
vi.mock('@/stores/workflows.store', () => {
const storeState: Partial<ReturnType<typeof useWorkflowsStore>> & {
@@ -40,7 +42,11 @@ vi.mock('@/stores/workflows.store', () => {
nodesIssuesExist: false,
executionWaitingForWebhook: false,
getCurrentWorkflow: vi.fn().mockReturnValue({ id: '123' }),
getNodeByName: vi.fn(),
getNodeByName: vi
.fn()
.mockImplementation((name) =>
name === 'Test node' ? { name: 'Test node', id: 'Test id' } : undefined,
),
getExecution: vi.fn(),
checkIfNodeHasChatParent: vi.fn(),
getParametersLastUpdate: vi.fn(),
@@ -59,6 +65,16 @@ vi.mock('@/stores/workflows.store', () => {
};
});
vi.mock('@/stores/parameterOverrides.store', () => {
const storeState: Partial<ReturnType<typeof useParameterOverridesStore>> & {} = {
parameterOverrides: {},
substituteParameters: vi.fn(),
};
return {
useParameterOverridesStore: vi.fn().mockReturnValue(storeState),
};
});
vi.mock('@/stores/pushConnection.store', () => ({
usePushConnectionStore: vi.fn().mockReturnValue({
isConnected: true,
@@ -122,6 +138,7 @@ describe('useRunWorkflow({ router })', () => {
let router: ReturnType<typeof useRouter>;
let workflowHelpers: ReturnType<typeof useWorkflowHelpers>;
let settingsStore: ReturnType<typeof useSettingsStore>;
let parameterOverridesStore: ReturnType<typeof useParameterOverridesStore>;
beforeEach(() => {
const pinia = createTestingPinia({ stubActions: false });
@@ -132,6 +149,7 @@ describe('useRunWorkflow({ router })', () => {
uiStore = useUIStore();
workflowsStore = useWorkflowsStore();
settingsStore = useSettingsStore();
parameterOverridesStore = useParameterOverridesStore();
router = useRouter();
workflowHelpers = useWorkflowHelpers({ router });
@@ -494,6 +512,69 @@ describe('useRunWorkflow({ router })', () => {
});
});
it('does substituteParameters on partial execution if `partialExecutionVersion` is set to 2', async () => {
// ARRANGE
const mockExecutionResponse = { executionId: '123' };
const mockRunData = { nodeName: [] };
const { runWorkflow } = useRunWorkflow({ router });
const dataCaptor = captor();
const workflow = mock<Workflow>({
name: 'Test Workflow',
id: 'WorkflowId',
nodes: {
'Test node': {
id: 'Test id',
name: 'Test node',
parameters: {
param: '0',
},
},
},
});
const workflowData = {
id: 'workflowId',
nodes: [
{
id: 'Test id',
name: 'Test node',
parameters: {
param: '0',
},
position: [0, 0],
type: 'n8n-nodes-base.test',
typeVersion: 1,
} as INode,
],
connections: {},
};
workflow.getParentNodes.mockReturnValue([]);
vi.mocked(settingsStore).partialExecutionVersion = 2;
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(workflowData);
vi.mocked(workflowsStore).getWorkflowRunData = mockRunData;
// ACT
const result = await runWorkflow({ destinationNode: 'Test node' });
// ASSERT
expect(parameterOverridesStore.substituteParameters).toHaveBeenCalledWith(
'WorkflowId',
'Test id',
{ param: '0' },
);
expect(result).toEqual(mockExecutionResponse);
expect(workflowsStore.setWorkflowExecutionData).toHaveBeenCalledTimes(1);
expect(workflowsStore.setWorkflowExecutionData).toHaveBeenCalledWith(dataCaptor);
expect(dataCaptor.value).toMatchObject({ data: { resultData: { runData: mockRunData } } });
});
it('retains the original run data if `partialExecutionVersion` is set to 2', async () => {
// ARRANGE
const mockExecutionResponse = { executionId: '123' };