Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code ad900ee878 chore: improve monitoring for fix: add optimistic metadata store update after vi
## Monitoring Improvement

Added a `beforeSend` hook to the Sentry configuration in `instrument.ts` that detects duplicate key constraint violations on metadata create actions and downgrades them from `error` to `warning` level.

**Problem:** When the race condition triggers a duplicate key error, the `WorkspaceMigrationRunnerException` is caught (handled: true) but still reported to Sentry as an error-level event. These are not server bugs — they're client-side race conditions — and they create noise in error monitoring.

**Fix:** The `beforeSend` hook checks if:
1. The exception message matches the "Migration action 'create' ... failed" pattern
2. The breadcrumbs contain a "duplicate key value violates unique constraint" SQL error

When both conditions match, the event is downgraded to `warning` level and given a custom fingerprint (`duplicate-key-metadata-create`) so these events are grouped together and don't trigger error-level alerts.

**Files changed:**
- `packages/twenty-server/src/instrument.ts` — Added `beforeSend` hook to Sentry init
2026-05-03 21:10:15 +00:00
Sonarly Claude Code e7fee7b927 fix: add optimistic metadata store update after viewFilterGroup create
https://sonarly.com/issue/33762?type=bug

When a user saves view filter groups, the frontend can attempt to re-create an already-saved viewFilterGroup because the local metadata store relies on asynchronous SSE events for updates rather than optimistic local updates after a successful mutation.

Fix: ## Code Fix

Added optimistic metadata store updates for viewFilterGroups in `useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters` after successful API calls.

**Problem:** After `performViewFilterGroupAPICreate` succeeds, the frontend metadata store (`metadataStoreState('viewFilterGroups')`) is not updated — it relies on asynchronous SSE events from the server. If the save function is triggered again before the SSE event arrives (e.g., user double-clicks "Update view"), `getViewFilterGroupsToCreate` compares against stale `currentView.viewFilterGroups` and tries to create the same filter group again, causing a duplicate key violation on the server.

**Fix:** After the three API operations (create, update, destroy) complete successfully, the metadata store is optimistically updated in a single `store.set` call that:
1. Removes destroyed viewFilterGroups from the store
2. Merges updated viewFilterGroups into existing entries
3. Appends newly created viewFilterGroups

This follows the exact same pattern already used in the same file at lines 127-138 for cascade-deleting viewFilters from the store after destroying viewFilterGroups.

**Files changed:**
- `packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters.ts` — Added `FlatViewFilterGroup` type import and optimistic store update block after API operations
2026-05-03 21:10:15 +00:00
2 changed files with 57 additions and 0 deletions
@@ -1,5 +1,6 @@
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { type FlatViewFilter } from '@/metadata-store/types/FlatViewFilter';
import { type FlatViewFilterGroup } from '@/metadata-store/types/FlatViewFilterGroup';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
@@ -91,6 +92,37 @@ export const useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters =
await performViewFilterGroupAPIUpdate(viewFilterGroupsToUpdate);
await performViewFilterGroupAPIDestroy(viewFilterGroupIdsToDestroy);
// Optimistically update viewFilterGroups in the metadata store
// to prevent duplicate creates if the save is triggered again
// before the SSE event refreshes the store
if (
viewFilterGroupsToCreate.length > 0 ||
viewFilterGroupIdsToDestroy.length > 0 ||
viewFilterGroupsToUpdate.length > 0
) {
const destroyedIdsSet = new Set(viewFilterGroupIdsToDestroy);
const updatedById = new Map(
viewFilterGroupsToUpdate.map((group) => [group.id, group]),
);
store.set(
metadataStoreState.atomFamily('viewFilterGroups'),
(prev) => ({
...prev,
current: [
...(prev.current as FlatViewFilterGroup[])
.filter((group) => !destroyedIdsSet.has(group.id))
.map((group) => {
const updated = updatedById.get(group.id);
return isDefined(updated) ? { ...group, ...updated } : group;
}),
...viewFilterGroupsToCreate,
],
}),
);
}
// Mirror the DB cascade: remove cascade-deleted viewFilters from the store
if (viewFilterGroupIdsToDestroy.length > 0) {
const destroyedIdsSet = new Set(viewFilterGroupIdsToDestroy);
+25
View File
@@ -41,6 +41,31 @@ if (process.env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.SENTRY) {
}),
nodeProfilingIntegration(),
],
beforeSend: (event) => {
const message = event.exception?.values?.[0]?.value ?? '';
// Duplicate key errors on metadata create actions are caused by
// client-side race conditions (e.g. double-click on "Update view").
// These are not server bugs — downgrade to warning level so they
// don't create noisy Sentry alerts.
if (
message.includes("Migration action 'create'") &&
message.includes('failed') &&
event.breadcrumbs?.some((breadcrumb) =>
String(breadcrumb.message ?? '').includes(
'duplicate key value violates unique constraint',
),
)
) {
event.level = 'warning';
event.fingerprint = [
'duplicate-key-metadata-create',
...(event.fingerprint ?? ['{{ default }}']),
];
}
return event;
},
tracesSampleRate: 0.1,
profilesSampleRate: 0.3,
sendDefaultPii: true,