mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Collapse button on table view (#16993)
This commit is contained in:
@@ -44,6 +44,7 @@ import IconLucideChevronDown from '~icons/lucide/chevron-down';
|
||||
import IconLucideChevronLeft from '~icons/lucide/chevron-left';
|
||||
import IconLucideChevronRight from '~icons/lucide/chevron-right';
|
||||
import IconLucideChevronUp from '~icons/lucide/chevron-up';
|
||||
import IconLucideChevronsDownUp from '~icons/lucide/chevrons-down-up';
|
||||
import IconLucideChevronsLeft from '~icons/lucide/chevrons-left';
|
||||
import IconLucideChevronsUpDown from '~icons/lucide/chevrons-up-down';
|
||||
import IconLucideCircle from '~icons/lucide/circle';
|
||||
@@ -438,6 +439,8 @@ export const updatedIconSet = {
|
||||
'chevron-right': IconLucideChevronRight,
|
||||
'chevron-up': IconLucideChevronUp,
|
||||
'chevrons-left': IconLucideChevronsLeft,
|
||||
'chevrons-down-up': IconLucideChevronsDownUp,
|
||||
'chevrons-up-down': IconLucideChevronsUpDown,
|
||||
circle: IconLucideCircle,
|
||||
'circle-alert': IconLucideCircleAlert,
|
||||
'circle-check': IconLucideCircleCheck,
|
||||
|
||||
@@ -683,6 +683,8 @@
|
||||
"dataMapping.tableView.tableColumnsExceeded": "Some columns are hidden",
|
||||
"dataMapping.tableView.tableColumnsExceeded.tooltip": "Your data has more than {columnLimit} columns so some are hidden. Switch to {link} to see all data.",
|
||||
"dataMapping.tableView.tableColumnsExceeded.tooltip.link": "JSON view",
|
||||
"dataMapping.tableView.columnCollapsing": "Collapse rows",
|
||||
"dataMapping.tableView.columnCollapsing.tooltip": "Collapse rows (to compare values in this column)",
|
||||
"dataMapping.schemaView.emptyData": "No fields - node executed, but no items were sent on this branch",
|
||||
"dataMapping.schemaView.emptySchema": "No fields - item(s) exist, but they're empty",
|
||||
"dataMapping.schemaView.emptySchemaWithBinary": "Only binary data exists. View it using the 'Binary' tab",
|
||||
|
||||
@@ -84,6 +84,7 @@ const showDraggableHintWithDelay = ref(false);
|
||||
const draggableHintShown = ref(false);
|
||||
|
||||
const mappedNode = ref<string | null>(null);
|
||||
const collapsingColumnName = ref<string | null>(null);
|
||||
const inputModes = [
|
||||
{ value: 'mapping', label: i18n.baseText('ndv.input.mapping') },
|
||||
{ value: 'debugging', label: i18n.baseText('ndv.input.fromAI') },
|
||||
@@ -365,6 +366,10 @@ function onConnectionHelpClick() {
|
||||
function activatePane() {
|
||||
emit('activatePane');
|
||||
}
|
||||
|
||||
function handleChangeCollapsingColumn(columnName: string | null) {
|
||||
collapsingColumnName.value = columnName;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -390,6 +395,7 @@ function activatePane() {
|
||||
pane-type="input"
|
||||
data-test-id="ndv-input-panel"
|
||||
:disable-ai-content="true"
|
||||
:collapsing-table-column-name="collapsingColumnName"
|
||||
@activate-pane="activatePane"
|
||||
@item-hover="onItemHover"
|
||||
@link-run="onLinkRun"
|
||||
@@ -398,6 +404,7 @@ function activatePane() {
|
||||
@table-mounted="onTableMounted"
|
||||
@search="onSearch"
|
||||
@display-mode-change="emit('displayModeChange', $event)"
|
||||
@collapsing-table-column-changed="handleChangeCollapsingColumn"
|
||||
>
|
||||
<template #header>
|
||||
<div :class="[$style.titleSection, { [$style.titleSectionV2]: isNDVV2 }]">
|
||||
|
||||
@@ -103,6 +103,7 @@ const outputTypes = ref([
|
||||
{ label: i18n.baseText('ndv.output.outType.logs'), value: OUTPUT_TYPE.LOGS },
|
||||
]);
|
||||
const runDataRef = ref<RunDataRef>();
|
||||
const collapsingColumnName = ref<string | null>(null);
|
||||
|
||||
// Computed
|
||||
|
||||
@@ -321,6 +322,10 @@ watch(defaultOutputMode, (newValue: OutputType, oldValue: OutputType) => {
|
||||
const activatePane = () => {
|
||||
emit('activatePane');
|
||||
};
|
||||
|
||||
function handleChangeCollapsingColumn(columnName: string | null) {
|
||||
collapsingColumnName.value = columnName;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -346,6 +351,7 @@ const activatePane = () => {
|
||||
:callout-message="allToolsWereUnusedNotice"
|
||||
:display-mode="displayMode"
|
||||
:disable-ai-content="true"
|
||||
:collapsing-table-column-name="collapsingColumnName"
|
||||
data-test-id="ndv-output-panel"
|
||||
@activate-pane="activatePane"
|
||||
@run-change="onRunIndexChange"
|
||||
@@ -355,6 +361,7 @@ const activatePane = () => {
|
||||
@item-hover="emit('itemHover', $event)"
|
||||
@search="emit('search', $event)"
|
||||
@display-mode-change="emit('displayModeChange', $event)"
|
||||
@collapsing-table-column-changed="handleChangeCollapsingColumn"
|
||||
>
|
||||
<template #header>
|
||||
<div :class="[$style.titleSection, { [$style.titleSectionV2]: isNDVV2 }]">
|
||||
|
||||
@@ -152,6 +152,7 @@ type Props = {
|
||||
tableHeaderBgColor?: 'base' | 'light';
|
||||
disableHoverHighlight?: boolean;
|
||||
disableAiContent?: boolean;
|
||||
collapsingTableColumnName: string | null;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -207,6 +208,7 @@ const emit = defineEmits<{
|
||||
},
|
||||
];
|
||||
displayModeChange: [IRunDataDisplayMode];
|
||||
collapsingTableColumnChanged: [columnName: string | null];
|
||||
}>();
|
||||
|
||||
const connectionType = ref<NodeConnectionType>(NodeConnectionTypes.Main);
|
||||
@@ -1436,6 +1438,16 @@ defineExpose({ enterEditMode });
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
<N8nIconButton
|
||||
v-if="displayMode === 'table' && collapsingTableColumnName !== null"
|
||||
:class="$style.resetCollapseButton"
|
||||
text
|
||||
icon="chevrons-up-down"
|
||||
size="xmini"
|
||||
type="tertiary"
|
||||
@click="emit('collapsingTableColumnChanged', null)"
|
||||
/>
|
||||
|
||||
<RunDataDisplayModeSelect
|
||||
v-show="
|
||||
hasPreviewSchema ||
|
||||
@@ -1844,9 +1856,11 @@ defineExpose({ enterEditMode });
|
||||
:header-bg-color="tableHeaderBgColor"
|
||||
:compact="props.compact"
|
||||
:disable-hover-highlight="props.disableHoverHighlight"
|
||||
:collapsing-column-name="collapsingTableColumnName"
|
||||
@mounted="emit('tableMounted', $event)"
|
||||
@active-row-changed="onItemHover"
|
||||
@display-mode-change="onDisplayModeChange"
|
||||
@collapsing-column-changed="emit('collapsingTableColumnChanged', $event)"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
@@ -2335,6 +2349,10 @@ defineExpose({ enterEditMode });
|
||||
}
|
||||
}
|
||||
|
||||
.resetCollapseButton {
|
||||
color: var(--color-foreground-xdark);
|
||||
}
|
||||
|
||||
@container (max-width: 240px) {
|
||||
/* Hide title when the panel is too narrow */
|
||||
.compact:hover .title {
|
||||
|
||||
@@ -130,6 +130,7 @@ watch(
|
||||
.ioSearchIcon {
|
||||
color: var(--color-foreground-xdark);
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:global(.el-input__prefix) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import RunDataTable from '@/components/RunDataTable.vue';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { cleanup } from '@testing-library/vue';
|
||||
import { cleanup, fireEvent, waitFor } from '@testing-library/vue';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
vi.mock('vue-router', () => {
|
||||
const push = vi.fn();
|
||||
@@ -107,4 +108,48 @@ describe('RunDataTable.vue', () => {
|
||||
expect(getAllByText(value)).not.toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('inserts col elements in DOM to specify column widths when collapsing column name is specified', async () => {
|
||||
const inputData = { json: { firstName: 'John', lastName: 'Doe' } };
|
||||
const rendered = renderComponent({
|
||||
props: {
|
||||
inputData: [inputData],
|
||||
collapsingColumnName: null,
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(rendered.container.querySelectorAll('col')).toHaveLength(0);
|
||||
|
||||
await rendered.rerender({
|
||||
inputData: [inputData],
|
||||
collapsingColumnName: 'firstName',
|
||||
});
|
||||
|
||||
await waitFor(() => expect(rendered.container.querySelectorAll('col')).toHaveLength(3)); // two data columns + one right margin column
|
||||
|
||||
await rendered.rerender({
|
||||
inputData: [inputData],
|
||||
collapsingColumnName: null,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(rendered.container.querySelectorAll('col')).toHaveLength(0));
|
||||
});
|
||||
|
||||
it('shows the button for column collapsing in the column header', async () => {
|
||||
const inputData = { json: { firstName: 'John', lastName: 'Doe' } };
|
||||
const rendered = renderComponent({
|
||||
props: {
|
||||
inputData: [inputData],
|
||||
collapsingColumnName: 'firstName',
|
||||
},
|
||||
});
|
||||
|
||||
expect(rendered.getAllByLabelText('Collapse rows')).toHaveLength(2);
|
||||
|
||||
await fireEvent.click(rendered.getAllByLabelText('Collapse rows')[0]);
|
||||
await fireEvent.click(rendered.getAllByLabelText('Collapse rows')[1]);
|
||||
expect(rendered.emitted('collapsingColumnChanged')).toEqual([[null], ['lastName']]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getMappedExpression } from '@/utils/mappingUtils';
|
||||
import { getPairedItemId } from '@/utils/pairedItemUtils';
|
||||
import { shorten } from '@/utils/typesUtils';
|
||||
import type { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useTemplateRef, computed, onMounted, ref, watch } from 'vue';
|
||||
import Draggable from '@/components/Draggable.vue';
|
||||
import MappingPill from './MappingPill.vue';
|
||||
import TextWithHighlights from './TextWithHighlights.vue';
|
||||
@@ -35,6 +35,7 @@ type Props = {
|
||||
headerBgColor?: 'base' | 'light';
|
||||
compact?: boolean;
|
||||
disableHoverHighlight?: boolean;
|
||||
collapsingColumnName: string | null;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -52,9 +53,13 @@ const emit = defineEmits<{
|
||||
activeRowChanged: [row: number | null];
|
||||
displayModeChange: [mode: IRunDataDisplayMode];
|
||||
mounted: [data: { avgRowHeight: number }];
|
||||
collapsingColumnChanged: [columnName: string | null];
|
||||
}>();
|
||||
|
||||
const externalHooks = useExternalHooks();
|
||||
|
||||
const tableRef = useTemplateRef('tableRef');
|
||||
|
||||
const activeColumn = ref(-1);
|
||||
const forceShowGrip = ref(false);
|
||||
const draggedColumn = ref(false);
|
||||
@@ -64,6 +69,7 @@ const activeRow = ref<number | null>(null);
|
||||
const columnLimit = ref(MAX_COLUMNS_LIMIT);
|
||||
const columnLimitExceeded = ref(false);
|
||||
const draggableRef = ref<DraggableRef>();
|
||||
const fixedColumnWidths = ref<number[] | undefined>();
|
||||
|
||||
const ndvStore = useNDVStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
@@ -82,6 +88,13 @@ const canDraggableDrop = computed(() => ndvStore.canDraggableDrop);
|
||||
const draggableStickyPosition = computed(() => ndvStore.draggableStickyPos);
|
||||
const pairedItemMappings = computed(() => workflowsStore.workflowExecutionPairedItemMappings);
|
||||
const tableData = computed(() => convertToTable(props.inputData));
|
||||
const collapsingColumnIndex = computed(() => {
|
||||
if (!props.collapsingColumnName) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return tableData.value.columns.indexOf(props.collapsingColumnName);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (tableData.value?.columns && draggableRef.value) {
|
||||
@@ -419,6 +432,15 @@ function switchToJsonView() {
|
||||
emit('displayModeChange', 'json');
|
||||
}
|
||||
|
||||
function handleSetCollapsingColumn(columnIndex: number) {
|
||||
emit(
|
||||
'collapsingColumnChanged',
|
||||
collapsingColumnIndex.value === columnIndex
|
||||
? null
|
||||
: (tableData.value.columns[columnIndex] ?? null),
|
||||
);
|
||||
}
|
||||
|
||||
watch(focusedMappableInput, (curr) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
@@ -427,6 +449,27 @@ watch(focusedMappableInput, (curr) => {
|
||||
curr ? 300 : 150,
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
[collapsingColumnIndex, tableRef],
|
||||
([index, table]) => {
|
||||
if (index === -1) {
|
||||
fixedColumnWidths.value = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if (table === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fixedColumnWidths.value = [...table.querySelectorAll('thead tr th')].map((el) =>
|
||||
el instanceof HTMLElement
|
||||
? el.getBoundingClientRect().width // using getBoundingClientRect for decimal accuracy
|
||||
: 0,
|
||||
);
|
||||
},
|
||||
{ immediate: true, flush: 'post' },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -437,6 +480,7 @@ watch(focusedMappableInput, (curr) => {
|
||||
[$style.highlight]: highlight,
|
||||
[$style.lightHeader]: headerBgColor === 'light',
|
||||
[$style.compact]: props.compact,
|
||||
[$style.hasCollapsingColumn]: fixedColumnWidths !== undefined,
|
||||
},
|
||||
]"
|
||||
>
|
||||
@@ -500,13 +544,20 @@ watch(focusedMappableInput, (curr) => {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table v-else :class="$style.table">
|
||||
<table v-else ref="tableRef" :class="$style.table">
|
||||
<colgroup v-if="fixedColumnWidths">
|
||||
<col v-for="(width, i) in fixedColumnWidths" :key="i" :width="width" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="tableData.metadata.hasExecutionIds" :class="$style.executionLinkRowHeader">
|
||||
<!-- column for execution link -->
|
||||
</th>
|
||||
<th v-for="(column, i) in tableData.columns || []" :key="column">
|
||||
<th
|
||||
v-for="(column, i) in tableData.columns || []"
|
||||
:key="column"
|
||||
:class="collapsingColumnIndex === i ? $style.isCollapsingColumn : ''"
|
||||
>
|
||||
<N8nTooltip placement="bottom-start" :disabled="!mappingEnabled" :show-after="1000">
|
||||
<template #content>
|
||||
<div>
|
||||
@@ -540,7 +591,23 @@ watch(focusedMappableInput, (curr) => {
|
||||
:content="getValueToRender(column || '')"
|
||||
:search="search"
|
||||
/>
|
||||
<div :class="$style.dragButton">
|
||||
<N8nTooltip
|
||||
:content="i18n.baseText('dataMapping.tableView.columnCollapsing.tooltip')"
|
||||
:disabled="mappingEnabled || collapsingColumnIndex === i"
|
||||
>
|
||||
<N8nIconButton
|
||||
:class="$style.collapseColumnButton"
|
||||
type="tertiary"
|
||||
size="xmini"
|
||||
text
|
||||
:icon="
|
||||
collapsingColumnIndex === i ? 'chevrons-up-down' : 'chevrons-down-up'
|
||||
"
|
||||
:aria-label="i18n.baseText('dataMapping.tableView.columnCollapsing')"
|
||||
@click="handleSetCollapsingColumn(i)"
|
||||
/>
|
||||
</N8nTooltip>
|
||||
<div v-if="mappingEnabled" :class="$style.dragButton">
|
||||
<n8n-icon icon="grip-vertical" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -629,7 +696,10 @@ watch(focusedMappableInput, (curr) => {
|
||||
:key="index2"
|
||||
:data-row="index1"
|
||||
:data-col="index2"
|
||||
:class="hasJsonInColumn(index2) ? $style.minColWidth : $style.limitColWidth"
|
||||
:class="[
|
||||
hasJsonInColumn(index2) ? $style.minColWidth : $style.limitColWidth,
|
||||
collapsingColumnIndex === index2 ? $style.isCollapsingColumn : '',
|
||||
]"
|
||||
@mouseenter="onMouseEnterCell"
|
||||
@mouseleave="onMouseLeaveCell"
|
||||
>
|
||||
@@ -757,6 +827,41 @@ watch(focusedMappableInput, (curr) => {
|
||||
td:last-child {
|
||||
border-right: var(--border-base);
|
||||
}
|
||||
|
||||
.hasCollapsingColumn & {
|
||||
table-layout: fixed;
|
||||
|
||||
td:not(.isCollapsingColumn) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
& :global(.n8n-tree) {
|
||||
height: 1.5em;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th.isCollapsingColumn {
|
||||
border-top-color: var(--color-foreground-xdark);
|
||||
border-left-color: var(--color-foreground-xdark);
|
||||
border-right-color: var(--color-foreground-xdark);
|
||||
}
|
||||
|
||||
td.isCollapsingColumn {
|
||||
border-left-color: var(--color-foreground-xdark);
|
||||
border-right-color: var(--color-foreground-xdark);
|
||||
|
||||
tr:last-child & {
|
||||
border-bottom-color: var(--color-foreground-xdark);
|
||||
}
|
||||
}
|
||||
|
||||
td.isCollapsingColumn + td,
|
||||
th.isCollapsingColumn + th {
|
||||
border-left-color: var(--color-foreground-xdark);
|
||||
}
|
||||
|
||||
.nodeClass {
|
||||
@@ -808,7 +913,10 @@ watch(focusedMappableInput, (curr) => {
|
||||
|
||||
.dragButton {
|
||||
opacity: 0;
|
||||
margin-left: var(--spacing-2xs);
|
||||
|
||||
& > svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.dataKey {
|
||||
@@ -885,4 +993,18 @@ watch(focusedMappableInput, (curr) => {
|
||||
.executionLinkRowHeader {
|
||||
width: var(--spacing-m);
|
||||
}
|
||||
|
||||
.collapseColumnButton {
|
||||
span {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
visibility: hidden;
|
||||
margin-block: calc(-2 * var(--spacing-2xs));
|
||||
|
||||
.isCollapsingColumn &,
|
||||
th:hover & {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -73,6 +73,7 @@ exports[`InputPanel > should render 1`] = `
|
||||
data-v-2e5cd75c=""
|
||||
>
|
||||
<!---->
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-radio-buttons radioGroup displayModeSelect"
|
||||
data-test-id="ndv-run-data-display-mode"
|
||||
|
||||
@@ -88,6 +88,8 @@ describe('LogDetailsPanel', () => {
|
||||
isOpen: true,
|
||||
logEntry: createLogEntry({ node: aiNode, runIndex: 0, runData: aiNodeRunData }),
|
||||
panels: LOG_DETAILS_PANEL_STATE.BOTH,
|
||||
collapsingInputTableColumnName: null,
|
||||
collapsingOutputTableColumnName: null,
|
||||
});
|
||||
|
||||
const header = within(rendered.getByTestId('log-details-header'));
|
||||
@@ -109,6 +111,8 @@ describe('LogDetailsPanel', () => {
|
||||
runData: { ...aiNodeRunData, executionStatus: 'running' },
|
||||
}),
|
||||
panels: LOG_DETAILS_PANEL_STATE.BOTH,
|
||||
collapsingInputTableColumnName: null,
|
||||
collapsingOutputTableColumnName: null,
|
||||
});
|
||||
|
||||
const inputPanel = within(rendered.getByTestId('log-details-input'));
|
||||
@@ -123,6 +127,8 @@ describe('LogDetailsPanel', () => {
|
||||
isOpen: true,
|
||||
logEntry: createLogEntry({ node: aiNode, runIndex: 0, runData: aiNodeRunData }),
|
||||
panels: LOG_DETAILS_PANEL_STATE.BOTH,
|
||||
collapsingInputTableColumnName: null,
|
||||
collapsingOutputTableColumnName: null,
|
||||
});
|
||||
|
||||
await fireEvent.mouseDown(rendered.getByTestId('resize-handle'));
|
||||
@@ -138,6 +144,8 @@ describe('LogDetailsPanel', () => {
|
||||
isOpen: true,
|
||||
logEntry: createLogEntry({ node: aiNode, runIndex: 0, runData: aiNodeRunData }),
|
||||
panels: LOG_DETAILS_PANEL_STATE.BOTH,
|
||||
collapsingInputTableColumnName: null,
|
||||
collapsingOutputTableColumnName: null,
|
||||
});
|
||||
|
||||
await fireEvent.mouseDown(rendered.getByTestId('resize-handle'));
|
||||
@@ -164,6 +172,8 @@ describe('LogDetailsPanel', () => {
|
||||
execution: { resultData: { runData: { A: [runDataA], B: [runDataB] } } },
|
||||
}),
|
||||
panels: LOG_DETAILS_PANEL_STATE.BOTH,
|
||||
collapsingInputTableColumnName: null,
|
||||
collapsingOutputTableColumnName: null,
|
||||
});
|
||||
|
||||
expect(
|
||||
|
||||
@@ -20,18 +20,30 @@ import { LOG_DETAILS_PANEL_STATE } from '@/features/logs/logs.constants';
|
||||
|
||||
const MIN_IO_PANEL_WIDTH = 200;
|
||||
|
||||
const { isOpen, logEntry, window, latestInfo, panels } = defineProps<{
|
||||
const {
|
||||
isOpen,
|
||||
logEntry,
|
||||
window,
|
||||
latestInfo,
|
||||
panels,
|
||||
collapsingInputTableColumnName,
|
||||
collapsingOutputTableColumnName,
|
||||
} = defineProps<{
|
||||
isOpen: boolean;
|
||||
logEntry: LogEntry;
|
||||
window?: Window;
|
||||
latestInfo?: LatestNodeInfo;
|
||||
panels: LogDetailsPanelState;
|
||||
collapsingInputTableColumnName: string | null;
|
||||
collapsingOutputTableColumnName: string | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
clickHeader: [];
|
||||
toggleInputOpen: [] | [boolean];
|
||||
toggleOutputOpen: [] | [boolean];
|
||||
collapsingInputTableColumnChanged: [columnName: string | null];
|
||||
collapsingOutputTableColumnChanged: [columnName: string | null];
|
||||
}>();
|
||||
|
||||
defineSlots<{ actions: {} }>();
|
||||
@@ -149,6 +161,8 @@ function handleResizeEnd() {
|
||||
pane-type="input"
|
||||
:title="locale.baseText('logs.details.header.actions.input')"
|
||||
:log-entry="logEntry"
|
||||
:collapsing-table-column-name="collapsingInputTableColumnName"
|
||||
@collapsing-table-column-changed="emit('collapsingInputTableColumnChanged', $event)"
|
||||
/>
|
||||
</N8nResizeWrapper>
|
||||
<LogsViewRunData
|
||||
@@ -158,6 +172,8 @@ function handleResizeEnd() {
|
||||
:class="$style.outputPanel"
|
||||
:title="locale.baseText('logs.details.header.actions.output')"
|
||||
:log-entry="logEntry"
|
||||
:collapsing-table-column-name="collapsingOutputTableColumnName"
|
||||
@collapsing-table-column-changed="emit('collapsingOutputTableColumnChanged', $event)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, computed, useTemplateRef } from 'vue';
|
||||
import { nextTick, computed, useTemplateRef, ref } from 'vue';
|
||||
import { N8nResizeWrapper } from '@n8n/design-system';
|
||||
import { useChatState } from '@/features/logs/composables/useChatState';
|
||||
import LogsOverviewPanel from '@/features/logs/components/LogsOverviewPanel.vue';
|
||||
@@ -66,6 +66,9 @@ const { selected, select, selectNext, selectPrev } = useLogsSelection(
|
||||
toggleExpanded,
|
||||
);
|
||||
|
||||
const inputTableColumnCollapsing = ref<{ nodeName: string; columnName: string }>();
|
||||
const outputTableColumnCollapsing = ref<{ nodeName: string; columnName: string }>();
|
||||
|
||||
const isLogDetailsOpen = computed(() => isOpen.value && selected.value !== undefined);
|
||||
const isLogDetailsVisuallyOpen = computed(
|
||||
() => isLogDetailsOpen.value && !isCollapsingDetailsPanel.value,
|
||||
@@ -79,6 +82,16 @@ const logsPanelActionsProps = computed<InstanceType<typeof LogsPanelActions>['$p
|
||||
onToggleOpen,
|
||||
onToggleSyncSelection: logsStore.toggleLogSelectionSync,
|
||||
}));
|
||||
const inputCollapsingColumnName = computed(() =>
|
||||
inputTableColumnCollapsing.value?.nodeName === selected.value?.node.name
|
||||
? (inputTableColumnCollapsing.value?.columnName ?? null)
|
||||
: null,
|
||||
);
|
||||
const outputCollapsingColumnName = computed(() =>
|
||||
outputTableColumnCollapsing.value?.nodeName === selected.value?.node.name
|
||||
? (outputTableColumnCollapsing.value?.columnName ?? null)
|
||||
: null,
|
||||
);
|
||||
|
||||
const keyMap = computed<KeyMap>(() => ({
|
||||
j: selectNext,
|
||||
@@ -110,6 +123,16 @@ function handleOpenNdv(treeNode: LogEntry) {
|
||||
ndvStore.setOutputRunIndex(treeNode.runIndex);
|
||||
});
|
||||
}
|
||||
|
||||
function handleChangeInputTableColumnCollapsing(columnName: string | null) {
|
||||
inputTableColumnCollapsing.value =
|
||||
columnName && selected.value ? { nodeName: selected.value.node.name, columnName } : undefined;
|
||||
}
|
||||
|
||||
function handleChangeOutputTableColumnCollapsing(columnName: string | null) {
|
||||
outputTableColumnCollapsing.value =
|
||||
columnName && selected.value ? { nodeName: selected.value.node.name, columnName } : undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -202,9 +225,13 @@ function handleOpenNdv(treeNode: LogEntry) {
|
||||
:window="pipWindow"
|
||||
:latest-info="latestNodeNameById[selected.node.id]"
|
||||
:panels="logsStore.detailsState"
|
||||
:collapsing-input-table-column-name="inputCollapsingColumnName"
|
||||
:collapsing-output-table-column-name="outputCollapsingColumnName"
|
||||
@click-header="onToggleOpen(true)"
|
||||
@toggle-input-open="logsStore.toggleInputOpen"
|
||||
@toggle-output-open="logsStore.toggleOutputOpen"
|
||||
@collapsing-input-table-column-changed="handleChangeInputTableColumnCollapsing"
|
||||
@collapsing-output-table-column-changed="handleChangeOutputTableColumnCollapsing"
|
||||
>
|
||||
<template #actions>
|
||||
<LogsPanelActions v-if="isLogDetailsVisuallyOpen" v-bind="logsPanelActionsProps" />
|
||||
|
||||
@@ -11,10 +11,15 @@ import { I18nT } from 'vue-i18n';
|
||||
import { PiPWindowSymbol } from '@/constants';
|
||||
import { isSubNodeLog } from '../logs.utils';
|
||||
|
||||
const { title, logEntry, paneType } = defineProps<{
|
||||
const { title, logEntry, paneType, collapsingTableColumnName } = defineProps<{
|
||||
title: string;
|
||||
paneType: NodePanelType;
|
||||
logEntry: LogEntry;
|
||||
collapsingTableColumnName: string | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
collapsingTableColumnChanged: [columnName: string | null];
|
||||
}>();
|
||||
|
||||
const locale = useI18n();
|
||||
@@ -85,7 +90,9 @@ function handleChangeDisplayMode(value: IRunDataDisplayMode) {
|
||||
:disable-ai-content="!isSubNodeLog(logEntry)"
|
||||
:is-executing="isExecuting"
|
||||
table-header-bg-color="light"
|
||||
:collapsing-table-column-name="collapsingTableColumnName"
|
||||
@display-mode-change="handleChangeDisplayMode"
|
||||
@collapsing-table-column-changed="emit('collapsingTableColumnChanged', $event)"
|
||||
>
|
||||
<template #header>
|
||||
<N8nText :class="$style.title" :bold="true" color="text-light" size="small">
|
||||
|
||||
Reference in New Issue
Block a user