From e838dfc68b8e1215872e6a0eb9ed87c903500fe3 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Wed, 5 Mar 2025 15:01:07 +0100 Subject: [PATCH] Fix advanced filters (#10665) This PR partially fixes advanced filters that were not working even with feature flag activated. Bugs fixed here : - Advanced filters are not applied - Root advanced filters cannot be created - Cannot close advanced filters dropdown - Can create multiple times the same non-advanced filter (reserved for advanced filters) upsertRecordFilter and removeRecordFilter have been refactored to take record filter id instead of field metadata id, because the user should be allowed to apply multiple filters for the same field. We now base view filter CRUD directly on id, otherwise it could lead to inconsistencies between advanced filters and simple filters. This PR also refactors an important hook : computeRecordGqlOperationFilter, so that it takes an object instead of multiple params. There are still bugs left, they will be taken in other PRs. --- .../utils/computeContextStoreFilters.ts | 22 ++-- .../AdvancedFilterAddFilterRuleSelect.tsx | 5 +- .../AdvancedFilterRuleOptionsDropdown.tsx | 7 +- ...ncedFilterAddFilterRuleSelectDropdownId.ts | 5 + .../components/AdvancedFilterButton.tsx | 2 +- ...pdownFilterSelectCompositeFieldSubMenu.tsx | 106 ++++++++++++------ ...jectFilterDropdownFilterSelectMenuItem.tsx | 44 +++++++- .../__tests__/useRemoveRecordFilter.test.tsx | 8 +- .../__tests__/useUpsertRecordFilter.test.tsx | 4 +- .../hooks/useRemoveRecordFilter.ts | 8 +- .../hooks/useUpsertRecordFilter.ts | 8 +- ...omputeViewRecordGqlOperationFilter.test.ts | 86 +++++++------- .../computeViewRecordGqlOperationFilter.ts | 95 +++++++++------- ...eRecordFilterInNonAdvancedRecordFilters.ts | 26 +++++ ...textStoreNumberOfSelectedRecordsEffect.tsx | 1 - .../export/hooks/useExportFetchRecords.ts | 1 - .../useFindManyRecordIndexTableParams.ts | 20 ++-- .../hooks/useLoadRecordIndexBoardColumn.ts | 17 ++- .../hooks/useLoadRecordIndexStates.ts | 8 +- .../RecordTableEmptyStateSoftDelete.tsx | 2 +- .../hooks/useAggregateRecordsForHeader.ts | 17 ++- ...egateRecordsForRecordTableColumnFooter.tsx | 15 ++- .../tableViewFilterGroupsComponentState.ts | 11 -- .../AdvancedFilterDropdownButton.tsx | 8 +- .../EditableFilterDropdownButton.tsx | 6 +- .../views/components/RecordFilterChip.tsx | 2 +- .../views/components/SoftDeleteFilterChip.tsx | 2 +- .../internal/usePersistViewFilterRecords.ts | 3 +- .../internal/usePersistViewSortRecords.ts | 1 + .../src/modules/views/hooks/useChangeView.ts | 2 +- ...aveRecordFilterGroupsToViewFilterGroups.ts | 6 +- .../__tests__/getViewFiltersToCreate.test.ts | 61 +--------- .../__tests__/getViewFiltersToDelete.test.ts | 42 +------ .../__tests__/getViewFiltersToUpdate.test.ts | 48 ++------ .../views/utils/getQueryVariablesFromView.ts | 20 +++- .../views/utils/getViewFiltersToCreate.ts | 7 +- .../views/utils/getViewFiltersToDelete.ts | 11 +- .../views/utils/getViewFiltersToUpdate.ts | 7 +- 38 files changed, 367 insertions(+), 377 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/states/tableViewFilterGroupsComponentState.ts diff --git a/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts b/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts index a86b7905f5..3e888aa6dc 100644 --- a/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts +++ b/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts @@ -3,7 +3,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; -import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; export const computeContextStoreFilters = ( @@ -16,12 +16,12 @@ export const computeContextStoreFilters = ( if (contextStoreTargetedRecordsRule.mode === 'exclusion') { queryFilter = makeAndFilterVariables([ - computeViewRecordGqlOperationFilter( + computeRecordGqlOperationFilter({ filterValueDependencies, - contextStoreFilters, - objectMetadataItem?.fields ?? [], - [], - ), + fields: objectMetadataItem?.fields ?? [], + recordFilters: contextStoreFilters, + recordFilterGroups: [], + }), contextStoreTargetedRecordsRule.excludedRecordIds.length > 0 ? { not: { @@ -41,12 +41,12 @@ export const computeContextStoreFilters = ( in: contextStoreTargetedRecordsRule.selectedRecordIds, }, } - : computeViewRecordGqlOperationFilter( + : computeRecordGqlOperationFilter({ filterValueDependencies, - contextStoreFilters, - objectMetadataItem?.fields ?? [], - [], - ); + fields: objectMetadataItem?.fields ?? [], + recordFilters: contextStoreFilters, + recordFilterGroups: [], + }); } return queryFilter; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx index ab5cad4a9a..7680af8c6c 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx @@ -1,6 +1,7 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { getAdvancedFilterAddFilterRuleSelectDropdownId } from '@/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId'; import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; @@ -27,7 +28,9 @@ export const AdvancedFilterAddFilterRuleSelect = ({ recordFilterGroup, lastChildPosition = 0, }: AdvancedFilterAddFilterRuleSelectProps) => { - const dropdownId = `advanced-filter-add-filter-rule-${recordFilterGroup.id}`; + const dropdownId = getAdvancedFilterAddFilterRuleSelectDropdownId( + recordFilterGroup.id, + ); const { currentViewId } = useGetCurrentView(); diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx index df45cd041d..3441144f59 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRuleOptionsDropdown.tsx @@ -8,7 +8,6 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId'; import { isDefined } from 'twenty-shared'; import { MenuItem } from 'twenty-ui'; @@ -46,7 +45,7 @@ export const AdvancedFilterRuleOptionsDropdown = ({ const handleRemove = async () => { if (isDefined(viewFilterId)) { - removeRecordFilter(viewFilterId); + removeRecordFilter({ recordFilterId: viewFilterId }); const isOnlyViewFilterInGroup = childViewFiltersAndViewFilterGroups.length === 1; @@ -66,7 +65,7 @@ export const AdvancedFilterRuleOptionsDropdown = ({ ); for (const childViewFilter of childViewFilters) { - removeRecordFilter(childViewFilter.id); + removeRecordFilter({ recordFilterId: childViewFilter.id }); } } else { throw new Error('No view filter or view filter group to remove'); @@ -86,7 +85,7 @@ export const AdvancedFilterRuleOptionsDropdown = ({ } - dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }} + dropdownHotkeyScope={{ scope: dropdownId }} dropdownOffset={{ y: 8, x: 0 }} dropdownPlacement="bottom-start" /> diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId.ts new file mode 100644 index 0000000000..497dae8fc3 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId.ts @@ -0,0 +1,5 @@ +export const getAdvancedFilterAddFilterRuleSelectDropdownId = ( + recordFilterGroupId: string, +) => { + return `advanced-filter-add-filter-rule-${recordFilterGroupId}`; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx index 1527662af1..99dcefc233 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx @@ -133,8 +133,8 @@ export const AdvancedFilterButton = () => { }); } - openAdvancedFilterDropdown(); closeObjectFilterDropdown(); + openAdvancedFilterDropdown(); }; return ( diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx index 7ac9491ef3..95f00a1d7f 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx @@ -9,12 +9,15 @@ import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-rec import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; +import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel'; import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue'; import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; @@ -81,55 +84,84 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { advancedFilterViewFilterId, ); + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const setSelectedFilter = useSetRecoilComponentStateV2( + selectedFilterComponentState, + ); + const handleSelectFilter = ( fieldMetadataItem: FieldMetadataItem | null | undefined, subFieldName?: string | null | undefined, ) => { - if (isDefined(fieldMetadataItem)) { - if ( - isDefined(advancedFilterViewFilterId) && - isDefined(advancedFilterViewFilterGroupId) - ) { - closeAdvancedFilterDropdown(); + if (!isDefined(fieldMetadataItem)) { + return; + } - const type = getFilterTypeFromFieldType(fieldMetadataItem.type); + const type = getFilterTypeFromFieldType(fieldMetadataItem.type); - const operand = getRecordFilterOperands({ - filterType: type, - subFieldName: subFieldName, - })[0]; + const defaultOperand = getRecordFilterOperands({ + filterType: type, + subFieldName: subFieldName, + })[0]; - const { value, displayValue } = getInitialFilterValue(type, operand); + if ( + isDefined(advancedFilterViewFilterId) && + isDefined(advancedFilterViewFilterGroupId) + ) { + closeAdvancedFilterDropdown(); - applyRecordFilter({ - id: advancedFilterViewFilterId, - fieldMetadataId: fieldMetadataItem.id, - value, - operand, - displayValue, - type: getFilterTypeFromFieldType(fieldMetadataItem.type), - label: fieldMetadataItem.label, - recordFilterGroupId: advancedFilterViewFilterGroupId, - subFieldName: subFieldName, - }); - } - - setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id); - - const type = getFilterTypeFromFieldType(fieldMetadataItem.type); - - setSelectedOperandInDropdown( - getRecordFilterOperands({ - filterType: type, - subFieldName: subFieldName, - })[0], + const { value, displayValue } = getInitialFilterValue( + type, + defaultOperand, ); - setSubFieldNameUsedInDropdown(subFieldName); + applyRecordFilter({ + id: advancedFilterViewFilterId, + fieldMetadataId: fieldMetadataItem.id, + value, + operand: defaultOperand, + displayValue, + type, + label: fieldMetadataItem.label, + recordFilterGroupId: advancedFilterViewFilterGroupId, + subFieldName: subFieldName, + }); + } - setObjectFilterDropdownSearchInput(''); + setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id); - setObjectFilterDropdownFilterIsSelected(true); + setSubFieldNameUsedInDropdown(subFieldName); + + setObjectFilterDropdownSearchInput(''); + + setObjectFilterDropdownFilterIsSelected(true); + + const duplicateFilterInCurrentRecordFilters = + findDuplicateRecordFilterInNonAdvancedRecordFilters({ + recordFilters: currentRecordFilters, + fieldMetadataItemId: fieldMetadataItem.id, + subFieldName, + }); + + const filterIsAlreadyInCurrentRecordFilters = isDefined( + duplicateFilterInCurrentRecordFilters, + ); + + const isSimpleFilter = !isDefined(advancedFilterViewFilterId); + + if (isSimpleFilter && filterIsAlreadyInCurrentRecordFilters) { + setSelectedFilter({ + ...duplicateFilterInCurrentRecordFilters, + }); + + setSelectedOperandInDropdown( + duplicateFilterInCurrentRecordFilters.operand, + ); + } else { + setSelectedOperandInDropdown(defaultOperand); } }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx index 36934f8ac8..b4e5c6b096 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx @@ -10,7 +10,10 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object- import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { RecordPickerHotkeyScope } from '@/object-record/record-picker/types/RecordPickerHotkeyScope'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; @@ -19,6 +22,7 @@ import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared'; import { MenuItemSelect, useIcons } from 'twenty-ui'; export type ObjectFilterDropdownFilterSelectMenuItemProps = { @@ -67,6 +71,14 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ advancedFilterViewFilterId, ); + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const setSelectedFilter = useSetRecoilComponentStateV2( + selectedFilterComponentState, + ); + const handleSelectFilter = (fieldMetadataItem: FieldMetadataItem) => { closeAdvancedFilterDropdown(); @@ -78,13 +90,35 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ setHotkeyScope(RecordPickerHotkeyScope.RecordPicker); } - setSelectedOperandInDropdown( - getRecordFilterOperands({ - filterType, - })[0], - ); + const defaultOperand = getRecordFilterOperands({ + filterType, + })[0]; setObjectFilterDropdownFilterIsSelected(true); + + const duplicateFilterInCurrentRecordFilters = + findDuplicateRecordFilterInNonAdvancedRecordFilters({ + recordFilters: currentRecordFilters, + fieldMetadataItemId: fieldMetadataItem.id, + }); + + const filterIsAlreadyInCurrentRecordFilters = isDefined( + duplicateFilterInCurrentRecordFilters, + ); + + const isSimpleFilter = !isDefined(advancedFilterViewFilterId); + + if (isSimpleFilter && filterIsAlreadyInCurrentRecordFilters) { + setSelectedFilter({ + ...duplicateFilterInCurrentRecordFilters, + }); + + setSelectedOperandInDropdown( + duplicateFilterInCurrentRecordFilters.operand, + ); + } else { + setSelectedOperandInDropdown(defaultOperand); + } }; const { getIcon } = useIcons(); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx index 00efe5de9d..cd778c1905 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx @@ -54,7 +54,9 @@ describe('useRemoveRecordFilter', () => { expect(result.current.currentRecordFilters[0]).toEqual(mockRecordFilter); act(() => { - result.current.removeRecordFilter(mockRecordFilter.fieldMetadataId); + result.current.removeRecordFilter({ + recordFilterId: mockRecordFilter.id, + }); }); expect(result.current.currentRecordFilters).toHaveLength(0); @@ -96,7 +98,9 @@ describe('useRemoveRecordFilter', () => { expect(result.current.currentRecordFilters).toHaveLength(1); act(() => { - result.current.removeRecordFilter('non-existent-field-metadata-id'); + result.current.removeRecordFilter({ + recordFilterId: 'non-existent-field-metadata-id', + }); }); expect(result.current.currentRecordFilters).toHaveLength(1); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx index edaf730594..00e6b0be08 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx @@ -14,7 +14,7 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({ }); describe('useUpsertRecordFilter', () => { - it('should add a new filter when fieldMetadataId does not exist', () => { + it('should add a new filter when record filter id does not exist', () => { const { result } = renderHook( () => { const currentRecordFilters = useRecoilComponentValueV2( @@ -48,7 +48,7 @@ describe('useUpsertRecordFilter', () => { expect(result.current.currentRecordFilters[0]).toEqual(mockNewRecordFilter); }); - it('should update an existing filter when fieldMetadataId exists', () => { + it('should update an existing filter when record filter id exists', () => { const { result } = renderHook( () => { const currentRecordFilters = useRecoilComponentValueV2( diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts index a41f5b3f87..0c0d20798a 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts @@ -10,7 +10,7 @@ export const useRemoveRecordFilter = () => { const removeRecordFilter = useRecoilCallback( ({ set, snapshot }) => - (fieldMetadataId: string) => { + ({ recordFilterId }: { recordFilterId: string }) => { const currentRecordFilters = getSnapshotValue( snapshot, currentRecordFiltersCallbackState, @@ -18,8 +18,7 @@ export const useRemoveRecordFilter = () => { const foundRecordFilterInCurrentRecordFilters = currentRecordFilters.some( - (existingFilter) => - existingFilter.fieldMetadataId === fieldMetadataId, + (existingFilter) => existingFilter.id === recordFilterId, ); if (foundRecordFilterInCurrentRecordFilters) { @@ -27,8 +26,7 @@ export const useRemoveRecordFilter = () => { const newCurrentRecordFilters = [...currentRecordFilters]; const indexOfFilterToRemove = newCurrentRecordFilters.findIndex( - (existingFilter) => - existingFilter.fieldMetadataId === fieldMetadataId, + (existingFilter) => existingFilter.id === recordFilterId, ); newCurrentRecordFilters.splice(indexOfFilterToRemove, 1); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts index 6f63ec6144..e12a676bb5 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts @@ -19,9 +19,7 @@ export const useUpsertRecordFilter = () => { const foundRecordFilterInCurrentRecordFilters = currentRecordFilters.some( - (existingFilter) => - existingFilter.fieldMetadataId === - recordFilterToSet.fieldMetadataId, + (existingFilter) => existingFilter.id === recordFilterToSet.id, ); if (!foundRecordFilterInCurrentRecordFilters) { @@ -34,9 +32,7 @@ export const useUpsertRecordFilter = () => { const newCurrentRecordFilters = [...currentRecordFilters]; const indexOfFilterToUpdate = newCurrentRecordFilters.findIndex( - (existingFilter) => - existingFilter.fieldMetadataId === - recordFilterToSet.fieldMetadataId, + (existingFilter) => existingFilter.id === recordFilterToSet.id, ); newCurrentRecordFilters[indexOfFilterToUpdate] = { diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts index f0277f78b9..484e078d5b 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts @@ -1,7 +1,7 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; -import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { FieldMetadataType } from '~/generated/graphql'; import { getCompaniesMock } from '~/testing/mock-data/companies'; @@ -40,12 +40,12 @@ describe('computeViewRecordGqlOperationFilter', () => { label: 'Name', }; - const result = computeViewRecordGqlOperationFilter( - mockFilterValueDependencies, - [nameFilter], - companyMockObjectMetadataItem.fields, - [], - ); + const result = computeRecordGqlOperationFilter({ + filterValueDependencies: mockFilterValueDependencies, + recordFilters: [nameFilter], + recordFilterGroups: [], + fields: companyMockObjectMetadataItem.fields, + }); expect(result).toEqual({ name: { @@ -85,12 +85,12 @@ describe('computeViewRecordGqlOperationFilter', () => { label: 'Employees', }; - const result = computeViewRecordGqlOperationFilter( - mockFilterValueDependencies, - [nameFilter, employeesFilter], - companyMockObjectMetadataItem.fields, - [], - ); + const result = computeRecordGqlOperationFilter({ + filterValueDependencies: mockFilterValueDependencies, + recordFilters: [nameFilter, employeesFilter], + recordFilterGroups: [], + fields: companyMockObjectMetadataItem.fields, + }); expect(result).toEqual({ and: [ @@ -156,17 +156,17 @@ describe('should work as expected for the different field types', () => { type: FieldMetadataType.ADDRESS, }; - const result = computeViewRecordGqlOperationFilter( - mockFilterValueDependencies, - [ + const result = computeRecordGqlOperationFilter({ + filterValueDependencies: mockFilterValueDependencies, + recordFilters: [ addressFilterContains, addressFilterDoesNotContain, addressFilterIsEmpty, addressFilterIsNotEmpty, ], - companyMockObjectMetadataItem.fields, - [], - ); + recordFilterGroups: [], + fields: companyMockObjectMetadataItem.fields, + }); expect(result).toEqual({ and: [ @@ -523,17 +523,17 @@ describe('should work as expected for the different field types', () => { type: FieldMetadataType.PHONES, }; - const result = computeViewRecordGqlOperationFilter( - mockFilterValueDependencies, - [ + const result = computeRecordGqlOperationFilter({ + filterValueDependencies: mockFilterValueDependencies, + recordFilters: [ phonesFilterContains, phonesFilterDoesNotContain, phonesFilterIsEmpty, phonesFilterIsNotEmpty, ], - personMockObjectMetadataItem.fields, - [], - ); + recordFilterGroups: [], + fields: personMockObjectMetadataItem.fields, + }); expect(result).toEqual({ and: [ @@ -657,17 +657,17 @@ describe('should work as expected for the different field types', () => { type: FieldMetadataType.EMAILS, }; - const result = computeViewRecordGqlOperationFilter( - mockFilterValueDependencies, - [ + const result = computeRecordGqlOperationFilter({ + filterValueDependencies: mockFilterValueDependencies, + recordFilters: [ emailsFilterContains, emailsFilterDoesNotContain, emailsFilterIsEmpty, emailsFilterIsNotEmpty, ], - personMockObjectMetadataItem.fields, - [], - ); + recordFilterGroups: [], + fields: personMockObjectMetadataItem.fields, + }); expect(result).toEqual({ and: [ @@ -793,18 +793,18 @@ describe('should work as expected for the different field types', () => { type: FieldMetadataType.DATE_TIME, }; - const result = computeViewRecordGqlOperationFilter( - mockFilterValueDependencies, - [ + const result = computeRecordGqlOperationFilter({ + filterValueDependencies: mockFilterValueDependencies, + recordFilters: [ dateFilterIsAfter, dateFilterIsBefore, dateFilterIs, dateFilterIsEmpty, dateFilterIsNotEmpty, ], - companyMockObjectMetadataItem.fields, - [], - ); + recordFilterGroups: [], + fields: companyMockObjectMetadataItem.fields, + }); expect(result).toEqual({ and: [ @@ -894,17 +894,17 @@ describe('should work as expected for the different field types', () => { type: FieldMetadataType.NUMBER, }; - const result = computeViewRecordGqlOperationFilter( - mockFilterValueDependencies, - [ + const result = computeRecordGqlOperationFilter({ + filterValueDependencies: mockFilterValueDependencies, + recordFilters: [ employeesFilterIsGreaterThan, employeesFilterIsLessThan, employeesFilterIsEmpty, employeesFilterIsNotEmpty, ], - companyMockObjectMetadataItem.fields, - [], - ); + recordFilterGroups: [], + fields: companyMockObjectMetadataItem.fields, + }); expect(result).toEqual({ and: [ diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts index 16fb47b1d3..5a1f39766b 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/computeViewRecordGqlOperationFilter.ts @@ -32,8 +32,7 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; import { getEmptyRecordGqlOperationFilter } from '@/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter'; -import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; -import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; + import { resolveDateViewFilterValue } from '@/views/view-filter-value/utils/resolveDateViewFilterValue'; import { resolveSelectViewFilterValue } from '@/views/view-filter-value/utils/resolveSelectViewFilterValue'; import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema'; @@ -41,6 +40,8 @@ import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/valid import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; import { z } from 'zod'; +import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType'; type ComputeFilterRecordGqlOperationFilterParams = { @@ -808,54 +809,55 @@ export const computeFilterRecordGqlOperationFilter = ({ } }; -const computeViewFilterGroupRecordGqlOperationFilter = ( +const computeRecordFilterGroupRecordGqlOperationFilter = ( filterValueDependencies: RecordFilterValueDependencies, filters: RecordFilter[], fields: Pick[], - viewFilterGroups: ViewFilterGroup[], - currentViewFilterGroupId?: string, + recordFilterGroups: RecordFilterGroup[], + currentRecordFilterGroupId?: string, ): RecordGqlOperationFilter | undefined => { - const currentViewFilterGroup = viewFilterGroups.find( - (viewFilterGroup) => viewFilterGroup.id === currentViewFilterGroupId, + const currentRecordFilterGroup = recordFilterGroups.find( + (recordFilterGroup) => recordFilterGroup.id === currentRecordFilterGroupId, ); - if (!currentViewFilterGroup) { + if (!currentRecordFilterGroup) { return; } - const groupFilters = filters.filter( - (filter) => filter.recordFilterGroupId === currentViewFilterGroupId, + const recordFiltersInGroup = filters.filter( + (filter) => filter.recordFilterGroupId === currentRecordFilterGroupId, ); - const groupRecordGqlOperationFilters = groupFilters - .map((filter) => + const groupRecordGqlOperationFilters = recordFiltersInGroup + .map((recordFilter) => computeFilterRecordGqlOperationFilter({ filterValueDependencies, - filter, + filter: recordFilter, fieldMetadataItems: fields, }), ) .filter(isDefined); - const subGroupRecordGqlOperationFilters = viewFilterGroups + const subGroupRecordGqlOperationFilters = recordFilterGroups .filter( - (viewFilterGroup) => - viewFilterGroup.parentViewFilterGroupId === currentViewFilterGroupId, + (recordFilterGroup) => + recordFilterGroup.parentRecordFilterGroupId === + currentRecordFilterGroupId, ) .map((subViewFilterGroup) => - computeViewFilterGroupRecordGqlOperationFilter( + computeRecordFilterGroupRecordGqlOperationFilter( filterValueDependencies, filters, fields, - viewFilterGroups, + recordFilterGroups, subViewFilterGroup.id, ), ) .filter(isDefined); if ( - currentViewFilterGroup.logicalOperator === - ViewFilterGroupLogicalOperator.AND + currentRecordFilterGroup.logicalOperator === + RecordFilterGroupLogicalOperator.AND ) { return { and: [ @@ -864,7 +866,8 @@ const computeViewFilterGroupRecordGqlOperationFilter = ( ], }; } else if ( - currentViewFilterGroup.logicalOperator === ViewFilterGroupLogicalOperator.OR + currentRecordFilterGroup.logicalOperator === + RecordFilterGroupLogicalOperator.OR ) { return { or: [ @@ -874,38 +877,44 @@ const computeViewFilterGroupRecordGqlOperationFilter = ( }; } else { throw new Error( - `Unknown logical operator ${currentViewFilterGroup.logicalOperator}`, + `Unknown logical operator ${currentRecordFilterGroup.logicalOperator}`, ); } }; -export const computeViewRecordGqlOperationFilter = ( - filterValueDependencies: RecordFilterValueDependencies, - filters: RecordFilter[], - fields: Pick[], - viewFilterGroups: ViewFilterGroup[], -): RecordGqlOperationFilter => { - const regularRecordGqlOperationFilter: RecordGqlOperationFilter[] = filters - .filter((filter) => !filter.recordFilterGroupId) - .map((regularFilter) => - computeFilterRecordGqlOperationFilter({ - filterValueDependencies, - filter: regularFilter, - fieldMetadataItems: fields, - }), - ) - .filter(isDefined); +export const computeRecordGqlOperationFilter = ({ + fields, + filterValueDependencies, + recordFilters, + recordFilterGroups, +}: { + filterValueDependencies: RecordFilterValueDependencies; + recordFilters: RecordFilter[]; + fields: Pick[]; + recordFilterGroups: RecordFilterGroup[]; +}): RecordGqlOperationFilter => { + const regularRecordGqlOperationFilter: RecordGqlOperationFilter[] = + recordFilters + .filter((filter) => !isDefined(filter.recordFilterGroupId)) + .map((regularFilter) => + computeFilterRecordGqlOperationFilter({ + filterValueDependencies, + filter: regularFilter, + fieldMetadataItems: fields, + }), + ) + .filter(isDefined); - const outermostFilterGroupId = viewFilterGroups.find( - (viewFilterGroup) => !viewFilterGroup.parentViewFilterGroupId, + const outermostFilterGroupId = recordFilterGroups.find( + (recordFilterGroup) => !recordFilterGroup.parentRecordFilterGroupId, )?.id; const advancedRecordGqlOperationFilter = - computeViewFilterGroupRecordGqlOperationFilter( + computeRecordFilterGroupRecordGqlOperationFilter( filterValueDependencies, - filters, + recordFilters, fields, - viewFilterGroups, + recordFilterGroups, outermostFilterGroupId, ); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters.ts new file mode 100644 index 0000000000..3a8a178aa4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters.ts @@ -0,0 +1,26 @@ +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined'; + +export const findDuplicateRecordFilterInNonAdvancedRecordFilters = ({ + recordFilters, + fieldMetadataItemId, + subFieldName, +}: { + recordFilters: RecordFilter[]; + fieldMetadataItemId: string; + subFieldName?: string | null | undefined; +}): RecordFilter | undefined => { + const duplicateFilterInCurrentRecordFilters = recordFilters.find( + (recordFilter) => + compareStrictlyExceptForNullAndUndefined( + recordFilter.fieldMetadataId, + fieldMetadataItemId, + ) && + compareStrictlyExceptForNullAndUndefined( + recordFilter.subFieldName, + subFieldName, + ), + ); + + return duplicateFilterInCurrentRecordFilters; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx index 2844535117..3859e5b793 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect.tsx @@ -34,7 +34,6 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect = const findManyRecordsParams = useFindManyRecordIndexTableParams( objectMetadataItem?.nameSingular ?? '', - objectMetadataItem?.namePlural ?? '', ); const contextStoreFilters = useRecoilComponentValueV2( diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts index 8404c6c0cf..2f8c0d90e9 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts @@ -83,7 +83,6 @@ export const useExportFetchRecords = ({ const findManyRecordsParams = useFindManyRecordIndexTableParams( objectMetadataItem.nameSingular, - recordIndexId, ); const finalColumns = [ diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts index cd13157089..853d209719 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useFindManyRecordIndexTableParams.ts @@ -1,17 +1,16 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; +import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition'; import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter'; import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; -import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; export const useFindManyRecordIndexTableParams = ( objectNameSingular: string, - recordTableId?: string, ) => { const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, @@ -23,9 +22,8 @@ export const useFindManyRecordIndexTableParams = ( const currentRecordGroupDefinition = useCurrentRecordGroupDefinition(); - const tableViewFilterGroups = useRecoilComponentValueV2( - tableViewFilterGroupsComponentState, - recordTableId, + const currentRecordFilterGroups = useRecoilComponentValueV2( + currentRecordFilterGroupsComponentState, ); const currentRecordSorts = useRecoilComponentValueV2( @@ -38,12 +36,12 @@ export const useFindManyRecordIndexTableParams = ( const { filterValueDependencies } = useFilterValueDependencies(); - const stateFilter = computeViewRecordGqlOperationFilter( + const stateFilter = computeRecordGqlOperationFilter({ + fields: objectMetadataItem?.fields ?? [], filterValueDependencies, - currentRecordFilters, - objectMetadataItem?.fields ?? [], - tableViewFilterGroups, - ); + recordFilterGroups: currentRecordFilterGroups, + recordFilters: currentRecordFilters, + }); const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts index 5cd5307067..ae273510c5 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts @@ -7,13 +7,14 @@ import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils import { useSetRecordIdsForColumn } from '@/object-record/record-board/hooks/useSetRecordIdsForColumn'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields'; import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; import { isDefined } from 'twenty-shared'; type UseLoadRecordIndexBoardProps = { @@ -43,6 +44,10 @@ export const useLoadRecordIndexBoardColumn = ({ recordIndexViewFilterGroupsState, ); + const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups( + recordIndexViewFilterGroups, + ); + const currentRecordFilters = useRecoilComponentValueV2( currentRecordFiltersComponentState, ); @@ -53,12 +58,12 @@ export const useLoadRecordIndexBoardColumn = ({ const { filterValueDependencies } = useFilterValueDependencies(); - const requestFilters = computeViewRecordGqlOperationFilter( + const requestFilters = computeRecordGqlOperationFilter({ filterValueDependencies, - currentRecordFilters, - objectMetadataItem?.fields ?? [], - recordIndexViewFilterGroups, - ); + recordFilters: currentRecordFilters, + recordFilterGroups, + fields: objectMetadataItem.fields, + }); const orderBy = turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts index 1bfc272097..3d3a9b0fd6 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts @@ -18,7 +18,6 @@ import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTab import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState'; import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState'; -import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; @@ -205,12 +204,7 @@ export const useLoadRecordIndexStates = () => { onViewFieldsChange(view.viewFields, objectMetadataItem, recordIndexId); onViewGroupsChange(view.viewGroups, objectMetadataItem, recordIndexId); - set( - tableViewFilterGroupsComponentState.atomFamily({ - instanceId: recordIndexId, - }), - view.viewFilterGroups ?? [], - ); + set( tableFiltersComponentState.atomFamily({ instanceId: recordIndexId, diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx index 45d922334c..42d761f8fa 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete.tsx @@ -36,7 +36,7 @@ export const RecordTableEmptyStateSoftDelete = () => { throw new Error('Deleted filter not found'); } - removeRecordFilter(deletedFilter.fieldMetadataId); + removeRecordFilter({ recordFilterId: deletedFilter.id }); toggleSoftDeleteFilterState(false); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts index b85b14257d..684b7bc4c0 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts @@ -3,11 +3,12 @@ import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords'; import { buildRecordGqlFieldsAggregateForView } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView'; import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; -import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; import { UserContext } from '@/users/contexts/UserContext'; +import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared'; @@ -26,6 +27,10 @@ export const useAggregateRecordsForHeader = ({ recordIndexViewFilterGroupsState, ); + const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups( + recordIndexViewFilterGroups, + ); + const recordIndexFilters = useRecoilValue(recordIndexFiltersState); const recordIndexKanbanAggregateOperation = useRecoilValue( @@ -36,12 +41,12 @@ export const useAggregateRecordsForHeader = ({ const { dateFormat, timeFormat, timeZone } = useContext(UserContext); - const requestFilters = computeViewRecordGqlOperationFilter( + const requestFilters = computeRecordGqlOperationFilter({ filterValueDependencies, - recordIndexFilters, - objectMetadataItem.fields, - recordIndexViewFilterGroups, - ); + recordFilters: recordIndexFilters, + recordFilterGroups, + fields: objectMetadataItem.fields, + }); const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregateForView({ objectMetadataItem, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx index 07ab95fbfc..1c481becc7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx @@ -2,7 +2,7 @@ import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords'; import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter'; import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; @@ -13,6 +13,7 @@ import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { UserContext } from '@/users/contexts/UserContext'; +import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined, isFieldMetadataDateKind } from 'twenty-shared'; @@ -33,13 +34,17 @@ export const useAggregateRecordsForRecordTableColumnFooter = ( const { filterValueDependencies } = useFilterValueDependencies(); - const requestFilters = computeViewRecordGqlOperationFilter( - filterValueDependencies, - currentRecordFilters, - objectMetadataItem.fields, + const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups( recordIndexViewFilterGroups, ); + const requestFilters = computeRecordGqlOperationFilter({ + fields: objectMetadataItem.fields, + filterValueDependencies, + recordFilterGroups, + recordFilters: currentRecordFilters, + }); + const { viewFieldId } = useContext( RecordTableColumnAggregateFooterCellContext, ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/tableViewFilterGroupsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/tableViewFilterGroupsComponentState.ts deleted file mode 100644 index c3f13977b8..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/states/tableViewFilterGroupsComponentState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; - -export const tableViewFilterGroupsComponentState = createComponentStateV2< - ViewFilterGroup[] ->({ - key: 'tableViewFilterGroupsComponentState', - defaultValue: [], - componentInstanceContext: RecordTableComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx index 9cdcf427fd..d40f7ba1e8 100644 --- a/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/AdvancedFilterDropdownButton.tsx @@ -29,10 +29,6 @@ export const AdvancedFilterDropdownButton = () => { const { removeRecordFilter } = useRemoveRecordFilter(); - const handleDropdownClickOutside = useCallback(() => {}, []); - - const handleDropdownClose = () => {}; - const removeAdvancedFilter = useCallback(async () => { if (!advancedRecordFilterIds) { throw new Error('No advanced view filters to remove'); @@ -48,7 +44,7 @@ export const AdvancedFilterDropdownButton = () => { } for (const recordFilterId of advancedRecordFilterIds) { - removeRecordFilter(recordFilterId); + removeRecordFilter({ recordFilterId }); } }, [ advancedRecordFilterIds, @@ -83,8 +79,6 @@ export const AdvancedFilterDropdownButton = () => { dropdownOffset={{ y: 8, x: 0 }} dropdownPlacement="bottom-start" dropdownMenuWidth={800} - onClickOutside={handleDropdownClickOutside} - onClose={handleDropdownClose} /> ); }; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 264314cc1b..461b061dfe 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -29,11 +29,11 @@ export const EditableFilterDropdownButton = ({ const handleRemove = () => { closeDropdown(); - removeRecordFilter(viewFilter.fieldMetadataId); + removeRecordFilter({ recordFilterId: viewFilter.id }); }; const handleDropdownClickOutside = useCallback(() => { - const { value, operand, fieldMetadataId } = viewFilter; + const { value, operand } = viewFilter; if ( !value && ![ @@ -44,7 +44,7 @@ export const EditableFilterDropdownButton = ({ RecordFilterOperand.IsToday, ].includes(operand) ) { - removeRecordFilter(fieldMetadataId); + removeRecordFilter({ recordFilterId: viewFilter.id }); } }, [viewFilter, removeRecordFilter]); diff --git a/packages/twenty-front/src/modules/views/components/RecordFilterChip.tsx b/packages/twenty-front/src/modules/views/components/RecordFilterChip.tsx index 15b8c9d2a9..9c53822367 100644 --- a/packages/twenty-front/src/modules/views/components/RecordFilterChip.tsx +++ b/packages/twenty-front/src/modules/views/components/RecordFilterChip.tsx @@ -21,7 +21,7 @@ export const RecordFilterChip = ({ recordFilter }: RecordFilterChipProps) => { const FieldMetadataItemIcon = getIcon(fieldMetadataItem.icon); const handleRemoveClick = () => { - removeRecordFilter(recordFilter.fieldMetadataId); + removeRecordFilter({ recordFilterId: recordFilter.id }); }; return ( diff --git a/packages/twenty-front/src/modules/views/components/SoftDeleteFilterChip.tsx b/packages/twenty-front/src/modules/views/components/SoftDeleteFilterChip.tsx index 94172dfb81..c49fdd3b0f 100644 --- a/packages/twenty-front/src/modules/views/components/SoftDeleteFilterChip.tsx +++ b/packages/twenty-front/src/modules/views/components/SoftDeleteFilterChip.tsx @@ -25,7 +25,7 @@ export const SoftDeleteFilterChip = ({ const { getIcon } = useIcons(); const handleRemoveClick = () => { - removeRecordFilter(recordFilter.fieldMetadataId); + removeRecordFilter({ recordFilterId: recordFilter.id }); setIsSoftDeleteFilterActive(false); }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts index 03c9152047..38cda89320 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts @@ -14,7 +14,6 @@ import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRe import { GraphQLView } from '@/views/types/GraphQLView'; import { ViewFilter } from '@/views/types/ViewFilter'; import { isDefined } from 'twenty-shared'; -import { v4 } from 'uuid'; export const usePersistViewFilterRecords = () => { const { objectMetadataItem } = useObjectMetadataItem({ @@ -51,7 +50,7 @@ export const usePersistViewFilterRecords = () => { mutation: createOneRecordMutation, variables: { input: { - id: v4(), + id: viewFilter.id, fieldMetadataId: viewFilter.fieldMetadataId, viewId: view.id, value: viewFilter.value, diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts index 7ef3948a8a..066c33651f 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts @@ -52,6 +52,7 @@ export const usePersistViewSortRecords = () => { fieldMetadataId: viewSort.fieldMetadataId, viewId: view.id, direction: viewSort.direction, + id: viewSort.id, }, }, update: (cache, { data }) => { diff --git a/packages/twenty-front/src/modules/views/hooks/useChangeView.ts b/packages/twenty-front/src/modules/views/hooks/useChangeView.ts index e12d0f7b7c..18bb2b2309 100644 --- a/packages/twenty-front/src/modules/views/hooks/useChangeView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useChangeView.ts @@ -3,7 +3,7 @@ import { useSetViewInUrl } from '@/views/hooks/useSetViewInUrl'; export const useChangeView = () => { const { setViewInUrl } = useSetViewInUrl(); - const changeView = async (viewId: string) => { + const changeView = (viewId: string) => { setViewInUrl(viewId); }; diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts index 17aa248ec3..7e5478ae45 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts @@ -59,8 +59,8 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => { newViewFilterGroups, ); - const viewFilterIdsToDelete = viewFilterGroupsToDelete.map( - (viewFilter) => viewFilter.id, + const viewFilterGroupIdsToDelete = viewFilterGroupsToDelete.map( + (viewFilterGroup) => viewFilterGroup.id, ); await createViewFilterGroupRecords( @@ -68,7 +68,7 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => { currentView, ); await updateViewFilterGroupRecords(viewFilterGroupsToUpdate); - await deleteViewFilterGroupRecords(viewFilterIdsToDelete); + await deleteViewFilterGroupRecords(viewFilterGroupIdsToDelete); }, [ createViewFilterGroupRecords, diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts index 5b87e142e4..66d3ee2311 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts @@ -17,8 +17,11 @@ describe('getViewFiltersToCreate', () => { it('should return all filters when current filters array is empty', () => { const currentViewFilters: ViewFilter[] = []; const newViewFilters: ViewFilter[] = [ - { ...baseFilter }, - { ...baseFilter, id: 'filter-2', fieldMetadataId: 'field-2' }, + { ...baseFilter } satisfies ViewFilter, + { + ...baseFilter, + id: 'filter-2', + } satisfies ViewFilter, ]; const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); @@ -40,8 +43,7 @@ describe('getViewFiltersToCreate', () => { const newFilterWithDifferentFieldMetadata = { ...baseFilter, id: 'filter-2', - fieldMetadataId: 'field-2', - }; + } satisfies ViewFilter; const currentViewFilters: ViewFilter[] = [existingFilter]; @@ -55,25 +57,6 @@ describe('getViewFiltersToCreate', () => { expect(result).toEqual([newFilterWithDifferentFieldMetadata]); }); - it('should handle filters with different viewFilterGroupIds', () => { - const existingFilter = { ...baseFilter }; - const filterWithDifferentGroup = { - ...baseFilter, - viewFilterGroupId: 'group-2', - }; - - const currentViewFilters: ViewFilter[] = [existingFilter]; - - const newViewFilters: ViewFilter[] = [ - existingFilter, - filterWithDifferentGroup, - ]; - - const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); - - expect(result).toEqual([filterWithDifferentGroup]); - }); - it('should handle empty arrays for both inputs', () => { const currentViewFilters: ViewFilter[] = []; const newViewFilters: ViewFilter[] = []; @@ -82,36 +65,4 @@ describe('getViewFiltersToCreate', () => { expect(result).toEqual([]); }); - - it('should consider filters with same fieldMetadataId but different viewFilterGroupId as new', () => { - const currentViewFilters: ViewFilter[] = [baseFilter]; - const filterWithSameFieldMetadataIdButDifferentGroup = { - ...baseFilter, - id: 'filter-2', - viewFilterGroupId: 'group-2', - }; - const newViewFilters: ViewFilter[] = [ - filterWithSameFieldMetadataIdButDifferentGroup, - ]; - - const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); - - expect(result).toEqual([filterWithSameFieldMetadataIdButDifferentGroup]); - }); - - it('should consider filters with same viewFilterGroupId but different fieldMetadataId as new', () => { - const currentViewFilters: ViewFilter[] = [baseFilter]; - const filterWithSameGroupButDifferentFieldMetadata = { - ...baseFilter, - id: 'filter-2', - fieldMetadataId: 'field-2', - }; - const newViewFilters: ViewFilter[] = [ - filterWithSameGroupButDifferentFieldMetadata, - ]; - - const result = getViewFiltersToCreate(currentViewFilters, newViewFilters); - - expect(result).toEqual([filterWithSameGroupButDifferentFieldMetadata]); - }); }); diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts index 0440b52362..07d3bee0e2 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts @@ -38,8 +38,7 @@ describe('getViewFiltersToDelete', () => { const filterToKeep = { ...baseFilter, id: 'filter-2', - fieldMetadataId: 'field-2', - }; + } satisfies ViewFilter; const currentViewFilters: ViewFilter[] = [filterToDelete, filterToKeep]; const newViewFilters: ViewFilter[] = [filterToKeep]; @@ -57,43 +56,4 @@ describe('getViewFiltersToDelete', () => { expect(result).toEqual([]); }); - - it('should identify filters to delete based on fieldMetadataId and viewFilterGroupId', () => { - const filterInGroup1 = { ...baseFilter }; - const filterInGroup2 = { - ...baseFilter, - viewFilterGroupId: 'group-2', - }; - const filterWithDifferentField = { - ...baseFilter, - fieldMetadataId: 'field-2', - }; - - const currentViewFilters: ViewFilter[] = [ - filterInGroup1, - filterInGroup2, - filterWithDifferentField, - ]; - const newViewFilters: ViewFilter[] = [filterInGroup1]; - - const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); - - expect(result).toEqual([filterInGroup2, filterWithDifferentField]); - }); - - it('should not delete filters that match in both fieldMetadataId and viewFilterGroupId', () => { - const existingFilter = { ...baseFilter }; - const matchingFilter = { - ...baseFilter, - value: 'different-value', - displayValue: 'different-value', - }; - - const currentViewFilters: ViewFilter[] = [existingFilter]; - const newViewFilters: ViewFilter[] = [matchingFilter]; - - const result = getViewFiltersToDelete(currentViewFilters, newViewFilters); - - expect(result).toEqual([]); - }); }); diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts index 275f0d6c0b..8bdc845b93 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts @@ -33,12 +33,12 @@ describe('getViewFiltersToUpdate', () => { }); it('should return filters that exist in both arrays but have different values', () => { - const existingFilter = { ...baseFilter }; + const existingFilter = { ...baseFilter } satisfies ViewFilter; const updatedFilter = { ...baseFilter, value: 'updated-value', displayValue: 'updated-value', - }; + } satisfies ViewFilter; const currentViewFilters: ViewFilter[] = [existingFilter]; const newViewFilters: ViewFilter[] = [updatedFilter]; @@ -49,8 +49,8 @@ describe('getViewFiltersToUpdate', () => { }); it('should not return filters that exist in both arrays with same values', () => { - const existingFilter = { ...baseFilter }; - const sameFilter = { ...baseFilter }; + const existingFilter = { ...baseFilter } satisfies ViewFilter; + const sameFilter = { ...baseFilter } satisfies ViewFilter; const currentViewFilters: ViewFilter[] = [existingFilter]; const newViewFilters: ViewFilter[] = [sameFilter]; @@ -69,44 +69,12 @@ describe('getViewFiltersToUpdate', () => { expect(result).toEqual([]); }); - it('should not update filters with same fieldMetadataId but different viewFilterGroupId', () => { - const existingFilter = { ...baseFilter }; - const filterInDifferentGroup = { - ...baseFilter, - viewFilterGroupId: 'group-2', - value: 'updated-value', - }; - - const currentViewFilters: ViewFilter[] = [existingFilter]; - const newViewFilters: ViewFilter[] = [filterInDifferentGroup]; - - const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); - - expect(result).toEqual([]); - }); - - it('should not update filters with same viewFilterGroupId but different fieldMetadataId', () => { - const existingFilter = { ...baseFilter }; - const filterWithDifferentField = { - ...baseFilter, - fieldMetadataId: 'field-2', - value: 'updated-value', - }; - - const currentViewFilters: ViewFilter[] = [existingFilter]; - const newViewFilters: ViewFilter[] = [filterWithDifferentField]; - - const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters); - - expect(result).toEqual([]); - }); - it('should update filter when operand changes', () => { - const existingFilter = { ...baseFilter }; + const existingFilter = { ...baseFilter } satisfies ViewFilter; const filterWithNewOperand = { ...baseFilter, operand: ViewFilterOperand.DoesNotContain, - }; + } satisfies ViewFilter; const currentViewFilters: ViewFilter[] = [existingFilter]; const newViewFilters: ViewFilter[] = [filterWithNewOperand]; @@ -117,11 +85,11 @@ describe('getViewFiltersToUpdate', () => { }); it('should update filter when position changes', () => { - const existingFilter = { ...baseFilter }; + const existingFilter = { ...baseFilter } satisfies ViewFilter; const filterWithNewPosition = { ...baseFilter, positionInViewFilterGroup: 1, - }; + } satisfies ViewFilter; const currentViewFilters: ViewFilter[] = [existingFilter]; const newViewFilters: ViewFilter[] = [filterWithNewPosition]; diff --git a/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromView.ts b/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromView.ts index d67978a5e1..49457a24fb 100644 --- a/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromView.ts +++ b/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromView.ts @@ -4,8 +4,9 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; -import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { View } from '@/views/types/View'; +import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; import { isDefined } from 'twenty-shared'; @@ -30,13 +31,22 @@ export const getQueryVariablesFromView = ({ const { viewFilterGroups, viewFilters, viewSorts } = view; - const filter = computeViewRecordGqlOperationFilter( - filterValueDependencies, - mapViewFiltersToFilters(viewFilters, fieldMetadataItems), - objectMetadataItem?.fields ?? [], + const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups( viewFilterGroups ?? [], ); + const recordFilters = mapViewFiltersToFilters( + viewFilters, + fieldMetadataItems, + ); + + const filter = computeRecordGqlOperationFilter({ + fields: objectMetadataItem?.fields ?? [], + filterValueDependencies, + recordFilterGroups, + recordFilters, + }); + const orderBy = turnSortsIntoOrderBy( objectMetadataItem, mapViewSortsToSorts(viewSorts), diff --git a/packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts b/packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts index aff45654a8..cfc803f78c 100644 --- a/packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts +++ b/packages/twenty-front/src/modules/views/utils/getViewFiltersToCreate.ts @@ -1,5 +1,6 @@ import { ViewFilter } from '@/views/types/ViewFilter'; import { isDefined } from 'twenty-shared'; +import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined'; export const getViewFiltersToCreate = ( currentViewFilters: ViewFilter[], @@ -8,8 +9,10 @@ export const getViewFiltersToCreate = ( return newViewFilters.filter((newViewFilter) => { const correspondingViewFilter = currentViewFilters.find( (currentViewFilter) => - currentViewFilter.fieldMetadataId === newViewFilter.fieldMetadataId && - currentViewFilter.viewFilterGroupId === newViewFilter.viewFilterGroupId, + compareStrictlyExceptForNullAndUndefined( + currentViewFilter.id, + newViewFilter.id, + ), ); const shouldCreateBecauseViewFilterIsNew = !isDefined( diff --git a/packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts b/packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts index d6ee34cff9..59de70b93e 100644 --- a/packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts +++ b/packages/twenty-front/src/modules/views/utils/getViewFiltersToDelete.ts @@ -1,4 +1,5 @@ import { ViewFilter } from '@/views/types/ViewFilter'; +import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined'; export const getViewFiltersToDelete = ( currentViewFilters: ViewFilter[], @@ -6,11 +7,11 @@ export const getViewFiltersToDelete = ( ) => { return currentViewFilters.filter( (currentViewFilter) => - !newViewFilters.some( - (newViewFilter) => - newViewFilter.fieldMetadataId === currentViewFilter.fieldMetadataId && - newViewFilter.viewFilterGroupId === - currentViewFilter.viewFilterGroupId, + !newViewFilters.some((newViewFilter) => + compareStrictlyExceptForNullAndUndefined( + currentViewFilter.id, + newViewFilter.id, + ), ), ); }; diff --git a/packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts b/packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts index b0776841e5..ebf34e1b0a 100644 --- a/packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts +++ b/packages/twenty-front/src/modules/views/utils/getViewFiltersToUpdate.ts @@ -1,6 +1,7 @@ import { ViewFilter } from '@/views/types/ViewFilter'; import { areViewFiltersEqual } from '@/views/utils/areViewFiltersEqual'; import { isDefined } from 'twenty-shared'; +import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined'; export const getViewFiltersToUpdate = ( currentViewFilters: ViewFilter[], @@ -9,8 +10,10 @@ export const getViewFiltersToUpdate = ( return newViewFilters.filter((newViewFilter) => { const correspondingViewFilter = currentViewFilters.find( (currentViewFilter) => - currentViewFilter.fieldMetadataId === newViewFilter.fieldMetadataId && - currentViewFilter.viewFilterGroupId === newViewFilter.viewFilterGroupId, + compareStrictlyExceptForNullAndUndefined( + currentViewFilter.id, + newViewFilter.id, + ), ); if (!isDefined(correspondingViewFilter)) {