Refacto filter action + handle missing composite fields (#13557)

- handle missing composite fields
- refacto filter action to be more reliable

Remaining improvements:
- filter out some field types
- improve relations
This commit is contained in:
Thomas Trompette
2025-08-04 10:19:52 +02:00
committed by GitHub
parent 8d367f1ec3
commit 810fe06aa3
5 changed files with 581 additions and 181 deletions
@@ -17,9 +17,10 @@ describe('evaluateFilterConditions', () => {
operand: ViewFilterOperand,
leftOperand: unknown,
rightOperand: unknown,
type = 'TEXT',
): ResolvedFilter => ({
id: 'filter1',
type: 'text',
type: type,
label: 'Test Filter',
rightOperand,
operand,
@@ -46,46 +47,83 @@ describe('evaluateFilterConditions', () => {
});
describe('single filter operands', () => {
describe('Is operand', () => {
it('should return true when values are equal', () => {
const filter = createFilter(ViewFilterOperand.Is, 'John', 'John');
describe('Relation/UUID filter operands', () => {
it('should return true when values are equal (RELATION)', () => {
const filter = createFilter(
ViewFilterOperand.Is,
'John',
'John',
'RELATION',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
it('should return true when values are loosely equal', () => {
const filter = createFilter(ViewFilterOperand.Is, '123', 123);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
it('should return false when values are not equal', () => {
const filter = createFilter(ViewFilterOperand.Is, 'John', 'Jane');
it('should return false when values are not equal (RELATION)', () => {
const filter = createFilter(
ViewFilterOperand.Is,
'John',
'Jane',
'RELATION',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(false);
});
it('should return true when values are equal but different types', () => {
const filter = createFilter(ViewFilterOperand.Is, '123', 123);
it('should return false when values are equal (IsNot RELATION)', () => {
const filter = createFilter(
ViewFilterOperand.IsNot,
'John',
'John',
'RELATION',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(false);
});
it('should return true when values are not equal (IsNot RELATION)', () => {
const filter = createFilter(
ViewFilterOperand.IsNot,
'John',
'Jane',
'RELATION',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
});
describe('IsNot operand', () => {
it('should return false when values are equal', () => {
const filter = createFilter(ViewFilterOperand.IsNot, 'John', 'John');
describe('Boolean filter operands', () => {
it('should return true when boolean values are equal', () => {
const filter = createFilter(
ViewFilterOperand.Is,
true,
true,
'BOOLEAN',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
it('should return false when boolean values are not equal', () => {
const filter = createFilter(
ViewFilterOperand.Is,
true,
false,
'BOOLEAN',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(false);
});
it('should return true when values are not equal', () => {
const filter = createFilter(ViewFilterOperand.IsNot, 'John', 'Jane');
it('should handle truthy/falsy conversion', () => {
const filter = createFilter(ViewFilterOperand.Is, 1, true, 'BOOLEAN');
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
@@ -98,16 +136,19 @@ describe('evaluateFilterConditions', () => {
ViewFilterOperand.GreaterThanOrEqual,
25,
25,
'NUMBER',
);
const filter2 = createFilter(
ViewFilterOperand.GreaterThanOrEqual,
30,
25,
'NUMBER',
);
const filter3 = createFilter(
ViewFilterOperand.GreaterThanOrEqual,
20,
25,
'NUMBER',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
@@ -116,9 +157,24 @@ describe('evaluateFilterConditions', () => {
});
it('should handle LessThanOrEqual operand correctly', () => {
const filter1 = createFilter(ViewFilterOperand.LessThanOrEqual, 25, 25);
const filter2 = createFilter(ViewFilterOperand.LessThanOrEqual, 20, 25);
const filter3 = createFilter(ViewFilterOperand.LessThanOrEqual, 30, 25);
const filter1 = createFilter(
ViewFilterOperand.LessThanOrEqual,
25,
25,
'NUMBER',
);
const filter2 = createFilter(
ViewFilterOperand.LessThanOrEqual,
20,
25,
'NUMBER',
);
const filter3 = createFilter(
ViewFilterOperand.LessThanOrEqual,
30,
25,
'NUMBER',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
@@ -132,11 +188,13 @@ describe('evaluateFilterConditions', () => {
ViewFilterOperand.Contains,
'Hello World',
'World',
'TEXT',
);
const filter2 = createFilter(
ViewFilterOperand.Contains,
'Hello',
'World',
'TEXT',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
@@ -148,11 +206,13 @@ describe('evaluateFilterConditions', () => {
ViewFilterOperand.DoesNotContain,
'Hello World',
'World',
'TEXT',
);
const filter2 = createFilter(
ViewFilterOperand.DoesNotContain,
'Hello',
'World',
'TEXT',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(false);
@@ -164,11 +224,13 @@ describe('evaluateFilterConditions', () => {
ViewFilterOperand.Contains,
['apple', 'banana', 'cherry'],
['apple'],
'ARRAY',
);
const filter2 = createFilter(
ViewFilterOperand.Contains,
['apple', 'banana', 'cherry'],
['grape'],
'ARRAY',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
@@ -180,11 +242,13 @@ describe('evaluateFilterConditions', () => {
ViewFilterOperand.DoesNotContain,
['apple', 'banana', 'cherry'],
['apple'],
'ARRAY',
);
const filter2 = createFilter(
ViewFilterOperand.DoesNotContain,
['apple', 'banana', 'cherry'],
['grape'],
'ARRAY',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(false);
@@ -194,14 +258,30 @@ describe('evaluateFilterConditions', () => {
describe('empty operands', () => {
it('should handle IsEmpty operand correctly', () => {
const filter1 = createFilter(ViewFilterOperand.IsEmpty, null, '');
const filter2 = createFilter(ViewFilterOperand.IsEmpty, undefined, '');
const filter3 = createFilter(ViewFilterOperand.IsEmpty, '', '');
const filter4 = createFilter(ViewFilterOperand.IsEmpty, [], '');
const filter1 = createFilter(
ViewFilterOperand.IsEmpty,
null,
'',
'TEXT',
);
const filter2 = createFilter(
ViewFilterOperand.IsEmpty,
undefined,
'',
'TEXT',
);
const filter3 = createFilter(ViewFilterOperand.IsEmpty, '', '', 'TEXT');
const filter4 = createFilter(
ViewFilterOperand.IsEmpty,
[],
'',
'ARRAY',
);
const filter5 = createFilter(
ViewFilterOperand.IsEmpty,
'not empty',
'',
'TEXT',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
@@ -216,15 +296,32 @@ describe('evaluateFilterConditions', () => {
ViewFilterOperand.IsNotEmpty,
'not empty',
'',
'TEXT',
);
const filter2 = createFilter(
ViewFilterOperand.IsNotEmpty,
['item'],
'',
'ARRAY',
);
const filter3 = createFilter(
ViewFilterOperand.IsNotEmpty,
null,
'',
'TEXT',
);
const filter4 = createFilter(
ViewFilterOperand.IsNotEmpty,
'',
'',
'TEXT',
);
const filter5 = createFilter(
ViewFilterOperand.IsNotEmpty,
[],
'',
'ARRAY',
);
const filter3 = createFilter(ViewFilterOperand.IsNotEmpty, null, '');
const filter4 = createFilter(ViewFilterOperand.IsNotEmpty, '', '');
const filter5 = createFilter(ViewFilterOperand.IsNotEmpty, [], '');
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
@@ -235,21 +332,193 @@ describe('evaluateFilterConditions', () => {
});
describe('date operands', () => {
it('should handle date operands (returning false as placeholder)', () => {
const dateOperands = [
ViewFilterOperand.IsRelative,
const now = new Date();
const pastDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 1 day ago
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 1 day from now
const today = new Date();
it('should handle IsInPast operand correctly', () => {
const filter = createFilter(
ViewFilterOperand.IsInPast,
pastDate,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
const futureFilter = createFilter(
ViewFilterOperand.IsInPast,
futureDate,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [futureFilter] })).toBe(
false,
);
});
it('should handle IsInFuture operand correctly', () => {
const filter = createFilter(
ViewFilterOperand.IsInFuture,
futureDate,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
const pastFilter = createFilter(
ViewFilterOperand.IsInFuture,
pastDate,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [pastFilter] })).toBe(false);
});
it('should handle IsToday operand correctly', () => {
const filter = createFilter(
ViewFilterOperand.IsToday,
today,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
const pastFilter = createFilter(
ViewFilterOperand.IsToday,
pastDate,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [pastFilter] })).toBe(false);
});
it('should handle IsBefore operand correctly', () => {
const filter = createFilter(
ViewFilterOperand.IsBefore,
pastDate,
now,
'DATE',
);
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
const futureFilter = createFilter(
ViewFilterOperand.IsBefore,
futureDate,
now,
'DATE',
);
expect(evaluateFilterConditions({ filters: [futureFilter] })).toBe(
false,
);
});
it('should handle IsAfter operand correctly', () => {
const filter = createFilter(
ViewFilterOperand.IsAfter,
];
futureDate,
now,
'DATE',
);
dateOperands.forEach((operand) => {
const filter = createFilter(operand, new Date(), new Date());
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
expect(evaluateFilterConditions({ filters: [filter] })).toBe(false);
});
const pastFilter = createFilter(
ViewFilterOperand.IsAfter,
pastDate,
now,
'DATE',
);
expect(evaluateFilterConditions({ filters: [pastFilter] })).toBe(false);
});
it('should handle Is operand for dates correctly', () => {
const sameDate1 = new Date('2023-01-15');
const sameDate2 = new Date('2023-01-15');
const filter = createFilter(
ViewFilterOperand.Is,
sameDate1,
sameDate2,
'DATE',
);
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
const differentFilter = createFilter(
ViewFilterOperand.Is,
sameDate1,
now,
'DATE',
);
expect(evaluateFilterConditions({ filters: [differentFilter] })).toBe(
false,
);
});
it('should handle date IsEmpty and IsNotEmpty operands', () => {
const emptyFilter = createFilter(
ViewFilterOperand.IsEmpty,
null,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [emptyFilter] })).toBe(true);
const notEmptyFilter = createFilter(
ViewFilterOperand.IsNotEmpty,
now,
null,
'DATE',
);
expect(evaluateFilterConditions({ filters: [notEmptyFilter] })).toBe(
true,
);
});
});
describe('currency operands', () => {
it('should handle currency code operands', () => {
const filter: ResolvedFilter = {
id: 'filter1',
type: 'CURRENCY',
label: 'Currency Filter',
rightOperand: 'USD',
operand: ViewFilterOperand.Is,
displayValue: 'USD',
stepFilterGroupId: 'group1',
leftOperand: 'USD',
compositeFieldSubFieldName: 'currencyCode',
};
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
});
it('should handle currency amount operands', () => {
const filter: ResolvedFilter = {
id: 'filter1',
type: 'CURRENCY',
label: 'Currency Filter',
rightOperand: 100,
operand: ViewFilterOperand.GreaterThanOrEqual,
displayValue: '100',
stepFilterGroupId: 'group1',
leftOperand: 150,
compositeFieldSubFieldName: 'amountMicros',
};
expect(evaluateFilterConditions({ filters: [filter] })).toBe(true);
});
});
@@ -259,10 +528,24 @@ describe('evaluateFilterConditions', () => {
'unknown' as ViewFilterOperand,
'value',
'value',
'TEXT',
);
expect(() => evaluateFilterConditions({ filters: [filter] })).toThrow();
});
it('should throw error for unsupported filter type', () => {
const filter = createFilter(
ViewFilterOperand.Contains,
'value',
'value',
'UNSUPPORTED_TYPE',
);
expect(() => evaluateFilterConditions({ filters: [filter] })).toThrow(
'Filter type UNSUPPORTED_TYPE not supported',
);
});
});
});
@@ -271,7 +554,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -281,7 +564,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'number',
type: 'NUMBER',
label: 'Age Filter',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -300,7 +583,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -310,7 +593,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'number',
type: 'NUMBER',
label: 'Age Filter',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -339,7 +622,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -349,7 +632,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'number',
type: 'NUMBER',
label: 'Age Filter',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -375,7 +658,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -385,7 +668,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'number',
type: 'NUMBER',
label: 'Age Filter',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -413,7 +696,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -423,7 +706,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'number',
type: 'NUMBER',
label: 'Age Filter',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -449,7 +732,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -459,7 +742,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'number',
type: 'NUMBER',
label: 'Age Filter',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -491,7 +774,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -501,7 +784,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'number',
type: 'NUMBER',
label: 'Age Filter',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -541,7 +824,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Filter 1',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -551,7 +834,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter2',
type: 'text',
type: 'RELATION',
label: 'Filter 2',
rightOperand: 'Smith',
operand: ViewFilterOperand.Is,
@@ -561,7 +844,7 @@ describe('evaluateFilterConditions', () => {
},
{
id: 'filter3',
type: 'number',
type: 'NUMBER',
label: 'Filter 3',
rightOperand: 25,
operand: ViewFilterOperand.GreaterThanOrEqual,
@@ -604,7 +887,7 @@ describe('evaluateFilterConditions', () => {
const filters: ResolvedFilter[] = [
{
id: 'filter1',
type: 'text',
type: 'RELATION',
label: 'Name Filter',
rightOperand: 'John',
operand: ViewFilterOperand.Is,
@@ -1,9 +1,9 @@
import { isString } from '@sniptt/guards';
import {
StepFilter,
StepFilterGroup,
ViewFilterOperand,
} from 'twenty-shared/types';
import { assertUnreachable } from 'twenty-shared/utils';
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
rightOperand: unknown;
@@ -11,129 +11,33 @@ type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
};
function evaluateFilter(filter: ResolvedFilter): boolean {
const leftValue = filter.leftOperand;
const rightValue = filter.rightOperand;
switch (filter.operand) {
case ViewFilterOperand.Is:
switch (typeof leftValue) {
case 'string':
return (
String(leftValue).toLowerCase() === String(rightValue).toLowerCase()
);
case 'boolean':
return Boolean(leftValue) === Boolean(rightValue);
default:
return leftValue === rightValue;
}
case ViewFilterOperand.IsNot:
return String(leftValue) !== String(rightValue);
case ViewFilterOperand.GreaterThanOrEqual:
return Number(leftValue) >= Number(rightValue);
case ViewFilterOperand.LessThanOrEqual:
return Number(leftValue) <= Number(rightValue);
case ViewFilterOperand.Contains:
if (Array.isArray(leftValue)) {
try {
const parsedRightValue = Array.isArray(rightValue)
? rightValue
: JSON.parse(rightValue as string);
if (Array.isArray(parsedRightValue)) {
return parsedRightValue.every((item) => leftValue.includes(item));
} else {
return leftValue.includes(parsedRightValue);
}
} catch (error) {
return leftValue.includes(rightValue);
}
}
return String(leftValue).includes(String(rightValue));
case ViewFilterOperand.DoesNotContain:
if (Array.isArray(leftValue)) {
try {
const parsedRightValue = Array.isArray(rightValue)
? rightValue
: JSON.parse(rightValue as string);
if (Array.isArray(parsedRightValue)) {
return !parsedRightValue.every((item) => leftValue.includes(item));
} else {
return !leftValue.includes(parsedRightValue);
}
} catch (error) {
return !leftValue.includes(rightValue);
}
}
return !String(leftValue).includes(String(rightValue));
case ViewFilterOperand.IsEmpty:
return (
leftValue === null ||
leftValue === undefined ||
leftValue === '' ||
(Array.isArray(leftValue) && leftValue.length === 0)
);
case ViewFilterOperand.IsNotEmpty:
return (
leftValue !== null &&
leftValue !== undefined &&
leftValue !== '' &&
(!Array.isArray(leftValue) || leftValue.length > 0)
);
case ViewFilterOperand.IsNotNull:
return leftValue !== null && leftValue !== undefined;
case ViewFilterOperand.IsInPast:
if (typeof leftValue === 'string') {
return Date.now() - new Date(leftValue).getTime() > 0;
}
return false;
case ViewFilterOperand.IsInFuture:
if (typeof leftValue === 'string') {
return Date.now() - new Date(leftValue).getTime() < 0;
}
return false;
case ViewFilterOperand.IsToday:
if (typeof leftValue === 'string') {
return new Date(leftValue).toDateString() === new Date().toDateString();
}
return false;
case ViewFilterOperand.IsBefore:
if (typeof leftValue === 'string' && typeof rightValue === 'string') {
return new Date(leftValue).getTime() < new Date(rightValue).getTime();
}
return false;
case ViewFilterOperand.IsAfter:
if (typeof leftValue === 'string' && typeof rightValue === 'string') {
return new Date(leftValue).getTime() > new Date(rightValue).getTime();
}
return false;
case ViewFilterOperand.VectorSearch:
case ViewFilterOperand.IsRelative:
return false;
switch (filter.type) {
case 'NUMBER':
case 'NUMERIC':
return evaluateNumberFilter(filter);
case 'DATE':
case 'DATE_TIME':
return evaluateDateFilter(filter);
case 'TEXT':
case 'SELECT':
case 'MULTI_SELECT':
case 'FULL_NAME':
case 'EMAILS':
case 'PHONES':
case 'ADDRESS':
case 'LINKS':
case 'ARRAY':
case 'RAW_JSON':
return evaluateTextAndArrayFilter(filter);
case 'BOOLEAN':
return evaluateBooleanFilter(filter);
case 'UUID':
case 'RELATION':
return evaluateRelationFilter(filter);
case 'CURRENCY':
return evaluateCurrencyFilter(filter);
default:
assertUnreachable(filter.operand);
throw new Error(`Filter type ${filter.type} not supported`);
}
}
@@ -181,6 +85,180 @@ function evaluateFilterGroup(
}
}
function contains(leftValue: unknown, rightValue: unknown): boolean {
// if two arrays, check if any item is in the other array
if (Array.isArray(leftValue) && Array.isArray(rightValue)) {
return leftValue.some((item) => rightValue.includes(item));
}
if (
(Array.isArray(leftValue) || isString(leftValue)) &&
isString(rightValue)
) {
try {
const parsedRightValue = JSON.parse(rightValue as string);
if (Array.isArray(parsedRightValue)) {
return parsedRightValue.some((item) => leftValue.includes(item));
} else {
return leftValue.includes(parsedRightValue);
}
} catch (error) {
return leftValue.includes(rightValue);
}
}
return String(leftValue).includes(String(rightValue));
}
function evaluateTextAndArrayFilter(filter: ResolvedFilter): boolean {
switch (filter.operand) {
case ViewFilterOperand.Contains:
return contains(filter.leftOperand, filter.rightOperand);
case ViewFilterOperand.DoesNotContain:
return !contains(filter.leftOperand, filter.rightOperand);
case ViewFilterOperand.IsEmpty:
return (
filter.leftOperand === null ||
filter.leftOperand === undefined ||
filter.leftOperand === '' ||
(Array.isArray(filter.leftOperand) && filter.leftOperand.length === 0)
);
case ViewFilterOperand.IsNotEmpty:
return (
filter.leftOperand !== null &&
filter.leftOperand !== undefined &&
filter.leftOperand !== '' &&
(!Array.isArray(filter.leftOperand) || filter.leftOperand.length > 0)
);
default:
throw new Error(
`Operand ${filter.operand} not supported for this filter type`,
);
}
}
function evaluateBooleanFilter(filter: ResolvedFilter): boolean {
switch (filter.operand) {
case ViewFilterOperand.Is:
return Boolean(filter.leftOperand) === Boolean(filter.rightOperand);
default:
throw new Error(
`Operand ${filter.operand} not supported for boolean filter`,
);
}
}
function evaluateDateFilter(filter: ResolvedFilter): boolean {
const dateLeftValue = new Date(String(filter.leftOperand));
const dateRightValue = new Date(String(filter.rightOperand));
switch (filter.operand) {
case ViewFilterOperand.Is:
return dateLeftValue.getDate() === dateRightValue.getDate();
case ViewFilterOperand.IsInPast:
return dateLeftValue.getTime() < Date.now();
case ViewFilterOperand.IsInFuture:
return dateLeftValue.getTime() > Date.now();
case ViewFilterOperand.IsToday:
return dateLeftValue.toDateString() === new Date().toDateString();
case ViewFilterOperand.IsBefore:
return dateLeftValue.getTime() < dateRightValue.getTime();
case ViewFilterOperand.IsAfter:
return dateLeftValue.getTime() > dateRightValue.getTime();
case ViewFilterOperand.IsEmpty:
return (
filter.leftOperand === null ||
filter.leftOperand === undefined ||
filter.leftOperand === ''
);
case ViewFilterOperand.IsNotEmpty:
return (
filter.leftOperand !== null &&
filter.leftOperand !== undefined &&
filter.leftOperand !== ''
);
default:
throw new Error(
`Operand ${filter.operand} not supported for date filter`,
);
}
}
function evaluateRelationFilter(filter: ResolvedFilter): boolean {
switch (filter.operand) {
case ViewFilterOperand.Is:
return filter.leftOperand === filter.rightOperand;
case ViewFilterOperand.IsNot:
return filter.leftOperand !== filter.rightOperand;
default:
throw new Error(
`Operand ${filter.operand} not supported for relation filter`,
);
}
}
function evaluateCurrencyFilter(filter: ResolvedFilter): boolean {
if (filter.compositeFieldSubFieldName === 'currencyCode') {
switch (filter.operand) {
case ViewFilterOperand.Is:
return filter.leftOperand === filter.rightOperand;
case ViewFilterOperand.IsNot:
return filter.leftOperand !== filter.rightOperand;
case ViewFilterOperand.IsEmpty:
return (
filter.leftOperand === null ||
filter.leftOperand === undefined ||
filter.leftOperand === ''
);
case ViewFilterOperand.IsNotEmpty:
return (
filter.leftOperand !== null &&
filter.leftOperand !== undefined &&
filter.leftOperand !== ''
);
default:
throw new Error(
`Operand ${filter.operand} not supported for currency filter`,
);
}
} else {
return evaluateNumberFilter(filter);
}
}
function evaluateNumberFilter(filter: ResolvedFilter): boolean {
const leftValue = filter.leftOperand;
const rightValue = filter.rightOperand;
switch (filter.operand) {
case ViewFilterOperand.GreaterThanOrEqual:
return Number(leftValue) >= Number(rightValue);
case ViewFilterOperand.LessThanOrEqual:
return Number(leftValue) <= Number(rightValue);
case ViewFilterOperand.IsEmpty:
return leftValue === null || leftValue === undefined || leftValue === '';
case ViewFilterOperand.IsNotEmpty:
return leftValue !== null && leftValue !== undefined && leftValue !== '';
default:
throw new Error(
`Operand ${filter.operand} not supported for number filter`,
);
}
}
export function evaluateFilterConditions({
filterGroups = [],
filters = [],