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; }