Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): Filter component + implement in If node #7490

Merged
merged 49 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
95b39bc
Add filter component
elsmr Oct 16, 2023
42e0c2e
Merge branch 'master' into node-785-filter-component
elsmr Oct 19, 2023
edadc73
Filter component UI
elsmr Oct 23, 2023
2a0833c
Implement extractValue for filter component + If node v2
elsmr Oct 26, 2023
db63788
Responsive filter component styling, frontend tweaks
elsmr Oct 26, 2023
117d48b
Add tests for IfNodeV2
elsmr Oct 27, 2023
c2ba0cc
Resolve typeOption expression in Filter component
elsmr Oct 27, 2023
67f6440
Remove duplicate switch case
elsmr Oct 27, 2023
23b6775
Add test coverage
elsmr Oct 27, 2023
629cfb3
Implement nested select for operator
elsmr Oct 30, 2023
7808401
Merge branch 'master' into node-785-filter-component
elsmr Oct 30, 2023
7bb4e9c
Handle continueOnFail and pairedItem in IfV2
elsmr Oct 30, 2023
0058068
Rename Filter component (reserved name), undo timestamp parse on date…
elsmr Nov 7, 2023
66f111a
Merge branch 'master' into node-785-filter-component
elsmr Nov 7, 2023
54e6708
Merge branch 'master' into node-785-filter-component
elsmr Nov 14, 2023
6fd0284
Fix product review issues
elsmr Nov 15, 2023
8bbdce4
Add test coverage for all filter comparison operators
elsmr Nov 15, 2023
28b3710
Minor fix to addCondition button (the hover text was white).
gandreini Nov 21, 2023
c2617bb
Minor copy updates.
gandreini Nov 22, 2023
c200bde
Changed copy for regex.
gandreini Nov 22, 2023
1def168
Copy tweaks.
gandreini Nov 23, 2023
c1cf073
Merge branch 'master' into node-785-filter-component
elsmr Nov 27, 2023
6808658
Throw errors when types are not as expected
elsmr Dec 1, 2023
de3aa7c
Show what condition is currently evaluated to
elsmr Dec 1, 2023
c9604d0
Implement review feedback: bugs, styling fixes
elsmr Dec 1, 2023
f7ccce2
Merge branch 'master' into node-785-filter-component
elsmr Dec 1, 2023
1326e32
Fix composable imports
elsmr Dec 1, 2023
aa49d2f
Fix icon positioning
elsmr Dec 1, 2023
586f2a8
Fix validation for operators with only a left value
elsmr Dec 1, 2023
cf729a5
Condition tooltip copy update
elsmr Dec 1, 2023
046e468
Fix linting & unit tests
elsmr Dec 1, 2023
a9a8255
Add test coverage for type validation
elsmr Dec 1, 2023
50e3bf1
Minor copy tweak to true/false icon tooltip.
gandreini Dec 4, 2023
25a92f2
Greyscale true/false icons, change default operator
elsmr Dec 4, 2023
83f86b3
Revert default operator to string:equals
elsmr Dec 4, 2023
31b8242
Fix drag & drop issue when targets are close to eachother
elsmr Dec 5, 2023
cb055dd
Status validation icon styling fix
elsmr Dec 5, 2023
d1c4cfa
Fix review remarks: error handling
elsmr Dec 6, 2023
f3ac99f
Merge branch 'master' into node-785-filter-component
elsmr Dec 6, 2023
2f1b190
Make array parsing more strict, add test
elsmr Dec 7, 2023
6e9c594
Add type annotations to fix linting
elsmr Dec 7, 2023
c494411
Remove .only from e2e test
elsmr Dec 7, 2023
a08dea9
Lazy render OperatorSelect options to improve performance
elsmr Dec 11, 2023
7b42ba4
Merge branch 'master' into node-785-filter-component
elsmr Dec 11, 2023
8f5f885
Use new ApplicationError
elsmr Dec 11, 2023
9dd3356
Use nodeHelpers composable
elsmr Dec 11, 2023
24d2ebb
Merge branch 'master' of https://github.com/n8n-io/n8n into node-785-…
michael-radency Dec 13, 2023
519fb38
:zap: linter fix
michael-radency Dec 13, 2023
5875ecd
Merge branch 'master' of https://github.com/n8n-io/n8n into node-785-…
michael-radency Dec 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
export const CODE_NODE_NAME = 'Code';
export const SET_NODE_NAME = 'Set';
export const EDIT_FIELDS_SET_NODE_NAME = 'Edit Fields';
export const IF_NODE_NAME = 'IF';
export const IF_NODE_NAME = 'If';
export const MERGE_NODE_NAME = 'Merge';
export const SWITCH_NODE_NAME = 'Switch';
export const GMAIL_NODE_NAME = 'Gmail';
Expand Down
58 changes: 58 additions & 0 deletions cypress/e2e/30-if-node.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { IF_NODE_NAME } from '../constants';
import { WorkflowPage, NDV } from '../pages';

