Fix page layout seeding for record page layouts (#17871)

## Context
Fix broken record page layout seeding. This was not detected by the CI
because it doesn't have the env variable yet.

Following the same mechanism as labelIdentifier in object for circular
dependency resolution
This commit is contained in:
Weiko
2026-02-11 17:56:21 +01:00
committed by GitHub
parent b2f7c745f8
commit c15536f9f8
7 changed files with 154 additions and 15 deletions
@@ -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<string, string>;
}): Record<string, string> | 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':
@@ -9,7 +9,9 @@ export type FlatCreatePageLayoutAction =
BaseFlatCreateWorkspaceMigrationAction<'pageLayout'>;
export type UniversalCreatePageLayoutAction =
BaseUniversalCreateWorkspaceMigrationAction<'pageLayout'>;
BaseUniversalCreateWorkspaceMigrationAction<'pageLayout'> & {
tabIdByUniversalIdentifier?: Record<string, string>;
};
export type FlatUpdatePageLayoutAction =
BaseFlatUpdateWorkspaceMigrationAction<'pageLayout'>;
@@ -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<UniversalCreatePageLayoutAction>): Promise<FlatCreatePageLayoutAction> {
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,
@@ -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,
};
}
@@ -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<string, string> | undefined;
flatPageLayoutTabMaps: AllFlatEntityMaps['flatPageLayoutTabMaps'];
}): string | null => {
const providedId = tabIdByUniversalIdentifier?.[universalIdentifier];
if (isDefined(providedId)) {
return providedId;
}
const existingTab =
flatPageLayoutTabMaps.byUniversalIdentifier[universalIdentifier];
return existingTab?.id ?? null;
};