[REQUIRES_FULL_CACHE_FLUSH_WHEN_RELEASED] Refactor FlatEntity to be UniversalFlatEntity superset (#17452)
# Introduction
In this PR we're refactoring the `FlatEntity` type to become a superset
of the `UniversalFlatEntity`.
Right now we're storing all the extra properties in `__universal`
property, at some point it might just be sibling to other entity and we
might rely on the `propertiesToCompare` constants and TypeScript
allowing passing a superset type into a smaller subset type
## FromTo utils
The entity to flat entity method now computes the universal information,
standardized a typing and pattern to do
## Example
Also strictly type
```ts
"bbb019ea-6205-498c-aea5-67bc53bce8a9": {
"workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationId": "d01b010d-b984-465b-b40b-370e954e5188",
"id": "bbb019ea-6205-498c-aea5-67bc53bce8a9",
"pageLayoutTabId": "791a512f-169f-4209-b731-aa86716668c6",
"title": "Deals by Company",
"type": "GRAPH",
"objectMetadataId": "9e14efea-df5b-4c0e-aba9-cfe455f32397",
"gridPosition": { "row": 0, "column": 6, "rowSpan": 6, "columnSpan": 6 },
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"groupByFieldMetadataId": "6673ff18-63d2-47a1-8f85-2b9b09ca27a5",
"aggregateFieldMetadataId": "8d64ee41-5dd4-4de6-945a-7c0c18399715"
},
"createdAt": "2026-01-28T14:08:52.140Z",
"updatedAt": "2026-01-28T14:08:52.140Z",
"deletedAt": null,
"__universal": {
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationUniversalIdentifier": "20202020-64aa-4b6f-b003-9c74b97cee20",
"pageLayoutTabUniversalIdentifier": "20202020-d011-4d11-8d11-da5ab0a01001",
"objectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
"gridPosition": {
"row": 0,
"column": 6,
"rowSpan": 6,
"columnSpan": 6
},
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"aggregateFieldMetadataUniversalIdentifier": "20202020-d01a-4131-8a31-f123456789ab",
"groupByFieldMetadataUniversalIdentifier": "20202020-cbac-457e-b565-adece5fc815f"
}
}
},
```
This commit is contained in:
+166
@@ -0,0 +1,166 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
type PageLayoutWidgetConfiguration = PageLayoutWidgetEntity['configuration'];
|
||||
|
||||
type UniversalPageLayoutWidgetConfiguration = NonNullable<
|
||||
FlatPageLayoutWidget['__universal']
|
||||
>['configuration'];
|
||||
|
||||
// Field metadata IDs in widget configurations don't need to reference existing entities
|
||||
const getFieldMetadataUniversalIdentifier = ({
|
||||
fieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadataIdToUniversalIdentifierMap: Map<string, string>;
|
||||
}): string | null => {
|
||||
return fieldMetadataIdToUniversalIdentifierMap.get(fieldMetadataId) ?? null;
|
||||
};
|
||||
|
||||
export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
|
||||
configuration,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: {
|
||||
configuration: PageLayoutWidgetConfiguration;
|
||||
fieldMetadataIdToUniversalIdentifierMap: Map<string, string>;
|
||||
}): UniversalPageLayoutWidgetConfiguration => {
|
||||
switch (configuration.configurationType) {
|
||||
case WidgetConfigurationType.AGGREGATE_CHART: {
|
||||
const { aggregateFieldMetadataId, ratioAggregateConfig, ...rest } =
|
||||
configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
...(isDefined(ratioAggregateConfig) && {
|
||||
ratioAggregateConfig: {
|
||||
optionValue: ratioAggregateConfig.optionValue,
|
||||
fieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: ratioAggregateConfig.fieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.GAUGE_CHART: {
|
||||
const { aggregateFieldMetadataId, ...rest } = configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.PIE_CHART: {
|
||||
const { aggregateFieldMetadataId, groupByFieldMetadataId, ...rest } =
|
||||
configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
groupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: groupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.BAR_CHART: {
|
||||
const {
|
||||
aggregateFieldMetadataId,
|
||||
primaryAxisGroupByFieldMetadataId,
|
||||
secondaryAxisGroupByFieldMetadataId,
|
||||
...rest
|
||||
} = configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
primaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: primaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
...(isDefined(secondaryAxisGroupByFieldMetadataId) && {
|
||||
secondaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: secondaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.LINE_CHART: {
|
||||
const {
|
||||
aggregateFieldMetadataId,
|
||||
primaryAxisGroupByFieldMetadataId,
|
||||
secondaryAxisGroupByFieldMetadataId,
|
||||
...rest
|
||||
} = configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
primaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: primaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
...(isDefined(secondaryAxisGroupByFieldMetadataId) && {
|
||||
secondaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: secondaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.VIEW:
|
||||
case WidgetConfigurationType.FIELD:
|
||||
case WidgetConfigurationType.FIELDS:
|
||||
case WidgetConfigurationType.TIMELINE:
|
||||
case WidgetConfigurationType.TASKS:
|
||||
case WidgetConfigurationType.NOTES:
|
||||
case WidgetConfigurationType.FILES:
|
||||
case WidgetConfigurationType.EMAILS:
|
||||
case WidgetConfigurationType.CALENDAR:
|
||||
case WidgetConfigurationType.FIELD_RICH_TEXT:
|
||||
case WidgetConfigurationType.WORKFLOW:
|
||||
case WidgetConfigurationType.WORKFLOW_VERSION:
|
||||
case WidgetConfigurationType.WORKFLOW_RUN:
|
||||
case WidgetConfigurationType.FRONT_COMPONENT:
|
||||
case WidgetConfigurationType.IFRAME:
|
||||
case WidgetConfigurationType.STANDALONE_RICH_TEXT:
|
||||
return configuration;
|
||||
}
|
||||
};
|
||||
+73
-5
@@ -1,17 +1,77 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} 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 FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { fromPageLayoutWidgetConfigurationToUniversalConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = (
|
||||
pageLayoutWidgetEntity: PageLayoutWidgetEntity,
|
||||
): FlatPageLayoutWidget => {
|
||||
type FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs =
|
||||
FromEntityToFlatEntityArgs<'pageLayoutWidget'> & {
|
||||
fieldMetadataIdToUniversalIdentifierMap: Map<string, string>;
|
||||
};
|
||||
|
||||
export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
|
||||
entity: pageLayoutWidgetEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
pageLayoutTabIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs): FlatPageLayoutWidget => {
|
||||
const pageLayoutWidgetEntityWithoutRelations = removePropertiesFromRecord(
|
||||
pageLayoutWidgetEntity,
|
||||
getMetadataEntityRelationProperties('pageLayoutWidget'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
pageLayoutWidgetEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${pageLayoutWidgetEntity.applicationId} not found for pageLayoutWidget ${pageLayoutWidgetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const pageLayoutTabUniversalIdentifier =
|
||||
pageLayoutTabIdToUniversalIdentifierMap.get(
|
||||
pageLayoutWidgetEntity.pageLayoutTabId,
|
||||
);
|
||||
|
||||
if (!isDefined(pageLayoutTabUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`PageLayoutTab with id ${pageLayoutWidgetEntity.pageLayoutTabId} not found for pageLayoutWidget ${pageLayoutWidgetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let objectMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(pageLayoutWidgetEntity.objectMetadataId)) {
|
||||
objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
pageLayoutWidgetEntity.objectMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${pageLayoutWidgetEntity.objectMetadataId} not found for pageLayoutWidget ${pageLayoutWidgetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const configurationWithUniversalIdentifiers =
|
||||
fromPageLayoutWidgetConfigurationToUniversalConfiguration({
|
||||
configuration: pageLayoutWidgetEntityWithoutRelations.configuration,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
return {
|
||||
...pageLayoutWidgetEntityWithoutRelations,
|
||||
createdAt: pageLayoutWidgetEntity.createdAt.toISOString(),
|
||||
@@ -20,5 +80,13 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = (
|
||||
universalIdentifier:
|
||||
pageLayoutWidgetEntityWithoutRelations.universalIdentifier,
|
||||
applicationId: pageLayoutWidgetEntityWithoutRelations.applicationId,
|
||||
__universal: {
|
||||
universalIdentifier: pageLayoutWidgetEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
pageLayoutTabUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
gridPosition: pageLayoutWidgetEntityWithoutRelations.gridPosition,
|
||||
configuration: configurationWithUniversalIdentifiers,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user