Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 2d2a85bb05 useGetViewGroupsFilters throws when view has viewGroups but null mainGroupByFieldMetadataId
https://sonarly.com/issue/3794?type=bug

The `useGetViewGroupsFilters` hook unconditionally throws when a view has hidden viewGroups but `mainGroupByFieldMetadataId` is null, a legitimate data state after the fieldMetadataId migration.

Fix: The fix addresses three sites where `throw new Error('mainGroupByFieldMetadataId is required')` crashes when a view has orphaned viewGroups but no `mainGroupByFieldMetadataId` (null after the fieldMetadataId migration or during cache transitions).

**`useGetViewGroupsFilters.ts`** (primary crash site from Sentry): Moved the `isDefined(mainGroupByFieldMetadataId)` check before iterating viewGroups, returning an empty array early. This is the correct behavior — without a group-by field, hidden viewGroup visibility filters are meaningless.

**`useSaveCurrentViewGroups.ts`**: Changed throw to early return in `saveViewGroups` callback, matching the existing early-return pattern used 3 times earlier in the same file for other precondition checks (lines 28-30, 34-36, 40-42).

**`useReorderRecordGroups.ts`**: Changed throw to early return in `reorderRecordGroups` callback. If there's no group field metadata, the reorder operation simply cannot proceed — silently returning is the correct behavior (same as the `isDeeplyEqual` check on line 63-67 which also returns without action).
2026-03-09 08:52:56 +00:00
Sonarly Claude Code 96022d8122 View with hidden groups crashes when mainGroupByFieldMetadataId is null
https://sonarly.com/issue/11500?type=bug

The `useGetViewGroupsFilters` hook throws an unhandled error when a view has hidden viewGroups but its `mainGroupByFieldMetadataId` field is null/undefined, crashing the companies page.

Fix: Replaced the hard `throw new Error('mainGroupByFieldMetadataId is required')` inside the `.map()` callback with an early return of `[]` at the top of the hook, before accessing `viewGroups`.

**What changed:** When `currentView` is undefined or its `mainGroupByFieldMetadataId` is null/undefined, the hook now returns an empty filter array instead of crashing. This is the correct semantic — if no group-by field is configured, there are no group-visibility filters to exclude.

**Why this is safe:** The filters produced by this hook are spread into the record filters array in `useGetRecordIndexTotalCount`. An empty array simply means "no additional group visibility filters" — the query works fine without them, it just won't exclude hidden groups from the count (which is the expected behavior when grouping isn't configured).

This also matches the pattern used in `mapViewGroupsToRecordGroupDefinitions` which returns `[]` when the field metadata is missing rather than throwing.

The `.filter(isDefined)` after `.map()` was also removed since the map callback always returns a concrete object (never `undefined`) — this was dead code.
2026-03-09 08:51:23 +00:00
3 changed files with 17 additions and 19 deletions
@@ -87,7 +87,7 @@ export const useReorderRecordGroups = ({
}, []);
if (!isDefined(recordIndexGroupFieldMetadataItem?.id)) {
throw new Error('mainGroupByFieldMetadataId is required');
return;
}
setRecordGroups({
@@ -6,24 +6,22 @@ import { getFilterTypeFromFieldType, isDefined } from 'twenty-shared/utils';
export const useGetViewGroupsFilters = (): RecordFilter[] => {
const { currentView } = useGetCurrentViewOnly();
return (
currentView?.viewGroups
.filter((recordGroup) => !recordGroup.isVisible)
.map((recordGroup) => {
if (!isDefined(currentView.mainGroupByFieldMetadataId)) {
throw new Error('mainGroupByFieldMetadataId is required');
}
if (!isDefined(currentView?.mainGroupByFieldMetadataId)) {
return [];
}
return {
id: recordGroup.id,
fieldMetadataId: currentView.mainGroupByFieldMetadataId,
value: JSON.stringify([recordGroup.fieldValue]),
operand: ViewFilterOperand.IS_NOT,
displayValue: '',
type: getFilterTypeFromFieldType(FieldMetadataType.SELECT),
label: '',
};
})
return (
currentView.viewGroups
.filter((recordGroup) => !recordGroup.isVisible)
.map((recordGroup) => ({
id: recordGroup.id,
fieldMetadataId: currentView.mainGroupByFieldMetadataId,
value: JSON.stringify([recordGroup.fieldValue]),
operand: ViewFilterOperand.IS_NOT,
displayValue: '',
type: getFilterTypeFromFieldType(FieldMetadataType.SELECT),
label: '',
}))
.filter(isDefined) || []
);
};
@@ -149,7 +149,7 @@ export const useSaveCurrentViewGroups = () => {
.filter(isDefined);
if (!isDefined(view.mainGroupByFieldMetadataId)) {
throw new Error('mainGroupByFieldMetadataId is required');
return;
}
await performViewGroupAPIUpdate(viewGroupsToUpdate);