Orphaned view sorts crash People page after field deactivation

https://sonarly.com/issue/3162?type=bug

When a field is deactivated/deleted, the server-side cleanup omits view sorts referencing that field, leaving orphaned view sorts in the database. The frontend's `EditableSortChip` throws an unhandled exception when trying to render these orphaned sorts, crashing the entire page.

Fix: ## Changes

### Server-side: Add view sort cleanup to field deactivation side effects

**`handle-field-metadata-deactivation-side-effects.util.ts`** — Added `flatViewSortMaps` to the function's input args (via `Pick<AllFlatEntityMaps, ...>`), added `FlatViewSort` import, added `flatViewSortsToDelete` to the return type and implementation. Uses `fromFlatFieldMetadata.viewSortIds` (which was already populated but never consumed) to find and return the view sorts to delete.

**`handle-flat-field-metadata-update-side-effect.util.ts`** — Added `flatViewSortMaps` to args, `flatViewSortsToDelete: []` to empty side effects constant, and propagation of `flatViewSortsToDelete` from the deactivation handler into the result.

**`from-update-field-input-to-flat-field-metadata.util.ts`** — Added `flatViewSortMaps` to the args type, destructured it, passed it through to `handleFlatFieldMetadataUpdateSideEffect`, and accumulated `flatViewSortsToDelete` in the reduce result.

**`field-metadata.service.ts`** — Added `flatViewSortMaps` to the cache fetch keys and destructuring, passed it to `fromUpdateFieldInputToFlatFieldMetadata`, and added a `viewSort` entry in `allFlatEntityOperationByMetadataName` with `flatEntityToDelete: flatViewSortsToDelete`.

### Frontend: Defense-in-depth filter for orphaned sorts

