Refactor page layout types (#18042)

## Refactor page layout widget types into shared package and expose from
SDK

### Why

Widget configuration types were defined only on the server, forcing SDK
consumer apps to import from deep internal `twenty-shared/dist` paths —
fragile and breaks on structural changes. Server DTOs also had no
compile-time guarantee they matched the canonical types.

### What changed

- **`twenty-shared`**: Migrated `ChartFilter`, `GridPosition`,
`RatioAggregateConfig` and all 20 widget configuration variants into
`twenty-shared/types`. `PageLayoutWidgetConfiguration` (base, with
`SerializedRelation`) and `PageLayoutWidgetUniversalConfiguration`
(derived via `FormatRecordSerializedRelationProperties`) are now the
single source of truth.
- **`twenty-sdk`**: Re-exported `AggregateOperations`,
`ObjectRecordGroupByDateGranularity`, `PageLayoutTabLayoutMode`, and
`PageLayoutWidgetUniversalConfiguration` so consumer apps import from
`twenty-sdk` directly.
- **`twenty-server`**: All widget DTOs now `implements` their shared
type for compile-time enforcement. Added helpers to convert nested
`fieldMetadataId` ↔ `fieldMetadataUniversalIdentifier` inside chart
filters. Removed redundant local type re-exports.
This commit is contained in:
Charles Bochet
2026-02-18 20:17:40 +01:00
committed by GitHub
parent 2cc3c75c7e
commit ce1ffa8550
53 changed files with 506 additions and 354 deletions
@@ -5,21 +5,21 @@ import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import {
PageLayoutTabLayoutMode,
PageLayoutWidgetPosition,
type GridPosition,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { type UniversalFlatPageLayoutTab } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout-tab.type';
import { FlatPageLayoutWidgetTypeValidatorService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
import { PageLayoutTabExceptionCode } from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception';
import { GraphType } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-type.enum';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
import { GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
import { validatePageLayoutWidgetGridPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-page-layout-widget-grid-position.util';
import { validatePageLayoutWidgetVerticalListPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-page-layout-widget-vertical-list-position.util';
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-grid-position.util';
import { type UniversalFlatPageLayoutTab } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout-tab.type';
import {
FailedFlatEntityValidation,
FlatEntityValidationError,
@@ -1,3 +1,7 @@
import {
type ChartFilter,
type UniversalChartFilter,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -38,6 +42,31 @@ const resolveFieldMetadataIdOrThrow = ({
return flatFieldMetadata.id;
};
const convertUniversalFilterToChartFilter = ({
filter,
flatFieldMetadataMaps,
}: {
filter: UniversalChartFilter | undefined;
flatFieldMetadataMaps: MetadataFlatEntityMaps<'fieldMetadata'>;
}): ChartFilter | undefined => {
if (!isDefined(filter)) {
return undefined;
}
return {
...filter,
recordFilters: filter.recordFilters?.map(
({ fieldMetadataUniversalIdentifier, ...rest }) => ({
...rest,
fieldMetadataId: resolveFieldMetadataIdOrThrow({
fieldMetadataUniversalIdentifier,
flatFieldMetadataMaps,
}),
}),
),
};
};
export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
universalConfiguration,
flatFieldMetadataMaps,
@@ -54,6 +83,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
const {
aggregateFieldMetadataUniversalIdentifier,
ratioAggregateConfig: universalRatioAggregateConfig,
filter,
...rest
} = universalConfiguration;
@@ -78,11 +108,15 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
...rest,
aggregateFieldMetadataId,
ratioAggregateConfig,
filter: convertUniversalFilterToChartFilter({
filter,
flatFieldMetadataMaps,
}),
};
}
case WidgetConfigurationType.GAUGE_CHART: {
const { aggregateFieldMetadataUniversalIdentifier, ...rest } =
const { aggregateFieldMetadataUniversalIdentifier, filter, ...rest } =
universalConfiguration;
const aggregateFieldMetadataId = resolveFieldMetadataIdOrThrow({
@@ -94,6 +128,10 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
return {
...rest,
aggregateFieldMetadataId,
filter: convertUniversalFilterToChartFilter({
filter,
flatFieldMetadataMaps,
}),
};
}
@@ -101,6 +139,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
const {
aggregateFieldMetadataUniversalIdentifier,
groupByFieldMetadataUniversalIdentifier,
filter,
...rest
} = universalConfiguration;
@@ -120,6 +159,10 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
...rest,
aggregateFieldMetadataId,
groupByFieldMetadataId,
filter: convertUniversalFilterToChartFilter({
filter,
flatFieldMetadataMaps,
}),
};
}
@@ -128,6 +171,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
aggregateFieldMetadataUniversalIdentifier,
primaryAxisGroupByFieldMetadataUniversalIdentifier,
secondaryAxisGroupByFieldMetadataUniversalIdentifier,
filter,
...rest
} = universalConfiguration;
@@ -158,6 +202,10 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
aggregateFieldMetadataId,
primaryAxisGroupByFieldMetadataId,
secondaryAxisGroupByFieldMetadataId,
filter: convertUniversalFilterToChartFilter({
filter,
flatFieldMetadataMaps,
}),
};
}
@@ -166,6 +214,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
aggregateFieldMetadataUniversalIdentifier,
primaryAxisGroupByFieldMetadataUniversalIdentifier,
secondaryAxisGroupByFieldMetadataUniversalIdentifier,
filter,
...rest
} = universalConfiguration;
@@ -196,6 +245,10 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
aggregateFieldMetadataId,
primaryAxisGroupByFieldMetadataId,
secondaryAxisGroupByFieldMetadataId,
filter: convertUniversalFilterToChartFilter({
filter,
flatFieldMetadataMaps,
}),
};
}