mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 21:37:32 -08:00
fix(editor): Fix run index input for RunData view in sub-nodes (#11538)
This commit is contained in:
parent
dfd785bc08
commit
434d31ce92
|
@ -44,6 +44,7 @@ import {
|
||||||
openNode,
|
openNode,
|
||||||
getConnectionBySourceAndTarget,
|
getConnectionBySourceAndTarget,
|
||||||
} from '../composables/workflow';
|
} from '../composables/workflow';
|
||||||
|
import { NDV, WorkflowPage } from '../pages';
|
||||||
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
||||||
|
|
||||||
describe('Langchain Integration', () => {
|
describe('Langchain Integration', () => {
|
||||||
|
@ -232,12 +233,7 @@ describe('Langchain Integration', () => {
|
||||||
|
|
||||||
const inputMessage = 'Hello!';
|
const inputMessage = 'Hello!';
|
||||||
const outputMessage = 'Hi there! How can I assist you today?';
|
const outputMessage = 'Hi there! How can I assist you today?';
|
||||||
|
const runData = [
|
||||||
runMockWorkflowExecution({
|
|
||||||
trigger: () => {
|
|
||||||
sendManualChatMessage(inputMessage);
|
|
||||||
},
|
|
||||||
runData: [
|
|
||||||
createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, {
|
createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, {
|
||||||
jsonData: {
|
jsonData: {
|
||||||
main: { input: inputMessage },
|
main: { input: inputMessage },
|
||||||
|
@ -320,7 +316,13 @@ describe('Langchain Integration', () => {
|
||||||
main: { output: 'Hi there! How can I assist you today?' },
|
main: { output: 'Hi there! How can I assist you today?' },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
];
|
||||||
|
|
||||||
|
runMockWorkflowExecution({
|
||||||
|
trigger: () => {
|
||||||
|
sendManualChatMessage(inputMessage);
|
||||||
|
},
|
||||||
|
runData,
|
||||||
lastNodeExecuted: AGENT_NODE_NAME,
|
lastNodeExecuted: AGENT_NODE_NAME,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -357,4 +359,56 @@ describe('Langchain Integration', () => {
|
||||||
getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist');
|
getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist');
|
||||||
getNodes().should('have.length', 3);
|
getNodes().should('have.length', 3);
|
||||||
});
|
});
|
||||||
|
it('should render runItems for sub-nodes and allow switching between them', () => {
|
||||||
|
const workflowPage = new WorkflowPage();
|
||||||
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
cy.visit(workflowPage.url);
|
||||||
|
cy.createFixtureWorkflow('In_memory_vector_store_fake_embeddings.json');
|
||||||
|
workflowPage.actions.zoomToFit();
|
||||||
|
|
||||||
|
workflowPage.actions.executeNode('Populate VS');
|
||||||
|
cy.get('[data-label="25 items"]').should('exist');
|
||||||
|
|
||||||
|
const assertInputOutputText = (text: string, assertion: 'exist' | 'not.exist') => {
|
||||||
|
ndv.getters.outputPanel().contains(text).should(assertion);
|
||||||
|
ndv.getters.inputPanel().contains(text).should(assertion);
|
||||||
|
};
|
||||||
|
|
||||||
|
workflowPage.actions.openNode('Character Text Splitter');
|
||||||
|
ndv.getters.outputRunSelector().should('exist');
|
||||||
|
ndv.getters.inputRunSelector().should('exist');
|
||||||
|
ndv.getters.inputRunSelector().find('input').should('include.value', '3 of 3');
|
||||||
|
ndv.getters.outputRunSelector().find('input').should('include.value', '3 of 3');
|
||||||
|
assertInputOutputText('Kyiv', 'exist');
|
||||||
|
assertInputOutputText('Berlin', 'not.exist');
|
||||||
|
assertInputOutputText('Prague', 'not.exist');
|
||||||
|
|
||||||
|
ndv.actions.changeOutputRunSelector('2 of 3');
|
||||||
|
assertInputOutputText('Berlin', 'exist');
|
||||||
|
assertInputOutputText('Kyiv', 'not.exist');
|
||||||
|
assertInputOutputText('Prague', 'not.exist');
|
||||||
|
|
||||||
|
ndv.actions.changeOutputRunSelector('1 of 3');
|
||||||
|
assertInputOutputText('Prague', 'exist');
|
||||||
|
assertInputOutputText('Berlin', 'not.exist');
|
||||||
|
assertInputOutputText('Kyiv', 'not.exist');
|
||||||
|
|
||||||
|
ndv.actions.toggleInputRunLinking();
|
||||||
|
ndv.actions.changeOutputRunSelector('2 of 3');
|
||||||
|
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
||||||
|
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 3');
|
||||||
|
ndv.getters.inputPanel().contains('Prague').should('exist');
|
||||||
|
ndv.getters.inputPanel().contains('Berlin').should('not.exist');
|
||||||
|
|
||||||
|
ndv.getters.outputPanel().contains('Berlin').should('exist');
|
||||||
|
ndv.getters.outputPanel().contains('Prague').should('not.exist');
|
||||||
|
|
||||||
|
ndv.actions.toggleInputRunLinking();
|
||||||
|
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
||||||
|
ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 3');
|
||||||
|
assertInputOutputText('Prague', 'exist');
|
||||||
|
assertInputOutputText('Berlin', 'not.exist');
|
||||||
|
assertInputOutputText('Kyiv', 'not.exist');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
347
cypress/fixtures/In_memory_vector_store_fake_embeddings.json
Normal file
347
cypress/fixtures/In_memory_vector_store_fake_embeddings.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -16,7 +16,7 @@ export function createMockNodeExecutionData(
|
||||||
return {
|
return {
|
||||||
[name]: {
|
[name]: {
|
||||||
startTime: new Date().getTime(),
|
startTime: new Date().getTime(),
|
||||||
executionTime: 0,
|
executionTime: 1,
|
||||||
executionStatus,
|
executionStatus,
|
||||||
data: jsonData
|
data: jsonData
|
||||||
? Object.keys(jsonData).reduce((acc, key) => {
|
? Object.keys(jsonData).reduce((acc, key) => {
|
||||||
|
@ -33,6 +33,7 @@ export function createMockNodeExecutionData(
|
||||||
}, {} as ITaskDataConnections)
|
}, {} as ITaskDataConnections)
|
||||||
: data,
|
: data,
|
||||||
source: [null],
|
source: [null],
|
||||||
|
inputOverride,
|
||||||
...rest,
|
...rest,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -179,7 +179,7 @@ export default defineComponent({
|
||||||
|
|
||||||
rootNode(): string {
|
rootNode(): string {
|
||||||
const workflow = this.workflow;
|
const workflow = this.workflow;
|
||||||
const rootNodes = workflow.getChildNodes(this.activeNode?.name ?? '', 'ALL_NON_MAIN');
|
const rootNodes = workflow.getChildNodes(this.activeNode?.name ?? '', 'ALL');
|
||||||
|
|
||||||
return rootNodes[0];
|
return rootNodes[0];
|
||||||
},
|
},
|
||||||
|
@ -342,7 +342,7 @@ export default defineComponent({
|
||||||
:node="currentNode"
|
:node="currentNode"
|
||||||
:nodes="isMappingMode ? rootNodesParents : parentNodes"
|
:nodes="isMappingMode ? rootNodesParents : parentNodes"
|
||||||
:workflow="workflow"
|
:workflow="workflow"
|
||||||
:run-index="runIndex"
|
:run-index="isMappingMode ? 0 : runIndex"
|
||||||
:linked-runs="linkedRuns"
|
:linked-runs="linkedRuns"
|
||||||
:can-link-runs="!mappedNode && canLinkRuns"
|
:can-link-runs="!mappedNode && canLinkRuns"
|
||||||
:too-much-data-title="$locale.baseText('ndv.input.tooMuchData.title')"
|
:too-much-data-title="$locale.baseText('ndv.input.tooMuchData.title')"
|
||||||
|
|
|
@ -154,6 +154,29 @@ const parentNode = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const inputNodeName = computed<string | undefined>(() => {
|
const inputNodeName = computed<string | undefined>(() => {
|
||||||
|
const nodeOutputs =
|
||||||
|
activeNode.value && activeNodeType.value
|
||||||
|
? NodeHelpers.getNodeOutputs(props.workflowObject, activeNode.value, activeNodeType.value)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const nonMainOutputs = nodeOutputs.filter((output) => {
|
||||||
|
if (typeof output === 'string') return output !== NodeConnectionType.Main;
|
||||||
|
|
||||||
|
return output.type !== NodeConnectionType.Main;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isSubNode = nonMainOutputs.length > 0;
|
||||||
|
|
||||||
|
if (isSubNode && activeNode.value) {
|
||||||
|
// For sub-nodes, we need to get their connected output node to determine the input
|
||||||
|
// because sub-nodes use specialized outputs (e.g. NodeConnectionType.AiTool)
|
||||||
|
// instead of the standard Main output type
|
||||||
|
const connectedOutputNode = props.workflowObject.getChildNodes(
|
||||||
|
activeNode.value.name,
|
||||||
|
'ALL_NON_MAIN',
|
||||||
|
)?.[0];
|
||||||
|
return connectedOutputNode;
|
||||||
|
}
|
||||||
return selectedInput.value || parentNode.value;
|
return selectedInput.value || parentNode.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -164,8 +164,9 @@ const aiData = computed<AIResult[]>(() => {
|
||||||
const result: AIResult[] = [];
|
const result: AIResult[] = [];
|
||||||
const connectedSubNodes = props.workflow.getParentNodes(props.node.name, 'ALL_NON_MAIN');
|
const connectedSubNodes = props.workflow.getParentNodes(props.node.name, 'ALL_NON_MAIN');
|
||||||
const rootNodeResult = workflowsStore.getWorkflowResultDataByNodeName(props.node.name);
|
const rootNodeResult = workflowsStore.getWorkflowResultDataByNodeName(props.node.name);
|
||||||
const rootNodeStartTime = rootNodeResult?.[0]?.startTime ?? 0;
|
const rootNodeStartTime = rootNodeResult?.[props.runIndex ?? 0]?.startTime ?? 0;
|
||||||
const rootNodeEndTime = rootNodeStartTime + (rootNodeResult?.[0]?.executionTime ?? 0);
|
const rootNodeEndTime =
|
||||||
|
rootNodeStartTime + (rootNodeResult?.[props.runIndex ?? 0]?.executionTime ?? 0);
|
||||||
|
|
||||||
connectedSubNodes.forEach((nodeName) => {
|
connectedSubNodes.forEach((nodeName) => {
|
||||||
const nodeRunData = workflowsStore.getWorkflowResultDataByNodeName(nodeName) ?? [];
|
const nodeRunData = workflowsStore.getWorkflowResultDataByNodeName(nodeName) ?? [];
|
||||||
|
@ -193,7 +194,7 @@ const aiData = computed<AIResult[]>(() => {
|
||||||
const currentNodeResult = result.filter((r) => {
|
const currentNodeResult = result.filter((r) => {
|
||||||
const startTime = r.data?.metadata?.startTime ?? 0;
|
const startTime = r.data?.metadata?.startTime ?? 0;
|
||||||
|
|
||||||
return startTime >= rootNodeStartTime && startTime <= rootNodeEndTime;
|
return startTime >= rootNodeStartTime && startTime < rootNodeEndTime;
|
||||||
});
|
});
|
||||||
|
|
||||||
return currentNodeResult;
|
return currentNodeResult;
|
||||||
|
|
Loading…
Reference in a new issue