From 56c278cda06dcc70eea450bf39d84bfa54aa3278 Mon Sep 17 00:00:00 2001 From: Suguru Inoue Date: Tue, 12 Aug 2025 15:07:51 +0200 Subject: [PATCH] perf(editor): Fix log view related slowdown of manual execution with large data (#18256) --- .../logs/composables/useLogsExecutionData.ts | 17 +++----- .../src/features/logs/logs.utils.test.ts | 25 ------------ .../editor-ui/src/features/logs/logs.utils.ts | 39 ------------------- 3 files changed, 5 insertions(+), 76 deletions(-) diff --git a/packages/frontend/editor-ui/src/features/logs/composables/useLogsExecutionData.ts b/packages/frontend/editor-ui/src/features/logs/composables/useLogsExecutionData.ts index 9eef55fff1..99c8304c9b 100644 --- a/packages/frontend/editor-ui/src/features/logs/composables/useLogsExecutionData.ts +++ b/packages/frontend/editor-ui/src/features/logs/composables/useLogsExecutionData.ts @@ -4,12 +4,7 @@ import { Workflow, type IRunExecutionData } from 'n8n-workflow'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useThrottleFn } from '@vueuse/core'; -import { - createLogTree, - deepToRaw, - findSubExecutionLocator, - mergeStartData, -} from '@/features/logs/logs.utils'; +import { createLogTree, findSubExecutionLocator, mergeStartData } from '@/features/logs/logs.utils'; import { parse } from 'flatted'; import { useToast } from '@/composables/useToast'; import type { LatestNodeInfo, LogEntry } from '../logs.types'; @@ -113,12 +108,10 @@ export function useLogsExecutionData() { execData.value = workflowsStore.workflowExecutionData === null ? undefined - : deepToRaw( - mergeStartData( - workflowsStore.workflowExecutionStartedData?.[1] ?? {}, - workflowsStore.workflowExecutionData, - ), - ); // Create deep copy to disable reactivity + : mergeStartData( + workflowsStore.workflowExecutionStartedData?.[1] ?? {}, + workflowsStore.workflowExecutionData, + ); if (executionId !== previousExecutionId) { // Reset sub workflow data when top-level execution changes diff --git a/packages/frontend/editor-ui/src/features/logs/logs.utils.test.ts b/packages/frontend/editor-ui/src/features/logs/logs.utils.test.ts index 16eb082d28..06981a1ba7 100644 --- a/packages/frontend/editor-ui/src/features/logs/logs.utils.test.ts +++ b/packages/frontend/editor-ui/src/features/logs/logs.utils.test.ts @@ -7,7 +7,6 @@ import { } from '@/__tests__/mocks'; import { createLogTree, - deepToRaw, findSelectedLogEntry, findSubExecutionLocator, getDefaultCollapsedEntries, @@ -27,7 +26,6 @@ import { } from './__test__/data'; import type { LogEntrySelection } from './logs.types'; import type { IExecutionResponse } from '@/Interface'; -import { isReactive, reactive } from 'vue'; import { createTestLogEntry } from './__test__/mocks'; 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, () => { it('should return unchanged execution response if start data is empty', () => { const response = createTestWorkflowExecutionResponse({ diff --git a/packages/frontend/editor-ui/src/features/logs/logs.utils.ts b/packages/frontend/editor-ui/src/features/logs/logs.utils.ts index 8c742221e7..cfe38e3248 100644 --- a/packages/frontend/editor-ui/src/features/logs/logs.utils.ts +++ b/packages/frontend/editor-ui/src/features/logs/logs.utils.ts @@ -14,7 +14,6 @@ import { type RelatedExecution, } from 'n8n-workflow'; 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 { type ChatMessage } from '@n8n/chat/types'; import get from 'lodash/get'; @@ -312,44 +311,6 @@ export function findSelectedLogEntry( } } -export function deepToRaw(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( entries: LogEntry[], collapsedEntryIds: Record,