fix(front): read fresh metadata in SSE update path to avoid false unknown-field warnings (#22897)
## Problem Sentry `warning`: *"SSE update event for person carried fields unknown to this tab's metadata: lastInboundAt, pdlCertifications, pdlBirthYear, …"* ([TWENTY-FRONT issue](https://twenty-v7.sentry.io/issues/7610855477)). The listed fields are all custom fields created by the **People Data Labs app** (`packages/twenty-apps/public/people-data-labs`). The flow that triggers this: 1. The app installs a batch of `person` fields (emits metadata SSE events). 2. Its enrichment logic-function updates a person record with all of those fields (emits a record-update SSE event). Field creation already emits metadata SSE events, and the front applies them **synchronously** to the Jotai metadata store (`MetadataStoreSSEEffect` → `applyChanges` → `store.set`). So the store converges. The bug is that the SSE **record-update** handler doesn't read the converged store. `useTriggerOptimisticEffectFromSseUpdateEvents` reads `objectMetadataItems` from a React/Jotai closure captured at render time. The long-lived SSE subscription in `useTriggerEventStreamCreation` holds a metadata snapshot that lags the store, so even after the field-create events have been applied, the record path still sees the old field set. It then: - flags the new fields as "unknown" and logs to Sentry, and - **drops those field values** from the optimistic update (`getUnknownRecordInputFields` filters them out), so already-loaded views miss the enriched data until a refetch. Metadata events are dispatched before record events within each SSE message (`useTriggerEventStreamCreation` lines 126-128), and the store update is synchronous, so a fresh store read at processing time sees fields that converged in the same or any earlier message. ## Fix Read `objectMetadataItems` fresh from the Jotai store at event-processing time instead of from the render closure, and re-resolve the object metadata item from that fresh list. This eliminates the false-positive warnings and stops dropping legitimately-known field values. Follows the design from #22474: the metadata-event pipeline owns schema convergence; the record pipeline just reads the converged store (now actually reading the current store rather than a stale snapshot). A genuine race where the record update truly precedes the field-create event is still tolerated and still warns. ## Testing - `nx typecheck twenty-front` passes - `nx lint:diff-with-main twenty-front` passes <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22897?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. -->
This commit is contained in:
+14
-4
@@ -1,6 +1,6 @@
|
||||
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
@@ -13,6 +13,7 @@ import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useU
|
||||
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
|
||||
import { getUnknownRecordInputFields } from '@/object-record/utils/getUnknownRecordInputFields';
|
||||
import { captureMessage } from '@sentry/react';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import {
|
||||
@@ -21,8 +22,8 @@ import {
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useTriggerOptimisticEffectFromSseUpdateEvents = () => {
|
||||
const store = useStore();
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const { refetchAggregateQueriesForObjectMetadataItem } =
|
||||
useRefetchAggregateQueriesForObjectMetadataItem();
|
||||
@@ -31,11 +32,20 @@ export const useTriggerOptimisticEffectFromSseUpdateEvents = () => {
|
||||
const triggerOptimisticEffectFromSseUpdateEvents = useCallback(
|
||||
({
|
||||
objectRecordEvents,
|
||||
objectMetadataItem,
|
||||
objectMetadataItem: objectMetadataItemFromCaller,
|
||||
}: {
|
||||
objectRecordEvents: ObjectRecordEvent[];
|
||||
objectMetadataItem: EnrichedObjectMetadataItem;
|
||||
}) => {
|
||||
const objectMetadataItems = store.get(
|
||||
objectMetadataItemsWithFieldsSelector.atom,
|
||||
);
|
||||
|
||||
const objectMetadataItem =
|
||||
objectMetadataItems.find(
|
||||
(item) => item.id === objectMetadataItemFromCaller.id,
|
||||
) ?? objectMetadataItemFromCaller;
|
||||
|
||||
const updateEvents = objectRecordEvents.filter((objectRecordEvent) => {
|
||||
return objectRecordEvent.action === DatabaseEventAction.UPDATED;
|
||||
});
|
||||
@@ -164,8 +174,8 @@ export const useTriggerOptimisticEffectFromSseUpdateEvents = () => {
|
||||
return isNonEmptyArray(updateEvents);
|
||||
},
|
||||
[
|
||||
store,
|
||||
apolloCoreClient.cache,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
refetchAggregateQueriesForObjectMetadataItem,
|
||||
upsertRecordsInStore,
|
||||
|
||||
Reference in New Issue
Block a user