Move filter group destruction after filter operations (#22248)

### The current order is:

1. Create filter groups
2. Update filter groups
3. Destroy filter groups ← **happens here**
4. Clean up store (cascade)
5. Create/update/delete filters (which may reference groups just
destroyed)

The fix is to move filter group destruction after filter operations, so
filters that reference those groups get created/updated/deleted first.

### after fix : The persistence order is now:

1. Create filter groups
2. Update filter groups
3. Create/update/delete view filters (these can safely reference groups
that still exist)
4. Destroy filter groups (only after all filter mutations are done)
5. Clean up store (cascade-deleted filters

**root cause :** step 4 happened before step 3, so filter
creates/updates would reference groups that had already been deleted in
the same save cycle ; causing the backend to fail with "Migration
execution failed" when it couldn't resolve the `viewFilterGroupId
`foreign key.

this fixes the bug : #21351

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22248?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: Souheyl Gouadria <souheyl.gouadria@medius.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
souheyl gouadria
2026-06-29 15:54:15 +01:00
committed by GitHub
parent 4eb28b73c7
commit 1b2f2c71c3
@@ -89,21 +89,6 @@ export const useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters =
currentView,
);
await performViewFilterGroupAPIUpdate(viewFilterGroupsToUpdate);
await performViewFilterGroupAPIDestroy(viewFilterGroupIdsToDestroy);
// Mirror the DB cascade: remove cascade-deleted viewFilters from the store
if (viewFilterGroupIdsToDestroy.length > 0) {
const destroyedIdsSet = new Set(viewFilterGroupIdsToDestroy);
store.set(metadataStoreState.atomFamily('viewFilters'), (prev) => ({
...prev,
current: (prev.current as FlatViewFilter[]).filter(
(viewFilter) =>
!isDefined(viewFilter.viewFilterGroupId) ||
!destroyedIdsSet.has(viewFilter.viewFilterGroupId),
),
}));
}
const currentViewFilters = currentView.viewFilters ?? [];
const currentRecordFilters = store.get(
@@ -195,6 +180,22 @@ export const useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters =
if (deleteResult.status === 'failed') {
return;
}
await performViewFilterGroupAPIDestroy(viewFilterGroupIdsToDestroy);
// Mirror the DB cascade: remove cascade-deleted viewFilters from the store
if (viewFilterGroupIdsToDestroy.length > 0) {
const destroyedIdsSet = new Set(viewFilterGroupIdsToDestroy);
store.set(metadataStoreState.atomFamily('viewFilters'), (prev) => ({
...prev,
current: (prev.current as FlatViewFilter[]).filter(
(viewFilter) =>
!isDefined(viewFilter.viewFilterGroupId) ||
!destroyedIdsSet.has(viewFilter.viewFilterGroupId),
),
}));
}
}, [
canPersistChanges,
currentView,