mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -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,
|
||||
getConnectionBySourceAndTarget,
|
||||
} from '../composables/workflow';
|
||||
import { NDV, WorkflowPage } from '../pages';
|
||||
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
||||
|
||||
describe('Langchain Integration', () => {
|
||||
|
@ -232,12 +233,7 @@ describe('Langchain Integration', () => {
|
|||
|
||||
const inputMessage = 'Hello!';
|
||||
const outputMessage = 'Hi there! How can I assist you today?';
|
||||
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => {
|
||||
sendManualChatMessage(inputMessage);
|
||||
},
|
||||
runData: [
|
||||
const runData = [
|
||||
createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, {
|
||||
jsonData: {
|
||||
main: { input: inputMessage },
|
||||
|
@ -320,7 +316,13 @@ describe('Langchain Integration', () => {
|
|||
main: { output: 'Hi there! How can I assist you today?' },
|
||||
},
|
||||
}),
|
||||
],
|
||||
];
|
||||
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => {
|
||||
sendManualChatMessage(inputMessage);
|
||||
},
|
||||
runData,
|
||||
lastNodeExecuted: AGENT_NODE_NAME,
|
||||
});
|
||||
|
||||
|
@ -357,4 +359,56 @@ describe('Langchain Integration', () => {
|
|||
getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist');
|
||||
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 {
|
||||
[name]: {
|
||||
startTime: new Date().getTime(),
|
||||
executionTime: 0,
|
||||
executionTime: 1,
|
||||
executionStatus,
|
||||
data: jsonData
|
||||
? Object.keys(jsonData).reduce((acc, key) => {
|
||||
|
@ -33,6 +33,7 @@ export function createMockNodeExecutionData(
|
|||
}, {} as ITaskDataConnections)
|
||||
: data,
|
||||
source: [null],
|
||||
inputOverride,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -179,7 +179,7 @@ export default defineComponent({
|
|||
|
||||
rootNode(): string {
|
||||
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];
|
||||
},
|
||||
|
@ -342,7 +342,7 @@ export default defineComponent({
|
|||
:node="currentNode"
|
||||
:nodes="isMappingMode ? rootNodesParents : parentNodes"
|
||||
:workflow="workflow"
|
||||
:run-index="runIndex"
|
||||
:run-index="isMappingMode ? 0 : runIndex"
|
||||
:linked-runs="linkedRuns"
|
||||
:can-link-runs="!mappedNode && canLinkRuns"
|
||||
:too-much-data-title="$locale.baseText('ndv.input.tooMuchData.title')"
|
||||
|
|
|
@ -154,6 +154,29 @@ const parentNode = computed(() => {
|
|||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
|
|
|
@ -164,8 +164,9 @@ const aiData = computed<AIResult[]>(() => {
|
|||
const result: AIResult[] = [];
|
||||
const connectedSubNodes = props.workflow.getParentNodes(props.node.name, 'ALL_NON_MAIN');
|
||||
const rootNodeResult = workflowsStore.getWorkflowResultDataByNodeName(props.node.name);
|
||||
const rootNodeStartTime = rootNodeResult?.[0]?.startTime ?? 0;
|
||||
const rootNodeEndTime = rootNodeStartTime + (rootNodeResult?.[0]?.executionTime ?? 0);
|
||||
const rootNodeStartTime = rootNodeResult?.[props.runIndex ?? 0]?.startTime ?? 0;
|
||||
const rootNodeEndTime =
|
||||
rootNodeStartTime + (rootNodeResult?.[props.runIndex ?? 0]?.executionTime ?? 0);
|
||||
|
||||
connectedSubNodes.forEach((nodeName) => {
|
||||
const nodeRunData = workflowsStore.getWorkflowResultDataByNodeName(nodeName) ?? [];
|
||||
|
@ -193,7 +194,7 @@ const aiData = computed<AIResult[]>(() => {
|
|||
const currentNodeResult = result.filter((r) => {
|
||||
const startTime = r.data?.metadata?.startTime ?? 0;
|
||||
|
||||
return startTime >= rootNodeStartTime && startTime <= rootNodeEndTime;
|
||||
return startTime >= rootNodeStartTime && startTime < rootNodeEndTime;
|
||||
});
|
||||
|
||||
return currentNodeResult;
|
||||
|
|
Loading…
Reference in a new issue