https://sonarly.com/issue/39756?type=bug Typing directly into a DATE cell in record table triggers an uncaught front-end exception and aborts inline edit. The failure is deterministic for this interaction path. Fix: I fixed the root cause by adding explicit DATE support in both draft-value utility paths used during inline edit initialization: - `computeDraftValueFromString` now treats DATE like other primitive string-backed field types (UUID/TEXT/DATE_TIME/NUMBER/RELATION), so keyboard-entered values no longer throw on DATE cells. - `computeEmptyDraftValue` now also handles DATE, preventing the same unsupported-type failure when empty draft initialization is used for DATE fields. - I also corrected the unsupported-type error message typo (`...${type}}` -> `...${type}`) in both utilities for cleaner diagnostics. To keep the fix production-safe and regression-resistant, I added utility-level tests that assert DATE returns: - the typed string in `computeDraftValueFromString` - an empty string in `computeEmptyDraftValue` Authored by Sonarly by autonomous analysis (run 45311).
101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
import { useCallback, useEffect } from 'react';
|
|
|
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|
import {
|
|
CombinedGraphQLErrors,
|
|
CombinedProtocolErrors,
|
|
LinkError,
|
|
LocalStateError,
|
|
ServerError,
|
|
ServerParseError,
|
|
UnconventionalError,
|
|
} from '@apollo/client/errors';
|
|
import { isDefined, isNonEmptyString, type CustomError } from 'twenty-shared/utils';
|
|
|
|
const isApolloError = (error: unknown): boolean =>
|
|
CombinedGraphQLErrors.is(error) ||
|
|
CombinedProtocolErrors.is(error) ||
|
|
LinkError.is(error) ||
|
|
LocalStateError.is(error) ||
|
|
ServerError.is(error) ||
|
|
ServerParseError.is(error) ||
|
|
UnconventionalError.is(error);
|
|
|
|
const hasErrorCode = (
|
|
error: CustomError | any,
|
|
): error is CustomError & { code: string } => {
|
|
return 'code' in error && isDefined(error.code);
|
|
};
|
|
|
|
export const PromiseRejectionEffect = () => {
|
|
const { enqueueErrorSnackBar } = useSnackBar();
|
|
|
|
const handlePromiseRejection = useCallback(
|
|
async (event: PromiseRejectionEvent) => {
|
|
const error = event.reason;
|
|
if (isApolloError(error)) {
|
|
enqueueErrorSnackBar({
|
|
apolloError: error,
|
|
});
|
|
return; // already handled by apolloLink
|
|
}
|
|
|
|
const isAbortError =
|
|
error?.networkError?.name === 'AbortError' ||
|
|
error?.name === 'AbortError';
|
|
|
|
if (isAbortError) {
|
|
return;
|
|
}
|
|
|
|
enqueueErrorSnackBar(
|
|
error instanceof Error ? { message: error.message } : {},
|
|
);
|
|
|
|
try {
|
|
const { captureException, captureMessage } = await import('@sentry/react');
|
|
|
|
if (error instanceof Error) {
|
|
captureException(error, (scope) => {
|
|
scope.setExtras({ mechanism: 'onUnhandledRejection' });
|
|
|
|
const fingerprint = hasErrorCode(error)
|
|
? error.code
|
|
: isNonEmptyString(error.message)
|
|
? error.message
|
|
: 'unknown-unhandled-rejection';
|
|
|
|
scope.setFingerprint([fingerprint]);
|
|
|
|
return scope;
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
captureMessage('Unhandled promise rejection with non-error reason', {
|
|
level: 'warning',
|
|
extra: {
|
|
mechanism: 'onUnhandledRejection',
|
|
reasonType: typeof error,
|
|
},
|
|
});
|
|
} catch (sentryError) {
|
|
// oxlint-disable-next-line no-console
|
|
console.warn('Failed to capture exception with Sentry:', sentryError);
|
|
}
|
|
},
|
|
[enqueueErrorSnackBar],
|
|
);
|
|
|
|
useEffect(() => {
|
|
window.addEventListener('unhandledrejection', handlePromiseRejection);
|
|
|
|
return () => {
|
|
window.removeEventListener('unhandledrejection', handlePromiseRejection);
|
|
};
|
|
}, [handlePromiseRejection]);
|
|
|
|
return <></>;
|
|
};
|