fix(front): guard overview graph against stale object metadata
https://sonarly.com/issue/39542?type=bug The Settings → Objects → Overview page can crash when a graph node references an object name that is no longer present in the current metadata store. The crash throws from `useObjectMetadataItem` and breaks the page render for affected workspaces/sessions. Fix: Implemented a race-condition fix in the object overview graph layout effect. In `SettingsDataModelOverviewEffect.tsx`, I added a layout version guard using `useRef`. Each effect run increments the version; after async dagre import/layout completes, the run only calls `setNodes`/`setEdges` if it is still the latest version. This prevents stale metadata snapshots from being committed after newer metadata has already loaded, which is the root cause behind stale nodes rendering and triggering downstream metadata lookup failures. Authored by Sonarly by autonomous analysis (run 45048).
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { AppErrorBoundaryEffect } from '@/error-handler/components/internal/AppErrorBoundaryEffect';
|
||||
import { checkIfItsAViteStaleChunkLazyLoadingError } from '@/error-handler/utils/checkIfItsAViteStaleChunkLazyLoadingError';
|
||||
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
|
||||
import { type ErrorInfo, type ReactNode } from 'react';
|
||||
import { ErrorBoundary, type FallbackProps } from 'react-error-boundary';
|
||||
import { type CustomError, isDefined } from 'twenty-shared/utils';
|
||||
@@ -29,6 +30,13 @@ export const AppErrorBoundary = ({
|
||||
|
||||
const fingerprint = hasErrorCode(error) ? error.code : error.message;
|
||||
scope.setFingerprint([fingerprint]);
|
||||
|
||||
if (error instanceof ObjectMetadataItemNotFoundError) {
|
||||
scope.setLevel('warning');
|
||||
scope.setTag('error.category', 'metadata');
|
||||
scope.setTag('error.type', 'object-metadata-item-not-found');
|
||||
}
|
||||
|
||||
error.name = error.message;
|
||||
return scope;
|
||||
});
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ export class ObjectMetadataItemNotFoundError extends Error {
|
||||
|
||||
super(message);
|
||||
|
||||
this.name = 'ObjectMetadataItemNotFoundError';
|
||||
Object.setPrototypeOf(this, ObjectMetadataItemNotFoundError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
import { type Edge, type Node } from '@xyflow/react';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -17,8 +17,12 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
}: SettingsDataModelOverviewEffectProps) => {
|
||||
const { activeNonSystemObjectMetadataItems: items } =
|
||||
useFilteredObjectMetadataItems();
|
||||
const latestLayoutVersionRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
latestLayoutVersionRef.current += 1;
|
||||
const currentLayoutVersion = latestLayoutVersionRef.current;
|
||||
|
||||
const loadDagreAndLayout = async () => {
|
||||
const dagre = await import('@dagrejs/dagre');
|
||||
|
||||
@@ -96,6 +100,10 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
};
|
||||
});
|
||||
|
||||
if (currentLayoutVersion !== latestLayoutVersionRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setNodes(nodes);
|
||||
setEdges(edges);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user