feat(core): Node hints improvements (no-changelog) (#9387)

Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
Michael Kret 2024-05-22 13:35:29 +03:00 committed by GitHub
parent 3761537880
commit 870412f093
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 113 additions and 28 deletions

View file

@ -225,8 +225,8 @@
// Callout // Callout
--color-callout-info-border: var(--color-foreground-base); --color-callout-info-border: var(--color-foreground-base);
--color-callout-info-background: var(--color-foreground-xlight); --color-callout-info-background: var(--color-foreground-xlight);
--color-callout-info-font: var(--color-info); --color-callout-info-font: var(--color-text-base);
--color-callout-info-icon: var(--color-info); --color-callout-info-icon: var(--color-text-light);
--color-callout-success-border: var(--color-success-light-2); --color-callout-success-border: var(--color-success-light-2);
--color-callout-success-background: var(--color-success-tint-2); --color-callout-success-background: var(--color-success-tint-2);
--color-callout-success-font: var(--color-success); --color-callout-success-font: var(--color-success);

View file

@ -1021,6 +1021,24 @@ export default defineComponent({
showIoSearchNoMatchContent(): boolean { showIoSearchNoMatchContent(): boolean {
return this.hasNodeRun && !this.inputData.length && !!this.search; 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: { watch: {
node(newNode: INodeUi, prevNode: INodeUi) { node(newNode: INodeUi, prevNode: INodeUi) {
@ -1118,19 +1136,18 @@ export default defineComponent({
}, },
shouldHintBeDisplayed(hint: NodeHint): boolean { shouldHintBeDisplayed(hint: NodeHint): boolean {
const { location, whenToDisplay } = hint; const { location, whenToDisplay } = hint;
if (location) { if (location) {
if (location === 'ndv') { if (location === 'ndv' && !['input', 'output'].includes(this.paneType)) {
return true; return false;
} }
if (location === 'inputPane' && this.paneType === 'input') { if (location === 'inputPane' && this.paneType !== 'input') {
return true; return false;
} }
if (location === 'outputPane' && this.paneType === 'output') { if (location === 'outputPane' && this.paneType !== 'output') {
return true; return false;
} }
return false;
} }
if (whenToDisplay === 'afterExecution' && !this.hasNodeRun) { if (whenToDisplay === 'afterExecution' && !this.hasNodeRun) {
@ -1150,7 +1167,12 @@ export default defineComponent({
if (workflowNode) { if (workflowNode) {
const executionHints = this.executionHints; 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); return executionHints.concat(nodeHints).filter(this.shouldHintBeDisplayed);
} }
} }

View file

@ -1,5 +1,5 @@
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeExecutionOutput, NodeOperationError } from 'n8n-workflow';
import { configurePostgres } from '../transport'; import { configurePostgres } from '../transport';
import { configureQueryRunner } from '../helpers/utils'; import { configureQueryRunner } from '../helpers/utils';
@ -17,7 +17,8 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
const credentials = (await this.getCredentials('postgres')) as PostgresNodeCredentials; const credentials = (await this.getCredentials('postgres')) as PostgresNodeCredentials;
const options = this.getNodeParameter('options', 0, {}) as PostgresNodeOptions; const options = this.getNodeParameter('options', 0, {}) as PostgresNodeOptions;
options.nodeVersion = this.getNode().typeVersion; const node = this.getNode();
options.nodeVersion = node.typeVersion;
options.operation = operation; options.operation = operation;
const { db, pgp, sshClient } = await configurePostgres(credentials, options); const { db, pgp, sshClient } = await configurePostgres(credentials, options);
@ -62,5 +63,17 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
if (!db.$pool.ending) await db.$pool.end(); if (!db.$pool.ending) await db.$pool.end();
} }
if (operation === 'select' && items.length > 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]; return [returnData];
} }

View file

@ -1,14 +1,14 @@
import get from 'lodash/get'; import get from 'lodash/get';
import unset from 'lodash/unset'; import unset from 'lodash/unset';
import { import { NodeOperationError, deepCopy, NodeExecutionOutput } from 'n8n-workflow';
type IBinaryData, import type {
NodeOperationError, IBinaryData,
deepCopy, IDataObject,
type IDataObject, IExecuteFunctions,
type IExecuteFunctions, INodeExecutionData,
type INodeExecutionData, INodeType,
type INodeType, INodeTypeDescription,
type INodeTypeDescription, NodeExecutionHint,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { prepareFieldsArray } from '../utils/utils'; import { prepareFieldsArray } from '../utils/utils';
@ -111,6 +111,7 @@ export class SplitOut implements INodeType {
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const returnData: INodeExecutionData[] = []; const returnData: INodeExecutionData[] = [];
const items = this.getInputData(); const items = this.getInputData();
const notFoundedFields: { [key: string]: boolean[] } = {};
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const fieldsToSplitOut = (this.getNodeParameter('fieldToSplitOut', i) as string) const fieldsToSplitOut = (this.getNodeParameter('fieldToSplitOut', i) as string)
@ -160,6 +161,14 @@ export class SplitOut implements INodeType {
if (entityToSplit === undefined) { if (entityToSplit === undefined) {
entityToSplit = []; entityToSplit = [];
if (!notFoundedFields[fieldToSplitOut]) {
notFoundedFields[fieldToSplitOut] = [];
}
notFoundedFields[fieldToSplitOut].push(false);
} else {
if (notFoundedFields[fieldToSplitOut]) {
notFoundedFields[fieldToSplitOut].push(true);
}
} }
if (typeof entityToSplit !== 'object' || entityToSplit === null) { 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]; return [returnData];
} }
} }

View file

@ -43,6 +43,7 @@ import type {
GenericValue, GenericValue,
DisplayCondition, DisplayCondition,
NodeHint, NodeHint,
INodeExecutionData,
} from './Interfaces'; } from './Interfaces';
import { import {
isFilterValue, isFilterValue,
@ -1125,6 +1126,11 @@ export function getNodeHints(
workflow: Workflow, workflow: Workflow,
node: INode, node: INode,
nodeTypeData: INodeTypeDescription, nodeTypeData: INodeTypeDescription,
nodeInputData?: {
runExecutionData: IRunExecutionData | null;
runIndex: number;
connectionInputData: INodeExecutionData[];
},
): NodeHint[] { ): NodeHint[] {
const hints: NodeHint[] = []; const hints: NodeHint[] = [];
@ -1132,12 +1138,32 @@ export function getNodeHints(
for (const hint of nodeTypeData.hints) { for (const hint of nodeTypeData.hints) {
if (hint.displayCondition) { if (hint.displayCondition) {
try { try {
const display = (workflow.expression.getSimpleParameterValue( let display;
node,
hint.displayCondition, if (nodeInputData === undefined) {
'internal', display = (workflow.expression.getSimpleParameterValue(
{}, node,
) || false) as boolean; 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') { if (typeof display !== 'boolean') {
console.warn( console.warn(