From 08450b20af0535cf643dc945867602635ce21e6a Mon Sep 17 00:00:00 2001 From: Dana <152518854+dana-gill@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:15:42 +0200 Subject: [PATCH] fix(editor): Change label for unexecuted nodes (#14260) --- CONTRIBUTING.md | 2 + .../src/components/VirtualSchema.test.ts | 14 +++--- .../src/components/VirtualSchema.vue | 41 +++++++++++++++-- .../__snapshots__/VirtualSchema.test.ts.snap | 46 +++++++++++-------- .../src/composables/useDataSchema.ts | 39 +++++++++------- .../src/plugins/i18n/locales/en.json | 1 + 6 files changed, 95 insertions(+), 48 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef062a12a0..9985ca3e1e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. diff --git a/packages/frontend/editor-ui/src/components/VirtualSchema.test.ts b/packages/frontend/editor-ui/src/components/VirtualSchema.test.ts index 3ac4bd83fc..be87f2267c 100644 --- a/packages/frontend/editor-ui/src/components/VirtualSchema.test.ts +++ b/packages/frontend/editor-ui/src/components/VirtualSchema.test.ts @@ -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'); diff --git a/packages/frontend/editor-ui/src/components/VirtualSchema.vue b/packages/frontend/editor-ui/src/components/VirtualSchema.vue index ea5c16584e..cdd727175e 100644 --- a/packages/frontend/editor-ui/src/components/VirtualSchema.vue +++ b/packages/frontend/editor-ui/src/components/VirtualSchema.vue @@ -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(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(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})` }" /> +
+ + + + + + +
diff --git a/packages/frontend/editor-ui/src/components/__snapshots__/VirtualSchema.test.ts.snap b/packages/frontend/editor-ui/src/components/__snapshots__/VirtualSchema.test.ts.snap index 638a1feffb..48e09fbebf 100644 --- a/packages/frontend/editor-ui/src/components/__snapshots__/VirtualSchema.test.ts.snap +++ b/packages/frontend/editor-ui/src/components/__snapshots__/VirtualSchema.test.ts.snap @@ -2005,32 +2005,40 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
- -
- - - - - - No fields - item(s) exist, but they're empty + + + + + + + + + to see schema - +
diff --git a/packages/frontend/editor-ui/src/composables/useDataSchema.ts b/packages/frontend/editor-ui/src/composables/useDataSchema.ts index 32420280a3..e15e4e847a 100644 --- a/packages/frontend/editor-ui/src/composables/useDataSchema.ts +++ b/packages/frontend/editor-ui/src/composables/useDataSchema.ts @@ -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; } diff --git a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json index 074dde5705..f7039d8464 100644 --- a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json @@ -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}",