feat(server): convert view to overridable entity (#21436)

## Context

Every entity created as a side effect of object creation must support
the overridable pattern (`isActive` + `overrides` + override routing)
before we can re-own side effects to their true application. Starting
with View.

viewField, viewFieldGroup, pageLayoutTab and pageLayoutWidget already
extend `OverridableEntity`. This PR brings `view` to the same pattern.

## What this does

- `ViewEntity` now extends `OverridableEntity<ViewOverrides>` (adds
`isActive` boolean + `overrides` jsonb). All editable view properties
are overridable; the 3 fieldMetadata foreign keys are converted to/from
universal identifiers like viewField's `viewFieldGroupId`.
- **Update**: mutations on a view not owned by the caller (e.g. standard
views like "All Companies") are written into `overrides` instead of
mutating the row. Reads merge overrides in
the DTO.
- **Delete/destroy**: views not owned by the caller are deactivated
(`isActive = false`) instead of deleted.
~~- **INDEX invariant**: `key = INDEX` views can only be created via
object-creation side effect. The API now rejects creating, deleting or
destroying INDEX views (object-deletion cascade is unaffected). This was
not really needed for this migration but was flagged during
implementation.~~
- **Front**: views with `isActive = false` are filtered out of the views
selector.
- Fast instance command adds the two columns
(`2-12-instance-command-fast-...-view-overridable-entity.ts`).

## Notes

- Custom (caller-owned) views behave exactly as before: direct updates,
soft delete.
- View-group side effects (kanban groups) are computed on the
override-merged view so overridden `mainGroupByFieldMetadataId` works.
This commit is contained in:
Weiko
2026-06-11 18:00:52 +02:00
committed by GitHub
parent 947a4d5253
commit cfb9772179
35 changed files with 497 additions and 52 deletions
@@ -327,8 +327,12 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
"visibility",
"mainGroupByFieldMetadataUniversalIdentifier",
"shouldHideEmptyGroups",
"isActive",
"universalOverrides",
],
"propertiesToStringify": [
"universalOverrides",
],
"propertiesToStringify": [],
},
"viewField": {
"propertiesToCompare": [
@@ -271,64 +271,101 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toStringify: false,
universalProperty: undefined,
},
name: { toCompare: true, toStringify: false, universalProperty: undefined },
type: { toCompare: true, toStringify: false, universalProperty: undefined },
icon: { toCompare: true, toStringify: false, universalProperty: undefined },
name: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
type: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
icon: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
position: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
isCompact: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
openRecordIn: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
kanbanAggregateOperation: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
kanbanAggregateOperationFieldMetadataId: {
toCompare: true,
toStringify: false,
universalProperty:
'kanbanAggregateOperationFieldMetadataUniversalIdentifier',
isOverridable: true,
},
anyFieldFilterValue: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
calendarLayout: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
calendarFieldMetadataId: {
toCompare: true,
toStringify: false,
universalProperty: 'calendarFieldMetadataUniversalIdentifier',
isOverridable: true,
},
visibility: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
mainGroupByFieldMetadataId: {
toCompare: true,
toStringify: false,
universalProperty: 'mainGroupByFieldMetadataUniversalIdentifier',
isOverridable: true,
},
shouldHideEmptyGroups: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
isActive: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: false,
},
overrides: {
toCompare: true,
toStringify: true,
universalProperty: 'universalOverrides',
},
objectMetadataId: {
toCompare: false,