feat(Data Table Node): Show 'Is False' / 'Is True' condition on boolean columns (no-changelog) (#19478)

This commit is contained in:
Jaakko Husso
2025-09-12 17:54:54 +03:00
committed by GitHub
parent 47f1d14b66
commit 1a5fd5a238
5 changed files with 182 additions and 74 deletions

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -83,7 +83,7 @@ export function getSelectFields(
default: '',
displayOptions: {
hide: {
condition: ['isEmpty', 'isNotEmpty'],
condition: ['isEmpty', 'isNotEmpty', 'isTrue', 'isFalse'],
},
},
},

View File

@@ -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,

View File

@@ -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%' },