const workflowPage = new WorkflowPage();
const ndv = new NDV();

const FILTER_PARAM_NAME = 'conditions';

describe('If Node (filter component)', () => {
beforeEach(() => {
workflowPage.actions.visit();
});

it('should be able to create and delete multiple conditions', () => {
workflowPage.actions.addInitialNodeToCanvas(IF_NODE_NAME, { keepNdvOpen: true });

// Default state
ndv.getters.filterComponent(FILTER_PARAM_NAME).should('exist');
ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 1);
ndv.getters
.filterConditionOperator(FILTER_PARAM_NAME)
.find('input')
.should('have.value', 'is equal to');

// Add
ndv.actions.addFilterCondition(FILTER_PARAM_NAME);
ndv.getters.filterConditionLeft(FILTER_PARAM_NAME, 0).find('input').type('first left');
ndv.getters.filterConditionLeft(FILTER_PARAM_NAME, 1).find('input').type('second left');
ndv.actions.addFilterCondition(FILTER_PARAM_NAME);
ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 3);

// Delete
ndv.actions.removeFilterCondition(FILTER_PARAM_NAME, 0);
ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 2);
ndv.getters
.filterConditionLeft(FILTER_PARAM_NAME, 0)
.find('input')
.should('have.value', 'second left');
ndv.actions.removeFilterCondition(FILTER_PARAM_NAME, 1);
ndv.getters.filterConditions(FILTER_PARAM_NAME).should('have.length', 1);
});

it('should correctly evaluate conditions', () => {
cy.fixture('Test_workflow_filter.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});

workflowPage.actions.zoomToFit();
workflowPage.actions.executeWorkflow();

workflowPage.actions.openNode('Then');
ndv.getters.outputPanel().contains('3 items').should('exist');
ndv.actions.close();

workflowPage.actions.openNode('Else');
ndv.getters.outputPanel().contains('1 item').should('exist');
});
});
7 changes: 4 additions & 3 deletions cypress/e2e/4-node-creator.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NodeCreator } from '../pages/features/node-creator';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
import { NDV } from '../pages/ndv';
import { getVisibleSelect } from '../utils';
import { IF_NODE_NAME } from '../constants';

const nodeCreatorFeature = new NodeCreator();
const WorkflowPage = new WorkflowPageClass();
Expand Down Expand Up @@ -360,19 +361,19 @@ describe('Node Creator', () => {
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Edit Fields (Set)');

nodeCreatorFeature.getters.searchBar().find('input').clear().type('i');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'IF');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME);
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch');

nodeCreatorFeature.getters.searchBar().find('input').clear().type('sw');
nodeCreatorFeature.getters.searchBar().find('input').clear().type('Edit F');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Edit Fields (Set)');

nodeCreatorFeature.getters.searchBar().find('input').clear().type('i');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'IF');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME);
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch');

nodeCreatorFeature.getters.searchBar().find('input').clear().type('IF');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'IF');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME);
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch');

