Refactored record filter saving to view filters (#9844)

This PR refactors the record filter saving to view filters.

Before we used states to track the change of view filters, now we just
check if there's a difference between the current record filters and the
current view filters before saving.

We also use this check to show the reset and save buttons.

CRUD operations to perform on view filters are computed by utils , and .

Also added unit tests on those utils.
This commit is contained in:
Lucas Bordeau
2025-01-24 18:48:59 +01:00
committed by GitHub
parent a4011676f0
commit 570b2e3530
17 changed files with 748 additions and 143 deletions

View File

@ -0,0 +1,46 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate';
import { getViewFiltersToDelete } from '@/views/utils/getViewFiltersToDelete';
import { getViewFiltersToUpdate } from '@/views/utils/getViewFiltersToUpdate';
import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter';
import { useMemo } from 'react';
export const useAreViewFiltersDifferentFromRecordFilters = () => {
const { currentView } = useGetCurrentViewOnly();
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const viewFiltersAreDifferentFromRecordFilters = useMemo(() => {
const currentViewFilters = currentView?.viewFilters ?? [];
const viewFiltersFromCurrentRecordFilters = currentRecordFilters.map(
mapRecordFilterToViewFilter,
);
const viewFiltersToCreate = getViewFiltersToCreate(
currentViewFilters,
viewFiltersFromCurrentRecordFilters,
);
const viewFiltersToDelete = getViewFiltersToDelete(
currentViewFilters,
viewFiltersFromCurrentRecordFilters,
);
const viewFiltersToUpdate = getViewFiltersToUpdate(
currentViewFilters,
viewFiltersFromCurrentRecordFilters,
);
const filtersHaveChanged =
viewFiltersToCreate.length > 0 ||
viewFiltersToDelete.length > 0 ||
viewFiltersToUpdate.length > 0;
return filtersHaveChanged;
}, [currentRecordFilters, currentView]);
return { viewFiltersAreDifferentFromRecordFilters };
};

View File

@ -0,0 +1,14 @@
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { areViewSortsDifferentFromRecordSortsSelector } from '@/views/states/selectors/areViewSortsDifferentFromRecordSortsFamilySelector';
export const useAreViewSortsDifferentFromRecordSorts = () => {
const { currentView } = useGetCurrentViewOnly();
const viewSortsAreDifferentFromRecordSorts = useRecoilComponentFamilyValueV2(
areViewSortsDifferentFromRecordSortsSelector,
{ viewId: currentView?.id },
);
return { viewSortsAreDifferentFromRecordSorts };
};

View File

@ -0,0 +1,22 @@
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { View } from '@/views/types/View';
import { useMemo } from 'react';
export const useGetCurrentViewOnly = () => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState);
const currentView = useMemo(
() => views.find((view) => view.id === currentViewId),
[views, currentViewId],
);
return {
currentView,
};
};

View File

