From 46e5420d202bf1886e8642f84982eb14df1eb617 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:04:15 +0530 Subject: [PATCH] dashboard workspace standard seeds (#16962) # Introduction In this pull request we're introducing new standard page layout, tabs and widget ( 1 page layout, 1 tab and 8 widgets ) and also a new opportunity field Also now prefilling new records, 6 opportunities and a dashboard. ## Standard declaration ### New workspace creation Relies on existing standard declaration builder ### Backfill command We've been hacking through the standard builder in order to extract only the standard page layout entities, updated their entity dependencies to match the workspace ids so the validation passes ## Remark - Refactored the `PageLayoutWidget` configuration type to be dynamically typed through a generic discriminated union --------- Co-authored-by: prastoin --- .../src/generated-metadata/graphql.ts | 67 +- .../twenty-front/src/generated/graphql.ts | 110 +- .../utils/isWidgetConfigurationOfType.ts | 59 + .../fragments/pageLayoutWidgetFragment.ts | 39 + ...ackfill-opportunity-owner-field.command.ts | 207 ++ ...-backfill-standard-page-layouts.command.ts | 248 +++ .../1-16-upgrade-version-command.module.ts | 25 +- ...t-widgets-with-workspace-field-ids.util.ts | 422 ++++ .../upgrade.command.ts | 6 + .../__tests__/workspace.service.spec.ts | 15 +- .../workspace/services/workspace.service.ts | 84 +- ...ity-from-flat-entity-maps-or-throw.util.ts | 2 +- ...y-by-universal-identifier-or-throw.util.ts | 37 + .../types/flat-page-layout-widget.type.ts | 5 +- ...ayout-widget-configuration-of-type.util.ts | 12 + ...r-by-widget-configuration-type.constant.ts | 48 +- .../dtos/calendar-configuration.dto.ts | 16 + .../dtos/emails-configuration.dto.ts | 16 + .../dtos/field-configuration.dto.ts | 16 + .../dtos/field-rich-text-configuration.dto.ts | 16 + .../dtos/fields-configuration.dto.ts | 16 + .../dtos/files-configuration.dto.ts | 16 + .../dtos/notes-configuration.dto.ts | 16 + .../dtos/tasks-configuration.dto.ts | 16 + .../dtos/timeline-configuration.dto.ts | 16 + .../dtos/view-configuration.dto.ts | 14 + .../dtos/workflow-configuration.dto.ts | 16 + .../dtos/workflow-run-configuration.dto.ts | 16 + .../workflow-version-configuration.dto.ts | 16 + .../entities/page-layout-widget.entity.ts | 12 +- ...l-page-layout-widget-configuration.type.ts | 8 +- .../page-layout-widget-configuration.type.ts | 8 + .../opportunity-data-seeds.constant.ts | 3 + .../prefill-dashboards.ts | 65 + .../prefill-opportunities.ts | 179 ++ .../prefill-people.ts | 14 +- .../constants/standard-object.constant.ts | 4 + .../standard-page-layout.constant.ts | 36 + ...nty-standard-all-metadata-name.constant.ts | 3 + .../all-standard-page-layout-name.type.ts | 3 + .../all-standard-page-layout-tab-name.type.ts | 6 + ...l-standard-page-layout-widget-name.type.ts | 12 + ...adata-related-entity-ids.util.spec.ts.snap | 1925 +++++++++++++++++ ...adata-related-entity-ids.util.spec.ts.snap | 40 + ...t-metadata-related-entity-ids.util.spec.ts | 25 + ...t-metadata-related-entity-ids.util.spec.ts | 25 + ...unity-standard-flat-field-metadata.util.ts | 24 + ...ember-standard-flat-field-metadata.util.ts | 23 + ...layout-metadata-related-entity-ids.util.ts | 101 + ...flat-page-layout-tab-metadata-maps.util.ts | 30 + ...dard-page-layout-tab-flat-metadata.util.ts | 68 + ...-flat-page-layout-tab-builders.constant.ts | 27 + ...t-page-layout-widget-metadata-maps.util.ts | 30 + ...d-page-layout-widget-flat-metadata.util.ts | 91 + ...at-page-layout-widget-builders.constant.ts | 464 ++++ ...ard-flat-page-layout-metadata-maps.util.ts | 30 + ...standard-page-layout-flat-metadata.util.ts | 45 + ...dard-flat-page-layout-builders.constant.ts | 24 + ...plication-all-flat-entity-maps.constant.ts | 35 +- .../workspace-manager.service.ts | 66 +- ...at-page-layout-widget-validator.service.ts | 8 +- .../opportunity.workspace-entity.ts | 3 + .../workspace-member.workspace-entity.ts | 2 + 63 files changed, 4930 insertions(+), 101 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-16/utils/update-standard-page-layout-widgets-with-workspace-field-ids.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/is-flat-page-layout-widget-configuration-of-type.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/calendar-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/emails-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-rich-text-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/fields-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/files-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/notes-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/tasks-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/timeline-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/view-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-run-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-version-configuration.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configuration.type.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts.snap create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/build-standard-flat-page-layout-tab-metadata-maps.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/create-standard-page-layout-tab-flat-metadata.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/standard-flat-page-layout-tab-builders.constant.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/standard-flat-page-layout-widget-builders.constant.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/standard-flat-page-layout-builders.constant.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 2c2b53d2b8..30a58b6025 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -570,6 +570,11 @@ export enum CalendarChannelVisibility { SHARE_EVERYTHING = 'SHARE_EVERYTHING' } +export type CalendarConfiguration = { + __typename?: 'CalendarConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type Captcha = { __typename?: 'Captcha'; provider?: Maybe; @@ -1317,6 +1322,11 @@ export enum EmailingDomainStatus { VERIFIED = 'VERIFIED' } +export type EmailsConfiguration = { + __typename?: 'EmailsConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type EventSubscription = { __typename?: 'EventSubscription'; eventStreamId: Scalars['String']; @@ -1393,6 +1403,11 @@ export type Field = { updatedAt: Scalars['DateTime']; }; +export type FieldConfiguration = { + __typename?: 'FieldConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type FieldConnection = { __typename?: 'FieldConnection'; /** Array of edges. */ @@ -1465,6 +1480,16 @@ export type FieldPermissionInput = { objectMetadataId: Scalars['UUID']; }; +export type FieldRichTextConfiguration = { + __typename?: 'FieldRichTextConfiguration'; + configurationType: WidgetConfigurationType; +}; + +export type FieldsConfiguration = { + __typename?: 'FieldsConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type File = { __typename?: 'File'; createdAt: Scalars['DateTime']; @@ -1486,6 +1511,11 @@ export enum FileFolder { WorkspaceLogo = 'WorkspaceLogo' } +export type FilesConfiguration = { + __typename?: 'FilesConfiguration'; + configurationType: WidgetConfigurationType; +}; + export enum FilterIs { NotNull = 'NotNull', Null = 'Null' @@ -2988,6 +3018,11 @@ export type NativeModelCapabilities = { webSearch?: Maybe; }; +export type NotesConfiguration = { + __typename?: 'NotesConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type Object = { __typename?: 'Object'; applicationId?: Maybe; @@ -4269,6 +4304,11 @@ export type SystemHealthService = { status: AdminPanelHealthServiceStatus; }; +export type TasksConfiguration = { + __typename?: 'TasksConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type TestHttpRequestInput = { /** Request body */ body?: InputMaybe; @@ -4331,6 +4371,11 @@ export type TimelineCalendarEventsWithTotal = { totalNumberOfCalendarEvents: Scalars['Int']; }; +export type TimelineConfiguration = { + __typename?: 'TimelineConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type TimelineThread = { __typename?: 'TimelineThread'; firstParticipant: TimelineThreadParticipant; @@ -4879,6 +4924,11 @@ export enum ViewCalendarLayout { WEEK = 'WEEK' } +export type ViewConfiguration = { + __typename?: 'ViewConfiguration'; + configurationType: WidgetConfigurationType; +}; + export enum ViewFilterGroupLogicalOperator { AND = 'AND', NOT = 'NOT', @@ -4941,7 +4991,7 @@ export type Webhook = { updatedAt: Scalars['DateTime']; }; -export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | PieChartConfiguration | StandaloneRichTextConfiguration; +export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | CalendarConfiguration | EmailsConfiguration | FieldConfiguration | FieldRichTextConfiguration | FieldsConfiguration | FilesConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | NotesConfiguration | PieChartConfiguration | StandaloneRichTextConfiguration | TasksConfiguration | TimelineConfiguration | ViewConfiguration | WorkflowConfiguration | WorkflowRunConfiguration | WorkflowVersionConfiguration; export enum WidgetConfigurationType { AGGREGATE_CHART = 'AGGREGATE_CHART', @@ -5026,12 +5076,22 @@ export enum WorkflowActionType { UPSERT_RECORD = 'UPSERT_RECORD' } +export type WorkflowConfiguration = { + __typename?: 'WorkflowConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type WorkflowRun = { __typename?: 'WorkflowRun'; id: Scalars['UUID']; status: WorkflowRunStatusEnum; }; +export type WorkflowRunConfiguration = { + __typename?: 'WorkflowRunConfiguration'; + configurationType: WidgetConfigurationType; +}; + /** Status of the workflow run */ export enum WorkflowRunStatusEnum { COMPLETED = 'COMPLETED', @@ -5061,6 +5121,11 @@ export type WorkflowStepPositionUpdateInput = { position: WorkflowStepPositionInput; }; +export type WorkflowVersionConfiguration = { + __typename?: 'WorkflowVersionConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type WorkflowVersionDto = { __typename?: 'WorkflowVersionDTO'; createdAt: Scalars['String']; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 97aebdd86b..fd232ca54a 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -570,6 +570,11 @@ export enum CalendarChannelVisibility { SHARE_EVERYTHING = 'SHARE_EVERYTHING' } +export type CalendarConfiguration = { + __typename?: 'CalendarConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type Captcha = { __typename?: 'Captcha'; provider?: Maybe; @@ -1300,6 +1305,11 @@ export enum EmailingDomainStatus { VERIFIED = 'VERIFIED' } +export type EmailsConfiguration = { + __typename?: 'EmailsConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type EventSubscription = { __typename?: 'EventSubscription'; eventStreamId: Scalars['String']; @@ -1376,6 +1386,11 @@ export type Field = { updatedAt: Scalars['DateTime']; }; +export type FieldConfiguration = { + __typename?: 'FieldConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type FieldConnection = { __typename?: 'FieldConnection'; /** Array of edges. */ @@ -1448,6 +1463,16 @@ export type FieldPermissionInput = { objectMetadataId: Scalars['UUID']; }; +export type FieldRichTextConfiguration = { + __typename?: 'FieldRichTextConfiguration'; + configurationType: WidgetConfigurationType; +}; + +export type FieldsConfiguration = { + __typename?: 'FieldsConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type File = { __typename?: 'File'; createdAt: Scalars['DateTime']; @@ -1469,6 +1494,11 @@ export enum FileFolder { WorkspaceLogo = 'WorkspaceLogo' } +export type FilesConfiguration = { + __typename?: 'FilesConfiguration'; + configurationType: WidgetConfigurationType; +}; + export enum FilterIs { NotNull = 'NotNull', Null = 'Null' @@ -2922,6 +2952,11 @@ export type NativeModelCapabilities = { webSearch?: Maybe; }; +export type NotesConfiguration = { + __typename?: 'NotesConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type Object = { __typename?: 'Object'; applicationId?: Maybe; @@ -4144,6 +4179,11 @@ export type SystemHealthService = { status: AdminPanelHealthServiceStatus; }; +export type TasksConfiguration = { + __typename?: 'TasksConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type TestHttpRequestInput = { /** Request body */ body?: InputMaybe; @@ -4206,6 +4246,11 @@ export type TimelineCalendarEventsWithTotal = { totalNumberOfCalendarEvents: Scalars['Int']; }; +export type TimelineConfiguration = { + __typename?: 'TimelineConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type TimelineThread = { __typename?: 'TimelineThread'; firstParticipant: TimelineThreadParticipant; @@ -4744,6 +4789,11 @@ export enum ViewCalendarLayout { WEEK = 'WEEK' } +export type ViewConfiguration = { + __typename?: 'ViewConfiguration'; + configurationType: WidgetConfigurationType; +}; + export enum ViewFilterGroupLogicalOperator { AND = 'AND', NOT = 'NOT', @@ -4806,7 +4856,7 @@ export type Webhook = { updatedAt: Scalars['DateTime']; }; -export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | PieChartConfiguration | StandaloneRichTextConfiguration; +export type WidgetConfiguration = AggregateChartConfiguration | BarChartConfiguration | CalendarConfiguration | EmailsConfiguration | FieldConfiguration | FieldRichTextConfiguration | FieldsConfiguration | FilesConfiguration | GaugeChartConfiguration | IframeConfiguration | LineChartConfiguration | NotesConfiguration | PieChartConfiguration | StandaloneRichTextConfiguration | TasksConfiguration | TimelineConfiguration | ViewConfiguration | WorkflowConfiguration | WorkflowRunConfiguration | WorkflowVersionConfiguration; export enum WidgetConfigurationType { AGGREGATE_CHART = 'AGGREGATE_CHART', @@ -4891,12 +4941,22 @@ export enum WorkflowActionType { UPSERT_RECORD = 'UPSERT_RECORD' } +export type WorkflowConfiguration = { + __typename?: 'WorkflowConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type WorkflowRun = { __typename?: 'WorkflowRun'; id: Scalars['UUID']; status: WorkflowRunStatusEnum; }; +export type WorkflowRunConfiguration = { + __typename?: 'WorkflowRunConfiguration'; + configurationType: WidgetConfigurationType; +}; + /** Status of the workflow run */ export enum WorkflowRunStatusEnum { COMPLETED = 'COMPLETED', @@ -4926,6 +4986,11 @@ export type WorkflowStepPositionUpdateInput = { position: WorkflowStepPositionInput; }; +export type WorkflowVersionConfiguration = { + __typename?: 'WorkflowVersionConfiguration'; + configurationType: WidgetConfigurationType; +}; + export type WorkflowVersionDto = { __typename?: 'WorkflowVersionDTO'; createdAt: Scalars['String']; @@ -5103,7 +5168,7 @@ export type SearchQueryVariables = Exact<{ export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } }; -export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration: { __typename?: 'AggregateChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: any, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, layout: BarChartLayout, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', configurationType: WidgetConfigurationType, url?: string | null } | { __typename?: 'LineChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', configurationType: WidgetConfigurationType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, manualSortOrder?: Array | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', configurationType: WidgetConfigurationType, body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } }; +export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration: { __typename?: 'AggregateChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: any, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, layout: BarChartLayout, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'CalendarConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'EmailsConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldRichTextConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldsConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FilesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'GaugeChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', configurationType: WidgetConfigurationType, url?: string | null } | { __typename?: 'LineChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'NotesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'PieChartConfiguration', configurationType: WidgetConfigurationType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, manualSortOrder?: Array | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', configurationType: WidgetConfigurationType, body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | { __typename?: 'TasksConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'TimelineConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'ViewConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowRunConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowVersionConfiguration', configurationType: WidgetConfigurationType } }; export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{ id: Scalars['String']; @@ -5111,7 +5176,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{ }>; -export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration: { __typename?: 'AggregateChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: any, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, layout: BarChartLayout, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', configurationType: WidgetConfigurationType, url?: string | null } | { __typename?: 'LineChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', configurationType: WidgetConfigurationType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, manualSortOrder?: Array | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', configurationType: WidgetConfigurationType, body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } }> | null }> | null } }; +export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration: { __typename?: 'AggregateChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: any, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, layout: BarChartLayout, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'CalendarConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'EmailsConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldRichTextConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldsConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FilesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'GaugeChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', configurationType: WidgetConfigurationType, url?: string | null } | { __typename?: 'LineChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'NotesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'PieChartConfiguration', configurationType: WidgetConfigurationType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, manualSortOrder?: Array | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', configurationType: WidgetConfigurationType, body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | { __typename?: 'TasksConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'TimelineConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'ViewConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowRunConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowVersionConfiguration', configurationType: WidgetConfigurationType } }> | null }> | null } }; export type OnDbEventSubscriptionVariables = Exact<{ input: OnDbEventInput; @@ -5544,6 +5609,45 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql` markdown } } + ... on CalendarConfiguration { + configurationType + } + ... on EmailsConfiguration { + configurationType + } + ... on FieldConfiguration { + configurationType + } + ... on FieldRichTextConfiguration { + configurationType + } + ... on FieldsConfiguration { + configurationType + } + ... on FilesConfiguration { + configurationType + } + ... on NotesConfiguration { + configurationType + } + ... on TasksConfiguration { + configurationType + } + ... on TimelineConfiguration { + configurationType + } + ... on ViewConfiguration { + configurationType + } + ... on WorkflowConfiguration { + configurationType + } + ... on WorkflowRunConfiguration { + configurationType + } + ... on WorkflowVersionConfiguration { + configurationType + } } pageLayoutTabId } diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType.ts index c2e7951044..50ef98671c 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType.ts @@ -3,13 +3,24 @@ import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguratio import { type AggregateChartConfiguration, type BarChartConfiguration, + type CalendarConfiguration, + type EmailsConfiguration, + type FieldRichTextConfiguration, + type FilesConfiguration, type GaugeChartConfiguration, type IframeConfiguration, type LineChartConfiguration, + type NotesConfiguration, type PieChartConfiguration, type StandaloneRichTextConfiguration, + type TasksConfiguration, + type TimelineConfiguration, + type ViewConfiguration, type WidgetConfiguration, type WidgetConfigurationType, + type WorkflowConfiguration, + type WorkflowRunConfiguration, + type WorkflowVersionConfiguration, } from '~/generated/graphql'; type WidgetConfigurationTypenameMap = { @@ -22,6 +33,27 @@ type WidgetConfigurationTypenameMap = { BarChartConfiguration: Omit & { configurationType: WidgetConfigurationType.BAR_CHART; }; + CalendarConfiguration: Omit & { + configurationType: WidgetConfigurationType.CALENDAR; + }; + EmailsConfiguration: Omit & { + configurationType: WidgetConfigurationType.EMAILS; + }; + FieldConfiguration: Omit & { + configurationType: WidgetConfigurationType.FIELD; + }; + FieldRichTextConfiguration: Omit< + FieldRichTextConfiguration, + 'configurationType' + > & { + configurationType: WidgetConfigurationType.FIELD_RICH_TEXT; + }; + FieldsConfiguration: Omit & { + configurationType: WidgetConfigurationType.FIELDS; + }; + FilesConfiguration: Omit & { + configurationType: WidgetConfigurationType.FILES; + }; GaugeChartConfiguration: Omit< GaugeChartConfiguration, 'configurationType' @@ -34,6 +66,9 @@ type WidgetConfigurationTypenameMap = { LineChartConfiguration: Omit & { configurationType: WidgetConfigurationType.LINE_CHART; }; + NotesConfiguration: Omit & { + configurationType: WidgetConfigurationType.NOTES; + }; PieChartConfiguration: Omit & { configurationType: WidgetConfigurationType.PIE_CHART; }; @@ -43,6 +78,30 @@ type WidgetConfigurationTypenameMap = { > & { configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT; }; + TasksConfiguration: Omit & { + configurationType: WidgetConfigurationType.TASKS; + }; + TimelineConfiguration: Omit & { + configurationType: WidgetConfigurationType.TIMELINE; + }; + ViewConfiguration: Omit & { + configurationType: WidgetConfigurationType.VIEW; + }; + WorkflowConfiguration: Omit & { + configurationType: WidgetConfigurationType.WORKFLOW; + }; + WorkflowRunConfiguration: Omit< + WorkflowRunConfiguration, + 'configurationType' + > & { + configurationType: WidgetConfigurationType.WORKFLOW_RUN; + }; + WorkflowVersionConfiguration: Omit< + WorkflowVersionConfiguration, + 'configurationType' + > & { + configurationType: WidgetConfigurationType.WORKFLOW_VERSION; + }; }; type WidgetConfigurationTypename = keyof WidgetConfigurationTypenameMap; diff --git a/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts b/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts index 1b792e57f7..e012b1bf49 100644 --- a/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts +++ b/packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts @@ -131,6 +131,45 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql` markdown } } + ... on CalendarConfiguration { + configurationType + } + ... on EmailsConfiguration { + configurationType + } + ... on FieldConfiguration { + configurationType + } + ... on FieldRichTextConfiguration { + configurationType + } + ... on FieldsConfiguration { + configurationType + } + ... on FilesConfiguration { + configurationType + } + ... on NotesConfiguration { + configurationType + } + ... on TasksConfiguration { + configurationType + } + ... on TimelineConfiguration { + configurationType + } + ... on ViewConfiguration { + configurationType + } + ... on WorkflowConfiguration { + configurationType + } + ... on WorkflowRunConfiguration { + configurationType + } + ... on WorkflowVersionConfiguration { + configurationType + } } pageLayoutTabId } diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command.ts new file mode 100644 index 0000000000..6fd41c1b02 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command.ts @@ -0,0 +1,207 @@ +import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; +import { FieldMetadataType, RelationType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; + +import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service'; +import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant'; + +@Command({ + name: 'upgrade:1-16:backfill-opportunity-owner-field', + description: 'Backfill owner field for opportunity object', +}) +export class BackfillOpportunityOwnerFieldCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + protected readonly logger = new Logger( + BackfillOpportunityOwnerFieldCommand.name, + ); + + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + @InjectRepository(FieldMetadataEntity) + private readonly fieldMetadataRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + private readonly fieldMetadataService: FieldMetadataService, + private readonly workspaceCacheService: WorkspaceCacheService, + private readonly applicationService: ApplicationService, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Starting backfill of owner field for opportunity in workspace ${workspaceId}`, + ); + + const { twentyStandardFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId, + }, + ); + + const { flatFieldMetadataMaps, flatObjectMetadataMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + ]); + + const opportunityObjectMetadata = Object.values( + flatObjectMetadataMaps.byId, + ).find( + (obj) => + isDefined(obj) && obj.standardId === STANDARD_OBJECT_IDS.opportunity, + ); + + if (!isDefined(opportunityObjectMetadata)) { + this.logger.log( + `Opportunity object not found for workspace ${workspaceId}, skipping`, + ); + + return; + } + + const workspaceMemberObjectMetadata = Object.values( + flatObjectMetadataMaps.byId, + ).find( + (obj) => + isDefined(obj) && + obj.standardId === STANDARD_OBJECT_IDS.workspaceMember, + ); + + if (!isDefined(workspaceMemberObjectMetadata)) { + this.logger.log( + `WorkspaceMember object not found for workspace ${workspaceId}, skipping`, + ); + + return; + } + + const flatFieldMetadatas = findManyFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityIds: opportunityObjectMetadata.fieldMetadataIds, + flatEntityMaps: flatFieldMetadataMaps, + }); + + const ownerUniversalIdentifier = + STANDARD_OBJECTS.opportunity.fields.owner.universalIdentifier; + + const ownerField = flatFieldMetadatas.find( + (field) => field.universalIdentifier === ownerUniversalIdentifier, + ); + + if (isDefined(ownerField)) { + this.logger.log( + `Opportunity already has owner field in workspace ${workspaceId}, skipping`, + ); + + return; + } + + if (options.dryRun) { + this.logger.log( + `[DRY RUN] Would create owner field for opportunity in workspace ${workspaceId}`, + ); + + return; + } + + const createFieldInput: CreateFieldInput = { + workspaceId, + objectMetadataId: opportunityObjectMetadata.id, + name: 'owner', + type: FieldMetadataType.RELATION, + label: 'Owner', + description: 'Opportunity owner', + icon: 'IconUserCircle', + isNullable: true, + isCustom: false, + isSystem: false, + isActive: true, + standardId: ownerUniversalIdentifier, + universalIdentifier: ownerUniversalIdentifier, + applicationId: twentyStandardFlatApplication.id, + relationCreationPayload: { + type: RelationType.MANY_TO_ONE, + targetObjectMetadataId: workspaceMemberObjectMetadata.id, + targetFieldLabel: 'Owned opportunities', + targetFieldIcon: 'IconTargetArrow', + }, + }; + + const ownedOpportunitiesUniversalIdentifier = + STANDARD_OBJECTS.workspaceMember.fields.ownedOpportunities + .universalIdentifier; + + try { + await this.fieldMetadataService.createManyFields({ + createFieldInputs: [createFieldInput], + workspaceId, + isSystemBuild: true, + }); + + this.logger.log( + `Created owner field for opportunity in workspace ${workspaceId}`, + ); + + const ownedOpportunitiesField = + await this.fieldMetadataRepository.findOne({ + where: { + workspaceId, + objectMetadataId: workspaceMemberObjectMetadata.id, + name: 'ownedOpportunities', + }, + }); + + if (isDefined(ownedOpportunitiesField)) { + await this.fieldMetadataRepository.update( + { id: ownedOpportunitiesField.id }, + { + universalIdentifier: ownedOpportunitiesUniversalIdentifier, + standardId: ownedOpportunitiesUniversalIdentifier, + applicationId: twentyStandardFlatApplication.id, + }, + ); + + this.logger.log( + `Updated ownedOpportunities field universalIdentifier in workspace ${workspaceId}`, + ); + + await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ + 'flatFieldMetadataMaps', + ]); + } else { + throw new Error( + `Should never occur could not find the created ownedOpportunities field`, + ); + } + + this.logger.log( + `Successfully backfilled owner field for opportunity in workspace ${workspaceId}`, + ); + } catch (error) { + this.logger.error( + `Failed to create owner field for opportunity in workspace ${workspaceId}`, + error, + ); + throw error; + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command.ts new file mode 100644 index 0000000000..989e7613e3 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command.ts @@ -0,0 +1,248 @@ +import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { FieldActorSource } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; + +import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { updateStandardPageLayoutWidgetsWithWorkspaceFieldIds } from 'src/database/commands/upgrade-version-command/1-16/utils/update-standard-page-layout-widgets-with-workspace-field-ids.util'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { MY_FIRST_DASHBOARD_ID } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards'; +import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; +import { DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity'; + +@Command({ + name: 'upgrade:1-16:backfill-standard-page-layouts', + description: 'Backfill standard page layouts and dashboard records', +}) +export class BackfillStandardPageLayoutsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + protected readonly logger = new Logger( + BackfillStandardPageLayoutsCommand.name, + ); + + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + private readonly applicationService: ApplicationService, + private readonly workspaceCacheService: WorkspaceCacheService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Starting backfill of standard page layouts for workspace ${workspaceId}`, + ); + + await this.backfillStandardPageLayoutMetadata({ workspaceId, options }); + await this.backfillDashboardRecord({ workspaceId, options }); + + this.logger.log( + `Successfully backfilled standard page layouts for workspace ${workspaceId}`, + ); + } + + private async backfillStandardPageLayoutMetadata({ + workspaceId, + options, + }: { + workspaceId: string; + options: RunOnWorkspaceArgs['options']; + }): Promise { + const { + flatPageLayoutMaps: fromFlatPageLayoutMaps, + flatPageLayoutTabMaps: fromFlatPageLayoutTabMaps, + flatPageLayoutWidgetMaps: fromFlatPageLayoutWidgetMaps, + featureFlagsMap, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatPageLayoutMaps', + 'flatPageLayoutWidgetMaps', + 'flatPageLayoutTabMaps', + 'featureFlagsMap', + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + ]); + + const flatPageLayout = findFlatEntityByUniversalIdentifier({ + flatEntityMaps: fromFlatPageLayoutMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier, + }); + + if (isDefined(flatPageLayout)) { + this.logger.log( + `Standard page layout already exists in workspace ${workspaceId}, skipping metadata backfill`, + ); + + return; + } + + const { twentyStandardFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId, + }, + ); + + const { + flatPageLayoutMaps: toFlatPageLayoutMaps, + flatPageLayoutTabMaps: toFlatPageLayoutTabMaps, + flatPageLayoutWidgetMaps: rawToFlatPageLayoutWidgetMaps, + } = computeTwentyStandardApplicationAllFlatEntityMaps({ + now: new Date().toISOString(), + workspaceId, + twentyStandardApplicationId: twentyStandardFlatApplication.id, + }); + + const toFlatPageLayoutWidgetMaps = + updateStandardPageLayoutWidgetsWithWorkspaceFieldIds({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps: rawToFlatPageLayoutWidgetMaps, + }); + + if (options.dryRun) { + this.logger.log( + `[DRY RUN] Would create standard page layout entities ${workspaceId}`, + ); + + return; + } + + const validateAndbuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigrationFromTo( + { + additionalCacheDataMaps: { + featureFlagsMap, + }, + buildOptions: { + isSystemBuild: true, + }, + fromToAllFlatEntityMaps: { + flatPageLayoutMaps: { + from: fromFlatPageLayoutMaps, + to: toFlatPageLayoutMaps, + }, + flatPageLayoutTabMaps: { + from: fromFlatPageLayoutTabMaps, + to: toFlatPageLayoutTabMaps, + }, + flatPageLayoutWidgetMaps: { + from: fromFlatPageLayoutWidgetMaps, + to: toFlatPageLayoutWidgetMaps, + }, + }, + workspaceId, + dependencyAllFlatEntityMaps: { + flatObjectMetadataMaps, + }, + }, + ); + + if (isDefined(validateAndbuildResult)) { + this.logger.error( + `Failed to create many fields \n ${JSON.stringify(validateAndbuildResult, null, 2)}`, + ); + throw new Error( + `Failed to create standard page layout/tab/widgets on workspace ${workspaceId}`, + ); + } + + this.logger.log( + `Successfully backfilled standard page layout metadata for workspace ${workspaceId}`, + ); + } + + private async backfillDashboardRecord({ + workspaceId, + options, + }: { + workspaceId: string; + options: RunOnWorkspaceArgs['options']; + }): Promise { + const dashboardRepository = + await this.twentyORMGlobalManager.getRepository( + workspaceId, + 'dashboard', + { shouldBypassPermissionChecks: true }, + ); + + const existingDashboard = await dashboardRepository.findOne({ + where: { id: MY_FIRST_DASHBOARD_ID }, + }); + + if (isDefined(existingDashboard)) { + this.logger.log( + `Dashboard already exists for workspace ${workspaceId}, skipping dashboard backfill`, + ); + + return; + } + + if (options.dryRun) { + this.logger.log( + `[DRY RUN] Would create dashboard record for workspace ${workspaceId}`, + ); + + return; + } + + const { flatPageLayoutMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatPageLayoutMaps', + ]); + + const myFirstDashboardPageLayout = findFlatEntityByUniversalIdentifier({ + flatEntityMaps: flatPageLayoutMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier, + }); + + if (!isDefined(myFirstDashboardPageLayout)) { + throw new Error( + `Page layout with universalIdentifier '${STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier}' not found`, + ); + } + + await dashboardRepository.insert({ + id: MY_FIRST_DASHBOARD_ID, + title: 'My First Dashboard', + pageLayoutId: myFirstDashboardPageLayout.id, + position: 0, + createdBy: { + source: FieldActorSource.SYSTEM, + workspaceMemberId: null, + name: 'System', + context: {}, + }, + updatedBy: { + source: FieldActorSource.SYSTEM, + workspaceMemberId: null, + name: 'System', + context: {}, + }, + }); + + this.logger.log( + `Successfully backfilled dashboard record for workspace ${workspaceId}`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts index 2e40de2725..d9d33db5e3 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts @@ -1,20 +1,39 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command'; +import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command'; import { UpdateTaskOnDeleteActionCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; +import { GlobalWorkspaceDataSourceModule } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +import { TwentyStandardApplicationModule } from 'src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module'; +import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; @Module({ imports: [ - TypeOrmModule.forFeature([WorkspaceEntity]), + TypeOrmModule.forFeature([WorkspaceEntity, FieldMetadataEntity]), DataSourceModule, WorkspaceCacheModule, FieldMetadataModule, + ApplicationModule, + GlobalWorkspaceDataSourceModule, + TwentyStandardApplicationModule, + WorkspaceMigrationModule, + ], + providers: [ + UpdateTaskOnDeleteActionCommand, + BackfillOpportunityOwnerFieldCommand, + BackfillStandardPageLayoutsCommand, + ], + exports: [ + UpdateTaskOnDeleteActionCommand, + BackfillOpportunityOwnerFieldCommand, + BackfillStandardPageLayoutsCommand, ], - providers: [UpdateTaskOnDeleteActionCommand], - exports: [UpdateTaskOnDeleteActionCommand], }) export class V1_16_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/utils/update-standard-page-layout-widgets-with-workspace-field-ids.util.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/utils/update-standard-page-layout-widgets-with-workspace-field-ids.util.ts new file mode 100644 index 0000000000..9038587430 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/utils/update-standard-page-layout-widgets-with-workspace-field-ids.util.ts @@ -0,0 +1,422 @@ +import { isDefined, type RecordFilter } from 'twenty-shared/utils'; + +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; +import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.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 FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; +import { isFlatPageLayoutWidgetConfigurationOfType } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/is-flat-page-layout-widget-configuration-of-type.util'; +import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; +import { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant'; +import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; + +type UpdateStandardPageLayoutWidgetsArgs = { + flatFieldMetadataMaps: FlatEntityMaps; + flatObjectMetadataMaps: FlatEntityMaps; + flatPageLayoutWidgetMaps: FlatEntityMaps; +}; + +const findWidgetByUniversalIdentifierOrThrow = < + T extends WidgetConfigurationType, +>({ + flatPageLayoutWidgetMaps, + universalIdentifier, + widgetDescription, + expectedConfigurationType, +}: { + flatPageLayoutWidgetMaps: FlatEntityMaps; + universalIdentifier: string; + widgetDescription: string; + expectedConfigurationType: T; +}): FlatPageLayoutWidget => { + const flatPageLayoutWidget = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatPageLayoutWidgetMaps, + universalIdentifier, + }); + + if ( + !isFlatPageLayoutWidgetConfigurationOfType( + flatPageLayoutWidget, + expectedConfigurationType, + ) + ) { + throw new Error( + `Widget ${widgetDescription} has unexpected configuration type ${flatPageLayoutWidget.configuration.configurationType}, expected ${expectedConfigurationType}`, + ); + } + + return flatPageLayoutWidget; +}; + +// Widget updater: dealsByCompany (PIE_CHART) +const updateDealsByCompanyWidget = ({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps, +}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps => { + const opportunityIdField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.id.universalIdentifier, + }); + + const opportunityCompanyField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.company.universalIdentifier, + }); + + const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatObjectMetadataMaps, + universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier, + }); + + const widget = findWidgetByUniversalIdentifierOrThrow({ + flatPageLayoutWidgetMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets.dealsByCompany + .universalIdentifier, + widgetDescription: 'dealsByCompany', + expectedConfigurationType: WidgetConfigurationType.PIE_CHART, + }); + + const updatedWidget: FlatPageLayoutWidget = + { + ...widget, + objectMetadataId: opportunityObject.id, + configuration: { + ...widget.configuration, + aggregateFieldMetadataId: opportunityIdField.id, + groupByFieldMetadataId: opportunityCompanyField.id, + }, + }; + + return replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedWidget, + flatEntityMaps: flatPageLayoutWidgetMaps, + }); +}; + +// Widget updater: pipelineValueByStage (BAR_CHART) +const updatePipelineValueByStageWidget = ({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps, +}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps => { + const opportunityAmountField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.amount.universalIdentifier, + }); + + const opportunityStageField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.stage.universalIdentifier, + }); + + const opportunityCompanyField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.company.universalIdentifier, + }); + + const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatObjectMetadataMaps, + universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier, + }); + + const widget = findWidgetByUniversalIdentifierOrThrow({ + flatPageLayoutWidgetMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets + .pipelineValueByStage.universalIdentifier, + widgetDescription: 'pipelineValueByStage', + expectedConfigurationType: WidgetConfigurationType.BAR_CHART, + }); + + const updatedWidget: FlatPageLayoutWidget = + { + ...widget, + objectMetadataId: opportunityObject.id, + configuration: { + ...widget.configuration, + aggregateFieldMetadataId: opportunityAmountField.id, + primaryAxisGroupByFieldMetadataId: opportunityStageField.id, + secondaryAxisGroupByFieldMetadataId: opportunityCompanyField.id, + }, + }; + + return replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedWidget, + flatEntityMaps: flatPageLayoutWidgetMaps, + }); +}; + +// Widget updater: revenueTimeline (LINE_CHART) +const updateRevenueTimelineWidget = ({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps, +}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps => { + const opportunityAmountField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.amount.universalIdentifier, + }); + + const opportunityCloseDateField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.closeDate.universalIdentifier, + }); + + const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatObjectMetadataMaps, + universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier, + }); + + const widget = findWidgetByUniversalIdentifierOrThrow({ + flatPageLayoutWidgetMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets.revenueTimeline + .universalIdentifier, + widgetDescription: 'revenueTimeline', + expectedConfigurationType: WidgetConfigurationType.LINE_CHART, + }); + + const updatedWidget: FlatPageLayoutWidget = + { + ...widget, + objectMetadataId: opportunityObject.id, + configuration: { + ...widget.configuration, + aggregateFieldMetadataId: opportunityAmountField.id, + primaryAxisGroupByFieldMetadataId: opportunityCloseDateField.id, + }, + }; + + return replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedWidget, + flatEntityMaps: flatPageLayoutWidgetMaps, + }); +}; + +// Widget updater: opportunitiesByOwner (BAR_CHART) +const updateOpportunitiesByOwnerWidget = ({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps, +}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps => { + const opportunityIdField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.id.universalIdentifier, + }); + + const opportunityOwnerField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.owner.universalIdentifier, + }); + + const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatObjectMetadataMaps, + universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier, + }); + + const widget = findWidgetByUniversalIdentifierOrThrow({ + flatPageLayoutWidgetMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets + .opportunitiesByOwner.universalIdentifier, + widgetDescription: 'opportunitiesByOwner', + expectedConfigurationType: WidgetConfigurationType.BAR_CHART, + }); + + const updatedWidget: FlatPageLayoutWidget = + { + ...widget, + objectMetadataId: opportunityObject.id, + configuration: { + ...widget.configuration, + aggregateFieldMetadataId: opportunityIdField.id, + primaryAxisGroupByFieldMetadataId: opportunityOwnerField.id, + secondaryAxisGroupByFieldMetadataId: opportunityOwnerField.id, + }, + }; + + return replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedWidget, + flatEntityMaps: flatPageLayoutWidgetMaps, + }); +}; + +// Widget updater: dealsCreatedThisMonth (AGGREGATE_CHART) +const updateDealsCreatedThisMonthWidget = ({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps, +}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps => { + const opportunityIdField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.id.universalIdentifier, + }); + + const opportunityCreatedAtField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.createdAt.universalIdentifier, + }); + + const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatObjectMetadataMaps, + universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier, + }); + + const widget = findWidgetByUniversalIdentifierOrThrow({ + flatPageLayoutWidgetMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets + .dealsCreatedThisMonth.universalIdentifier, + widgetDescription: 'dealsCreatedThisMonth', + expectedConfigurationType: WidgetConfigurationType.AGGREGATE_CHART, + }); + + const updatedRecordFilters = widget.configuration.filter?.recordFilters?.map( + (filter: RecordFilter) => ({ + ...filter, + fieldMetadataId: opportunityCreatedAtField.id, + }), + ); + + const updatedWidget: FlatPageLayoutWidget = + { + ...widget, + objectMetadataId: opportunityObject.id, + configuration: { + ...widget.configuration, + aggregateFieldMetadataId: opportunityIdField.id, + filter: isDefined(widget.configuration.filter) + ? { + ...widget.configuration.filter, + recordFilters: updatedRecordFilters, + } + : undefined, + }, + }; + + return replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedWidget, + flatEntityMaps: flatPageLayoutWidgetMaps, + }); +}; + +// Widget updater: dealValueCreatedThisMonth (AGGREGATE_CHART) +const updateDealValueCreatedThisMonthWidget = ({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps, +}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps => { + const opportunityAmountField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.amount.universalIdentifier, + }); + + const opportunityCreatedAtField = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: + STANDARD_OBJECTS.opportunity.fields.createdAt.universalIdentifier, + }); + + const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatObjectMetadataMaps, + universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier, + }); + + const widget = findWidgetByUniversalIdentifierOrThrow({ + flatPageLayoutWidgetMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets + .dealValueCreatedThisMonth.universalIdentifier, + widgetDescription: 'dealValueCreatedThisMonth', + expectedConfigurationType: WidgetConfigurationType.AGGREGATE_CHART, + }); + + const updatedRecordFilters = widget.configuration.filter?.recordFilters?.map( + (filter: RecordFilter) => ({ + ...filter, + fieldMetadataId: opportunityCreatedAtField.id, + }), + ); + + const updatedWidget: FlatPageLayoutWidget = + { + ...widget, + objectMetadataId: opportunityObject.id, + configuration: { + ...widget.configuration, + aggregateFieldMetadataId: opportunityAmountField.id, + filter: isDefined(widget.configuration.filter) + ? { + ...widget.configuration.filter, + recordFilters: updatedRecordFilters, + } + : undefined, + }, + }; + + return replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedWidget, + flatEntityMaps: flatPageLayoutWidgetMaps, + }); +}; + +export const updateStandardPageLayoutWidgetsWithWorkspaceFieldIds = ({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps, +}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps => { + let updatedMaps = flatPageLayoutWidgetMaps; + + updatedMaps = updateDealsByCompanyWidget({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps: updatedMaps, + }); + + updatedMaps = updatePipelineValueByStageWidget({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps: updatedMaps, + }); + + updatedMaps = updateRevenueTimelineWidget({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps: updatedMaps, + }); + + updatedMaps = updateOpportunitiesByOwnerWidget({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps: updatedMaps, + }); + + updatedMaps = updateDealsCreatedThisMonthWidget({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps: updatedMaps, + }); + + updatedMaps = updateDealValueCreatedThisMonthWidget({ + flatFieldMetadataMaps, + flatObjectMetadataMaps, + flatPageLayoutWidgetMaps: updatedMaps, + }); + + return updatedMaps; +}; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index 8b8ec10121..6c42a5643f 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -22,6 +22,8 @@ import { AddWorkspaceForeignKeysMigrationCommand } from 'src/database/commands/u import { BackfillUpdatedByFieldCommand } from 'src/database/commands/upgrade-version-command/1-15/1-15-backfill-updated-by-field.command'; import { FixNanPositionValuesInNotesCommand } from 'src/database/commands/upgrade-version-command/1-15/1-15-fix-nan-position-values-in-notes.command'; import { MigratePageLayoutWidgetConfigurationCommand } from 'src/database/commands/upgrade-version-command/1-15/1-15-migrate-page-layout-widget-configuration.command'; +import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command'; +import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command'; import { UpdateTaskOnDeleteActionCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -63,6 +65,8 @@ export class UpgradeCommand extends UpgradeCommandRunner { // 1.16 Commands protected readonly updateTaskOnDeleteActionCommand: UpdateTaskOnDeleteActionCommand, + protected readonly backfillOpportunityOwnerFieldCommand: BackfillOpportunityOwnerFieldCommand, + protected readonly backfillStandardPageLayoutsCommand: BackfillStandardPageLayoutsCommand, ) { super( workspaceRepository, @@ -98,6 +102,8 @@ export class UpgradeCommand extends UpgradeCommandRunner { const commands_1160: VersionCommands = [ this.updateTaskOnDeleteActionCommand, + this.backfillOpportunityOwnerFieldCommand, + this.backfillStandardPageLayoutsCommand, ]; this.allCommands = { diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts index 1eb09c40ed..7c1c77f13e 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/__tests__/workspace.service.spec.ts @@ -1,5 +1,5 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; +import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm'; import { type Repository } from 'typeorm'; @@ -141,6 +141,19 @@ describe('WorkspaceService', () => { add: jest.fn(), }, }, + { + provide: getDataSourceToken(), + useValue: { + createQueryRunner: jest.fn().mockReturnValue({ + connect: jest.fn(), + startTransaction: jest.fn(), + commitTransaction: jest.fn(), + rollbackTransaction: jest.fn(), + release: jest.fn(), + manager: {}, + }), + }, + }, ], }).compile(); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index 33fc6b71b3..c0e74f782f 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import assert from 'assert'; @@ -8,7 +8,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { PermissionFlagType } from 'twenty-shared/constants'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; -import { Repository } from 'typeorm'; +import { DataSource, Repository } from 'typeorm'; import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; @@ -45,6 +45,12 @@ import { import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; +import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; +import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; +import { prefillDashboards } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards'; +import { prefillOpportunities } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities'; +import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people'; +import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-migration/constant/default-feature-flags'; import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version-major-minor-patch'; @@ -99,6 +105,8 @@ export class WorkspaceService extends TypeOrmQueryService { private readonly customDomainManagerService: CustomDomainManagerService, @InjectMessageQueue(MessageQueue.deleteCascadeQueue) private readonly messageQueueService: MessageQueueService, + @InjectDataSource() + private readonly coreDataSource: DataSource, ) { super(workspaceRepository); } @@ -253,8 +261,14 @@ export class WorkspaceService extends TypeOrmQueryService { workspace, userId: user.id, }); + await this.userWorkspaceService.createWorkspaceMember(workspace.id, user); + await this.prefillCreatedWorkspaceRecords({ + workspaceId: workspace.id, + schemaName: getWorkspaceSchemaName(workspace.id), + }); + const appVersion = this.twentyConfigService.get('APP_VERSION'); await this.workspaceRepository.update(workspace.id, { @@ -460,4 +474,70 @@ export class WorkspaceService extends TypeOrmQueryService { } } } + + private async prefillCreatedWorkspaceRecords({ + workspaceId, + schemaName, + }: { + workspaceId: string; + schemaName: string; + }): Promise { + const { + flatObjectMetadataMaps, + flatFieldMetadataMaps, + flatPageLayoutMaps, + } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + 'flatPageLayoutMaps', + ], + }, + ); + + const queryRunner = this.coreDataSource.createQueryRunner(); + + await queryRunner.connect(); + + try { + await queryRunner.startTransaction(); + + await prefillCompanies(queryRunner.manager, schemaName); + + await prefillPeople(queryRunner.manager, schemaName); + + await prefillWorkflows( + queryRunner.manager, + schemaName, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + ); + + await prefillOpportunities(queryRunner.manager, schemaName); + + await prefillDashboards( + queryRunner.manager, + schemaName, + flatPageLayoutMaps, + ); + + await queryRunner.commitTransaction(); + } catch (error) { + if (queryRunner.isTransactionActive) { + try { + await queryRunner.rollbackTransaction(); + } catch (rollbackError) { + this.logger.error( + `Failed to rollback prefill transaction: ${rollbackError.message}`, + ); + } + } + throw error; + } finally { + await queryRunner.release(); + } + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts index c43677bdc3..dcb3afa76f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts @@ -45,7 +45,7 @@ export const deleteFlatEntityFromFlatEntityMapsOrThrow = < ); if ( - isDefined(stillPresentUniversalIdentifiers) || + !isDefined(stillPresentUniversalIdentifiers) || isEmpty(stillPresentUniversalIdentifiers) ) { return undefined; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts new file mode 100644 index 0000000000..e9679ce6b9 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts @@ -0,0 +1,37 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; +import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; + +export type FindFlatEntityByUniversalIdentifierOrThrowArgs< + T extends SyncableFlatEntity, +> = { + flatEntityMaps: FlatEntityMaps; + universalIdentifier: string; +}; + +export const findFlatEntityByUniversalIdentifierOrThrow = < + T extends SyncableFlatEntity, +>({ + flatEntityMaps, + universalIdentifier, +}: FindFlatEntityByUniversalIdentifierOrThrowArgs): T => { + const flatEntity = findFlatEntityByUniversalIdentifier({ + flatEntityMaps, + universalIdentifier, + }); + + if (!isDefined(flatEntity)) { + throw new FlatEntityMapsException( + `Could not find flat entity with universal identifier ${universalIdentifier}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + + return flatEntity; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts index ba58b2cb57..b985bef7f8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts @@ -1,4 +1,7 @@ import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity'; +import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; -export type FlatPageLayoutWidget = FlatEntityFrom; +export type FlatPageLayoutWidget< + T extends WidgetConfigurationType = WidgetConfigurationType, +> = FlatEntityFrom>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/is-flat-page-layout-widget-configuration-of-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/is-flat-page-layout-widget-configuration-of-type.util.ts new file mode 100644 index 0000000000..240ea1c797 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/is-flat-page-layout-widget-configuration-of-type.util.ts @@ -0,0 +1,12 @@ +import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; +import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; + +export function isFlatPageLayoutWidgetConfigurationOfType< + Widget extends FlatPageLayoutWidget, + Type extends WidgetConfigurationType, +>( + widget: Pick, + type: Type, +): widget is Widget & FlatPageLayoutWidget { + return widget.configuration.configurationType === type; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/constants/all-widget-configuration-type-validator-by-widget-configuration-type.constant.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/constants/all-widget-configuration-type-validator-by-widget-configuration-type.constant.ts index 41d2f503d1..eab707adc1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/constants/all-widget-configuration-type-validator-by-widget-configuration-type.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/constants/all-widget-configuration-type-validator-by-widget-configuration-type.constant.ts @@ -1,24 +1,48 @@ import { AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/aggregate-chart-configuration.dto'; import { BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto'; +import { CalendarConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/calendar-configuration.dto'; +import { EmailsConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/emails-configuration.dto'; +import { FieldConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/field-configuration.dto'; +import { FieldRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/field-rich-text-configuration.dto'; +import { FieldsConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/fields-configuration.dto'; +import { FilesConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/files-configuration.dto'; import { GaugeChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/gauge-chart-configuration.dto'; import { IframeConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/iframe-configuration.dto'; import { LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto'; +import { NotesConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/notes-configuration.dto'; import { PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/pie-chart-configuration.dto'; import { StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto'; -import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; +import { TasksConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/tasks-configuration.dto'; +import { TimelineConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/timeline-configuration.dto'; +import { ViewConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/view-configuration.dto'; +import { WorkflowConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/workflow-configuration.dto'; +import { WorkflowRunConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/workflow-run-configuration.dto'; +import { WorkflowVersionConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/workflow-version-configuration.dto'; +import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; import { type PageLayoutWidgetConfigurationBase } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configurationt-base.type'; export const ALL_WIDGET_CONFIGURATION_TYPE_VALIDATOR_BY_WIDGET_CONFIGURATION_TYPE = { - [WidgetConfigurationType.AGGREGATE_CHART]: AggregateChartConfigurationDTO, - [WidgetConfigurationType.BAR_CHART]: BarChartConfigurationDTO, - [WidgetConfigurationType.GAUGE_CHART]: GaugeChartConfigurationDTO, - [WidgetConfigurationType.IFRAME]: IframeConfigurationDTO, - [WidgetConfigurationType.LINE_CHART]: LineChartConfigurationDTO, - [WidgetConfigurationType.PIE_CHART]: PieChartConfigurationDTO, - [WidgetConfigurationType.STANDALONE_RICH_TEXT]: - StandaloneRichTextConfigurationDTO, - // TODO: Remove the partial when we have all the widget configuration types in the backend - } as const satisfies Partial<{ + AGGREGATE_CHART: AggregateChartConfigurationDTO, + STANDALONE_RICH_TEXT: StandaloneRichTextConfigurationDTO, + PIE_CHART: PieChartConfigurationDTO, + LINE_CHART: LineChartConfigurationDTO, + IFRAME: IframeConfigurationDTO, + GAUGE_CHART: GaugeChartConfigurationDTO, + BAR_CHART: BarChartConfigurationDTO, + CALENDAR: CalendarConfigurationDTO, + EMAILS: EmailsConfigurationDTO, + FIELD: FieldConfigurationDTO, + FIELD_RICH_TEXT: FieldRichTextConfigurationDTO, + FIELDS: FieldsConfigurationDTO, + FILES: FilesConfigurationDTO, + NOTES: NotesConfigurationDTO, + TASKS: TasksConfigurationDTO, + TIMELINE: TimelineConfigurationDTO, + VIEW: ViewConfigurationDTO, + WORKFLOW: WorkflowConfigurationDTO, + WORKFLOW_RUN: WorkflowRunConfigurationDTO, + WORKFLOW_VERSION: WorkflowVersionConfigurationDTO, + } as const satisfies { [P in WidgetConfigurationType]: new () => PageLayoutWidgetConfigurationBase; - }>; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/calendar-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/calendar-configuration.dto.ts new file mode 100644 index 0000000000..1085ac0f2e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/calendar-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.CALENDAR]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.CALENDAR; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/emails-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/emails-configuration.dto.ts new file mode 100644 index 0000000000..a77adbde49 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/emails-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.EMAILS]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.EMAILS; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-configuration.dto.ts new file mode 100644 index 0000000000..9370a87ab8 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.FIELD]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.FIELD; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-rich-text-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-rich-text-configuration.dto.ts new file mode 100644 index 0000000000..62f1f9c984 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/field-rich-text-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.FIELD_RICH_TEXT]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.FIELD_RICH_TEXT; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/fields-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/fields-configuration.dto.ts new file mode 100644 index 0000000000..905d363ba3 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/fields-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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('FieldsConfiguration') +export class FieldsConfigurationDTO + implements PageLayoutWidgetConfigurationBase +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.FIELDS]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.FIELDS; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/files-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/files-configuration.dto.ts new file mode 100644 index 0000000000..507ecd1b9d --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/files-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.FILES]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.FILES; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/notes-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/notes-configuration.dto.ts new file mode 100644 index 0000000000..1dc159cd88 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/notes-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.NOTES]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.NOTES; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/tasks-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/tasks-configuration.dto.ts new file mode 100644 index 0000000000..86b84cb58b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/tasks-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.TASKS]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.TASKS; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/timeline-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/timeline-configuration.dto.ts new file mode 100644 index 0000000000..c1c16e6ac1 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/timeline-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.TIMELINE]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.TIMELINE; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/view-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/view-configuration.dto.ts new file mode 100644 index 0000000000..76ae15683d --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/view-configuration.dto.ts @@ -0,0 +1,14 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 { + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.VIEW]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.VIEW; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-configuration.dto.ts new file mode 100644 index 0000000000..30a25db23b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.WORKFLOW]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.WORKFLOW; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-run-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-run-configuration.dto.ts new file mode 100644 index 0000000000..ece2a5bd5c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-run-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.WORKFLOW_RUN]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.WORKFLOW_RUN; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-version-configuration.dto.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-version-configuration.dto.ts new file mode 100644 index 0000000000..4cb47c29e1 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/workflow-version-configuration.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsIn, IsNotEmpty } from 'class-validator'; + +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 +{ + @Field(() => WidgetConfigurationType) + @IsIn([WidgetConfigurationType.WORKFLOW_VERSION]) + @IsNotEmpty() + configurationType: WidgetConfigurationType.WORKFLOW_VERSION; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity.ts index acdc9c11b8..e5c79e12c5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity.ts @@ -9,15 +9,16 @@ import { JoinColumn, ManyToOne, PrimaryGeneratedColumn, - type Relation, UpdateDateColumn, + type Relation, } from 'typeorm'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; 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 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 { 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'; @Entity({ name: 'pageLayoutWidget', schema: 'core' }) @@ -27,7 +28,10 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti ['workspaceId', 'pageLayoutTabId'], { where: '"deletedAt" IS NULL' }, ) -export class PageLayoutWidgetEntity +export class PageLayoutWidgetEntity< + TWidgetConfigurationType extends + WidgetConfigurationType = WidgetConfigurationType, + > extends SyncableEntity implements Required { @@ -68,7 +72,7 @@ export class PageLayoutWidgetEntity gridPosition: GridPosition; @Column({ type: 'jsonb', nullable: false }) - configuration: AllPageLayoutWidgetConfiguration; + configuration: PageLayoutWidgetConfigurationTypeSettings; @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type.ts index 1df41a884f..c08638835e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type.ts @@ -1,5 +1,5 @@ -import { type ALL_WIDGET_CONFIGURATION_TYPE_VALIDATOR_BY_WIDGET_CONFIGURATION_TYPE } from 'src/engine/metadata-modules/page-layout-widget/constants/all-widget-configuration-type-validator-by-widget-configuration-type.constant'; +import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; +import { type PageLayoutWidgetConfigurationTypeSettings } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configuration.type'; -export type AllPageLayoutWidgetConfiguration = InstanceType< - (typeof ALL_WIDGET_CONFIGURATION_TYPE_VALIDATOR_BY_WIDGET_CONFIGURATION_TYPE)[keyof typeof ALL_WIDGET_CONFIGURATION_TYPE_VALIDATOR_BY_WIDGET_CONFIGURATION_TYPE] ->; +export type AllPageLayoutWidgetConfiguration = + PageLayoutWidgetConfigurationTypeSettings; diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configuration.type.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configuration.type.ts new file mode 100644 index 0000000000..2b86072ae1 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configuration.type.ts @@ -0,0 +1,8 @@ +import { type ALL_WIDGET_CONFIGURATION_TYPE_VALIDATOR_BY_WIDGET_CONFIGURATION_TYPE } from 'src/engine/metadata-modules/page-layout-widget/constants/all-widget-configuration-type-validator-by-widget-configuration-type.constant'; +import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; + +export type PageLayoutWidgetConfigurationTypeSettings< + T extends WidgetConfigurationType, +> = InstanceType< + (typeof ALL_WIDGET_CONFIGURATION_TYPE_VALIDATOR_BY_WIDGET_CONFIGURATION_TYPE)[T] +>; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts index 85aa8fe64d..59dc61e181 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts @@ -12,6 +12,7 @@ type OpportunityDataSeed = { position: number; pointOfContactId: string; companyId: string; + ownerId: string; createdBySource: string; createdByWorkspaceMemberId: string; createdByName: string; @@ -30,6 +31,7 @@ export const OPPORTUNITY_DATA_SEED_COLUMNS: (keyof OpportunityDataSeed)[] = [ 'position', 'pointOfContactId', 'companyId', + 'ownerId', 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', @@ -191,6 +193,7 @@ const GENERATE_OPPORTUNITY_SEEDS = (): OpportunityDataSeed[] => { COMPANY_DATA_SEED_IDS[ `ID_${Math.ceil(INDEX / 2)}` as keyof typeof COMPANY_DATA_SEED_IDS ] || COMPANY_DATA_SEED_IDS.ID_1, + ownerId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdBySource: 'MANUAL', createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim Cook', diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards.ts new file mode 100644 index 0000000000..2d554a4d70 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards.ts @@ -0,0 +1,65 @@ +import { FieldActorSource } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { type EntityManager } from 'typeorm'; + +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; +import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; +import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; + +export const MY_FIRST_DASHBOARD_ID = 'f31ecf3b-87d3-4e8a-a84b-b6f0f3f8c7e2'; + +export const prefillDashboards = async ( + entityManager: EntityManager, + schemaName: string, + flatPageLayoutMaps: FlatEntityMaps, +) => { + const myFirstDashboardPageLayout = findFlatEntityByUniversalIdentifier({ + flatEntityMaps: flatPageLayoutMaps, + universalIdentifier: + STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier, + }); + + if (!isDefined(myFirstDashboardPageLayout)) { + throw new Error( + `Page layout with universalIdentifier '${STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier}' not found`, + ); + } + + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.dashboard`, [ + 'id', + 'title', + 'pageLayoutId', + 'position', + 'createdBySource', + 'createdByWorkspaceMemberId', + 'createdByName', + 'createdByContext', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', + 'updatedByContext', + ]) + .orIgnore() + .values([ + { + id: MY_FIRST_DASHBOARD_ID, + title: 'My First Dashboard', + pageLayoutId: myFirstDashboardPageLayout.id, + position: 0, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + createdByContext: {}, + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + updatedByContext: {}, + }, + ]) + .returning('*') + .execute(); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities.ts new file mode 100644 index 0000000000..b496f10d6c --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-opportunities.ts @@ -0,0 +1,179 @@ +import { FieldActorSource } from 'twenty-shared/types'; +import { type EntityManager } from 'typeorm'; + +import { + AIRBNB_ID, + ANTHROPIC_ID, + FIGMA_ID, + NOTION_ID, + STRIPE_ID, +} from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; +import { + BRIAN_CHESKY_ID, + DARIO_AMODEI_ID, + DYLAN_FIELD_ID, + IVAN_ZHAO_ID, + PATRICK_COLLISON_ID, +} from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people'; + +export const OPPORTUNITY_STRIPE_PLATFORM_MIGRATION_ID = + '822639e5-9bf7-40f1-8882-a11140362339'; +export const OPPORTUNITY_ANTHROPIC_AI_MODEL_ID = + 'fc747edc-cb00-4078-8d6b-1fab2611dae4'; +export const OPPORTUNITY_NOTION_WORKSPACE_ID = + '75de302f-1044-4957-8da4-1f67ebefd52b'; +export const OPPORTUNITY_STRIPE_API_INTEGRATION_ID = + '2beb07b0-340c-41d7-be33-5aa91757f329'; +export const OPPORTUNITY_AIRBNB_ENTERPRISE_ID = + '9543adcf-ec03-44e2-9233-3c2d3ebae98a'; +export const OPPORTUNITY_FIGMA_DESIGN_ID = + '9457f8e9-16ae-43b9-92ee-cbd21f3dded5'; + +export const prefillOpportunities = async ( + entityManager: EntityManager, + schemaName: string, +) => { + const workspaceMember = await entityManager + .createQueryBuilder() + .select('id') + .from(`${schemaName}.workspaceMember`, 'workspaceMember') + .limit(1) + .getRawOne(); + + const ownerId = workspaceMember?.id ?? null; + + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.opportunity`, [ + 'id', + 'name', + 'amountAmountMicros', + 'amountCurrencyCode', + 'closeDate', + 'stage', + 'position', + 'companyId', + 'pointOfContactId', + 'ownerId', + 'createdBySource', + 'createdByWorkspaceMemberId', + 'createdByName', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', + ]) + .orIgnore() + .values([ + { + id: OPPORTUNITY_STRIPE_PLATFORM_MIGRATION_ID, + name: 'Platform Migration', + amountAmountMicros: 60000000000, + amountCurrencyCode: 'USD', + closeDate: new Date('2026-01-31T16:25:00.000Z'), + stage: 'PROPOSAL', + position: 1, + companyId: STRIPE_ID, + pointOfContactId: PATRICK_COLLISON_ID, + ownerId, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + { + id: OPPORTUNITY_ANTHROPIC_AI_MODEL_ID, + name: 'AI Model Training', + amountAmountMicros: 100000000000, + amountCurrencyCode: 'USD', + closeDate: new Date('2026-02-15T16:25:00.000Z'), + stage: 'CUSTOMER', + position: 2, + companyId: ANTHROPIC_ID, + pointOfContactId: DARIO_AMODEI_ID, + ownerId, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + { + id: OPPORTUNITY_NOTION_WORKSPACE_ID, + name: 'Workspace Expansion', + amountAmountMicros: 45000000000, + amountCurrencyCode: 'USD', + closeDate: new Date('2026-01-20T16:26:00.000Z'), + stage: 'MEETING', + position: 3, + companyId: NOTION_ID, + pointOfContactId: IVAN_ZHAO_ID, + ownerId, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + { + id: OPPORTUNITY_STRIPE_API_INTEGRATION_ID, + name: 'API Integration Deal', + amountAmountMicros: 75000000000, + amountCurrencyCode: 'USD', + closeDate: new Date('2026-01-25T16:26:00.000Z'), + stage: 'SCREENING', + position: 4, + companyId: STRIPE_ID, + pointOfContactId: PATRICK_COLLISON_ID, + ownerId, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + { + id: OPPORTUNITY_AIRBNB_ENTERPRISE_ID, + name: 'Enterprise Plan Upgrade', + amountAmountMicros: 50000000000, + amountCurrencyCode: 'USD', + closeDate: new Date('2026-03-10T16:26:00.000Z'), + stage: 'NEW', + position: 5, + companyId: AIRBNB_ID, + pointOfContactId: BRIAN_CHESKY_ID, + ownerId, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + { + id: OPPORTUNITY_FIGMA_DESIGN_ID, + name: 'Design Partnership', + amountAmountMicros: 30000000000, + amountCurrencyCode: 'USD', + closeDate: new Date('2026-01-15T16:27:00.000Z'), + stage: 'NEW', + position: 6, + companyId: FIGMA_ID, + pointOfContactId: DYLAN_FIELD_ID, + ownerId, + createdBySource: FieldActorSource.SYSTEM, + createdByWorkspaceMemberId: null, + createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', + }, + ]) + .returning('*') + .execute(); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts index 55ed1b52f1..69b6e2f6a7 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts @@ -1,5 +1,5 @@ -import { type EntityManager } from 'typeorm'; import { FieldActorSource } from 'twenty-shared/types'; +import { type EntityManager } from 'typeorm'; import { AIRBNB_ID, @@ -9,6 +9,12 @@ import { STRIPE_ID, } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; +export const BRIAN_CHESKY_ID = 'a2e78a5e-338b-46df-8811-fa08c7d19d35'; // Airbnb +export const DARIO_AMODEI_ID = '93c72d2e-e65c-44c4-99ad-f87f50349dcf'; // Anthropic +export const PATRICK_COLLISON_ID = 'edf6d445-13a7-4373-9a47-8f89e8c0a877'; // Stripe +export const DYLAN_FIELD_ID = 'b1e26fa6-c757-4c88-abfa-4b11f5cf3acf'; // Figma +export const IVAN_ZHAO_ID = '7a93d1e5-3f74-4945-8a65-d7f996083f72'; // Notion + export const prefillPeople = async ( entityManager: EntityManager, schemaName: string, @@ -17,6 +23,7 @@ export const prefillPeople = async ( .createQueryBuilder() .insert() .into(`${schemaName}.person`, [ + 'id', 'nameFirstName', 'nameLastName', 'city', @@ -36,6 +43,7 @@ export const prefillPeople = async ( .orIgnore() .values([ { + id: BRIAN_CHESKY_ID, nameFirstName: 'Brian', nameLastName: 'Chesky', city: 'San Francisco', @@ -54,6 +62,7 @@ export const prefillPeople = async ( companyId: AIRBNB_ID, }, { + id: DARIO_AMODEI_ID, nameFirstName: 'Dario', nameLastName: 'Amodei', city: 'San Francisco', @@ -72,6 +81,7 @@ export const prefillPeople = async ( companyId: ANTHROPIC_ID, }, { + id: PATRICK_COLLISON_ID, nameFirstName: 'Patrick', nameLastName: 'Collison', city: 'San Francisco', @@ -90,6 +100,7 @@ export const prefillPeople = async ( companyId: STRIPE_ID, }, { + id: DYLAN_FIELD_ID, nameFirstName: 'Dylan', nameLastName: 'Field', city: 'San Francisco', @@ -108,6 +119,7 @@ export const prefillPeople = async ( companyId: FIGMA_ID, }, { + id: IVAN_ZHAO_ID, nameFirstName: 'Ivan', nameLastName: 'Zhao', city: 'San Francisco', diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts index 11e0ec783e..30b15486eb 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts @@ -1142,6 +1142,7 @@ export const STANDARD_OBJECTS = { universalIdentifier: OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact, }, company: { universalIdentifier: OPPORTUNITY_STANDARD_FIELD_IDS.company }, + owner: { universalIdentifier: '20202020-be7e-4d1e-8e19-3d5c7c4b9f2a' }, favorites: { universalIdentifier: OPPORTUNITY_STANDARD_FIELD_IDS.favorites, }, @@ -1944,6 +1945,9 @@ export const STANDARD_OBJECTS = { assignedTasks: { universalIdentifier: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.assignedTasks, }, + ownedOpportunities: { + universalIdentifier: '20202020-9e4d-4b3a-8c1f-6d7e8f9a0b1c', + }, favorites: { universalIdentifier: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.favorites, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant.ts new file mode 100644 index 0000000000..2a5a315d04 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant.ts @@ -0,0 +1,36 @@ +export const STANDARD_PAGE_LAYOUTS = { + myFirstDashboard: { + universalIdentifier: '20202020-d001-4d01-8d01-da5ab0a00001', + tabs: { + tab1: { + universalIdentifier: '20202020-d011-4d11-8d11-da5ab0a01001', + widgets: { + welcomeRichText: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11001', + }, + dealsByCompany: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11002', + }, + pipelineValueByStage: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11003', + }, + revenueTimeline: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11004', + }, + opportunitiesByOwner: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11005', + }, + stockMarketIframe: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11006', + }, + dealsCreatedThisMonth: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11007', + }, + dealValueCreatedThisMonth: { + universalIdentifier: '20202020-d111-4d11-8d11-da5ab0a11008', + }, + }, + }, + }, + }, +} as const; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts index 8ea6d9bb30..a475f501ec 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-all-metadata-name.constant.ts @@ -11,4 +11,7 @@ export const TWENTY_STANDARD_ALL_METADATA_NAME = [ 'role', 'agent', 'skill', + 'pageLayout', + 'pageLayoutTab', + 'pageLayoutWidget', ] as const satisfies AllMetadataName[]; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type.ts new file mode 100644 index 0000000000..d065ea7d1c --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type.ts @@ -0,0 +1,3 @@ +import { type STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; + +export type AllStandardPageLayoutName = keyof typeof STANDARD_PAGE_LAYOUTS; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type.ts new file mode 100644 index 0000000000..e075474000 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type.ts @@ -0,0 +1,6 @@ +import { type STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; + +export type AllStandardPageLayoutTabName< + T extends AllStandardPageLayoutName = AllStandardPageLayoutName, +> = keyof (typeof STANDARD_PAGE_LAYOUTS)[T]['tabs']; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type.ts new file mode 100644 index 0000000000..825c29068d --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type.ts @@ -0,0 +1,12 @@ +import { type STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { type AllStandardPageLayoutTabName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type'; + +export type AllStandardPageLayoutWidgetName< + PageLayout extends AllStandardPageLayoutName, + PageLayouTab extends AllStandardPageLayoutTabName, +> = (typeof STANDARD_PAGE_LAYOUTS)[PageLayout]['tabs'][PageLayouTab] extends { + widgets: infer Widgets; +} + ? keyof Widgets + : never; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap new file mode 100644 index 0000000000..7f8f0a8a04 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap @@ -0,0 +1,1925 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getStandardObjectMetadataRelatedEntityIds should return standard object metadata related entity ids 1`] = ` +{ + "attachment": { + "fields": { + "company": { + "id": "00000000-0000-0000-0000-000000000013", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000002", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000008", + }, + "dashboard": { + "id": "00000000-0000-0000-0000-000000000015", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000004", + }, + "fileCategory": { + "id": "00000000-0000-0000-0000-000000000007", + }, + "fullPath": { + "id": "00000000-0000-0000-0000-000000000006", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000001", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000005", + }, + "note": { + "id": "00000000-0000-0000-0000-000000000011", + }, + "opportunity": { + "id": "00000000-0000-0000-0000-000000000014", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000012", + }, + "task": { + "id": "00000000-0000-0000-0000-000000000010", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000003", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000009", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000016", + }, + }, + "id": "00000000-0000-0000-0000-000000000017", + "views": undefined, + }, + "blocklist": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000019", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000021", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000022", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000018", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000020", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000023", + }, + }, + "id": "00000000-0000-0000-0000-000000000024", + "views": undefined, + }, + "calendarChannel": { + "fields": { + "calendarChannelEventAssociations": { + "id": "00000000-0000-0000-0000-000000000045", + }, + "connectedAccount": { + "id": "00000000-0000-0000-0000-000000000038", + }, + "contactAutoCreationPolicy": { + "id": "00000000-0000-0000-0000-000000000042", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000035", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000037", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000039", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000034", + }, + "isContactAutoCreationEnabled": { + "id": "00000000-0000-0000-0000-000000000041", + }, + "isSyncEnabled": { + "id": "00000000-0000-0000-0000-000000000043", + }, + "syncCursor": { + "id": "00000000-0000-0000-0000-000000000044", + }, + "syncStage": { + "id": "00000000-0000-0000-0000-000000000048", + }, + "syncStageStartedAt": { + "id": "00000000-0000-0000-0000-000000000049", + }, + "syncStatus": { + "id": "00000000-0000-0000-0000-000000000047", + }, + "syncedAt": { + "id": "00000000-0000-0000-0000-000000000050", + }, + "throttleFailureCount": { + "id": "00000000-0000-0000-0000-000000000046", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000036", + }, + "visibility": { + "id": "00000000-0000-0000-0000-000000000040", + }, + }, + "id": "00000000-0000-0000-0000-000000000051", + "views": undefined, + }, + "calendarChannelEventAssociation": { + "fields": { + "calendarChannel": { + "id": "00000000-0000-0000-0000-000000000029", + }, + "calendarEvent": { + "id": "00000000-0000-0000-0000-000000000030", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000026", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000028", + }, + "eventExternalId": { + "id": "00000000-0000-0000-0000-000000000031", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000025", + }, + "recurringEventExternalId": { + "id": "00000000-0000-0000-0000-000000000032", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000027", + }, + }, + "id": "00000000-0000-0000-0000-000000000033", + "views": undefined, + }, + "calendarEvent": { + "fields": { + "calendarChannelEventAssociations": { + "id": "00000000-0000-0000-0000-000000000080", + }, + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000000081", + }, + "conferenceLink": { + "id": "00000000-0000-0000-0000-000000000079", + }, + "conferenceSolution": { + "id": "00000000-0000-0000-0000-000000000078", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000065", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000067", + }, + "description": { + "id": "00000000-0000-0000-0000-000000000075", + }, + "endsAt": { + "id": "00000000-0000-0000-0000-000000000072", + }, + "externalCreatedAt": { + "id": "00000000-0000-0000-0000-000000000073", + }, + "externalUpdatedAt": { + "id": "00000000-0000-0000-0000-000000000074", + }, + "iCalUid": { + "id": "00000000-0000-0000-0000-000000000077", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000064", + }, + "isCanceled": { + "id": "00000000-0000-0000-0000-000000000069", + }, + "isFullDay": { + "id": "00000000-0000-0000-0000-000000000070", + }, + "location": { + "id": "00000000-0000-0000-0000-000000000076", + }, + "startsAt": { + "id": "00000000-0000-0000-0000-000000000071", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000068", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000066", + }, + }, + "id": "00000000-0000-0000-0000-000000000091", + "views": { + "allCalendarEvents": { + "id": "00000000-0000-0000-0000-000000000090", + "viewFields": { + "conferenceLink": { + "id": "00000000-0000-0000-0000-000000000087", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000089", + }, + "endsAt": { + "id": "00000000-0000-0000-0000-000000000084", + }, + "isCanceled": { + "id": "00000000-0000-0000-0000-000000000088", + }, + "isFullDay": { + "id": "00000000-0000-0000-0000-000000000085", + }, + "location": { + "id": "00000000-0000-0000-0000-000000000086", + }, + "startsAt": { + "id": "00000000-0000-0000-0000-000000000083", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000082", + }, + }, + "viewGroups": {}, + }, + }, + }, + "calendarEventParticipant": { + "fields": { + "calendarEvent": { + "id": "00000000-0000-0000-0000-000000000056", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000053", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000055", + }, + "displayName": { + "id": "00000000-0000-0000-0000-000000000058", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000057", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000052", + }, + "isOrganizer": { + "id": "00000000-0000-0000-0000-000000000059", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000061", + }, + "responseStatus": { + "id": "00000000-0000-0000-0000-000000000060", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000054", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000062", + }, + }, + "id": "00000000-0000-0000-0000-000000000063", + "views": undefined, + }, + "company": { + "fields": { + "accountOwner": { + "id": "00000000-0000-0000-0000-000000000108", + }, + "address": { + "id": "00000000-0000-0000-0000-000000000098", + }, + "annualRecurringRevenue": { + "id": "00000000-0000-0000-0000-000000000102", + }, + "attachments": { + "id": "00000000-0000-0000-0000-000000000113", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000093", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000105", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000095", + }, + "domainName": { + "id": "00000000-0000-0000-0000-000000000097", + }, + "employees": { + "id": "00000000-0000-0000-0000-000000000099", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000112", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000092", + }, + "idealCustomerProfile": { + "id": "00000000-0000-0000-0000-000000000103", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000100", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000096", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000110", + }, + "opportunities": { + "id": "00000000-0000-0000-0000-000000000111", + }, + "people": { + "id": "00000000-0000-0000-0000-000000000107", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000104", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000115", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000109", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000114", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000094", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000106", + }, + "xLink": { + "id": "00000000-0000-0000-0000-000000000101", + }, + }, + "id": "00000000-0000-0000-0000-000000000125", + "views": { + "allCompanies": { + "id": "00000000-0000-0000-0000-000000000124", + "viewFields": { + "accountOwner": { + "id": "00000000-0000-0000-0000-000000000119", + }, + "address": { + "id": "00000000-0000-0000-0000-000000000123", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000120", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000118", + }, + "domainName": { + "id": "00000000-0000-0000-0000-000000000117", + }, + "employees": { + "id": "00000000-0000-0000-0000-000000000121", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000122", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000116", + }, + }, + "viewGroups": {}, + }, + }, + }, + "connectedAccount": { + "fields": { + "accessToken": { + "id": "00000000-0000-0000-0000-000000000132", + }, + "accountOwner": { + "id": "00000000-0000-0000-0000-000000000134", + }, + "authFailedAt": { + "id": "00000000-0000-0000-0000-000000000136", + }, + "calendarChannels": { + "id": "00000000-0000-0000-0000-000000000139", + }, + "connectionParameters": { + "id": "00000000-0000-0000-0000-000000000142", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000127", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000129", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000130", + }, + "handleAliases": { + "id": "00000000-0000-0000-0000-000000000140", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000126", + }, + "lastCredentialsRefreshedAt": { + "id": "00000000-0000-0000-0000-000000000137", + }, + "lastSyncHistoryId": { + "id": "00000000-0000-0000-0000-000000000135", + }, + "messageChannels": { + "id": "00000000-0000-0000-0000-000000000138", + }, + "provider": { + "id": "00000000-0000-0000-0000-000000000131", + }, + "refreshToken": { + "id": "00000000-0000-0000-0000-000000000133", + }, + "scopes": { + "id": "00000000-0000-0000-0000-000000000141", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000128", + }, + }, + "id": "00000000-0000-0000-0000-000000000143", + "views": undefined, + }, + "dashboard": { + "fields": { + "attachments": { + "id": "00000000-0000-0000-0000-000000000155", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000145", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000151", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000147", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000154", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000144", + }, + "pageLayoutId": { + "id": "00000000-0000-0000-0000-000000000150", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000149", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000156", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000153", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000148", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000146", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000152", + }, + }, + "id": "00000000-0000-0000-0000-000000000162", + "views": { + "allDashboards": { + "id": "00000000-0000-0000-0000-000000000161", + "viewFields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000159", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000158", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000157", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000160", + }, + }, + "viewGroups": {}, + }, + }, + }, + "favorite": { + "fields": { + "company": { + "id": "00000000-0000-0000-0000-000000000170", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000164", + }, + "dashboard": { + "id": "00000000-0000-0000-0000-000000000179", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000166", + }, + "favoriteFolder": { + "id": "00000000-0000-0000-0000-000000000178", + }, + "forWorkspaceMember": { + "id": "00000000-0000-0000-0000-000000000168", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000163", + }, + "note": { + "id": "00000000-0000-0000-0000-000000000176", + }, + "opportunity": { + "id": "00000000-0000-0000-0000-000000000171", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000169", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000167", + }, + "task": { + "id": "00000000-0000-0000-0000-000000000175", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000165", + }, + "viewId": { + "id": "00000000-0000-0000-0000-000000000177", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000172", + }, + "workflowRun": { + "id": "00000000-0000-0000-0000-000000000174", + }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000173", + }, + }, + "id": "00000000-0000-0000-0000-000000000180", + "views": undefined, + }, + "favoriteFolder": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000182", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000184", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000187", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000181", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000186", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000185", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000183", + }, + }, + "id": "00000000-0000-0000-0000-000000000188", + "views": undefined, + }, + "message": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000259", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000261", + }, + "direction": { + "id": "00000000-0000-0000-0000-000000000264", + }, + "headerMessageId": { + "id": "00000000-0000-0000-0000-000000000262", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000258", + }, + "messageChannelMessageAssociations": { + "id": "00000000-0000-0000-0000-000000000269", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000268", + }, + "messageThread": { + "id": "00000000-0000-0000-0000-000000000263", + }, + "receivedAt": { + "id": "00000000-0000-0000-0000-000000000267", + }, + "subject": { + "id": "00000000-0000-0000-0000-000000000265", + }, + "text": { + "id": "00000000-0000-0000-0000-000000000266", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000260", + }, + }, + "id": "00000000-0000-0000-0000-000000000278", + "views": { + "allMessages": { + "id": "00000000-0000-0000-0000-000000000277", + "viewFields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000276", + }, + "headerMessageId": { + "id": "00000000-0000-0000-0000-000000000274", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000272", + }, + "messageThread": { + "id": "00000000-0000-0000-0000-000000000271", + }, + "receivedAt": { + "id": "00000000-0000-0000-0000-000000000273", + }, + "subject": { + "id": "00000000-0000-0000-0000-000000000270", + }, + "text": { + "id": "00000000-0000-0000-0000-000000000275", + }, + }, + "viewGroups": {}, + }, + }, + }, + "messageChannel": { + "fields": { + "connectedAccount": { + "id": "00000000-0000-0000-0000-000000000206", + }, + "contactAutoCreationPolicy": { + "id": "00000000-0000-0000-0000-000000000209", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000201", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000203", + }, + "excludeGroupEmails": { + "id": "00000000-0000-0000-0000-000000000211", + }, + "excludeNonProfessionalEmails": { + "id": "00000000-0000-0000-0000-000000000210", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000205", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000200", + }, + "isContactAutoCreationEnabled": { + "id": "00000000-0000-0000-0000-000000000208", + }, + "isSyncEnabled": { + "id": "00000000-0000-0000-0000-000000000216", + }, + "messageChannelMessageAssociations": { + "id": "00000000-0000-0000-0000-000000000212", + }, + "messageFolderImportPolicy": { + "id": "00000000-0000-0000-0000-000000000214", + }, + "messageFolders": { + "id": "00000000-0000-0000-0000-000000000213", + }, + "pendingGroupEmailsAction": { + "id": "00000000-0000-0000-0000-000000000215", + }, + "syncCursor": { + "id": "00000000-0000-0000-0000-000000000217", + }, + "syncStage": { + "id": "00000000-0000-0000-0000-000000000220", + }, + "syncStageStartedAt": { + "id": "00000000-0000-0000-0000-000000000221", + }, + "syncStatus": { + "id": "00000000-0000-0000-0000-000000000219", + }, + "syncedAt": { + "id": "00000000-0000-0000-0000-000000000218", + }, + "throttleFailureCount": { + "id": "00000000-0000-0000-0000-000000000222", + }, + "type": { + "id": "00000000-0000-0000-0000-000000000207", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000202", + }, + "visibility": { + "id": "00000000-0000-0000-0000-000000000204", + }, + }, + "id": "00000000-0000-0000-0000-000000000223", + "views": undefined, + }, + "messageChannelMessageAssociation": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000190", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000192", + }, + "direction": { + "id": "00000000-0000-0000-0000-000000000198", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000189", + }, + "message": { + "id": "00000000-0000-0000-0000-000000000194", + }, + "messageChannel": { + "id": "00000000-0000-0000-0000-000000000193", + }, + "messageExternalId": { + "id": "00000000-0000-0000-0000-000000000195", + }, + "messageThread": { + "id": "00000000-0000-0000-0000-000000000196", + }, + "messageThreadExternalId": { + "id": "00000000-0000-0000-0000-000000000197", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000191", + }, + }, + "id": "00000000-0000-0000-0000-000000000199", + "views": undefined, + }, + "messageFolder": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000225", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000227", + }, + "externalId": { + "id": "00000000-0000-0000-0000-000000000234", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000224", + }, + "isSentFolder": { + "id": "00000000-0000-0000-0000-000000000232", + }, + "isSynced": { + "id": "00000000-0000-0000-0000-000000000233", + }, + "messageChannel": { + "id": "00000000-0000-0000-0000-000000000230", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000228", + }, + "parentFolderId": { + "id": "00000000-0000-0000-0000-000000000229", + }, + "pendingSyncAction": { + "id": "00000000-0000-0000-0000-000000000235", + }, + "syncCursor": { + "id": "00000000-0000-0000-0000-000000000231", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000226", + }, + }, + "id": "00000000-0000-0000-0000-000000000236", + "views": undefined, + }, + "messageParticipant": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000238", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000240", + }, + "displayName": { + "id": "00000000-0000-0000-0000-000000000244", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000243", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000237", + }, + "message": { + "id": "00000000-0000-0000-0000-000000000241", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000245", + }, + "role": { + "id": "00000000-0000-0000-0000-000000000242", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000239", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000246", + }, + }, + "id": "00000000-0000-0000-0000-000000000247", + "views": undefined, + }, + "messageThread": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000249", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000251", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000248", + }, + "messageChannelMessageAssociations": { + "id": "00000000-0000-0000-0000-000000000253", + }, + "messages": { + "id": "00000000-0000-0000-0000-000000000252", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000250", + }, + }, + "id": "00000000-0000-0000-0000-000000000257", + "views": { + "allMessageThreads": { + "id": "00000000-0000-0000-0000-000000000256", + "viewFields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000255", + }, + "messages": { + "id": "00000000-0000-0000-0000-000000000254", + }, + }, + "viewGroups": {}, + }, + }, + }, + "note": { + "fields": { + "attachments": { + "id": "00000000-0000-0000-0000-000000000289", + }, + "bodyV2": { + "id": "00000000-0000-0000-0000-000000000285", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000280", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000286", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000282", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000291", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000279", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000288", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000283", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000292", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000290", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000284", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000281", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000287", + }, + }, + "id": "00000000-0000-0000-0000-000000000299", + "views": { + "allNotes": { + "id": "00000000-0000-0000-0000-000000000298", + "viewFields": { + "bodyV2": { + "id": "00000000-0000-0000-0000-000000000295", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000297", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000296", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000294", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000293", + }, + }, + "viewGroups": {}, + }, + }, + }, + "noteTarget": { + "fields": { + "company": { + "id": "00000000-0000-0000-0000-000000000306", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000301", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000303", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000300", + }, + "note": { + "id": "00000000-0000-0000-0000-000000000304", + }, + "opportunity": { + "id": "00000000-0000-0000-0000-000000000307", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000305", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000302", + }, + }, + "id": "00000000-0000-0000-0000-000000000308", + "views": undefined, + }, + "opportunity": { + "fields": { + "amount": { + "id": "00000000-0000-0000-0000-000000000314", + }, + "attachments": { + "id": "00000000-0000-0000-0000-000000000326", + }, + "closeDate": { + "id": "00000000-0000-0000-0000-000000000315", + }, + "company": { + "id": "00000000-0000-0000-0000-000000000321", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000310", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000318", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000312", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000323", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000309", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000313", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000325", + }, + "owner": { + "id": "00000000-0000-0000-0000-000000000322", + }, + "pointOfContact": { + "id": "00000000-0000-0000-0000-000000000320", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000317", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000328", + }, + "stage": { + "id": "00000000-0000-0000-0000-000000000316", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000324", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000327", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000311", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000319", + }, + }, + "id": "00000000-0000-0000-0000-000000000348", + "views": { + "allOpportunities": { + "id": "00000000-0000-0000-0000-000000000335", + "viewFields": { + "amount": { + "id": "00000000-0000-0000-0000-000000000330", + }, + "closeDate": { + "id": "00000000-0000-0000-0000-000000000332", + }, + "company": { + "id": "00000000-0000-0000-0000-000000000333", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000331", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000329", + }, + "pointOfContact": { + "id": "00000000-0000-0000-0000-000000000334", + }, + }, + "viewGroups": {}, + }, + "byStage": { + "id": "00000000-0000-0000-0000-000000000347", + "viewFields": { + "amount": { + "id": "00000000-0000-0000-0000-000000000337", + }, + "closeDate": { + "id": "00000000-0000-0000-0000-000000000339", + }, + "company": { + "id": "00000000-0000-0000-0000-000000000340", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000338", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000336", + }, + "pointOfContact": { + "id": "00000000-0000-0000-0000-000000000341", + }, + }, + "viewGroups": { + "customer": { + "id": "00000000-0000-0000-0000-000000000346", + }, + "meeting": { + "id": "00000000-0000-0000-0000-000000000344", + }, + "new": { + "id": "00000000-0000-0000-0000-000000000342", + }, + "proposal": { + "id": "00000000-0000-0000-0000-000000000345", + }, + "screening": { + "id": "00000000-0000-0000-0000-000000000343", + }, + }, + }, + }, + }, + "person": { + "fields": { + "attachments": { + "id": "00000000-0000-0000-0000-000000000369", + }, + "avatarUrl": { + "id": "00000000-0000-0000-0000-000000000360", + }, + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000000371", + }, + "city": { + "id": "00000000-0000-0000-0000-000000000359", + }, + "company": { + "id": "00000000-0000-0000-0000-000000000364", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000350", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000362", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000352", + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000354", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000368", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000349", + }, + "jobTitle": { + "id": "00000000-0000-0000-0000-000000000357", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000355", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000370", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000353", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000367", + }, + "phones": { + "id": "00000000-0000-0000-0000-000000000358", + }, + "pointOfContactForOpportunities": { + "id": "00000000-0000-0000-0000-000000000365", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000361", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000373", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000366", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000372", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000351", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000363", + }, + "xLink": { + "id": "00000000-0000-0000-0000-000000000356", + }, + }, + "id": "00000000-0000-0000-0000-000000000385", + "views": { + "allPeople": { + "id": "00000000-0000-0000-0000-000000000384", + "viewFields": { + "city": { + "id": "00000000-0000-0000-0000-000000000380", + }, + "company": { + "id": "00000000-0000-0000-0000-000000000377", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000379", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000376", + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000375", + }, + "jobTitle": { + "id": "00000000-0000-0000-0000-000000000381", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000382", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000374", + }, + "phones": { + "id": "00000000-0000-0000-0000-000000000378", + }, + "xLink": { + "id": "00000000-0000-0000-0000-000000000383", + }, + }, + "viewGroups": {}, + }, + }, + }, + "task": { + "fields": { + "assignee": { + "id": "00000000-0000-0000-0000-000000000399", + }, + "attachments": { + "id": "00000000-0000-0000-0000-000000000398", + }, + "bodyV2": { + "id": "00000000-0000-0000-0000-000000000392", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000387", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000395", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000389", + }, + "dueAt": { + "id": "00000000-0000-0000-0000-000000000393", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000401", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000386", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000390", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000402", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000394", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000397", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000400", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000391", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000388", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000396", + }, + }, + "id": "00000000-0000-0000-0000-000000000433", + "views": { + "allTasks": { + "id": "00000000-0000-0000-0000-000000000411", + "viewFields": { + "assignee": { + "id": "00000000-0000-0000-0000-000000000408", + }, + "bodyV2": { + "id": "00000000-0000-0000-0000-000000000409", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000410", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000406", + }, + "dueAt": { + "id": "00000000-0000-0000-0000-000000000407", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000404", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000405", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000403", + }, + }, + "viewGroups": {}, + }, + "assignedToMe": { + "id": "00000000-0000-0000-0000-000000000423", + "viewFields": { + "assignee": { + "id": "00000000-0000-0000-0000-000000000416", + }, + "bodyV2": { + "id": "00000000-0000-0000-0000-000000000417", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000418", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000414", + }, + "dueAt": { + "id": "00000000-0000-0000-0000-000000000415", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000413", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000412", + }, + }, + "viewGroups": { + "done": { + "id": "00000000-0000-0000-0000-000000000421", + }, + "empty": { + "id": "00000000-0000-0000-0000-000000000422", + }, + "inProgress": { + "id": "00000000-0000-0000-0000-000000000420", + }, + "todo": { + "id": "00000000-0000-0000-0000-000000000419", + }, + }, + }, + "byStatus": { + "id": "00000000-0000-0000-0000-000000000432", + "viewFields": { + "assignee": { + "id": "00000000-0000-0000-0000-000000000427", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000428", + }, + "dueAt": { + "id": "00000000-0000-0000-0000-000000000426", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000425", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000424", + }, + }, + "viewGroups": { + "done": { + "id": "00000000-0000-0000-0000-000000000431", + }, + "inProgress": { + "id": "00000000-0000-0000-0000-000000000430", + }, + "todo": { + "id": "00000000-0000-0000-0000-000000000429", + }, + }, + }, + }, + }, + "taskTarget": { + "fields": { + "company": { + "id": "00000000-0000-0000-0000-000000000440", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000435", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000437", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000434", + }, + "opportunity": { + "id": "00000000-0000-0000-0000-000000000441", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000439", + }, + "task": { + "id": "00000000-0000-0000-0000-000000000438", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000436", + }, + }, + "id": "00000000-0000-0000-0000-000000000442", + "views": undefined, + }, + "timelineActivity": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000444", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000446", + }, + "happensAt": { + "id": "00000000-0000-0000-0000-000000000447", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000443", + }, + "linkedObjectMetadataId": { + "id": "00000000-0000-0000-0000-000000000462", + }, + "linkedRecordCachedName": { + "id": "00000000-0000-0000-0000-000000000460", + }, + "linkedRecordId": { + "id": "00000000-0000-0000-0000-000000000461", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000448", + }, + "properties": { + "id": "00000000-0000-0000-0000-000000000449", + }, + "targetCompany": { + "id": "00000000-0000-0000-0000-000000000452", + }, + "targetDashboard": { + "id": "00000000-0000-0000-0000-000000000459", + }, + "targetNote": { + "id": "00000000-0000-0000-0000-000000000455", + }, + "targetOpportunity": { + "id": "00000000-0000-0000-0000-000000000453", + }, + "targetPerson": { + "id": "00000000-0000-0000-0000-000000000451", + }, + "targetTask": { + "id": "00000000-0000-0000-0000-000000000454", + }, + "targetWorkflow": { + "id": "00000000-0000-0000-0000-000000000456", + }, + "targetWorkflowRun": { + "id": "00000000-0000-0000-0000-000000000458", + }, + "targetWorkflowVersion": { + "id": "00000000-0000-0000-0000-000000000457", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000445", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000450", + }, + }, + "id": "00000000-0000-0000-0000-000000000469", + "views": { + "allTimelineActivities": { + "id": "00000000-0000-0000-0000-000000000468", + "viewFields": { + "happensAt": { + "id": "00000000-0000-0000-0000-000000000464", + }, + "linkedRecordCachedName": { + "id": "00000000-0000-0000-0000-000000000467", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000463", + }, + "properties": { + "id": "00000000-0000-0000-0000-000000000465", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000466", + }, + }, + "viewGroups": {}, + }, + }, + }, + "workflow": { + "fields": { + "attachments": { + "id": "00000000-0000-0000-0000-000000000483", + }, + "automatedTriggers": { + "id": "00000000-0000-0000-0000-000000000480", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000471", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000484", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000473", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000481", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000470", + }, + "lastPublishedVersionId": { + "id": "00000000-0000-0000-0000-000000000475", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000474", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000477", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000479", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000486", + }, + "statuses": { + "id": "00000000-0000-0000-0000-000000000476", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000482", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000472", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000485", + }, + "versions": { + "id": "00000000-0000-0000-0000-000000000478", + }, + }, + "id": "00000000-0000-0000-0000-000000000494", + "views": { + "allWorkflows": { + "id": "00000000-0000-0000-0000-000000000493", + "viewFields": { + "createdBy": { + "id": "00000000-0000-0000-0000-000000000490", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000487", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000492", + }, + "statuses": { + "id": "00000000-0000-0000-0000-000000000488", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000489", + }, + "versions": { + "id": "00000000-0000-0000-0000-000000000491", + }, + }, + "viewGroups": {}, + }, + }, + }, + "workflowAutomatedTrigger": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000496", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000498", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000495", + }, + "settings": { + "id": "00000000-0000-0000-0000-000000000500", + }, + "type": { + "id": "00000000-0000-0000-0000-000000000499", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000497", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000501", + }, + }, + "id": "00000000-0000-0000-0000-000000000502", + "views": undefined, + }, + "workflowRun": { + "fields": { + "context": { + "id": "00000000-0000-0000-0000-000000000518", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000504", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000515", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000506", + }, + "endedAt": { + "id": "00000000-0000-0000-0000-000000000512", + }, + "enqueuedAt": { + "id": "00000000-0000-0000-0000-000000000510", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000520", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000503", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000507", + }, + "output": { + "id": "00000000-0000-0000-0000-000000000517", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000514", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000522", + }, + "startedAt": { + "id": "00000000-0000-0000-0000-000000000511", + }, + "state": { + "id": "00000000-0000-0000-0000-000000000519", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000513", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000521", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000505", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000516", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000509", + }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000508", + }, + }, + "id": "00000000-0000-0000-0000-000000000530", + "views": { + "allWorkflowRuns": { + "id": "00000000-0000-0000-0000-000000000529", + "viewFields": { + "createdBy": { + "id": "00000000-0000-0000-0000-000000000527", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000523", + }, + "startedAt": { + "id": "00000000-0000-0000-0000-000000000526", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000525", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000524", + }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000528", + }, + }, + "viewGroups": {}, + }, + }, + }, + "workflowVersion": { + "fields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000532", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000534", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000542", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000531", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000535", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000539", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000540", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000544", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000538", + }, + "steps": { + "id": "00000000-0000-0000-0000-000000000541", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000543", + }, + "trigger": { + "id": "00000000-0000-0000-0000-000000000537", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000533", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000536", + }, + }, + "id": "00000000-0000-0000-0000-000000000551", + "views": { + "allWorkflowVersions": { + "id": "00000000-0000-0000-0000-000000000550", + "viewFields": { + "name": { + "id": "00000000-0000-0000-0000-000000000545", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000549", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000547", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000548", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000546", + }, + }, + "viewGroups": {}, + }, + }, + }, + "workspaceMember": { + "fields": { + "accountOwnerForCompanies": { + "id": "00000000-0000-0000-0000-000000000566", + }, + "assignedTasks": { + "id": "00000000-0000-0000-0000-000000000563", + }, + "avatarUrl": { + "id": "00000000-0000-0000-0000-000000000560", + }, + "blocklist": { + "id": "00000000-0000-0000-0000-000000000569", + }, + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000000570", + }, + "calendarStartDay": { + "id": "00000000-0000-0000-0000-000000000576", + }, + "colorScheme": { + "id": "00000000-0000-0000-0000-000000000558", + }, + "connectedAccounts": { + "id": "00000000-0000-0000-0000-000000000567", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000553", + }, + "dateFormat": { + "id": "00000000-0000-0000-0000-000000000573", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000555", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000565", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000552", + }, + "locale": { + "id": "00000000-0000-0000-0000-000000000559", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000568", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000557", + }, + "numberFormat": { + "id": "00000000-0000-0000-0000-000000000577", + }, + "ownedOpportunities": { + "id": "00000000-0000-0000-0000-000000000564", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000556", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000575", + }, + "timeFormat": { + "id": "00000000-0000-0000-0000-000000000574", + }, + "timeZone": { + "id": "00000000-0000-0000-0000-000000000572", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000571", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000554", + }, + "userEmail": { + "id": "00000000-0000-0000-0000-000000000561", + }, + "userId": { + "id": "00000000-0000-0000-0000-000000000562", + }, + }, + "id": "00000000-0000-0000-0000-000000000588", + "views": { + "allWorkspaceMembers": { + "id": "00000000-0000-0000-0000-000000000587", + "viewFields": { + "avatarUrl": { + "id": "00000000-0000-0000-0000-000000000580", + }, + "colorScheme": { + "id": "00000000-0000-0000-0000-000000000581", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000586", + }, + "dateFormat": { + "id": "00000000-0000-0000-0000-000000000584", + }, + "locale": { + "id": "00000000-0000-0000-0000-000000000582", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000578", + }, + "timeFormat": { + "id": "00000000-0000-0000-0000-000000000585", + }, + "timeZone": { + "id": "00000000-0000-0000-0000-000000000583", + }, + "userEmail": { + "id": "00000000-0000-0000-0000-000000000579", + }, + }, + "viewGroups": {}, + }, + }, + }, +} +`; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts.snap new file mode 100644 index 0000000000..bf0abe08a5 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard page layout metadata related entity ids 1`] = ` +{ + "myFirstDashboard": { + "id": "00000000-0000-0000-0000-000000000010", + "tabs": { + "tab1": { + "id": "00000000-0000-0000-0000-000000000009", + "widgets": { + "dealValueCreatedThisMonth": { + "id": "00000000-0000-0000-0000-000000000008", + }, + "dealsByCompany": { + "id": "00000000-0000-0000-0000-000000000002", + }, + "dealsCreatedThisMonth": { + "id": "00000000-0000-0000-0000-000000000007", + }, + "opportunitiesByOwner": { + "id": "00000000-0000-0000-0000-000000000005", + }, + "pipelineValueByStage": { + "id": "00000000-0000-0000-0000-000000000003", + }, + "revenueTimeline": { + "id": "00000000-0000-0000-0000-000000000004", + }, + "stockMarketIframe": { + "id": "00000000-0000-0000-0000-000000000006", + }, + "welcomeRichText": { + "id": "00000000-0000-0000-0000-000000000001", + }, + }, + }, + }, + }, +} +`; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts new file mode 100644 index 0000000000..103e3773f4 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts @@ -0,0 +1,25 @@ +let uuidCounter = 0; + +jest.mock('uuid', () => ({ + v4: jest.fn( + () => `00000000-0000-0000-0000-${String(++uuidCounter).padStart(12, '0')}`, + ), +})); + +import { getStandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util'; + +describe('getStandardObjectMetadataRelatedEntityIds', () => { + beforeEach(() => { + uuidCounter = 0; + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it('should return standard object metadata related entity ids', () => { + const result = getStandardObjectMetadataRelatedEntityIds(); + + expect(result).toMatchSnapshot(); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts new file mode 100644 index 0000000000..ac40a48e85 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts @@ -0,0 +1,25 @@ +let uuidCounter = 0; + +jest.mock('uuid', () => ({ + v4: jest.fn( + () => `00000000-0000-0000-0000-${String(++uuidCounter).padStart(12, '0')}`, + ), +})); + +import { getStandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util'; + +describe('getStandardPageLayoutMetadataRelatedEntityIds', () => { + beforeEach(() => { + uuidCounter = 0; + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it('should return standard page layout metadata related entity ids', () => { + const result = getStandardPageLayoutMetadataRelatedEntityIds(); + + expect(result).toMatchSnapshot(); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts index 4b52fc1af9..43bcf6be8a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts @@ -442,4 +442,28 @@ export const buildOpportunityStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + owner: createStandardRelationFieldFlatMetadata({ + objectName, + workspaceId, + context: { + type: FieldMetadataType.RELATION, + morphId: null, + fieldName: 'owner', + label: 'Owner', + description: 'Opportunity owner', + icon: 'IconUserCircle', + isNullable: true, + targetObjectName: 'workspaceMember', + targetFieldName: 'ownedOpportunities', + settings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: RelationOnDeleteAction.SET_NULL, + joinColumnName: 'ownerId', + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), }); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts index fca26d306f..bf937b189a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts @@ -596,4 +596,27 @@ export const buildWorkspaceMemberStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + ownedOpportunities: createStandardRelationFieldFlatMetadata({ + objectName, + workspaceId, + context: { + type: FieldMetadataType.RELATION, + morphId: null, + fieldName: 'ownedOpportunities', + label: 'Owned opportunities', + description: 'Opportunities owned by the workspace member', + icon: 'IconTargetArrow', + isNullable: false, + isUIReadOnly: true, + targetObjectName: 'opportunity', + targetFieldName: 'owner', + settings: { + relationType: RelationType.ONE_TO_MANY, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), }); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util.ts new file mode 100644 index 0000000000..0e41b0bd67 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util.ts @@ -0,0 +1,101 @@ +import { v4 } from 'uuid'; + +import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { type AllStandardPageLayoutTabName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type'; +import { type AllStandardPageLayoutWidgetName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type'; + +type StandardPageLayoutWidgetIds< + L extends AllStandardPageLayoutName, + T extends AllStandardPageLayoutTabName, +> = Record, { id: string }>; + +type StandardPageLayoutTabIds = { + [T in AllStandardPageLayoutTabName]: { + id: string; + widgets: StandardPageLayoutWidgetIds; + }; +}; + +export type StandardPageLayoutMetadataRelatedEntityIds = { + [L in AllStandardPageLayoutName]: { + id: string; + tabs: StandardPageLayoutTabIds; + }; +}; + +const computeStandardPageLayoutWidgetIds = < + L extends AllStandardPageLayoutName, + T extends AllStandardPageLayoutTabName, +>({ + layoutName, + tabName, +}: { + layoutName: L; + tabName: T; +}): StandardPageLayoutWidgetIds => { + // @ts-expect-error legacy generic pattern + const tabDefinition = STANDARD_PAGE_LAYOUTS[layoutName].tabs[tabName] as { + widgets: Record; + }; + + const widgetNames = Object.keys( + tabDefinition.widgets, + ) as AllStandardPageLayoutWidgetName[]; + + const widgetIds = {} as StandardPageLayoutWidgetIds; + + for (const widgetName of widgetNames) { + widgetIds[widgetName] = { id: v4() }; + } + + return widgetIds; +}; + +const computeStandardPageLayoutTabIds = ({ + layoutName, +}: { + layoutName: L; +}): StandardPageLayoutTabIds => { + const layoutDefinition = STANDARD_PAGE_LAYOUTS[layoutName]; + + const tabNames = Object.keys( + layoutDefinition.tabs, + ) as AllStandardPageLayoutTabName[]; + + const tabIds = {} as StandardPageLayoutTabIds; + + for (const tabName of tabNames) { + const widgetIds = computeStandardPageLayoutWidgetIds({ + layoutName, + tabName, + }); + + tabIds[tabName] = { + id: v4(), + widgets: widgetIds, + }; + } + + return tabIds; +}; + +export const getStandardPageLayoutMetadataRelatedEntityIds = + (): StandardPageLayoutMetadataRelatedEntityIds => { + const result = {} as StandardPageLayoutMetadataRelatedEntityIds; + + for (const layoutName of Object.keys( + STANDARD_PAGE_LAYOUTS, + ) as AllStandardPageLayoutName[]) { + const tabIds = computeStandardPageLayoutTabIds({ + layoutName, + }); + + result[layoutName] = { + id: v4(), + tabs: tabIds, + }; + } + + return result; + }; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/build-standard-flat-page-layout-tab-metadata-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/build-standard-flat-page-layout-tab-metadata-maps.util.ts new file mode 100644 index 0000000000..bbe9f0325f --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/build-standard-flat-page-layout-tab-metadata-maps.util.ts @@ -0,0 +1,30 @@ +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; +import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; +import { type CreateStandardPageLayoutTabArgs } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/create-standard-page-layout-tab-flat-metadata.util'; +import { STANDARD_FLAT_PAGE_LAYOUT_TAB_BUILDERS_BY_LAYOUT_NAME } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/standard-flat-page-layout-tab-builders.constant'; + +export type BuildStandardFlatPageLayoutTabMetadataMapsArgs = Omit< + CreateStandardPageLayoutTabArgs, + 'context' +>; + +export const buildStandardFlatPageLayoutTabMetadataMaps = ( + args: BuildStandardFlatPageLayoutTabMetadataMapsArgs, +): FlatEntityMaps => { + const allPageLayoutTabMetadatas: FlatPageLayoutTab[] = Object.values( + STANDARD_FLAT_PAGE_LAYOUT_TAB_BUILDERS_BY_LAYOUT_NAME, + ).flatMap((builder) => builder(args)); + + let flatPageLayoutTabMaps = createEmptyFlatEntityMaps(); + + for (const pageLayoutTabMetadata of allPageLayoutTabMetadatas) { + flatPageLayoutTabMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: pageLayoutTabMetadata, + flatEntityMaps: flatPageLayoutTabMaps, + }); + } + + return flatPageLayoutTabMaps; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/create-standard-page-layout-tab-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/create-standard-page-layout-tab-flat-metadata.util.ts new file mode 100644 index 0000000000..6ae4ac3aa6 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/create-standard-page-layout-tab-flat-metadata.util.ts @@ -0,0 +1,68 @@ +import { isDefined } from 'class-validator'; + +import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; +import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { type AllStandardPageLayoutTabName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type'; +import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util'; + +export type CreateStandardPageLayoutTabContext< + L extends AllStandardPageLayoutName, +> = { + layoutName: L; + tabName: AllStandardPageLayoutTabName; + title: string; + position: number; +}; + +export type CreateStandardPageLayoutTabArgs< + L extends AllStandardPageLayoutName = AllStandardPageLayoutName, +> = { + now: string; + workspaceId: string; + twentyStandardApplicationId: string; + standardPageLayoutMetadataRelatedEntityIds: StandardPageLayoutMetadataRelatedEntityIds; + context: CreateStandardPageLayoutTabContext; +}; + +export const createStandardPageLayoutTabFlatMetadata = < + L extends AllStandardPageLayoutName, +>({ + context: { layoutName, tabName, title, position }, + workspaceId, + twentyStandardApplicationId, + standardPageLayoutMetadataRelatedEntityIds, + now, +}: CreateStandardPageLayoutTabArgs): FlatPageLayoutTab => { + const layoutIds = standardPageLayoutMetadataRelatedEntityIds[layoutName]; + // @ts-expect-error ignore + const tabDefinition = STANDARD_PAGE_LAYOUTS[layoutName].tabs[tabName] as { + universalIdentifier: string; + }; + + if (!isDefined(tabDefinition)) { + throw new Error( + `Invalid configuration ${layoutName} ${tabName.toString()}`, + ); + } + + const tabIds = layoutIds.tabs[tabName]; + + const widgetIds = Object.values(tabIds.widgets).map( + (widget: { id: string }) => widget.id, + ); + + return { + id: tabIds.id, + universalIdentifier: tabDefinition.universalIdentifier, + applicationId: twentyStandardApplicationId, + workspaceId, + title, + position, + pageLayoutId: layoutIds.id, + widgetIds, + createdAt: now, + updatedAt: now, + deletedAt: null, + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/standard-flat-page-layout-tab-builders.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/standard-flat-page-layout-tab-builders.constant.ts new file mode 100644 index 0000000000..a62500da77 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/standard-flat-page-layout-tab-builders.constant.ts @@ -0,0 +1,27 @@ +import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; +import {} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { type AllStandardPageLayoutTabName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type'; +import { + type CreateStandardPageLayoutTabArgs, + createStandardPageLayoutTabFlatMetadata, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/create-standard-page-layout-tab-flat-metadata.util'; + +type TabBuilderArgs = Omit; + +export const STANDARD_FLAT_PAGE_LAYOUT_TAB_BUILDERS_BY_LAYOUT_NAME = { + tab1: (args: TabBuilderArgs) => + createStandardPageLayoutTabFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + title: 'Tab 1', + position: 0, + }, + }), +} satisfies { + [T in AllStandardPageLayoutTabName]: ( + args: TabBuilderArgs, + ) => FlatPageLayoutTab; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util.ts new file mode 100644 index 0000000000..bed9eac75d --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util.ts @@ -0,0 +1,30 @@ +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; +import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; +import { type CreateStandardPageLayoutWidgetArgs } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util'; +import { STANDARD_FLAT_PAGE_LAYOUT_WIDGET_BUILDERS } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/standard-flat-page-layout-widget-builders.constant'; + +export type BuildStandardFlatPageLayoutWidgetMetadataMapsArgs = Omit< + CreateStandardPageLayoutWidgetArgs, + 'context' +>; + +export const buildStandardFlatPageLayoutWidgetMetadataMaps = ( + args: BuildStandardFlatPageLayoutWidgetMetadataMapsArgs, +): FlatEntityMaps => { + const allWidgetMetadatas: FlatPageLayoutWidget[] = Object.values( + STANDARD_FLAT_PAGE_LAYOUT_WIDGET_BUILDERS, + ).flatMap((builder) => builder(args)); + + let flatPageLayoutWidgetMaps = createEmptyFlatEntityMaps(); + + for (const widgetMetadata of allWidgetMetadatas) { + flatPageLayoutWidgetMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: widgetMetadata, + flatEntityMaps: flatPageLayoutWidgetMaps, + }); + } + + return flatPageLayoutWidgetMaps; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util.ts new file mode 100644 index 0000000000..ff626d1249 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util.ts @@ -0,0 +1,91 @@ +import { isDefined } from 'class-validator'; + +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 { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { type AllStandardPageLayoutTabName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type'; +import { type AllStandardPageLayoutWidgetName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type'; +import { type StandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util'; +import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util'; + +export type CreateStandardPageLayoutWidgetContext< + L extends AllStandardPageLayoutName, + T extends AllStandardPageLayoutTabName, +> = { + layoutName: L; + tabName: T; + widgetName: AllStandardPageLayoutWidgetName; + title: string; + type: WidgetType; + gridPosition: GridPosition; + configuration: AllPageLayoutWidgetConfiguration; + objectMetadataId: string | null; +}; + +export type CreateStandardPageLayoutWidgetArgs< + L extends AllStandardPageLayoutName = AllStandardPageLayoutName, + T extends AllStandardPageLayoutTabName = AllStandardPageLayoutTabName, +> = { + now: string; + workspaceId: string; + twentyStandardApplicationId: string; + standardObjectMetadataRelatedEntityIds: StandardObjectMetadataRelatedEntityIds; + standardPageLayoutMetadataRelatedEntityIds: StandardPageLayoutMetadataRelatedEntityIds; + context: CreateStandardPageLayoutWidgetContext; +}; + +export const createStandardPageLayoutWidgetFlatMetadata = < + L extends AllStandardPageLayoutName, + T extends AllStandardPageLayoutTabName, +>({ + context: { + layoutName, + tabName, + widgetName, + title, + type, + gridPosition, + configuration, + objectMetadataId, + }, + workspaceId, + twentyStandardApplicationId, + standardPageLayoutMetadataRelatedEntityIds, + now, +}: CreateStandardPageLayoutWidgetArgs): FlatPageLayoutWidget => { + const layoutIds = standardPageLayoutMetadataRelatedEntityIds[layoutName]; + // @ts-expect-error ignore + const widgetDef = STANDARD_PAGE_LAYOUTS[layoutName].tabs[tabName].widgets[ + widgetName + ] as { + universalIdentifier: string; + }; + + if (!isDefined(widgetDef)) { + throw new Error( + `Invalid configuration ${layoutName} ${tabName.toString()} ${widgetName}`, + ); + } + + const tabIds = layoutIds.tabs[tabName]; + const widgetIds = tabIds.widgets[widgetName]; + + return { + id: widgetIds.id, + universalIdentifier: widgetDef.universalIdentifier, + applicationId: twentyStandardApplicationId, + workspaceId, + pageLayoutTabId: tabIds.id, + title, + type, + gridPosition, + configuration, + objectMetadataId, + createdAt: now, + updatedAt: now, + deletedAt: null, + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/standard-flat-page-layout-widget-builders.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/standard-flat-page-layout-widget-builders.constant.ts new file mode 100644 index 0000000000..0e0020f294 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/standard-flat-page-layout-widget-builders.constant.ts @@ -0,0 +1,464 @@ +import { CalendarStartDay } from 'twenty-shared/constants'; +import { v4 } from 'uuid'; + +import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; +import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; +import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout-widget/enums/axis-name-display.enum'; +import { BarChartLayout } from 'src/engine/metadata-modules/page-layout-widget/enums/bar-chart-layout.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 { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { type AllStandardPageLayoutTabName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type'; +import { type AllStandardPageLayoutWidgetName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type'; +import { + type CreateStandardPageLayoutWidgetArgs, + createStandardPageLayoutWidgetFlatMetadata, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util'; + +const createFilterConfig = ( + filters: Array<{ + type: string; + label: string; + value: string; + displayValue: string; + operand: string; + fieldMetadataId: string; + }>, +) => { + const groupId = v4(); + + 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', + }, + ], + }; +}; + +type WidgetBuilderArgs = Omit; + +export const STANDARD_FLAT_PAGE_LAYOUT_WIDGET_BUILDERS = { + welcomeRichText: (args: WidgetBuilderArgs) => + createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'welcomeRichText', + title: 'Untitled Rich Text', + type: WidgetType.STANDALONE_RICH_TEXT, + gridPosition: { row: 0, column: 0, rowSpan: 6, columnSpan: 6 }, + configuration: { + configurationType: + WidgetConfigurationType.STANDALONE_RICH_TEXT as const, + body: { + blocknote: JSON.stringify([ + { + id: v4(), + type: 'heading', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + level: 3, + }, + content: [ + { + type: 'text', + text: 'Welcome to your workspace', + styles: {}, + }, + ], + children: [], + }, + { + id: v4(), + type: 'paragraph', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + }, + content: [], + children: [], + }, + { + id: v4(), + type: 'paragraph', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + }, + content: [ + { + type: 'text', + text: 'You can edit this dashboard by clicking the ', + styles: {}, + }, + { type: 'text', text: 'Edit', styles: { code: true } }, + { + type: 'text', + text: ' button in the top-right corner to add your own charts or customize this one.', + styles: {}, + }, + ], + children: [], + }, + { + id: v4(), + type: 'paragraph', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + }, + content: [], + children: [], + }, + { + id: v4(), + type: 'paragraph', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + }, + content: [ + { + type: 'text', + text: "Don't forget to replace the sample data with your own.", + styles: {}, + }, + ], + children: [], + }, + { + id: v4(), + type: 'paragraph', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + }, + content: [], + children: [], + }, + { + id: v4(), + type: 'paragraph', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + }, + content: [ + { + type: 'text', + text: 'If you have any issues, you can check ', + styles: {}, + }, + { + type: 'link', + href: 'https://docs.twenty.com/user-guide/introduction', + content: [ + { + type: 'text', + text: 'our documentation', + styles: {}, + }, + ], + }, + { + type: 'text', + text: ' or contact us through the Support section in Settings.', + styles: {}, + }, + ], + children: [], + }, + { + id: v4(), + type: 'paragraph', + props: { + textColor: 'default', + backgroundColor: 'default', + textAlignment: 'left', + }, + content: [], + children: [], + }, + ]), + markdown: null, + }, + }, + objectMetadataId: null, + }, + }), + + dealsByCompany: (args: WidgetBuilderArgs) => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'dealsByCompany', + title: 'Deals by Company', + type: WidgetType.GRAPH, + gridPosition: { row: 0, column: 6, rowSpan: 6, columnSpan: 6 }, + configuration: { + configurationType: WidgetConfigurationType.PIE_CHART, + groupByFieldMetadataId: opportunityFields.company.id, + groupBySubFieldName: 'name', + aggregateFieldMetadataId: opportunityFields.id.id, + aggregateOperation: AggregateOperations.COUNT, + orderBy: GraphOrderBy.FIELD_ASC, + displayDataLabel: false, + showCenterMetric: true, + displayLegend: true, + color: 'orange', + timezone: 'UTC', + firstDayOfTheWeek: CalendarStartDay.SUNDAY, + }, + objectMetadataId: opportunityObjectId, + }, + }); + }, + + pipelineValueByStage: (args: WidgetBuilderArgs) => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'pipelineValueByStage', + title: 'Pipeline Value by Stage', + type: WidgetType.GRAPH, + gridPosition: { row: 6, column: 0, rowSpan: 6, columnSpan: 6 }, + configuration: { + configurationType: WidgetConfigurationType.BAR_CHART, + aggregateFieldMetadataId: opportunityFields.amount.id, + aggregateOperation: AggregateOperations.SUM, + primaryAxisGroupByFieldMetadataId: opportunityFields.stage.id, + primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.DAY, + primaryAxisOrderBy: GraphOrderBy.FIELD_POSITION_ASC, + secondaryAxisGroupByFieldMetadataId: opportunityFields.company.id, + secondaryAxisGroupBySubFieldName: 'name', + secondaryAxisGroupByDateGranularity: + ObjectRecordGroupByDateGranularity.DAY, + secondaryAxisOrderBy: GraphOrderBy.FIELD_ASC, + axisNameDisplay: AxisNameDisplay.NONE, + displayDataLabel: true, + displayLegend: true, + color: 'green', + layout: BarChartLayout.VERTICAL, + timezone: 'UTC', + firstDayOfTheWeek: CalendarStartDay.SUNDAY, + }, + objectMetadataId: opportunityObjectId, + }, + }); + }, + + revenueTimeline: (args: WidgetBuilderArgs) => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'revenueTimeline', + title: 'Revenue Timeline', + type: WidgetType.GRAPH, + gridPosition: { row: 6, column: 6, rowSpan: 6, columnSpan: 6 }, + configuration: { + configurationType: WidgetConfigurationType.LINE_CHART, + aggregateFieldMetadataId: opportunityFields.amount.id, + aggregateOperation: AggregateOperations.SUM, + primaryAxisGroupByFieldMetadataId: opportunityFields.closeDate.id, + primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.DAY, + primaryAxisOrderBy: GraphOrderBy.FIELD_ASC, + axisNameDisplay: AxisNameDisplay.NONE, + displayDataLabel: false, + displayLegend: true, + color: 'crimson', + isCumulative: false, + timezone: 'UTC', + firstDayOfTheWeek: CalendarStartDay.SUNDAY, + }, + objectMetadataId: opportunityObjectId, + }, + }); + }, + + opportunitiesByOwner: (args: WidgetBuilderArgs) => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'opportunitiesByOwner', + title: 'Opportunities by Owner', + type: WidgetType.GRAPH, + gridPosition: { row: 12, column: 0, rowSpan: 6, columnSpan: 6 }, + configuration: { + configurationType: WidgetConfigurationType.BAR_CHART, + aggregateFieldMetadataId: opportunityFields.id.id, + aggregateOperation: AggregateOperations.COUNT, + primaryAxisGroupByFieldMetadataId: opportunityFields.owner.id, + primaryAxisGroupBySubFieldName: 'name.firstName', + primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.DAY, + primaryAxisOrderBy: GraphOrderBy.FIELD_ASC, + secondaryAxisGroupByFieldMetadataId: opportunityFields.owner.id, + secondaryAxisGroupBySubFieldName: 'name.firstName', + secondaryAxisGroupByDateGranularity: + ObjectRecordGroupByDateGranularity.DAY, + secondaryAxisOrderBy: GraphOrderBy.FIELD_ASC, + axisNameDisplay: AxisNameDisplay.NONE, + displayDataLabel: false, + displayLegend: true, + color: 'blue', + layout: BarChartLayout.HORIZONTAL, + isCumulative: false, + timezone: 'UTC', + firstDayOfTheWeek: CalendarStartDay.SUNDAY, + }, + objectMetadataId: opportunityObjectId, + }, + }); + }, + + stockMarketIframe: (args: WidgetBuilderArgs) => + createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'stockMarketIframe', + title: 'Stock market (Iframe)', + type: WidgetType.IFRAME, + gridPosition: { row: 12, column: 6, rowSpan: 8, columnSpan: 6 }, + configuration: { + configurationType: WidgetConfigurationType.IFRAME as const, + url: 'https://www.tradingview.com/embed-widget/hotlists/?locale=en', + }, + objectMetadataId: null, + }, + }), + + dealsCreatedThisMonth: (args: WidgetBuilderArgs) => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'dealsCreatedThisMonth', + title: 'Deals created this month', + type: WidgetType.GRAPH, + gridPosition: { row: 18, column: 0, rowSpan: 2, columnSpan: 3 }, + configuration: { + configurationType: WidgetConfigurationType.AGGREGATE_CHART, + aggregateFieldMetadataId: opportunityFields.id.id, + aggregateOperation: AggregateOperations.COUNT, + displayDataLabel: false, + filter: createFilterConfig([ + { + type: 'DATE_TIME', + label: 'Creation date', + value: 'THIS_1_MONTH;;UTC;;SUNDAY;;', + displayValue: 'THIS_1_MONTH;;UTC;;SUNDAY;;', + operand: 'IS_RELATIVE', + fieldMetadataId: opportunityFields.createdAt.id, + }, + ]), + prefix: '', + timezone: 'UTC', + firstDayOfTheWeek: CalendarStartDay.SUNDAY, + }, + objectMetadataId: opportunityObjectId, + }, + }); + }, + + dealValueCreatedThisMonth: (args: WidgetBuilderArgs) => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabName: 'tab1', + widgetName: 'dealValueCreatedThisMonth', + title: 'Deal value created this month', + type: WidgetType.GRAPH, + gridPosition: { row: 18, column: 3, rowSpan: 2, columnSpan: 3 }, + configuration: { + configurationType: WidgetConfigurationType.AGGREGATE_CHART, + aggregateFieldMetadataId: opportunityFields.amount.id, + aggregateOperation: AggregateOperations.SUM, + displayDataLabel: false, + filter: createFilterConfig([ + { + type: 'DATE_TIME', + label: 'Creation date', + value: 'THIS_1_MONTH;;UTC;;SUNDAY;;', + displayValue: 'THIS_1_MONTH;;UTC;;SUNDAY;;', + operand: 'IS_RELATIVE', + fieldMetadataId: opportunityFields.createdAt.id, + }, + ]), + prefix: '$', + timezone: 'UTC', + firstDayOfTheWeek: CalendarStartDay.SUNDAY, + }, + objectMetadataId: opportunityObjectId, + }, + }); + }, +} satisfies { + [T in AllStandardPageLayoutWidgetName< + AllStandardPageLayoutName, + AllStandardPageLayoutTabName + >]: (args: WidgetBuilderArgs) => FlatPageLayoutWidget; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util.ts new file mode 100644 index 0000000000..41d3b0ba51 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util.ts @@ -0,0 +1,30 @@ +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; +import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; +import { type CreateStandardPageLayoutArgs } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util'; +import { STANDARD_FLAT_PAGE_LAYOUT_BUILDERS_BY_LAYOUT_NAME } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/standard-flat-page-layout-builders.constant'; + +export type BuildStandardFlatPageLayoutMetadataMapsArgs = Omit< + CreateStandardPageLayoutArgs, + 'context' +>; + +export const buildStandardFlatPageLayoutMetadataMaps = ( + args: BuildStandardFlatPageLayoutMetadataMapsArgs, +): FlatEntityMaps => { + const allPageLayoutMetadatas: FlatPageLayout[] = Object.values( + STANDARD_FLAT_PAGE_LAYOUT_BUILDERS_BY_LAYOUT_NAME, + ).map((builder) => builder(args)); + + let flatPageLayoutMaps = createEmptyFlatEntityMaps(); + + for (const pageLayoutMetadata of allPageLayoutMetadatas) { + flatPageLayoutMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: pageLayoutMetadata, + flatEntityMaps: flatPageLayoutMaps, + }); + } + + return flatPageLayoutMaps; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util.ts new file mode 100644 index 0000000000..1f050c97f6 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util.ts @@ -0,0 +1,45 @@ +import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; +import { type PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util'; + +export type CreateStandardPageLayoutContext = { + layoutName: AllStandardPageLayoutName; + name: string; + type: PageLayoutType; + objectMetadataId: string | null; +}; + +export type CreateStandardPageLayoutArgs = { + now: string; + workspaceId: string; + twentyStandardApplicationId: string; + standardPageLayoutMetadataRelatedEntityIds: StandardPageLayoutMetadataRelatedEntityIds; + context: CreateStandardPageLayoutContext; +}; + +export const createStandardPageLayoutFlatMetadata = ({ + context: { layoutName, name, type, objectMetadataId }, + workspaceId, + twentyStandardApplicationId, + standardPageLayoutMetadataRelatedEntityIds, + now, +}: CreateStandardPageLayoutArgs): FlatPageLayout => { + const universalIdentifier = + STANDARD_PAGE_LAYOUTS[layoutName].universalIdentifier; + + return { + id: standardPageLayoutMetadataRelatedEntityIds[layoutName].id, + universalIdentifier, + applicationId: twentyStandardApplicationId, + workspaceId, + name, + type, + objectMetadataId, + tabIds: [], + createdAt: now, + updatedAt: now, + deletedAt: null, + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/standard-flat-page-layout-builders.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/standard-flat-page-layout-builders.constant.ts new file mode 100644 index 0000000000..516938f9eb --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout/standard-flat-page-layout-builders.constant.ts @@ -0,0 +1,24 @@ +import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { type AllStandardPageLayoutName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type'; +import { + type CreateStandardPageLayoutArgs, + createStandardPageLayoutFlatMetadata, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util'; + +export const STANDARD_FLAT_PAGE_LAYOUT_BUILDERS_BY_LAYOUT_NAME = { + myFirstDashboard: (args: Omit) => + createStandardPageLayoutFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + name: 'My First Dashboard', + type: PageLayoutType.DASHBOARD, + objectMetadataId: null, + }, + }), +} satisfies { + [P in AllStandardPageLayoutName]: ( + args: Omit, + ) => FlatPageLayout; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts index 2fc36f2bb0..688d134330 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant.ts @@ -1,12 +1,16 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; import { type TwentyStandardAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/types/twenty-standard-all-flat-entity-maps.type'; import { buildStandardFlatAgentMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/build-standard-flat-agent-metadata-maps.util'; -import { buildStandardFlatSkillMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/build-standard-flat-skill-metadata-maps.util'; import { buildStandardFlatFieldMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/build-standard-flat-field-metadata-maps.util'; import { getStandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util'; +import { getStandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util'; import { buildStandardFlatIndexMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/index/build-standard-flat-index-metadata-maps.util'; import { buildStandardFlatObjectMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/build-standard-flat-object-metadata-maps.util'; +import { buildStandardFlatPageLayoutTabMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/build-standard-flat-page-layout-tab-metadata-maps.util'; +import { buildStandardFlatPageLayoutWidgetMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util'; +import { buildStandardFlatPageLayoutMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util'; import { buildStandardFlatRoleMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/build-standard-flat-role-metadata-maps.util'; +import { buildStandardFlatSkillMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/build-standard-flat-skill-metadata-maps.util'; import { buildStandardFlatViewFieldMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util'; import { buildStandardFlatViewFilterMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-filter/build-standard-flat-view-filter-metadata-maps.util'; import { buildStandardFlatViewGroupMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-group/build-standard-flat-view-group-metadata-maps.util'; @@ -131,6 +135,32 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ dependencyFlatEntityMaps: undefined, }); + const standardPageLayoutMetadataRelatedEntityIds = + getStandardPageLayoutMetadataRelatedEntityIds(); + + const flatPageLayoutMaps = buildStandardFlatPageLayoutMetadataMaps({ + now, + workspaceId, + twentyStandardApplicationId, + standardPageLayoutMetadataRelatedEntityIds, + }); + + const flatPageLayoutTabMaps = buildStandardFlatPageLayoutTabMetadataMaps({ + now, + workspaceId, + twentyStandardApplicationId, + standardPageLayoutMetadataRelatedEntityIds, + }); + + const flatPageLayoutWidgetMaps = + buildStandardFlatPageLayoutWidgetMetadataMaps({ + now, + workspaceId, + twentyStandardApplicationId, + standardObjectMetadataRelatedEntityIds, + standardPageLayoutMetadataRelatedEntityIds, + }); + return { flatViewFieldMaps, flatViewFilterMaps, @@ -142,5 +172,8 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ flatRoleMaps, flatAgentMaps, flatSkillMaps, + flatPageLayoutMaps, + flatPageLayoutTabMaps, + flatPageLayoutWidgetMaps, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts index f0e737ad13..ed9dd3b628 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts @@ -1,14 +1,13 @@ import { Injectable, Logger } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; -import { DataSource, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { ADMIN_ROLE } from 'src/engine/metadata-modules/role/constants/admin-role'; @@ -17,9 +16,6 @@ import { RoleService } from 'src/engine/metadata-modules/role/role.service'; import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; -import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies'; -import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people'; -import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows'; import { TwentyStandardApplicationService } from 'src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service'; @Injectable() @@ -27,8 +23,6 @@ export class WorkspaceManagerService { private readonly logger = new Logger(WorkspaceManagerService.name); constructor( - @InjectDataSource() - private readonly coreDataSource: DataSource, private readonly workspaceDataSourceService: WorkspaceDataSourceService, private readonly objectMetadataService: ObjectMetadataService, private readonly dataSourceService: DataSourceService, @@ -46,7 +40,6 @@ export class WorkspaceManagerService { @InjectRepository(ServerlessFunctionEntity) private readonly serverlessFunctionRepository: Repository, private readonly applicationService: ApplicationService, - private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} public async init({ @@ -86,11 +79,6 @@ export class WorkspaceManagerService { }, ); - await this.prefillCreatedWorkspaceRecords({ - workspaceId, - schemaName, - }); - const dataSourceMetadataCreationEnd = performance.now(); this.logger.log( @@ -111,56 +99,6 @@ export class WorkspaceManagerService { }); } - private async prefillCreatedWorkspaceRecords({ - workspaceId, - schemaName, - }: { - workspaceId: string; - schemaName: string; - }): Promise { - const { flatObjectMetadataMaps, flatFieldMetadataMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'], - }, - ); - - const queryRunner = this.coreDataSource.createQueryRunner(); - - await queryRunner.connect(); - - try { - await queryRunner.startTransaction(); - - await prefillCompanies(queryRunner.manager, schemaName); - - await prefillPeople(queryRunner.manager, schemaName); - - await prefillWorkflows( - queryRunner.manager, - schemaName, - flatObjectMetadataMaps, - flatFieldMetadataMaps, - ); - - await queryRunner.commitTransaction(); - } catch (error) { - if (queryRunner.isTransactionActive) { - try { - await queryRunner.rollbackTransaction(); - } catch (rollbackError) { - this.logger.error( - `Failed to rollback prefill transaction: ${rollbackError.message}`, - ); - } - } - throw error; - } finally { - await queryRunner.release(); - } - } - /** * @deprecated Should be removed once AddWorkspaceForeignKeysMigrationCommand has been run successfully in production * As we will be able to rely on foreignKey delete cascading diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts index bb4afe05b8..9ee622e53a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts @@ -42,9 +42,11 @@ export class FlatPageLayoutWidgetValidatorService { const isDashboardV2Enabled = featureFlagsMap[FeatureFlagKey.IS_DASHBOARD_V2_ENABLED] ?? false; - const existingFlatPageLayoutWidget = - optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatPageLayoutWidgetMaps - .byId[flatEntityId]; + const existingFlatPageLayoutWidget = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: flatEntityId, + flatEntityMaps: + optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatPageLayoutWidgetMaps, + }); const validationResult = getEmptyFlatEntityValidationError({ flatEntityMinimalInformation: { diff --git a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts index 0c45937a82..f2c24c9cf9 100644 --- a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts +++ b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts @@ -14,6 +14,7 @@ import { type NoteTargetWorkspaceEntity } from 'src/modules/note/standard-object import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { type TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity'; import { type TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; const NAME_FIELD_NAME = 'name'; @@ -38,6 +39,8 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity { noteTargets: EntityRelation; attachments: EntityRelation; timelineActivities: EntityRelation; + owner: EntityRelation | null; + ownerId: string | null; /** @deprecated */ probability: string; searchVector: string; diff --git a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts index df9deead3f..cbbc3b68f1 100644 --- a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts @@ -13,6 +13,7 @@ import { type CompanyWorkspaceEntity } from 'src/modules/company/standard-object import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { type FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; +import { type OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; import { type TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity'; import { type TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; @@ -84,6 +85,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity { CalendarEventParticipantWorkspaceEntity[] >; timelineActivities: EntityRelation; + ownedOpportunities: EntityRelation; searchVector: string; numberFormat: string; }