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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -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,
|
||||
|
||||
+8
@@ -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,
|
||||
|
||||
+10
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+6
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user