From 1a5fd5a2389ca67734020a7754bbba77abb5e9ae Mon Sep 17 00:00:00 2001 From: Jaakko Husso Date: Fri, 12 Sep 2025 17:54:54 +0300 Subject: [PATCH] feat(Data Table Node): Show 'Is False' / 'Is True' condition on boolean columns (no-changelog) (#19478) --- .../nodes/DataTable/common/constants.ts | 2 +- .../nodes/DataTable/common/methods.ts | 47 +++-- .../nodes/DataTable/common/selectMany.ts | 2 +- .../nodes/DataTable/common/utils.ts | 12 ++ .../nodes/DataTable/test/common/utils.test.ts | 193 ++++++++++++------ 5 files changed, 182 insertions(+), 74 deletions(-) diff --git a/packages/nodes-base/nodes/DataTable/common/constants.ts b/packages/nodes-base/nodes/DataTable/common/constants.ts index bdb3134c94..37b45084a9 100644 --- a/packages/nodes-base/nodes/DataTable/common/constants.ts +++ b/packages/nodes-base/nodes/DataTable/common/constants.ts @@ -10,7 +10,7 @@ export type FilterType = typeof ANY_CONDITION | typeof ALL_CONDITIONS; export type FieldEntry = | { keyName: string; - condition: 'isEmpty' | 'isNotEmpty'; + condition: 'isEmpty' | 'isNotEmpty' | 'isTrue' | 'isFalse'; } | { keyName: string; diff --git a/packages/nodes-base/nodes/DataTable/common/methods.ts b/packages/nodes-base/nodes/DataTable/common/methods.ts index 792b41de4a..20d2bf47af 100644 --- a/packages/nodes-base/nodes/DataTable/common/methods.ts +++ b/packages/nodes-base/nodes/DataTable/common/methods.ts @@ -1,9 +1,9 @@ -import type { - ILoadOptionsFunctions, - INodeListSearchResult, - INodePropertyOptions, - ResourceMapperField, - ResourceMapperFields, +import { + type ILoadOptionsFunctions, + type INodeListSearchResult, + type INodePropertyOptions, + type ResourceMapperField, + type ResourceMapperFields, } from 'n8n-workflow'; import { getDataTableAggregateProxy, getDataTableProxyLoadOptions } from './utils'; @@ -80,14 +80,21 @@ export async function getConditionsForColumn(this: ILoadOptionsFunctions) { } const keyName = this.getCurrentNodeParameter('&keyName') as string; - // Base conditions available for all column types - const baseConditions: INodePropertyOptions[] = [ - { name: 'Equals', value: 'eq' }, - { name: 'Not Equals', value: 'neq' }, + const nullConditions: INodePropertyOptions[] = [ { name: 'Is Empty', value: 'isEmpty' }, { name: 'Is Not Empty', value: 'isNotEmpty' }, ]; + const equalsConditions: INodePropertyOptions[] = [ + { name: 'Equals', value: 'eq' }, + { name: 'Not Equals', value: 'neq' }, + ]; + + const booleanConditions: INodePropertyOptions[] = [ + { name: 'Is True', value: 'isTrue' }, + { name: 'Is False', value: 'isFalse' }, + ]; + const comparableConditions: INodePropertyOptions[] = [ { name: 'Greater Than', value: 'gt' }, { name: 'Greater Than or Equal', value: 'gte' }, @@ -100,7 +107,13 @@ export async function getConditionsForColumn(this: ILoadOptionsFunctions) { { name: 'Contains (Case-Insensitive)', value: 'ilike' }, ]; - const allConditions = [...baseConditions, ...comparableConditions, ...stringConditions]; + const allConditions = [ + ...nullConditions, + ...equalsConditions, + ...booleanConditions, + ...comparableConditions, + ...stringConditions, + ]; // If no column is selected yet, return all conditions if (!keyName) { @@ -113,20 +126,28 @@ export async function getConditionsForColumn(this: ILoadOptionsFunctions) { (await proxy.getColumns()).find((col) => col.name === keyName); if (!column) { - return baseConditions; + return [...equalsConditions, ...nullConditions]; } - const conditions = baseConditions; + const conditions: INodePropertyOptions[] = []; + + if (column.type === 'boolean') { + conditions.push.apply(conditions, booleanConditions); + } // String columns get LIKE operators if (column.type === 'string') { + conditions.push.apply(conditions, equalsConditions); conditions.push.apply(conditions, stringConditions); } if (['number', 'date', 'string'].includes(column.type)) { + conditions.push.apply(conditions, equalsConditions); conditions.push.apply(conditions, comparableConditions); } + conditions.push.apply(conditions, nullConditions); + return conditions; } diff --git a/packages/nodes-base/nodes/DataTable/common/selectMany.ts b/packages/nodes-base/nodes/DataTable/common/selectMany.ts index f9117706ca..05aea16358 100644 --- a/packages/nodes-base/nodes/DataTable/common/selectMany.ts +++ b/packages/nodes-base/nodes/DataTable/common/selectMany.ts @@ -83,7 +83,7 @@ export function getSelectFields( default: '', displayOptions: { hide: { - condition: ['isEmpty', 'isNotEmpty'], + condition: ['isEmpty', 'isNotEmpty', 'isTrue', 'isFalse'], }, }, }, diff --git a/packages/nodes-base/nodes/DataTable/common/utils.ts b/packages/nodes-base/nodes/DataTable/common/utils.ts index 602d2c00db..0d562d0e03 100644 --- a/packages/nodes-base/nodes/DataTable/common/utils.ts +++ b/packages/nodes-base/nodes/DataTable/common/utils.ts @@ -101,6 +101,18 @@ export function buildGetManyFilter( condition: 'neq' as const, value: null, }; + case 'isTrue': + return { + columnName: x.keyName, + condition: 'eq' as const, + value: true, + }; + case 'isFalse': + return { + columnName: x.keyName, + condition: 'eq' as const, + value: false, + }; default: return { columnName: x.keyName, diff --git a/packages/nodes-base/nodes/DataTable/test/common/utils.test.ts b/packages/nodes-base/nodes/DataTable/test/common/utils.test.ts index 38eb751126..6fe2d29655 100644 --- a/packages/nodes-base/nodes/DataTable/test/common/utils.test.ts +++ b/packages/nodes-base/nodes/DataTable/test/common/utils.test.ts @@ -203,75 +203,150 @@ describe('dataObjectToApiInput', () => { }); }); -describe('buildGetManyFilter - isEmpty/isNotEmpty translation', () => { - it('should translate isEmpty to eq with null value', () => { - const fieldEntries = [{ keyName: 'name', condition: 'isEmpty' as const, keyValue: 'ignored' }]; +describe('buildGetManyFilter', () => { + describe('isEmpty/isNotEmpty translation', () => { + it('should translate isEmpty to eq with null value', () => { + const fieldEntries = [ + { keyName: 'name', condition: 'isEmpty' as const, keyValue: 'ignored' }, + ]; - const result = buildGetManyFilter(fieldEntries, ALL_CONDITIONS); + const result = buildGetManyFilter(fieldEntries, ALL_CONDITIONS); - expect(result).toEqual({ - type: 'and', - filters: [ - { - columnName: 'name', - condition: 'eq', - value: null, - }, - ], + expect(result).toEqual({ + type: 'and', + filters: [ + { + columnName: 'name', + condition: 'eq', + value: null, + }, + ], + }); + }); + + it('should translate isNotEmpty to neq with null value', () => { + const fieldEntries = [ + { keyName: 'email', condition: 'isNotEmpty' as const, keyValue: 'ignored' }, + ]; + + const result = buildGetManyFilter(fieldEntries, ANY_CONDITION); + + expect(result).toEqual({ + type: 'or', + filters: [ + { + columnName: 'email', + condition: 'neq', + value: null, + }, + ], + }); + }); + + it('should handle mixed conditions including isEmpty/isNotEmpty', () => { + const fieldEntries = [ + { keyName: 'name', condition: 'eq' as const, keyValue: 'John' }, + { keyName: 'email', condition: 'isEmpty' as const, keyValue: 'ignored' }, + { keyName: 'phone', condition: 'isNotEmpty' as const, keyValue: 'ignored' }, + ]; + + const result = buildGetManyFilter(fieldEntries, ALL_CONDITIONS); + + expect(result).toEqual({ + type: 'and', + filters: [ + { + columnName: 'name', + condition: 'eq', + value: 'John', + }, + { + columnName: 'email', + condition: 'eq', + value: null, + }, + { + columnName: 'phone', + condition: 'neq', + value: null, + }, + ], + }); }); }); - it('should translate isNotEmpty to neq with null value', () => { - const fieldEntries = [ - { keyName: 'email', condition: 'isNotEmpty' as const, keyValue: 'ignored' }, - ]; + describe('isTrue/isFalse translation', () => { + it('should translate isTrue to eq with true value', () => { + const fieldEntries = [ + { keyName: 'isActive', condition: 'isTrue' as const, keyValue: 'ignored' }, + ]; - const result = buildGetManyFilter(fieldEntries, ANY_CONDITION); + const result = buildGetManyFilter(fieldEntries, ALL_CONDITIONS); - expect(result).toEqual({ - type: 'or', - filters: [ - { - columnName: 'email', - condition: 'neq', - value: null, - }, - ], + expect(result).toEqual({ + type: 'and', + filters: [ + { + columnName: 'isActive', + condition: 'eq', + value: true, + }, + ], + }); + }); + + it('should translate isFalse to eq with false value', () => { + const fieldEntries = [ + { keyName: 'email', condition: 'isFalse' as const, keyValue: 'ignored' }, + ]; + + const result = buildGetManyFilter(fieldEntries, ANY_CONDITION); + + expect(result).toEqual({ + type: 'or', + filters: [ + { + columnName: 'email', + condition: 'eq', + value: false, + }, + ], + }); + }); + + it('should handle mixed conditions including isTrue/isFalse', () => { + const fieldEntries = [ + { keyName: 'name', condition: 'eq' as const, keyValue: 'John' }, + { keyName: 'isActive', condition: 'isTrue' as const, keyValue: 'ignored' }, + { keyName: 'isDeleted', condition: 'isFalse' as const, keyValue: 'ignored' }, + ]; + + const result = buildGetManyFilter(fieldEntries, ALL_CONDITIONS); + + expect(result).toEqual({ + type: 'and', + filters: [ + { + columnName: 'name', + condition: 'eq', + value: 'John', + }, + { + columnName: 'isActive', + condition: 'eq', + value: true, + }, + { + columnName: 'isDeleted', + condition: 'eq', + value: false, + }, + ], + }); }); }); - it('should handle mixed conditions including isEmpty/isNotEmpty', () => { - const fieldEntries = [ - { keyName: 'name', condition: 'eq' as const, keyValue: 'John' }, - { keyName: 'email', condition: 'isEmpty' as const, keyValue: 'ignored' }, - { keyName: 'phone', condition: 'isNotEmpty' as const, keyValue: 'ignored' }, - ]; - - const result = buildGetManyFilter(fieldEntries, ALL_CONDITIONS); - - expect(result).toEqual({ - type: 'and', - filters: [ - { - columnName: 'name', - condition: 'eq', - value: 'John', - }, - { - columnName: 'email', - condition: 'eq', - value: null, - }, - { - columnName: 'phone', - condition: 'neq', - value: null, - }, - ], - }); - }); - - it('should preserve existing conditions unchanged', () => { + it('should handle other conditions', () => { const fieldEntries = [ { keyName: 'age', condition: 'gt' as const, keyValue: 18 }, { keyName: 'name', condition: 'like' as const, keyValue: '%john%' },