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
@@ -10,7 +10,6 @@ export default definePageLayout({
{
universalIdentifier: '${uuidv4()}',
title: 'Overview',
position: 0,
widgets: [],
},
],
+7 -1
View File
@@ -47,9 +47,15 @@ export type {
export type { RoutePayload } from './logic-functions/triggers/route-payload-type';
export { defineNavigationMenuItem } from './navigation-menu-items/define-navigation-menu-item';
export { defineObject } from './objects/define-object';
export { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from './objects/standard-object-ids';
export { definePageLayout } from './page-layouts/define-page-layout';
export type { PageLayoutConfig } from './page-layouts/page-layout-config';
export { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from './objects/standard-object-ids';
export {
AggregateOperations,
ObjectRecordGroupByDateGranularity,
PageLayoutTabLayoutMode,
} from 'twenty-shared/types';
export type { PageLayoutWidgetUniversalConfiguration } from 'twenty-shared/types';
export { defineRole } from './roles/define-role';
export { PermissionFlag } from './roles/permission-flag-type';
export { defineView } from './views/define-view';
@@ -24,7 +24,7 @@ export const fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget = ({
pageLayoutWidgetManifest.objectUniversalIdentifier ?? null,
conditionalDisplay: pageLayoutWidgetManifest.conditionalDisplay ?? null,
gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 },
position: pageLayoutWidgetManifest.position ?? null,
position: null,
universalConfiguration:
pageLayoutWidgetManifest.configuration as UniversalFlatPageLayoutWidget['universalConfiguration'],
createdAt: now,
@@ -1,3 +1,7 @@
import {
type ChartFilter,
type UniversalChartFilter,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -39,6 +43,33 @@ const getFieldMetadataUniversalIdentifier = ({
return universalIdentifier;
};
const convertChartFilterToUniversalFilter = ({
filter,
fieldMetadataUniversalIdentifierById,
}: {
filter: ChartFilter | undefined;
fieldMetadataUniversalIdentifierById: Partial<Record<string, string>>;
shouldThrowOnMissingIdentifier: boolean;
}): UniversalChartFilter | undefined => {
if (!isDefined(filter)) {
return undefined;
}
return {
...filter,
recordFilters: filter.recordFilters?.map(
({ fieldMetadataId, ...rest }) => ({
...rest,
fieldMetadataUniversalIdentifier: getFieldMetadataUniversalIdentifier({
fieldMetadataId,
fieldMetadataUniversalIdentifierById,
shouldThrowOnMissingIdentifier: false,
}),
}),
),
};
};
export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
configuration,
fieldMetadataUniversalIdentifierById,
@@ -52,8 +83,12 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
}): UniversalPageLayoutWidgetConfiguration => {
switch (configuration.configurationType) {
case WidgetConfigurationType.AGGREGATE_CHART: {
const { aggregateFieldMetadataId, ratioAggregateConfig, ...rest } =
configuration;
const {
aggregateFieldMetadataId,
ratioAggregateConfig,
filter,
...rest
} = configuration;
const aggregateFieldMetadataUniversalIdentifier =
getFieldMetadataUniversalIdentifier({
@@ -78,11 +113,16 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
...rest,
aggregateFieldMetadataUniversalIdentifier,
ratioAggregateConfig: universalRatioAggregateConfig,
filter: convertChartFilterToUniversalFilter({
filter,
fieldMetadataUniversalIdentifierById,
shouldThrowOnMissingIdentifier,
}),
};
}
case WidgetConfigurationType.GAUGE_CHART: {
const { aggregateFieldMetadataId, ...rest } = configuration;
const { aggregateFieldMetadataId, filter, ...rest } = configuration;
const aggregateFieldMetadataUniversalIdentifier =
getFieldMetadataUniversalIdentifier({
@@ -94,12 +134,21 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
return {
...rest,
aggregateFieldMetadataUniversalIdentifier,
filter: convertChartFilterToUniversalFilter({
filter,
fieldMetadataUniversalIdentifierById,
shouldThrowOnMissingIdentifier,
}),
};
}
case WidgetConfigurationType.PIE_CHART: {
const { aggregateFieldMetadataId, groupByFieldMetadataId, ...rest } =
configuration;
const {
aggregateFieldMetadataId,
groupByFieldMetadataId,
filter,
...rest
} = configuration;
const aggregateFieldMetadataUniversalIdentifier =
getFieldMetadataUniversalIdentifier({
@@ -119,6 +168,11 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
...rest,
aggregateFieldMetadataUniversalIdentifier,
groupByFieldMetadataUniversalIdentifier,
filter: convertChartFilterToUniversalFilter({
filter,
fieldMetadataUniversalIdentifierById,
shouldThrowOnMissingIdentifier,
}),
};
}
@@ -127,6 +181,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
aggregateFieldMetadataId,
primaryAxisGroupByFieldMetadataId,
secondaryAxisGroupByFieldMetadataId,
filter,
...rest
} = configuration;
@@ -159,6 +214,11 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
aggregateFieldMetadataUniversalIdentifier,
primaryAxisGroupByFieldMetadataUniversalIdentifier,
secondaryAxisGroupByFieldMetadataUniversalIdentifier,
filter: convertChartFilterToUniversalFilter({
filter,
fieldMetadataUniversalIdentifierById,
shouldThrowOnMissingIdentifier,
}),
};
}
@@ -167,6 +227,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
aggregateFieldMetadataId,
primaryAxisGroupByFieldMetadataId,
secondaryAxisGroupByFieldMetadataId,
filter,
...rest
} = configuration;
@@ -199,6 +260,11 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
aggregateFieldMetadataUniversalIdentifier,
primaryAxisGroupByFieldMetadataUniversalIdentifier,
secondaryAxisGroupByFieldMetadataUniversalIdentifier,
filter: convertChartFilterToUniversalFilter({
filter,
fieldMetadataUniversalIdentifierById,
shouldThrowOnMissingIdentifier,
}),
};
}
@@ -17,17 +17,20 @@ import {
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { CalendarStartDay } from 'twenty-shared/constants';
import { AggregateOperations, SerializedRelation } from 'twenty-shared/types';
import {
type AggregateChartConfiguration,
AggregateOperations,
type ChartFilter,
SerializedRelation,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { RatioAggregateConfigDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/ratio-aggregate-config.dto';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('AggregateChartConfiguration')
export class AggregateChartConfigurationDTO
implements PageLayoutWidgetConfigurationBase
implements AggregateChartConfiguration
{
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.AGGREGATE_CHART])
@@ -17,7 +17,12 @@ import {
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { CalendarStartDay } from 'twenty-shared/constants';
import { AggregateOperations, SerializedRelation } from 'twenty-shared/types';
import {
AggregateOperations,
type BarChartConfiguration,
type ChartFilter,
SerializedRelation,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout-widget/enums/axis-name-display.enum';
@@ -26,13 +31,9 @@ import { BarChartLayout } from 'src/engine/metadata-modules/page-layout-widget/e
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout-widget/enums/date-granularity.enum';
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('BarChartConfiguration')
export class BarChartConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class BarChartConfigurationDTO implements BarChartConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.BAR_CHART])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type CalendarConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('CalendarConfiguration')
export class CalendarConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class CalendarConfigurationDTO implements CalendarConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.CALENDAR])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type EmailsConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('EmailsConfiguration')
export class EmailsConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class EmailsConfigurationDTO implements EmailsConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.EMAILS])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type FieldConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('FieldConfiguration')
export class FieldConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class FieldConfigurationDTO implements FieldConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.FIELD])
@IsNotEmpty()
@@ -1,13 +1,13 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type FieldRichTextConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('FieldRichTextConfiguration')
export class FieldRichTextConfigurationDTO
implements PageLayoutWidgetConfigurationBase
implements FieldRichTextConfiguration
{
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.FIELD_RICH_TEXT])
@@ -9,9 +9,9 @@ import {
IsUUID,
ValidateNested,
} from 'class-validator';
import { type FieldsConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('NewFieldDefaultConfiguration')
export class NewFieldDefaultConfigurationDTO {
@@ -26,9 +26,7 @@ export class NewFieldDefaultConfigurationDTO {
}
@ObjectType('FieldsConfiguration')
export class FieldsConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class FieldsConfigurationDTO implements FieldsConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.FIELDS])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type FilesConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('FilesConfiguration')
export class FilesConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class FilesConfigurationDTO implements FilesConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.FILES])
@IsNotEmpty()
@@ -1,14 +1,14 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty, IsUUID } from 'class-validator';
import { type FrontComponentConfiguration } from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('FrontComponentConfiguration')
export class FrontComponentConfigurationDTO
implements PageLayoutWidgetConfigurationBase
implements FrontComponentConfiguration
{
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.FRONT_COMPONENT])
@@ -15,17 +15,18 @@ import {
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { CalendarStartDay } from 'twenty-shared/constants';
import { AggregateOperations, SerializedRelation } from 'twenty-shared/types';
import {
AggregateOperations,
type ChartFilter,
type GaugeChartConfiguration,
SerializedRelation,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('GaugeChartConfiguration')
export class GaugeChartConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class GaugeChartConfigurationDTO implements GaugeChartConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.GAUGE_CHART])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty, IsOptional, IsString, IsUrl } from 'class-validator';
import { type IframeConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('IframeConfiguration')
export class IframeConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class IframeConfigurationDTO implements IframeConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.IFRAME])
@IsNotEmpty()
@@ -1,8 +1,7 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsInt, IsNotEmpty, Min } from 'class-validator';
import { GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
import { type GridPosition } from 'twenty-shared/types';
@InputType('GridPositionInput')
export class GridPositionInput implements GridPosition {
@@ -17,20 +17,21 @@ import {
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { CalendarStartDay } from 'twenty-shared/constants';
import { AggregateOperations, SerializedRelation } from 'twenty-shared/types';
import {
AggregateOperations,
type ChartFilter,
type LineChartConfiguration,
SerializedRelation,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout-widget/enums/axis-name-display.enum';
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout-widget/enums/date-granularity.enum';
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('LineChartConfiguration')
export class LineChartConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class LineChartConfigurationDTO implements LineChartConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.LINE_CHART])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type NotesConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('NotesConfiguration')
export class NotesConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class NotesConfigurationDTO implements NotesConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.NOTES])
@IsNotEmpty()
@@ -16,19 +16,20 @@ import {
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { CalendarStartDay } from 'twenty-shared/constants';
import { AggregateOperations, SerializedRelation } from 'twenty-shared/types';
import {
AggregateOperations,
type ChartFilter,
type PieChartConfiguration,
SerializedRelation,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout-widget/enums/date-granularity.enum';
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('PieChartConfiguration')
export class PieChartConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class PieChartConfigurationDTO implements PieChartConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.PIE_CHART])
@IsNotEmpty()
@@ -1,12 +1,15 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
import { SerializedRelation } from 'twenty-shared/types';
import {
type RatioAggregateConfig,
SerializedRelation,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@ObjectType('RatioAggregateConfig')
export class RatioAggregateConfigDTO {
export class RatioAggregateConfigDTO implements RatioAggregateConfig {
@Field(() => UUIDScalarType)
@IsUUID()
@IsNotEmpty()
@@ -2,14 +2,14 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { IsIn, IsNotEmpty, ValidateNested } from 'class-validator';
import { type StandaloneRichTextConfiguration } from 'twenty-shared/types';
import { RichTextV2BodyDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/rich-text-v2-body.dto';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('StandaloneRichTextConfiguration')
export class StandaloneRichTextConfigurationDTO
implements PageLayoutWidgetConfigurationBase
implements StandaloneRichTextConfiguration
{
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.STANDALONE_RICH_TEXT])
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type TasksConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('TasksConfiguration')
export class TasksConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class TasksConfigurationDTO implements TasksConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.TASKS])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type TimelineConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('TimelineConfiguration')
export class TimelineConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class TimelineConfigurationDTO implements TimelineConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.TIMELINE])
@IsNotEmpty()
@@ -1,12 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type ViewConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('ViewConfiguration')
export class ViewConfigurationDTO implements PageLayoutWidgetConfigurationBase {
export class ViewConfigurationDTO implements ViewConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.VIEW])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type WorkflowConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('WorkflowConfiguration')
export class WorkflowConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class WorkflowConfigurationDTO implements WorkflowConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.WORKFLOW])
@IsNotEmpty()
@@ -1,14 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type WorkflowRunConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('WorkflowRunConfiguration')
export class WorkflowRunConfigurationDTO
implements PageLayoutWidgetConfigurationBase
{
export class WorkflowRunConfigurationDTO implements WorkflowRunConfiguration {
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.WORKFLOW_RUN])
@IsNotEmpty()
@@ -1,13 +1,13 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsIn, IsNotEmpty } from 'class-validator';
import { type WorkflowVersionConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type';
@ObjectType('WorkflowVersionConfiguration')
export class WorkflowVersionConfigurationDTO
implements PageLayoutWidgetConfigurationBase
implements WorkflowVersionConfiguration
{
@Field(() => WidgetConfigurationType)
@IsIn([WidgetConfigurationType.WORKFLOW_VERSION])
@@ -3,6 +3,7 @@ import { ObjectType } from '@nestjs/graphql';
import {
PageLayoutWidgetConditionalDisplay,
PageLayoutWidgetPosition,
type GridPosition,
} from 'twenty-shared/types';
import {
Column,
@@ -21,7 +22,6 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
import { PageLayoutWidgetConfigurationTypeSettings } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configuration.type';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { type JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
@@ -1,4 +1,5 @@
import { msg } from '@lingui/core/macro';
import { type GridPosition } from 'twenty-shared/types';
import { WIDGET_GRID_MAX_COLUMNS } from 'src/engine/metadata-modules/page-layout-widget/constants/widget-grid-max-columns.constant';
import { WIDGET_GRID_MAX_ROWS } from 'src/engine/metadata-modules/page-layout-widget/constants/widget-grid-max-rows.constant';
@@ -9,13 +10,6 @@ import {
} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
type GridPosition = {
row: number;
column: number;
rowSpan: number;
columnSpan: number;
};
// TODO: remove in favor of validatePageLayoutWidgetGridPosition once gridPosition is deprecated
export const validateWidgetGridPosition = (
gridPosition: GridPosition,
@@ -503,7 +503,6 @@ export class PageLayoutUpdateService {
flatFieldMetadataMaps.universalIdentifierById,
viewFieldGroupUniversalIdentifierById:
flatViewFieldGroupMaps.universalIdentifierById,
shouldThrowOnMissingIdentifier: true,
}),
};
},
@@ -4,10 +4,10 @@ import {
type PageLayoutWidgetConditionalDisplay,
type PageLayoutWidgetGridPosition,
type PageLayoutWidgetVerticalListPosition,
type GridPosition,
} from 'twenty-shared/types';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
export const CONDITIONAL_DISPLAY_DEVICE_MOBILE = {
and: [{ '===': [{ var: 'device' }, 'MOBILE'] }],
@@ -2,10 +2,10 @@ import {
type PageLayoutTabLayoutMode,
type PageLayoutWidgetConditionalDisplay,
type PageLayoutWidgetPosition,
type GridPosition,
} from 'twenty-shared/types';
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
import { type PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
export type StandardPageLayoutWidgetConfig = {
@@ -23,7 +23,7 @@ type DashboardWidgetBuilderArgs = Omit<
'context'
>;
const createFilterConfig = (
const createFilterConfigs = (
filters: Array<{
type: string;
label: string;
@@ -31,27 +31,44 @@ const createFilterConfig = (
displayValue: string;
operand: string;
fieldMetadataId: string;
fieldMetadataUniversalIdentifier: string;
}>,
) => {
const groupId = v4();
const recordFilterGroups = [
{
id: groupId,
logicalOperator: 'AND',
},
];
return {
recordFilters: filters.map((filter) => ({
id: v4(),
type: filter.type,
label: filter.label,
value: filter.value,
displayValue: filter.displayValue,
operand: filter.operand,
fieldMetadataId: filter.fieldMetadataId,
recordFilterGroupId: groupId,
})),
recordFilterGroups: [
{
id: groupId,
logicalOperator: 'AND',
},
],
configuration: {
recordFilters: filters.map((filter) => ({
type: filter.type,
label: filter.label,
value: filter.value,
displayValue: filter.displayValue,
operand: filter.operand,
fieldMetadataId: filter.fieldMetadataId,
recordFilterGroupId: groupId,
})),
recordFilterGroups,
},
universal: {
recordFilters: filters.map((filter) => ({
type: filter.type,
label: filter.label,
value: filter.value,
displayValue: filter.displayValue,
operand: filter.operand,
fieldMetadataUniversalIdentifier:
filter.fieldMetadataUniversalIdentifier,
recordFilterGroupId: groupId,
})),
recordFilterGroups,
},
};
};
@@ -562,7 +579,7 @@ const createDealsCreatedThisMonth = ({
const opportunityObjectId =
args.standardObjectMetadataRelatedEntityIds.opportunity.id;
const filterConfig = createFilterConfig([
const filterConfigs = createFilterConfigs([
{
type: 'DATE_TIME',
label: 'Creation date',
@@ -570,6 +587,8 @@ const createDealsCreatedThisMonth = ({
displayValue: 'THIS_1_MONTH;;UTC;;SUNDAY;;',
operand: 'IS_RELATIVE',
fieldMetadataId: opportunityFields.createdAt.id,
fieldMetadataUniversalIdentifier:
STANDARD_OBJECTS.opportunity.fields.createdAt.universalIdentifier,
},
]);
@@ -596,7 +615,7 @@ const createDealsCreatedThisMonth = ({
aggregateFieldMetadataId: opportunityFields.id.id,
aggregateOperation: AggregateOperations.COUNT,
displayDataLabel: false,
filter: filterConfig,
filter: filterConfigs.configuration,
prefix: '',
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
@@ -607,7 +626,7 @@ const createDealsCreatedThisMonth = ({
STANDARD_OBJECTS.opportunity.fields.id.universalIdentifier,
aggregateOperation: AggregateOperations.COUNT,
displayDataLabel: false,
filter: filterConfig,
filter: filterConfigs.universal,
prefix: '',
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
@@ -628,7 +647,7 @@ const createDealValueCreatedThisMonth = ({
const opportunityObjectId =
args.standardObjectMetadataRelatedEntityIds.opportunity.id;
const filterConfig = createFilterConfig([
const filterConfigs = createFilterConfigs([
{
type: 'DATE_TIME',
label: 'Creation date',
@@ -636,6 +655,8 @@ const createDealValueCreatedThisMonth = ({
displayValue: 'THIS_1_MONTH;;UTC;;SUNDAY;;',
operand: 'IS_RELATIVE',
fieldMetadataId: opportunityFields.createdAt.id,
fieldMetadataUniversalIdentifier:
STANDARD_OBJECTS.opportunity.fields.createdAt.universalIdentifier,
},
]);
@@ -662,7 +683,7 @@ const createDealValueCreatedThisMonth = ({
aggregateFieldMetadataId: opportunityFields.amount.id,
aggregateOperation: AggregateOperations.SUM,
displayDataLabel: false,
filter: filterConfig,
filter: filterConfigs.configuration,
prefix: '$',
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
@@ -673,7 +694,7 @@ const createDealValueCreatedThisMonth = ({
STANDARD_OBJECTS.opportunity.fields.amount.universalIdentifier,
aggregateOperation: AggregateOperations.SUM,
displayDataLabel: false,
filter: filterConfig,
filter: filterConfigs.universal,
prefix: '$',
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.SUNDAY,
@@ -1,6 +1,7 @@
import {
type PageLayoutWidgetConditionalDisplay,
type PageLayoutWidgetPosition,
type GridPosition,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -8,7 +9,6 @@ import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/fl
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { type StandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util';
@@ -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,
}),
};
}
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { CalendarStartDay } from 'twenty-shared/constants';
import {
AggregateOperations,
type ChartFilter,
ObjectRecordGroupByDateGranularity,
OrderByWithGroupBy,
} from 'twenty-shared/types';
@@ -20,7 +21,6 @@ import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/fl
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
import { ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
import { GRAPH_DEFAULT_DATE_GRANULARITY } from 'src/modules/dashboard/chart-data/constants/graph-default-date-granularity.constant';
import { GRAPH_DEFAULT_ORDER_BY } from 'src/modules/dashboard/chart-data/constants/graph-default-order-by.constant';
import {
@@ -1,4 +1,5 @@
import {
type ChartFilter,
type CompositeFieldSubFieldName,
type FilterableAndTSVectorFieldType,
type PartialFieldMetadataItem,
@@ -14,11 +15,10 @@ import {
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
type ConvertChartFilterToGqlOperationFilterParams = {
filter: ChartFilter | undefined;
@@ -72,7 +72,7 @@ export const convertChartFilterToGqlOperationFilter = ({
})
.filter(isDefined);
const convertedRecordFilters: RecordFilter[] = recordFilters.map(
const convertedRecordFilters: Omit<RecordFilter, 'id'>[] = recordFilters.map(
(recordFilter) => {
const field = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: recordFilter.fieldMetadataId,
@@ -80,7 +80,6 @@ export const convertChartFilterToGqlOperationFilter = ({
});
return {
id: recordFilter.id,
fieldMetadataId: recordFilter.fieldMetadataId,
value: recordFilter.value ?? '',
type: (field?.type ??
@@ -2,8 +2,7 @@ import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsT
import {
type PageLayoutTabLayoutMode,
type PageLayoutWidgetConditionalDisplay,
type PageLayoutWidgetPosition,
type PageLayoutWidgetUniversalConfiguration,
type PageLayoutWidgetUniversalConfiguration
} from '@/types';
export type PageLayoutWidgetManifest = SyncableEntityOptions & {
@@ -11,7 +10,6 @@ export type PageLayoutWidgetManifest = SyncableEntityOptions & {
type: string;
objectUniversalIdentifier?: string;
conditionalDisplay?: PageLayoutWidgetConditionalDisplay;
position?: PageLayoutWidgetPosition;
configuration: PageLayoutWidgetUniversalConfiguration;
};
+32
View File
@@ -137,6 +137,37 @@ export { ObjectRecordGroupByDateGranularity } from './ObjectRecordGroupByDateGra
export type { ObjectsPermissions } from './ObjectsPermissions';
export type { ObjectsPermissionsByRoleId } from './ObjectsPermissionsByRoleId';
export type { OrderBy } from './OrderBy';
export type {
ChartRecordFilter,
ChartRecordFilterGroup,
ChartFilter,
UniversalChartFilter,
} from './page-layout/chart-filter.type';
export type { GridPosition } from './page-layout/grid-position.type';
export type {
AggregateChartConfiguration,
GaugeChartConfiguration,
PieChartConfiguration,
BarChartConfiguration,
LineChartConfiguration,
ViewConfiguration,
FieldConfiguration,
FieldsConfiguration,
FieldRichTextConfiguration,
StandaloneRichTextConfiguration,
IframeConfiguration,
FrontComponentConfiguration,
TimelineConfiguration,
TasksConfiguration,
NotesConfiguration,
FilesConfiguration,
EmailsConfiguration,
CalendarConfiguration,
WorkflowConfiguration,
WorkflowVersionConfiguration,
WorkflowRunConfiguration,
PageLayoutWidgetConfiguration,
} from './page-layout/page-layout-widget-configuration.type';
export type {
PageLayoutWidgetGridPosition,
PageLayoutWidgetVerticalListPosition,
@@ -146,6 +177,7 @@ export type {
export type { PageLayoutWidgetUniversalConfiguration } from './page-layout/page-layout-widget-universal-configuration.type';
export { PageLayoutTabLayoutMode } from './page-layout/PageLayoutTabLayoutMode';
export type { PageLayoutWidgetConditionalDisplay } from './page-layout/PageLayoutWidgetConditionalDisplay';
export type { RatioAggregateConfig } from './page-layout/ratio-aggregate-config.type';
export type { PartialFieldMetadataItem } from './PartialFieldMetadataItem';
export type { PartialFieldMetadataItemOption } from './PartialFieldMetadataOption';
export type { QueryCursorDirection } from './QueryCursorDirection';
@@ -1,6 +1,8 @@
import { type FormatRecordSerializedRelationProperties } from '../FormatRecordSerializedRelationProperties.type';
import { type SerializedRelation } from '../SerializedRelation.type';
export type ChartRecordFilter = {
id: string;
fieldMetadataId: string;
fieldMetadataId: SerializedRelation;
operand: string;
value?: string | null;
type?: string;
@@ -18,3 +20,6 @@ export type ChartFilter = {
recordFilters?: ChartRecordFilter[];
recordFilterGroups?: ChartRecordFilterGroup[];
};
export type UniversalChartFilter =
FormatRecordSerializedRelationProperties<ChartFilter>;
@@ -0,0 +1,188 @@
import { type AggregateOperations } from '../AggregateOperations';
import { type ObjectRecordGroupByDateGranularity } from '../ObjectRecordGroupByDateGranularity';
import { type SerializedRelation } from '../SerializedRelation.type';
import { type ChartFilter } from './chart-filter.type';
import { type RatioAggregateConfig } from './ratio-aggregate-config.type';
type BaseChartConfiguration = {
aggregateFieldMetadataId: SerializedRelation;
aggregateOperation: AggregateOperations;
displayDataLabel?: boolean;
description?: string;
color?: string;
filter?: ChartFilter;
timezone?: string;
firstDayOfTheWeek?: number;
};
export type AggregateChartConfiguration = BaseChartConfiguration & {
configurationType: 'AGGREGATE_CHART';
label?: string;
format?: string;
prefix?: string;
suffix?: string;
ratioAggregateConfig?: RatioAggregateConfig;
};
export type GaugeChartConfiguration = BaseChartConfiguration & {
configurationType: 'GAUGE_CHART';
};
export type PieChartConfiguration = BaseChartConfiguration & {
configurationType: 'PIE_CHART';
groupByFieldMetadataId: SerializedRelation;
groupBySubFieldName?: string;
dateGranularity?: ObjectRecordGroupByDateGranularity;
orderBy?: string;
manualSortOrder?: string[];
showCenterMetric?: boolean;
displayLegend?: boolean;
hideEmptyCategory?: boolean;
splitMultiValueFields?: boolean;
};
export type BarChartConfiguration = BaseChartConfiguration & {
configurationType: 'BAR_CHART';
primaryAxisGroupByFieldMetadataId: SerializedRelation;
primaryAxisGroupBySubFieldName?: string;
primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity;
primaryAxisOrderBy?: string;
primaryAxisManualSortOrder?: string[];
secondaryAxisGroupByFieldMetadataId?: SerializedRelation;
secondaryAxisGroupBySubFieldName?: string;
secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity;
secondaryAxisOrderBy?: string;
secondaryAxisManualSortOrder?: string[];
omitNullValues?: boolean;
splitMultiValueFields?: boolean;
axisNameDisplay?: string;
displayLegend?: boolean;
rangeMin?: number;
rangeMax?: number;
groupMode?: string;
layout?: string;
isCumulative?: boolean;
};
export type LineChartConfiguration = BaseChartConfiguration & {
configurationType: 'LINE_CHART';
primaryAxisGroupByFieldMetadataId: SerializedRelation;
primaryAxisGroupBySubFieldName?: string;
primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity;
primaryAxisOrderBy?: string;
primaryAxisManualSortOrder?: string[];
secondaryAxisGroupByFieldMetadataId?: SerializedRelation;
secondaryAxisGroupBySubFieldName?: string;
secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity;
secondaryAxisOrderBy?: string;
secondaryAxisManualSortOrder?: string[];
omitNullValues?: boolean;
splitMultiValueFields?: boolean;
axisNameDisplay?: string;
displayLegend?: boolean;
rangeMin?: number;
rangeMax?: number;
isStacked?: boolean;
isCumulative?: boolean;
};
export type ViewConfiguration = {
configurationType: 'VIEW';
};
export type FieldConfiguration = {
configurationType: 'FIELD';
};
type NewFieldDefaultConfiguration = {
isVisible: boolean;
viewFieldGroupId: string | null;
};
export type FieldsConfiguration = {
configurationType: 'FIELDS';
viewId?: string | null;
newFieldDefaultConfiguration?: NewFieldDefaultConfiguration | null;
};
export type FieldRichTextConfiguration = {
configurationType: 'FIELD_RICH_TEXT';
};
export type StandaloneRichTextConfiguration = {
configurationType: 'STANDALONE_RICH_TEXT';
body: {
blocknote?: string | null;
markdown: string | null;
};
};
export type IframeConfiguration = {
configurationType: 'IFRAME';
url?: string;
};
export type FrontComponentConfiguration = {
configurationType: 'FRONT_COMPONENT';
frontComponentId: string;
};
export type TimelineConfiguration = {
configurationType: 'TIMELINE';
};
export type TasksConfiguration = {
configurationType: 'TASKS';
};
export type NotesConfiguration = {
configurationType: 'NOTES';
};
export type FilesConfiguration = {
configurationType: 'FILES';
};
export type EmailsConfiguration = {
configurationType: 'EMAILS';
};
export type CalendarConfiguration = {
configurationType: 'CALENDAR';
};
export type WorkflowConfiguration = {
configurationType: 'WORKFLOW';
};
export type WorkflowVersionConfiguration = {
configurationType: 'WORKFLOW_VERSION';
};
export type WorkflowRunConfiguration = {
configurationType: 'WORKFLOW_RUN';
};
export type PageLayoutWidgetConfiguration =
| AggregateChartConfiguration
| GaugeChartConfiguration
| PieChartConfiguration
| BarChartConfiguration
| LineChartConfiguration
| ViewConfiguration
| FieldConfiguration
| FieldsConfiguration
| FieldRichTextConfiguration
| StandaloneRichTextConfiguration
| IframeConfiguration
| FrontComponentConfiguration
| TimelineConfiguration
| TasksConfiguration
| NotesConfiguration
| FilesConfiguration
| EmailsConfiguration
| CalendarConfiguration
| WorkflowConfiguration
| WorkflowVersionConfiguration
| WorkflowRunConfiguration;
@@ -1,207 +1,6 @@
import { type AggregateOperations } from '../AggregateOperations';
import { type FormatRecordSerializedRelationProperties } from '../FormatRecordSerializedRelationProperties.type';
type ChartFilterRecordFilter = {
id: string;
fieldMetadataUniversalIdentifier: string;
operand: string;
value?: string | null;
type?: string;
recordFilterGroupId?: string | null;
subFieldName?: string | null;
};
type ChartFilterRecordFilterGroup = {
id: string;
logicalOperator: string;
parentRecordFilterGroupId?: string | null;
};
type UniversalChartFilter = {
recordFilters?: ChartFilterRecordFilter[];
recordFilterGroups?: ChartFilterRecordFilterGroup[];
};
type RatioAggregateConfig = {
fieldMetadataUniversalIdentifier: string | null;
optionValue: string;
};
type BaseChartFields = {
aggregateFieldMetadataUniversalIdentifier: string | null;
aggregateOperation: AggregateOperations;
displayDataLabel?: boolean;
description?: string;
color?: string;
filter?: UniversalChartFilter;
timezone?: string;
firstDayOfTheWeek?: number;
};
type AggregateChartUniversalConfiguration = BaseChartFields & {
configurationType: 'AGGREGATE_CHART';
label?: string;
format?: string;
prefix?: string;
suffix?: string;
ratioAggregateConfig?: RatioAggregateConfig;
};
type GaugeChartUniversalConfiguration = BaseChartFields & {
configurationType: 'GAUGE_CHART';
};
type PieChartUniversalConfiguration = BaseChartFields & {
configurationType: 'PIE_CHART';
groupByFieldMetadataUniversalIdentifier: string | null;
groupBySubFieldName?: string;
dateGranularity?: string;
orderBy?: string;
manualSortOrder?: string[];
showCenterMetric?: boolean;
displayLegend?: boolean;
hideEmptyCategory?: boolean;
splitMultiValueFields?: boolean;
};
type BarChartUniversalConfiguration = BaseChartFields & {
configurationType: 'BAR_CHART';
primaryAxisGroupByFieldMetadataUniversalIdentifier: string | null;
primaryAxisGroupBySubFieldName?: string;
primaryAxisDateGranularity?: string;
primaryAxisOrderBy?: string;
primaryAxisManualSortOrder?: string[];
secondaryAxisGroupByFieldMetadataUniversalIdentifier?: string | null;
secondaryAxisGroupBySubFieldName?: string;
secondaryAxisGroupByDateGranularity?: string;
secondaryAxisOrderBy?: string;
secondaryAxisManualSortOrder?: string[];
omitNullValues?: boolean;
splitMultiValueFields?: boolean;
axisNameDisplay?: string;
displayLegend?: boolean;
rangeMin?: number;
rangeMax?: number;
groupMode?: string;
layout?: string;
isCumulative?: boolean;
};
type LineChartUniversalConfiguration = BaseChartFields & {
configurationType: 'LINE_CHART';
primaryAxisGroupByFieldMetadataUniversalIdentifier: string | null;
primaryAxisGroupBySubFieldName?: string;
primaryAxisDateGranularity?: string;
primaryAxisOrderBy?: string;
primaryAxisManualSortOrder?: string[];
secondaryAxisGroupByFieldMetadataUniversalIdentifier?: string | null;
secondaryAxisGroupBySubFieldName?: string;
secondaryAxisGroupByDateGranularity?: string;
secondaryAxisOrderBy?: string;
secondaryAxisManualSortOrder?: string[];
omitNullValues?: boolean;
splitMultiValueFields?: boolean;
axisNameDisplay?: string;
displayLegend?: boolean;
rangeMin?: number;
rangeMax?: number;
isStacked?: boolean;
isCumulative?: boolean;
};
type ViewUniversalConfiguration = {
configurationType: 'VIEW';
};
type FieldUniversalConfiguration = {
configurationType: 'FIELD';
};
type FieldsUniversalConfiguration = {
configurationType: 'FIELDS';
viewId?: string | null;
newFieldDefaultConfiguration?: {
isVisible: boolean;
viewFieldGroupId: string | null;
} | null;
};
type FieldRichTextUniversalConfiguration = {
configurationType: 'FIELD_RICH_TEXT';
};
type StandaloneRichTextUniversalConfiguration = {
configurationType: 'STANDALONE_RICH_TEXT';
body: {
blocknote?: string | null;
markdown: string | null;
};
};
type IframeUniversalConfiguration = {
configurationType: 'IFRAME';
url?: string;
};
type FrontComponentUniversalConfiguration = {
configurationType: 'FRONT_COMPONENT';
frontComponentId: string;
};
type TimelineUniversalConfiguration = {
configurationType: 'TIMELINE';
};
type TasksUniversalConfiguration = {
configurationType: 'TASKS';
};
type NotesUniversalConfiguration = {
configurationType: 'NOTES';
};
type FilesUniversalConfiguration = {
configurationType: 'FILES';
};
type EmailsUniversalConfiguration = {
configurationType: 'EMAILS';
};
type CalendarUniversalConfiguration = {
configurationType: 'CALENDAR';
};
type WorkflowUniversalConfiguration = {
configurationType: 'WORKFLOW';
};
type WorkflowVersionUniversalConfiguration = {
configurationType: 'WORKFLOW_VERSION';
};
type WorkflowRunUniversalConfiguration = {
configurationType: 'WORKFLOW_RUN';
};
import { type PageLayoutWidgetConfiguration } from './page-layout-widget-configuration.type';
export type PageLayoutWidgetUniversalConfiguration =
| AggregateChartUniversalConfiguration
| GaugeChartUniversalConfiguration
| PieChartUniversalConfiguration
| BarChartUniversalConfiguration
| LineChartUniversalConfiguration
| ViewUniversalConfiguration
| FieldUniversalConfiguration
| FieldsUniversalConfiguration
| FieldRichTextUniversalConfiguration
| StandaloneRichTextUniversalConfiguration
| IframeUniversalConfiguration
| FrontComponentUniversalConfiguration
| TimelineUniversalConfiguration
| TasksUniversalConfiguration
| NotesUniversalConfiguration
| FilesUniversalConfiguration
| EmailsUniversalConfiguration
| CalendarUniversalConfiguration
| WorkflowUniversalConfiguration
| WorkflowVersionUniversalConfiguration
| WorkflowRunUniversalConfiguration;
FormatRecordSerializedRelationProperties<PageLayoutWidgetConfiguration>;
@@ -0,0 +1,6 @@
import { type SerializedRelation } from '../SerializedRelation.type';
export type RatioAggregateConfig = {
fieldMetadataId: SerializedRelation;
optionValue: string;
};
@@ -15,7 +15,7 @@ export const computeGqlOperationFilterForEmails = ({
correspondingFieldMetadataItem,
subFieldName,
}: {
recordFilter: RecordFilter;
recordFilter: Omit<RecordFilter, 'id'>;
correspondingFieldMetadataItem: Pick<
PartialFieldMetadataItem,
'name' | 'type'
@@ -13,7 +13,7 @@ export const computeGqlOperationFilterForLinks = ({
correspondingFieldMetadataItem,
subFieldName,
}: {
recordFilter: RecordFilter;
recordFilter: Omit<RecordFilter, 'id'>;
correspondingFieldMetadataItem: Pick<
PartialFieldMetadataItem,
'name' | 'type'
@@ -12,7 +12,7 @@ export const computeEmptyGqlOperationFilterForEmails = ({
recordFilter,
correspondingFieldMetadataItem,
}: {
recordFilter: RecordFilter;
recordFilter: Omit<RecordFilter, 'id'>;
correspondingFieldMetadataItem: Pick<
PartialFieldMetadataItem,
'name' | 'type'
@@ -12,7 +12,7 @@ export const computeEmptyGqlOperationFilterForLinks = ({
recordFilter,
correspondingFieldMetadataItem,
}: {
recordFilter: RecordFilter;
recordFilter: Omit<RecordFilter, 'id'>;
correspondingFieldMetadataItem: Pick<PartialFieldMetadataItem, 'name'>;
}): RecordGqlOperationFilter => {
const subFieldName = recordFilter.subFieldName;
@@ -17,7 +17,7 @@ export const computeRecordGqlOperationFilter = ({
recordFilterGroups,
filterValueDependencies,
}: {
recordFilters: RecordFilter[];
recordFilters: Omit<RecordFilter, 'id'>[];
fields: PartialFieldMetadataItem[];
recordFilterGroups: RecordFilterGroup[];
filterValueDependencies: RecordFilterValueDependencies;
@@ -35,7 +35,7 @@ export const turnRecordFilterGroupsIntoGqlOperationFilter = ({
currentRecordFilterGroupId,
}: {
filterValueDependencies: RecordFilterValueDependencies;
filters: RecordFilter[];
filters: Omit<RecordFilter, 'id'>[];
fields: PartialFieldMetadataItem[];
recordFilterGroups: RecordFilterGroup[];
currentRecordFilterGroupId?: string;
@@ -63,7 +63,7 @@ type FieldShared = {
type TurnRecordFilterIntoRecordGqlOperationFilterParams = {
filterValueDependencies: RecordFilterValueDependencies;
recordFilter: RecordFilter;
recordFilter: Omit<RecordFilter, 'id'>;
fieldMetadataItems: FieldShared[];
};
@@ -27,7 +27,7 @@ import { isNonEmptyString } from '@sniptt/guards';
type GetEmptyRecordGqlOperationFilterParams = {
operand: ViewFilterOperand;
correspondingField: Pick<PartialFieldMetadataItem, 'id' | 'name' | 'type'>;
recordFilter: RecordFilter;
recordFilter: Omit<RecordFilter, 'id'>;
};
export const getEmptyRecordGqlOperationFilter = ({