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:
@ -11,15 +11,15 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { UPDATE_VIEW_BUTTON_DROPDOWN_ID } from '@/views/constants/UpdateViewButtonDropdownId';
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters';
|
||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { canPersistViewComponentFamilySelector } from '@/views/states/selectors/canPersistViewComponentFamilySelector';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
|
||||
@ -46,11 +46,6 @@ export const UpdateViewButtonGroup = ({
|
||||
|
||||
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState);
|
||||
|
||||
const canPersistView = useRecoilComponentFamilyValueV2(
|
||||
canPersistViewComponentFamilySelector,
|
||||
{ viewId: currentViewId },
|
||||
);
|
||||
|
||||
const { closeDropdown: closeUpdateViewButtonDropdown } = useDropdown(
|
||||
UPDATE_VIEW_BUTTON_DROPDOWN_ID,
|
||||
);
|
||||
@ -89,7 +84,16 @@ export const UpdateViewButtonGroup = ({
|
||||
|
||||
const { hasFiltersQueryParams } = useViewFromQueryParams();
|
||||
|
||||
const canShowButton = canPersistView && !hasFiltersQueryParams;
|
||||
const { viewFiltersAreDifferentFromRecordFilters } =
|
||||
useAreViewFiltersDifferentFromRecordFilters();
|
||||
|
||||
const { viewSortsAreDifferentFromRecordSorts } =
|
||||
useAreViewSortsDifferentFromRecordSorts();
|
||||
|
||||
const canShowButton =
|
||||
(viewFiltersAreDifferentFromRecordFilters ||
|
||||
viewSortsAreDifferentFromRecordSorts) &&
|
||||
!hasFiltersQueryParams;
|
||||
|
||||
if (!canShowButton) {
|
||||
return <></>;
|
||||
|
||||
@ -4,10 +4,8 @@ import { ReactNode, useMemo } from 'react';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { AddObjectFilterFromDetailsButton } from '@/object-record/object-filter-dropdown/components/AddObjectFilterFromDetailsButton';
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { AdvancedFilterDropdownButton } from '@/views/components/AdvancedFilterDropdownButton';
|
||||
import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton';
|
||||
@ -15,14 +13,14 @@ import { EditableSortChip } from '@/views/components/EditableSortChip';
|
||||
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters';
|
||||
import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAreViewFiltersDifferentFromRecordFilters';
|
||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
|
||||
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
||||
import { canPersistViewComponentFamilySelector } from '@/views/states/selectors/canPersistViewComponentFamilySelector';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { VariantFilterChip } from './VariantFilterChip';
|
||||
@ -118,13 +116,8 @@ export const ViewBarDetails = ({
|
||||
|
||||
const { hasFiltersQueryParams } = useViewFromQueryParams();
|
||||
|
||||
const canPersistView = useRecoilComponentFamilyValueV2(
|
||||
canPersistViewComponentFamilySelector,
|
||||
{ viewId },
|
||||
);
|
||||
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
const currentRecordFilters = useRecoilComponentValueV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
const availableSortDefinitions = useRecoilComponentValueV2(
|
||||
@ -139,35 +132,34 @@ export const ViewBarDetails = ({
|
||||
viewBarId: viewBarId,
|
||||
});
|
||||
const { resetUnsavedViewStates } = useResetUnsavedViewStates();
|
||||
const canResetView = canPersistView && !hasFiltersQueryParams;
|
||||
|
||||
const { otherViewFilters, defaultViewFilters } = useMemo(() => {
|
||||
if (!currentViewWithCombinedFiltersAndSorts) {
|
||||
return {
|
||||
otherViewFilters: [],
|
||||
defaultViewFilters: [],
|
||||
};
|
||||
}
|
||||
const { viewFiltersAreDifferentFromRecordFilters } =
|
||||
useAreViewFiltersDifferentFromRecordFilters();
|
||||
|
||||
const otherViewFilters =
|
||||
currentViewWithCombinedFiltersAndSorts.viewFilters.filter(
|
||||
(viewFilter) =>
|
||||
viewFilter.variant &&
|
||||
viewFilter.variant !== 'default' &&
|
||||
!viewFilter.viewFilterGroupId,
|
||||
);
|
||||
const defaultViewFilters =
|
||||
currentViewWithCombinedFiltersAndSorts.viewFilters.filter(
|
||||
(viewFilter) =>
|
||||
(!viewFilter.variant || viewFilter.variant === 'default') &&
|
||||
!viewFilter.viewFilterGroupId,
|
||||
);
|
||||
const { viewSortsAreDifferentFromRecordSorts } =
|
||||
useAreViewSortsDifferentFromRecordSorts();
|
||||
|
||||
return {
|
||||
otherViewFilters,
|
||||
defaultViewFilters,
|
||||
};
|
||||
}, [currentViewWithCombinedFiltersAndSorts]);
|
||||
const canResetView =
|
||||
(viewFiltersAreDifferentFromRecordFilters ||
|
||||
viewSortsAreDifferentFromRecordSorts) &&
|
||||
!hasFiltersQueryParams;
|
||||
|
||||
const otherViewFilters = useMemo(() => {
|
||||
return currentRecordFilters.filter(
|
||||
(viewFilter) =>
|
||||
viewFilter.variant &&
|
||||
viewFilter.variant !== 'default' &&
|
||||
!viewFilter.viewFilterGroupId,
|
||||
);
|
||||
}, [currentRecordFilters]);
|
||||
|
||||
const defaultViewFilters = useMemo(() => {
|
||||
return currentRecordFilters.filter(
|
||||
(viewFilter) =>
|
||||
(!viewFilter.variant || viewFilter.variant === 'default') &&
|
||||
!viewFilter.viewFilterGroupId,
|
||||
);
|
||||
}, [currentRecordFilters]);
|
||||
|
||||
const { applyCurrentViewFiltersToCurrentRecordFilters } =
|
||||
useApplyCurrentViewFiltersToCurrentRecordFilters();
|
||||
@ -181,9 +173,9 @@ export const ViewBarDetails = ({
|
||||
};
|
||||
|
||||
const shouldExpandViewBar =
|
||||
canPersistView ||
|
||||
viewFiltersAreDifferentFromRecordFilters ||
|
||||
((currentViewWithCombinedFiltersAndSorts?.viewSorts?.length ||
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters?.length) &&
|
||||
currentRecordFilters?.length) &&
|
||||
isViewBarExpanded);
|
||||
|
||||
if (!shouldExpandViewBar) {
|
||||
@ -201,11 +193,7 @@ export const ViewBarDetails = ({
|
||||
{otherViewFilters.map((viewFilter) => (
|
||||
<VariantFilterChip
|
||||
key={viewFilter.fieldMetadataId}
|
||||
// Why do we have two types, Filter and ViewFilter?
|
||||
// Why key defition is already present in the Filter type and added on the fly here with mapViewFiltersToFilters ?
|
||||
// Also as filter is spread into viewFilter, definition is present
|
||||
// FixMe: Ugly hack to make it work
|
||||
viewFilter={viewFilter as unknown as RecordFilter}
|
||||
viewFilter={viewFilter}
|
||||
viewBarId={viewBarId}
|
||||
/>
|
||||
))}
|
||||
@ -228,10 +216,7 @@ export const ViewBarDetails = ({
|
||||
</StyledSeperatorContainer>
|
||||
)}
|
||||
{showAdvancedFilterDropdownButton && <AdvancedFilterDropdownButton />}
|
||||
{mapViewFiltersToFilters(
|
||||
defaultViewFilters,
|
||||
availableFilterDefinitions,
|
||||
).map((viewFilter) => (
|
||||
{defaultViewFilters.map((viewFilter) => (
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
key={viewFilter.id}
|
||||
value={{ instanceId: viewFilter.id }}
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
@ -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 };
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -1,35 +1,21 @@
|
||||
import { createComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { unsavedToDeleteViewFilterIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewFilterIdsComponentFamilyState';
|
||||
import { unsavedToDeleteViewSortIdsComponentFamilyState } from '@/views/states/unsavedToDeleteViewSortIdsComponentFamilyState';
|
||||
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
|
||||
import { unsavedToUpsertViewSortsComponentFamilyState } from '@/views/states/unsavedToUpsertViewSortsComponentFamilyState';
|
||||
|
||||
export const canPersistViewComponentFamilySelector =
|
||||
export const areViewSortsDifferentFromRecordSortsSelector =
|
||||
createComponentFamilySelectorV2<boolean, { viewId?: string }>({
|
||||
key: 'canPersistViewComponentFamilySelector',
|
||||
key: 'areViewSortsDifferentFromRecordSortsSelector',
|
||||
get:
|
||||
({ familyKey, instanceId }) =>
|
||||
({ get }) => {
|
||||
return (
|
||||
get(
|
||||
unsavedToUpsertViewFiltersComponentFamilyState.atomFamily({
|
||||
familyKey,
|
||||
instanceId,
|
||||
}),
|
||||
).length > 0 ||
|
||||
get(
|
||||
unsavedToUpsertViewSortsComponentFamilyState.atomFamily({
|
||||
familyKey,
|
||||
instanceId,
|
||||
}),
|
||||
).length > 0 ||
|
||||
get(
|
||||
unsavedToDeleteViewFilterIdsComponentFamilyState.atomFamily({
|
||||
familyKey,
|
||||
instanceId,
|
||||
}),
|
||||
).length > 0 ||
|
||||
get(
|
||||
unsavedToDeleteViewSortIdsComponentFamilyState.atomFamily({
|
||||
familyKey,
|
||||
@ -0,0 +1,89 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { areViewFiltersEqual } from '../areViewFiltersEqual';
|
||||
|
||||
describe('areViewFiltersEqual', () => {
|
||||
const baseFilter: ViewFilter = {
|
||||
__typename: 'ViewFilter',
|
||||
id: 'filter-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
};
|
||||
|
||||
it('should return true when all comparable properties are equal', () => {
|
||||
const filterA = { ...baseFilter };
|
||||
const filterB = { ...baseFilter };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when displayValue is different', () => {
|
||||
const filterA = { ...baseFilter };
|
||||
const filterB = { ...baseFilter, displayValue: 'different' };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when fieldMetadataId is different', () => {
|
||||
const filterA = { ...baseFilter };
|
||||
const filterB = { ...baseFilter, fieldMetadataId: 'field-2' };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when viewFilterGroupId is different', () => {
|
||||
const filterA = { ...baseFilter };
|
||||
const filterB = { ...baseFilter, viewFilterGroupId: 'group-2' };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when operand is different', () => {
|
||||
const filterA = { ...baseFilter };
|
||||
const filterB = {
|
||||
...baseFilter,
|
||||
operand: ViewFilterOperand.DoesNotContain,
|
||||
};
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when positionInViewFilterGroup is different', () => {
|
||||
const filterA = { ...baseFilter };
|
||||
const filterB = { ...baseFilter, positionInViewFilterGroup: 1 };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when value is different', () => {
|
||||
const filterA = { ...baseFilter };
|
||||
const filterB = { ...baseFilter, value: 'different' };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should ignore non-comparable properties', () => {
|
||||
const filterA = { ...baseFilter, id: 'id-1', createdAt: '2023-01-01' };
|
||||
const filterB = { ...baseFilter, id: 'id-2', createdAt: '2023-01-02' };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle undefined optional properties', () => {
|
||||
const filterA = { ...baseFilter, viewFilterGroupId: undefined };
|
||||
const filterB = { ...baseFilter, viewFilterGroupId: undefined };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle one filter having optional property and other not', () => {
|
||||
const filterA = { ...baseFilter, viewFilterGroupId: 'group-1' };
|
||||
const filterB = { ...baseFilter, viewFilterGroupId: undefined };
|
||||
|
||||
expect(areViewFiltersEqual(filterA, filterB)).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,117 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getViewFiltersToCreate } from '../getViewFiltersToCreate';
|
||||
|
||||
describe('getViewFiltersToCreate', () => {
|
||||
const baseFilter: ViewFilter = {
|
||||
__typename: 'ViewFilter',
|
||||
id: 'filter-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
};
|
||||
|
||||
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' },
|
||||
];
|
||||
|
||||
const result = getViewFiltersToCreate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual(newViewFilters);
|
||||
});
|
||||
|
||||
it('should return empty array when new filters array is empty', () => {
|
||||
const currentViewFilters: ViewFilter[] = [baseFilter];
|
||||
const newViewFilters: ViewFilter[] = [];
|
||||
|
||||
const result = getViewFiltersToCreate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return only filters that do not exist in current filters', () => {
|
||||
const existingFilter = { ...baseFilter };
|
||||
const newFilterWithDifferentFieldMetadata = {
|
||||
...baseFilter,
|
||||
id: 'filter-2',
|
||||
fieldMetadataId: 'field-2',
|
||||
};
|
||||
|
||||
const currentViewFilters: ViewFilter[] = [existingFilter];
|
||||
|
||||
const newViewFilters: ViewFilter[] = [
|
||||
existingFilter,
|
||||
newFilterWithDifferentFieldMetadata,
|
||||
];
|
||||
|
||||
const result = getViewFiltersToCreate(currentViewFilters, newViewFilters);
|
||||
|
||||
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[] = [];
|
||||
|
||||
const result = getViewFiltersToCreate(currentViewFilters, newViewFilters);
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,99 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getViewFiltersToDelete } from '../getViewFiltersToDelete';
|
||||
|
||||
describe('getViewFiltersToDelete', () => {
|
||||
const baseFilter: ViewFilter = {
|
||||
__typename: 'ViewFilter',
|
||||
id: 'filter-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
};
|
||||
|
||||
it('should return empty array when current filters array is empty', () => {
|
||||
const currentViewFilters: ViewFilter[] = [];
|
||||
const newViewFilters: ViewFilter[] = [baseFilter];
|
||||
|
||||
const result = getViewFiltersToDelete(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return all current filters when new filters array is empty', () => {
|
||||
const existingFilter = { ...baseFilter };
|
||||
const currentViewFilters: ViewFilter[] = [existingFilter];
|
||||
const newViewFilters: ViewFilter[] = [];
|
||||
|
||||
const result = getViewFiltersToDelete(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([existingFilter]);
|
||||
});
|
||||
|
||||
it('should return filters that exist in current but not in new filters', () => {
|
||||
const filterToDelete = { ...baseFilter };
|
||||
const filterToKeep = {
|
||||
...baseFilter,
|
||||
id: 'filter-2',
|
||||
fieldMetadataId: 'field-2',
|
||||
};
|
||||
|
||||
const currentViewFilters: ViewFilter[] = [filterToDelete, filterToKeep];
|
||||
const newViewFilters: ViewFilter[] = [filterToKeep];
|
||||
|
||||
const result = getViewFiltersToDelete(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([filterToDelete]);
|
||||
});
|
||||
|
||||
it('should handle empty arrays for both inputs', () => {
|
||||
const currentViewFilters: ViewFilter[] = [];
|
||||
const newViewFilters: ViewFilter[] = [];
|
||||
|
||||
const result = getViewFiltersToDelete(currentViewFilters, newViewFilters);
|
||||
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,133 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getViewFiltersToUpdate } from '../getViewFiltersToUpdate';
|
||||
|
||||
describe('getViewFiltersToUpdate', () => {
|
||||
const baseFilter: ViewFilter = {
|
||||
__typename: 'ViewFilter',
|
||||
id: 'filter-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'test',
|
||||
displayValue: 'test',
|
||||
viewFilterGroupId: 'group-1',
|
||||
positionInViewFilterGroup: 0,
|
||||
};
|
||||
|
||||
it('should return empty array when current filters array is empty', () => {
|
||||
const currentViewFilters: ViewFilter[] = [];
|
||||
const newViewFilters: ViewFilter[] = [baseFilter];
|
||||
|
||||
const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when new filters array is empty', () => {
|
||||
const currentViewFilters: ViewFilter[] = [baseFilter];
|
||||
const newViewFilters: ViewFilter[] = [];
|
||||
|
||||
const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return filters that exist in both arrays but have different values', () => {
|
||||
const existingFilter = { ...baseFilter };
|
||||
const updatedFilter = {
|
||||
...baseFilter,
|
||||
value: 'updated-value',
|
||||
displayValue: 'updated-value',
|
||||
};
|
||||
|
||||
const currentViewFilters: ViewFilter[] = [existingFilter];
|
||||
const newViewFilters: ViewFilter[] = [updatedFilter];
|
||||
|
||||
const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([updatedFilter]);
|
||||
});
|
||||
|
||||
it('should not return filters that exist in both arrays with same values', () => {
|
||||
const existingFilter = { ...baseFilter };
|
||||
const sameFilter = { ...baseFilter };
|
||||
|
||||
const currentViewFilters: ViewFilter[] = [existingFilter];
|
||||
const newViewFilters: ViewFilter[] = [sameFilter];
|
||||
|
||||
const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty arrays for both inputs', () => {
|
||||
const currentViewFilters: ViewFilter[] = [];
|
||||
const newViewFilters: ViewFilter[] = [];
|
||||
|
||||
const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters);
|
||||
|
||||
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 filterWithNewOperand = {
|
||||
...baseFilter,
|
||||
operand: ViewFilterOperand.DoesNotContain,
|
||||
};
|
||||
|
||||
const currentViewFilters: ViewFilter[] = [existingFilter];
|
||||
const newViewFilters: ViewFilter[] = [filterWithNewOperand];
|
||||
|
||||
const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([filterWithNewOperand]);
|
||||
});
|
||||
|
||||
it('should update filter when position changes', () => {
|
||||
const existingFilter = { ...baseFilter };
|
||||
const filterWithNewPosition = {
|
||||
...baseFilter,
|
||||
positionInViewFilterGroup: 1,
|
||||
};
|
||||
|
||||
const currentViewFilters: ViewFilter[] = [existingFilter];
|
||||
const newViewFilters: ViewFilter[] = [filterWithNewPosition];
|
||||
|
||||
const result = getViewFiltersToUpdate(currentViewFilters, newViewFilters);
|
||||
|
||||
expect(result).toEqual([filterWithNewPosition]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,19 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
|
||||
export const areViewFiltersEqual = (
|
||||
viewFilterA: ViewFilter,
|
||||
viewFilterB: ViewFilter,
|
||||
) => {
|
||||
const propertiesToCompare: (keyof ViewFilter)[] = [
|
||||
'fieldMetadataId',
|
||||
'viewFilterGroupId',
|
||||
'positionInViewFilterGroup',
|
||||
'value',
|
||||
'displayValue',
|
||||
'operand',
|
||||
];
|
||||
|
||||
return propertiesToCompare.every(
|
||||
(property) => viewFilterA[property] === viewFilterB[property],
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const getViewFiltersToCreate = (
|
||||
currentViewFilters: ViewFilter[],
|
||||
newViewFilters: ViewFilter[],
|
||||
) => {
|
||||
return newViewFilters.filter((newViewFilter) => {
|
||||
const correspondingViewFilter = currentViewFilters.find(
|
||||
(currentViewFilter) =>
|
||||
currentViewFilter.fieldMetadataId === newViewFilter.fieldMetadataId &&
|
||||
currentViewFilter.viewFilterGroupId === newViewFilter.viewFilterGroupId,
|
||||
);
|
||||
|
||||
const shouldCreateBecauseViewFilterIsNew = !isDefined(
|
||||
correspondingViewFilter,
|
||||
);
|
||||
|
||||
return shouldCreateBecauseViewFilterIsNew;
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
|
||||
export const getViewFiltersToDelete = (
|
||||
currentViewFilters: ViewFilter[],
|
||||
newViewFilters: ViewFilter[],
|
||||
) => {
|
||||
return currentViewFilters.filter(
|
||||
(currentViewFilter) =>
|
||||
!newViewFilters.some(
|
||||
(newViewFilter) =>
|
||||
newViewFilter.fieldMetadataId === currentViewFilter.fieldMetadataId &&
|
||||
newViewFilter.viewFilterGroupId ===
|
||||
currentViewFilter.viewFilterGroupId,
|
||||
),
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { areViewFiltersEqual } from '@/views/utils/areViewFiltersEqual';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const getViewFiltersToUpdate = (
|
||||
currentViewFilters: ViewFilter[],
|
||||
newViewFilters: ViewFilter[],
|
||||
) => {
|
||||
return newViewFilters.filter((newViewFilter) => {
|
||||
const correspondingViewFilter = currentViewFilters.find(
|
||||
(currentViewFilter) =>
|
||||
currentViewFilter.fieldMetadataId === newViewFilter.fieldMetadataId &&
|
||||
currentViewFilter.viewFilterGroupId === newViewFilter.viewFilterGroupId,
|
||||
);
|
||||
|
||||
if (!isDefined(correspondingViewFilter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shouldUpdateBecauseViewFilterIsDifferent = !areViewFiltersEqual(
|
||||
newViewFilter,
|
||||
correspondingViewFilter,
|
||||
);
|
||||
|
||||
return shouldUpdateBecauseViewFilterIsDifferent;
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
|
||||
export const mapRecordFilterToViewFilter = (
|
||||
recordFilter: RecordFilter,
|
||||
): ViewFilter => {
|
||||
return {
|
||||
__typename: 'ViewFilter',
|
||||
...recordFilter,
|
||||
} satisfies ViewFilter;
|
||||
};
|
||||
Reference in New Issue
Block a user