From 446afcbc3015782064b6fe6b5c9d5896a5d17937 Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 30 Jan 2026 12:40:36 +0100 Subject: [PATCH] Add standard record page layout (#17567) --- ...-backfill-standard-page-layouts.command.ts | 7 + ...9579383-setPageLayoutDefaultTabDeferred.ts | 19 + .../twenty-config/config-variables.ts | 13 +- .../utils/reject-widget-type.util.ts | 10 +- .../standard-page-layout-tabs.template.ts | 146 +++++ .../standard-page-layout.constant.ts | 62 +-- .../constants/standard-page-layout.types.ts | 48 ++ .../twenty-standard-application.service.ts | 7 + .../twenty-standard-application.module.ts | 2 + .../all-standard-page-layout-name.type.ts | 3 - .../all-standard-page-layout-tab-name.type.ts | 6 - ...l-standard-page-layout-widget-name.type.ts | 12 - ...adata-related-entity-ids.util.spec.ts.snap | 352 +++++++++++- ...layout-metadata-related-entity-ids.util.ts | 90 +--- .../utils/page-layout-config/index.ts | 10 + .../standard-company-page-layout.config.ts | 94 ++++ .../standard-dashboard-page-layout.config.ts | 53 ++ .../standard-note-page-layout.config.ts | 68 +++ ...standard-opportunity-page-layout.config.ts | 94 ++++ .../standard-page-layout-config.type.ts | 31 ++ .../standard-person-page-layout.config.ts | 94 ++++ .../standard-task-page-layout.config.ts | 68 +++ .../standard-workflow-page-layout.config.ts | 34 ++ ...tandard-workflow-run-page-layout.config.ts | 45 ++ ...ard-workflow-version-page-layout.config.ts | 45 ++ ...flat-page-layout-tab-metadata-maps.util.ts | 64 ++- ...dard-page-layout-tab-flat-metadata.util.ts | 54 +- ...-flat-page-layout-tab-builders.constant.ts | 27 - ...t-page-layout-widget-metadata-maps.util.ts | 107 +++- ...compute-my-first-dashboard-widgets.util.ts | 504 ++++++++++++++++++ ...d-page-layout-widget-flat-metadata.util.ts | 51 +- ...at-page-layout-widget-builders.constant.ts | 464 ---------------- ...ard-flat-page-layout-metadata-maps.util.ts | 19 +- ...standard-page-layout-flat-metadata.util.ts | 94 +++- ...dard-flat-page-layout-builders.constant.ts | 63 ++- ...plication-all-flat-entity-maps.constant.ts | 6 + 36 files changed, 2131 insertions(+), 735 deletions(-) create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1769679579383-setPageLayoutDefaultTabDeferred.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/index.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-company-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-dashboard-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-note-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-opportunity-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-person-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-task-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-run-page-layout.config.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-version-page-layout.config.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/standard-flat-page-layout-tab-builders.constant.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/compute-my-first-dashboard-widgets.util.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/standard-flat-page-layout-widget-builders.constant.ts 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 index 989e7613e3..c9c01eb11d 100644 --- 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 @@ -10,6 +10,7 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/ 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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.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'; @@ -36,6 +37,7 @@ export class BackfillStandardPageLayoutsCommand extends ActiveOrSuspendedWorkspa protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, protected readonly dataSourceService: DataSourceService, private readonly applicationService: ApplicationService, + private readonly twentyConfigService: TwentyConfigService, private readonly workspaceCacheService: WorkspaceCacheService, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, ) { @@ -102,6 +104,10 @@ export class BackfillStandardPageLayoutsCommand extends ActiveOrSuspendedWorkspa }, ); + const shouldIncludeRecordPageLayouts = this.twentyConfigService.get( + 'SHOULD_SEED_STANDARD_RECORD_PAGE_LAYOUTS', + ); + const { flatPageLayoutMaps: toFlatPageLayoutMaps, flatPageLayoutTabMaps: toFlatPageLayoutTabMaps, @@ -110,6 +116,7 @@ export class BackfillStandardPageLayoutsCommand extends ActiveOrSuspendedWorkspa now: new Date().toISOString(), workspaceId, twentyStandardApplicationId: twentyStandardFlatApplication.id, + shouldIncludeRecordPageLayouts, }); const toFlatPageLayoutWidgetMaps = diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1769679579383-setPageLayoutDefaultTabDeferred.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1769679579383-setPageLayoutDefaultTabDeferred.ts new file mode 100644 index 0000000000..d897a2892c --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1769679579383-setPageLayoutDefaultTabDeferred.ts @@ -0,0 +1,19 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class SetPageLayoutDefaultTabDeferred1769679579383 + implements MigrationInterface +{ + name = 'SetPageLayoutDefaultTabDeferred1769679579383'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ALTER CONSTRAINT "FK_747fbc25827bdcb9e35cc68a990" DEFERRABLE INITIALLY DEFERRED`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ALTER CONSTRAINT "FK_747fbc25827bdcb9e35cc68a990" NOT DEFERRABLE INITIALLY DEFERRED`, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts index 907c05e7c3..292c71b0c3 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts @@ -22,12 +22,12 @@ import { EmailDriver } from 'src/engine/core-modules/email/enums/email-driver.en import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces'; import { StorageDriverType } from 'src/engine/core-modules/file-storage/interfaces'; import { LoggerDriverType } from 'src/engine/core-modules/logger/interfaces'; -import { type MeterDriver } from 'src/engine/core-modules/metrics/types/meter-driver.type'; import { LogicFunctionExecutorDriverType } from 'src/engine/core-modules/logic-function-executor/logic-function-executor.interface'; +import { type MeterDriver } from 'src/engine/core-modules/metrics/types/meter-driver.type'; import { CastToLogLevelArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-log-level-array.decorator'; -import { CastToTypeORMLogLevelArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-typeorm-log-level-array.decorator'; import { CastToMeterDriverArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-meter-driver.decorator'; import { CastToPositiveNumber } from 'src/engine/core-modules/twenty-config/decorators/cast-to-positive-number.decorator'; +import { CastToTypeORMLogLevelArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-typeorm-log-level-array.decorator'; import { CastToUpperSnakeCase } from 'src/engine/core-modules/twenty-config/decorators/cast-to-upper-snake-case.decorator'; import { ConfigVariablesMetadata } from 'src/engine/core-modules/twenty-config/decorators/config-variables-metadata.decorator'; import { IsAWSRegion } from 'src/engine/core-modules/twenty-config/decorators/is-aws-region.decorator'; @@ -207,6 +207,15 @@ export class ConfigVariables { @ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED) AUTH_MICROSOFT_APIS_CALLBACK_URL: string; + @ConfigVariablesMetadata({ + group: ConfigVariablesGroup.OTHER, + description: + 'Enable or disable the seeding of standard record page layouts', + type: ConfigVariableType.BOOLEAN, + }) + @IsOptional() + SHOULD_SEED_STANDARD_RECORD_PAGE_LAYOUTS = false; + @ConfigVariablesMetadata({ group: ConfigVariablesGroup.MICROSOFT_AUTH, description: 'Enable or disable the Microsoft messaging integration', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/validators/utils/reject-widget-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/validators/utils/reject-widget-type.util.ts index 85e085faf6..4aa3ec4042 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/validators/utils/reject-widget-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/validators/utils/reject-widget-type.util.ts @@ -1,5 +1,6 @@ import { type msg } from '@lingui/core/macro'; +import { type GenericValidateFlatPageLayoutWidgetTypeSpecificitiesArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service'; import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type'; import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum'; import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception'; @@ -9,7 +10,14 @@ export const rejectWidgetType = ( message: string, userFriendlyMessage: ReturnType, ) => { - return (): FlatPageLayoutWidgetValidationError[] => { + return ( + args: GenericValidateFlatPageLayoutWidgetTypeSpecificitiesArgs, + ): FlatPageLayoutWidgetValidationError[] => { + // Allow standard widgets to use widget types that are not yet supported for user creation + if (args.buildOptions.isSystemBuild) { + return []; + } + return [ { code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts new file mode 100644 index 0000000000..635d5d989f --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts @@ -0,0 +1,146 @@ +import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; + +import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum'; +import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type'; + +export const GRID_POSITIONS = { + FULL_WIDTH: { + row: 0, + column: 0, + rowSpan: 12, + columnSpan: 12, + }, + HALF_HEIGHT: { + row: 0, + column: 0, + rowSpan: 6, + columnSpan: 12, + }, + RICH_TEXT: { + row: 12, + column: 0, + rowSpan: 6, + columnSpan: 12, + }, +} as const satisfies Record; + +export const TAB_PROPS = { + home: { + title: 'Home', + position: 100, + icon: 'IconHome', + layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, + }, + timeline: { + title: 'Timeline', + position: 200, + icon: 'IconTimelineEvent', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + tasks: { + title: 'Tasks', + position: 300, + icon: 'IconCheckbox', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + notes: { + title: 'Notes', + position: 400, + icon: 'IconNotes', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + files: { + title: 'Files', + position: 500, + icon: 'IconPaperclip', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + emails: { + title: 'Emails', + position: 600, + icon: 'IconMail', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + calendar: { + title: 'Calendar', + position: 700, + icon: 'IconCalendarEvent', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + note: { + title: 'Note', + position: 150, + icon: 'IconNotes', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + flow: { + title: 'Flow', + position: 100, + icon: 'IconSettings', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, + flowSecondary: { + title: 'Flow', + position: 200, + icon: 'IconSettings', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + }, +} as const; + +export const WIDGET_PROPS = { + fields: { + title: 'Fields', + type: WidgetType.FIELDS, + gridPosition: GRID_POSITIONS.FULL_WIDTH, + }, + timeline: { + title: 'Timeline', + type: WidgetType.TIMELINE, + gridPosition: GRID_POSITIONS.HALF_HEIGHT, + }, + tasks: { + title: 'Tasks', + type: WidgetType.TASKS, + gridPosition: GRID_POSITIONS.HALF_HEIGHT, + }, + notes: { + title: 'Notes', + type: WidgetType.NOTES, + gridPosition: GRID_POSITIONS.HALF_HEIGHT, + }, + files: { + title: 'Files', + type: WidgetType.FILES, + gridPosition: GRID_POSITIONS.HALF_HEIGHT, + }, + emails: { + title: 'Emails', + type: WidgetType.EMAILS, + gridPosition: GRID_POSITIONS.HALF_HEIGHT, + }, + calendar: { + title: 'Calendar', + type: WidgetType.CALENDAR, + gridPosition: GRID_POSITIONS.HALF_HEIGHT, + }, + richText: { + title: 'Note', + type: WidgetType.FIELD_RICH_TEXT, + gridPosition: GRID_POSITIONS.RICH_TEXT, + }, + workflow: { + title: 'Flow', + type: WidgetType.WORKFLOW, + gridPosition: GRID_POSITIONS.FULL_WIDTH, + }, + workflowVersion: { + title: 'Flow', + type: WidgetType.WORKFLOW_VERSION, + gridPosition: GRID_POSITIONS.FULL_WIDTH, + }, + workflowRun: { + title: 'Flow', + type: WidgetType.WORKFLOW_RUN, + gridPosition: GRID_POSITIONS.FULL_WIDTH, + }, +} as const; 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 index 2a5a315d04..789bc0e18c 100644 --- 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 @@ -1,36 +1,30 @@ +import { + STANDARD_COMPANY_PAGE_LAYOUT_CONFIG, + STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG, + STANDARD_NOTE_PAGE_LAYOUT_CONFIG, + STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG, + STANDARD_PERSON_PAGE_LAYOUT_CONFIG, + STANDARD_TASK_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config'; +import { type StandardRecordPageLayouts } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types'; + 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', - }, - }, - }, - }, - }, + myFirstDashboard: STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG, + companyRecordPage: STANDARD_COMPANY_PAGE_LAYOUT_CONFIG, + personRecordPage: STANDARD_PERSON_PAGE_LAYOUT_CONFIG, + opportunityRecordPage: STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG, + noteRecordPage: STANDARD_NOTE_PAGE_LAYOUT_CONFIG, + taskRecordPage: STANDARD_TASK_PAGE_LAYOUT_CONFIG, + workflowRecordPage: STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG, + workflowVersionRecordPage: STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG, + workflowRunRecordPage: STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG, } as const; + +const { myFirstDashboard: _myFirstDashboard, ...recordPageLayouts } = + STANDARD_PAGE_LAYOUTS; + +export const STANDARD_RECORD_PAGE_LAYOUTS = + recordPageLayouts satisfies StandardRecordPageLayouts; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types.ts new file mode 100644 index 0000000000..b124ec8428 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types.ts @@ -0,0 +1,48 @@ +import { type PageLayoutTabLayoutMode } from 'twenty-shared/types'; + +import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum'; +import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type'; + +export type StandardPageLayoutWidgetDefinition = { + universalIdentifier: string; + title?: string; + type?: WidgetType; + gridPosition?: GridPosition; +}; + +export type StandardPageLayoutTabDefinition = { + universalIdentifier: string; + title: string; + position: number; + icon: string | null; + layoutMode: PageLayoutTabLayoutMode; + widgets: Record; +}; + +export type StandardRecordPageWidgetDefinition = { + universalIdentifier: string; + title: string; + type: WidgetType; + gridPosition: GridPosition; +}; + +export type StandardRecordPageTabDefinition = { + universalIdentifier: string; + title: string; + position: number; + icon: string | null; + layoutMode: PageLayoutTabLayoutMode; + widgets: Record; +}; + +export type StandardRecordPageLayoutDefinition = { + universalIdentifier: string; + objectUniversalIdentifier: string | null; + defaultTabUniversalIdentifier: string | null; + tabs: Record; +}; + +export type StandardRecordPageLayouts = Record< + string, + StandardRecordPageLayoutDefinition +>; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts index e31f125939..67204404d0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts @@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; @@ -25,6 +26,7 @@ import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/f export class TwentyStandardApplicationService { constructor( private readonly applicationService: ApplicationService, + private readonly twentyConfigService: TwentyConfigService, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, private readonly workspaceCacheService: WorkspaceCacheService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, @@ -148,11 +150,16 @@ export class TwentyStandardApplicationService { ...TWENTY_STANDARD_ALL_METADATA_NAME.map(getMetadataFlatEntityMapsKey), 'featureFlagsMap', ]); + const shouldIncludeRecordPageLayouts = this.twentyConfigService.get( + 'SHOULD_SEED_STANDARD_RECORD_PAGE_LAYOUTS', + ); + const toTwentyStandardAllFlatEntityMaps = computeTwentyStandardApplicationAllFlatEntityMaps({ now: new Date().toISOString(), workspaceId, twentyStandardApplicationId: twentyStandardFlatApplication.id, + shouldIncludeRecordPageLayouts, }); const fromToAllFlatEntityMaps: FromToAllFlatEntityMaps = {}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module.ts index 82128d848d..1290401211 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; +import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.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'; @@ -12,6 +13,7 @@ import { TwentyStandardApplicationService } from './services/twenty-standard-app providers: [TwentyStandardApplicationService], imports: [ ApplicationModule, + TwentyConfigModule, WorkspaceCacheModule, WorkspaceMigrationModule, GlobalWorkspaceDataSourceModule, 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 deleted file mode 100644 index d065ea7d1c..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-name.type.ts +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index e075474000..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-tab-name.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 825c29068d..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/types/all-standard-page-layout-widget-name.type.ts +++ /dev/null @@ -1,12 +0,0 @@ -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-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 index 23093894bb..9e0e38975a 100644 --- 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 @@ -2,35 +2,353 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard page layout metadata related entity ids 1`] = ` { + "companyRecordPage": { + "id": "00000000-0000-0000-0000-000000000011", + "tabs": { + "calendar": { + "id": "00000000-0000-0000-0000-000000000024", + "widgets": { + "calendar": { + "id": "00000000-0000-0000-0000-000000000025", + }, + }, + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000022", + "widgets": { + "emails": { + "id": "00000000-0000-0000-0000-000000000023", + }, + }, + }, + "files": { + "id": "00000000-0000-0000-0000-000000000020", + "widgets": { + "files": { + "id": "00000000-0000-0000-0000-000000000021", + }, + }, + }, + "home": { + "id": "00000000-0000-0000-0000-000000000012", + "widgets": { + "fields": { + "id": "00000000-0000-0000-0000-000000000013", + }, + }, + }, + "notes": { + "id": "00000000-0000-0000-0000-000000000018", + "widgets": { + "notes": { + "id": "00000000-0000-0000-0000-000000000019", + }, + }, + }, + "tasks": { + "id": "00000000-0000-0000-0000-000000000016", + "widgets": { + "tasks": { + "id": "00000000-0000-0000-0000-000000000017", + }, + }, + }, + "timeline": { + "id": "00000000-0000-0000-0000-000000000014", + "widgets": { + "timeline": { + "id": "00000000-0000-0000-0000-000000000015", + }, + }, + }, + }, + }, "myFirstDashboard": { - "id": "00000000-0000-0000-0000-000000000010", + "id": "00000000-0000-0000-0000-000000000001", "tabs": { "tab1": { - "id": "00000000-0000-0000-0000-000000000009", + "id": "00000000-0000-0000-0000-000000000002", "widgets": { "dealValueCreatedThisMonth": { - "id": "00000000-0000-0000-0000-000000000008", + "id": "00000000-0000-0000-0000-000000000010", }, "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": { + "dealsCreatedThisMonth": { + "id": "00000000-0000-0000-0000-000000000009", + }, + "opportunitiesByOwner": { + "id": "00000000-0000-0000-0000-000000000007", + }, + "pipelineValueByStage": { + "id": "00000000-0000-0000-0000-000000000005", + }, + "revenueTimeline": { "id": "00000000-0000-0000-0000-000000000006", }, + "stockMarketIframe": { + "id": "00000000-0000-0000-0000-000000000008", + }, "welcomeRichText": { - "id": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000000003", + }, + }, + }, + }, + }, + "noteRecordPage": { + "id": "00000000-0000-0000-0000-000000000056", + "tabs": { + "files": { + "id": "00000000-0000-0000-0000-000000000064", + "widgets": { + "files": { + "id": "00000000-0000-0000-0000-000000000065", + }, + }, + }, + "home": { + "id": "00000000-0000-0000-0000-000000000057", + "widgets": { + "fields": { + "id": "00000000-0000-0000-0000-000000000058", + }, + "richText": { + "id": "00000000-0000-0000-0000-000000000059", + }, + }, + }, + "note": { + "id": "00000000-0000-0000-0000-000000000060", + "widgets": { + "richText": { + "id": "00000000-0000-0000-0000-000000000061", + }, + }, + }, + "timeline": { + "id": "00000000-0000-0000-0000-000000000062", + "widgets": { + "timeline": { + "id": "00000000-0000-0000-0000-000000000063", + }, + }, + }, + }, + }, + "opportunityRecordPage": { + "id": "00000000-0000-0000-0000-000000000041", + "tabs": { + "calendar": { + "id": "00000000-0000-0000-0000-000000000054", + "widgets": { + "calendar": { + "id": "00000000-0000-0000-0000-000000000055", + }, + }, + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000052", + "widgets": { + "emails": { + "id": "00000000-0000-0000-0000-000000000053", + }, + }, + }, + "files": { + "id": "00000000-0000-0000-0000-000000000050", + "widgets": { + "files": { + "id": "00000000-0000-0000-0000-000000000051", + }, + }, + }, + "home": { + "id": "00000000-0000-0000-0000-000000000042", + "widgets": { + "fields": { + "id": "00000000-0000-0000-0000-000000000043", + }, + }, + }, + "notes": { + "id": "00000000-0000-0000-0000-000000000048", + "widgets": { + "notes": { + "id": "00000000-0000-0000-0000-000000000049", + }, + }, + }, + "tasks": { + "id": "00000000-0000-0000-0000-000000000046", + "widgets": { + "tasks": { + "id": "00000000-0000-0000-0000-000000000047", + }, + }, + }, + "timeline": { + "id": "00000000-0000-0000-0000-000000000044", + "widgets": { + "timeline": { + "id": "00000000-0000-0000-0000-000000000045", + }, + }, + }, + }, + }, + "personRecordPage": { + "id": "00000000-0000-0000-0000-000000000026", + "tabs": { + "calendar": { + "id": "00000000-0000-0000-0000-000000000039", + "widgets": { + "calendar": { + "id": "00000000-0000-0000-0000-000000000040", + }, + }, + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000037", + "widgets": { + "emails": { + "id": "00000000-0000-0000-0000-000000000038", + }, + }, + }, + "files": { + "id": "00000000-0000-0000-0000-000000000035", + "widgets": { + "files": { + "id": "00000000-0000-0000-0000-000000000036", + }, + }, + }, + "home": { + "id": "00000000-0000-0000-0000-000000000027", + "widgets": { + "fields": { + "id": "00000000-0000-0000-0000-000000000028", + }, + }, + }, + "notes": { + "id": "00000000-0000-0000-0000-000000000033", + "widgets": { + "notes": { + "id": "00000000-0000-0000-0000-000000000034", + }, + }, + }, + "tasks": { + "id": "00000000-0000-0000-0000-000000000031", + "widgets": { + "tasks": { + "id": "00000000-0000-0000-0000-000000000032", + }, + }, + }, + "timeline": { + "id": "00000000-0000-0000-0000-000000000029", + "widgets": { + "timeline": { + "id": "00000000-0000-0000-0000-000000000030", + }, + }, + }, + }, + }, + "taskRecordPage": { + "id": "00000000-0000-0000-0000-000000000066", + "tabs": { + "files": { + "id": "00000000-0000-0000-0000-000000000074", + "widgets": { + "files": { + "id": "00000000-0000-0000-0000-000000000075", + }, + }, + }, + "home": { + "id": "00000000-0000-0000-0000-000000000067", + "widgets": { + "fields": { + "id": "00000000-0000-0000-0000-000000000068", + }, + "richText": { + "id": "00000000-0000-0000-0000-000000000069", + }, + }, + }, + "note": { + "id": "00000000-0000-0000-0000-000000000070", + "widgets": { + "richText": { + "id": "00000000-0000-0000-0000-000000000071", + }, + }, + }, + "timeline": { + "id": "00000000-0000-0000-0000-000000000072", + "widgets": { + "timeline": { + "id": "00000000-0000-0000-0000-000000000073", + }, + }, + }, + }, + }, + "workflowRecordPage": { + "id": "00000000-0000-0000-0000-000000000076", + "tabs": { + "flow": { + "id": "00000000-0000-0000-0000-000000000077", + "widgets": { + "workflow": { + "id": "00000000-0000-0000-0000-000000000078", + }, + }, + }, + }, + }, + "workflowRunRecordPage": { + "id": "00000000-0000-0000-0000-000000000084", + "tabs": { + "flow": { + "id": "00000000-0000-0000-0000-000000000087", + "widgets": { + "workflowRun": { + "id": "00000000-0000-0000-0000-000000000088", + }, + }, + }, + "home": { + "id": "00000000-0000-0000-0000-000000000085", + "widgets": { + "fields": { + "id": "00000000-0000-0000-0000-000000000086", + }, + }, + }, + }, + }, + "workflowVersionRecordPage": { + "id": "00000000-0000-0000-0000-000000000079", + "tabs": { + "flow": { + "id": "00000000-0000-0000-0000-000000000082", + "widgets": { + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000083", + }, + }, + }, + "home": { + "id": "00000000-0000-0000-0000-000000000080", + "widgets": { + "fields": { + "id": "00000000-0000-0000-0000-000000000081", }, }, }, 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 index 0e41b0bd67..489a3d172f 100644 --- 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 @@ -1,79 +1,35 @@ 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 WidgetIds = Record; -type StandardPageLayoutTabIds = { - [T in AllStandardPageLayoutTabName]: { - id: string; - widgets: StandardPageLayoutWidgetIds; - }; -}; +type TabIds = Record; -export type StandardPageLayoutMetadataRelatedEntityIds = { - [L in AllStandardPageLayoutName]: { - id: string; - tabs: StandardPageLayoutTabIds; - }; -}; +export type StandardPageLayoutMetadataRelatedEntityIds = Record< + string, + { id: string; tabs: TabIds } +>; -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 computeWidgetIds = (widgets: Record): WidgetIds => { + const widgetIds: WidgetIds = {}; - const widgetNames = Object.keys( - tabDefinition.widgets, - ) as AllStandardPageLayoutWidgetName[]; - - const widgetIds = {} as StandardPageLayoutWidgetIds; - - for (const widgetName of widgetNames) { + for (const widgetName of Object.keys(widgets)) { widgetIds[widgetName] = { id: v4() }; } return widgetIds; }; -const computeStandardPageLayoutTabIds = ({ - layoutName, -}: { - layoutName: L; -}): StandardPageLayoutTabIds => { - const layoutDefinition = STANDARD_PAGE_LAYOUTS[layoutName]; +const computeTabIds = ( + tabs: Record }>, +): TabIds => { + const tabIds: TabIds = {}; - const tabNames = Object.keys( - layoutDefinition.tabs, - ) as AllStandardPageLayoutTabName[]; - - const tabIds = {} as StandardPageLayoutTabIds; - - for (const tabName of tabNames) { - const widgetIds = computeStandardPageLayoutWidgetIds({ - layoutName, - tabName, - }); - - tabIds[tabName] = { + for (const tabTitle of Object.keys(tabs)) { + tabIds[tabTitle] = { id: v4(), - widgets: widgetIds, + widgets: computeWidgetIds(tabs[tabTitle].widgets), }; } @@ -82,18 +38,16 @@ const computeStandardPageLayoutTabIds = ({ export const getStandardPageLayoutMetadataRelatedEntityIds = (): StandardPageLayoutMetadataRelatedEntityIds => { - const result = {} as StandardPageLayoutMetadataRelatedEntityIds; + const result: StandardPageLayoutMetadataRelatedEntityIds = {}; - for (const layoutName of Object.keys( - STANDARD_PAGE_LAYOUTS, - ) as AllStandardPageLayoutName[]) { - const tabIds = computeStandardPageLayoutTabIds({ - layoutName, - }); + for (const layoutName of Object.keys(STANDARD_PAGE_LAYOUTS)) { + const layout = STANDARD_PAGE_LAYOUTS[ + layoutName as keyof typeof STANDARD_PAGE_LAYOUTS + ] as { tabs: Record }> }; result[layoutName] = { id: v4(), - tabs: tabIds, + tabs: computeTabIds(layout.tabs), }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/index.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/index.ts new file mode 100644 index 0000000000..e31b3a5b90 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/index.ts @@ -0,0 +1,10 @@ +export { STANDARD_COMPANY_PAGE_LAYOUT_CONFIG } from './standard-company-page-layout.config'; +export { STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG } from './standard-dashboard-page-layout.config'; +export { STANDARD_NOTE_PAGE_LAYOUT_CONFIG } from './standard-note-page-layout.config'; +export { STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG } from './standard-opportunity-page-layout.config'; +export { STANDARD_PERSON_PAGE_LAYOUT_CONFIG } from './standard-person-page-layout.config'; +export { STANDARD_TASK_PAGE_LAYOUT_CONFIG } from './standard-task-page-layout.config'; +export { STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG } from './standard-workflow-page-layout.config'; +export { STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG } from './standard-workflow-run-page-layout.config'; +export { STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG } from './standard-workflow-version-page-layout.config'; +export { type StandardPageLayoutConfig } from './standard-page-layout-config.type'; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-company-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-company-page-layout.config.ts new file mode 100644 index 0000000000..1da1a81ff7 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-company-page-layout.config.ts @@ -0,0 +1,94 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const COMPANY_PAGE_TABS = { + home: { + universalIdentifier: '20202020-ab01-4001-8001-c0aba11c0101', + ...TAB_PROPS.home, + widgets: { + fields: { + universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0111', + ...WIDGET_PROPS.fields, + }, + }, + }, + timeline: { + universalIdentifier: '20202020-ab01-4001-8001-c0aba11c0102', + ...TAB_PROPS.timeline, + widgets: { + timeline: { + universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0121', + ...WIDGET_PROPS.timeline, + }, + }, + }, + tasks: { + universalIdentifier: '20202020-ab01-4001-8001-c0aba11c0103', + ...TAB_PROPS.tasks, + widgets: { + tasks: { + universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0131', + ...WIDGET_PROPS.tasks, + }, + }, + }, + notes: { + universalIdentifier: '20202020-ab01-4001-8001-c0aba11c0104', + ...TAB_PROPS.notes, + widgets: { + notes: { + universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0141', + ...WIDGET_PROPS.notes, + }, + }, + }, + files: { + universalIdentifier: '20202020-ab01-4001-8001-c0aba11c0105', + ...TAB_PROPS.files, + widgets: { + files: { + universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0151', + ...WIDGET_PROPS.files, + }, + }, + }, + emails: { + universalIdentifier: '20202020-ab01-4001-8001-c0aba11c0106', + ...TAB_PROPS.emails, + widgets: { + emails: { + universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0161', + ...WIDGET_PROPS.emails, + }, + }, + }, + calendar: { + universalIdentifier: '20202020-ab01-4001-8001-c0aba11c0107', + ...TAB_PROPS.calendar, + widgets: { + calendar: { + universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0171', + ...WIDGET_PROPS.calendar, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_COMPANY_PAGE_LAYOUT_CONFIG = { + layoutName: 'companyRecordPage', + name: 'Default Company Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.company, + universalIdentifier: '20202020-a101-4001-8001-c0aba11c0001', + defaultTabUniversalIdentifier: COMPANY_PAGE_TABS.home.universalIdentifier, + tabs: COMPANY_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-dashboard-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-dashboard-page-layout.config.ts new file mode 100644 index 0000000000..515361ec21 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-dashboard-page-layout.config.ts @@ -0,0 +1,53 @@ +import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const DASHBOARD_PAGE_TABS = { + tab1: { + universalIdentifier: '20202020-d011-4d11-8d11-da5ab0a01001', + title: 'Tab 1', + position: 0, + icon: null, + layoutMode: PageLayoutTabLayoutMode.GRID, + 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 satisfies Record; + +export const STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG = { + layoutName: 'myFirstDashboard', + name: 'My First Dashboard', + type: PageLayoutType.DASHBOARD, + objectUniversalIdentifier: null, + universalIdentifier: '20202020-d001-4d01-8d01-da5ab0a00001', + defaultTabUniversalIdentifier: DASHBOARD_PAGE_TABS.tab1.universalIdentifier, + tabs: DASHBOARD_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-note-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-note-page-layout.config.ts new file mode 100644 index 0000000000..298376ef29 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-note-page-layout.config.ts @@ -0,0 +1,68 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const NOTE_PAGE_TABS = { + home: { + universalIdentifier: '20202020-ab04-4004-8004-a0be5a11a401', + ...TAB_PROPS.home, + widgets: { + fields: { + universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a411', + ...WIDGET_PROPS.fields, + }, + richText: { + universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a412', + ...WIDGET_PROPS.richText, + }, + }, + }, + note: { + universalIdentifier: '20202020-ab04-4004-8004-a0be5a11a402', + ...TAB_PROPS.note, + widgets: { + richText: { + universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a421', + ...WIDGET_PROPS.richText, + }, + }, + }, + timeline: { + universalIdentifier: '20202020-ab04-4004-8004-a0be5a11a403', + ...TAB_PROPS.timeline, + widgets: { + timeline: { + universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a431', + ...WIDGET_PROPS.timeline, + }, + }, + }, + files: { + universalIdentifier: '20202020-ab04-4004-8004-a0be5a11a404', + ...TAB_PROPS.files, + widgets: { + files: { + universalIdentifier: '20202020-ac04-4004-8004-a0be5a11a441', + ...WIDGET_PROPS.files, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_NOTE_PAGE_LAYOUT_CONFIG = { + layoutName: 'noteRecordPage', + name: 'Default Note Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.note, + universalIdentifier: '20202020-a104-4004-8004-a0be5a11a004', + defaultTabUniversalIdentifier: NOTE_PAGE_TABS.home.universalIdentifier, + tabs: NOTE_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-opportunity-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-opportunity-page-layout.config.ts new file mode 100644 index 0000000000..bb729088be --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-opportunity-page-layout.config.ts @@ -0,0 +1,94 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const OPPORTUNITY_PAGE_TABS = { + home: { + universalIdentifier: '20202020-ab03-4003-8003-0aa0b1ca1301', + ...TAB_PROPS.home, + widgets: { + fields: { + universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1311', + ...WIDGET_PROPS.fields, + }, + }, + }, + timeline: { + universalIdentifier: '20202020-ab03-4003-8003-0aa0b1ca1302', + ...TAB_PROPS.timeline, + widgets: { + timeline: { + universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1321', + ...WIDGET_PROPS.timeline, + }, + }, + }, + tasks: { + universalIdentifier: '20202020-ab03-4003-8003-0aa0b1ca1303', + ...TAB_PROPS.tasks, + widgets: { + tasks: { + universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1331', + ...WIDGET_PROPS.tasks, + }, + }, + }, + notes: { + universalIdentifier: '20202020-ab03-4003-8003-0aa0b1ca1304', + ...TAB_PROPS.notes, + widgets: { + notes: { + universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1341', + ...WIDGET_PROPS.notes, + }, + }, + }, + files: { + universalIdentifier: '20202020-ab03-4003-8003-0aa0b1ca1305', + ...TAB_PROPS.files, + widgets: { + files: { + universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1351', + ...WIDGET_PROPS.files, + }, + }, + }, + emails: { + universalIdentifier: '20202020-ab03-4003-8003-0aa0b1ca1306', + ...TAB_PROPS.emails, + widgets: { + emails: { + universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1361', + ...WIDGET_PROPS.emails, + }, + }, + }, + calendar: { + universalIdentifier: '20202020-ab03-4003-8003-0aa0b1ca1307', + ...TAB_PROPS.calendar, + widgets: { + calendar: { + universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1371', + ...WIDGET_PROPS.calendar, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG = { + layoutName: 'opportunityRecordPage', + name: 'Default Opportunity Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.opportunity, + universalIdentifier: '20202020-a103-4003-8003-0aa0b1ca1003', + defaultTabUniversalIdentifier: OPPORTUNITY_PAGE_TABS.home.universalIdentifier, + tabs: OPPORTUNITY_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type.ts new file mode 100644 index 0000000000..8733f5774c --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type.ts @@ -0,0 +1,31 @@ +import { type PageLayoutTabLayoutMode } from 'twenty-shared/types'; + +import { type PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum'; +import { type GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type'; + +export type StandardPageLayoutWidgetConfig = { + universalIdentifier: string; + title?: string; + type?: WidgetType; + gridPosition?: GridPosition; +}; + +export type StandardPageLayoutTabConfig = { + universalIdentifier: string; + title: string; + position: number; + icon: string | null; + layoutMode: PageLayoutTabLayoutMode; + widgets: Record; +}; + +export type StandardPageLayoutConfig = { + layoutName: string; + name: string; + type: PageLayoutType; + objectUniversalIdentifier: string | null; + universalIdentifier: string; + defaultTabUniversalIdentifier: string | null; + tabs: Record; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-person-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-person-page-layout.config.ts new file mode 100644 index 0000000000..e99dd7ccdd --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-person-page-layout.config.ts @@ -0,0 +1,94 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const PERSON_PAGE_TABS = { + home: { + universalIdentifier: '20202020-ab02-4002-8002-ae0a1ea11201', + ...TAB_PROPS.home, + widgets: { + fields: { + universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11211', + ...WIDGET_PROPS.fields, + }, + }, + }, + timeline: { + universalIdentifier: '20202020-ab02-4002-8002-ae0a1ea11202', + ...TAB_PROPS.timeline, + widgets: { + timeline: { + universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11221', + ...WIDGET_PROPS.timeline, + }, + }, + }, + tasks: { + universalIdentifier: '20202020-ab02-4002-8002-ae0a1ea11203', + ...TAB_PROPS.tasks, + widgets: { + tasks: { + universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11231', + ...WIDGET_PROPS.tasks, + }, + }, + }, + notes: { + universalIdentifier: '20202020-ab02-4002-8002-ae0a1ea11204', + ...TAB_PROPS.notes, + widgets: { + notes: { + universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11241', + ...WIDGET_PROPS.notes, + }, + }, + }, + files: { + universalIdentifier: '20202020-ab02-4002-8002-ae0a1ea11205', + ...TAB_PROPS.files, + widgets: { + files: { + universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11251', + ...WIDGET_PROPS.files, + }, + }, + }, + emails: { + universalIdentifier: '20202020-ab02-4002-8002-ae0a1ea11206', + ...TAB_PROPS.emails, + widgets: { + emails: { + universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11261', + ...WIDGET_PROPS.emails, + }, + }, + }, + calendar: { + universalIdentifier: '20202020-ab02-4002-8002-ae0a1ea11207', + ...TAB_PROPS.calendar, + widgets: { + calendar: { + universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11271', + ...WIDGET_PROPS.calendar, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_PERSON_PAGE_LAYOUT_CONFIG = { + layoutName: 'personRecordPage', + name: 'Default Person Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.person, + universalIdentifier: '20202020-a102-4002-8002-ae0a1ea11002', + defaultTabUniversalIdentifier: PERSON_PAGE_TABS.home.universalIdentifier, + tabs: PERSON_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-task-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-task-page-layout.config.ts new file mode 100644 index 0000000000..c8b16a27bb --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-task-page-layout.config.ts @@ -0,0 +1,68 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const TASK_PAGE_TABS = { + home: { + universalIdentifier: '20202020-ab05-4005-8005-ba5ca11a5501', + ...TAB_PROPS.home, + widgets: { + fields: { + universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5511', + ...WIDGET_PROPS.fields, + }, + richText: { + universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5512', + ...WIDGET_PROPS.richText, + }, + }, + }, + note: { + universalIdentifier: '20202020-ab05-4005-8005-ba5ca11a5502', + ...TAB_PROPS.note, + widgets: { + richText: { + universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5521', + ...WIDGET_PROPS.richText, + }, + }, + }, + timeline: { + universalIdentifier: '20202020-ab05-4005-8005-ba5ca11a5503', + ...TAB_PROPS.timeline, + widgets: { + timeline: { + universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5531', + ...WIDGET_PROPS.timeline, + }, + }, + }, + files: { + universalIdentifier: '20202020-ab05-4005-8005-ba5ca11a5504', + ...TAB_PROPS.files, + widgets: { + files: { + universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5541', + ...WIDGET_PROPS.files, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_TASK_PAGE_LAYOUT_CONFIG = { + layoutName: 'taskRecordPage', + name: 'Default Task Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.task, + universalIdentifier: '20202020-a105-4005-8005-ba5ca11a1005', + defaultTabUniversalIdentifier: TASK_PAGE_TABS.home.universalIdentifier, + tabs: TASK_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-page-layout.config.ts new file mode 100644 index 0000000000..86f76a4bc1 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-page-layout.config.ts @@ -0,0 +1,34 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const WORKFLOW_PAGE_TABS = { + flow: { + universalIdentifier: '20202020-ab06-4006-8006-a0bcf10a6601', + ...TAB_PROPS.flow, + widgets: { + workflow: { + universalIdentifier: '20202020-ac06-4006-8006-a0bcf10a6611', + ...WIDGET_PROPS.workflow, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG = { + layoutName: 'workflowRecordPage', + name: 'Default Workflow Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.workflow, + universalIdentifier: '20202020-a106-4006-8006-a0bcf10aa006', + defaultTabUniversalIdentifier: WORKFLOW_PAGE_TABS.flow.universalIdentifier, + tabs: WORKFLOW_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-run-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-run-page-layout.config.ts new file mode 100644 index 0000000000..a64d043d1b --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-run-page-layout.config.ts @@ -0,0 +1,45 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const WORKFLOW_RUN_PAGE_TABS = { + home: { + universalIdentifier: '20202020-ab08-4008-8008-a0bcf10a8801', + ...TAB_PROPS.home, + widgets: { + fields: { + universalIdentifier: '20202020-ac08-4008-8008-a0bcf10a8811', + ...WIDGET_PROPS.fields, + }, + }, + }, + flow: { + universalIdentifier: '20202020-ab08-4008-8008-a0bcf10a8802', + ...TAB_PROPS.flowSecondary, + widgets: { + workflowRun: { + universalIdentifier: '20202020-ac08-4008-8008-a0bcf10a8821', + ...WIDGET_PROPS.workflowRun, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG = { + layoutName: 'workflowRunRecordPage', + name: 'Default Workflow Run Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.workflowRun, + universalIdentifier: '20202020-a108-4008-8008-a0bcf10ac008', + defaultTabUniversalIdentifier: + WORKFLOW_RUN_PAGE_TABS.flow.universalIdentifier, + tabs: WORKFLOW_RUN_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-version-page-layout.config.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-version-page-layout.config.ts new file mode 100644 index 0000000000..064289f25c --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-workflow-version-page-layout.config.ts @@ -0,0 +1,45 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; +import { + TAB_PROPS, + WIDGET_PROPS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template'; +import { + type StandardPageLayoutConfig, + type StandardPageLayoutTabConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type'; + +const WORKFLOW_VERSION_PAGE_TABS = { + home: { + universalIdentifier: '20202020-ab07-4007-8007-a0bcf10a7701', + ...TAB_PROPS.home, + widgets: { + fields: { + universalIdentifier: '20202020-ac07-4007-8007-a0bcf10a7711', + ...WIDGET_PROPS.fields, + }, + }, + }, + flow: { + universalIdentifier: '20202020-ab07-4007-8007-a0bcf10a7702', + ...TAB_PROPS.flowSecondary, + widgets: { + workflowVersion: { + universalIdentifier: '20202020-ac07-4007-8007-a0bcf10a7721', + ...WIDGET_PROPS.workflowVersion, + }, + }, + }, +} as const satisfies Record; + +export const STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG = { + layoutName: 'workflowVersionRecordPage', + name: 'Default Workflow Version Layout', + type: PageLayoutType.RECORD_PAGE, + objectUniversalIdentifier: STANDARD_OBJECT_IDS.workflowVersion, + universalIdentifier: '20202020-a107-4007-8007-a0bcf10ab007', + defaultTabUniversalIdentifier: + WORKFLOW_VERSION_PAGE_TABS.flow.universalIdentifier, + tabs: WORKFLOW_VERSION_PAGE_TABS, +} as const satisfies StandardPageLayoutConfig; 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 index bbe9f0325f..b6d0e5a92c 100644 --- 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 @@ -2,20 +2,66 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti 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'; +import { + STANDARD_PAGE_LAYOUTS, + STANDARD_RECORD_PAGE_LAYOUTS, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type StandardPageLayoutTabDefinition } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types'; +import { + type CreateStandardPageLayoutTabArgs, + createStandardPageLayoutTabFlatMetadata, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/create-standard-page-layout-tab-flat-metadata.util'; export type BuildStandardFlatPageLayoutTabMetadataMapsArgs = Omit< CreateStandardPageLayoutTabArgs, 'context' ->; +> & { + shouldIncludeRecordPageLayouts?: boolean; +}; -export const buildStandardFlatPageLayoutTabMetadataMaps = ( - args: BuildStandardFlatPageLayoutTabMetadataMapsArgs, -): FlatEntityMaps => { - const allPageLayoutTabMetadatas: FlatPageLayoutTab[] = Object.values( - STANDARD_FLAT_PAGE_LAYOUT_TAB_BUILDERS_BY_LAYOUT_NAME, - ).flatMap((builder) => builder(args)); +export const buildStandardFlatPageLayoutTabMetadataMaps = ({ + now, + workspaceId, + twentyStandardApplicationId, + standardPageLayoutMetadataRelatedEntityIds, + shouldIncludeRecordPageLayouts, +}: BuildStandardFlatPageLayoutTabMetadataMapsArgs): FlatEntityMaps => { + const allPageLayoutTabMetadatas: FlatPageLayoutTab[] = []; + const recordPageLayoutNames = Object.keys(STANDARD_RECORD_PAGE_LAYOUTS); + + for (const layoutName of Object.keys(STANDARD_PAGE_LAYOUTS)) { + if ( + !shouldIncludeRecordPageLayouts && + recordPageLayoutNames.includes(layoutName) + ) { + continue; + } + + const layout = STANDARD_PAGE_LAYOUTS[ + layoutName as keyof typeof STANDARD_PAGE_LAYOUTS + ] as { tabs: Record }; + + for (const tabTitle of Object.keys(layout.tabs)) { + const tab = layout.tabs[tabTitle]; + + allPageLayoutTabMetadatas.push( + createStandardPageLayoutTabFlatMetadata({ + now, + workspaceId, + twentyStandardApplicationId, + standardPageLayoutMetadataRelatedEntityIds, + context: { + layoutName, + tabTitle, + title: tab.title, + position: tab.position, + icon: tab.icon, + layoutMode: tab.layoutMode, + }, + }), + ); + } + } let flatPageLayoutTabMaps = createEmptyFlatEntityMaps(); 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 index ce5a61e1a8..3e13d49b37 100644 --- 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 @@ -1,57 +1,47 @@ -import { isDefined } from 'class-validator'; -import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; +import { type PageLayoutTabLayoutMode } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; 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 StandardPageLayoutTabDefinition } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types'; 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; +export type CreateStandardPageLayoutTabContext = { + layoutName: string; + tabTitle: string; title: string; position: number; + icon: string | null; + layoutMode: PageLayoutTabLayoutMode; }; -export type CreateStandardPageLayoutTabArgs< - L extends AllStandardPageLayoutName = AllStandardPageLayoutName, -> = { +export type CreateStandardPageLayoutTabArgs = { now: string; workspaceId: string; twentyStandardApplicationId: string; standardPageLayoutMetadataRelatedEntityIds: StandardPageLayoutMetadataRelatedEntityIds; - context: CreateStandardPageLayoutTabContext; + context: CreateStandardPageLayoutTabContext; }; -export const createStandardPageLayoutTabFlatMetadata = < - L extends AllStandardPageLayoutName, ->({ - context: { layoutName, tabName, title, position }, +export const createStandardPageLayoutTabFlatMetadata = ({ + context: { layoutName, tabTitle, title, position, icon, layoutMode }, workspaceId, twentyStandardApplicationId, standardPageLayoutMetadataRelatedEntityIds, now, -}: CreateStandardPageLayoutTabArgs): FlatPageLayoutTab => { +}: CreateStandardPageLayoutTabArgs): FlatPageLayoutTab => { const layoutIds = standardPageLayoutMetadataRelatedEntityIds[layoutName]; - // @ts-expect-error ignore - const tabDefinition = STANDARD_PAGE_LAYOUTS[layoutName].tabs[tabName] as { - universalIdentifier: string; - }; + const layout = STANDARD_PAGE_LAYOUTS[ + layoutName as keyof typeof STANDARD_PAGE_LAYOUTS + ] as { tabs: Record }; + const tabDefinition = layout.tabs[tabTitle]; if (!isDefined(tabDefinition)) { - throw new Error( - `Invalid configuration ${layoutName} ${tabName.toString()}`, - ); + throw new Error(`Invalid configuration ${layoutName} ${tabTitle}`); } - const tabIds = layoutIds.tabs[tabName]; - - const widgetIds = Object.values(tabIds.widgets).map( - (widget: { id: string }) => widget.id, - ); + const tabIds = layoutIds.tabs[tabTitle]; + const widgetIds = Object.values(tabIds.widgets).map((widget) => widget.id); return { id: tabIds.id, @@ -65,7 +55,7 @@ export const createStandardPageLayoutTabFlatMetadata = < createdAt: now, updatedAt: now, deletedAt: null, - icon: null, - layoutMode: PageLayoutTabLayoutMode.GRID, + icon, + layoutMode, }; }; 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 deleted file mode 100644 index a62500da77..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/standard-flat-page-layout-tab-builders.constant.ts +++ /dev/null @@ -1,27 +0,0 @@ -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 index bed9eac75d..e5bd9c3b42 100644 --- 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 @@ -2,20 +2,115 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti 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'; +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 { STANDARD_RECORD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; +import { type StandardRecordPageLayoutDefinition } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types'; +import { computeMyFirstDashboardWidgets } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/compute-my-first-dashboard-widgets.util'; +import { + type CreateStandardPageLayoutWidgetArgs, + createStandardPageLayoutWidgetFlatMetadata, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util'; +import { findObjectNameByUniversalIdentifier } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/create-standard-page-layout-flat-metadata.util'; export type BuildStandardFlatPageLayoutWidgetMetadataMapsArgs = Omit< CreateStandardPageLayoutWidgetArgs, 'context' ->; +> & { + shouldIncludeRecordPageLayouts?: boolean; +}; + +const RECORD_PAGE_LAYOUT_WIDGET_TYPES = [ + WidgetType.FIELDS, + WidgetType.FIELD, + WidgetType.STANDALONE_RICH_TEXT, + WidgetType.TIMELINE, + WidgetType.TASKS, + WidgetType.NOTES, + WidgetType.FILES, + WidgetType.EMAILS, + WidgetType.CALENDAR, + WidgetType.FIELD_RICH_TEXT, +]; + +const computeRecordPageWidgets = ({ + now, + workspaceId, + twentyStandardApplicationId, + standardObjectMetadataRelatedEntityIds, + standardPageLayoutMetadataRelatedEntityIds, +}: BuildStandardFlatPageLayoutWidgetMetadataMapsArgs): FlatPageLayoutWidget[] => { + const allWidgets: FlatPageLayoutWidget[] = []; + + for (const layoutName of Object.keys(STANDARD_RECORD_PAGE_LAYOUTS)) { + const layout = STANDARD_RECORD_PAGE_LAYOUTS[ + layoutName as keyof typeof STANDARD_RECORD_PAGE_LAYOUTS + ] as StandardRecordPageLayoutDefinition; + + let layoutObjectMetadataId: string | null = null; + + if (layout.objectUniversalIdentifier) { + const objectName = findObjectNameByUniversalIdentifier( + layout.objectUniversalIdentifier, + ); + + if (objectName) { + layoutObjectMetadataId = + standardObjectMetadataRelatedEntityIds[ + objectName as keyof typeof standardObjectMetadataRelatedEntityIds + ]?.id ?? null; + } + } + + for (const tabTitle of Object.keys(layout.tabs)) { + const tab = layout.tabs[tabTitle]; + + for (const widgetName of Object.keys(tab.widgets)) { + const widget = tab.widgets[widgetName]; + + const objectMetadataId = RECORD_PAGE_LAYOUT_WIDGET_TYPES.includes( + widget.type, + ) + ? layoutObjectMetadataId + : null; + + allWidgets.push( + createStandardPageLayoutWidgetFlatMetadata({ + now, + workspaceId, + twentyStandardApplicationId, + standardObjectMetadataRelatedEntityIds, + standardPageLayoutMetadataRelatedEntityIds, + context: { + layoutName, + tabTitle, + widgetName, + title: widget.title, + type: widget.type, + gridPosition: widget.gridPosition, + configuration: { + configurationType: WidgetConfigurationType.FIELDS, + }, + objectMetadataId, + }, + }), + ); + } + } + } + + return allWidgets; +}; export const buildStandardFlatPageLayoutWidgetMetadataMaps = ( args: BuildStandardFlatPageLayoutWidgetMetadataMapsArgs, ): FlatEntityMaps => { - const allWidgetMetadatas: FlatPageLayoutWidget[] = Object.values( - STANDARD_FLAT_PAGE_LAYOUT_WIDGET_BUILDERS, - ).flatMap((builder) => builder(args)); + const allWidgetMetadatas: FlatPageLayoutWidget[] = [ + ...computeMyFirstDashboardWidgets(args), + ...(args.shouldIncludeRecordPageLayouts + ? computeRecordPageWidgets(args) + : []), + ]; let flatPageLayoutWidgetMaps = createEmptyFlatEntityMaps(); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/compute-my-first-dashboard-widgets.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/compute-my-first-dashboard-widgets.util.ts new file mode 100644 index 0000000000..359e748b5b --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/compute-my-first-dashboard-widgets.util.ts @@ -0,0 +1,504 @@ +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 CreateStandardPageLayoutWidgetArgs, + createStandardPageLayoutWidgetFlatMetadata, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/create-standard-page-layout-widget-flat-metadata.util'; + +type DashboardWidgetBuilderArgs = Omit< + CreateStandardPageLayoutWidgetArgs, + 'context' +>; + +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', + }, + ], + }; +}; + +const createWelcomeRichText = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => + createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); + +const createDealsByCompany = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); +}; + +const createPipelineValueByStage = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); +}; + +const createRevenueTimeline = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); +}; + +const createOpportunitiesByOwner = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); +}; + +const createStockMarketIframe = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => + createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); + +const createDealsCreatedThisMonth = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); +}; + +const createDealValueCreatedThisMonth = ({ + args, +}: { + args: DashboardWidgetBuilderArgs; +}): FlatPageLayoutWidget => { + const opportunityFields = + args.standardObjectMetadataRelatedEntityIds.opportunity.fields; + const opportunityObjectId = + args.standardObjectMetadataRelatedEntityIds.opportunity.id; + + return createStandardPageLayoutWidgetFlatMetadata({ + ...args, + context: { + layoutName: 'myFirstDashboard', + tabTitle: '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, + }, + }); +}; + +export const computeMyFirstDashboardWidgets = ( + args: DashboardWidgetBuilderArgs, +): FlatPageLayoutWidget[] => { + return [ + createWelcomeRichText({ args }), + createDealsByCompany({ args }), + createPipelineValueByStage({ args }), + createRevenueTimeline({ args }), + createOpportunitiesByOwner({ args }), + createStockMarketIframe({ args }), + createDealsCreatedThisMonth({ args }), + createDealValueCreatedThisMonth({ args }), + ]; +}; 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 index ae9f12a9b5..5bfef08850 100644 --- 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 @@ -1,23 +1,21 @@ -import { isDefined } from 'class-validator'; +import { isDefined } from 'twenty-shared/utils'; import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; import { type 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 StandardPageLayoutTabDefinition, + type StandardPageLayoutWidgetDefinition, +} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types'; 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; +export type CreateStandardPageLayoutWidgetContext = { + layoutName: string; + tabTitle: string; + widgetName: string; title: string; type: WidgetType; gridPosition: GridPosition; @@ -25,25 +23,19 @@ export type CreateStandardPageLayoutWidgetContext< objectMetadataId: string | null; }; -export type CreateStandardPageLayoutWidgetArgs< - L extends AllStandardPageLayoutName = AllStandardPageLayoutName, - T extends AllStandardPageLayoutTabName = AllStandardPageLayoutTabName, -> = { +export type CreateStandardPageLayoutWidgetArgs = { now: string; workspaceId: string; twentyStandardApplicationId: string; standardObjectMetadataRelatedEntityIds: StandardObjectMetadataRelatedEntityIds; standardPageLayoutMetadataRelatedEntityIds: StandardPageLayoutMetadataRelatedEntityIds; - context: CreateStandardPageLayoutWidgetContext; + context: CreateStandardPageLayoutWidgetContext; }; -export const createStandardPageLayoutWidgetFlatMetadata = < - L extends AllStandardPageLayoutName, - T extends AllStandardPageLayoutTabName, ->({ +export const createStandardPageLayoutWidgetFlatMetadata = ({ context: { layoutName, - tabName, + tabTitle, widgetName, title, type, @@ -55,22 +47,21 @@ export const createStandardPageLayoutWidgetFlatMetadata = < twentyStandardApplicationId, standardPageLayoutMetadataRelatedEntityIds, now, -}: CreateStandardPageLayoutWidgetArgs): FlatPageLayoutWidget => { +}: CreateStandardPageLayoutWidgetArgs): FlatPageLayoutWidget => { const layoutIds = standardPageLayoutMetadataRelatedEntityIds[layoutName]; - // @ts-expect-error ignore - const widgetDef = STANDARD_PAGE_LAYOUTS[layoutName].tabs[tabName].widgets[ - widgetName - ] as { - universalIdentifier: string; - }; + const layout = STANDARD_PAGE_LAYOUTS[ + layoutName as keyof typeof STANDARD_PAGE_LAYOUTS + ] as { tabs: Record }; + const widgetDef: StandardPageLayoutWidgetDefinition = + layout.tabs[tabTitle].widgets[widgetName]; if (!isDefined(widgetDef)) { throw new Error( - `Invalid configuration ${layoutName} ${tabName.toString()} ${widgetName}`, + `Invalid configuration ${layoutName} ${tabTitle} ${widgetName}`, ); } - const tabIds = layoutIds.tabs[tabName]; + const tabIds = layoutIds.tabs[tabTitle]; const widgetIds = tabIds.widgets[widgetName]; return { 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 deleted file mode 100644 index 0e0020f294..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/standard-flat-page-layout-widget-builders.constant.ts +++ /dev/null @@ -1,464 +0,0 @@ -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 index 41d3b0ba51..6a6429da95 100644 --- 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 @@ -2,20 +2,33 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti 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 { STANDARD_RECORD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant'; 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' ->; +> & { + shouldIncludeRecordPageLayouts?: boolean; +}; export const buildStandardFlatPageLayoutMetadataMaps = ( args: BuildStandardFlatPageLayoutMetadataMapsArgs, ): FlatEntityMaps => { - const allPageLayoutMetadatas: FlatPageLayout[] = Object.values( + const recordPageLayoutNames = Object.keys(STANDARD_RECORD_PAGE_LAYOUTS); + + const layoutEntries = Object.entries( STANDARD_FLAT_PAGE_LAYOUT_BUILDERS_BY_LAYOUT_NAME, - ).map((builder) => builder(args)); + ).filter( + ([layoutName]) => + args.shouldIncludeRecordPageLayouts || + !recordPageLayoutNames.includes(layoutName), + ); + + const allPageLayoutMetadatas: FlatPageLayout[] = layoutEntries.map( + ([, builder]) => builder(args), + ); let flatPageLayoutMaps = createEmptyFlatEntityMaps(); 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 index 63ea1ab1bd..534293a178 100644 --- 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 @@ -1,36 +1,114 @@ +import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; +import { isDefined } from 'twenty-shared/utils'; + 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 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 CreateStandardPageLayoutContext = { - layoutName: AllStandardPageLayoutName; + layoutName: string; name: string; type: PageLayoutType; - objectMetadataId: string | null; + objectUniversalIdentifier: string | null; + defaultTabUniversalIdentifier: string | null; }; export type CreateStandardPageLayoutArgs = { now: string; workspaceId: string; twentyStandardApplicationId: string; + standardObjectMetadataRelatedEntityIds: StandardObjectMetadataRelatedEntityIds; standardPageLayoutMetadataRelatedEntityIds: StandardPageLayoutMetadataRelatedEntityIds; context: CreateStandardPageLayoutContext; }; +export const findObjectNameByUniversalIdentifier = ( + objectUniversalIdentifier: string, +): string => { + for (const [objectName, objectId] of Object.entries(STANDARD_OBJECT_IDS)) { + if (objectId === objectUniversalIdentifier) { + return objectName; + } + } + + throw new Error( + `Object with universal identifier ${objectUniversalIdentifier} not found`, + ); +}; + +const findTabKeyByUniversalIdentifier = ( + layoutName: string, + tabUniversalIdentifier: string, +): string => { + const layout = + STANDARD_PAGE_LAYOUTS[layoutName as keyof typeof STANDARD_PAGE_LAYOUTS]; + + if (!isDefined(layout)) { + throw new Error(`Layout with name ${layoutName} not found`); + } + + for (const [tabKey, tab] of Object.entries(layout.tabs)) { + if (tab.universalIdentifier === tabUniversalIdentifier) { + return tabKey; + } + } + + throw new Error( + `Tab with universal identifier ${tabUniversalIdentifier} not found`, + ); +}; + export const createStandardPageLayoutFlatMetadata = ({ - context: { layoutName, name, type, objectMetadataId }, + context: { + layoutName, + name, + type, + objectUniversalIdentifier, + defaultTabUniversalIdentifier, + }, workspaceId, twentyStandardApplicationId, + standardObjectMetadataRelatedEntityIds, standardPageLayoutMetadataRelatedEntityIds, now, }: CreateStandardPageLayoutArgs): FlatPageLayout => { - const universalIdentifier = - STANDARD_PAGE_LAYOUTS[layoutName].universalIdentifier; + const layout = + STANDARD_PAGE_LAYOUTS[layoutName as keyof typeof STANDARD_PAGE_LAYOUTS]; + const universalIdentifier = layout.universalIdentifier; + const layoutIds = standardPageLayoutMetadataRelatedEntityIds[layoutName]; + + let objectMetadataId: string | null = null; + + if (objectUniversalIdentifier) { + const objectName = findObjectNameByUniversalIdentifier( + objectUniversalIdentifier, + ); + + if (isDefined(objectName)) { + objectMetadataId = + standardObjectMetadataRelatedEntityIds[ + objectName as keyof typeof standardObjectMetadataRelatedEntityIds + ]?.id ?? null; + } + } + + let defaultTabToFocusOnMobileAndSidePanelId: string | null = null; + + if (defaultTabUniversalIdentifier) { + const tabKey = findTabKeyByUniversalIdentifier( + layoutName, + defaultTabUniversalIdentifier, + ); + + if (isDefined(tabKey)) { + defaultTabToFocusOnMobileAndSidePanelId = layoutIds.tabs[tabKey].id; + } + } return { - id: standardPageLayoutMetadataRelatedEntityIds[layoutName].id, + id: layoutIds.id, universalIdentifier, applicationId: twentyStandardApplicationId, workspaceId, @@ -41,6 +119,6 @@ export const createStandardPageLayoutFlatMetadata = ({ createdAt: now, updatedAt: now, deletedAt: null, - defaultTabToFocusOnMobileAndSidePanelId: null, + defaultTabToFocusOnMobileAndSidePanelId, }; }; 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 index 516938f9eb..46d06cf7f9 100644 --- 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 @@ -1,24 +1,61 @@ 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 { + STANDARD_COMPANY_PAGE_LAYOUT_CONFIG, + STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG, + STANDARD_NOTE_PAGE_LAYOUT_CONFIG, + STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG, + STANDARD_PERSON_PAGE_LAYOUT_CONFIG, + STANDARD_TASK_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG, + type StandardPageLayoutConfig, +} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config'; 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) => +export const STANDARD_PAGE_LAYOUT_CONFIGS = [ + STANDARD_DASHBOARD_PAGE_LAYOUT_CONFIG, + STANDARD_COMPANY_PAGE_LAYOUT_CONFIG, + STANDARD_PERSON_PAGE_LAYOUT_CONFIG, + STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG, + STANDARD_NOTE_PAGE_LAYOUT_CONFIG, + STANDARD_TASK_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_VERSION_PAGE_LAYOUT_CONFIG, + STANDARD_WORKFLOW_RUN_PAGE_LAYOUT_CONFIG, +] as const; + +type BuilderArgs = Omit; + +type BuilderFn = (args: BuilderArgs) => FlatPageLayout; + +const createBuilderFromConfig = ( + config: StandardPageLayoutConfig, +): BuilderFn => { + return (args: BuilderArgs) => createStandardPageLayoutFlatMetadata({ ...args, context: { - layoutName: 'myFirstDashboard', - name: 'My First Dashboard', - type: PageLayoutType.DASHBOARD, - objectMetadataId: null, + layoutName: config.layoutName, + name: config.name, + type: config.type, + objectUniversalIdentifier: config.objectUniversalIdentifier, + defaultTabUniversalIdentifier: config.defaultTabUniversalIdentifier, }, - }), -} satisfies { - [P in AllStandardPageLayoutName]: ( - args: Omit, - ) => FlatPageLayout; + }); }; + +const createBuilders = (): Record => { + return Object.fromEntries( + STANDARD_PAGE_LAYOUT_CONFIGS.map((config) => [ + config.layoutName, + createBuilderFromConfig(config), + ]), + ); +}; + +export const STANDARD_FLAT_PAGE_LAYOUT_BUILDERS_BY_LAYOUT_NAME = + createBuilders(); 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 48e37ce68d..738711e118 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 @@ -21,12 +21,14 @@ export type ComputeTwentyStandardApplicationAllFlatEntityMapsArgs = { now: string; workspaceId: string; twentyStandardApplicationId: string; + shouldIncludeRecordPageLayouts?: boolean; }; export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ now, workspaceId, twentyStandardApplicationId, + shouldIncludeRecordPageLayouts, }: ComputeTwentyStandardApplicationAllFlatEntityMapsArgs): TwentyStandardAllFlatEntityMaps => { const standardObjectMetadataRelatedEntityIds = getStandardObjectMetadataRelatedEntityIds(); @@ -143,7 +145,9 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ now, workspaceId, twentyStandardApplicationId, + standardObjectMetadataRelatedEntityIds, standardPageLayoutMetadataRelatedEntityIds, + shouldIncludeRecordPageLayouts, }); const flatPageLayoutTabMaps = buildStandardFlatPageLayoutTabMetadataMaps({ @@ -151,6 +155,7 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ workspaceId, twentyStandardApplicationId, standardPageLayoutMetadataRelatedEntityIds, + shouldIncludeRecordPageLayouts, }); const flatPageLayoutWidgetMaps = @@ -160,6 +165,7 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({ twentyStandardApplicationId, standardObjectMetadataRelatedEntityIds, standardPageLayoutMetadataRelatedEntityIds, + shouldIncludeRecordPageLayouts, }); const flatNavigationMenuItemMaps = buildStandardFlatNavigationMenuItemMaps({