Skip to content

Commit

Permalink
feat(Filter Node): New node
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-radency authored Mar 22, 2023
1 parent 5dda3f2 commit cc9fe7b
Show file tree
Hide file tree
Showing 6 changed files with 1,085 additions and 0 deletions.
19 changes: 19 additions & 0 deletions packages/nodes-base/nodes/Filter/Filter.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.filter",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"details": "The Filter node can be used to filter items based on a condition. If the condition is met, the item will be passed on to the next node. If the condition is not met, the item will be omited. Conditions can be combined together by AND(meet all conditions), or OR(meet at least one condition).",
"categories": ["Core Nodes"],
"resources": {
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.filter/"
}
],
"generic": []
},
"alias": ["Router", "Filter", "Condition", "Logic", "Boolean", "Branch"],
"subcategories": {
"Core Nodes": ["Flow"]
}
}
360 changes: 360 additions & 0 deletions packages/nodes-base/nodes/Filter/Filter.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeParameters,
INodeType,
INodeTypeDescription,
NodeParameterValue,
} from 'n8n-workflow';

import { compareOperationFunctions, convertDateTime } from './GenericFunctions';

export class Filter implements INodeType {
description: INodeTypeDescription = {
displayName: 'Filter',
name: 'filter',
icon: 'fa:filter',
group: ['transform'],
version: 1,
description: 'Filter out incoming items based on given conditions',
defaults: {
name: 'Filter',
color: '#229eff',
},
inputs: ['main'],
outputs: ['main'],
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 Conditions',
name: 'combineConditions',
type: 'options',
options: [
{
name: 'AND',
description: 'Items are passed to the next node only if they meet all the conditions',
value: 'AND',
},
{
name: 'OR',
description: 'Items are passed to the next node if they meet at least one condition',
value: 'OR',
},
],
default: 'AND',
description:
'How to combine the conditions: AND requires all conditions to be true, OR requires at least one condition to be true',
},
],
};

async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const returnData: INodeExecutionData[] = [];

const items = this.getInputData();

const dataTypes = ['boolean', 'dateTime', 'number', 'string'];

itemLoop: for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const item = items[itemIndex];

const combineConditions = this.getNodeParameter('combineConditions', itemIndex) as string;

for (const dataType of dataTypes) {
const typeConditions = this.getNodeParameter(
`conditions.${dataType}`,
itemIndex,
[],
) as INodeParameters[];

for (const condition of typeConditions) {
let value1 = condition.value1 as NodeParameterValue;
let value2 = condition.value2 as NodeParameterValue;

if (dataType === 'dateTime') {
const node = this.getNode();
value1 = convertDateTime(node, value1);
value2 = convertDateTime(node, value2);
}

const compareResult = compareOperationFunctions[condition.operation as string](
value1,
value2,
);

// If the operation is "OR" it means the item did match one condition no ned to check further
if (compareResult && combineConditions === 'OR') {
returnData.push(item);
continue itemLoop;
}

// If the operation is "AND" it means the item failed one condition no ned to check further
if (!compareResult && combineConditions === 'AND') {
continue itemLoop;
}
}
}

// If the operation is "AND" it means the item did match all conditions
if (combineConditions === 'AND') returnData.push(item);
}

return [returnData];
}
}
Loading

0 comments on commit cc9fe7b

Please sign in to comment.