Fix filters + add behaviour for select (#13997)

On field selection, we need to retrieve the fieldMetadata to set the
filter type. Current code was using the current step filter
fieldMetdataId instead of the new one.

Also adding a special behaviour for Select filter: it behaves as
arrays/multiselect but the opearands are not the same.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas Trompette
2025-08-20 17:14:31 +02:00
committed by GitHub
parent f9abea2387
commit 4b66e8021a
17 changed files with 287 additions and 146 deletions
@@ -1,24 +1,12 @@
import { CustomError } from '@/error-handler/CustomError';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getFieldMetadataItemById } from '@/object-metadata/utils/getFieldMetadataItemById';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
const FIELD_METADATA_ITEM_NOT_FOUND_ERROR_CODE =
'FIELD_METADATA_ITEM_NOT_FOUND';
export const useFieldMetadataItemById = (fieldMetadataId: string) => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const fieldMetadataItem = objectMetadataItems
.flatMap((objectMetadataItem) => objectMetadataItem.fields)
.find((field) => field.id === fieldMetadataId);
if (!isDefined(fieldMetadataItem)) {
throw new CustomError(
`Field metadata item not found for id ${fieldMetadataId}`,
FIELD_METADATA_ITEM_NOT_FOUND_ERROR_CODE,
);
}
return { fieldMetadataItem };
return getFieldMetadataItemById({
fieldMetadataId,
objectMetadataItems,
});
};
@@ -0,0 +1,12 @@
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getFieldMetadataItemByIdOrThrow } from '@/object-metadata/utils/getFieldMetadataItemByIdOrThrow';
import { useRecoilValue } from 'recoil';
export const useFieldMetadataItemByIdOrThrow = (fieldMetadataId: string) => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
return getFieldMetadataItemByIdOrThrow({
fieldMetadataId,
objectMetadataItems,
});
};
@@ -1,23 +1,29 @@
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getFieldMetadataItemByIdOrThrow } from '@/object-metadata/utils/getFieldMetadataItemByIdOrThrow';
import { useRecoilCallback } from 'recoil';
export const useGetFieldMetadataItemById = () => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const getFieldMetadataItemById = (fieldMetadataId: string) => {
const fieldMetadataItem = objectMetadataItems
.flatMap((objectMetadataItem) => objectMetadataItem.fields)
.find((field) => field.id === fieldMetadataId);
if (!isDefined(fieldMetadataItem)) {
throw new Error(
`Field metadata item not found for id ${fieldMetadataId}`,
);
}
return fieldMetadataItem;
};
return { getFieldMetadataItemById };
type GetFieldMetadataItemByIdOrThrowResult = {
fieldMetadataItem: FieldMetadataItem;
objectMetadataItem: ObjectMetadataItem;
};
export const useGetFieldMetadataItemByIdOrThrow = () => {
const getFieldMetadataItemById = useRecoilCallback(
({ snapshot }) =>
(fieldMetadataId: string): GetFieldMetadataItemByIdOrThrowResult => {
const objectMetadataItems = snapshot
.getLoadable(objectMetadataItemsState)
.getValue();
return getFieldMetadataItemByIdOrThrow({
fieldMetadataId,
objectMetadataItems,
});
},
[],
);
return { getFieldMetadataItemByIdOrThrow: getFieldMetadataItemById };
};
@@ -0,0 +1,24 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getFieldMetadataItemByIdOrThrow } from '@/object-metadata/utils/getFieldMetadataItemByIdOrThrow';
type GetFieldMetadataItemByIdParams = {
fieldMetadataId: string;
objectMetadataItems: ObjectMetadataItem[];
};
export const getFieldMetadataItemById = ({
fieldMetadataId,
objectMetadataItems,
}: GetFieldMetadataItemByIdParams) => {
try {
return getFieldMetadataItemByIdOrThrow({
fieldMetadataId,
objectMetadataItems,
});
} catch {
return {
fieldMetadataItem: undefined,
objectMetadataItem: undefined,
};
}
};
@@ -0,0 +1,43 @@
import { CustomError } from '@/error-handler/CustomError';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isDefined } from 'twenty-shared/utils';
type GetFieldMetadataItemByIdParams = {
fieldMetadataId: string;
objectMetadataItems: ObjectMetadataItem[];
};
const FIELD_METADATA_ITEM_NOT_FOUND_ERROR_CODE =
'FIELD_METADATA_ITEM_NOT_FOUND';
export const getFieldMetadataItemByIdOrThrow = ({
fieldMetadataId,
objectMetadataItems,
}: GetFieldMetadataItemByIdParams) => {
const objectMetadataItem = objectMetadataItems.find((objectMetadataItem) =>
objectMetadataItem.fields.some((field) => field.id === fieldMetadataId),
);
if (!isDefined(objectMetadataItem)) {
throw new CustomError(
`Object metadata item not found for field id ${fieldMetadataId}`,
FIELD_METADATA_ITEM_NOT_FOUND_ERROR_CODE,
);
}
const fieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === fieldMetadataId,
);
if (!isDefined(fieldMetadataItem)) {
throw new CustomError(
`Field metadata item not found for field id ${fieldMetadataId}`,
FIELD_METADATA_ITEM_NOT_FOUND_ERROR_CODE,
);
}
return {
fieldMetadataItem,
objectMetadataItem,
};
};
@@ -1,4 +1,3 @@
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { getAdvancedFilterInputPlaceholderText } from '@/object-record/advanced-filter/utils/getAdvancedFilterInputPlacedholderText';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SelectControl } from '@/ui/input/components/SelectControl';
@@ -8,6 +7,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import styled from '@emotion/styled';
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { isDefined } from 'twenty-shared/utils';
// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752
@@ -48,11 +48,9 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
const shouldUsePlaceholder = !isNonEmptyString(recordFilter?.value);
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
const fieldMetadataItem = isNonEmptyString(recordFilter?.fieldMetadataId)
? getFieldMetadataItemById(recordFilter?.fieldMetadataId)
: undefined;
const { fieldMetadataItem } = useFieldMetadataItemById(
recordFilter?.fieldMetadataId ?? '',
);
const placeholderText = isDefined(fieldMetadataItem)
? getAdvancedFilterInputPlaceholderText(fieldMetadataItem)
@@ -1,4 +1,4 @@
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
@@ -17,11 +17,9 @@ export const useRecordFilterField = (recordFilterId: string) => {
(recordFilter) => recordFilter.id === recordFilterId,
);
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
const fieldMetadataItem = isNonEmptyString(recordFilter?.fieldMetadataId)
? getFieldMetadataItemById(recordFilter?.fieldMetadataId)
: undefined;
const { fieldMetadataItem } = useFieldMetadataItemById(
recordFilter?.fieldMetadataId ?? '',
);
const { getIcon } = useIcons();
@@ -1,4 +1,4 @@
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
@@ -45,7 +45,8 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
const { getFieldMetadataItemByIdOrThrow } =
useGetFieldMetadataItemByIdOrThrow();
const setSubFieldNameUsedInDropdown = useSetRecoilComponentState(
subFieldNameUsedInDropdownComponentState,
@@ -64,7 +65,8 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
}: SelectFilterParams) => {
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId);
const fieldMetadataItem = getFieldMetadataItemById(fieldMetadataItemId);
const { fieldMetadataItem } =
getFieldMetadataItemByIdOrThrow(fieldMetadataItemId);
if (!isDefined(fieldMetadataItem)) {
return;
@@ -1,4 +1,4 @@
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { useFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useFieldMetadataItemByIdOrThrow';
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
@@ -21,7 +21,7 @@ export const EditableFilterChip = ({
}: EditableFilterChipProps) => {
const { getIcon } = useIcons();
const { fieldMetadataItem } = useFieldMetadataItemById(
const { fieldMetadataItem } = useFieldMetadataItemByIdOrThrow(
recordFilter.fieldMetadataId,
);
@@ -1,4 +1,4 @@
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { useFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useFieldMetadataItemByIdOrThrow';
import { useRemoveRecordSort } from '@/object-record/record-sort/hooks/useRemoveRecordSort';
import { useUpsertRecordSort } from '@/object-record/record-sort/hooks/useUpsertRecordSort';
import { type RecordSort } from '@/object-record/record-sort/types/RecordSort';
@@ -18,7 +18,7 @@ export const EditableSortChip = ({ recordSort }: EditableSortChipProps) => {
removeRecordSort(recordSort.fieldMetadataId);
};
const { fieldMetadataItem } = useFieldMetadataItemById(
const { fieldMetadataItem } = useFieldMetadataItemByIdOrThrow(
recordSort.fieldMetadataId,
);
@@ -1,6 +1,6 @@
import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd';
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { useObjectOptionsForTable } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForTable';
@@ -32,7 +32,8 @@ export const ViewFieldsVisibleDropdownSection = () => {
viewBarId: recordIndexId,
});
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
const { getFieldMetadataItemByIdOrThrow } =
useGetFieldMetadataItemByIdOrThrow();
const handleReorderFields =
viewType === ViewType.Kanban
@@ -93,7 +94,7 @@ export const ViewFieldsVisibleDropdownSection = () => {
index +
(isDefined(fieldMetadataItemLabelIdentifier) ? 1 : 0);
const fieldMetadataItem = getFieldMetadataItemById(
const { fieldMetadataItem } = getFieldMetadataItemByIdOrThrow(
recordField.fieldMetadataItemId,
);
@@ -1,8 +1,9 @@
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useWorkflowVersionIdOrThrow } from '@/workflow/hooks/useWorkflowVersionIdOrThrow';
import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector';
import { useFilterFieldMetadataItem } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterFieldMetadataItem';
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { getViewFilterOperands } from '@/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands';
@@ -25,6 +26,8 @@ type WorkflowStepFilterFieldSelectProps = {
const NON_SELECTABLE_FIELD_TYPES = [
FieldMetadataType.ACTOR,
FieldMetadataType.RICH_TEXT_V2,
FieldMetadataType.ARRAY,
FieldMetadataType.RATING,
];
export const WorkflowStepFilterFieldSelect = ({
@@ -53,10 +56,11 @@ export const WorkflowStepFilterFieldSelect = ({
const { getIcon } = useIcons();
const {
fieldMetadataItem: filterFieldMetadataItem,
objectMetadataItem: filterObjectMetadataItem,
} = useFilterFieldMetadataItem(stepFilter.fieldMetadataId ?? '');
const { fieldMetadataItem: filterFieldMetadataItem } =
useFieldMetadataItemById(stepFilter.fieldMetadataId ?? '');
const { getFieldMetadataItemByIdOrThrow } =
useGetFieldMetadataItemByIdOrThrow();
const availableVariablesInWorkflowStep = useAvailableVariablesInWorkflowStep({
shouldDisplayRecordFields,
@@ -92,6 +96,13 @@ export const WorkflowStepFilterFieldSelect = ({
isFullRecord: false,
});
const {
fieldMetadataItem: filterFieldMetadataItem,
objectMetadataItem: filterObjectMetadataItem,
} = isDefined(fieldMetadataId)
? getFieldMetadataItemByIdOrThrow(fieldMetadataId)
: { fieldMetadataItem: undefined, objectMetadataItem: undefined };
const filterType = isDefined(fieldMetadataId)
? (filterFieldMetadataItem?.type ?? 'unknown')
: variableType;
@@ -119,11 +130,10 @@ export const WorkflowStepFilterFieldSelect = ({
});
},
[
workflowVersionId,
getFieldMetadataItemByIdOrThrow,
upsertStepFilterSettings,
stepFilter,
workflowVersionId,
filterFieldMetadataItem,
filterObjectMetadataItem,
],
);
@@ -185,25 +195,27 @@ export const WorkflowStepFilterFieldSelect = ({
}
return (
<WorkflowVariablesDropdown
instanceId={dropdownId}
onVariableSelect={handleChange}
clickableComponent={
<SelectControl
selectedOption={{
value: stepFilter.stepOutputKey,
label: label,
Icon: filterFieldMetadataItem?.icon
? getIcon(filterFieldMetadataItem.icon)
: undefined,
}}
textAccent={isSelectedFieldNotFound ? 'placeholder' : 'default'}
/>
}
shouldDisplayRecordFields={shouldDisplayRecordFields}
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
shouldEnableSelectRelationObject={true}
fieldTypesToExclude={NON_SELECTABLE_FIELD_TYPES}
/>
<>
<WorkflowVariablesDropdown
instanceId={dropdownId}
onVariableSelect={handleChange}
clickableComponent={
<SelectControl
selectedOption={{
value: stepFilter.stepOutputKey,
label: label,
Icon: filterFieldMetadataItem?.icon
? getIcon(filterFieldMetadataItem.icon)
: undefined,
}}
textAccent={isSelectedFieldNotFound ? 'placeholder' : 'default'}
/>
}
shouldDisplayRecordFields={shouldDisplayRecordFields}
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
shouldEnableSelectRelationObject={true}
fieldTypesToExclude={NON_SELECTABLE_FIELD_TYPES}
/>
</>
);
};
@@ -1,3 +1,4 @@
import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { FormFieldInput } from '@/object-record/record-field/ui/components/FormFieldInput';
import { FormMultiSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormMultiSelectFieldInput';
@@ -7,7 +8,6 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { WorkflowStepFilterValueCompositeInput } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueCompositeInput';
import { useFilterFieldMetadataItem } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterFieldMetadataItem';
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
@@ -91,7 +91,7 @@ export const WorkflowStepFilterValueInput = ({
} = stepFilter;
const { fieldMetadataItem: selectedFieldMetadataItem, objectMetadataItem } =
useFilterFieldMetadataItem(fieldMetadataId ?? '');
useFieldMetadataItemById(fieldMetadataId ?? '');
if (isDisabled || operandHasNoInput) {
return null;
@@ -1,18 +0,0 @@
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
export const useFilterFieldMetadataItem = (fieldMetadataId: string) => {
const { objectMetadataItems } = useObjectMetadataItems();
const objectMetadataItem = objectMetadataItems.find((objectMetadataItem) =>
objectMetadataItem.fields.some((field) => field.id === fieldMetadataId),
);
const fieldMetadataItem = objectMetadataItem?.fields.find(
(field) => field.id === fieldMetadataId,
);
return {
objectMetadataItem,
fieldMetadataItem,
};
};
@@ -314,6 +314,80 @@ describe('evaluateFilterConditions', () => {
});
});
describe('Select filter operands', () => {
it('should return true when there are common values (SELECT)', () => {
const filter = createFilter(
ViewFilterOperand.Is,
['John'],
['John', 'Jane'],
'SELECT',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
it('should return false when there are no common values (SELECT)', () => {
const filter = createFilter(
ViewFilterOperand.Is,
['John'],
['Jane'],
'SELECT',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(false);
});
it('should return true when there are no common values (IsNot)', () => {
const filter = createFilter(
ViewFilterOperand.IsNot,
['John'],
['Jane'],
'SELECT',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
it('should return true when there are no values (IsEmpty)', () => {
const filter = createFilter(
ViewFilterOperand.IsEmpty,
[],
'',
'SELECT',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
it('should return false when there is a value (IsEmpty)', () => {
const filter = createFilter(
ViewFilterOperand.IsEmpty,
['John'],
'',
'SELECT',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(false);
});
it('should return true when there are values (IsNotEmpty)', () => {
const filter = createFilter(
ViewFilterOperand.IsNotEmpty,
['John'],
'',
'SELECT',
);
const result = evaluateFilterConditions({ filters: [filter] });
expect(result).toBe(true);
});
});
describe('Boolean filter operands', () => {
it('should return true when boolean values are equal', () => {
const filter = createFilter(
@@ -1,4 +1,9 @@
import { isObject, isString } from '@sniptt/guards';
import {
isNonEmptyArray,
isNonEmptyString,
isObject,
isString,
} from '@sniptt/guards';
import {
type StepFilter,
type StepFilterGroup,
@@ -21,7 +26,6 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
case 'DATE_TIME':
return evaluateDateFilter(filter);
case 'TEXT':
case 'SELECT':
case 'MULTI_SELECT':
case 'FULL_NAME':
case 'EMAILS':
@@ -31,6 +35,8 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
case 'ARRAY':
case 'RAW_JSON':
return evaluateTextAndArrayFilter(filter);
case 'SELECT':
return evaluateSelectFilter(filter);
case 'BOOLEAN':
return evaluateBooleanFilter(filter);
case 'UUID':
@@ -121,20 +127,11 @@ function evaluateTextAndArrayFilter(filter: ResolvedFilter): boolean {
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)
);
return !isNotEmptyTextOrArray(filter.leftOperand);
case ViewFilterOperand.IsNotEmpty:
return (
filter.leftOperand !== null &&
filter.leftOperand !== undefined &&
filter.leftOperand !== '' &&
(!Array.isArray(filter.leftOperand) || filter.leftOperand.length > 0)
);
return isNotEmptyTextOrArray(filter.leftOperand);
default:
throw new Error(
`Operand ${filter.operand} not supported for this filter type`,
@@ -142,6 +139,10 @@ function evaluateTextAndArrayFilter(filter: ResolvedFilter): boolean {
}
}
function isNotEmptyTextOrArray(value: unknown): boolean {
return isNonEmptyString(value) || isNonEmptyArray(value);
}
function evaluateBooleanFilter(filter: ResolvedFilter): boolean {
switch (filter.operand) {
case ViewFilterOperand.Is:
@@ -255,17 +256,9 @@ function evaluateCurrencyFilter(filter: ResolvedFilter): boolean {
case ViewFilterOperand.IsNot:
return filter.leftOperand !== filter.rightOperand;
case ViewFilterOperand.IsEmpty:
return (
filter.leftOperand === null ||
filter.leftOperand === undefined ||
filter.leftOperand === ''
);
return !isNonEmptyString(filter.leftOperand);
case ViewFilterOperand.IsNotEmpty:
return (
filter.leftOperand !== null &&
filter.leftOperand !== undefined &&
filter.leftOperand !== ''
);
return isNonEmptyString(filter.leftOperand);
default:
throw new Error(
`Operand ${filter.operand} not supported for currency filter`,
@@ -288,10 +281,10 @@ function evaluateNumberFilter(filter: ResolvedFilter): boolean {
return Number(leftValue) <= Number(rightValue);
case ViewFilterOperand.IsEmpty:
return leftValue === null || leftValue === undefined || leftValue === '';
return !isNonEmptyString(leftValue);
case ViewFilterOperand.IsNotEmpty:
return leftValue !== null && leftValue !== undefined && leftValue !== '';
return isNonEmptyString(leftValue);
default:
throw new Error(
@@ -310,19 +303,9 @@ function evaluateDefaultFilter(filter: ResolvedFilter): boolean {
case ViewFilterOperand.IsNot:
return leftValue != rightValue;
case ViewFilterOperand.IsEmpty:
return (
leftValue === null ||
leftValue === undefined ||
leftValue === '' ||
(Array.isArray(leftValue) && leftValue.length === 0)
);
return !isNotEmptyTextOrArray(leftValue);
case ViewFilterOperand.IsNotEmpty:
return (
leftValue !== null &&
leftValue !== undefined &&
leftValue !== '' &&
(!Array.isArray(leftValue) || leftValue.length > 0)
);
return isNotEmptyTextOrArray(leftValue);
case ViewFilterOperand.Contains:
return contains(leftValue, rightValue);
case ViewFilterOperand.DoesNotContain:
@@ -338,6 +321,24 @@ function evaluateDefaultFilter(filter: ResolvedFilter): boolean {
}
}
function evaluateSelectFilter(filter: ResolvedFilter): boolean {
switch (filter.operand) {
case ViewFilterOperand.Is:
return contains(filter.leftOperand, filter.rightOperand);
case ViewFilterOperand.IsNot:
return !contains(filter.leftOperand, filter.rightOperand);
case ViewFilterOperand.IsEmpty:
return !isNotEmptyTextOrArray(filter.leftOperand);
case ViewFilterOperand.IsNotEmpty:
return isNotEmptyTextOrArray(filter.leftOperand);
default:
throw new Error(
`Operand ${filter.operand} not supported for select filter`,
);
}
}
export function evaluateFilterConditions({
filterGroups = [],
filters = [],
+1 -1
View File
@@ -170,4 +170,4 @@
]
}
}
}
}