From 454e5c77ade0d412eb2de1719a58a07fffcc4649 Mon Sep 17 00:00:00 2001 From: Suguru Inoue Date: Wed, 23 Apr 2025 15:31:12 +0200 Subject: [PATCH] fix(editor): Styling/UX improvements on the new logs view (#14789) --- .../CanvasChat/composables/useChatState.ts | 4 + .../CanvasChat/future/LogsPanel.test.ts | 10 +- .../CanvasChat/future/LogsPanel.vue | 21 ++-- .../future/components/LogDetailsPanel.test.ts | 12 +- .../future/components/LogDetailsPanel.vue | 19 +++- .../future/components/LogsOverviewPanel.vue | 9 +- .../future/components/LogsOverviewRow.vue | 49 ++------ .../future/components/PanelHeader.vue | 2 +- .../future/components/RunDataView.vue | 1 + .../future/composables/useLayout.ts | 28 +++-- .../composables/useResizablePanel.test.ts | 10 +- .../future/composables/useResizablePanel.ts | 63 +++++++---- .../components/ContextMenu/ContextMenu.vue | 3 + .../editor-ui/src/components/RunData.vue | 107 ++++++++---------- .../components/RunDataDisplayModeSelect.vue | 3 +- .../src/components/RunDataItemCount.vue | 9 +- .../editor-ui/src/components/RunDataJson.vue | 14 ++- .../src/components/RunDataPaginationBar.vue | 62 ++++++++++ .../editor-ui/src/components/RunDataTable.vue | 24 +++- .../src/components/VirtualSchema.vue | 8 +- .../__snapshots__/InputPanel.test.ts.snap | 85 +++++++------- .../src/components/canvas/Canvas.vue | 4 + .../composables/useViewportAutoAdjust.test.ts | 55 +++++++++ .../composables/useViewportAutoAdjust.ts | 57 ++++++++++ .../src/composables/useStyles.test.ts | 2 +- .../editor-ui/src/composables/useStyles.ts | 1 + .../editor-ui/src/composables/useToast.ts | 28 ++--- .../frontend/editor-ui/src/views/NodeView.vue | 10 +- 28 files changed, 472 insertions(+), 228 deletions(-) create mode 100644 packages/frontend/editor-ui/src/components/RunDataPaginationBar.vue create mode 100644 packages/frontend/editor-ui/src/components/canvas/composables/useViewportAutoAdjust.test.ts create mode 100644 packages/frontend/editor-ui/src/components/canvas/composables/useViewportAutoAdjust.ts diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts b/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts index 03fbfae9ae..8cfda2ac54 100644 --- a/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts +++ b/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts @@ -207,6 +207,10 @@ export function useChatState(isReadOnly: boolean, onWindowResize?: () => void): nodeHelpers.updateNodesExecutionIssues(); messages.value = []; currentSessionId.value = uuid().replace(/-/g, ''); + + if (logsPanelState.value !== LOGS_PANEL_STATE.CLOSED) { + chatEventBus.emit('focusInput'); + } } function displayExecution(executionId: string) { diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts b/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts index 7aad952e23..86de59fc28 100644 --- a/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts +++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts @@ -141,15 +141,17 @@ describe('LogsPanel', () => { await fireEvent.click(await rendered.findByTestId('logs-overview-header')); await fireEvent.click(await rendered.findByText('AI Agent')); - const detailsPanel = rendered.getByTestId('log-details'); - // Click the toggle button to close the panel - await fireEvent.click(within(detailsPanel).getByLabelText('Collapse panel')); + await fireEvent.click( + within(rendered.getByTestId('log-details')).getByLabelText('Collapse panel'), + ); expect(rendered.queryByTestId('chat-messages-empty')).not.toBeInTheDocument(); expect(rendered.queryByTestId('logs-overview-body')).not.toBeInTheDocument(); // Click again to open the panel - await fireEvent.click(within(detailsPanel).getByLabelText('Open panel')); + await fireEvent.click( + within(rendered.getByTestId('logs-overview')).getByLabelText('Open panel'), + ); expect(await rendered.findByTestId('chat-messages-empty')).toBeInTheDocument(); expect(await rendered.findByTestId('logs-overview-body')).toBeInTheDocument(); }); diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.vue index d78f89c4df..3be60ae895 100644 --- a/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.vue +++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.vue @@ -77,10 +77,10 @@ const selectedLogEntry = computed(() => ? undefined : manualLogEntrySelection.value.data, ); -const isLogDetailsOpen = computed( - () => selectedLogEntry.value !== undefined && !isCollapsingDetailsPanel.value, +const isLogDetailsOpen = computed(() => isOpen.value && selectedLogEntry.value !== undefined); +const isLogDetailsVisuallyOpen = computed( + () => isLogDetailsOpen.value && !isCollapsingDetailsPanel.value, ); -const isLogDetailsOpenOrCollapsing = computed(() => selectedLogEntry.value !== undefined); const logsPanelActionsProps = computed['$props']>(() => ({ isOpen: isOpen.value, showToggleButton: !isPoppedOut.value, @@ -149,9 +149,9 @@ function handleResizeOverviewPanelEnd() { diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts index ad486c6468..959f2f85ef 100644 --- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts +++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts @@ -92,9 +92,13 @@ describe('LogDetailsPanel', () => { createdAt: '2025-04-16T00:00:00.000Z', startedAt: '2025-04-16T00:00:01.000Z', }); + + localStorage.clear(); }); it('should show name, run status, input, and output of the node', async () => { + localStorage.setItem('N8N_LOGS_DETAIL_PANEL_CONTENT', 'both'); + const rendered = render({ isOpen: true, logEntry: createTestLogEntry({ node: 'AI Agent', runIndex: 0 }), @@ -118,12 +122,12 @@ describe('LogDetailsPanel', () => { const header = within(rendered.getByTestId('log-details-header')); - expect(rendered.queryByTestId('log-details-input')).toBeInTheDocument(); + expect(rendered.queryByTestId('log-details-input')).not.toBeInTheDocument(); expect(rendered.queryByTestId('log-details-output')).toBeInTheDocument(); await fireEvent.click(header.getByText('Input')); - expect(rendered.queryByTestId('log-details-input')).not.toBeInTheDocument(); + expect(rendered.queryByTestId('log-details-input')).toBeInTheDocument(); expect(rendered.queryByTestId('log-details-output')).toBeInTheDocument(); await fireEvent.click(header.getByText('Output')); @@ -133,6 +137,8 @@ describe('LogDetailsPanel', () => { }); it('should close input panel by dragging the divider to the left end', async () => { + localStorage.setItem('N8N_LOGS_DETAIL_PANEL_CONTENT', 'both'); + const rendered = render({ isOpen: true, logEntry: createTestLogEntry({ node: 'AI Agent', runIndex: 0 }), @@ -150,6 +156,8 @@ describe('LogDetailsPanel', () => { }); it('should close output panel by dragging the divider to the right end', async () => { + localStorage.setItem('N8N_LOGS_DETAIL_PANEL_CONTENT', 'both'); + const rendered = render({ isOpen: true, logEntry: createTestLogEntry({ node: 'AI Agent', runIndex: 0 }), diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue index 74c3860122..63da30b74d 100644 --- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue +++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue @@ -12,8 +12,9 @@ import { type INodeUi } from '@/Interface'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { N8nButton, N8nResizeWrapper, N8nText } from '@n8n/design-system'; +import { useLocalStorage } from '@vueuse/core'; import { type ITaskData } from 'n8n-workflow'; -import { computed, ref, useTemplateRef } from 'vue'; +import { computed, useTemplateRef } from 'vue'; const MIN_IO_PANEL_WIDTH = 200; @@ -32,7 +33,11 @@ const telemetry = useTelemetry(); const workflowsStore = useWorkflowsStore(); const nodeTypeStore = useNodeTypesStore(); -const content = ref(LOG_DETAILS_CONTENT.BOTH); +const content = useLocalStorage( + 'N8N_LOGS_DETAIL_PANEL_CONTENT', + LOG_DETAILS_CONTENT.OUTPUT, + { writeDefaults: false }, +); const node = computed(() => workflowsStore.nodesByName[logEntry.node]); const type = computed(() => (node.value ? nodeTypeStore.getNodeType(node.value.type) : undefined)); @@ -100,7 +105,11 @@ function handleResizeEnd() {