diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts
index 0ba17a0187..47297467c2 100644
--- a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts
+++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts
@@ -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,
+ });
};
diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemByIdOrThrow.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemByIdOrThrow.ts
new file mode 100644
index 0000000000..247a4fdd52
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemByIdOrThrow.ts
@@ -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,
+ });
+};
diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts
index 8e9ad3ccd9..8debd87c5e 100644
--- a/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts
+++ b/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts
@@ -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 };
};
diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getFieldMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/utils/getFieldMetadataItemById.ts
new file mode 100644
index 0000000000..5a03e3d19e
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/utils/getFieldMetadataItemById.ts
@@ -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,
+ };
+ }
+};
diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getFieldMetadataItemByIdOrThrow.ts b/packages/twenty-front/src/modules/object-metadata/utils/getFieldMetadataItemByIdOrThrow.ts
new file mode 100644
index 0000000000..e35226ea12
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/utils/getFieldMetadataItemByIdOrThrow.ts
@@ -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,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx
index 8a49b2b5b3..f4c4a7b6a6 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx
@@ -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)
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts
index 8c9587c32a..4c05056f63 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useRecordFilterField.ts
@@ -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();
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts
index df3018afd9..6581c1e507 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown.ts
@@ -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;
diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx
index 5967b97987..2d832c8456 100644
--- a/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx
+++ b/packages/twenty-front/src/modules/views/components/EditableFilterChip.tsx
@@ -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,
);
diff --git a/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx b/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx
index b802af0735..6eb6701b77 100644
--- a/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx
+++ b/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx
@@ -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,
);
diff --git a/packages/twenty-front/src/modules/views/components/ViewFieldsVisibleDropdownSection.tsx b/packages/twenty-front/src/modules/views/components/ViewFieldsVisibleDropdownSection.tsx
index e1caf1560d..411b2bf4a3 100644
--- a/packages/twenty-front/src/modules/views/components/ViewFieldsVisibleDropdownSection.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewFieldsVisibleDropdownSection.tsx
@@ -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,
);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx
index 9e300880e9..8f1b2e7078 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx
@@ -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 (
-
- }
- shouldDisplayRecordFields={shouldDisplayRecordFields}
- shouldDisplayRecordObjects={shouldDisplayRecordObjects}
- shouldEnableSelectRelationObject={true}
- fieldTypesToExclude={NON_SELECTABLE_FIELD_TYPES}
- />
+ <>
+
+ }
+ shouldDisplayRecordFields={shouldDisplayRecordFields}
+ shouldDisplayRecordObjects={shouldDisplayRecordObjects}
+ shouldEnableSelectRelationObject={true}
+ fieldTypesToExclude={NON_SELECTABLE_FIELD_TYPES}
+ />
+ >
);
};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx
index c9a3dbe16f..01666b84e9 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx
@@ -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;
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterFieldMetadataItem.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterFieldMetadataItem.ts
deleted file mode 100644
index 8f3204ea22..0000000000
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterFieldMetadataItem.ts
+++ /dev/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,
- };
-};
diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts
index dfb08da4d4..779b9fc9b0 100644
--- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts
+++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts
@@ -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(
diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts
index 8638372bd2..a8ca62c036 100644
--- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts
+++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts
@@ -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 = [],
diff --git a/packages/twenty-ui/package.json b/packages/twenty-ui/package.json
index 4a6d5309bc..d36989f015 100644
--- a/packages/twenty-ui/package.json
+++ b/packages/twenty-ui/package.json
@@ -170,4 +170,4 @@
]
}
}
-}
\ No newline at end of file
+}