feat(Filter Node): Overhaul UI by adding the new filter component (#8016)

## Summary

Adds a new version of the Filter node (v2) that uses the filter
component (like in the new If node).

<img width="1612" alt="image"
src="https://github.com/n8n-io/n8n/assets/8850410/bca38e47-305f-4a9e-9c5c-ec550b9f7d4a">

Test by adding a new Filter node to the canvas and trying different
operators/options. Example workflow can be found in
`packages/nodes-base/nodes/Filter/test/workflow_v2.json`

## Related tickets and issues
https://linear.app/n8n/issue/NODE-982



## Review / Merge checklist
- [x] PR title and summary are descriptive. **Remember, the title
automatically goes into the changelog. Use `(no-changelog)` otherwise.**
([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md))
- [x] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up
ticket created.
- [x] Tests included.
> A bug is not considered fixed, unless a test is added to prevent it
from happening again.
   > A feature is not complete without tests.

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Elias Meire 2023-12-15 11:56:49 +01:00 committed by GitHub
parent f18bc5f4b7
commit 3d530522f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 837 additions and 366 deletions

View file

@ -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<INodeExecutionData[][]> {
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);
}
}

View file

@ -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<INodeExecutionData[][]> {
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];
}
}

View file

@ -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<INodeExecutionData[][]> {
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];
}
}

View file

@ -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": []
}