**`ViewBarRecordSortEffect.tsx`** — Before setting `currentRecordSorts`, filters `currentView.viewSorts` to only include sorts whose `fieldMetadataId` exists in `objectMetadataItem.fields`. This prevents the crash for any orphaned sorts that already exist in the database (before the server-side fix takes effect for new deactivations).
This commit is contained in:
Sonarly Claude Code
2026-03-07 06:03:51 +00:00
parent 66d93c4d28
commit 347a084f2c
5 changed files with 39 additions and 1 deletions
@@ -46,7 +46,12 @@ export const ViewBarRecordSortEffect = () => {
}
if (isDefined(currentView)) {
setCurrentRecordSorts(currentView.viewSorts);
const validViewSorts = currentView.viewSorts.filter((viewSort) =>
objectMetadataItem.fields.some(
(field) => field.id === viewSort.fieldMetadataId,
),
);
setCurrentRecordSorts(validViewSorts);
setHasInitializedCurrentRecordSorts(true);
}
}
@@ -181,6 +181,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
flatViewGroupMaps: existingFlatViewGroupMaps,
flatViewMaps: existingFlatViewMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewSortMaps: existingFlatViewSortMaps,
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
@@ -192,6 +193,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
'flatViewGroupMaps',
'flatViewMaps',
'flatViewFieldMaps',
'flatViewSortMaps',
],
},
);
@@ -205,6 +207,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
flatViewGroupMaps: existingFlatViewGroupMaps,
flatViewMaps: existingFlatViewMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewSortMaps: existingFlatViewSortMaps,
flatApplication: resolvedOwnerFlatApplication,
isSystemBuild,
});
@@ -243,6 +246,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
flatViewFiltersToDelete,
flatViewFiltersToUpdate,
flatViewFieldsToDelete,
flatViewSortsToDelete,
flatViewsToUpdate,
flatViewsToDelete,
} = inputTranspilationResult.result;
@@ -281,6 +285,11 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
flatEntityToDelete: flatViewFieldsToDelete,
flatEntityToUpdate: [],
},
viewSort: {
flatEntityToCreate: [],
flatEntityToDelete: flatViewSortsToDelete,
flatEntityToUpdate: [],
},
},
workspaceId,
isSystemBuild,
@@ -35,6 +35,7 @@ type FromUpdateFieldInputToFlatFieldMetadataArgs = {
| 'flatViewGroupMaps'
| 'flatViewMaps'
| 'flatViewFieldMaps'
| 'flatViewSortMaps'
>;
type FlatFieldMetadataAndIndexToUpdate = {
@@ -51,6 +52,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
flatViewGroupMaps,
flatViewMaps,
flatViewFieldMaps,
flatViewSortMaps,
isSystemBuild,
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<FlatFieldMetadataAndIndexToUpdate> => {
const updateFieldInputInformalProperties =
@@ -150,6 +152,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
toFlatFieldMetadata,
flatViewMaps,
flatViewFieldMaps,
flatViewSortMaps,
flatApplication,
});
@@ -171,6 +174,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
flatIndexMetadatasToDelete,
flatViewsToDelete,
flatViewFieldsToDelete,
flatViewSortsToDelete,
flatViewsToUpdate,
flatFieldMetadatasToUpdate: flatFieldMetadatasToUpdateFromSideEffect,
} = sideEffectResult.result;
@@ -222,6 +226,10 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
...accumulator.flatViewFieldsToDelete,
...flatViewFieldsToDelete,
],
flatViewSortsToDelete: [
...accumulator.flatViewSortsToDelete,
...flatViewSortsToDelete,
],
flatViewsToUpdate: [
...accumulator.flatViewsToUpdate,
...flatViewsToUpdate,
@@ -6,6 +6,7 @@ import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
import { type FlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
type HandleFlatFieldMetadataDeactivationSideEffectsArgs = FromTo<
@@ -18,6 +19,7 @@ type HandleFlatFieldMetadataDeactivationSideEffectsArgs = FromTo<
| 'flatViewFilterMaps'
| 'flatViewFieldMaps'
| 'flatViewGroupMaps'
| 'flatViewSortMaps'
>;
export type FieldMetadataDeactivationSideEffect = {
@@ -25,6 +27,7 @@ export type FieldMetadataDeactivationSideEffect = {
flatViewsToUpdate: FlatView[];
flatViewFieldsToDelete: FlatViewField[];
flatViewFiltersToDelete: FlatViewFilter[];
flatViewSortsToDelete: FlatViewSort[];
};
export const handleFieldMetadataDeactivationSideEffects = ({
@@ -33,6 +36,7 @@ export const handleFieldMetadataDeactivationSideEffects = ({
flatViewFieldMaps,
flatViewFilterMaps,
flatViewGroupMaps,
flatViewSortMaps,
}: HandleFlatFieldMetadataDeactivationSideEffectsArgs): FieldMetadataDeactivationSideEffect => {
const flatViewFiltersToDelete = findManyFlatEntityByIdInFlatEntityMapsOrThrow(
{
@@ -46,6 +50,11 @@ export const handleFieldMetadataDeactivationSideEffects = ({
flatEntityMaps: flatViewFieldMaps,
});
const flatViewSortsToDelete = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.viewSortIds,
flatEntityMaps: flatViewSortMaps,
});
const flatViewsAffected: FlatView[] =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.mainGroupByFieldMetadataViewIds,
@@ -104,5 +113,6 @@ export const handleFieldMetadataDeactivationSideEffects = ({
flatViewsToDelete,
flatViewFieldsToDelete,
flatViewFiltersToDelete,
flatViewSortsToDelete,
};
};
@@ -41,6 +41,7 @@ type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
| 'flatViewGroupMaps'
| 'flatViewMaps'
| 'flatViewFieldMaps'
| 'flatViewSortMaps'
> & {
flatApplication: FlatApplication;
};
@@ -57,6 +58,7 @@ export const FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS: FlatFieldMetadataUpd
flatViewGroupsToUpdate: [],
flatViewsToDelete: [],
flatViewFieldsToDelete: [],
flatViewSortsToDelete: [],
flatViewsToUpdate: [],
flatFieldMetadatasToUpdate: [],
};
@@ -71,6 +73,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatViewGroupMaps,
flatViewMaps,
flatViewFieldMaps,
flatViewSortMaps,
flatApplication,
}: HandleFlatFieldMetadataUpdateSideEffectArgs): FieldInputTranspilationResult<FlatFieldMetadataUpdateSideEffects> => {
const sideEffectResult = structuredClone(
@@ -86,6 +89,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatViewsToDelete,
flatViewFieldsToDelete,
flatViewFiltersToDelete,
flatViewSortsToDelete,
flatViewsToUpdate,
} = handleFieldMetadataDeactivationSideEffects({
flatViewMaps,
@@ -94,12 +98,14 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatViewFieldMaps,
flatViewFilterMaps,
flatViewGroupMaps,
flatViewSortMaps,
});
sideEffectResult.flatViewsToUpdate.push(...flatViewsToUpdate);
sideEffectResult.flatViewsToDelete.push(...flatViewsToDelete);
sideEffectResult.flatViewFieldsToDelete.push(...flatViewFieldsToDelete);
sideEffectResult.flatViewFiltersToDelete.push(...flatViewFiltersToDelete);
sideEffectResult.flatViewSortsToDelete.push(...flatViewSortsToDelete);
} else if (
isEnumFlatFieldMetadata(toFlatFieldMetadata) &&
isEnumFlatFieldMetadata(fromFlatFieldMetadata)