feat(editor): Filter component + implement in If node (#7490)
New Filter component + implementation in If node (v2) <img width="3283" alt="image" src="https://github.com/n8n-io/n8n/assets/8850410/35c379ef-4b62-4d06-82e7-673d4edcd652"> --------- Co-authored-by: Giulio Andreini <andreini@netseven.it> Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
@@ -1,489 +1,25 @@
|
||||
import moment from 'moment';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeParameterValue,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
|
||||
import { VersionedNodeType } from 'n8n-workflow';
|
||||
|
||||
export class If implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'IF',
|
||||
name: 'if',
|
||||
icon: 'fa:map-signs',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'Route items to different branches (true/false)',
|
||||
defaults: {
|
||||
name: 'IF',
|
||||
color: '#408000',
|
||||
},
|
||||
inputs: ['main'],
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
|
||||
outputs: ['main', 'main'],
|
||||
outputNames: ['true', 'false'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Conditions',
|
||||
name: 'conditions',
|
||||
placeholder: 'Add Condition',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
sortable: true,
|
||||
},
|
||||
description: 'The type of values to compare',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'boolean',
|
||||
displayName: 'Boolean',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-operation-without-no-data-expression
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'notEqual',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'dateTime',
|
||||
displayName: 'Date & Time',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-operation-without-no-data-expression
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Occurred After',
|
||||
value: 'after',
|
||||
},
|
||||
{
|
||||
name: 'Occurred Before',
|
||||
value: 'before',
|
||||
},
|
||||
],
|
||||
default: 'after',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
displayName: 'Number',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Smaller',
|
||||
value: 'smaller',
|
||||
},
|
||||
{
|
||||
name: 'Smaller or Equal',
|
||||
value: 'smallerEqual',
|
||||
},
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'notEqual',
|
||||
},
|
||||
{
|
||||
name: 'Larger',
|
||||
value: 'larger',
|
||||
},
|
||||
{
|
||||
name: 'Larger or Equal',
|
||||
value: 'largerEqual',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'isEmpty',
|
||||
},
|
||||
{
|
||||
name: 'Is Not Empty',
|
||||
value: 'isNotEmpty',
|
||||
},
|
||||
],
|
||||
default: 'smaller',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operation: ['isEmpty', 'isNotEmpty'],
|
||||
},
|
||||
},
|
||||
default: 0,
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'string',
|
||||
displayName: 'String',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Contains',
|
||||
value: 'contains',
|
||||
},
|
||||
{
|
||||
name: 'Not Contains',
|
||||
value: 'notContains',
|
||||
},
|
||||
{
|
||||
name: 'Ends With',
|
||||
value: 'endsWith',
|
||||
},
|
||||
{
|
||||
name: 'Not Ends With',
|
||||
value: 'notEndsWith',
|
||||
},
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'notEqual',
|
||||
},
|
||||
{
|
||||
name: 'Regex Match',
|
||||
value: 'regex',
|
||||
},
|
||||
{
|
||||
name: 'Regex Not Match',
|
||||
value: 'notRegex',
|
||||
},
|
||||
{
|
||||
name: 'Starts With',
|
||||
value: 'startsWith',
|
||||
},
|
||||
{
|
||||
name: 'Not Starts With',
|
||||
value: 'notStartsWith',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'isEmpty',
|
||||
},
|
||||
{
|
||||
name: 'Is Not Empty',
|
||||
value: 'isNotEmpty',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operation: ['isEmpty', 'isNotEmpty', 'regex', 'notRegex'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
{
|
||||
displayName: 'Regex',
|
||||
name: 'value2',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['regex', 'notRegex'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
placeholder: '/text/i',
|
||||
description: 'The regex which has to match',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Combine',
|
||||
name: 'combineOperation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ALL',
|
||||
description: 'Only if all conditions are met it goes into "true" branch',
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
name: 'ANY',
|
||||
description: 'If any of the conditions is met it goes into "true" branch',
|
||||
value: 'any',
|
||||
},
|
||||
],
|
||||
default: 'all',
|
||||
description:
|
||||
'If multiple rules got set this settings decides if it is true as soon as ANY condition matches or only if ALL get meet',
|
||||
},
|
||||
],
|
||||
};
|
||||
import { IfV1 } from './V1/IfV1.node';
|
||||
import { IfV2 } from './V2/IfV2.node';
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const returnDataTrue: INodeExecutionData[] = [];
|
||||
const returnDataFalse: INodeExecutionData[] = [];
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
let item: INodeExecutionData;
|
||||
let combineOperation: string;
|
||||
|
||||
const isDateObject = (value: NodeParameterValue) =>
|
||||
Object.prototype.toString.call(value) === '[object Date]';
|
||||
const isDateInvalid = (value: NodeParameterValue) => value?.toString() === 'Invalid Date';
|
||||
|
||||
// The compare operations
|
||||
const compareOperationFunctions: {
|
||||
[key: string]: (value1: NodeParameterValue, value2: NodeParameterValue) => boolean;
|
||||
} = {
|
||||
after: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) > (value2 || 0),
|
||||
before: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) < (value2 || 0),
|
||||
contains: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || '').toString().includes((value2 || '').toString()),
|
||||
notContains: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
!(value1 || '').toString().includes((value2 || '').toString()),
|
||||
endsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 as string).endsWith(value2 as string),
|
||||
notEndsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
!(value1 as string).endsWith(value2 as string),
|
||||
equal: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 === value2,
|
||||
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
||||
larger: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) > (value2 || 0),
|
||||
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) >= (value2 || 0),
|
||||
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) < (value2 || 0),
|
||||
smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) <= (value2 || 0),
|
||||
startsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 as string).startsWith(value2 as string),
|
||||
notStartsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
!(value1 as string).startsWith(value2 as string),
|
||||
isEmpty: (value1: NodeParameterValue) =>
|
||||
[undefined, null, '', NaN].includes(value1 as string) ||
|
||||
(typeof value1 === 'object' && value1 !== null && !isDateObject(value1)
|
||||
? Object.entries(value1 as string).length === 0
|
||||
: false) ||
|
||||
(isDateObject(value1) && isDateInvalid(value1)),
|
||||
isNotEmpty: (value1: NodeParameterValue) =>
|
||||
!(
|
||||
[undefined, null, '', NaN].includes(value1 as string) ||
|
||||
(typeof value1 === 'object' && value1 !== null && !isDateObject(value1)
|
||||
? Object.entries(value1 as string).length === 0
|
||||
: false) ||
|
||||
(isDateObject(value1) && isDateInvalid(value1))
|
||||
),
|
||||
regex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
||||
|
||||
let regex: RegExp;
|
||||
if (!regexMatch) {
|
||||
regex = new RegExp((value2 || '').toString());
|
||||
} else if (regexMatch.length === 1) {
|
||||
regex = new RegExp(regexMatch[1]);
|
||||
} else {
|
||||
regex = new RegExp(regexMatch[1], regexMatch[2]);
|
||||
}
|
||||
|
||||
return !!(value1 || '').toString().match(regex);
|
||||
},
|
||||
notRegex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
||||
|
||||
let regex: RegExp;
|
||||
if (!regexMatch) {
|
||||
regex = new RegExp((value2 || '').toString());
|
||||
} else if (regexMatch.length === 1) {
|
||||
regex = new RegExp(regexMatch[1]);
|
||||
} else {
|
||||
regex = new RegExp(regexMatch[1], regexMatch[2]);
|
||||
}
|
||||
|
||||
return !(value1 || '').toString().match(regex);
|
||||
},
|
||||
export class If extends VersionedNodeType {
|
||||
constructor() {
|
||||
const baseDescription: INodeTypeBaseDescription = {
|
||||
displayName: 'If',
|
||||
name: 'if',
|
||||
icon: 'fa:map-signs',
|
||||
group: ['transform'],
|
||||
description: 'Route items to different branches (true/false)',
|
||||
defaultVersion: 2,
|
||||
};
|
||||
|
||||
// Converts the input data of a dateTime into a number for easy compare
|
||||
const convertDateTime = (value: NodeParameterValue): number => {
|
||||
let returnValue: number | undefined = undefined;
|
||||
if (typeof value === 'string') {
|
||||
returnValue = new Date(value).getTime();
|
||||
} else if (typeof value === 'number') {
|
||||
returnValue = value;
|
||||
}
|
||||
if (moment.isMoment(value)) {
|
||||
returnValue = value.unix();
|
||||
}
|
||||
if ((value as unknown as object) instanceof Date) {
|
||||
returnValue = (value as unknown as Date).getTime();
|
||||
}
|
||||
|
||||
if (returnValue === undefined || isNaN(returnValue)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`The value "${value}" is not a valid DateTime.`,
|
||||
);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||
1: new IfV1(baseDescription),
|
||||
2: new IfV2(baseDescription),
|
||||
};
|
||||
|
||||
// The different dataTypes to check the values in
|
||||
const dataTypes = ['boolean', 'dateTime', 'number', 'string'];
|
||||
|
||||
// Iterate over all items to check which ones should be output as via output "true" and
|
||||
// which ones via output "false"
|
||||
let dataType: string;
|
||||
let compareOperationResult: boolean;
|
||||
let value1: NodeParameterValue, value2: NodeParameterValue;
|
||||
itemLoop: for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
item = items[itemIndex];
|
||||
|
||||
let compareData: INodeParameters;
|
||||
|
||||
combineOperation = this.getNodeParameter('combineOperation', itemIndex) as string;
|
||||
|
||||
// Check all the values of the different dataTypes
|
||||
for (dataType of dataTypes) {
|
||||
// Check all the values of the current dataType
|
||||
for (compareData of this.getNodeParameter(
|
||||
`conditions.${dataType}`,
|
||||
itemIndex,
|
||||
[],
|
||||
) as INodeParameters[]) {
|
||||
// Check if the values passes
|
||||
|
||||
value1 = compareData.value1 as NodeParameterValue;
|
||||
value2 = compareData.value2 as NodeParameterValue;
|
||||
|
||||
if (dataType === 'dateTime') {
|
||||
value1 = convertDateTime(value1);
|
||||
value2 = convertDateTime(value2);
|
||||
}
|
||||
|
||||
compareOperationResult = compareOperationFunctions[compareData.operation as string](
|
||||
value1,
|
||||
value2,
|
||||
);
|
||||
|
||||
if (compareOperationResult && combineOperation === 'any') {
|
||||
// If it passes and the operation is "any" we do not have to check any
|
||||
// other ones as it should pass anyway. So go on with the next item.
|
||||
returnDataTrue.push(item);
|
||||
continue itemLoop;
|
||||
} else if (!compareOperationResult && combineOperation === 'all') {
|
||||
// If it fails and the operation is "all" we do not have to check any
|
||||
// other ones as it should be not pass anyway. So go on with the next item.
|
||||
returnDataFalse.push(item);
|
||||
continue itemLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.pairedItem === undefined) {
|
||||
item.pairedItem = [{ item: itemIndex }];
|
||||
}
|
||||
|
||||
if (combineOperation === 'all') {
|
||||
// If the operation is "all" it means the item did match all conditions
|
||||
// so it passes.
|
||||
returnDataTrue.push(item);
|
||||
} else {
|
||||
// If the operation is "any" it means the the item did not match any condition.
|
||||
returnDataFalse.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return [returnDataTrue, returnDataFalse];
|
||||
super(nodeVersions, baseDescription);
|
||||
}
|
||||
}
|
||||
|
||||
486
packages/nodes-base/nodes/If/V1/IfV1.node.ts
Normal file
486
packages/nodes-base/nodes/If/V1/IfV1.node.ts
Normal file
@@ -0,0 +1,486 @@
|
||||
import moment from 'moment';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
NodeParameterValue,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
export class IfV1 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
version: 1,
|
||||
defaults: {
|
||||
name: 'If',
|
||||
color: '#408000',
|
||||
},
|
||||
inputs: ['main'],
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
|
||||
outputs: ['main', 'main'],
|
||||
outputNames: ['true', 'false'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Conditions',
|
||||
name: 'conditions',
|
||||
placeholder: 'Add Condition',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
sortable: true,
|
||||
},
|
||||
description: 'The type of values to compare',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'boolean',
|
||||
displayName: 'Boolean',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-operation-without-no-data-expression
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'notEqual',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'dateTime',
|
||||
displayName: 'Date & Time',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-operation-without-no-data-expression
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Occurred After',
|
||||
value: 'after',
|
||||
},
|
||||
{
|
||||
name: 'Occurred Before',
|
||||
value: 'before',
|
||||
},
|
||||
],
|
||||
default: 'after',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
displayName: 'Number',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Smaller',
|
||||
value: 'smaller',
|
||||
},
|
||||
{
|
||||
name: 'Smaller or Equal',
|
||||
value: 'smallerEqual',
|
||||
},
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'notEqual',
|
||||
},
|
||||
{
|
||||
name: 'Larger',
|
||||
value: 'larger',
|
||||
},
|
||||
{
|
||||
name: 'Larger or Equal',
|
||||
value: 'largerEqual',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'isEmpty',
|
||||
},
|
||||
{
|
||||
name: 'Is Not Empty',
|
||||
value: 'isNotEmpty',
|
||||
},
|
||||
],
|
||||
default: 'smaller',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operation: ['isEmpty', 'isNotEmpty'],
|
||||
},
|
||||
},
|
||||
default: 0,
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'string',
|
||||
displayName: 'String',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value 1',
|
||||
name: 'value1',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The value to compare with the second one',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Contains',
|
||||
value: 'contains',
|
||||
},
|
||||
{
|
||||
name: 'Not Contains',
|
||||
value: 'notContains',
|
||||
},
|
||||
{
|
||||
name: 'Ends With',
|
||||
value: 'endsWith',
|
||||
},
|
||||
{
|
||||
name: 'Not Ends With',
|
||||
value: 'notEndsWith',
|
||||
},
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'notEqual',
|
||||
},
|
||||
{
|
||||
name: 'Regex Match',
|
||||
value: 'regex',
|
||||
},
|
||||
{
|
||||
name: 'Regex Not Match',
|
||||
value: 'notRegex',
|
||||
},
|
||||
{
|
||||
name: 'Starts With',
|
||||
value: 'startsWith',
|
||||
},
|
||||
{
|
||||
name: 'Not Starts With',
|
||||
value: 'notStartsWith',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'isEmpty',
|
||||
},
|
||||
{
|
||||
name: 'Is Not Empty',
|
||||
value: 'isNotEmpty',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
description: 'Operation to decide where the the data should be mapped to',
|
||||
},
|
||||
{
|
||||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operation: ['isEmpty', 'isNotEmpty', 'regex', 'notRegex'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The value to compare with the first one',
|
||||
},
|
||||
{
|
||||
displayName: 'Regex',
|
||||
name: 'value2',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['regex', 'notRegex'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
placeholder: '/text/i',
|
||||
description: 'The regex which has to match',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Combine',
|
||||
name: 'combineOperation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ALL',
|
||||
description: 'Only if all conditions are met it goes into "true" branch',
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
name: 'ANY',
|
||||
description: 'If any of the conditions is met it goes into "true" branch',
|
||||
value: 'any',
|
||||
},
|
||||
],
|
||||
default: 'all',
|
||||
description:
|
||||
'If multiple rules got set this settings decides if it is true as soon as ANY condition matches or only if ALL get meet',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const returnDataTrue: INodeExecutionData[] = [];
|
||||
const returnDataFalse: INodeExecutionData[] = [];
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
let item: INodeExecutionData;
|
||||
let combineOperation: string;
|
||||
|
||||
const isDateObject = (value: NodeParameterValue) =>
|
||||
Object.prototype.toString.call(value) === '[object Date]';
|
||||
const isDateInvalid = (value: NodeParameterValue) => value?.toString() === 'Invalid Date';
|
||||
|
||||
// The compare operations
|
||||
const compareOperationFunctions: {
|
||||
[key: string]: (value1: NodeParameterValue, value2: NodeParameterValue) => boolean;
|
||||
} = {
|
||||
after: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) > (value2 || 0),
|
||||
before: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) < (value2 || 0),
|
||||
contains: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || '').toString().includes((value2 || '').toString()),
|
||||
notContains: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
!(value1 || '').toString().includes((value2 || '').toString()),
|
||||
endsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 as string).endsWith(value2 as string),
|
||||
notEndsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
!(value1 as string).endsWith(value2 as string),
|
||||
equal: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 === value2,
|
||||
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
||||
larger: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) > (value2 || 0),
|
||||
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) >= (value2 || 0),
|
||||
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) < (value2 || 0),
|
||||
smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 || 0) <= (value2 || 0),
|
||||
startsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
(value1 as string).startsWith(value2 as string),
|
||||
notStartsWith: (value1: NodeParameterValue, value2: NodeParameterValue) =>
|
||||
!(value1 as string).startsWith(value2 as string),
|
||||
isEmpty: (value1: NodeParameterValue) =>
|
||||
[undefined, null, '', NaN].includes(value1 as string) ||
|
||||
(typeof value1 === 'object' && value1 !== null && !isDateObject(value1)
|
||||
? Object.entries(value1 as string).length === 0
|
||||
: false) ||
|
||||
(isDateObject(value1) && isDateInvalid(value1)),
|
||||
isNotEmpty: (value1: NodeParameterValue) =>
|
||||
!(
|
||||
[undefined, null, '', NaN].includes(value1 as string) ||
|
||||
(typeof value1 === 'object' && value1 !== null && !isDateObject(value1)
|
||||
? Object.entries(value1 as string).length === 0
|
||||
: false) ||
|
||||
(isDateObject(value1) && isDateInvalid(value1))
|
||||
),
|
||||
regex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
||||
|
||||
let regex: RegExp;
|
||||
if (!regexMatch) {
|
||||
regex = new RegExp((value2 || '').toString());
|
||||
} else if (regexMatch.length === 1) {
|
||||
regex = new RegExp(regexMatch[1]);
|
||||
} else {
|
||||
regex = new RegExp(regexMatch[1], regexMatch[2]);
|
||||
}
|
||||
|
||||
return !!(value1 || '').toString().match(regex);
|
||||
},
|
||||
notRegex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimusy]*)$'));
|
||||
|
||||
let regex: RegExp;
|
||||
if (!regexMatch) {
|
||||
regex = new RegExp((value2 || '').toString());
|
||||
} else if (regexMatch.length === 1) {
|
||||
regex = new RegExp(regexMatch[1]);
|
||||
} else {
|
||||
regex = new RegExp(regexMatch[1], regexMatch[2]);
|
||||
}
|
||||
|
||||
return !(value1 || '').toString().match(regex);
|
||||
},
|
||||
};
|
||||
|
||||
// Converts the input data of a dateTime into a number for easy compare
|
||||
const convertDateTime = (value: NodeParameterValue): number => {
|
||||
let returnValue: number | undefined = undefined;
|
||||
if (typeof value === 'string') {
|
||||
returnValue = new Date(value).getTime();
|
||||
} else if (typeof value === 'number') {
|
||||
returnValue = value;
|
||||
}
|
||||
if (moment.isMoment(value)) {
|
||||
returnValue = value.unix();
|
||||
}
|
||||
if ((value as unknown as object) instanceof Date) {
|
||||
returnValue = (value as unknown as Date).getTime();
|
||||
}
|
||||
|
||||
if (returnValue === undefined || isNaN(returnValue)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`The value "${value}" is not a valid DateTime.`,
|
||||
);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
// The different dataTypes to check the values in
|
||||
const dataTypes = ['boolean', 'dateTime', 'number', 'string'];
|
||||
|
||||
// Iterate over all items to check which ones should be output as via output "true" and
|
||||
// which ones via output "false"
|
||||
let dataType: string;
|
||||
let compareOperationResult: boolean;
|
||||
let value1: NodeParameterValue, value2: NodeParameterValue;
|
||||
itemLoop: for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
item = items[itemIndex];
|
||||
|
||||
let compareData: INodeParameters;
|
||||
|
||||
combineOperation = this.getNodeParameter('combineOperation', itemIndex) as string;
|
||||
|
||||
// Check all the values of the different dataTypes
|
||||
for (dataType of dataTypes) {
|
||||
// Check all the values of the current dataType
|
||||
for (compareData of this.getNodeParameter(
|
||||
`conditions.${dataType}`,
|
||||
itemIndex,
|
||||
[],
|
||||
) as INodeParameters[]) {
|
||||
// Check if the values passes
|
||||
|
||||
value1 = compareData.value1 as NodeParameterValue;
|
||||
value2 = compareData.value2 as NodeParameterValue;
|
||||
|
||||
if (dataType === 'dateTime') {
|
||||
value1 = convertDateTime(value1);
|
||||
value2 = convertDateTime(value2);
|
||||
}
|
||||
|
||||
compareOperationResult = compareOperationFunctions[compareData.operation as string](
|
||||
value1,
|
||||
value2,
|
||||
);
|
||||
|
||||
if (compareOperationResult && combineOperation === 'any') {
|
||||
// If it passes and the operation is "any" we do not have to check any
|
||||
// other ones as it should pass anyway. So go on with the next item.
|
||||
returnDataTrue.push(item);
|
||||
continue itemLoop;
|
||||
} else if (!compareOperationResult && combineOperation === 'all') {
|
||||
// If it fails and the operation is "all" we do not have to check any
|
||||
// other ones as it should be not pass anyway. So go on with the next item.
|
||||
returnDataFalse.push(item);
|
||||
continue itemLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (combineOperation === 'all') {
|
||||
// If the operation is "all" it means the item did match all conditions
|
||||
// so it passes.
|
||||
returnDataTrue.push(item);
|
||||
} else {
|
||||
// If the operation is "any" it means the the item did not match any condition.
|
||||
returnDataFalse.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return [returnDataTrue, returnDataFalse];
|
||||
}
|
||||
}
|
||||
111
packages/nodes-base/nodes/If/V2/IfV2.node.ts
Normal file
111
packages/nodes-base/nodes/If/V2/IfV2.node.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import set from 'lodash/set';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class IfV2 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
version: 2,
|
||||
defaults: {
|
||||
name: 'If',
|
||||
color: '#408000',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main', 'main'],
|
||||
outputNames: ['true', 'false'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Conditions',
|
||||
name: 'conditions',
|
||||
placeholder: 'Add Condition',
|
||||
type: 'filter',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
filter: {
|
||||
caseSensitive: '={{!$parameter.options.ignoreCase}}',
|
||||
typeValidation: '={{$parameter.options.looseTypeValidation ? "loose" : "strict"}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Ignore Case',
|
||||
description: 'Whether to ignore letter case when evaluating conditions',
|
||||
name: 'ignoreCase',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Less Strict Type Validation',
|
||||
description: 'Whether to try casting value types based on the selected operator',
|
||||
name: 'looseTypeValidation',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const trueItems: INodeExecutionData[] = [];
|
||||
const falseItems: INodeExecutionData[] = [];
|
||||
|
||||
this.getInputData().forEach((item, itemIndex) => {
|
||||
try {
|
||||
const options = this.getNodeParameter('options', itemIndex) as {
|
||||
ignoreCase?: boolean;
|
||||
looseTypeValidation?: boolean;
|
||||
};
|
||||
let pass = false;
|
||||
try {
|
||||
pass = this.getNodeParameter('conditions', itemIndex, false, {
|
||||
extractValue: true,
|
||||
}) as boolean;
|
||||
} catch (error) {
|
||||
if (!options.looseTypeValidation) {
|
||||
set(
|
||||
error,
|
||||
'description',
|
||||
"Try to change the operator, switch ON the option 'Less Strict Type Validation', or change the type with an expression",
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (item.pairedItem === undefined) {
|
||||
item.pairedItem = { item: itemIndex };
|
||||
}
|
||||
|
||||
if (pass) {
|
||||
trueItems.push(item);
|
||||
} else {
|
||||
falseItems.push(item);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
falseItems.push(item);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [trueItems, falseItems];
|
||||
}
|
||||
}
|
||||
165
packages/nodes-base/nodes/If/test/v2/IfV2.date-time.json
Normal file
165
packages/nodes-base/nodes/If/test/v2/IfV2.date-time.json
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"name": "Filter test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f332a7d1-31b4-4e78-b31e-9e8db945bf3f",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-60,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return [\n {\n \"date\": \"2023-10-26T16:45:52.367Z\",\n \"label\": \"Apple\",\n },\n {\n \"date\": \"2023-10-20T16:45:52.367Z\",\n \"label\": \"Banana\"\n },\n {\n \"date\": \"2023-10-20T16:45:52.367Z\",\n \"label\": \"Kiwi\"\n },\n {\n \"date\": \"2023-10-20T16:45:52.367Z\",\n \"label\": \"Orange\"\n }\n]"
|
||||
},
|
||||
"id": "60697c7f-3948-4790-97ba-8aba03d02ac2",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
160,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": ""
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.date }}",
|
||||
"rightValue": "2023-10-21",
|
||||
"operator": {
|
||||
"type": "dateTime",
|
||||
"operation": "before"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "or"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "7531191b-5ac3-45dc-8afb-27ae83d8f33a",
|
||||
"name": "If",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
380,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "d8c614ea-0bbf-4b12-ad7d-c9ebe09ce583",
|
||||
"name": "Then",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
600,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "69364770-60d2-4ef4-9f29-9570718a9a10",
|
||||
"name": "Else",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
600,
|
||||
580
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Then": [
|
||||
{
|
||||
"json": {
|
||||
"date": "2023-10-20T16:45:52.367Z",
|
||||
"label": "Banana"
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"date": "2023-10-20T16:45:52.367Z",
|
||||
"label": "Kiwi"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"date": "2023-10-20T16:45:52.367Z",
|
||||
"label": "Orange"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Else": [
|
||||
{
|
||||
"json": {
|
||||
"date": "2023-10-26T16:45:52.367Z",
|
||||
"label": "Apple"
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Then",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Else",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "ce7ed9ae-704e-4da8-b178-24556f720b2a",
|
||||
"id": "BWUTRs5RHxVgQ4uT",
|
||||
"meta": {
|
||||
"instanceId": "78577815012af39cf16dad7a787b0898c42fb7514b8a7f99b2136862c2af502c"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
5
packages/nodes-base/nodes/If/test/v2/IfV2.node.test.ts
Normal file
5
packages/nodes-base/nodes/If/test/v2/IfV2.node.test.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { testWorkflows, getWorkflowFilenames } from '@test/nodes/Helpers';
|
||||
|
||||
const workflows = getWorkflowFilenames(__dirname);
|
||||
|
||||
describe('Test IF v2 Node', () => testWorkflows(workflows));
|
||||
165
packages/nodes-base/nodes/If/test/v2/IfV2.number.json
Normal file
165
packages/nodes-base/nodes/If/test/v2/IfV2.number.json
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"name": "Filter test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f332a7d1-31b4-4e78-b31e-9e8db945bf3f",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-60,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return [\n {\n \"count\": 1,\n \"label\": \"Apple\",\n },\n {\n \"count\": 5,\n \"label\": \"Banana\"\n },\n {\n \"count\": 12,\n \"label\": \"Kiwi\"\n }\n]"
|
||||
},
|
||||
"id": "60697c7f-3948-4790-97ba-8aba03d02ac2",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
160,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": ""
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.count }}",
|
||||
"rightValue": "1",
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "equals"
|
||||
}
|
||||
},
|
||||
{
|
||||
"leftValue": "={{ $json.count }}",
|
||||
"rightValue": "10",
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "gt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "or"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "7531191b-5ac3-45dc-8afb-27ae83d8f33a",
|
||||
"name": "If",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
380,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "d8c614ea-0bbf-4b12-ad7d-c9ebe09ce583",
|
||||
"name": "Then",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
600,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "69364770-60d2-4ef4-9f29-9570718a9a10",
|
||||
"name": "Else",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
600,
|
||||
580
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Then": [
|
||||
{
|
||||
"json": {
|
||||
"count": 1,
|
||||
"label": "Apple"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"count": 12,
|
||||
"label": "Kiwi"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Else": [
|
||||
{
|
||||
"json": {
|
||||
"count": 5,
|
||||
"label": "Banana"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Then",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Else",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "78d54316-4f39-4012-bbb8-c789f1f8865b",
|
||||
"id": "BWUTRs5RHxVgQ4uT",
|
||||
"meta": {
|
||||
"instanceId": "78577815012af39cf16dad7a787b0898c42fb7514b8a7f99b2136862c2af502c"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
182
packages/nodes-base/nodes/If/test/v2/IfV2.other.json
Normal file
182
packages/nodes-base/nodes/If/test/v2/IfV2.other.json
Normal file
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"name": "Filter test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f332a7d1-31b4-4e78-b31e-9e8db945bf3f",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-60,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return [\n {\n \"label\": \"Apple\",\n tags: [],\n meta: {foo: 'bar'}\n },\n {\n \"label\": \"Banana\",\n tags: ['exotic'],\n meta: {}\n },\n {\n \"label\": \"Kiwi\",\n tags: ['exotic'],\n meta: {}\n },\n {\n \"label\": \"Orange\",\n meta: {}\n }\n]"
|
||||
},
|
||||
"id": "60697c7f-3948-4790-97ba-8aba03d02ac2",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
160,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": ""
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.tags }}",
|
||||
"rightValue": "exotic",
|
||||
"operator": {
|
||||
"type": "array",
|
||||
"operation": "contains",
|
||||
"rightType": "any"
|
||||
}
|
||||
},
|
||||
{
|
||||
"leftValue": "={{ $json.meta }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "object",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "or"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "7531191b-5ac3-45dc-8afb-27ae83d8f33a",
|
||||
"name": "If",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
380,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "d8c614ea-0bbf-4b12-ad7d-c9ebe09ce583",
|
||||
"name": "Then",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
600,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "69364770-60d2-4ef4-9f29-9570718a9a10",
|
||||
"name": "Else",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
600,
|
||||
580
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Then": [
|
||||
{
|
||||
"json": {
|
||||
"label": "Apple",
|
||||
"tags": [],
|
||||
"meta": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"label": "Banana",
|
||||
"tags": [
|
||||
"exotic"
|
||||
],
|
||||
"meta": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"label": "Kiwi",
|
||||
"tags": [
|
||||
"exotic"
|
||||
],
|
||||
"meta": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Else": [
|
||||
{
|
||||
"json": {
|
||||
"label": "Orange",
|
||||
"meta": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Then",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Else",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "a6249f48-d88f-4b80-9ed9-79555e522d48",
|
||||
"id": "BWUTRs5RHxVgQ4uT",
|
||||
"meta": {
|
||||
"instanceId": "78577815012af39cf16dad7a787b0898c42fb7514b8a7f99b2136862c2af502c"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
178
packages/nodes-base/nodes/If/test/v2/IfV2.string.json
Normal file
178
packages/nodes-base/nodes/If/test/v2/IfV2.string.json
Normal file
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"name": "Filter test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "96490c63-a3f4-4923-b969-8f9adcbb1bbb",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-160,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": false,
|
||||
"leftValue": ""
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.firstname }}",
|
||||
"rightValue": "s",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "startsWith"
|
||||
}
|
||||
},
|
||||
{
|
||||
"leftValue": "={{ $json.lastname }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "any",
|
||||
"operation": "exists"
|
||||
}
|
||||
},
|
||||
{
|
||||
"leftValue": "={{ $json.email }}",
|
||||
"rightValue": "@yahoo.com",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "endsWith"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {
|
||||
"caseSensitive": false
|
||||
}
|
||||
},
|
||||
"id": "48399b31-219a-42fa-bb5b-380dbb4a2e7d",
|
||||
"name": "If",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
260,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "8a59a941-bafd-46a6-8692-878de715d912",
|
||||
"name": "false",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
560,
|
||||
440
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "b82546fa-f9e9-4fa3-9dcb-e2c94a3784de",
|
||||
"name": "Then",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
560,
|
||||
140
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return [\n {\n \"email\": \"Shane@yahoo.com\",\n \"firstname\": \"Shane\",\n \"lastname\": \"Martin\"\n },\n {\n \"email\": \"Sharon@yahoo.com\",\n \"firstname\": \"Sharon\",\n \"lastname\": \"Tromp\"\n },\n {\n \"email\": \"sarah@gmail.com\",\n \"firstname\": \"Sarah\",\n \"lastname\": \"Dawson\"\n }\n]"
|
||||
},
|
||||
"id": "674c5688-ac03-49a7-83fb-62460a10cc10",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
60,
|
||||
300
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Then": [
|
||||
{
|
||||
"json": {
|
||||
"email": "Shane@yahoo.com",
|
||||
"firstname": "Shane",
|
||||
"lastname": "Martin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"email": "Sharon@yahoo.com",
|
||||
"firstname": "Sharon",
|
||||
"lastname": "Tromp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"false": [
|
||||
{
|
||||
"json": {
|
||||
"email": "sarah@gmail.com",
|
||||
"firstname": "Sarah",
|
||||
"lastname": "Dawson"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Then",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "false",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "48c6a584-e79b-4ce4-ab4b-2b4ab663b89d",
|
||||
"id": "BWUTRs5RHxVgQ4uT",
|
||||
"meta": {
|
||||
"instanceId": "78577815012af39cf16dad7a787b0898c42fb7514b8a7f99b2136862c2af502c"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user