View field override (#18572)

## Context
This PR introduces overrides for view fields which will be useful for
page layout FIELDS widgets fields position/groups/visibility override +
restore logic.
This commit is contained in:
Weiko
2026-03-14 12:40:15 +01:00
committed by GitHub
parent 0b0ffcb8fa
commit 48172d60fd
50 changed files with 864 additions and 114 deletions
@@ -199,6 +199,7 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
size: DEFAULT_VIEW_FIELD_SIZE,
position,
aggregateOperation: null,
universalOverrides: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -66,6 +66,7 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE,
position: createViewFieldInput.position ?? 0,
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
universalOverrides: null,
viewFieldGroupUniversalIdentifier,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
@@ -10,6 +10,9 @@ import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-editable-properties.constant';
import { type FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
import { fromViewFieldOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-view-field/utils/from-view-field-overrides-to-universal-overrides.util';
import { isCallerOverridingEntity } from 'src/engine/metadata-modules/utils/is-caller-overriding-entity.util';
import { sanitizeOverridableEntityInput } from 'src/engine/metadata-modules/utils/sanitize-overridable-entity-input.util';
import { type UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
import {
ViewFieldException,
@@ -22,9 +25,13 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
updateViewFieldInput: rawUpdateViewFieldInput,
flatViewFieldMaps,
flatViewFieldGroupMaps,
callerApplicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
}: {
updateViewFieldInput: UpdateViewFieldInput;
flatViewFieldMaps: FlatViewFieldMaps;
callerApplicationUniversalIdentifier: string;
workspaceCustomApplicationUniversalIdentifier: string;
} & Pick<
AllFlatEntityMaps,
'flatViewFieldGroupMaps'
@@ -47,23 +54,43 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
);
}
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
const editableProperties = extractAndSanitizeObjectStringFields(
rawUpdateViewFieldInput.update,
FLAT_VIEW_FIELD_EDITABLE_PROPERTIES,
);
const flatViewFieldToUpdate = mergeUpdateInExistingRecord({
existing: existingFlatViewFieldToUpdate,
properties: FLAT_VIEW_FIELD_EDITABLE_PROPERTIES,
update: updatedEditableFieldProperties,
const shouldOverride = isCallerOverridingEntity({
callerApplicationUniversalIdentifier,
entityApplicationUniversalIdentifier:
existingFlatViewFieldToUpdate.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
});
if (updatedEditableFieldProperties.viewFieldGroupId !== undefined) {
const { overrides, updatedEditableProperties } =
sanitizeOverridableEntityInput({
metadataName: 'viewField',
existingFlatEntity: existingFlatViewFieldToUpdate,
updatedEditableProperties: editableProperties,
shouldOverride,
});
const mergedRecord = mergeUpdateInExistingRecord({
existing: existingFlatViewFieldToUpdate,
properties: [...FLAT_VIEW_FIELD_EDITABLE_PROPERTIES],
update: updatedEditableProperties,
});
const flatViewFieldToUpdate = {
...mergedRecord,
overrides,
} as UniversalFlatViewField;
if (updatedEditableProperties.viewFieldGroupId !== undefined) {
const { viewFieldGroupUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'viewField',
foreignKeyValues: {
viewFieldGroupId: flatViewFieldToUpdate.viewFieldGroupId,
viewFieldGroupId: mergedRecord.viewFieldGroupId,
},
flatEntityMaps: { flatViewFieldGroupMaps },
});
@@ -72,5 +99,16 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
viewFieldGroupUniversalIdentifier;
}
if (isDefined(overrides)) {
flatViewFieldToUpdate.universalOverrides =
fromViewFieldOverridesToUniversalOverrides({
overrides,
viewFieldGroupUniversalIdentifierById:
flatViewFieldGroupMaps.universalIdentifierById,
});
} else {
flatViewFieldToUpdate.universalOverrides = null;
}
return flatViewFieldToUpdate;
};
@@ -6,6 +6,7 @@ import {
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { fromViewFieldOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-view-field/utils/from-view-field-overrides-to-universal-overrides.util';
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
export const fromViewFieldEntityToFlatViewField = ({
@@ -69,6 +70,18 @@ export const fromViewFieldEntityToFlatViewField = ({
}
}
const viewFieldGroupUniversalIdentifierById = Object.fromEntries(
viewFieldGroupIdToUniversalIdentifierMap.entries(),
);
const universalOverrides = isDefined(viewFieldEntity.overrides)
? fromViewFieldOverridesToUniversalOverrides({
overrides: viewFieldEntity.overrides,
viewFieldGroupUniversalIdentifierById,
shouldThrowOnMissingIdentifier: false,
})
: null;
return {
...viewFieldEntityWithoutRelations,
createdAt: viewFieldEntity.createdAt.toISOString(),
@@ -79,5 +92,6 @@ export const fromViewFieldEntityToFlatViewField = ({
fieldMetadataUniversalIdentifier,
viewUniversalIdentifier,
viewFieldGroupUniversalIdentifier,
universalOverrides,
};
};
@@ -0,0 +1,54 @@
import { type FormatRecordSerializedRelationProperties } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type ViewFieldOverrides } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
type UniversalViewFieldOverrides =
FormatRecordSerializedRelationProperties<ViewFieldOverrides>;
export const fromViewFieldOverridesToUniversalOverrides = ({
overrides,
viewFieldGroupUniversalIdentifierById,
shouldThrowOnMissingIdentifier = true,
}: {
overrides: ViewFieldOverrides;
viewFieldGroupUniversalIdentifierById: Partial<Record<string, string>>;
shouldThrowOnMissingIdentifier?: boolean;
}): UniversalViewFieldOverrides => {
const { viewFieldGroupId, ...scalarOverrides } = overrides;
if (!isDefined(viewFieldGroupId)) {
return {
...scalarOverrides,
...(viewFieldGroupId === null
? { viewFieldGroupUniversalIdentifier: null }
: {}),
};
}
const viewFieldGroupUniversalIdentifier =
viewFieldGroupUniversalIdentifierById[viewFieldGroupId];
if (!isDefined(viewFieldGroupUniversalIdentifier)) {
if (shouldThrowOnMissingIdentifier) {
throw new FlatEntityMapsException(
`ViewFieldGroup universal identifier not found for id: ${viewFieldGroupId}`,
FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND,
);
}
return {
...scalarOverrides,
viewFieldGroupUniversalIdentifier: null,
};
}
return {
...scalarOverrides,
viewFieldGroupUniversalIdentifier,
};
};