diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap index f188f56f83..4c168ad231 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap @@ -127,6 +127,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma "name", "type", "objectMetadataUniversalIdentifier", + "defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier", "deletedAt", ], "propertiesToStringify": [], diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index 6ea3b26eb9..4a65381b45 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -228,6 +228,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { toStringify: false, universalProperty: 'objectMetadataUniversalIdentifier', }, + defaultTabToFocusOnMobileAndSidePanelId: { + toStringify: false, + universalProperty: + 'defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier', + }, deletedAt: { toStringify: false, universalProperty: undefined }, }, pageLayoutWidget: { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts index a5384d8e52..8cbd5a102c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts @@ -3,6 +3,7 @@ import { assertUnreachable, isDefined } from 'twenty-shared/utils'; import { type UniversalCreateFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action'; import { type UniversalCreateObjectAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action'; +import { type UniversalCreatePageLayoutAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/types/workspace-migration-page-layout-action.type'; import { type WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration'; export type IdByUniversalIdentifierByMetadataName = { @@ -40,6 +41,25 @@ const buildFieldIdByUniversalIdentifier = ({ return fieldIdByUniversalIdentifier; }; +const buildTabIdByUniversalIdentifier = ({ + action, + pageLayoutTabIdByUniversalIdentifier, +}: { + action: UniversalCreatePageLayoutAction; + pageLayoutTabIdByUniversalIdentifier: Record; +}): Record | undefined => { + const tabIdByUniversalIdentifier = { + ...action.tabIdByUniversalIdentifier, + ...pageLayoutTabIdByUniversalIdentifier, + }; + + if (Object.keys(tabIdByUniversalIdentifier).length === 0) { + return undefined; + } + + return tabIdByUniversalIdentifier; +}; + export const enrichCreateWorkspaceMigrationActionsWithIds = ({ workspaceMigration, idByUniversalIdentifierByMetadataName, @@ -49,6 +69,8 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({ }): WorkspaceMigration => { const fieldMetadataIdByUniversalIdentifier = idByUniversalIdentifierByMetadataName.fieldMetadata; + const pageLayoutTabIdByUniversalIdentifier = + idByUniversalIdentifierByMetadataName.pageLayoutTab; const enrichedActions = workspaceMigration.actions.map((action) => { if (action.type !== 'create') { @@ -60,7 +82,8 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({ if ( !isDefined(idByUniversalIdentifier) && - !isDefined(fieldMetadataIdByUniversalIdentifier) + !isDefined(fieldMetadataIdByUniversalIdentifier) && + !isDefined(pageLayoutTabIdByUniversalIdentifier) ) { return action; } @@ -98,6 +121,25 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({ }), }; } + case 'pageLayout': { + const id = isDefined(idByUniversalIdentifier) + ? idByUniversalIdentifier[action.flatEntity.universalIdentifier] + : undefined; + const tabIdByUniversalIdentifier = isDefined( + pageLayoutTabIdByUniversalIdentifier, + ) + ? buildTabIdByUniversalIdentifier({ + action, + pageLayoutTabIdByUniversalIdentifier, + }) + : undefined; + + return { + ...action, + id, + tabIdByUniversalIdentifier, + }; + } case 'view': case 'viewField': case 'viewGroup': @@ -111,7 +153,6 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({ case 'roleTarget': case 'agent': case 'skill': - case 'pageLayout': case 'pageLayoutWidget': case 'pageLayoutTab': case 'commandMenuItem': diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/types/workspace-migration-page-layout-action.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/types/workspace-migration-page-layout-action.type.ts index 43575bd74a..75ccfcbfb5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/types/workspace-migration-page-layout-action.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/types/workspace-migration-page-layout-action.type.ts @@ -9,7 +9,9 @@ export type FlatCreatePageLayoutAction = BaseFlatCreateWorkspaceMigrationAction<'pageLayout'>; export type UniversalCreatePageLayoutAction = - BaseUniversalCreateWorkspaceMigrationAction<'pageLayout'>; + BaseUniversalCreateWorkspaceMigrationAction<'pageLayout'> & { + tabIdByUniversalIdentifier?: Record; + }; export type FlatUpdatePageLayoutAction = BaseFlatUpdateWorkspaceMigrationAction<'pageLayout'>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts index dc41109f53..d403c03c59 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts @@ -1,15 +1,21 @@ import { Injectable } from '@nestjs/common'; +import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreatePageLayoutAction, UniversalCreatePageLayoutAction, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/types/workspace-migration-page-layout-action.type'; +import { findPageLayoutTabIdInCreatePageLayoutContext } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/utils/find-page-layout-tab-id-in-create-page-layout-context.util'; import { WorkspaceMigrationActionRunnerArgs, WorkspaceMigrationActionRunnerContext, @@ -30,12 +36,40 @@ export class CreatePageLayoutActionHandlerService extends WorkspaceMigrationRunn flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { - const { objectMetadataId, defaultTabToFocusOnMobileAndSidePanelId } = - resolveUniversalRelationIdentifiersToIds({ - flatEntityMaps: allFlatEntityMaps, - metadataName: action.metadataName, - universalForeignKeyValues: action.flatEntity, - }); + const { objectMetadataId } = resolveUniversalRelationIdentifiersToIds< + 'pageLayout', + 'objectMetadataUniversalIdentifier' + >({ + flatEntityMaps: allFlatEntityMaps, + metadataName: 'pageLayout', + universalForeignKeyValues: { + objectMetadataUniversalIdentifier: + action.flatEntity.objectMetadataUniversalIdentifier, + }, + }); + + let defaultTabToFocusOnMobileAndSidePanelId: string | null = null; + + const defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier = + action.flatEntity + .defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier; + + if (isDefined(defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier)) { + defaultTabToFocusOnMobileAndSidePanelId = + findPageLayoutTabIdInCreatePageLayoutContext({ + universalIdentifier: + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier, + tabIdByUniversalIdentifier: action.tabIdByUniversalIdentifier, + flatPageLayoutTabMaps: allFlatEntityMaps.flatPageLayoutTabMaps, + }); + + if (!isDefined(defaultTabToFocusOnMobileAndSidePanelId)) { + throw new FlatEntityMapsException( + `Could not resolve defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier to defaultTabToFocusOnMobileAndSidePanelId: no pageLayoutTab found for universal identifier ${defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + } return { ...action, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/update-page-layout-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/update-page-layout-action-handler.service.ts index 4bff4dd810..7bb5b1e41d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/update-page-layout-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/update-page-layout-action-handler.service.ts @@ -1,8 +1,16 @@ import { Injectable } from '@nestjs/common'; +import { isDefined } from 'twenty-shared/utils'; + import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { type FlatEntityUpdate } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-update.type'; import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; +import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity'; import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util'; import { @@ -29,17 +37,41 @@ export class UpdatePageLayoutActionHandlerService extends WorkspaceMigrationRunn universalIdentifier: action.universalIdentifier, }); - const update = resolveUniversalUpdateRelationIdentifiersToIds({ - metadataName: 'pageLayout', - universalUpdate: action.update, - allFlatEntityMaps, - }); + const { + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier, + ...restUpdate + } = action.update; + + const transpiledUpdate: FlatEntityUpdate<'pageLayout'> = + resolveUniversalUpdateRelationIdentifiersToIds({ + metadataName: 'pageLayout', + universalUpdate: restUpdate, + allFlatEntityMaps, + }); + + if (isDefined(defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier)) { + const flatPageLayoutTab = findFlatEntityByUniversalIdentifier({ + flatEntityMaps: allFlatEntityMaps.flatPageLayoutTabMaps, + universalIdentifier: + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier, + }); + + if (!isDefined(flatPageLayoutTab)) { + throw new FlatEntityMapsException( + `Could not resolve defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier to defaultTabToFocusOnMobileAndSidePanelId: no pageLayoutTab found for universal identifier ${defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + + transpiledUpdate.defaultTabToFocusOnMobileAndSidePanelId = + flatPageLayoutTab.id; + } return { type: 'update', metadataName: 'pageLayout', entityId: flatPageLayout.id, - update, + update: transpiledUpdate, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/utils/find-page-layout-tab-id-in-create-page-layout-context.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/utils/find-page-layout-tab-id-in-create-page-layout-context.util.ts new file mode 100644 index 0000000000..1e495d193e --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/utils/find-page-layout-tab-id-in-create-page-layout-context.util.ts @@ -0,0 +1,24 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; + +export const findPageLayoutTabIdInCreatePageLayoutContext = ({ + universalIdentifier, + tabIdByUniversalIdentifier, + flatPageLayoutTabMaps, +}: { + universalIdentifier: string; + tabIdByUniversalIdentifier: Record | undefined; + flatPageLayoutTabMaps: AllFlatEntityMaps['flatPageLayoutTabMaps']; +}): string | null => { + const providedId = tabIdByUniversalIdentifier?.[universalIdentifier]; + + if (isDefined(providedId)) { + return providedId; + } + + const existingTab = + flatPageLayoutTabMaps.byUniversalIdentifier[universalIdentifier]; + + return existingTab?.id ?? null; +};