nodeCreatorFeature.getters.searchBar().find('input').clear().type('sw');
Expand Down
153 changes: 153 additions & 0 deletions cypress/fixtures/Test_workflow_filter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
{
"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\": \"Pear\",\n tags: ['other'],\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
}
},
{
"leftValue": "={{ $json.label }}",
"rightValue": "Pea",
"operator": {
"type": "string",
"operation": "startsWith",
"rightType": "string"
}
}
],
"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": {},
"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": []
}
29 changes: 24 additions & 5 deletions cypress/pages/ndv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ export class NDV extends BasePage {
parameterExpressionPreview: (parameterName: string) =>
this.getters
.nodeParameters()
.find(
`[data-test-id="parameter-input-${parameterName}"] + [data-test-id="parameter-expression-preview"]`,
),
.find(`[data-test-id="parameter-expression-preview-${parameterName}"]`),
nodeNameContainer: () => cy.getByTestId('node-title-container'),
nodeRenameInput: () => cy.getByTestId('node-rename-input'),
executePrevious: () => cy.getByTestId('execute-previous-node'),
Expand Down Expand Up @@ -79,6 +77,23 @@ export class NDV extends BasePage {
cy.getByTestId('columns-parameter-input-options-container'),
resourceMapperRemoveAllFieldsOption: () => cy.getByTestId('action-removeAllFields'),
sqlEditorContainer: () => cy.getByTestId('sql-editor-container'),
filterComponent: (paramName: string) => cy.getByTestId(`filter-${paramName}`),
filterCombinator: (paramName: string, index = 0) =>
this.getters.filterComponent(paramName).getByTestId('filter-combinator-select').eq(index),
filterConditions: (paramName: string) =>
this.getters.filterComponent(paramName).getByTestId('filter-condition'),
filterCondition: (paramName: string, index = 0) =>
this.getters.filterComponent(paramName).getByTestId('filter-condition').eq(index),
filterConditionLeft: (paramName: string, index = 0) =>
this.getters.filterComponent(paramName).getByTestId('filter-condition-left').eq(index),
filterConditionRight: (paramName: string, index = 0) =>
this.getters.filterComponent(paramName).getByTestId('filter-condition-right').eq(index),
filterConditionOperator: (paramName: string, index = 0) =>
this.getters.filterComponent(paramName).getByTestId('filter-operator-select').eq(index),
filterConditionRemove: (paramName: string, index = 0) =>
this.getters.filterComponent(paramName).getByTestId('filter-remove-condition').eq(index),
filterConditionAdd: (paramName: string) =>
this.getters.filterComponent(paramName).getByTestId('filter-add-condition'),
searchInput: () => cy.getByTestId('ndv-search'),
pagination: () => cy.getByTestId('ndv-data-pagination'),
nodeVersion: () => cy.getByTestId('node-version'),
Expand Down Expand Up @@ -199,7 +214,6 @@ export class NDV extends BasePage {
.find('span')
.should('include.html', asEncodedHTML(value));
},

refreshResourceMapperColumns: () => {
this.getters.resourceMapperSelectColumn().realHover();
this.getters
Expand All @@ -210,7 +224,12 @@ export class NDV extends BasePage {

getVisiblePopper().find('li').last().click();
},

addFilterCondition: (paramName: string) => {
this.getters.filterConditionAdd(paramName).click();
},
removeFilterCondition: (paramName: string, index: number) => {
this.getters.filterConditionRemove(paramName, index).click();
},
setInvalidExpression: ({
fieldName,
invalidExpression,
Expand Down
51 changes: 37 additions & 14 deletions packages/core/src/ExtractValue.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import type {
INode,
INodeParameters,
INodeProperties,
INodePropertyCollection,
INodePropertyOptions,
INodeType,
NodeParameterValueType,
} from 'n8n-workflow';
import get from 'lodash/get';
import {
NodeOperationError,
NodeHelpers,
ApplicationError,
LoggerProxy,
NodeHelpers,
NodeOperationError,
WorkflowOperationError,
ApplicationError,
executeFilter,
isFilterValue,
type INode,
type INodeParameters,
type INodeProperties,
type INodePropertyCollection,
type INodePropertyOptions,
type INodeType,
type NodeParameterValueType,
} from 'n8n-workflow';

function findPropertyFromParameterName(
Expand Down Expand Up @@ -123,6 +124,25 @@ function extractValueRLC(
return executeRegexExtractValue(value.value, regex, parameterName, property.displayName);
}

function extractValueFilter(
value: NodeParameterValueType | object,
property: INodeProperties,
parameterName: string,
itemIndex: number,
): NodeParameterValueType | object {
if (!isFilterValue(value)) {
return value;
}

if (property.extractValue?.type) {
throw new Error(
`Property "${parameterName}" has an invalid extractValue type. Filter parameters only support extractValue: true`,
);
}

return executeFilter(value, { itemIndex });
}

function extractValueOther(
value: NodeParameterValueType | object,
property: INodeProperties | INodePropertyCollection,
Expand Down Expand Up @@ -162,6 +182,7 @@ export function extractValue(
parameterName: string,
node: INode,
nodeType: INodeType,
itemIndex = 0,
): NodeParameterValueType | object {
let property: INodePropertyOptions | INodeProperties | INodePropertyCollection;
try {
Expand All @@ -174,10 +195,12 @@ export function extractValue(

if (property.type === 'resourceLocator') {
return extractValueRLC(value, property, parameterName);
} else if (property.type === 'filter') {
return extractValueFilter(value, property, parameterName, itemIndex);
}
return extractValueOther(value, property, parameterName);
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
throw new NodeOperationError(node, error);
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment
throw new NodeOperationError(node, error, { description: get(error, 'description') });
}
}
Loading
Loading