fix(editor): Change label for unexecuted nodes (#14260)

This commit is contained in:
Dana
2025-04-01 16:15:42 +02:00
committed by GitHub
parent 837131fc96
commit 08450b20af
6 changed files with 95 additions and 48 deletions

View File

@@ -251,6 +251,8 @@ If that gets executed in one of the package folders it will only run the tests
of this package. If it gets executed in the n8n-root folder it will run all
tests of all packages.
If you made a change which requires an update on a `.test.ts.snap` file, pass `-u` to the command to run tests or press `u` in watch mode.
#### E2E tests
⚠️ You have to run `pnpm cypress:install` to install cypress before running the tests for the first time and to update cypress.

View File

@@ -191,16 +191,14 @@ describe('VirtualSchema.vue', () => {
});
});
it('renders schema for empty data', async () => {
it('renders schema for empty data for unexecuted nodes', async () => {
const { getAllByText, getAllByTestId } = renderComponent();
await waitFor(() =>
expect(getAllByText("No fields - item(s) exist, but they're empty").length).toBe(2),
);
await waitFor(() => expect(getAllByText('Execute previous nodes').length).toBe(2));
// Collapse second node
await userEvent.click(getAllByTestId('run-data-schema-header')[1]);
expect(getAllByText("No fields - item(s) exist, but they're empty").length).toBe(1);
expect(getAllByText('Execute previous nodes').length).toBe(1);
});
it('renders schema for empty data with binary', async () => {
@@ -295,7 +293,7 @@ describe('VirtualSchema.vue', () => {
await waitFor(() => {
const items = getAllByTestId('run-data-schema-item');
expect(items).toHaveLength(6);
expect(items).toHaveLength(5);
});
expect(container).toMatchSnapshot();
@@ -309,7 +307,7 @@ describe('VirtualSchema.vue', () => {
const { getAllByText } = renderComponent();
await waitFor(() =>
expect(getAllByText("No fields - item(s) exist, but they're empty").length).toBe(2),
expect(getAllByText("No fields - item(s) exist, but they're empty").length).toBe(1),
);
});
@@ -482,7 +480,7 @@ describe('VirtualSchema.vue', () => {
const { getAllByTestId } = renderComponent();
await waitFor(() => {
expect(getAllByTestId('run-data-schema-item')).toHaveLength(6);
expect(getAllByTestId('run-data-schema-item')).toHaveLength(5);
});
const items = getAllByTestId('run-data-schema-item');

View File

@@ -49,6 +49,7 @@ import { asyncComputed } from '@vueuse/core';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
import { pick } from 'lodash-es';
import { DateTime } from 'luxon';
import NodeExecuteButton from './NodeExecuteButton.vue';
type Props = {
nodes?: IConnectedNode[];
@@ -89,7 +90,7 @@ const { getSchemaForExecutionData, getSchemaForJsonSchema, getSchema, filterSche
useDataSchema();
const { closedNodes, flattenSchema, flattenMultipleSchemas, toggleLeaf, toggleNode } =
useFlattenSchema();
const { getNodeInputData } = useNodeHelpers();
const { getNodeInputData, getNodeTaskData } = useNodeHelpers();
const emit = defineEmits<{
'clear:search': [];
@@ -104,6 +105,8 @@ const toggleNodeAndScrollTop = (id: string) => {
const getNodeSchema = async (fullNode: INodeUi, connectedNode: IConnectedNode) => {
const pinData = workflowsStore.pinDataByNodeName(connectedNode.name);
const hasPinnedData = pinData ? pinData.length > 0 : false;
const isNodeExecuted = getNodeTaskData(fullNode, props.runIndex) !== null || hasPinnedData;
const connectedOutputIndexes = connectedNode.indicies.length > 0 ? connectedNode.indicies : [0];
const nodeData = connectedOutputIndexes.map((outputIndex) =>
getNodeInputData(fullNode, props.runIndex, outputIndex, props.paneType, props.connectionType),
@@ -128,6 +131,7 @@ const getNodeSchema = async (fullNode: INodeUi, connectedNode: IConnectedNode) =
itemsCount: data.length,
preview,
hasBinary,
isNodeExecuted,
};
};
@@ -242,10 +246,8 @@ const nodesSchemas = asyncComputed<SchemaNode[]>(async () => {
const nodeType = nodeTypesStore.getNodeType(fullNode.type, fullNode.typeVersion);
if (!nodeType) continue;
const { schema, connectedOutputIndexes, itemsCount, preview, hasBinary } = await getNodeSchema(
fullNode,
node,
);
const { schema, connectedOutputIndexes, itemsCount, preview, hasBinary, isNodeExecuted } =
await getNodeSchema(fullNode, node);
const filteredSchema = filterSchema(schema, search);
@@ -260,6 +262,7 @@ const nodesSchemas = asyncComputed<SchemaNode[]>(async () => {
schema: filteredSchema,
preview,
hasBinary,
isNodeExecuted,
});
}
@@ -431,6 +434,34 @@ const onDragEnd = (el: HTMLElement) => {
class="notice"
:style="{ marginLeft: `calc(var(--spacing-l) + var(--spacing-l) * ${item.level})` }"
/>
<div
v-else-if="item.type === 'empty'"
:style="{
paddingBottom: `var(--spacing-xs)`,
marginLeft: `var(--spacing-xl)`,
}"
>
<N8nText tag="div" size="small">
<i18n-t
v-if="item.key === 'executeSchema'"
tag="span"
keypath="dataMapping.schemaView.executeSchema"
>
<template #link>
<NodeExecuteButton
:node-name="item.nodeName"
text
telemetry-source="inputs"
hide-icon
:label="i18n.baseText('ndv.input.noOutputData.executePrevious')"
size="small"
:style="{ padding: 0 }"
/>
</template>
</i18n-t>
<i18n-t v-else tag="span" :keypath="`dataMapping.schemaView.${item.key}`" />
</N8nText>
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>

View File

@@ -2005,32 +2005,40 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
<div
class="schema-item draggable"
data-test-id="run-data-schema-item"
data-v-0f5e7239=""
data-v-d00cba9a=""
type="item"
>
<div
class="toggle-container"
data-v-0f5e7239=""
class="n8n-text size-small regular"
data-v-d00cba9a=""
>
<!--v-if-->
</div>
<!--v-if-->
<!--v-if-->
<span
class="content text"
data-test-id="run-data-schema-item-value"
data-v-0f5e7239=""
data-v-d00cba9a=""
>
<span>
<button
aria-live="polite"
class="button button primary small text el-tooltip__trigger el-tooltip__trigger"
style="padding: 0px;"
title="Runs the current node. Will also run previous nodes if they have not been run yet"
transparent-background="false"
>
<!--v-if-->
No fields - item(s) exist, but they're empty
</span>
<span>
Execute previous nodes
</span>
</button>
<!--teleport start-->
<!--teleport end-->
to see schema
</span>
</div>
</div>

View File

@@ -233,6 +233,7 @@ export type SchemaNode = {
itemsCount: number;
schema: Schema;
preview: boolean;
isNodeExecuted: boolean;
hasBinary: boolean;
};
@@ -279,7 +280,14 @@ export type RenderNotice = {
message: string;
};
export type Renders = RenderHeader | RenderItem | RenderIcon | RenderNotice;
export type RenderEmpty = {
id: string;
type: 'empty';
nodeName: string;
key: 'emptyData' | 'emptyDataWithBinary' | 'executeSchema';
};
export type Renders = RenderHeader | RenderItem | RenderIcon | RenderNotice | RenderEmpty;
const icons = {
object: 'cube',
@@ -296,13 +304,11 @@ const icons = {
const getIconBySchemaType = (type: Schema['type']): string => icons[type];
const emptyItem = (
message = useI18n().baseText('dataMapping.schemaView.emptyData'),
): RenderItem => ({
const emptyItem = (key: RenderEmpty['key'], nodeName?: string): RenderEmpty => ({
id: `empty-${window.crypto.randomUUID()}`,
icon: '',
value: message,
type: 'item',
type: 'empty',
key,
nodeName: nodeName || '',
});
const moreFieldsItem = (): RenderIcon => ({
@@ -361,10 +367,10 @@ export const useFlattenSchema = () => {
prefix?: string;
level?: number;
preview?: boolean;
}): RenderItem[] => {
}): Renders[] => {
// only show empty item for the first level
if (isDataEmpty(schema) && depth <= 0) {
return [emptyItem()];
return [emptyItem('emptyData')];
}
const expression = `{{ ${expressionPrefix ? expressionPrefix + schema.path : schema.path.slice(1)} }}`;
@@ -372,7 +378,7 @@ export const useFlattenSchema = () => {
const id = expression;
if (Array.isArray(schema.value)) {
const items: RenderItem[] = [];
const items: Renders[] = [];
if (schema.key) {
items.push({
@@ -459,13 +465,14 @@ export const useFlattenSchema = () => {
return acc;
}
if (isDataEmpty(item.schema) && !item.isNodeExecuted && !item.hasBinary) {
acc.push(emptyItem('executeSchema', item.node.name));
return acc;
}
if (isDataEmpty(item.schema)) {
const message = useI18n().baseText(
item.hasBinary
? 'dataMapping.schemaView.emptyDataWithBinary'
: 'dataMapping.schemaView.emptyData',
);
acc.push(emptyItem(message));
const key = item.hasBinary ? 'emptyDataWithBinary' : 'emptyData';
acc.push(emptyItem(key));
return acc;
}

View File

@@ -669,6 +669,7 @@
"dataMapping.tableView.tableColumnsExceeded.tooltip.link": "JSON view",
"dataMapping.schemaView.emptyData": "No fields - item(s) exist, but they're empty",
"dataMapping.schemaView.emptyDataWithBinary": "Only binary data exists. View it using the 'Binary' tab",
"dataMapping.schemaView.executeSchema": "{link} to see schema",
"dataMapping.schemaView.disabled": "This node is disabled and will just pass data through",
"dataMapping.schemaView.noMatches": "No results for '{search}'",
"dataMapping.schemaView.preview": "Usually outputs the following fields. Execute the node to see the actual ones. {link}",