@ -3,16 +3,14 @@ import { useRecoilCallback } from 'recoil';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords';
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { useSaveRecordFiltersToViewFilters } from '@/views/hooks/useSaveRecordFiltersToViewFilters';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { unsavedToDeleteViewFilterGroupIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterGroupIdsComponentFamilyState';
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState';
import { unsavedToUpsertViewFilterGroupsComponentFamilyState } from '@/views/states/unsavedToUpsertViewFilterGroupsComponentFamilyState';
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -39,18 +37,6 @@ export const useSaveCurrentViewFiltersAndSorts = (
viewBarComponentId,
);
const unsavedToDeleteViewFilterIdsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToDeleteViewFilterIdsComponentFamilyState,
viewBarComponentId,
);
const unsavedToUpsertViewFiltersCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFiltersComponentFamilyState,
viewBarComponentId,
);
const unsavedToUpsertViewFilterGroupsCallbackState =
useRecoilComponentCallbackStateV2(
unsavedToUpsertViewFilterGroupsComponentFamilyState,
@ -69,12 +55,6 @@ export const useSaveCurrentViewFiltersAndSorts = (
deleteViewSortRecords,
} = usePersistViewSortRecords();
const {
createViewFilterRecords,
updateViewFilterRecords,
deleteViewFilterRecords,
} = usePersistViewFilterRecords();
const {
createViewFilterGroupRecords,
deleteViewFilterGroupRecords,
@ -130,53 +110,6 @@ export const useSaveCurrentViewFiltersAndSorts = (
],
);
const saveViewFilters = useRecoilCallback(
({ snapshot }) =>
async (viewId: string) => {
const unsavedToDeleteViewFilterIds = getSnapshotValue(
snapshot,
unsavedToDeleteViewFilterIdsCallbackState({ viewId }),
);
const unsavedToUpsertViewFilters = getSnapshotValue(
snapshot,
unsavedToUpsertViewFiltersCallbackState({ viewId }),
);
const view = await getViewFromCache(viewId);
if (isUndefinedOrNull(view)) {
return;
}
const viewFiltersToCreate = unsavedToUpsertViewFilters.filter(
(viewFilter) =>
!view.viewFilters.some(
(viewFilterToFilter) => viewFilterToFilter.id === viewFilter.id,
),
);
const viewFiltersToUpdate = unsavedToUpsertViewFilters.filter(
(viewFilter) =>
view.viewFilters.some(
(viewFilterToFilter) => viewFilterToFilter.id === viewFilter.id,
),
);
await createViewFilterRecords(viewFiltersToCreate, view);
await updateViewFilterRecords(viewFiltersToUpdate);
await deleteViewFilterRecords(unsavedToDeleteViewFilterIds);
},
[
createViewFilterRecords,
deleteViewFilterRecords,
getViewFromCache,
unsavedToDeleteViewFilterIdsCallbackState,
unsavedToUpsertViewFiltersCallbackState,
updateViewFilterRecords,
],
);
const saveViewFilterGroups = useRecoilCallback(
({ snapshot }) =>
async (viewId: string) => {
@ -226,6 +159,9 @@ export const useSaveCurrentViewFiltersAndSorts = (
],
);
const { saveRecordFiltersToViewFilters } =
useSaveRecordFiltersToViewFilters();
const saveCurrentViewFilterAndSorts = useRecoilCallback(
({ snapshot }) =>
async (viewIdFromProps?: string) => {
@ -240,17 +176,18 @@ export const useSaveCurrentViewFiltersAndSorts = (
const viewId = viewIdFromProps ?? currentViewId;
await saveViewFilterGroups(viewId);
await saveViewFilters(viewId);
await saveViewSorts(viewId);
await saveRecordFiltersToViewFilters();
resetUnsavedViewStates(viewId);
},
[
currentViewIdCallbackState,
resetUnsavedViewStates,
saveViewFilters,
saveViewSorts,
saveViewFilterGroups,
saveRecordFiltersToViewFilters,
],
);

View File

@ -0,0 +1,79 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate';
import { getViewFiltersToDelete } from '@/views/utils/getViewFiltersToDelete';
import { getViewFiltersToUpdate } from '@/views/utils/getViewFiltersToUpdate';
import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-ui';
export const useSaveRecordFiltersToViewFilters = () => {
const {
createViewFilterRecords,
updateViewFilterRecords,
deleteViewFilterRecords,
} = usePersistViewFilterRecords();
const { currentView } = useGetCurrentViewOnly();
const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2(
currentRecordFiltersComponentState,
);
const saveRecordFiltersToViewFilters = useRecoilCallback(
({ snapshot }) =>
async () => {
if (!isDefined(currentView)) {
return;
}
const currentViewFilters = currentView?.viewFilters ?? [];
const currentRecordFilters = getSnapshotValue(
snapshot,
currentRecordFiltersCallbackState,
);
const newViewFilters = currentRecordFilters.map(
mapRecordFilterToViewFilter,
);
const viewFiltersToCreate = getViewFiltersToCreate(
currentViewFilters,
newViewFilters,
);
const viewFiltersToDelete = getViewFiltersToDelete(
currentViewFilters,
newViewFilters,
);
const viewFiltersToUpdate = getViewFiltersToUpdate(
currentViewFilters,
newViewFilters,
);
const viewFilterIdsToDelete = viewFiltersToDelete.map(
(viewFilter) => viewFilter.id,
);
await createViewFilterRecords(viewFiltersToCreate, currentView);
await updateViewFilterRecords(viewFiltersToUpdate);
await deleteViewFilterRecords(viewFilterIdsToDelete);
},
[
createViewFilterRecords,
deleteViewFilterRecords,
updateViewFilterRecords,
currentRecordFiltersCallbackState,
currentView,
],
);
return {
saveRecordFiltersToViewFilters,
};
};