mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-19 11:01:15 +00:00
perf(editor): Fix log view related slowdown of manual execution with large data (#18256)
This commit is contained in:
@@ -4,12 +4,7 @@ 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 {
|
import { createLogTree, findSubExecutionLocator, mergeStartData } from '@/features/logs/logs.utils';
|
||||||
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';
|
||||||
@@ -113,12 +108,10 @@ export function useLogsExecutionData() {
|
|||||||
execData.value =
|
execData.value =
|
||||||
workflowsStore.workflowExecutionData === null
|
workflowsStore.workflowExecutionData === null
|
||||||
? undefined
|
? undefined
|
||||||
: deepToRaw(
|
: mergeStartData(
|
||||||
mergeStartData(
|
workflowsStore.workflowExecutionStartedData?.[1] ?? {},
|
||||||
workflowsStore.workflowExecutionStartedData?.[1] ?? {},
|
workflowsStore.workflowExecutionData,
|
||||||
workflowsStore.workflowExecutionData,
|
);
|
||||||
),
|
|
||||||
); // Create deep copy to disable reactivity
|
|
||||||
|
|
||||||
if (executionId !== previousExecutionId) {
|
if (executionId !== previousExecutionId) {
|
||||||
// Reset sub workflow data when top-level execution changes
|
// Reset sub workflow data when top-level execution changes
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
} from '@/__tests__/mocks';
|
} from '@/__tests__/mocks';
|
||||||
import {
|
import {
|
||||||
createLogTree,
|
createLogTree,
|
||||||
deepToRaw,
|
|
||||||
findSelectedLogEntry,
|
findSelectedLogEntry,
|
||||||
findSubExecutionLocator,
|
findSubExecutionLocator,
|
||||||
getDefaultCollapsedEntries,
|
getDefaultCollapsedEntries,
|
||||||
@@ -27,7 +26,6 @@ import {
|
|||||||
} from './__test__/data';
|
} from './__test__/data';
|
||||||
import type { LogEntrySelection } from './logs.types';
|
import type { LogEntrySelection } from './logs.types';
|
||||||
import type { IExecutionResponse } from '@/Interface';
|
import type { IExecutionResponse } from '@/Interface';
|
||||||
import { isReactive, reactive } from 'vue';
|
|
||||||
import { createTestLogEntry } from './__test__/mocks';
|
import { createTestLogEntry } from './__test__/mocks';
|
||||||
import { AGENT_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE } from '@/constants';
|
import { AGENT_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE } from '@/constants';
|
||||||
|
|
||||||
@@ -1279,29 +1277,6 @@ describe('extractBotResponse', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(deepToRaw, () => {
|
|
||||||
it('should convert reactive fields to raw in data with circular structure', () => {
|
|
||||||
const data = reactive({
|
|
||||||
foo: reactive({ bar: {} }),
|
|
||||||
bazz: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
data.foo.bar = data;
|
|
||||||
data.bazz = data;
|
|
||||||
|
|
||||||
const raw = deepToRaw(data);
|
|
||||||
|
|
||||||
expect(isReactive(data)).toBe(true);
|
|
||||||
expect(isReactive(data.foo)).toBe(true);
|
|
||||||
expect(isReactive(data.foo.bar)).toBe(true);
|
|
||||||
expect(isReactive(data.bazz)).toBe(true);
|
|
||||||
expect(isReactive(raw)).toBe(false);
|
|
||||||
expect(isReactive(raw.foo)).toBe(false);
|
|
||||||
expect(isReactive(raw.foo.bar)).toBe(false);
|
|
||||||
expect(isReactive(raw.bazz)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(mergeStartData, () => {
|
describe(mergeStartData, () => {
|
||||||
it('should return unchanged execution response if start data is empty', () => {
|
it('should return unchanged execution response if start data is empty', () => {
|
||||||
const response = createTestWorkflowExecutionResponse({
|
const response = createTestWorkflowExecutionResponse({
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
type RelatedExecution,
|
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 { CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE } from '@/constants';
|
import { CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE } from '@/constants';
|
||||||
import { type ChatMessage } from '@n8n/chat/types';
|
import { type ChatMessage } from '@n8n/chat/types';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
@@ -312,44 +311,6 @@ export function findSelectedLogEntry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deepToRaw<T>(sourceObj: T): T {
|
|
||||||
const seen = new WeakMap();
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const objectIterator = (input: any): any => {
|
|
||||||
if (seen.has(input)) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input !== null && typeof input === 'object') {
|
|
||||||
seen.set(input, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(input)) {
|
|
||||||
return input.map((item) => objectIterator(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRef(input) || isReactive(input) || isProxy(input)) {
|
|
||||||
return objectIterator(toRaw(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
input !== null &&
|
|
||||||
typeof input === 'object' &&
|
|
||||||
Object.getPrototypeOf(input) === Object.prototype
|
|
||||||
) {
|
|
||||||
return Object.keys(input).reduce((acc, key) => {
|
|
||||||
acc[key as keyof typeof acc] = objectIterator(input[key]);
|
|
||||||
return acc;
|
|
||||||
}, {} as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
};
|
|
||||||
|
|
||||||
return objectIterator(sourceObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function flattenLogEntries(
|
export function flattenLogEntries(
|
||||||
entries: LogEntry[],
|
entries: LogEntry[],
|
||||||
collapsedEntryIds: Record<string, boolean>,
|
collapsedEntryIds: Record<string, boolean>,
|
||||||
|
|||||||
Reference in New Issue
Block a user