diff --git a/packages/nodes-base/nodes/Filter/Filter.node.ts b/packages/nodes-base/nodes/Filter/Filter.node.ts index 76913e891f..880542c488 100644 --- a/packages/nodes-base/nodes/Filter/Filter.node.ts +++ b/packages/nodes-base/nodes/Filter/Filter.node.ts @@ -1,372 +1,24 @@ -import type { - IExecuteFunctions, - INodeExecutionData, - INodeParameters, - INodeType, - INodeTypeDescription, - NodeParameterValue, -} from 'n8n-workflow'; +import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow'; +import { VersionedNodeType } from 'n8n-workflow'; -import { compareOperationFunctions, convertDateTime } from './GenericFunctions'; +import { FilterV1 } from './V1/FilterV1.node'; +import { FilterV2 } from './V2/FilterV2.node'; -export class Filter implements INodeType { - description: INodeTypeDescription = { - displayName: 'Filter', - name: 'filter', - icon: 'fa:filter', - group: ['transform'], - version: 1, - description: 'Remove items matching a condition', - defaults: { - name: 'Filter', - color: '#229eff', - }, - inputs: ['main'], - outputs: ['main'], - outputNames: ['Kept', 'Discarded'], - 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', - }, - ], - }; +export class Filter extends VersionedNodeType { + constructor() { + const baseDescription: INodeTypeBaseDescription = { + displayName: 'Filter', + name: 'filter', + icon: 'fa:filter', + group: ['transform'], + description: 'Remove items matching a condition', + }; - async execute(this: IExecuteFunctions): Promise { - const returnDataTrue: INodeExecutionData[] = []; - const returnDataFalse: INodeExecutionData[] = []; + const nodeVersions: IVersionedNodeType['nodeVersions'] = { + 1: new FilterV1(baseDescription), + 2: new FilterV2(baseDescription), + }; - 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 (item.pairedItem === undefined) { - item.pairedItem = [{ item: itemIndex }]; - } - - // If the operation is "OR" it means the item did match one condition no ned to check further - if (compareResult && combineConditions === 'OR') { - returnDataTrue.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') { - returnDataFalse.push(item); - continue itemLoop; - } - } - } - - // If the operation is "AND" it means the item did match all conditions - if (combineConditions === 'AND') { - returnDataTrue.push(item); - } else { - // If the operation is "OR" it means the the item did not match any condition. - returnDataFalse.push(item); - } - } - - return [returnDataTrue, returnDataFalse]; + super(nodeVersions, baseDescription); } } diff --git a/packages/nodes-base/nodes/Filter/V1/FilterV1.node.ts b/packages/nodes-base/nodes/Filter/V1/FilterV1.node.ts new file mode 100644 index 0000000000..62c4060eff --- /dev/null +++ b/packages/nodes-base/nodes/Filter/V1/FilterV1.node.ts @@ -0,0 +1,373 @@ +import type { + IExecuteFunctions, + INodeExecutionData, + INodeParameters, + INodeType, + INodeTypeBaseDescription, + INodeTypeDescription, + NodeParameterValue, +} from 'n8n-workflow'; + +import { compareOperationFunctions, convertDateTime } from './GenericFunctions'; + +export class FilterV1 implements INodeType { + description: INodeTypeDescription; + + constructor(baseDescription: INodeTypeBaseDescription) { + this.description = { + ...baseDescription, + version: 1, + defaults: { + name: 'Filter', + color: '#229eff', + }, + inputs: ['main'], + outputs: ['main'], + outputNames: ['Kept', 'Discarded'], + 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 { + const returnDataTrue: INodeExecutionData[] = []; + const returnDataFalse: 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 (item.pairedItem === undefined) { + item.pairedItem = [{ item: itemIndex }]; + } + + // If the operation is "OR" it means the item did match one condition no ned to check further + if (compareResult && combineConditions === 'OR') { + returnDataTrue.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') { + returnDataFalse.push(item); + continue itemLoop; + } + } + } + + // If the operation is "AND" it means the item did match all conditions + if (combineConditions === 'AND') { + returnDataTrue.push(item); + } else { + // If the operation is "OR" it means the the item did not match any condition. + returnDataFalse.push(item); + } + } + + return [returnDataTrue, returnDataFalse]; + } +} diff --git a/packages/nodes-base/nodes/Filter/GenericFunctions.ts b/packages/nodes-base/nodes/Filter/V1/GenericFunctions.ts similarity index 100% rename from packages/nodes-base/nodes/Filter/GenericFunctions.ts rename to packages/nodes-base/nodes/Filter/V1/GenericFunctions.ts diff --git a/packages/nodes-base/nodes/Filter/V2/FilterV2.node.ts b/packages/nodes-base/nodes/Filter/V2/FilterV2.node.ts new file mode 100644 index 0000000000..be9fb9f80b --- /dev/null +++ b/packages/nodes-base/nodes/Filter/V2/FilterV2.node.ts @@ -0,0 +1,111 @@ +import set from 'lodash/set'; +import type { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeBaseDescription, + INodeTypeDescription, +} from 'n8n-workflow'; + +export class FilterV2 implements INodeType { + description: INodeTypeDescription; + + constructor(baseDescription: INodeTypeBaseDescription) { + this.description = { + ...baseDescription, + version: 2, + defaults: { + name: 'Filter', + color: '#229eff', + }, + inputs: ['main'], + outputs: ['main'], + outputNames: ['Kept', 'Discarded'], + 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 { + const keptItems: INodeExecutionData[] = []; + const discardedItems: 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) { + keptItems.push(item); + } else { + discardedItems.push(item); + } + } catch (error) { + if (this.continueOnFail()) { + discardedItems.push(item); + } else { + throw error; + } + } + }); + + return [keptItems, discardedItems]; + } +} diff --git a/packages/nodes-base/nodes/Filter/test/workflow.json b/packages/nodes-base/nodes/Filter/test/workflow_v1.json similarity index 100% rename from packages/nodes-base/nodes/Filter/test/workflow.json rename to packages/nodes-base/nodes/Filter/test/workflow_v1.json diff --git a/packages/nodes-base/nodes/Filter/test/workflow_v2.json b/packages/nodes-base/nodes/Filter/test/workflow_v2.json new file mode 100644 index 0000000000..d7836473c9 --- /dev/null +++ b/packages/nodes-base/nodes/Filter/test/workflow_v2.json @@ -0,0 +1,335 @@ +{ + "name": "Filter v2", + "nodes": [ + { + "parameters": { + "jsCode": "return [\n {\n id: 1,\n name: 'Adam',\n subscribed: false,\n updatedAt: '2011-10-05T14:48:00.000Z',\n notes: null,\n email: 'adam@mail.com',\n },\n {\n id: 2,\n name: 'Victor',\n subscribed: true,\n updatedAt: '2020-10-05T14:48:00.000Z',\n notes: 'some notes',\n email: 'victor@mail.com',\n },\n {\n id: 3,\n name: 'Sam',\n subscribed: true,\n updatedAt: '2021-10-05T14:48:00.000Z',\n notes: 'other notes',\n email: 'sam@mail.com',\n }, \n];" + }, + "id": "d8f7d6a2-02f5-40f6-83ca-540467f80ad8", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 1, + "position": [ + 2480, + 1080 + ] + }, + { + "parameters": {}, + "id": "e112277f-9c6a-404f-a8f2-9b69fd88db16", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 2320, + 1080 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "b65a0fbd-abdb-4622-8312-c12496444ad3", + "leftValue": "={{ $json.subscribed }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + }, + { + "id": "adf82766-a27f-450e-84a9-9616c5be5191", + "leftValue": "={{ $json.notes }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "exists", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "daaa1445-f279-4edb-b2d6-b6415cb5fb55", + "name": "Filter Boolean", + "type": "n8n-nodes-base.filter", + "typeVersion": 2, + "position": [ + 2700, + 800 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "480a5f38-0e31-44c5-9f61-5c34344d265e", + "leftValue": "={{ $json.updatedAt }}", + "rightValue": "2018-12-31T22:00:00", + "operator": { + "type": "dateTime", + "operation": "after" + } + }, + { + "id": "a9240497-a583-4a6a-97e5-a4197036e04d", + "leftValue": "={{ $json.updatedAt }}", + "rightValue": "2021-08-03T03:30:08", + "operator": { + "type": "dateTime", + "operation": "before" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "816b4bac-2213-4057-b50e-1f7f39542476", + "name": "Filter Date", + "type": "n8n-nodes-base.filter", + "typeVersion": 2, + "position": [ + 2700, + 960 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose" + }, + "conditions": [ + { + "id": "21265681-4230-4648-ba79-5513b3d9260f", + "leftValue": "={{ $json.id }}", + "rightValue": 1, + "operator": { + "type": "number", + "operation": "gt" + } + }, + { + "id": "b4f736a7-d60d-4acd-abc4-04c324581ccf", + "leftValue": "={{ $json.id }}", + "rightValue": 3, + "operator": { + "type": "number", + "operation": "lte" + } + }, + { + "id": "8b29b1da-8dc3-42b0-9440-7394709e3619", + "leftValue": "={{ $json.id }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "exists", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": { + "looseTypeValidation": true + } + }, + "id": "66fe5d53-2652-4860-8d31-cb033af3a7c3", + "name": "Filter Number", + "type": "n8n-nodes-base.filter", + "typeVersion": 2, + "position": [ + 2700, + 1120 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": false, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "dafeb20d-80ae-4bb5-8375-ff61002f1bdd", + "leftValue": "={{ $json.name }}", + "rightValue": "v", + "operator": { + "type": "string", + "operation": "startsWith" + } + }, + { + "id": "8bb39457-005a-427d-9584-6985a05b589d", + "leftValue": "={{ $json.name }}", + "rightValue": "s", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "or" + }, + "options": { + "ignoreCase": true + } + }, + "id": "b625a4d4-4bd7-4480-a76a-622d460f4392", + "name": "Filter String", + "type": "n8n-nodes-base.filter", + "typeVersion": 2, + "position": [ + 2700, + 1280 + ] + } + ], + "pinData": { + "Filter Boolean": [ + { + "json": { + "id": 2, + "name": "Victor", + "subscribed": true, + "updatedAt": "2020-10-05T14:48:00.000Z", + "notes": "some notes", + "email": "victor@mail.com" + } + }, + { + "json": { + "id": 3, + "name": "Sam", + "subscribed": true, + "updatedAt": "2021-10-05T14:48:00.000Z", + "notes": "other notes", + "email": "sam@mail.com" + } + } + ], + "Filter Date": [ + { + "json": { + "id": 2, + "name": "Victor", + "subscribed": true, + "updatedAt": "2020-10-05T14:48:00.000Z", + "notes": "some notes", + "email": "victor@mail.com" + } + } + ], + "Filter Number": [ + { + "json": { + "id": 2, + "name": "Victor", + "subscribed": true, + "updatedAt": "2020-10-05T14:48:00.000Z", + "notes": "some notes", + "email": "victor@mail.com" + } + }, + { + "json": { + "id": 3, + "name": "Sam", + "subscribed": true, + "updatedAt": "2021-10-05T14:48:00.000Z", + "notes": "other notes", + "email": "sam@mail.com" + } + } + ], + "Filter String": [ + { + "json": { + "id": 1, + "name": "Adam", + "subscribed": false, + "updatedAt": "2011-10-05T14:48:00.000Z", + "notes": null, + "email": "adam@mail.com" + } + }, + { + "json": { + "id": 2, + "name": "Victor", + "subscribed": true, + "updatedAt": "2020-10-05T14:48:00.000Z", + "notes": "some notes", + "email": "victor@mail.com" + } + } + ] + }, + "connections": { + "Code": { + "main": [ + [ + { + "node": "Filter Boolean", + "type": "main", + "index": 0 + }, + { + "node": "Filter Date", + "type": "main", + "index": 0 + }, + { + "node": "Filter Number", + "type": "main", + "index": 0 + }, + { + "node": "Filter String", + "type": "main", + "index": 0 + } + ] + ] + }, + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "1c680397-66f2-4ec1-a8a6-bb60d6d564d7", + "id": "0nx3Xwa3s9uIqflV", + "tags": [] + }