mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat: Add assignment component with drag and drop to Set node (#8283)
Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
@@ -21,7 +21,7 @@ const versionDescription: INodeTypeDescription = {
|
||||
name: 'set',
|
||||
icon: 'fa:pen',
|
||||
group: ['input'],
|
||||
version: [3, 3.1, 3.2],
|
||||
version: [3, 3.1, 3.2, 3.3],
|
||||
description: 'Modify, add, or remove item fields',
|
||||
subtitle: '={{$parameter["mode"]}}',
|
||||
defaults: {
|
||||
@@ -44,7 +44,7 @@ const versionDescription: INodeTypeDescription = {
|
||||
action: 'Edit item fields one by one',
|
||||
},
|
||||
{
|
||||
name: 'JSON Output',
|
||||
name: 'JSON',
|
||||
value: 'raw',
|
||||
description: 'Customize item output with JSON',
|
||||
action: 'Customize item output with JSON',
|
||||
@@ -96,6 +96,11 @@ const versionDescription: INodeTypeDescription = {
|
||||
type: 'options',
|
||||
description: 'How to select the fields you want to include in your output items',
|
||||
default: 'all',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [3, 3.1, 3.2],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'All Input Fields',
|
||||
@@ -119,6 +124,49 @@ const versionDescription: INodeTypeDescription = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Include Other Input Fields',
|
||||
name: 'includeOtherFields',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
"Whether to pass to the output all the input fields (along with the fields set in 'Fields to Set')",
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'@version': [3, 3.1, 3.2],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Input Fields to Include',
|
||||
name: 'include',
|
||||
type: 'options',
|
||||
description: 'How to select the fields you want to include in your output items',
|
||||
default: 'all',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'@version': [3, 3.1, 3.2],
|
||||
'/includeOtherFields': [false],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'All',
|
||||
value: INCLUDE.ALL,
|
||||
description: 'Also include all unchanged fields from the input',
|
||||
},
|
||||
{
|
||||
name: 'Selected',
|
||||
value: INCLUDE.SELECTED,
|
||||
description: 'Also include the fields listed in the parameter “Fields to Include”',
|
||||
},
|
||||
{
|
||||
name: 'All Except',
|
||||
value: INCLUDE.EXCEPT,
|
||||
description: 'Exclude the fields listed in the parameter “Fields to Exclude”',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Fields to Include',
|
||||
name: 'includeFields',
|
||||
@@ -232,11 +280,16 @@ export class SetV2 implements INodeType {
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const include = this.getNodeParameter('include', i) as IncludeMods;
|
||||
const includeOtherFields = this.getNodeParameter('includeOtherFields', i, false) as boolean;
|
||||
const include = this.getNodeParameter('include', i, 'all') as IncludeMods;
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const node = this.getNode();
|
||||
|
||||
options.include = include;
|
||||
if (node.typeVersion >= 3.3) {
|
||||
options.include = includeOtherFields ? include : 'none';
|
||||
} else {
|
||||
options.include = include;
|
||||
}
|
||||
|
||||
const newItem = await setNode[mode].execute.call(
|
||||
this,
|
||||
|
||||
@@ -17,6 +17,12 @@ export type SetField = {
|
||||
objectValue?: string | IDataObject;
|
||||
};
|
||||
|
||||
export type AssignmentSetField = {
|
||||
name: string;
|
||||
value: unknown;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export const INCLUDE = {
|
||||
ALL: 'all',
|
||||
NONE: 'none',
|
||||
|
||||
@@ -4,21 +4,22 @@ import type {
|
||||
IExecuteFunctions,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
ValidationResult,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
deepCopy,
|
||||
ApplicationError,
|
||||
NodeOperationError,
|
||||
deepCopy,
|
||||
jsonParse,
|
||||
validateFieldType,
|
||||
ApplicationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import set from 'lodash/set';
|
||||
import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
import unset from 'lodash/unset';
|
||||
|
||||
import { getResolvables, sanitazeDataPathKey } from '../../../../utils/utilities';
|
||||
import type { SetNodeOptions, SetField } from './interfaces';
|
||||
import type { SetNodeOptions } from './interfaces';
|
||||
import { INCLUDE } from './interfaces';
|
||||
|
||||
const configureFieldHelper = (dotNotation?: boolean) => {
|
||||
@@ -163,46 +164,43 @@ export const parseJsonParameter = (
|
||||
};
|
||||
|
||||
export const validateEntry = (
|
||||
entry: SetField,
|
||||
name: string,
|
||||
type: FieldType,
|
||||
value: unknown,
|
||||
node: INode,
|
||||
itemIndex: number,
|
||||
ignoreErrors = false,
|
||||
nodeVersion?: number,
|
||||
) => {
|
||||
let entryValue = entry[entry.type];
|
||||
const name = entry.name;
|
||||
|
||||
if (nodeVersion && nodeVersion >= 3.2 && (entryValue === undefined || entryValue === null)) {
|
||||
if (nodeVersion && nodeVersion >= 3.2 && (value === undefined || value === null)) {
|
||||
return { name, value: null };
|
||||
}
|
||||
|
||||
const entryType = entry.type.replace('Value', '') as FieldType;
|
||||
|
||||
const description = `To fix the error try to change the type for the field "${name}" or activate the option “Ignore Type Conversion Errors” to apply a less strict type validation`;
|
||||
|
||||
if (entryType === 'string') {
|
||||
if (nodeVersion && nodeVersion > 3 && (entryValue === undefined || entryValue === null)) {
|
||||
if (type === 'string') {
|
||||
if (nodeVersion && nodeVersion > 3 && (value === undefined || value === null)) {
|
||||
if (ignoreErrors) {
|
||||
return { name, value: null };
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`'${name}' expects a ${entryType} but we got '${String(entryValue)}' [item ${itemIndex}]`,
|
||||
`'${name}' expects a ${type} but we got '${String(value)}' [item ${itemIndex}]`,
|
||||
{ description },
|
||||
);
|
||||
}
|
||||
} else if (typeof entryValue === 'object') {
|
||||
entryValue = JSON.stringify(entryValue);
|
||||
} else if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
} else {
|
||||
entryValue = String(entryValue);
|
||||
value = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
const validationResult = validateFieldType(name, entryValue, entryType);
|
||||
const validationResult = validateFieldType(name, value, type);
|
||||
|
||||
if (!validationResult.valid) {
|
||||
if (ignoreErrors) {
|
||||
validationResult.newValue = entry[entry.type];
|
||||
validationResult.newValue = value as ValidationResult['newValue'];
|
||||
} else {
|
||||
const message = `${validationResult.errorMessage} [item ${itemIndex}]`;
|
||||
throw new NodeOperationError(node, message, {
|
||||
@@ -212,9 +210,10 @@ export const validateEntry = (
|
||||
}
|
||||
}
|
||||
|
||||
const value = validationResult.newValue === undefined ? null : validationResult.newValue;
|
||||
|
||||
return { name, value };
|
||||
return {
|
||||
name,
|
||||
value: validationResult.newValue === undefined ? null : validationResult.newValue,
|
||||
};
|
||||
};
|
||||
|
||||
export function resolveRawData(this: IExecuteFunctions, rawData: string, i: number) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type {
|
||||
AssignmentCollectionValue,
|
||||
FieldType,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INode,
|
||||
@@ -23,6 +25,11 @@ const properties: INodeProperties[] = [
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
description: 'Edit existing fields or add new ones to modify the output data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [3, 3.1, 3.2],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
sortable: true,
|
||||
@@ -156,6 +163,17 @@ const properties: INodeProperties[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Fields to Set',
|
||||
name: 'assignments',
|
||||
type: 'assignmentCollection',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'@version': [3, 3.1, 3.2],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
@@ -175,35 +193,60 @@ export async function execute(
|
||||
node: INode,
|
||||
) {
|
||||
try {
|
||||
const fields = this.getNodeParameter('fields.values', i, []) as SetField[];
|
||||
if (node.typeVersion < 3.3) {
|
||||
const fields = this.getNodeParameter('fields.values', i, []) as SetField[];
|
||||
|
||||
const newData: IDataObject = {};
|
||||
const newData: IDataObject = {};
|
||||
|
||||
for (const entry of fields) {
|
||||
if (
|
||||
entry.type === 'objectValue' &&
|
||||
rawFieldsData[entry.name] !== undefined &&
|
||||
entry.objectValue !== undefined &&
|
||||
entry.objectValue !== null
|
||||
) {
|
||||
entry.objectValue = parseJsonParameter(
|
||||
resolveRawData.call(this, rawFieldsData[entry.name] as string, i),
|
||||
for (const entry of fields) {
|
||||
if (
|
||||
entry.type === 'objectValue' &&
|
||||
rawFieldsData[entry.name] !== undefined &&
|
||||
entry.objectValue !== undefined &&
|
||||
entry.objectValue !== null
|
||||
) {
|
||||
entry.objectValue = parseJsonParameter(
|
||||
resolveRawData.call(this, rawFieldsData[entry.name] as string, i),
|
||||
node,
|
||||
i,
|
||||
entry.name,
|
||||
);
|
||||
}
|
||||
|
||||
const { name, value } = validateEntry(
|
||||
entry.name,
|
||||
entry.type.replace('Value', '') as FieldType,
|
||||
entry[entry.type],
|
||||
node,
|
||||
i,
|
||||
entry.name,
|
||||
options.ignoreConversionErrors,
|
||||
node.typeVersion,
|
||||
);
|
||||
newData[name] = value;
|
||||
}
|
||||
|
||||
const { name, value } = validateEntry(
|
||||
entry,
|
||||
node,
|
||||
i,
|
||||
options.ignoreConversionErrors,
|
||||
node.typeVersion,
|
||||
);
|
||||
newData[name] = value;
|
||||
return composeReturnItem.call(this, i, item, newData, options);
|
||||
}
|
||||
|
||||
const assignmentCollection = this.getNodeParameter(
|
||||
'assignments',
|
||||
i,
|
||||
) as AssignmentCollectionValue;
|
||||
const newData = Object.fromEntries(
|
||||
(assignmentCollection?.assignments ?? []).map((assignment) => {
|
||||
const { name, value } = validateEntry(
|
||||
assignment.name,
|
||||
assignment.type as FieldType,
|
||||
assignment.value,
|
||||
node,
|
||||
i,
|
||||
options.ignoreConversionErrors,
|
||||
node.typeVersion,
|
||||
);
|
||||
|
||||
return [name, value];
|
||||
}),
|
||||
);
|
||||
return composeReturnItem.call(this, i, item, newData, options);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { SetNodeOptions } from './helpers/interfaces';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'JSON Output',
|
||||
displayName: 'JSON',
|
||||
name: 'jsonOutput',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
|
||||
Reference in New Issue
Block a user