diff --git a/packages/design-system/src/css/_tokens.scss b/packages/design-system/src/css/_tokens.scss index 7278517474b16..d40e4d8ae0c98 100644 --- a/packages/design-system/src/css/_tokens.scss +++ b/packages/design-system/src/css/_tokens.scss @@ -225,8 +225,8 @@ // Callout --color-callout-info-border: var(--color-foreground-base); --color-callout-info-background: var(--color-foreground-xlight); - --color-callout-info-font: var(--color-info); - --color-callout-info-icon: var(--color-info); + --color-callout-info-font: var(--color-text-base); + --color-callout-info-icon: var(--color-text-light); --color-callout-success-border: var(--color-success-light-2); --color-callout-success-background: var(--color-success-tint-2); --color-callout-success-font: var(--color-success); diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 2452726924145..703ebdf7779f5 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -1007,6 +1007,24 @@ export default defineComponent({ showIoSearchNoMatchContent(): boolean { return this.hasNodeRun && !this.inputData.length && this.search; }, + parentNodeOutputData(): INodeExecutionData[] { + const workflow = this.workflowsStore.getCurrentWorkflow(); + + const parentNode = workflow.getParentNodesByDepth(this.node.name)[0]; + let parentNodeData: INodeExecutionData[] = []; + + if (parentNode?.name) { + parentNodeData = this.nodeHelpers.getNodeInputData( + workflow.getNode(parentNode?.name), + this.runIndex, + this.outputIndex, + 'input', + this.connectionType, + ); + } + + return parentNodeData; + }, }, watch: { node(newNode: INodeUi, prevNode: INodeUi) { @@ -1104,19 +1122,18 @@ export default defineComponent({ }, shouldHintBeDisplayed(hint: NodeHint): boolean { const { location, whenToDisplay } = hint; + if (location) { - if (location === 'ndv') { - return true; + if (location === 'ndv' && !['input', 'output'].includes(this.paneType)) { + return false; } - if (location === 'inputPane' && this.paneType === 'input') { - return true; + if (location === 'inputPane' && this.paneType !== 'input') { + return false; } - if (location === 'outputPane' && this.paneType === 'output') { - return true; + if (location === 'outputPane' && this.paneType !== 'output') { + return false; } - - return false; } if (whenToDisplay === 'afterExecution' && !this.hasNodeRun) { @@ -1136,7 +1153,12 @@ export default defineComponent({ if (workflowNode) { const executionHints = this.executionHints; - const nodeHints = NodeHelpers.getNodeHints(workflow, workflowNode, this.nodeType); + const nodeHints = NodeHelpers.getNodeHints(workflow, workflowNode, this.nodeType, { + runExecutionData: this.workflowExecution?.data ?? null, + runIndex: this.runIndex, + connectionInputData: this.parentNodeOutputData, + }); + return executionHints.concat(nodeHints).filter(this.shouldHintBeDisplayed); } } diff --git a/packages/nodes-base/nodes/Postgres/v2/actions/router.ts b/packages/nodes-base/nodes/Postgres/v2/actions/router.ts index 47cc0a3f40c85..75c9b7e944591 100644 --- a/packages/nodes-base/nodes/Postgres/v2/actions/router.ts +++ b/packages/nodes-base/nodes/Postgres/v2/actions/router.ts @@ -1,5 +1,5 @@ import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; -import { NodeOperationError } from 'n8n-workflow'; +import { NodeExecutionOutput, NodeOperationError } from 'n8n-workflow'; import { configurePostgres } from '../transport'; import { configureQueryRunner } from '../helpers/utils'; @@ -17,7 +17,8 @@ export async function router(this: IExecuteFunctions): Promise 1 && !node.executeOnce) { + return new NodeExecutionOutput( + [returnData], + [ + { + message: `This node ran ${items.length} times, once for each input item. To run for the first item only, enable 'execute once' in the node settings`, + location: 'outputPane', + }, + ], + ); + } + return [returnData]; } diff --git a/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts b/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts index 5a48662cb903c..121a63f519e26 100644 --- a/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts +++ b/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts @@ -1,14 +1,14 @@ import get from 'lodash/get'; import unset from 'lodash/unset'; -import { - type IBinaryData, - NodeOperationError, - deepCopy, - type IDataObject, - type IExecuteFunctions, - type INodeExecutionData, - type INodeType, - type INodeTypeDescription, +import { NodeOperationError, deepCopy, NodeExecutionOutput } from 'n8n-workflow'; +import type { + IBinaryData, + IDataObject, + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeExecutionHint, } from 'n8n-workflow'; import { prepareFieldsArray } from '../utils/utils'; @@ -111,6 +111,7 @@ export class SplitOut implements INodeType { async execute(this: IExecuteFunctions): Promise { const returnData: INodeExecutionData[] = []; const items = this.getInputData(); + const notFoundedFields: { [key: string]: boolean[] } = {}; for (let i = 0; i < items.length; i++) { const fieldsToSplitOut = (this.getNodeParameter('fieldToSplitOut', i) as string) @@ -160,6 +161,14 @@ export class SplitOut implements INodeType { if (entityToSplit === undefined) { entityToSplit = []; + if (!notFoundedFields[fieldToSplitOut]) { + notFoundedFields[fieldToSplitOut] = []; + } + notFoundedFields[fieldToSplitOut].push(false); + } else { + if (notFoundedFields[fieldToSplitOut]) { + notFoundedFields[fieldToSplitOut].push(true); + } } if (typeof entityToSplit !== 'object' || entityToSplit === null) { @@ -254,6 +263,21 @@ export class SplitOut implements INodeType { } } + if (Object.keys(notFoundedFields).length) { + const hints: NodeExecutionHint[] = []; + + for (const [field, values] of Object.entries(notFoundedFields)) { + if (values.every((value) => !value)) { + hints.push({ + message: `The field '${field}' wasn't found in any input item`, + location: 'outputPane', + }); + } + } + + if (hints.length) return new NodeExecutionOutput([returnData], hints); + } + return [returnData]; } } diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 8149022d8834e..d930d023af6e7 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -43,6 +43,7 @@ import type { GenericValue, DisplayCondition, NodeHint, + INodeExecutionData, } from './Interfaces'; import { isFilterValue, @@ -1125,6 +1126,11 @@ export function getNodeHints( workflow: Workflow, node: INode, nodeTypeData: INodeTypeDescription, + nodeInputData?: { + runExecutionData: IRunExecutionData | null; + runIndex: number; + connectionInputData: INodeExecutionData[]; + }, ): NodeHint[] { const hints: NodeHint[] = []; @@ -1132,12 +1138,32 @@ export function getNodeHints( for (const hint of nodeTypeData.hints) { if (hint.displayCondition) { try { - const display = (workflow.expression.getSimpleParameterValue( - node, - hint.displayCondition, - 'internal', - {}, - ) || false) as boolean; + let display; + + if (nodeInputData === undefined) { + display = (workflow.expression.getSimpleParameterValue( + node, + hint.displayCondition, + 'internal', + {}, + ) || false) as boolean; + } else { + const { runExecutionData, runIndex, connectionInputData } = nodeInputData; + display = workflow.expression.getParameterValue( + hint.displayCondition, + runExecutionData ?? null, + runIndex, + 0, + node.name, + connectionInputData, + 'manual', + {}, + ); + } + + if (typeof display === 'string' && display.trim() === 'true') { + display = true; + } if (typeof display !== 'boolean') { console.warn(