mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(editor): Cannot expand sub execution log if it finished with an error (#16236)
Co-authored-by: Csaba Tuncsik <csaba.tuncsik@gmail.com>
This commit is contained in:
@@ -31,7 +31,6 @@
|
|||||||
"@codemirror/view": "^6.26.3",
|
"@codemirror/view": "^6.26.3",
|
||||||
"@dagrejs/dagre": "^1.1.4",
|
"@dagrejs/dagre": "^1.1.4",
|
||||||
"@lezer/common": "1.1.0",
|
"@lezer/common": "1.1.0",
|
||||||
"@n8n/rest-api-client": "workspace:*",
|
|
||||||
"@n8n/api-types": "workspace:*",
|
"@n8n/api-types": "workspace:*",
|
||||||
"@n8n/chat": "workspace:*",
|
"@n8n/chat": "workspace:*",
|
||||||
"@n8n/codemirror-lang": "workspace:*",
|
"@n8n/codemirror-lang": "workspace:*",
|
||||||
@@ -41,6 +40,7 @@
|
|||||||
"@n8n/design-system": "workspace:*",
|
"@n8n/design-system": "workspace:*",
|
||||||
"@n8n/i18n": "workspace:*",
|
"@n8n/i18n": "workspace:*",
|
||||||
"@n8n/permissions": "workspace:*",
|
"@n8n/permissions": "workspace:*",
|
||||||
|
"@n8n/rest-api-client": "workspace:*",
|
||||||
"@n8n/stores": "workspace:*",
|
"@n8n/stores": "workspace:*",
|
||||||
"@n8n/utils": "workspace:*",
|
"@n8n/utils": "workspace:*",
|
||||||
"@replit/codemirror-indentation-markers": "^6.5.3",
|
"@replit/codemirror-indentation-markers": "^6.5.3",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useI18n } from '@n8n/i18n';
|
|||||||
import { I18nT } from 'vue-i18n';
|
import { I18nT } from 'vue-i18n';
|
||||||
import { toDayMonth, toTime } from '@/utils/formatters/dateFormatter';
|
import { toDayMonth, toTime } from '@/utils/formatters/dateFormatter';
|
||||||
import LogsViewNodeName from '@/features/logs/components/LogsViewNodeName.vue';
|
import LogsViewNodeName from '@/features/logs/components/LogsViewNodeName.vue';
|
||||||
import { getSubtreeTotalConsumedTokens } from '@/features/logs/logs.utils';
|
import { getSubtreeTotalConsumedTokens, hasSubExecution } from '@/features/logs/logs.utils';
|
||||||
import { useTimestamp } from '@vueuse/core';
|
import { useTimestamp } from '@vueuse/core';
|
||||||
import type { LatestNodeInfo, LogEntry } from '@/features/logs/logs.types';
|
import type { LatestNodeInfo, LogEntry } from '@/features/logs/logs.types';
|
||||||
|
|
||||||
@@ -70,9 +70,7 @@ const subtreeConsumedTokens = computed(() =>
|
|||||||
props.shouldShowTokenCountColumn ? getSubtreeTotalConsumedTokens(props.data, false) : undefined,
|
props.shouldShowTokenCountColumn ? getSubtreeTotalConsumedTokens(props.data, false) : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasChildren = computed(
|
const hasChildren = computed(() => props.data.children.length > 0 || hasSubExecution(props.data));
|
||||||
() => props.data.children.length > 0 || !!props.data.runData?.metadata?.subExecution,
|
|
||||||
);
|
|
||||||
|
|
||||||
function isLastChild(level: number) {
|
function isLastChild(level: number) {
|
||||||
let parent = props.data.parent;
|
let parent = props.data.parent;
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { Workflow, type IRunExecutionData } from 'n8n-workflow';
|
|||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useThrottleFn } from '@vueuse/core';
|
import { useThrottleFn } from '@vueuse/core';
|
||||||
import { createLogTree, deepToRaw, mergeStartData } from '@/features/logs/logs.utils';
|
import {
|
||||||
|
createLogTree,
|
||||||
|
deepToRaw,
|
||||||
|
findSubExecutionLocator,
|
||||||
|
mergeStartData,
|
||||||
|
} from '@/features/logs/logs.utils';
|
||||||
import { parse } from 'flatted';
|
import { parse } from 'flatted';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import type { LatestNodeInfo, LogEntry } from '../logs.types';
|
import type { LatestNodeInfo, LogEntry } from '../logs.types';
|
||||||
@@ -68,15 +73,14 @@ export function useLogsExecutionData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadSubExecution(logEntry: LogEntry) {
|
async function loadSubExecution(logEntry: LogEntry) {
|
||||||
const executionId = logEntry.runData?.metadata?.subExecution?.executionId;
|
const locator = findSubExecutionLocator(logEntry);
|
||||||
const workflowId = logEntry.runData?.metadata?.subExecution?.workflowId;
|
|
||||||
|
|
||||||
if (!execData.value?.data || !executionId || !workflowId) {
|
if (!execData.value?.data || locator === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const subExecution = await workflowsStore.fetchExecutionDataById(executionId);
|
const subExecution = await workflowsStore.fetchExecutionDataById(locator.executionId);
|
||||||
const data = subExecution?.data
|
const data = subExecution?.data
|
||||||
? (parse(subExecution.data as unknown as string) as IRunExecutionData)
|
? (parse(subExecution.data as unknown as string) as IRunExecutionData)
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -85,8 +89,8 @@ export function useLogsExecutionData() {
|
|||||||
throw Error('Data is missing');
|
throw Error('Data is missing');
|
||||||
}
|
}
|
||||||
|
|
||||||
subWorkflowExecData.value[executionId] = data;
|
subWorkflowExecData.value[locator.executionId] = data;
|
||||||
subWorkflows.value[workflowId] = new Workflow({
|
subWorkflows.value[locator.workflowId] = new Workflow({
|
||||||
...subExecution.workflowData,
|
...subExecution.workflowData,
|
||||||
nodeTypes: workflowsStore.getNodeTypes(),
|
nodeTypes: workflowsStore.getNodeTypes(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
createLogTree,
|
createLogTree,
|
||||||
deepToRaw,
|
deepToRaw,
|
||||||
findSelectedLogEntry,
|
findSelectedLogEntry,
|
||||||
|
findSubExecutionLocator,
|
||||||
getDefaultCollapsedEntries,
|
getDefaultCollapsedEntries,
|
||||||
getTreeNodeData,
|
getTreeNodeData,
|
||||||
mergeStartData,
|
mergeStartData,
|
||||||
@@ -1271,3 +1272,43 @@ describe(restoreChatHistory, () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(findSubExecutionLocator, () => {
|
||||||
|
it('should return undefined if given log entry has no related sub execution', () => {
|
||||||
|
const found = findSubExecutionLocator(
|
||||||
|
createTestLogEntry({
|
||||||
|
runData: createTestTaskData({
|
||||||
|
metadata: {},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(found).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find workflowId and executionId in metadata', () => {
|
||||||
|
const found = findSubExecutionLocator(
|
||||||
|
createTestLogEntry({
|
||||||
|
runData: createTestTaskData({
|
||||||
|
metadata: { subExecution: { workflowId: 'w0', executionId: 'e0' } },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(found).toEqual({ workflowId: 'w0', executionId: 'e0' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find workflowId and executionId in error object', () => {
|
||||||
|
const found = findSubExecutionLocator(
|
||||||
|
createTestLogEntry({
|
||||||
|
runData: createTestTaskData({
|
||||||
|
error: {
|
||||||
|
errorResponse: { workflowId: 'w1', executionId: 'e1' },
|
||||||
|
} as unknown as ExecutionError,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(found).toEqual({ workflowId: 'w1', executionId: 'e1' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
type Workflow,
|
type Workflow,
|
||||||
type INode,
|
type INode,
|
||||||
type ISourceData,
|
type ISourceData,
|
||||||
|
parseErrorMetadata,
|
||||||
|
type RelatedExecution,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import type { LogEntry, LogEntrySelection, LogTreeCreationContext } from './logs.types';
|
import type { LogEntry, LogEntrySelection, LogTreeCreationContext } from './logs.types';
|
||||||
import { isProxy, isReactive, isRef, toRaw } from 'vue';
|
import { isProxy, isReactive, isRef, toRaw } from 'vue';
|
||||||
@@ -70,13 +72,13 @@ function getChildNodes(
|
|||||||
runIndex: number | undefined,
|
runIndex: number | undefined,
|
||||||
context: LogTreeCreationContext,
|
context: LogTreeCreationContext,
|
||||||
) {
|
) {
|
||||||
if (hasSubExecution(treeNode)) {
|
const subExecutionLocator = findSubExecutionLocator(treeNode);
|
||||||
const workflowId = treeNode.runData?.metadata?.subExecution?.workflowId;
|
|
||||||
const executionId = treeNode.runData?.metadata?.subExecution?.executionId;
|
|
||||||
const workflow = workflowId ? context.workflows[workflowId] : undefined;
|
|
||||||
const subWorkflowRunData = executionId ? context.subWorkflowData[executionId] : undefined;
|
|
||||||
|
|
||||||
if (!workflow || !subWorkflowRunData || !executionId) {
|
if (subExecutionLocator !== undefined) {
|
||||||
|
const workflow = context.workflows[subExecutionLocator.workflowId];
|
||||||
|
const subWorkflowRunData = context.subWorkflowData[subExecutionLocator.executionId];
|
||||||
|
|
||||||
|
if (!workflow || !subWorkflowRunData) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ function getChildNodes(
|
|||||||
parent: treeNode,
|
parent: treeNode,
|
||||||
depth: context.depth + 1,
|
depth: context.depth + 1,
|
||||||
workflow,
|
workflow,
|
||||||
executionId,
|
executionId: subExecutionLocator.executionId,
|
||||||
data: subWorkflowRunData,
|
data: subWorkflowRunData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -434,7 +436,17 @@ export function mergeStartData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function hasSubExecution(entry: LogEntry): boolean {
|
export function hasSubExecution(entry: LogEntry): boolean {
|
||||||
return !!entry.runData?.metadata?.subExecution;
|
return findSubExecutionLocator(entry) !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findSubExecutionLocator(entry: LogEntry): RelatedExecution | undefined {
|
||||||
|
const metadata = entry.runData?.metadata?.subExecution;
|
||||||
|
|
||||||
|
if (metadata) {
|
||||||
|
return { workflowId: metadata.workflowId, executionId: metadata.executionId };
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseErrorMetadata(entry.runData?.error)?.subExecution;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultCollapsedEntries(entries: LogEntry[]): Record<string, boolean> {
|
export function getDefaultCollapsedEntries(entries: LogEntry[]): Record<string, boolean> {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ catalog:
|
|||||||
xml2js: 0.6.2
|
xml2js: 0.6.2
|
||||||
xss: 1.0.15
|
xss: 1.0.15
|
||||||
zod: 3.24.1
|
zod: 3.24.1
|
||||||
'zod-to-json-schema': 3.23.3
|
zod-to-json-schema: 3.23.3
|
||||||
'@langchain/core': 0.3.48
|
'@langchain/core': 0.3.48
|
||||||
'@langchain/openai': 0.5.0
|
'@langchain/openai': 0.5.0
|
||||||
'@langchain/anthropic': 0.3.21
|
'@langchain/anthropic': 0.3.21
|
||||||
@@ -66,4 +66,4 @@ catalogs:
|
|||||||
vue-tsc: ^2.2.8
|
vue-tsc: ^2.2.8
|
||||||
vue-markdown-render: ^2.2.1
|
vue-markdown-render: ^2.2.1
|
||||||
highlight.js: ^11.8.0
|
highlight.js: ^11.8.0
|
||||||
'element-plus': 2.4.3
|
element-plus: 2.4.3
|
||||||
|
|||||||
Reference in New Issue
Block a user