Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/n8n-io/n8n into node-1669-…
Browse files Browse the repository at this point in the history
…switch-node-adding-routing-rule-reconnects-fallback-route
  • Loading branch information
michael-radency committed Oct 31, 2024
2 parents 15944d8 + 73b0a80 commit 97b7a5b
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 83 deletions.
16 changes: 8 additions & 8 deletions cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ function createRunDataWithError(inputMessage: string) {
routine: 'InitPostgres',
} as unknown as Error,
} as ExecutionError,
metadata: {
subRun: [
{
node: 'Postgres Chat Memory',
runIndex: 0,
},
],
},
}),
createMockNodeExecutionData(AGENT_NODE_NAME, {
executionStatus: 'error',
Expand Down Expand Up @@ -124,14 +132,6 @@ function createRunDataWithError(inputMessage: string) {
description: 'Internal error',
message: 'Internal error',
} as unknown as ExecutionError,
metadata: {
subRun: [
{
node: 'Postgres Chat Memory',
runIndex: 0,
},
],
},
}),
];
}
Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/30-langchain.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ describe('Langchain Integration', () => {
},
},
},
metadata: {
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
},
inputOverride: {
ai_languageModel: [
[
Expand Down Expand Up @@ -316,9 +319,6 @@ describe('Langchain Integration', () => {
jsonData: {
main: { output: 'Hi there! How can I assist you today?' },
},
metadata: {
subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }],
},
}),
],
lastNodeExecuted: AGENT_NODE_NAME,
Expand Down
16 changes: 9 additions & 7 deletions packages/editor-ui/src/components/OutputPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,15 @@ const isTriggerNode = computed(() => {
});
const hasAiMetadata = computed(() => {
if (node.value) {
const resultData = workflowsStore.getWorkflowResultDataByNodeName(node.value.name);
if (isNodeRunning.value || !workflowRunData.value) {
return false;
}
if (!resultData || !Array.isArray(resultData) || resultData.length === 0) {
return false;
}
if (node.value) {
const connectedSubNodes = props.workflow.getParentNodes(node.value.name, 'ALL_NON_MAIN');
const resultData = connectedSubNodes.map(workflowsStore.getWorkflowResultDataByNodeName);
return !!resultData[resultData.length - 1].metadata;
return resultData && Array.isArray(resultData) && resultData.length > 0;
}
return false;
});
Expand Down Expand Up @@ -295,6 +296,7 @@ const activatePane = () => {
:block-u-i="blockUI"
:is-production-execution-preview="isProductionExecutionPreview"
:is-pane-active="isPaneActive"
:hide-pagination="outputMode === 'logs'"
pane-type="output"
:data-output-type="outputMode"
@activate-pane="activatePane"
Expand Down Expand Up @@ -368,7 +370,7 @@ const activatePane = () => {
</template>

<template v-if="outputMode === 'logs' && node" #content>
<RunDataAi :node="node" :run-index="runIndex" />
<RunDataAi :node="node" :run-index="runIndex" :workflow="workflow" />
</template>

<template #recovered-artificial-output-data>
Expand Down
5 changes: 5 additions & 0 deletions packages/editor-ui/src/components/RunData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
hidePagination: {
type: Boolean,
default: false,
},
},
setup(props) {
const ndvStore = useNDVStore();
Expand Down Expand Up @@ -1743,6 +1747,7 @@ export default defineComponent({
</div>
<div
v-if="
hidePagination === false &&
hasNodeRun &&
!hasRunError &&
displayMode !== 'binary' &&
Expand Down
100 changes: 52 additions & 48 deletions packages/editor-ui/src/components/RunDataAi/RunDataAi.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script lang="ts" setup>
import type { Ref } from 'vue';
import { computed, ref, watch } from 'vue';
import type { ITaskSubRunMetadata, ITaskDataConnections } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import type { ITaskDataConnections, NodeConnectionType, Workflow, ITaskData } from 'n8n-workflow';
import type { IAiData, IAiDataContent, INodeUi } from '@/Interface';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
Expand All @@ -28,29 +27,21 @@ export interface Props {
runIndex?: number;
hideTitle?: boolean;
slim?: boolean;
workflow: Workflow;
}
const props = withDefaults(defineProps<Props>(), { runIndex: 0 });
const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
const selectedRun: Ref<IAiData[]> = ref([]);
function isTreeNodeSelected(node: TreeNode) {
return selectedRun.value.some((run) => run.node === node.node && run.runIndex === node.runIndex);
}
function getReferencedData(
reference: ITaskSubRunMetadata,
taskData: ITaskData,
withInput: boolean,
withOutput: boolean,
): IAiDataContent[] {
const resultData = workflowsStore.getWorkflowResultDataByNodeName(reference.node);
if (!resultData?.[reference.runIndex]) {
return [];
}
const taskData = resultData[reference.runIndex];
if (!taskData) {
return [];
}
Expand Down Expand Up @@ -98,18 +89,18 @@ function onItemClick(data: TreeNode) {
return;
}
const selectedNodeRun = workflowsStore.getWorkflowResultDataByNodeName(data.node)?.[
data.runIndex
];
if (!selectedNodeRun) {
return;
}
selectedRun.value = [
{
node: data.node,
runIndex: data.runIndex,
data: getReferencedData(
{
node: data.node,
runIndex: data.runIndex,
},
true,
true,
),
data: getReferencedData(selectedNodeRun, true, true),
},
];
}
Expand Down Expand Up @@ -145,21 +136,20 @@ const createNode = (
});
function getTreeNodeData(nodeName: string, currentDepth: number): TreeNode[] {
const { connectionsByDestinationNode } = workflowsStore.getCurrentWorkflow();
const connections = connectionsByDestinationNode[nodeName];
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const connections = props.workflow.connectionsByDestinationNode[nodeName];
const resultData = aiData.value?.filter((data) => data.node === nodeName) ?? [];
if (!connections) {
return resultData.map((d) => createNode(nodeName, currentDepth, d));
}
const nonMainConnectionsKeys = Object.keys(connections).filter(
(key) => key !== NodeConnectionType.Main,
);
const children = nonMainConnectionsKeys.flatMap((key) =>
connections[key][0].flatMap((node) => getTreeNodeData(node.node, currentDepth + 1)),
);
// Get the first level of children
const connectedSubNodes = props.workflow.getParentNodes(nodeName, 'ALL_NON_MAIN', 1);
const children = connectedSubNodes
// Only include sub-nodes which have data
.filter((name) => aiData.value?.find((data) => data.node === name))
.flatMap((name) => getTreeNodeData(name, currentDepth + 1));
children.sort((a, b) => a.startTime - b.startTime);
Expand All @@ -170,43 +160,57 @@ function getTreeNodeData(nodeName: string, currentDepth: number): TreeNode[] {
return [createNode(nodeName, currentDepth, undefined, children)];
}
const aiData = computed<AIResult[] | undefined>(() => {
const resultData = workflowsStore.getWorkflowResultDataByNodeName(props.node.name);
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);
if (!resultData || !Array.isArray(resultData)) {
return;
}
connectedSubNodes.forEach((nodeName) => {
const nodeRunData = workflowsStore.getWorkflowResultDataByNodeName(nodeName) ?? [];
const subRun = resultData[props.runIndex].metadata?.subRun;
if (!Array.isArray(subRun)) {
return;
}
// Extend the subRun with the data and sort by adding execution time + startTime and comparing them
const subRunWithData = subRun.flatMap((run) =>
getReferencedData(run, false, true).map((data) => ({ ...run, data })),
);
nodeRunData.forEach((run, runIndex) => {
const referenceData = {
data: getReferencedData(run, false, true)[0],
node: nodeName,
runIndex,
};
subRunWithData.sort((a, b) => {
const aTime = a.data?.metadata?.startTime || 0;
const bTime = b.data?.metadata?.startTime || 0;
result.push(referenceData);
});
});
// Sort the data by start time
result.sort((a, b) => {
const aTime = a.data?.metadata?.startTime ?? 0;
const bTime = b.data?.metadata?.startTime ?? 0;
return aTime - bTime;
});
return subRunWithData;
// Only show data that is within the root node's execution time
// This is because sub-node could be connected to multiple root nodes
const currentNodeResult = result.filter((r) => {
const startTime = r.data?.metadata?.startTime ?? 0;
return startTime >= rootNodeStartTime && startTime <= rootNodeEndTime;
});
return currentNodeResult;
});
const executionTree = computed<TreeNode[]>(() => {
const rootNode = props.node;
const tree = getTreeNodeData(rootNode.name, 0);
const tree = getTreeNodeData(rootNode.name, 1);
return tree || [];
});
watch(() => props.runIndex, selectFirst, { immediate: true });
</script>

<template>
<div v-if="aiData" :class="$style.container">
<div v-if="aiData.length > 0" :class="$style.container">
<div :class="{ [$style.tree]: true, [$style.slim]: slim }">
<ElTree
:data="executionTree"
Expand Down
Loading

0 comments on commit 97b7a5b

Please sign in to comment.