diff --git a/packages/frontend/editor-ui/src/features/execution/logs/components/LogDetailsPanel.vue b/packages/frontend/editor-ui/src/features/execution/logs/components/LogDetailsPanel.vue index cb8532934fb..4bd481cc7d2 100644 --- a/packages/frontend/editor-ui/src/features/execution/logs/components/LogDetailsPanel.vue +++ b/packages/frontend/editor-ui/src/features/execution/logs/components/LogDetailsPanel.vue @@ -3,6 +3,7 @@ import LogsViewExecutionSummary from '@/features/execution/logs/components/LogsV import LogsViewConsumedTokenCountText from '@/features/execution/logs/components/LogsViewConsumedTokenCountText.vue'; import LogsPanelHeader from '@/features/execution/logs/components/LogsPanelHeader.vue'; import LogsViewRunData from '@/features/execution/logs/components/LogsViewRunData.vue'; +import LogsGroupRunData from '@/features/execution/logs/components/LogsGroupRunData.vue'; import { useResizablePanel } from '@/app/composables/useResizablePanel'; import { isLogGroupEntry, @@ -17,6 +18,8 @@ import LogsViewNodeName from '@/features/execution/logs/components/LogsViewNodeN import { computed, useTemplateRef } from 'vue'; import KeyboardShortcutTooltip from '@/app/components/KeyboardShortcutTooltip.vue'; import { + getGroupInputEntries, + getGroupOutputEntries, getSubtreeTotalConsumedTokens, isPlaceholderLog, } from '@/features/execution/logs/logs.utils'; @@ -76,6 +79,14 @@ const inputEntry = computed(() => (isLogGroupEntry(logEntry) ? logEntry.inputLog const outputEntry = computed(() => isLogGroupEntry(logEntry) ? logEntry.outputLogEntry : logEntry, ); +// A group can have multiple boundary members on either side; the panes let the +// user switch between them (run selection is handled by RunData below). +const groupInputEntries = computed(() => + isLogGroupEntry(logEntry) ? getGroupInputEntries(logEntry) : [], +); +const groupOutputEntries = computed(() => + isLogGroupEntry(logEntry) ? getGroupOutputEntries(logEntry) : [], +); const type = computed(() => isLogGroupEntry(logEntry) ? undefined : nodeTypeStore.getNodeType(logEntry.node.type), ); @@ -223,7 +234,19 @@ function handleResizeEnd() { @resize="resizer.onResize" @resizeend="handleResizeEnd" > + + +import { computed } from 'vue'; +import { N8nOption, N8nSelect } from '@n8n/design-system'; +import NodeIcon from '@/app/components/NodeIcon.vue'; +import { useNodeTypesStore } from '@/app/stores/nodeTypes.store'; +import type { LogEntry } from '@/features/execution/logs/logs.types'; + +const props = defineProps<{ + entries: LogEntry[]; + modelValue: number; + ariaLabel?: string; +}>(); + +const emit = defineEmits<{ + 'update:model-value': [value: number]; +}>(); + +const nodeTypesStore = useNodeTypesStore(); + +const options = computed(() => + props.entries.map((entry, index) => ({ + index, + name: entry.node.name, + type: nodeTypesStore.getNodeType(entry.node.type, entry.node.typeVersion), + })), +); + +const selectedType = computed(() => options.value[props.modelValue]?.type ?? null); + +function onChange(value: unknown) { + emit('update:model-value', Number(value)); +} + + + + + diff --git a/packages/frontend/editor-ui/src/features/execution/logs/components/LogsGroupRunData.vue b/packages/frontend/editor-ui/src/features/execution/logs/components/LogsGroupRunData.vue new file mode 100644 index 00000000000..2e468cf6cbe --- /dev/null +++ b/packages/frontend/editor-ui/src/features/execution/logs/components/LogsGroupRunData.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/packages/frontend/editor-ui/src/features/execution/logs/components/LogsViewRunData.vue b/packages/frontend/editor-ui/src/features/execution/logs/components/LogsViewRunData.vue index 68c8a87b3de..5bd97347e3b 100644 --- a/packages/frontend/editor-ui/src/features/execution/logs/components/LogsViewRunData.vue +++ b/packages/frontend/editor-ui/src/features/execution/logs/components/LogsViewRunData.vue @@ -25,6 +25,7 @@ const { paneType, collapsingTableColumnName, showRedactedOverlay = true, + enableRunSelection = false, } = defineProps<{ title: string; paneType: NodePanelType; @@ -32,6 +33,7 @@ const { collapsingTableColumnName: string | null; searchShortcut?: SearchShortcut; showRedactedOverlay?: boolean; + enableRunSelection?: boolean; }>(); const emit = defineEmits<{ @@ -72,6 +74,15 @@ const runDataProps = computed< overrideOutputs: [source.previousNodeOutput ?? 0], }; }); +// For the source-based path (input pane), RunData must read the source node's +// OUTPUT data (taskData.data), not its inputOverride (what it received). Using +// paneType="output" makes getNodeInputData skip inputOverride and read taskData.data, +// which is the data the selected node actually receives as input. +const effectivePaneType = computed(() => + paneType === 'input' && !isSubNodeLog(logEntry) && !!runDataProps.value?.overrideOutputs + ? 'output' + : paneType, +); const isExecuting = computed( () => paneType === 'output' && @@ -92,14 +103,14 @@ function handleChangeDisplayMode(value: IRunDataDisplayMode) { + +