diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-favorites-to-navigation-menu-items.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-favorites-to-navigation-menu-items.command.ts index e4f5dd58d4..f026a92fbb 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-favorites-to-navigation-menu-items.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-favorites-to-navigation-menu-items.command.ts @@ -121,6 +121,8 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen } = await this.migrateFavoriteFolders({ workspaceId, workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + workspaceCustomApplicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, existingFlatNavigationMenuItemMaps, }); @@ -128,7 +130,11 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen await this.migrateFavorites({ workspaceId, twentyStandardApplicationId: twentyStandardFlatApplication.id, + twentyStandardApplicationUniversalIdentifier: + twentyStandardFlatApplication.universalIdentifier, workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + workspaceCustomApplicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, flatObjectMetadataMaps, flatViewMaps, folderIdMapping, @@ -189,10 +195,12 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen private async migrateFavoriteFolders({ workspaceId, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, existingFlatNavigationMenuItemMaps, }: { workspaceId: string; workspaceCustomApplicationId: string; + workspaceCustomApplicationUniversalIdentifier: string; existingFlatNavigationMenuItemMaps: FlatNavigationMenuItemMaps; }): Promise<{ folderIdMapping: Map>; @@ -270,12 +278,17 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen userWorkspaceId, targetRecordId: null, targetObjectMetadataId: null, + targetObjectMetadataUniversalIdentifier: null, viewId: null, + viewUniversalIdentifier: null, folderId: null, + folderUniversalIdentifier: null, name: favoriteFolder.name, position: favoriteFolder.position, workspaceId, applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, createdAt: now, updatedAt: now, }; @@ -297,12 +310,17 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen userWorkspaceId: null, targetRecordId: null, targetObjectMetadataId: null, + targetObjectMetadataUniversalIdentifier: null, viewId: null, + viewUniversalIdentifier: null, folderId: null, + folderUniversalIdentifier: null, name: favoriteFolder.name, position: favoriteFolder.position, workspaceId, applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, createdAt: now, updatedAt: now, }; @@ -325,7 +343,9 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen private async migrateFavorites({ workspaceId, twentyStandardApplicationId, + twentyStandardApplicationUniversalIdentifier, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, flatObjectMetadataMaps, flatViewMaps, folderIdMapping, @@ -334,7 +354,9 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen }: { workspaceId: string; twentyStandardApplicationId: string; + twentyStandardApplicationUniversalIdentifier: string; workspaceCustomApplicationId: string; + workspaceCustomApplicationUniversalIdentifier: string; flatObjectMetadataMaps: FlatEntityMaps; flatViewMaps: FlatEntityMaps; folderIdMapping: Map>; @@ -471,13 +493,18 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen continue; } - const { applicationId, universalIdentifier } = - this.getApplicationIdAndUniversalIdentifierForViewFavorite({ - viewId: favorite.viewId, - flatViewMaps, - twentyStandardApplicationId, - workspaceCustomApplicationId, - }); + const { + applicationId, + applicationUniversalIdentifier, + universalIdentifier, + } = this.getApplicationIdAndUniversalIdentifierForViewFavorite({ + viewId: favorite.viewId, + flatViewMaps, + twentyStandardApplicationId, + twentyStandardApplicationUniversalIdentifier, + workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, + }); const now = new Date().toISOString(); @@ -487,12 +514,17 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen userWorkspaceId, targetRecordId: null, targetObjectMetadataId: null, + targetObjectMetadataUniversalIdentifier: null, viewId: favorite.viewId, + viewUniversalIdentifier: + flatViewMaps.universalIdentifierById[favorite.viewId] ?? null, folderId, + folderUniversalIdentifier: folderId, name: null, position: favorite.position, workspaceId, applicationId, + applicationUniversalIdentifier, createdAt: now, updatedAt: now, }); @@ -525,12 +557,20 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen userWorkspaceId, targetRecordId, targetObjectMetadataId, + targetObjectMetadataUniversalIdentifier: + flatObjectMetadataMaps.universalIdentifierById[ + targetObjectMetadataId + ] ?? null, viewId: null, + viewUniversalIdentifier: null, folderId, + folderUniversalIdentifier: folderId, name: null, position: favorite.position, workspaceId, applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, createdAt: now, updatedAt: now, }); @@ -548,13 +588,21 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen viewId, flatViewMaps, twentyStandardApplicationId, + twentyStandardApplicationUniversalIdentifier, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, }: { viewId: string; flatViewMaps: FlatEntityMaps; twentyStandardApplicationId: string; + twentyStandardApplicationUniversalIdentifier: string; workspaceCustomApplicationId: string; - }): { applicationId: string; universalIdentifier: string } { + workspaceCustomApplicationUniversalIdentifier: string; + }): { + applicationId: string; + applicationUniversalIdentifier: string; + universalIdentifier: string; + } { const flatView = findFlatEntityByIdInFlatEntityMaps({ flatEntityMaps: flatViewMaps, flatEntityId: viewId, @@ -573,10 +621,14 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen return !isDefined(matchingStandardItem) ? { applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, universalIdentifier: uuidv4(), } : { applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + twentyStandardApplicationUniversalIdentifier, universalIdentifier: matchingStandardItem.universalIdentifier, }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts index af69638447..7ae3ea566c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts @@ -106,6 +106,12 @@ export class AgentService { input: CreateAgentInput & { isCustom: boolean }, workspaceId: string, ): Promise { + const { flatApplicationMaps, flatRoleMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatApplicationMaps', + 'flatRoleMaps', + ]); + const { workspaceCustomFlatApplication } = await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( { @@ -113,14 +119,19 @@ export class AgentService { }, ); + const flatApplication = isDefined(input.applicationId) + ? flatApplicationMaps.byId[input.applicationId] + : undefined; + + const resolvedFlatApplication = + flatApplication ?? workspaceCustomFlatApplication; + const { flatAgentToCreate, flatRoleTargetToCreate } = fromCreateAgentInputToFlatAgent({ - createAgentInput: { - ...input, - applicationId: - input.applicationId ?? workspaceCustomFlatApplication.id, - }, + createAgentInput: input, workspaceId, + flatApplication: resolvedFlatApplication, + flatRoleMaps, }); const validateAndBuildResult = @@ -184,10 +195,11 @@ export class AgentService { }, ); - const { flatRoleTargetByAgentIdMaps, flatAgentMaps } = + const { flatRoleTargetByAgentIdMaps, flatAgentMaps, flatRoleMaps } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ 'flatRoleTargetByAgentIdMaps', 'flatAgentMaps', + 'flatRoleMaps', ]); const { @@ -199,6 +211,7 @@ export class AgentService { updateAgentInput: input, flatAgentMaps, flatRoleTargetByAgentIdMaps, + flatRoleMaps, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts index a489d6664d..557ae0ad6c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts @@ -6,18 +6,25 @@ import { } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input'; import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type'; export type FromCreateAgentInputToFlatAgentArgs = { - createAgentInput: CreateAgentInput & { applicationId: string }; + createAgentInput: CreateAgentInput; workspaceId: string; + flatApplication: FlatApplication; + flatRoleMaps: AllFlatEntityMaps['flatRoleMaps']; }; export const fromCreateAgentInputToFlatAgent = ({ createAgentInput: rawCreateAgentInput, workspaceId, + flatApplication, + flatRoleMaps, }: FromCreateAgentInputToFlatAgentArgs): { flatAgentToCreate: FlatAgent; flatRoleTargetToCreate: FlatRoleTarget | null; @@ -25,16 +32,7 @@ export const fromCreateAgentInputToFlatAgent = ({ const { roleId, ...createAgentInput } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( rawCreateAgentInput, - [ - 'name', - 'label', - 'icon', - 'description', - 'prompt', - 'modelId', - 'applicationId', - 'roleId', - ], + ['name', 'label', 'icon', 'description', 'prompt', 'modelId', 'roleId'], ); const createdAt = new Date().toISOString(); @@ -54,7 +52,8 @@ export const fromCreateAgentInputToFlatAgent = ({ workspaceId, isCustom: true, universalIdentifier: v4(), - applicationId: createAgentInput.applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, modelConfiguration: createAgentInput.modelConfiguration ?? null, evaluationInputs: createAgentInput.evaluationInputs ?? [], createdAt, @@ -62,20 +61,31 @@ export const fromCreateAgentInputToFlatAgent = ({ deletedAt: null, }; - const flatRoleTargetToCreate: FlatRoleTarget | null = isDefined(roleId) - ? { - id: v4(), - roleId, - userWorkspaceId: null, - agentId, - apiKeyId: null, - createdAt, - updatedAt: createdAt, - universalIdentifier: v4(), - workspaceId, - applicationId: createAgentInput.applicationId, - } - : null; + let flatRoleTargetToCreate: FlatRoleTarget | null = null; + + if (isDefined(roleId)) { + const { roleUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'roleTarget', + foreignKeyValues: { roleId }, + flatEntityMaps: { flatRoleMaps }, + }); + + flatRoleTargetToCreate = { + id: v4(), + roleId, + roleUniversalIdentifier, + userWorkspaceId: null, + agentId, + apiKeyId: null, + createdAt, + updatedAt: createdAt, + universalIdentifier: v4(), + workspaceId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + }; + } return { flatAgentToCreate, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util.ts index e0ae6d9c27..91f4ce6b4c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util.ts @@ -13,9 +13,11 @@ import { import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input'; import { FLAT_AGENT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant'; import { type FlatAgentMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type'; -import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; import { type FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type'; import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util'; import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; @@ -29,10 +31,12 @@ const computeAgentFlatRoleTargetToUpdate = ({ roleId, flatAgent, flatRoleTargetByAgentIdMaps, + flatRoleMaps, }: { roleId: string | null | undefined; flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps; flatAgent: FlatAgent; + flatRoleMaps: AllFlatEntityMaps['flatRoleMaps']; }): FlatRoleTargetToUpdateCreateDelete => { if (roleId === undefined) { return {}; @@ -51,11 +55,17 @@ const computeAgentFlatRoleTargetToUpdate = ({ return {}; } + const flatRole = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityMaps: flatRoleMaps, + flatEntityId: roleId, + }); + if (isDefined(existingRoleTarget)) { return { flatRoleTargetToUpdate: { ...existingRoleTarget, roleId, + roleUniversalIdentifier: flatRole.universalIdentifier, updatedAt, }, }; @@ -65,6 +75,7 @@ const computeAgentFlatRoleTargetToUpdate = ({ flatRoleTargetToCreate: { id: v4(), roleId, + roleUniversalIdentifier: flatRole.universalIdentifier, userWorkspaceId: null, agentId: flatAgent.id, apiKeyId: null, @@ -73,6 +84,7 @@ const computeAgentFlatRoleTargetToUpdate = ({ universalIdentifier: v4(), workspaceId: flatAgent.workspaceId, applicationId: flatAgent.applicationId, + applicationUniversalIdentifier: flatAgent.applicationUniversalIdentifier, }, }; }; @@ -81,12 +93,13 @@ export type FromUpdateAgentInputToFlatAgentToUpdateArgs = { updateAgentInput: UpdateAgentInput; flatAgentMaps: FlatAgentMaps; flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps; -}; +} & Pick; export const fromUpdateAgentInputToFlatAgentToUpdate = ({ updateAgentInput: rawUpdateAgentInput, flatAgentMaps, flatRoleTargetByAgentIdMaps, + flatRoleMaps, }: FromUpdateAgentInputToFlatAgentToUpdateArgs): { flatAgentToUpdate: FlatAgent; } & FlatRoleTargetToUpdateCreateDelete => { @@ -139,6 +152,7 @@ export const fromUpdateAgentInputToFlatAgentToUpdate = ({ roleId: rawUpdateAgentInput.roleId, flatAgent: existingFlatAgent, flatRoleTargetByAgentIdMaps, + flatRoleMaps, }); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.service.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.service.ts index f1890999e1..f3ba8329c3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.service.ts @@ -88,6 +88,14 @@ export class CommandMenuItemService { input: CreateCommandMenuItemInput, workspaceId: string, ): Promise { + const { flatObjectMetadataMaps, flatFrontComponentMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatObjectMetadataMaps', 'flatFrontComponentMaps'], + }, + ); + const { workspaceCustomFlatApplication } = await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( { workspaceId }, @@ -97,7 +105,9 @@ export class CommandMenuItemService { fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate({ createCommandMenuItemInput: input, workspaceId, - applicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatObjectMetadataMaps, + flatFrontComponentMaps, }); const validateAndBuildResult = @@ -149,11 +159,14 @@ export class CommandMenuItemService { { workspaceId }, ); - const { flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps } = + const { + flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, - flatMapsKeys: ['flatCommandMenuItemMaps'], + flatMapsKeys: ['flatCommandMenuItemMaps', 'flatObjectMetadataMaps'], }, ); @@ -161,6 +174,7 @@ export class CommandMenuItemService { fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow({ flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps, updateCommandMenuItemInput: input, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts index cc8f80a0c3..409fe8fb91 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts @@ -39,9 +39,6 @@ export const transformAgentEntityToFlatAgent = ({ applicationId: agentEntity.applicationId, modelConfiguration: agentEntity.modelConfiguration, evaluationInputs: agentEntity.evaluationInputs, - __universal: { - universalIdentifier: agentEntity.universalIdentifier, - applicationUniversalIdentifier, - }, + applicationUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts index c2de36fa1d..eb21cd6db7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util.ts @@ -72,11 +72,8 @@ export const fromCommandMenuItemEntityToFlatCommandMenuItem = ({ applicationId: commandMenuItemEntity.applicationId, createdAt: commandMenuItemEntity.createdAt.toISOString(), updatedAt: commandMenuItemEntity.updatedAt.toISOString(), - __universal: { - universalIdentifier: commandMenuItemEntity.universalIdentifier, - applicationUniversalIdentifier, - availabilityObjectMetadataUniversalIdentifier, - frontComponentUniversalIdentifier, - }, + applicationUniversalIdentifier, + availabilityObjectMetadataUniversalIdentifier, + frontComponentUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts index 0b27d55e82..47b8ed012b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts @@ -1,6 +1,7 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { CommandMenuItemException, CommandMenuItemExceptionCode, @@ -8,16 +9,23 @@ import { import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input'; import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity'; import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({ createCommandMenuItemInput, workspaceId, - applicationId, + flatApplication, + flatObjectMetadataMaps, + flatFrontComponentMaps, }: { createCommandMenuItemInput: CreateCommandMenuItemInput; workspaceId: string; - applicationId: string; -}): FlatCommandMenuItem => { + flatApplication: FlatApplication; +} & Pick< + AllFlatEntityMaps, + 'flatObjectMetadataMaps' | 'flatFrontComponentMaps' +>): FlatCommandMenuItem => { const hasWorkflowVersionId = isDefined( createCommandMenuItemInput.workflowVersionId, ); @@ -35,11 +43,25 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({ const id = uuidv4(); const now = new Date().toISOString(); + const { + availabilityObjectMetadataUniversalIdentifier, + frontComponentUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'commandMenuItem', + foreignKeyValues: { + availabilityObjectMetadataId: + createCommandMenuItemInput.availabilityObjectMetadataId, + frontComponentId: createCommandMenuItemInput.frontComponentId, + }, + flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps }, + }); + return { id, universalIdentifier: id, workflowVersionId: createCommandMenuItemInput.workflowVersionId ?? null, frontComponentId: createCommandMenuItemInput.frontComponentId ?? null, + frontComponentUniversalIdentifier, label: createCommandMenuItemInput.label, icon: createCommandMenuItemInput.icon ?? null, isPinned: createCommandMenuItemInput.isPinned ?? false, @@ -48,8 +70,10 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({ CommandMenuItemAvailabilityType.GLOBAL, availabilityObjectMetadataId: createCommandMenuItemInput.availabilityObjectMetadataId ?? null, + availabilityObjectMetadataUniversalIdentifier, workspaceId, - applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, createdAt: now, updatedAt: now, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-update-command-menu-item-input-to-flat-command-menu-item-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-update-command-menu-item-input-to-flat-command-menu-item-to-update-or-throw.util.ts index e2d0c12f03..5439438229 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-update-command-menu-item-input-to-flat-command-menu-item-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-update-command-menu-item-input-to-flat-command-menu-item-to-update-or-throw.util.ts @@ -5,20 +5,26 @@ import { CommandMenuItemExceptionCode, } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception'; import { type UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-command-menu-item/constants/flat-command-menu-item-editable-properties.constant'; import { type FlatCommandMenuItemMaps } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item-maps.type'; import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type'; -import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow = ({ flatCommandMenuItemMaps, updateCommandMenuItemInput, + flatObjectMetadataMaps, }: { flatCommandMenuItemMaps: FlatCommandMenuItemMaps; updateCommandMenuItemInput: UpdateCommandMenuItemInput; - }): FlatCommandMenuItem => { + } & Pick< + AllFlatEntityMaps, + 'flatObjectMetadataMaps' + >): FlatCommandMenuItem => { const existingFlatCommandMenuItem = findFlatEntityByIdInFlatEntityMaps({ flatEntityId: updateCommandMenuItemInput.id, flatEntityMaps: flatCommandMenuItemMaps, @@ -33,7 +39,7 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow const { id: _id, ...updates } = updateCommandMenuItemInput; - return { + const flatCommandMenuItemToUpdate = { ...mergeUpdateInExistingRecord({ existing: existingFlatCommandMenuItem, properties: [...FLAT_COMMAND_MENU_ITEM_EDITABLE_PROPERTIES], @@ -41,4 +47,21 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow }), updatedAt: new Date().toISOString(), }; + + if (updates.availabilityObjectMetadataId !== undefined) { + const { availabilityObjectMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'commandMenuItem', + foreignKeyValues: { + availabilityObjectMetadataId: + flatCommandMenuItemToUpdate.availabilityObjectMetadataId, + }, + flatEntityMaps: { flatObjectMetadataMaps }, + }); + + flatCommandMenuItemToUpdate.availabilityObjectMetadataUniversalIdentifier = + availabilityObjectMetadataUniversalIdentifier; + } + + return flatCommandMenuItemToUpdate; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts index c23dc5af31..cca9fc9ac0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts @@ -10,7 +10,7 @@ import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modul import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type'; import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; -type ManyToOneRelationValue< +export type ManyToOneRelationValue< TSourceMetadataName extends AllMetadataName, TRelationProperty extends ExtractEntityManyToOneEntityRelationProperties< MetadataEntity @@ -29,6 +29,9 @@ type ManyToOneRelationValue< MetadataEntity, 'id' | 'workspaceId' >; + isNullable: null extends MetadataEntity[TRelationProperty] + ? true + : false; } : null; @@ -84,11 +87,13 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'availabilityObjectMetadataId', + isNullable: true, }, frontComponent: { metadataName: 'frontComponent', flatEntityForeignKeyAggregator: null, foreignKey: 'frontComponentId', + isNullable: true, }, }, oneToMany: {}, @@ -102,16 +107,19 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'targetObjectMetadataId', + isNullable: true, }, folder: { metadataName: 'navigationMenuItem', flatEntityForeignKeyAggregator: null, foreignKey: 'folderId', + isNullable: true, }, view: { metadataName: 'view', flatEntityForeignKeyAggregator: null, foreignKey: 'viewId', + isNullable: true, }, }, oneToMany: {}, @@ -122,6 +130,7 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: 'fieldIds', foreignKey: 'objectMetadataId', + isNullable: false, }, workspace: null, application: null, @@ -129,11 +138,13 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'relationTargetFieldMetadataId', + isNullable: true, }, relationTargetObjectMetadata: { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'relationTargetObjectMetadataId', + isNullable: true, }, }, oneToMany: { @@ -167,6 +178,7 @@ export const ALL_METADATA_RELATIONS = { foreignKey: 'objectMetadataId', metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: 'viewIds', + isNullable: false, }, workspace: null, createdBy: null, @@ -175,16 +187,19 @@ export const ALL_METADATA_RELATIONS = { foreignKey: 'calendarFieldMetadataId', metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: 'calendarViewIds', + isNullable: true, }, kanbanAggregateOperationFieldMetadata: { foreignKey: 'kanbanAggregateOperationFieldMetadataId', metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: 'kanbanAggregateOperationViewIds', + isNullable: true, }, mainGroupByFieldMetadata: { foreignKey: 'mainGroupByFieldMetadataId', metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: 'mainGroupByFieldMetadataViewIds', + isNullable: true, }, }, oneToMany: { @@ -204,11 +219,13 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: 'viewFieldIds', foreignKey: 'fieldMetadataId', + isNullable: false, }, view: { metadataName: 'view', flatEntityForeignKeyAggregator: 'viewFieldIds', foreignKey: 'viewId', + isNullable: false, }, workspace: null, application: null, @@ -221,16 +238,19 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: 'viewFilterIds', foreignKey: 'fieldMetadataId', + isNullable: false, }, view: { metadataName: 'view', flatEntityForeignKeyAggregator: 'viewFilterIds', foreignKey: 'viewId', + isNullable: false, }, viewFilterGroup: { flatEntityForeignKeyAggregator: 'viewFilterIds', foreignKey: 'viewFilterGroupId', metadataName: 'viewFilterGroup', + isNullable: true, }, workspace: null, application: null, @@ -243,6 +263,7 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'view', flatEntityForeignKeyAggregator: 'viewGroupIds', foreignKey: 'viewId', + isNullable: false, }, workspace: null, application: null, @@ -255,6 +276,7 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: 'indexMetadataIds', foreignKey: 'objectMetadataId', + isNullable: false, }, workspace: null, application: null, @@ -294,6 +316,7 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'role', flatEntityForeignKeyAggregator: 'roleTargetIds', foreignKey: 'roleId', + isNullable: false, }, apiKey: null, workspace: null, @@ -308,12 +331,14 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'objectMetadataId', + isNullable: true, }, application: null, defaultTabToFocusOnMobileAndSidePanel: { metadataName: 'pageLayoutTab', flatEntityForeignKeyAggregator: null, foreignKey: 'defaultTabToFocusOnMobileAndSidePanelId', + isNullable: true, }, }, oneToMany: { @@ -327,6 +352,7 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'pageLayout', flatEntityForeignKeyAggregator: 'tabIds', foreignKey: 'pageLayoutId', + isNullable: false, }, application: null, }, @@ -341,11 +367,13 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'pageLayoutTab', flatEntityForeignKeyAggregator: 'widgetIds', foreignKey: 'pageLayoutTabId', + isNullable: false, }, objectMetadata: { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'objectMetadataId', + isNullable: true, }, application: null, }, @@ -358,26 +386,31 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'role', flatEntityForeignKeyAggregator: null, foreignKey: 'roleId', + isNullable: false, }, fieldMetadata: { metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'fieldMetadataId', + isNullable: false, }, workspaceMemberFieldMetadata: { metadataName: 'fieldMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'workspaceMemberFieldMetadataId', + isNullable: true, }, objectMetadata: { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'objectMetadataId', + isNullable: false, }, rowLevelPermissionPredicateGroup: { metadataName: 'rowLevelPermissionPredicateGroup', flatEntityForeignKeyAggregator: null, foreignKey: 'rowLevelPermissionPredicateGroupId', + isNullable: true, }, application: null, }, @@ -389,17 +422,20 @@ export const ALL_METADATA_RELATIONS = { metadataName: 'objectMetadata', flatEntityForeignKeyAggregator: null, foreignKey: 'objectMetadataId', + isNullable: false, }, role: { metadataName: 'role', foreignKey: 'roleId', flatEntityForeignKeyAggregator: null, + isNullable: false, }, parentRowLevelPermissionPredicateGroup: { metadataName: 'rowLevelPermissionPredicateGroup', foreignKey: 'parentRowLevelPermissionPredicateGroupId', flatEntityForeignKeyAggregator: 'childRowLevelPermissionPredicateGroupIds', + isNullable: true, }, workspace: null, application: null, @@ -420,11 +456,13 @@ export const ALL_METADATA_RELATIONS = { flatEntityForeignKeyAggregator: 'childViewFilterGroupIds', foreignKey: 'parentViewFilterGroupId', metadataName: 'viewFilterGroup', + isNullable: true, }, view: { metadataName: 'view', flatEntityForeignKeyAggregator: 'viewFilterGroupIds', foreignKey: 'viewId', + isNullable: false, }, workspace: null, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts index d2fac754c3..c8bb0c7ab9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts @@ -8,6 +8,8 @@ import { } from 'src/utils/custom-exception'; export const FlatEntityMapsExceptionCode = appendCommonExceptionCode({ + RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND: + 'RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND', ENTITY_ALREADY_EXISTS: 'ENTITY_ALREADY_EXISTS', ENTITY_NOT_FOUND: 'ENTITY_NOT_FOUND', ENTITY_MALFORMED: 'ENTITY_MALFORMED', @@ -17,6 +19,7 @@ const getFlatEntityMapsExceptionUserFriendlyMessage = ( code: keyof typeof FlatEntityMapsExceptionCode, ) => { switch (code) { + case FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND: case FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS: case FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND: case FlatEntityMapsExceptionCode.ENTITY_MALFORMED: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-graphql-api-exception.filter.ts new file mode 100644 index 0000000000..fa915ac7dd --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-graphql-api-exception.filter.ts @@ -0,0 +1,28 @@ +import { Catch, type ExceptionFilter, Injectable } from '@nestjs/common'; + +import { + InternalServerError, + NotFoundError, +} from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; + +@Catch(FlatEntityMapsException) +@Injectable() +export class FlatEntityMapsGraphqlApiExceptionFilter + implements ExceptionFilter +{ + catch(exception: FlatEntityMapsException) { + switch (exception.code) { + case FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND: + throw new NotFoundError(exception); + case FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND: + case FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS: + case FlatEntityMapsExceptionCode.ENTITY_MALFORMED: + case FlatEntityMapsExceptionCode.INTERNAL_SERVER_ERROR: + throw new InternalServerError(exception); + } + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type.ts deleted file mode 100644 index a6e1d2c3ad..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type AllMetadataName } from 'twenty-shared/metadata'; - -import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; - -/** - * Currently under migration but aims to replace FlatEntity afterwards - */ -export type FlatEntityFromV2< - TEntity, - TMetadataName extends AllMetadataName | undefined = undefined, - TInnerFlatEntity extends { __universal?: unknown } = FlatEntityFrom< - TEntity, - TMetadataName - >, -> = Omit & TInnerFlatEntity['__universal']; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts index b75aac2dd2..e82c53ded6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts @@ -28,18 +28,11 @@ export type FlatEntityFrom< CastRecordTypeOrmDatePropertiesToString & AddSuffixToEntityOneToManyProperties & (TEntity extends SyncableEntity - ? { - /** - * /!\ Under migration the idea is at some point to replace FlatEntity by UniversalFlatEntity /!\ - * Please avoid any usage or contact me ( prastoin ) before doing so - * TODO remove with FlatEntity once it has been fully migrated - */ - __universal?: UniversalFlatEntityExtraProperties< - TEntity, - TMetadataName extends undefined - ? FromMetadataEntityToMetadataName - : TMetadataName - > & { universalIdentifier: string }; - } + ? UniversalFlatEntityExtraProperties< + TEntity, + TMetadataName extends undefined + ? FromMetadataEntityToMetadataName + : TMetadataName + > & { universalIdentifier: string } : // eslint-disable-next-line @typescript-eslint/no-empty-object-type {}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/resolve-entity-relation-universal-identifiers.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/resolve-entity-relation-universal-identifiers.util.spec.ts new file mode 100644 index 0000000000..53d9e5d61b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/resolve-entity-relation-universal-identifiers.util.spec.ts @@ -0,0 +1,151 @@ +import { FlatEntityMapsExceptionCode } from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; + +const buildFlatEntityMaps = ( + entries: { id: string; universalIdentifier: string }[], +): FlatEntityMaps => ({ + byUniversalIdentifier: Object.fromEntries( + entries.map((entry) => [ + entry.universalIdentifier, + { universalIdentifier: entry.universalIdentifier, id: entry.id }, + ]), + ), + universalIdentifierById: Object.fromEntries( + entries.map((entry) => [entry.id, entry.universalIdentifier]), + ), + universalIdentifiersByApplicationId: {}, +}); + +const EMPTY_FLAT_ENTITY_MAPS: FlatEntityMaps = { + byUniversalIdentifier: {}, + universalIdentifierById: {}, + universalIdentifiersByApplicationId: {}, +}; + +describe('resolveEntityRelationUniversalIdentifiers', () => { + describe('non-nullable relations', () => { + it('should resolve universal identifier for a provided foreign key', () => { + const result = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewField', + foreignKeyValues: { + fieldMetadataId: 'field-id-1', + viewId: 'view-id-1', + }, + flatEntityMaps: { + flatFieldMetadataMaps: buildFlatEntityMaps([ + { id: 'field-id-1', universalIdentifier: 'field-ui-1' }, + ]), + flatViewMaps: buildFlatEntityMaps([ + { id: 'view-id-1', universalIdentifier: 'view-ui-1' }, + ]), + }, + }); + + expect(result).toEqual({ + fieldMetadataUniversalIdentifier: 'field-ui-1', + viewUniversalIdentifier: 'view-ui-1', + }); + }); + + it('should throw when a non-nullable foreign key id does not exist in maps', () => { + expect(() => + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewField', + foreignKeyValues: { + fieldMetadataId: 'non-existent-id', + viewId: 'view-id-1', + }, + flatEntityMaps: { + flatFieldMetadataMaps: EMPTY_FLAT_ENTITY_MAPS, + flatViewMaps: buildFlatEntityMaps([ + { id: 'view-id-1', universalIdentifier: 'view-ui-1' }, + ]), + }, + }), + ).toThrow( + expect.objectContaining({ + code: FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND, + }), + ); + }); + }); + + describe('nullable relations', () => { + it('should return null when a nullable foreign key value is null', () => { + const result = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayout', + foreignKeyValues: { + objectMetadataId: null, + defaultTabToFocusOnMobileAndSidePanelId: undefined, + }, + flatEntityMaps: { + flatObjectMetadataMaps: EMPTY_FLAT_ENTITY_MAPS, + flatPageLayoutTabMaps: EMPTY_FLAT_ENTITY_MAPS, + }, + }); + + expect(result).toEqual({ + objectMetadataUniversalIdentifier: null, + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null, + }); + }); + + it('should resolve universal identifier when a nullable foreign key has a valid id', () => { + const result = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayout', + foreignKeyValues: { + objectMetadataId: 'obj-id-1', + }, + flatEntityMaps: { + flatObjectMetadataMaps: buildFlatEntityMaps([ + { id: 'obj-id-1', universalIdentifier: 'obj-ui-1' }, + ]), + }, + }); + + expect(result).toEqual({ + objectMetadataUniversalIdentifier: 'obj-ui-1', + }); + }); + + it('should throw when a nullable foreign key has a non-existent id', () => { + expect(() => + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayout', + foreignKeyValues: { + objectMetadataId: 'non-existent-id', + }, + flatEntityMaps: { + flatObjectMetadataMaps: EMPTY_FLAT_ENTITY_MAPS, + }, + }), + ).toThrow( + expect.objectContaining({ + code: FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND, + }), + ); + }); + }); + + describe('partial foreign key resolution', () => { + it('should only resolve universal identifiers for provided foreign keys', () => { + const result = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewField', + foreignKeyValues: { + fieldMetadataId: 'field-id-1', + }, + flatEntityMaps: { + flatFieldMetadataMaps: buildFlatEntityMaps([ + { id: 'field-id-1', universalIdentifier: 'field-ui-1' }, + ]), + }, + }); + + expect(result).toEqual({ + fieldMetadataUniversalIdentifier: 'field-ui-1', + }); + expect(result).not.toHaveProperty('viewUniversalIdentifier'); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts index b4f1a057b0..11c2384d60 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util.ts @@ -7,18 +7,17 @@ import { import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; +import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type'; export const findFlatEntityByUniversalIdentifierOrThrow = < - T extends SyncableFlatEntity, + T extends SyncableFlatEntity | UniversalSyncableFlatEntity, >({ flatEntityMaps, universalIdentifier, }: { flatEntityMaps: FlatEntityMaps; universalIdentifier: string; -}): T & { - id: string; -} => { +}): T => { const flatEntity = findFlatEntityByUniversalIdentifier({ flatEntityMaps, universalIdentifier, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util.ts index 734ca3bff6..3859335f35 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util.ts @@ -1,8 +1,9 @@ import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type'; export const findFlatEntityByUniversalIdentifier = < - T extends SyncableFlatEntity, + T extends SyncableFlatEntity | UniversalSyncableFlatEntity, >({ flatEntityMaps, universalIdentifier, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util.ts new file mode 100644 index 0000000000..29e79a4bf6 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util.ts @@ -0,0 +1,23 @@ +import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; +import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type'; + +export type FindManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrowArgs< + T extends SyncableFlatEntity | UniversalSyncableFlatEntity, +> = { + flatEntityMaps: FlatEntityMaps; + universalIdentifiers: string[]; +}; + +export const findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow = + ({ + flatEntityMaps, + universalIdentifiers, + }: FindManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrowArgs): T[] => + universalIdentifiers.map((universalIdentifier) => + findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps, + universalIdentifier, + }), + ); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts new file mode 100644 index 0000000000..ca131b3cbb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts @@ -0,0 +1,131 @@ +import { t } from '@lingui/core/macro'; +import { type AllMetadataName } from 'twenty-shared/metadata'; +import { isDefined } from 'twenty-shared/utils'; + +import { + ALL_METADATA_RELATIONS, + type ManyToOneRelationValue, +} from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant'; +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type'; +import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type'; +import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type'; +import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key'; +import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util'; +import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/remove-suffix.type'; + +type ManyToOneConfig = + (typeof ALL_METADATA_RELATIONS)[T]['manyToOne']; + +type TargetMetadataNamesForForeignKeys< + T extends AllMetadataName, + TProvidedKeys extends string, +> = { + [K in keyof ManyToOneConfig]: ManyToOneConfig[K] extends { + foreignKey: infer _FK extends TProvidedKeys; + metadataName: infer MN extends AllMetadataName; + } + ? MN + : never; +}[keyof ManyToOneConfig]; + +type RequiredFlatEntityMapsForForeignKeys< + T extends AllMetadataName, + TProvidedKeys extends string, +> = Pick< + AllFlatEntityMaps, + MetadataToFlatEntityMapsKey< + TargetMetadataNamesForForeignKeys + > +>; + +type ResolvedUniversalIdentifiers< + T extends AllMetadataName, + TProvidedKeys extends Extract, string>, +> = { + [K in keyof ManyToOneConfig as ManyToOneConfig[K] extends { + foreignKey: infer FK extends string; + } + ? FK extends TProvidedKeys + ? `${RemoveSuffix}UniversalIdentifier` + : never + : never]: ManyToOneConfig[K] extends { isNullable: true } + ? string | null + : string; +}; + +export const resolveEntityRelationUniversalIdentifiers = < + T extends AllMetadataName, + TProvidedKeys extends Extract< + MetadataManyToOneJoinColumn, + string + > = Extract, string>, +>({ + metadataName, + foreignKeyValues, + flatEntityMaps, +}: { + metadataName: T; + foreignKeyValues: Record; + flatEntityMaps: RequiredFlatEntityMapsForForeignKeys; +}): ResolvedUniversalIdentifiers => { + const relations = ALL_METADATA_RELATIONS[metadataName].manyToOne; + const result: Record = {}; + + for (const relation of Object.values(relations) as ManyToOneRelationValue< + T, + ExtractEntityManyToOneEntityRelationProperties> + >[]) { + if (!isDefined(relation)) { + continue; + } + + const { + foreignKey, + metadataName: targetMetadataName, + isNullable, + } = relation; + + if (!Object.prototype.hasOwnProperty.call(foreignKeyValues, foreignKey)) { + continue; + } + + const foreignKeyValue = + foreignKeyValues[foreignKey as keyof typeof foreignKeyValues]; + + const mapsKey = getMetadataFlatEntityMapsKey( + targetMetadataName, + ) as keyof RequiredFlatEntityMapsForForeignKeys; + const targetFlatEntityMaps = flatEntityMaps[mapsKey]; + + // TODO refactor using the new ALL_METADATA_UNIVERSAL_RELATION afterwards + const universalIdentifierKey = foreignKey.replace( + /Id$/, + 'UniversalIdentifier', + ); + + if (isNullable && !isDefined(foreignKeyValue)) { + result[universalIdentifierKey] = null; + + continue; + } + + const resolvedUniversalIdentifier = + targetFlatEntityMaps.universalIdentifierById[foreignKeyValue as string]; + + if (!isDefined(resolvedUniversalIdentifier)) { + throw new FlatEntityMapsException( + t`Could not find ${targetMetadataName} for given ${foreignKey}`, + FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND, + ); + } + + result[universalIdentifierKey] = resolvedUniversalIdentifier; + } + + return result as ResolvedUniversalIdentifiers; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-universal-identifier-from-flat-entity-id-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-universal-identifier-from-flat-entity-id-or-throw.util.ts new file mode 100644 index 0000000000..4362be7dc9 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-universal-identifier-from-flat-entity-id-or-throw.util.ts @@ -0,0 +1,60 @@ +import { t } from '@lingui/core/macro'; +import { type AllMetadataName } from 'twenty-shared/metadata'; +import { isDefined } from 'twenty-shared/utils'; + +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; + +// @deprecated Use resolveEntityRelationUniversalIdentifiers instead +export const resolveUniversalIdentifierFromFlatEntityIdOrThrow = < + T extends AllMetadataName, +>({ + flatEntityMaps, + flatEntityId, + metadataName, +}: { + flatEntityMaps: MetadataFlatEntityMaps; + flatEntityId: string; + metadataName: T; +}): string => { + const flatEntity = findFlatEntityByIdInFlatEntityMaps({ + flatEntityMaps, + flatEntityId, + }); + + if (!isDefined(flatEntity)) { + throw new FlatEntityMapsException( + t`Could not find ${metadataName}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + + return flatEntity.universalIdentifier; +}; + +// @deprecated Use resolveEntityRelationUniversalIdentifiers instead +export const resolveNullableUniversalIdentifierFromFlatEntityId = < + T extends AllMetadataName, +>({ + flatEntityMaps, + flatEntityId, + metadataName, +}: { + flatEntityMaps: MetadataFlatEntityMaps; + flatEntityId: string | null | undefined; + metadataName: T; +}): string | null => { + if (!isDefined(flatEntityId)) { + return null; + } + + return resolveUniversalIdentifierFromFlatEntityIdOrThrow({ + flatEntityMaps, + flatEntityId, + metadataName, + }); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap index 0a7a9ee3a1..e0e3e48ca8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload has different relation types 1`] = ` { @@ -315,6 +315,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "indexMetadatas": [ { "applicationId": Any, + "applicationUniversalIdentifier": Any, "createdAt": Any, "flatIndexFieldMetadatas": [ { @@ -333,12 +334,14 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": false, "name": "IDX_f687e4e4252800dddd8e5518362", "objectMetadataId": Any, + "objectMetadataUniversalIdentifier": Any, "universalIdentifier": Any, "updatedAt": Any, "workspaceId": Any, }, { "applicationId": Any, + "applicationUniversalIdentifier": Any, "createdAt": Any, "flatIndexFieldMetadatas": [ { @@ -357,6 +360,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": false, "name": "IDX_cb15d901e889e25d0a9acecb595", "objectMetadataId": Any, + "objectMetadataUniversalIdentifier": Any, "universalIdentifier": Any, "updatedAt": Any, "workspaceId": Any, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts index 74b6d89a34..680e76aad7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts @@ -1,10 +1,10 @@ import { type FieldMetadataType } from 'twenty-shared/types'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { type FlatEntityFromV2 } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; export type FlatFieldMetadata = - FlatEntityFromV2< + FlatEntityFrom< Omit< FieldMetadataEntity, 'relationTargetFieldMetadata' | 'relationTargetObjectMetadata' diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts index 6908473938..19d1d8cb00 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts @@ -41,10 +41,14 @@ export const generateIndexForFlatFieldMetadata = ({ isCustom: true, isUnique: flatFieldMetadata.isUnique ?? false, objectMetadataId: flatObjectMetadata.id, + objectMetadataUniversalIdentifier: + flatObjectMetadata.universalIdentifier, universalIdentifier: indexId, updatedAt: createdAt, workspaceId, applicationId: flatFieldMetadata.applicationId, + applicationUniversalIdentifier: + flatFieldMetadata.applicationUniversalIdentifier, }, flatObjectMetadata, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-field-metadata-deactivation-side-effects.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-field-metadata-deactivation-side-effects.util.ts index 897fb699a5..3fcc842ff3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-field-metadata-deactivation-side-effects.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-field-metadata-deactivation-side-effects.util.ts @@ -2,10 +2,10 @@ import { type FromTo } from 'twenty-shared/types'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type'; import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type'; -import { reduceFlatViewGroupsByViewId } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util'; import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; type HandleFlatFieldMetadataDeactivationSideEffectsArgs = FromTo< @@ -59,14 +59,22 @@ export const handleFieldMetadataDeactivationSideEffects = ({ flatEntityMaps: flatViewGroupMaps, }); - const { flatViewGroupRecordByViewId } = reduceFlatViewGroupsByViewId({ - flatViewGroups, - }); + const uniqueViewUniversalIdentifiers = [ + ...new Set(flatViewGroups.map((vg) => vg.viewUniversalIdentifier)), + ]; + + const flatViewsWithViewGroups = + findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow({ + flatEntityMaps: flatViewMaps, + universalIdentifiers: uniqueViewUniversalIdentifiers, + }); + + const viewIdsFromViewGroups = flatViewsWithViewGroups.map((view) => view.id); // Note: We assume a view only has view groups related to one field const viewIdsToDelete = [ ...new Set([ - ...Object.keys(flatViewGroupRecordByViewId), + ...viewIdsFromViewGroups, ...fromFlatFieldMetadata.calendarViewIds, ...fromFlatFieldMetadata.mainGroupByFieldMetadataViewIds, ]), diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts index e3b28c4b83..57f3c43f14 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts @@ -3,10 +3,11 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; -import { reduceFlatViewGroupsByViewId } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util'; +import { reduceFlatViewGroupsByViewUniversalIdentifier } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-universal-identifier.util'; type RecomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdateArgs = FromTo< FlatFieldMetadata, @@ -47,15 +48,19 @@ export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({ ), flatEntityMaps: allFlatViewGroups, }); - const { flatViewGroupRecordByViewId, highestViewGroupPositionByViewId } = - reduceFlatViewGroupsByViewId({ - flatViewGroups, - }); + const { + flatViewGroupRecordByViewUniversalIdentifier, + highestViewGroupPositionByViewUniversalIdentifier, + } = reduceFlatViewGroupsByViewUniversalIdentifier({ + flatViewGroups, + }); - for (const viewId in flatViewGroupRecordByViewId) { - const flatViewGroups = Object.values(flatViewGroupRecordByViewId[viewId]); + for (const viewUniversalIdentifier in flatViewGroupRecordByViewUniversalIdentifier) { + const flatViewGroupsForView = Object.values( + flatViewGroupRecordByViewUniversalIdentifier[viewUniversalIdentifier], + ); - const emptyValueFlatViewGroup = flatViewGroups.find( + const emptyValueFlatViewGroup = flatViewGroupsForView.find( (flatViewGroup) => flatViewGroup.fieldValue === '', ); @@ -63,21 +68,32 @@ export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({ toFlatFieldMetadata.isNullable === true && !isDefined(emptyValueFlatViewGroup) ) { - const highestViewGroupPosition = highestViewGroupPositionByViewId[viewId]; + const highestViewGroupPosition = + highestViewGroupPositionByViewUniversalIdentifier[ + viewUniversalIdentifier + ]; const viewGroupId = v4(); const createdAt = new Date().toISOString(); + const flatView = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatViewMaps, + universalIdentifier: viewUniversalIdentifier, + }); + sideEffectResult.flatViewGroupsToCreate.push({ id: viewGroupId, universalIdentifier: viewGroupId, fieldValue: '', position: highestViewGroupPosition + 1, + applicationUniversalIdentifier: + toFlatFieldMetadata.applicationUniversalIdentifier, + viewUniversalIdentifier, isVisible: true, workspaceId: toFlatFieldMetadata.workspaceId, createdAt, updatedAt: createdAt, deletedAt: null, - viewId, + viewId: flatView.id, applicationId: toFlatFieldMetadata.applicationId, }); } else if (isDefined(emptyValueFlatViewGroup)) { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts index e5cb134d67..af9b98954d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts @@ -8,11 +8,12 @@ import { FlatEntityMapsExceptionCode, } from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { compareTwoFlatFieldMetadataEnumOptions } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata-enum-options.util'; import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; -import { reduceFlatViewGroupsByViewId } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util'; +import { reduceFlatViewGroupsByViewUniversalIdentifier } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-universal-identifier.util'; type RecomputeViewGroupsOnFlatFieldMetadataOptionsUpdateArgs = { fromFlatFieldMetadata: FlatFieldMetadata; @@ -85,51 +86,71 @@ export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({ ), ); - const viewGroupsByViewId = reduceFlatViewGroupsByViewId({ - flatViewGroups: remainingFlatViewGroups, - }); + const viewGroupsByViewUniversalIdentifier = + reduceFlatViewGroupsByViewUniversalIdentifier({ + flatViewGroups: remainingFlatViewGroups, + }); // Count visible view groups per view to enforce the limit - const visibleViewGroupCountByViewId = remainingFlatViewGroups.reduce< - Record - >((acc, flatViewGroup) => { - if (flatViewGroup.isVisible) { - acc[flatViewGroup.viewId] = (acc[flatViewGroup.viewId] ?? 0) + 1; - } + const visibleViewGroupCountByViewUniversalIdentifier = + remainingFlatViewGroups.reduce>( + (acc, flatViewGroup) => { + if (flatViewGroup.isVisible) { + acc[flatViewGroup.viewUniversalIdentifier] = + (acc[flatViewGroup.viewUniversalIdentifier] ?? 0) + 1; + } - return acc; - }, {}); + return acc; + }, + {}, + ); - const viewIds = Object.keys(viewGroupsByViewId.flatViewGroupRecordByViewId); + const viewUniversalIdentifiers = Object.keys( + viewGroupsByViewUniversalIdentifier.flatViewGroupRecordByViewUniversalIdentifier, + ); const createdAt = new Date().toISOString(); const flatViewGroupsToCreate = createdFieldMetadataOptions.flatMap( (option, createdOptionIndex) => - viewIds.map((viewId) => { + viewUniversalIdentifiers.map((viewUniversalIdentifier) => { const viewGroupHighestPosition = - viewGroupsByViewId.highestViewGroupPositionByViewId[viewId]; + viewGroupsByViewUniversalIdentifier + .highestViewGroupPositionByViewUniversalIdentifier[ + viewUniversalIdentifier + ]; if (!isDefined(viewGroupHighestPosition)) { throw new FlatEntityMapsException( - 'View id highest position not found, should never occur', + 'View universal identifier highest position not found, should never occur', FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, ); } - const currentVisibleCount = visibleViewGroupCountByViewId[viewId] ?? 0; + const currentVisibleCount = + visibleViewGroupCountByViewUniversalIdentifier[ + viewUniversalIdentifier + ] ?? 0; const isVisible = currentVisibleCount < VIEW_GROUP_VISIBLE_OPTIONS_MAX; // Increment the count for future iterations if this group will be visible if (isVisible) { - visibleViewGroupCountByViewId[viewId] = currentVisibleCount + 1; + visibleViewGroupCountByViewUniversalIdentifier[ + viewUniversalIdentifier + ] = currentVisibleCount + 1; } const viewGroupId = v4(); + const flatView = findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: flatViewMaps, + universalIdentifier: viewUniversalIdentifier, + }); + return { id: viewGroupId, fieldMetadataId: fromFlatFieldMetadata.id, - viewId, + viewId: flatView.id, + viewUniversalIdentifier, workspaceId, createdAt: createdAt, updatedAt: createdAt, @@ -139,6 +160,8 @@ export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({ fieldValue: option.value, position: viewGroupHighestPosition + createdOptionIndex + 1, applicationId: fromFlatFieldMetadata.applicationId, + applicationUniversalIdentifier: + fromFlatFieldMetadata.applicationUniversalIdentifier, }; }), ); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-create-front-component-input-to-flat-front-component-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-create-front-component-input-to-flat-front-component-to-create.util.ts index 2d7ec5e963..4e1854bdd5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-create-front-component-input-to-flat-front-component-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-create-front-component-input-to-flat-front-component-to-create.util.ts @@ -1,17 +1,18 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type'; import { type CreateFrontComponentInput } from 'src/engine/metadata-modules/front-component/dtos/create-front-component.input'; export const fromCreateFrontComponentInputToFlatFrontComponentToCreate = ({ createFrontComponentInput, workspaceId, - applicationId, + flatApplication, }: { createFrontComponentInput: CreateFrontComponentInput; workspaceId: string; - applicationId: string; + flatApplication: FlatApplication; }): FlatFrontComponent => { const now = new Date().toISOString(); @@ -29,6 +30,7 @@ export const fromCreateFrontComponentInputToFlatFrontComponentToCreate = ({ createdAt: now, updatedAt: now, universalIdentifier: id, - applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-front-component-entity-to-flat-front-component.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-front-component-entity-to-flat-front-component.util.ts index 22233637cb..04df5c94cf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-front-component-entity-to-flat-front-component.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-front-component/utils/from-front-component-entity-to-flat-front-component.util.ts @@ -31,9 +31,6 @@ export const fromFrontComponentEntityToFlatFrontComponent = ({ applicationId: frontComponentEntity.applicationId, createdAt: frontComponentEntity.createdAt.toISOString(), updatedAt: frontComponentEntity.updatedAt.toISOString(), - __universal: { - universalIdentifier: frontComponentEntity.universalIdentifier, - applicationUniversalIdentifier, - }, + applicationUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts index e9279a344f..4bb935fe77 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts @@ -4,7 +4,13 @@ import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-m import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types'; type FlatIndexMetadataOverrides = Required< - Pick + Pick< + FlatIndexMetadata, + | 'universalIdentifier' + | 'objectMetadataId' + | 'objectMetadataUniversalIdentifier' + | 'applicationUniversalIdentifier' + > > & Partial; export const getFlatIndexMetadataMock = ( diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts index 19582dc909..f09ee2a3c5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts @@ -58,10 +58,7 @@ export const fromIndexMetadataEntityToFlatIndexMetadata = ({ updatedAt: indexFieldMetadata.updatedAt.toISOString(), }), ), - __universal: { - universalIdentifier: indexMetadataEntity.universalIdentifier, - applicationUniversalIdentifier, - objectMetadataUniversalIdentifier, - }, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts index 8b3cc3ec12..a8eabf4910 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-create-navigation-menu-item-input-to-flat-navigation-menu-item-to-create.util.ts @@ -1,6 +1,9 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatNavigationMenuItemMaps } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item-maps.type'; import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type'; import { type CreateNavigationMenuItemInput } from 'src/engine/metadata-modules/navigation-menu-item/dtos/create-navigation-menu-item.input'; @@ -9,14 +12,19 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate = ({ createNavigationMenuItemInput, workspaceId, - applicationId, + flatApplication, flatNavigationMenuItemMaps, + flatObjectMetadataMaps, + flatViewMaps, }: { createNavigationMenuItemInput: CreateNavigationMenuItemInput; workspaceId: string; - applicationId: string; + flatApplication: FlatApplication; flatNavigationMenuItemMaps: FlatNavigationMenuItemMaps; - }): FlatNavigationMenuItem => { + } & Pick< + AllFlatEntityMaps, + 'flatObjectMetadataMaps' | 'flatViewMaps' + >): FlatNavigationMenuItem => { const id = uuidv4(); const now = new Date().toISOString(); @@ -40,6 +48,25 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate = position = maxPosition + 1; } + const { + targetObjectMetadataUniversalIdentifier, + viewUniversalIdentifier, + folderUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'navigationMenuItem', + foreignKeyValues: { + targetObjectMetadataId: + createNavigationMenuItemInput.targetObjectMetadataId, + viewId: createNavigationMenuItemInput.viewId, + folderId: createNavigationMenuItemInput.folderId, + }, + flatEntityMaps: { + flatObjectMetadataMaps, + flatViewMaps, + flatNavigationMenuItemMaps, + }, + }); + return { id, universalIdentifier: id, @@ -47,12 +74,16 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate = targetRecordId: createNavigationMenuItemInput.targetRecordId ?? null, targetObjectMetadataId: createNavigationMenuItemInput.targetObjectMetadataId ?? null, + targetObjectMetadataUniversalIdentifier, viewId: createNavigationMenuItemInput.viewId ?? null, + viewUniversalIdentifier, folderId: createNavigationMenuItemInput.folderId ?? null, + folderUniversalIdentifier, name: createNavigationMenuItemInput.name ?? null, position, workspaceId, - applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, createdAt: now, updatedAt: now, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts index 98c18f2d0e..0e05737106 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util.ts @@ -87,12 +87,9 @@ export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({ applicationId: navigationMenuItemEntity.applicationId, createdAt: navigationMenuItemEntity.createdAt.toISOString(), updatedAt: navigationMenuItemEntity.updatedAt.toISOString(), - __universal: { - universalIdentifier: navigationMenuItemEntity.universalIdentifier, - applicationUniversalIdentifier, - targetObjectMetadataUniversalIdentifier, - folderUniversalIdentifier, - viewUniversalIdentifier, - }, + applicationUniversalIdentifier, + targetObjectMetadataUniversalIdentifier, + folderUniversalIdentifier, + viewUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts index ceb549a9b4..83e23a73a1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-navigation-menu-item/utils/from-update-navigation-menu-item-input-to-flat-navigation-menu-item-to-update-or-throw.util.ts @@ -1,6 +1,7 @@ import { isDefined } from 'twenty-shared/utils'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { FLAT_NAVIGATION_MENU_ITEM_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-navigation-menu-item/constants/flat-navigation-menu-item-editable-properties.constant'; import { type FlatNavigationMenuItemMaps } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item-maps.type'; import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type'; @@ -35,7 +36,7 @@ export const fromUpdateNavigationMenuItemInputToFlatNavigationMenuItemToUpdateOr const { id: _id, ...updates } = updateNavigationMenuItemInput; - return { + const flatNavigationMenuItemToUpdate = { ...mergeUpdateInExistingRecord({ existing: existingFlatNavigationMenuItem, properties: [...FLAT_NAVIGATION_MENU_ITEM_EDITABLE_PROPERTIES], @@ -43,4 +44,20 @@ export const fromUpdateNavigationMenuItemInputToFlatNavigationMenuItemToUpdateOr }), updatedAt: new Date().toISOString(), }; + + if (updates.folderId !== undefined) { + const { folderUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'navigationMenuItem', + foreignKeyValues: { + folderId: flatNavigationMenuItemToUpdate.folderId, + }, + flatEntityMaps: { flatNavigationMenuItemMaps }, + }); + + flatNavigationMenuItemToUpdate.folderUniversalIdentifier = + folderUniversalIdentifier; + } + + return flatNavigationMenuItemToUpdate; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts index e2361aa29f..575d3b59ac 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts @@ -1,7 +1,7 @@ -import { type FlatEntityFromV2 } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -type BaseFlatObjectMetadata = FlatEntityFromV2< +type BaseFlatObjectMetadata = FlatEntityFrom< Omit >; export type FlatObjectMetadata = BaseFlatObjectMetadata & { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/handle-flat-object-metadata-update-side-effect.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/handle-flat-object-metadata-update-side-effect.util.ts index 939531f870..69ba469aed 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/handle-flat-object-metadata-update-side-effect.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/handle-flat-object-metadata-update-side-effect.util.ts @@ -75,6 +75,7 @@ export const handleFlatObjectMetadataUpdateSideEffect = ({ existingFlatObjectMetadata: fromFlatObjectMetadata, flatViewFieldMaps, flatViewMaps, + flatFieldMetadataMaps, updatedLabelIdentifierFieldMetadataId: toFlatObjectMetadata.labelIdentifierFieldMetadataId, }) diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts index 44b676dfb2..bbc454d7e9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts @@ -2,6 +2,7 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant'; @@ -10,7 +11,10 @@ import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/ type RecomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdateArgs = { existingFlatObjectMetadata: FlatObjectMetadata; updatedLabelIdentifierFieldMetadataId: string; -} & Pick; +} & Pick< + AllFlatEntityMaps, + 'flatViewFieldMaps' | 'flatViewMaps' | 'flatFieldMetadataMaps' +>; type FlatViewFieldToCreateAndUpdate = { flatViewFieldsToCreate: FlatViewField[]; @@ -20,6 +24,7 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({ existingFlatObjectMetadata, flatViewFieldMaps, flatViewMaps, + flatFieldMetadataMaps, updatedLabelIdentifierFieldMetadataId, }: RecomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdateArgs): FlatViewFieldToCreateAndUpdate => { const flatViews = findManyFlatEntityByIdInFlatEntityMapsOrThrow({ @@ -27,6 +32,12 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({ flatEntityIds: existingFlatObjectMetadata.viewIds, }); + const updatedLabelIdentifierFieldMetadata = + findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + flatEntityId: updatedLabelIdentifierFieldMetadataId, + }); + const accumulator: FlatViewFieldToCreateAndUpdate = { flatViewFieldsToCreate: [], flatViewFieldsToUpdate: [], @@ -55,10 +66,13 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({ const createdAt = new Date().toISOString(); const flatViewFieldToCreate: FlatViewField = { fieldMetadataId: updatedLabelIdentifierFieldMetadataId, + fieldMetadataUniversalIdentifier: + updatedLabelIdentifierFieldMetadata.universalIdentifier, position: lowestViewFieldPosition - 1, isVisible: true, size: DEFAULT_VIEW_FIELD_SIZE, viewId: flatView.id, + viewUniversalIdentifier: flatView.universalIdentifier, workspaceId: flatView.workspaceId, id: viewFieldId, createdAt: createdAt, @@ -67,6 +81,8 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({ universalIdentifier: viewFieldId, aggregateOperation: null, applicationId: existingFlatObjectMetadata.applicationId, + applicationUniversalIdentifier: + existingFlatObjectMetadata.applicationUniversalIdentifier, }; accumulator.flatViewFieldsToCreate.push(flatViewFieldToCreate); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts index 6962fe6c9b..14d0a974b7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util.ts @@ -2,19 +2,23 @@ import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; import { type CreatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout-tab/dtos/inputs/create-page-layout-tab.input'; export type FromCreatePageLayoutTabInputToFlatPageLayoutTabToCreateArgs = { createPageLayoutTabInput: CreatePageLayoutTabInput; workspaceId: string; - workspaceCustomApplicationId: string; -}; + flatApplication: FlatApplication; +} & Pick; export const fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate = ({ createPageLayoutTabInput: rawCreatePageLayoutTabInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatPageLayoutMaps, }: FromCreatePageLayoutTabInputToFlatPageLayoutTabToCreateArgs): FlatPageLayoutTab => { const createPageLayoutTabInput = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( @@ -25,18 +29,32 @@ export const fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate = ({ const createdAt = new Date().toISOString(); const pageLayoutTabId = v4(); + const { pageLayoutUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + flatEntityMaps: { + flatPageLayoutMaps, + }, + foreignKeyValues: { + pageLayoutId: createPageLayoutTabInput.pageLayoutId, + }, + metadataName: 'pageLayoutTab', + }); + return { id: pageLayoutTabId, title: createPageLayoutTabInput.title, position: createPageLayoutTabInput.position ?? 0, pageLayoutId: createPageLayoutTabInput.pageLayoutId, + pageLayoutUniversalIdentifier, workspaceId, createdAt, updatedAt: createdAt, deletedAt: null, universalIdentifier: pageLayoutTabId, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, widgetIds: [], + widgetUniversalIdentifiers: [], icon: null, layoutMode: PageLayoutTabLayoutMode.GRID, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/transform-page-layout-tab-entity-to-flat-page-layout-tab.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/transform-page-layout-tab-entity-to-flat-page-layout-tab.util.ts index 5877bf2b5b..b089d9fd65 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/transform-page-layout-tab-entity-to-flat-page-layout-tab.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/transform-page-layout-tab-entity-to-flat-page-layout-tab.util.ts @@ -48,13 +48,10 @@ export const transformPageLayoutTabEntityToFlatPageLayoutTab = ({ widgetIds: pageLayoutTabEntity.widgets.map((widget) => widget.id), icon: pageLayoutTabEntity.icon, layoutMode: pageLayoutTabEntity.layoutMode, - __universal: { - universalIdentifier: pageLayoutTabEntity.universalIdentifier, - applicationUniversalIdentifier, - pageLayoutUniversalIdentifier, - widgetUniversalIdentifiers: pageLayoutTabEntity.widgets.map( - (widget) => widget.universalIdentifier, - ), - }, + applicationUniversalIdentifier, + pageLayoutUniversalIdentifier, + widgetUniversalIdentifiers: pageLayoutTabEntity.widgets.map( + (widget) => widget.universalIdentifier, + ), }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type.ts index 86656cdbbb..f4026a30bf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type.ts @@ -1,4 +1,6 @@ import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; -export type FlatPageLayoutWidgetMaps = FlatEntityMaps; +export type FlatPageLayoutWidgetMaps = FlatEntityMaps< + Omit +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts index 853fde7d41..878a399d89 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type.ts @@ -4,4 +4,4 @@ import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-l export type FlatPageLayoutWidget< T extends WidgetConfigurationType = WidgetConfigurationType, -> = FlatEntityFrom>; +> = Omit>, 'universalConfiguration'>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts index a3d8d06058..f7c3fb7d38 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts @@ -1,6 +1,9 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; import { type CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input'; import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util'; @@ -9,13 +12,18 @@ export type FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs = { createPageLayoutWidgetInput: CreatePageLayoutWidgetInput; workspaceId: string; - workspaceCustomApplicationId: string; - }; + flatApplication: FlatApplication; + } & Pick< + AllFlatEntityMaps, + 'flatPageLayoutTabMaps' | 'flatObjectMetadataMaps' + >; export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({ createPageLayoutWidgetInput: rawCreatePageLayoutWidgetInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, }: FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs): FlatPageLayoutWidget => { const { pageLayoutTabId, ...createPageLayoutWidgetInput } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( @@ -30,9 +38,22 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({ const createdAt = new Date().toISOString(); const pageLayoutWidgetId = v4(); + const { + pageLayoutTabUniversalIdentifier, + objectMetadataUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayoutWidget', + foreignKeyValues: { + pageLayoutTabId, + objectMetadataId: createPageLayoutWidgetInput.objectMetadataId, + }, + flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps }, + }); + return { id: pageLayoutWidgetId, pageLayoutTabId, + pageLayoutTabUniversalIdentifier, workspaceId, createdAt, updatedAt: createdAt, @@ -41,10 +62,12 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({ title: createPageLayoutWidgetInput.title, type: createPageLayoutWidgetInput.type, objectMetadataId: createPageLayoutWidgetInput.objectMetadataId ?? null, + objectMetadataUniversalIdentifier, gridPosition: createPageLayoutWidgetInput.gridPosition, position: createPageLayoutWidgetInput.position ?? null, configuration: createPageLayoutWidgetInput.configuration, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, conditionalDisplay: null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts index 39f91d7e18..39a646c73b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts @@ -6,9 +6,9 @@ import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout type PageLayoutWidgetConfiguration = PageLayoutWidgetEntity['configuration']; -type UniversalPageLayoutWidgetConfiguration = NonNullable< - FlatPageLayoutWidget['__universal'] ->['universalConfiguration']; +type UniversalPageLayoutWidgetConfiguration = + // @ts-expect-error Ignoring until the pageLayoutWidget has been universally migrated + NonNullable['universalConfiguration']; // Field metadata IDs in widget configurations don't need to reference existing entities const getFieldMetadataUniversalIdentifier = ({ diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts index e036b7b20b..451eb62085 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts @@ -6,7 +6,6 @@ import { } from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; -import { fromPageLayoutWidgetConfigurationToUniversalConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util'; import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type'; type FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs = @@ -19,7 +18,6 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({ applicationIdToUniversalIdentifierMap, pageLayoutTabIdToUniversalIdentifierMap, objectMetadataIdToUniversalIdentifierMap, - fieldMetadataIdToUniversalIdentifierMap, }: FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs): FlatPageLayoutWidget => { const pageLayoutWidgetEntityWithoutRelations = removePropertiesFromRecord( pageLayoutWidgetEntity, @@ -66,11 +64,12 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({ } } - const configurationWithUniversalIdentifiers = - fromPageLayoutWidgetConfigurationToUniversalConfiguration({ - configuration: pageLayoutWidgetEntityWithoutRelations.configuration, - fieldMetadataIdToUniversalIdentifierMap, - }); + // TODO uncomment once page layout widget has been migrated + // const configurationWithUniversalIdentifiers = + // fromPageLayoutWidgetConfigurationToUniversalConfiguration({ + // configuration: pageLayoutWidgetEntityWithoutRelations.configuration, + // fieldMetadataIdToUniversalIdentifierMap, + // }); return { ...pageLayoutWidgetEntityWithoutRelations, @@ -80,12 +79,9 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({ universalIdentifier: pageLayoutWidgetEntityWithoutRelations.universalIdentifier, applicationId: pageLayoutWidgetEntityWithoutRelations.applicationId, - __universal: { - universalIdentifier: pageLayoutWidgetEntity.universalIdentifier, - applicationUniversalIdentifier, - pageLayoutTabUniversalIdentifier, - objectMetadataUniversalIdentifier, - universalConfiguration: configurationWithUniversalIdentifiers, - }, + applicationUniversalIdentifier, + pageLayoutTabUniversalIdentifier, + objectMetadataUniversalIdentifier, + // universalConfiguration: configurationWithUniversalIdentifiers, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-update-page-layout-widget-input-to-flat-page-layout-widget-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-update-page-layout-widget-input-to-flat-page-layout-widget-to-update-or-throw.util.ts index bf67867ece..3cfbf1cf15 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-update-page-layout-widget-input-to-flat-page-layout-widget-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-update-page-layout-widget-input-to-flat-page-layout-widget-to-update-or-throw.util.ts @@ -4,8 +4,10 @@ import { isDefined, } from 'twenty-shared/utils'; -import { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/flat-page-layout-widget-editable-properties.constant'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/flat-page-layout-widget-editable-properties.constant'; import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type'; import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; import { type UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget.input'; @@ -25,10 +27,14 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro ({ updatePageLayoutWidgetInput: rawUpdatePageLayoutWidgetInput, flatPageLayoutWidgetMaps, + flatObjectMetadataMaps, }: { updatePageLayoutWidgetInput: UpdatePageLayoutWidgetInputWithId; flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps; - }): FlatPageLayoutWidget => { + } & Pick< + AllFlatEntityMaps, + 'flatObjectMetadataMaps' + >): FlatPageLayoutWidget => { const { id: pageLayoutWidgetToUpdateId } = extractAndSanitizeObjectStringFields(rawUpdatePageLayoutWidgetInput, [ 'id', @@ -63,9 +69,25 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro }); } - return mergeUpdateInExistingRecord({ + const flatPageLayoutWidgetToUpdate = mergeUpdateInExistingRecord({ existing: existingFlatPageLayoutWidgetToUpdate, properties: FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES, update: updatedEditableFieldProperties, }); + + if (updatedEditableFieldProperties.objectMetadataId !== undefined) { + const { objectMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayoutWidget', + foreignKeyValues: { + objectMetadataId: flatPageLayoutWidgetToUpdate.objectMetadataId, + }, + flatEntityMaps: { flatObjectMetadataMaps }, + }); + + flatPageLayoutWidgetToUpdate.objectMetadataUniversalIdentifier = + objectMetadataUniversalIdentifier; + } + + return flatPageLayoutWidgetToUpdate; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-create-page-layout-input-to-flat-page-layout-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-create-page-layout-input-to-flat-page-layout-to-create.util.ts index 321dd83279..2d1187bcc0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-create-page-layout-input-to-flat-page-layout-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-create-page-layout-input-to-flat-page-layout-to-create.util.ts @@ -1,6 +1,9 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; import { type CreatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout.input'; import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; @@ -8,13 +11,14 @@ import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/pa export type FromCreatePageLayoutInputToFlatPageLayoutToCreateArgs = { createPageLayoutInput: CreatePageLayoutInput; workspaceId: string; - workspaceCustomApplicationId: string; -}; + flatApplication: FlatApplication; +} & Pick; export const fromCreatePageLayoutInputToFlatPageLayoutToCreate = ({ createPageLayoutInput: rawCreatePageLayoutInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatObjectMetadataMaps, }: FromCreatePageLayoutInputToFlatPageLayoutToCreateArgs): FlatPageLayout => { const createPageLayoutInput = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( @@ -25,18 +29,31 @@ export const fromCreatePageLayoutInputToFlatPageLayoutToCreate = ({ const createdAt = new Date().toISOString(); const pageLayoutId = v4(); + const { objectMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayout', + foreignKeyValues: { + objectMetadataId: createPageLayoutInput.objectMetadataId, + }, + flatEntityMaps: { flatObjectMetadataMaps }, + }); + return { id: pageLayoutId, name: createPageLayoutInput.name, type: createPageLayoutInput.type ?? PageLayoutType.RECORD_PAGE, objectMetadataId: createPageLayoutInput.objectMetadataId ?? null, + objectMetadataUniversalIdentifier, workspaceId, createdAt, updatedAt: createdAt, deletedAt: null, universalIdentifier: pageLayoutId, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, tabIds: [], + tabUniversalIdentifiers: [], defaultTabToFocusOnMobileAndSidePanelId: null, + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-update-page-layout-input-to-flat-page-layout-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-update-page-layout-input-to-flat-page-layout-to-update-or-throw.util.ts index 4ac5c259eb..a004668006 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-update-page-layout-input-to-flat-page-layout-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-update-page-layout-input-to-flat-page-layout-to-update-or-throw.util.ts @@ -4,8 +4,10 @@ import { isDefined, } from 'twenty-shared/utils'; -import { FLAT_PAGE_LAYOUT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout/constants/flat-page-layout-editable-properties.constant'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { FLAT_PAGE_LAYOUT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout/constants/flat-page-layout-editable-properties.constant'; import { type FlatPageLayoutMaps } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout-maps.type'; import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; import { type UpdatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout.input'; @@ -23,10 +25,11 @@ export type UpdatePageLayoutInputWithId = { export const fromUpdatePageLayoutInputToFlatPageLayoutToUpdateOrThrow = ({ updatePageLayoutInput: rawUpdatePageLayoutInput, flatPageLayoutMaps, + flatObjectMetadataMaps, }: { updatePageLayoutInput: UpdatePageLayoutInputWithId; flatPageLayoutMaps: FlatPageLayoutMaps; -}): FlatPageLayout => { +} & Pick): FlatPageLayout => { const { id: pageLayoutToUpdateId } = extractAndSanitizeObjectStringFields( rawUpdatePageLayoutInput, ['id'], @@ -49,9 +52,25 @@ export const fromUpdatePageLayoutInputToFlatPageLayoutToUpdateOrThrow = ({ FLAT_PAGE_LAYOUT_EDITABLE_PROPERTIES, ); - return mergeUpdateInExistingRecord({ + const flatPageLayoutToUpdate = mergeUpdateInExistingRecord({ existing: existingFlatPageLayoutToUpdate, properties: FLAT_PAGE_LAYOUT_EDITABLE_PROPERTIES, update: updatedEditableFieldProperties, }); + + if (updatedEditableFieldProperties.objectMetadataId !== undefined) { + const { objectMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayout', + foreignKeyValues: { + objectMetadataId: flatPageLayoutToUpdate.objectMetadataId, + }, + flatEntityMaps: { flatObjectMetadataMaps }, + }); + + flatPageLayoutToUpdate.objectMetadataUniversalIdentifier = + objectMetadataUniversalIdentifier; + } + + return flatPageLayoutToUpdate; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/transform-page-layout-entity-to-flat-page-layout.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/transform-page-layout-entity-to-flat-page-layout.util.ts index 88b8d4e462..a02917c37d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/transform-page-layout-entity-to-flat-page-layout.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/transform-page-layout-entity-to-flat-page-layout.util.ts @@ -70,14 +70,11 @@ export const transformPageLayoutEntityToFlatPageLayout = ({ tabIds: pageLayoutEntity.tabs.map((tab) => tab.id), defaultTabToFocusOnMobileAndSidePanelId: pageLayoutEntity.defaultTabToFocusOnMobileAndSidePanelId, - __universal: { - universalIdentifier: pageLayoutEntity.universalIdentifier, - applicationUniversalIdentifier, - objectMetadataUniversalIdentifier, - tabUniversalIdentifiers: pageLayoutEntity.tabs.map( - (tab) => tab.universalIdentifier, - ), - defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier, - }, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + tabUniversalIdentifiers: pageLayoutEntity.tabs.map( + (tab) => tab.universalIdentifier, + ), + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util.ts index 87e98b07fc..6f8ecf0f33 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util.ts @@ -44,10 +44,7 @@ export const fromRoleTargetEntityToFlatRoleTarget = ({ universalIdentifier: roleTargetEntity.universalIdentifier, createdAt: roleTargetEntity.createdAt.toISOString(), updatedAt: roleTargetEntity.updatedAt.toISOString(), - __universal: { - universalIdentifier: roleTargetEntity.universalIdentifier, - applicationUniversalIdentifier, - roleUniversalIdentifier, - }, + applicationUniversalIdentifier, + roleUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts index 26c0bc8f0c..42223007c9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-create-role-input-to-flat-role-to-create.util.ts @@ -1,17 +1,18 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type'; import { type CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/create-role-input.dto'; export const fromCreateRoleInputToFlatRoleToCreate = ({ createRoleInput, workspaceId, - applicationId, + flatApplication, }: { createRoleInput: CreateRoleInput; workspaceId: string; - applicationId: string; + flatApplication: FlatApplication; }): FlatRole => { const now = new Date().toISOString(); @@ -48,12 +49,16 @@ export const fromCreateRoleInputToFlatRoleToCreate = ({ createdAt: now, updatedAt: now, universalIdentifier: createRoleInput.universalIdentifier ?? v4(), - applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, roleTargetIds: [], + roleTargetUniversalIdentifiers: [], objectPermissionIds: [], permissionFlagIds: [], fieldPermissionIds: [], rowLevelPermissionPredicateIds: [], + rowLevelPermissionPredicateUniversalIdentifiers: [], rowLevelPermissionPredicateGroupIds: [], + rowLevelPermissionPredicateGroupUniversalIdentifiers: [], }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts index d6d807c744..3edc353c8b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts @@ -64,20 +64,17 @@ export const fromRoleEntityToFlatRole = ({ ), rowLevelPermissionPredicateGroupIds: roleEntity.rowLevelPermissionPredicateGroups.map(({ id }) => id), - __universal: { - universalIdentifier: roleEntity.universalIdentifier, - applicationUniversalIdentifier, - roleTargetUniversalIdentifiers: roleEntity.roleTargets.map( + applicationUniversalIdentifier, + roleTargetUniversalIdentifiers: roleEntity.roleTargets.map( + ({ universalIdentifier }) => universalIdentifier, + ), + rowLevelPermissionPredicateUniversalIdentifiers: + roleEntity.rowLevelPermissionPredicates.map( + ({ universalIdentifier }) => universalIdentifier, + ), + rowLevelPermissionPredicateGroupUniversalIdentifiers: + roleEntity.rowLevelPermissionPredicateGroups.map( ({ universalIdentifier }) => universalIdentifier, ), - rowLevelPermissionPredicateUniversalIdentifiers: - roleEntity.rowLevelPermissionPredicates.map( - ({ universalIdentifier }) => universalIdentifier, - ), - rowLevelPermissionPredicateGroupUniversalIdentifiers: - roleEntity.rowLevelPermissionPredicateGroups.map( - ({ universalIdentifier }) => universalIdentifier, - ), - }, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts index f61f2ed0ec..835e65b0cc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts @@ -120,15 +120,11 @@ export const fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredic rowLevelPermissionPredicateEntity.deletedAt?.toISOString() ?? null, universalIdentifier: rowLevelPermissionPredicateEntityWithoutRelations.universalIdentifier, - __universal: { - universalIdentifier: - rowLevelPermissionPredicateEntity.universalIdentifier, - applicationUniversalIdentifier, - fieldMetadataUniversalIdentifier, - objectMetadataUniversalIdentifier, - roleUniversalIdentifier, - workspaceMemberFieldMetadataUniversalIdentifier, - rowLevelPermissionPredicateGroupUniversalIdentifier, - }, + applicationUniversalIdentifier, + fieldMetadataUniversalIdentifier, + objectMetadataUniversalIdentifier, + roleUniversalIdentifier, + workspaceMemberFieldMetadataUniversalIdentifier, + rowLevelPermissionPredicateGroupUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-group-entity-to-flat-row-level-permission-predicate-group.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-group-entity-to-flat-row-level-permission-predicate-group.util.ts index 741554236f..df1bbae7bd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-group-entity-to-flat-row-level-permission-predicate-group.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-group-entity-to-flat-row-level-permission-predicate-group.util.ts @@ -106,21 +106,17 @@ export const fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionP rowLevelPermissionPredicateGroupEntity.rowLevelPermissionPredicates ?? [] ).map(({ id }) => id), - __universal: { - universalIdentifier: - rowLevelPermissionPredicateGroupEntity.universalIdentifier, - applicationUniversalIdentifier, - objectMetadataUniversalIdentifier, - roleUniversalIdentifier, - parentRowLevelPermissionPredicateGroupUniversalIdentifier, - childRowLevelPermissionPredicateGroupUniversalIdentifiers: ( - rowLevelPermissionPredicateGroupEntity.childRowLevelPermissionPredicateGroups ?? - [] - ).map(({ universalIdentifier }) => universalIdentifier), - rowLevelPermissionPredicateUniversalIdentifiers: ( - rowLevelPermissionPredicateGroupEntity.rowLevelPermissionPredicates ?? - [] - ).map(({ universalIdentifier }) => universalIdentifier), - }, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + roleUniversalIdentifier, + parentRowLevelPermissionPredicateGroupUniversalIdentifier, + childRowLevelPermissionPredicateGroupUniversalIdentifiers: ( + rowLevelPermissionPredicateGroupEntity.childRowLevelPermissionPredicateGroups ?? + [] + ).map(({ universalIdentifier }) => universalIdentifier), + rowLevelPermissionPredicateUniversalIdentifiers: ( + rowLevelPermissionPredicateGroupEntity.rowLevelPermissionPredicates ?? + [] + ).map(({ universalIdentifier }) => universalIdentifier), }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-create-skill-input-to-flat-skill-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-create-skill-input-to-flat-skill-to-create.util.ts index bfa6a02b4c..1541062a83 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-create-skill-input-to-flat-skill-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-create-skill-input-to-flat-skill-to-create.util.ts @@ -1,17 +1,18 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type'; import { type CreateSkillInput } from 'src/engine/metadata-modules/skill/dtos/create-skill.input'; export const fromCreateSkillInputToFlatSkillToCreate = ({ createSkillInput, workspaceId, - applicationId, + flatApplication, }: { createSkillInput: CreateSkillInput; workspaceId: string; - applicationId: string; + flatApplication: FlatApplication; }): FlatSkill => { const now = new Date().toISOString(); @@ -39,6 +40,7 @@ export const fromCreateSkillInputToFlatSkillToCreate = ({ createdAt: now, updatedAt: now, universalIdentifier: id, - applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-skill-entity-to-flat-skill.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-skill-entity-to-flat-skill.util.ts index 0f60d002ce..e02a54113b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-skill-entity-to-flat-skill.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/from-skill-entity-to-flat-skill.util.ts @@ -35,9 +35,6 @@ export const fromSkillEntityToFlatSkill = ({ isActive: skillEntity.isActive, universalIdentifier: skillEntity.universalIdentifier, applicationId: skillEntity.applicationId, - __universal: { - universalIdentifier: skillEntity.universalIdentifier, - applicationUniversalIdentifier, - }, + applicationUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts index ead78241bf..4c8130b407 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts @@ -1,6 +1,9 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant'; import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type'; import { type CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input'; @@ -8,13 +11,15 @@ import { type CreateViewFieldInput } from 'src/engine/metadata-modules/view-fiel export type FromCreateViewFieldInputToFlatViewFieldToCreateArgs = { createViewFieldInput: CreateViewFieldInput; workspaceId: string; - workspaceCustomApplicationId: string; -}; + flatApplication: FlatApplication; +} & Pick; export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({ createViewFieldInput: rawCreateViewFieldInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatFieldMetadataMaps, + flatViewMaps, }: FromCreateViewFieldInputToFlatViewFieldToCreateArgs): FlatViewField => { const { fieldMetadataId, viewId, ...createViewFieldInput } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( @@ -25,10 +30,19 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({ const createdAt = new Date().toISOString(); const viewFieldId = createViewFieldInput.id ?? v4(); + const { fieldMetadataUniversalIdentifier, viewUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewField', + foreignKeyValues: { fieldMetadataId, viewId }, + flatEntityMaps: { flatFieldMetadataMaps, flatViewMaps }, + }); + return { id: viewFieldId, fieldMetadataId, + fieldMetadataUniversalIdentifier, viewId, + viewUniversalIdentifier, workspaceId, createdAt: createdAt, updatedAt: createdAt, @@ -38,6 +52,7 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({ size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE, position: createViewFieldInput.position ?? 0, aggregateOperation: createViewFieldInput.aggregateOperation ?? null, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts index 36c3e49345..3bfbf211d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts @@ -58,11 +58,8 @@ export const fromViewFieldEntityToFlatViewField = ({ updatedAt: viewFieldEntity.updatedAt.toISOString(), deletedAt: viewFieldEntity.deletedAt?.toISOString() ?? null, universalIdentifier: viewFieldEntityWithoutRelations.universalIdentifier, - __universal: { - universalIdentifier: viewFieldEntity.universalIdentifier, - applicationUniversalIdentifier, - fieldMetadataUniversalIdentifier, - viewUniversalIdentifier, - }, + applicationUniversalIdentifier, + fieldMetadataUniversalIdentifier, + viewUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-create-view-filter-group-input-to-flat-view-filter-group-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-create-view-filter-group-input-to-flat-view-filter-group-to-create.util.ts index 9b3dfb4c4c..fbbc9dc43a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-create-view-filter-group-input-to-flat-view-filter-group-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-create-view-filter-group-input-to-flat-view-filter-group-to-create.util.ts @@ -1,6 +1,9 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group.type'; import { type CreateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/create-view-filter-group.input'; import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator'; @@ -8,12 +11,17 @@ import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view export const fromCreateViewFilterGroupInputToFlatViewFilterGroupToCreate = ({ createViewFilterGroupInput: rawCreateViewFilterGroupInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatViewMaps, + flatViewFilterGroupMaps, }: { createViewFilterGroupInput: CreateViewFilterGroupInput; workspaceId: string; - workspaceCustomApplicationId: string; -}): FlatViewFilterGroup => { + flatApplication: FlatApplication; +} & Pick< + AllFlatEntityMaps, + 'flatViewMaps' | 'flatViewFilterGroupMaps' +>): FlatViewFilterGroup => { const { viewId, ...createViewFilterGroupInput } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( rawCreateViewFilterGroupInput, @@ -23,9 +31,21 @@ export const fromCreateViewFilterGroupInputToFlatViewFilterGroupToCreate = ({ const createdAt = new Date().toISOString(); const viewFilterGroupId = createViewFilterGroupInput.id ?? v4(); + const { viewUniversalIdentifier, parentViewFilterGroupUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewFilterGroup', + foreignKeyValues: { + viewId, + parentViewFilterGroupId: + createViewFilterGroupInput.parentViewFilterGroupId, + }, + flatEntityMaps: { flatViewMaps, flatViewFilterGroupMaps }, + }); + return { id: viewFilterGroupId, viewId, + viewUniversalIdentifier, workspaceId, createdAt, updatedAt: createdAt, @@ -36,10 +56,14 @@ export const fromCreateViewFilterGroupInputToFlatViewFilterGroupToCreate = ({ ViewFilterGroupLogicalOperator.AND, parentViewFilterGroupId: createViewFilterGroupInput.parentViewFilterGroupId ?? null, + parentViewFilterGroupUniversalIdentifier, positionInViewFilterGroup: createViewFilterGroupInput.positionInViewFilterGroup ?? null, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, viewFilterIds: [], + viewFilterUniversalIdentifiers: [], childViewFilterGroupIds: [], + childViewFilterGroupUniversalIdentifiers: [], }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-update-view-filter-group-input-to-flat-view-filter-group-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-update-view-filter-group-input-to-flat-view-filter-group-to-update-or-throw.util.ts index 308c3bb787..77449d468b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-update-view-filter-group-input-to-flat-view-filter-group-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-update-view-filter-group-input-to-flat-view-filter-group-to-update-or-throw.util.ts @@ -4,8 +4,9 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties, } from 'twenty-shared/utils'; -import { FLAT_VIEW_FILTER_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter-group/constants/flat-view-filter-group-editable-properties.constant'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { FLAT_VIEW_FILTER_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter-group/constants/flat-view-filter-group-editable-properties.constant'; import { type FlatViewFilterGroupMaps } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group-maps.type'; import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group.type'; import { type UpdateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/update-view-filter-group.input'; @@ -47,9 +48,26 @@ export const fromUpdateViewFilterGroupInputToFlatViewFilterGroupToUpdateOrThrow FLAT_VIEW_FILTER_GROUP_EDITABLE_PROPERTIES, ); - return mergeUpdateInExistingRecord({ + const flatViewFilterGroupToUpdate = mergeUpdateInExistingRecord({ existing: existingFlatViewFilterGroupToUpdate, properties: FLAT_VIEW_FILTER_GROUP_EDITABLE_PROPERTIES, update: updatedEditableFieldProperties, }); + + if (updatedEditableFieldProperties.parentViewFilterGroupId !== undefined) { + const { parentViewFilterGroupUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewFilterGroup', + foreignKeyValues: { + parentViewFilterGroupId: + flatViewFilterGroupToUpdate.parentViewFilterGroupId, + }, + flatEntityMaps: { flatViewFilterGroupMaps }, + }); + + flatViewFilterGroupToUpdate.parentViewFilterGroupUniversalIdentifier = + parentViewFilterGroupUniversalIdentifier; + } + + return flatViewFilterGroupToUpdate; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts index 7b11221027..cb349c0ff4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts @@ -73,19 +73,16 @@ export const fromViewFilterGroupEntityToFlatViewFilterGroup = ({ viewFilterIds: viewFilterGroupEntity.viewFilters?.map(({ id }) => id) ?? [], childViewFilterGroupIds: viewFilterGroupEntity.childViewFilterGroups?.map(({ id }) => id) ?? [], - __universal: { - universalIdentifier: viewFilterGroupEntity.universalIdentifier, - applicationUniversalIdentifier, - parentViewFilterGroupUniversalIdentifier, - viewUniversalIdentifier, - viewFilterUniversalIdentifiers: - viewFilterGroupEntity.viewFilters?.map( - ({ universalIdentifier }) => universalIdentifier, - ) ?? [], - childViewFilterGroupUniversalIdentifiers: - viewFilterGroupEntity.childViewFilterGroups?.map( - ({ universalIdentifier }) => universalIdentifier, - ) ?? [], - }, + applicationUniversalIdentifier, + parentViewFilterGroupUniversalIdentifier, + viewUniversalIdentifier, + viewFilterUniversalIdentifiers: + viewFilterGroupEntity.viewFilters?.map( + ({ universalIdentifier }) => universalIdentifier, + ) ?? [], + childViewFilterGroupUniversalIdentifiers: + viewFilterGroupEntity.childViewFilterGroups?.map( + ({ universalIdentifier }) => universalIdentifier, + ) ?? [], }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts index 13ab260ea0..068bf2da4a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts @@ -2,18 +2,27 @@ import { ViewFilterOperand } from 'twenty-shared/types'; import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type'; import { type CreateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input'; export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({ createViewFilterInput: rawCreateViewFilterInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatFieldMetadataMaps, + flatViewMaps, + flatViewFilterGroupMaps, }: { createViewFilterInput: CreateViewFilterInput; workspaceId: string; - workspaceCustomApplicationId: string; -}): FlatViewFilter => { + flatApplication: FlatApplication; +} & Pick< + AllFlatEntityMaps, + 'flatFieldMetadataMaps' | 'flatViewMaps' | 'flatViewFilterGroupMaps' +>): FlatViewFilter => { const { fieldMetadataId, viewId, value, ...createViewFilterInput } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( rawCreateViewFilterInput, @@ -30,10 +39,30 @@ export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({ const createdAt = new Date().toISOString(); const viewFilterId = createViewFilterInput.id ?? v4(); + const { + fieldMetadataUniversalIdentifier, + viewUniversalIdentifier, + viewFilterGroupUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewFilter', + foreignKeyValues: { + fieldMetadataId, + viewId, + viewFilterGroupId: createViewFilterInput.viewFilterGroupId, + }, + flatEntityMaps: { + flatFieldMetadataMaps, + flatViewMaps, + flatViewFilterGroupMaps, + }, + }); + return { id: viewFilterId, fieldMetadataId, + fieldMetadataUniversalIdentifier, viewId, + viewUniversalIdentifier, workspaceId, createdAt: createdAt, updatedAt: createdAt, @@ -42,9 +71,11 @@ export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({ operand: createViewFilterInput.operand ?? ViewFilterOperand.CONTAINS, value: value, viewFilterGroupId: createViewFilterInput.viewFilterGroupId ?? null, + viewFilterGroupUniversalIdentifier, positionInViewFilterGroup: createViewFilterInput.positionInViewFilterGroup ?? null, subFieldName: createViewFilterInput.subFieldName ?? null, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-update-view-filter-input-to-flat-view-filter-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-update-view-filter-input-to-flat-view-filter-to-update-or-throw.util.ts index 97d6244cce..17eaaee13a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-update-view-filter-input-to-flat-view-filter-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-update-view-filter-input-to-flat-view-filter-to-update-or-throw.util.ts @@ -5,8 +5,10 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties, } from 'twenty-shared/utils'; -import { FLAT_VIEW_FILTER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-editable-properties.constant'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { FLAT_VIEW_FILTER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-editable-properties.constant'; import { type FlatViewFilterMaps } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter-maps.type'; import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type'; import { type UpdateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/update-view-filter.input'; @@ -19,10 +21,15 @@ import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing- export const fromUpdateViewFilterInputToFlatViewFilterToUpdateOrThrow = ({ updateViewFilterInput: rawUpdateViewFilterInput, flatViewFilterMaps, + flatFieldMetadataMaps, + flatViewFilterGroupMaps, }: { updateViewFilterInput: UpdateViewFilterInput; flatViewFilterMaps: FlatViewFilterMaps; -}): FlatViewFilter => { +} & Pick< + AllFlatEntityMaps, + 'flatFieldMetadataMaps' | 'flatViewFilterGroupMaps' +>): FlatViewFilter => { const { id: viewFilterToUpdateId } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( rawUpdateViewFilterInput, @@ -46,9 +53,39 @@ export const fromUpdateViewFilterInputToFlatViewFilterToUpdateOrThrow = ({ FLAT_VIEW_FILTER_EDITABLE_PROPERTIES, ); - return mergeUpdateInExistingRecord({ + const flatViewFilterToUpdate = mergeUpdateInExistingRecord({ existing: existingFlatViewFilterToUpdate, properties: FLAT_VIEW_FILTER_EDITABLE_PROPERTIES, update: updatedEditableFieldProperties, }); + + if (updatedEditableFieldProperties.fieldMetadataId !== undefined) { + const { fieldMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewFilter', + foreignKeyValues: { + fieldMetadataId: flatViewFilterToUpdate.fieldMetadataId, + }, + flatEntityMaps: { flatFieldMetadataMaps }, + }); + + flatViewFilterToUpdate.fieldMetadataUniversalIdentifier = + fieldMetadataUniversalIdentifier; + } + + if (updatedEditableFieldProperties.viewFilterGroupId !== undefined) { + const { viewFilterGroupUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'viewFilter', + foreignKeyValues: { + viewFilterGroupId: flatViewFilterToUpdate.viewFilterGroupId, + }, + flatEntityMaps: { flatViewFilterGroupMaps }, + }); + + flatViewFilterToUpdate.viewFilterGroupUniversalIdentifier = + viewFilterGroupUniversalIdentifier; + } + + return flatViewFilterToUpdate; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts index 0f8d4df1db..c3f89f1886 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts @@ -75,12 +75,9 @@ export const fromViewFilterEntityToFlatViewFilter = ({ updatedAt: viewFilterEntity.updatedAt.toISOString(), deletedAt: viewFilterEntity.deletedAt?.toISOString() ?? null, universalIdentifier: viewFilterEntityWithoutRelations.universalIdentifier, - __universal: { - universalIdentifier: viewFilterEntity.universalIdentifier, - applicationUniversalIdentifier, - fieldMetadataUniversalIdentifier, - viewFilterGroupUniversalIdentifier, - viewUniversalIdentifier, - }, + applicationUniversalIdentifier, + fieldMetadataUniversalIdentifier, + viewFilterGroupUniversalIdentifier, + viewUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/compute-flat-view-groups-on-view-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/compute-flat-view-groups-on-view-create.util.ts index cc55c5bf55..7a107c15de 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/compute-flat-view-groups-on-view-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/compute-flat-view-groups-on-view-create.util.ts @@ -12,11 +12,13 @@ import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/ type ComputeFlatViewGroupsOnViewCreateArgs = { flatViewToCreateId: string; + flatViewToCreateUniversalIdentifier: string; mainGroupByFieldMetadataId: string; } & Pick; export const computeFlatViewGroupsOnViewCreate = ({ flatViewToCreateId, + flatViewToCreateUniversalIdentifier, mainGroupByFieldMetadataId, flatFieldMetadataMaps, }: ComputeFlatViewGroupsOnViewCreateArgs): FlatViewGroup[] => { @@ -43,6 +45,7 @@ export const computeFlatViewGroupsOnViewCreate = ({ id: viewGroupId, fieldMetadataId: mainGroupByFieldMetadata.id, viewId: flatViewToCreateId, + viewUniversalIdentifier: flatViewToCreateUniversalIdentifier, workspaceId: mainGroupByFieldMetadata.workspaceId, createdAt, updatedAt: createdAt, @@ -52,6 +55,8 @@ export const computeFlatViewGroupsOnViewCreate = ({ fieldValue: option.value, position: index, applicationId: mainGroupByFieldMetadata.applicationId, + applicationUniversalIdentifier: + mainGroupByFieldMetadata.applicationUniversalIdentifier, }; }); @@ -64,6 +69,7 @@ export const computeFlatViewGroupsOnViewCreate = ({ flatViewGroups.push({ id: emptyGroupId, viewId: flatViewToCreateId, + viewUniversalIdentifier: flatViewToCreateUniversalIdentifier, workspaceId: mainGroupByFieldMetadata.workspaceId, createdAt, updatedAt: createdAt, @@ -73,6 +79,8 @@ export const computeFlatViewGroupsOnViewCreate = ({ fieldValue: '', position: emptyGroupPosition, applicationId: mainGroupByFieldMetadata.applicationId, + applicationUniversalIdentifier: + mainGroupByFieldMetadata.applicationUniversalIdentifier, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts index 64ce5c99ee..d20a6efc4e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts @@ -1,18 +1,22 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input'; export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({ createViewGroupInput: rawCreateViewGroupInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatViewMaps, }: { createViewGroupInput: CreateViewGroupInput; workspaceId: string; - workspaceCustomApplicationId: string; -}): FlatViewGroup => { + flatApplication: FlatApplication; +} & Pick): FlatViewGroup => { const { viewId, ...createViewGroupInput } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( rawCreateViewGroupInput, @@ -22,9 +26,18 @@ export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({ const createdAt = new Date().toISOString(); const viewGroupId = createViewGroupInput.id ?? v4(); + const { viewUniversalIdentifier } = resolveEntityRelationUniversalIdentifiers( + { + metadataName: 'viewGroup', + foreignKeyValues: { viewId }, + flatEntityMaps: { flatViewMaps }, + }, + ); + return { id: viewGroupId, viewId, + viewUniversalIdentifier, workspaceId, createdAt: createdAt, updatedAt: createdAt, @@ -33,6 +46,7 @@ export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({ isVisible: createViewGroupInput.isVisible ?? true, fieldValue: createViewGroupInput.fieldValue, position: createViewGroupInput.position ?? 0, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts index 1041b45595..eb60547411 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts @@ -45,10 +45,7 @@ export const fromViewGroupEntityToFlatViewGroup = ({ updatedAt: viewGroupEntity.updatedAt.toISOString(), deletedAt: viewGroupEntity.deletedAt?.toISOString() ?? null, universalIdentifier: viewGroupEntityWithoutRelations.universalIdentifier, - __universal: { - universalIdentifier: viewGroupEntity.universalIdentifier, - applicationUniversalIdentifier, - viewUniversalIdentifier, - }, + applicationUniversalIdentifier, + viewUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util.ts deleted file mode 100644 index 7f0818d389..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { isDefined } from 'twenty-shared/utils'; - -import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; - -type FlatViewGroupsByViewId = { - flatViewGroupRecordByViewId: Record>; - highestViewGroupPositionByViewId: Record; -}; -export const reduceFlatViewGroupsByViewId = ({ - flatViewGroups, -}: { - flatViewGroups: FlatViewGroup[]; -}): FlatViewGroupsByViewId => { - const initialAccumulator: FlatViewGroupsByViewId = { - flatViewGroupRecordByViewId: {}, - highestViewGroupPositionByViewId: {}, - }; - - return flatViewGroups.reduce((accumulator, flatViewGroup) => { - const accumulatorHighestPosition = - accumulator.highestViewGroupPositionByViewId[flatViewGroup.viewId]; - - return { - flatViewGroupRecordByViewId: { - ...accumulator.flatViewGroupRecordByViewId, - [flatViewGroup.viewId]: { - ...accumulator.flatViewGroupRecordByViewId[flatViewGroup.viewId], - [flatViewGroup.id]: flatViewGroup, - }, - }, - highestViewGroupPositionByViewId: { - ...accumulator.highestViewGroupPositionByViewId, - [flatViewGroup.viewId]: isDefined(accumulatorHighestPosition) - ? Math.max(accumulatorHighestPosition, flatViewGroup.position) - : flatViewGroup.position, - }, - }; - }, initialAccumulator); -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-universal-identifier.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-universal-identifier.util.ts new file mode 100644 index 0000000000..0508206639 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-universal-identifier.util.ts @@ -0,0 +1,49 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; + +type FlatViewGroupsByViewUniversalIdentifier = { + flatViewGroupRecordByViewUniversalIdentifier: Record< + string, + Record + >; + highestViewGroupPositionByViewUniversalIdentifier: Record; +}; + +export const reduceFlatViewGroupsByViewUniversalIdentifier = ({ + flatViewGroups, +}: { + flatViewGroups: FlatViewGroup[]; +}): FlatViewGroupsByViewUniversalIdentifier => { + const initialAccumulator: FlatViewGroupsByViewUniversalIdentifier = { + flatViewGroupRecordByViewUniversalIdentifier: {}, + highestViewGroupPositionByViewUniversalIdentifier: {}, + }; + + return flatViewGroups.reduce((accumulator, flatViewGroup) => { + const accumulatorHighestPosition = + accumulator.highestViewGroupPositionByViewUniversalIdentifier[ + flatViewGroup.viewUniversalIdentifier + ]; + + return { + flatViewGroupRecordByViewUniversalIdentifier: { + ...accumulator.flatViewGroupRecordByViewUniversalIdentifier, + [flatViewGroup.viewUniversalIdentifier]: { + ...accumulator.flatViewGroupRecordByViewUniversalIdentifier[ + flatViewGroup.viewUniversalIdentifier + ], + [flatViewGroup.id]: flatViewGroup, + }, + }, + highestViewGroupPositionByViewUniversalIdentifier: { + ...accumulator.highestViewGroupPositionByViewUniversalIdentifier, + [flatViewGroup.viewUniversalIdentifier]: isDefined( + accumulatorHighestPosition, + ) + ? Math.max(accumulatorHighestPosition, flatViewGroup.position) + : flatViewGroup.position, + }, + }; + }, initialAccumulator); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts index 9336f572ae..6a2d953a18 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts @@ -4,7 +4,9 @@ import { } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; import { computeFlatViewGroupsOnViewCreate } from 'src/engine/metadata-modules/flat-view-group/utils/compute-flat-view-groups-on-view-create.util'; import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; @@ -17,14 +19,16 @@ export const fromCreateViewInputToFlatViewToCreate = ({ createViewInput: rawCreateViewInput, workspaceId, createdByUserWorkspaceId, - workspaceCustomApplicationId, + flatApplication, flatFieldMetadataMaps, + flatObjectMetadataMaps, }: { createViewInput: CreateViewInput; workspaceId: string; createdByUserWorkspaceId?: string; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps']; + flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; }): { flatViewToCreate: FlatView; flatViewGroupsToCreate: FlatViewGroup[]; @@ -38,9 +42,27 @@ export const fromCreateViewInputToFlatViewToCreate = ({ const createdAt = new Date().toISOString(); const viewId = createViewInput.id ?? v4(); + const { + objectMetadataUniversalIdentifier, + calendarFieldMetadataUniversalIdentifier, + kanbanAggregateOperationFieldMetadataUniversalIdentifier, + mainGroupByFieldMetadataUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'view', + foreignKeyValues: { + objectMetadataId, + calendarFieldMetadataId: createViewInput.calendarFieldMetadataId, + kanbanAggregateOperationFieldMetadataId: + createViewInput.kanbanAggregateOperationFieldMetadataId, + mainGroupByFieldMetadataId: createViewInput.mainGroupByFieldMetadataId, + }, + flatEntityMaps: { flatObjectMetadataMaps, flatFieldMetadataMaps }, + }); + const flatViewToCreate = { id: viewId, objectMetadataId, + objectMetadataUniversalIdentifier, workspaceId, name: createViewInput.name, createdAt: createdAt, @@ -49,6 +71,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({ isCustom: true, anyFieldFilterValue: createViewInput.anyFieldFilterValue ?? null, calendarFieldMetadataId: createViewInput.calendarFieldMetadataId ?? null, + calendarFieldMetadataUniversalIdentifier, calendarLayout: createViewInput.calendarLayout ?? null, icon: createViewInput.icon, isCompact: createViewInput.isCompact ?? false, @@ -56,8 +79,10 @@ export const fromCreateViewInputToFlatViewToCreate = ({ kanbanAggregateOperation: createViewInput.kanbanAggregateOperation ?? null, kanbanAggregateOperationFieldMetadataId: createViewInput.kanbanAggregateOperationFieldMetadataId ?? null, + kanbanAggregateOperationFieldMetadataUniversalIdentifier, mainGroupByFieldMetadataId: createViewInput.mainGroupByFieldMetadataId ?? null, + mainGroupByFieldMetadataUniversalIdentifier, key: createViewInput.key ?? null, openRecordIn: createViewInput.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL, position: createViewInput.position ?? 0, @@ -66,10 +91,15 @@ export const fromCreateViewInputToFlatViewToCreate = ({ visibility: createViewInput.visibility ?? ViewVisibility.WORKSPACE, createdByUserWorkspaceId: createdByUserWorkspaceId ?? null, viewFieldIds: [], + viewFieldUniversalIdentifiers: [], viewFilterIds: [], + viewFilterUniversalIdentifiers: [], viewGroupIds: [], + viewGroupUniversalIdentifiers: [], viewFilterGroupIds: [], - applicationId: workspaceCustomApplicationId, + viewFilterGroupUniversalIdentifiers: [], + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, }; let flatViewGroupsToCreate: FlatViewGroup[] = []; @@ -77,6 +107,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({ if (isDefined(flatViewToCreate.mainGroupByFieldMetadataId)) { flatViewGroupsToCreate = computeFlatViewGroupsOnViewCreate({ flatViewToCreateId: flatViewToCreate.id, + flatViewToCreateUniversalIdentifier: flatViewToCreate.universalIdentifier, mainGroupByFieldMetadataId: flatViewToCreate.mainGroupByFieldMetadataId, flatFieldMetadataMaps, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts index e1025745d6..7d7d9c46ae 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts @@ -7,6 +7,7 @@ import { import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatViewGroupMaps } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group-maps.type'; import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; @@ -67,6 +68,53 @@ export const fromUpdateViewInputToFlatViewToUpdateOrThrow = ({ update: updatedEditableFieldProperties, }); + if ( + updatedEditableFieldProperties.kanbanAggregateOperationFieldMetadataId !== + undefined + ) { + const { kanbanAggregateOperationFieldMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'view', + foreignKeyValues: { + kanbanAggregateOperationFieldMetadataId: + flatViewToUpdate.kanbanAggregateOperationFieldMetadataId, + }, + flatEntityMaps: { flatFieldMetadataMaps }, + }); + + flatViewToUpdate.kanbanAggregateOperationFieldMetadataUniversalIdentifier = + kanbanAggregateOperationFieldMetadataUniversalIdentifier; + } + + if (updatedEditableFieldProperties.calendarFieldMetadataId !== undefined) { + const { calendarFieldMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'view', + foreignKeyValues: { + calendarFieldMetadataId: flatViewToUpdate.calendarFieldMetadataId, + }, + flatEntityMaps: { flatFieldMetadataMaps }, + }); + + flatViewToUpdate.calendarFieldMetadataUniversalIdentifier = + calendarFieldMetadataUniversalIdentifier; + } + + if (updatedEditableFieldProperties.mainGroupByFieldMetadataId !== undefined) { + const { mainGroupByFieldMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'view', + foreignKeyValues: { + mainGroupByFieldMetadataId: + flatViewToUpdate.mainGroupByFieldMetadataId, + }, + flatEntityMaps: { flatFieldMetadataMaps }, + }); + + flatViewToUpdate.mainGroupByFieldMetadataUniversalIdentifier = + mainGroupByFieldMetadataUniversalIdentifier; + } + // If changing visibility from WORKSPACE to UNLISTED, ensure createdByUserWorkspaceId is set // This prevents the view from disappearing for the user making the change if ( diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts index d6931db5dd..221b19559b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts @@ -98,26 +98,23 @@ export const fromViewEntityToFlatView = ({ viewFilterIds: viewEntity.viewFilters.map(({ id }) => id), viewGroupIds: viewEntity.viewGroups.map(({ id }) => id), viewFilterGroupIds: viewEntity.viewFilterGroups?.map(({ id }) => id) ?? [], - __universal: { - universalIdentifier: viewEntity.universalIdentifier, - applicationUniversalIdentifier, - objectMetadataUniversalIdentifier, - kanbanAggregateOperationFieldMetadataUniversalIdentifier, - calendarFieldMetadataUniversalIdentifier, - mainGroupByFieldMetadataUniversalIdentifier, - viewFieldUniversalIdentifiers: viewEntity.viewFields.map( + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + kanbanAggregateOperationFieldMetadataUniversalIdentifier, + calendarFieldMetadataUniversalIdentifier, + mainGroupByFieldMetadataUniversalIdentifier, + viewFieldUniversalIdentifiers: viewEntity.viewFields.map( + ({ universalIdentifier }) => universalIdentifier, + ), + viewFilterUniversalIdentifiers: viewEntity.viewFilters.map( + ({ universalIdentifier }) => universalIdentifier, + ), + viewGroupUniversalIdentifiers: viewEntity.viewGroups.map( + ({ universalIdentifier }) => universalIdentifier, + ), + viewFilterGroupUniversalIdentifiers: + viewEntity.viewFilterGroups?.map( ({ universalIdentifier }) => universalIdentifier, - ), - viewFilterUniversalIdentifiers: viewEntity.viewFilters.map( - ({ universalIdentifier }) => universalIdentifier, - ), - viewGroupUniversalIdentifiers: viewEntity.viewGroups.map( - ({ universalIdentifier }) => universalIdentifier, - ), - viewFilterGroupUniversalIdentifiers: - viewEntity.viewFilterGroups?.map( - ({ universalIdentifier }) => universalIdentifier, - ) ?? [], - }, + ) ?? [], }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/handle-flat-view-update-side-effect.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/handle-flat-view-update-side-effect.util.ts index 99d870ba32..5f5096561f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/handle-flat-view-update-side-effect.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/handle-flat-view-update-side-effect.util.ts @@ -51,6 +51,7 @@ export const handleFlatViewUpdateSideEffect = ({ sideEffectResult.flatViewGroupsToCreate = computeFlatViewGroupsOnViewCreate({ flatViewToCreateId: toFlatView.id, + flatViewToCreateUniversalIdentifier: toFlatView.universalIdentifier, mainGroupByFieldMetadataId: newMainGroupByFieldMetadataId, flatFieldMetadataMaps, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-create-webhook-input-to-flat-webhook-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-create-webhook-input-to-flat-webhook-to-create.util.ts index 8d0d7a7b43..a484e67765 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-create-webhook-input-to-flat-webhook-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-create-webhook-input-to-flat-webhook-to-create.util.ts @@ -1,6 +1,7 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type FlatWebhook } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook.type'; import { type CreateWebhookInput } from 'src/engine/metadata-modules/webhook/dtos/create-webhook.input'; import { generateWebhookSecret } from 'src/engine/metadata-modules/webhook/utils/generate-webhook-secret.util'; @@ -8,11 +9,11 @@ import { generateWebhookSecret } from 'src/engine/metadata-modules/webhook/utils export const fromCreateWebhookInputToFlatWebhookToCreate = ({ createWebhookInput, workspaceId, - applicationId, + flatApplication, }: { createWebhookInput: CreateWebhookInput; workspaceId: string; - applicationId: string; + flatApplication: FlatApplication; }): FlatWebhook => { const now = new Date().toISOString(); @@ -36,6 +37,7 @@ export const fromCreateWebhookInputToFlatWebhookToCreate = ({ updatedAt: now, deletedAt: null, universalIdentifier: id, - applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-webhook-entity-to-flat-webhook.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-webhook-entity-to-flat-webhook.util.ts index 932f04489c..15285c7630 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-webhook-entity-to-flat-webhook.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-webhook/utils/from-webhook-entity-to-flat-webhook.util.ts @@ -33,9 +33,6 @@ export const fromWebhookEntityToFlatWebhook = ({ createdAt: webhookEntity.createdAt.toISOString(), updatedAt: webhookEntity.updatedAt.toISOString(), deletedAt: webhookEntity.deletedAt?.toISOString() ?? null, - __universal: { - universalIdentifier: webhookEntity.universalIdentifier, - applicationUniversalIdentifier, - }, + applicationUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/front-component/front-component.service.ts b/packages/twenty-server/src/engine/metadata-modules/front-component/front-component.service.ts index 370e3e2326..3ef572f720 100644 --- a/packages/twenty-server/src/engine/metadata-modules/front-component/front-component.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/front-component/front-component.service.ts @@ -80,7 +80,7 @@ export class FrontComponentService { fromCreateFrontComponentInputToFlatFrontComponentToCreate({ createFrontComponentInput: input, workspaceId, - applicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util.ts index 30f11a0b30..1fb3d50afa 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util.ts @@ -78,9 +78,6 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({ ? DEFAULT_TOOL_INPUT_SCHEMA : null, isTool: rawCreateLogicFunctionInput?.isTool ?? false, - __universal: { - applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier, - universalIdentifier, - }, + applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts index 10d268b762..4198b1e69e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts @@ -34,9 +34,6 @@ export const fromLogicFunctionEntityToFlatLogicFunction = ({ createdAt: logicFunctionEntity.createdAt.toISOString(), updatedAt: logicFunctionEntity.updatedAt.toISOString(), deletedAt: logicFunctionEntity.deletedAt?.toISOString() ?? null, - __universal: { - universalIdentifier: logicFunctionEntity.universalIdentifier, - applicationUniversalIdentifier, - }, + applicationUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts index 6e48eebf9a..95d572300f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts @@ -1,23 +1,25 @@ import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; import { AiAgentMonitorModule } from 'src/engine/metadata-modules/ai/ai-agent-monitor/ai-agent-monitor.module'; import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module'; import { AiChatModule } from 'src/engine/metadata-modules/ai/ai-chat/ai-chat.module'; import { CommandMenuItemModule } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.module'; -import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; +import { FlatEntityMapsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-graphql-api-exception.filter'; import { FrontComponentModule } from 'src/engine/metadata-modules/front-component/front-component.module'; +import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module'; +import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; +import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; -import { WebhookModule } from 'src/engine/metadata-modules/webhook/webhook.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; import { RouteTriggerModule } from 'src/engine/metadata-modules/route-trigger/route-trigger.module'; import { SearchFieldMetadataModule } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.module'; -import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module'; -import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; import { SkillModule } from 'src/engine/metadata-modules/skill/skill.module'; import { ViewModule } from 'src/engine/metadata-modules/view/view.module'; +import { WebhookModule } from 'src/engine/metadata-modules/webhook/webhook.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; @Module({ @@ -42,7 +44,12 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work RouteTriggerModule, WebhookModule, ], - providers: [], + providers: [ + { + provide: APP_FILTER, + useClass: FlatEntityMapsGraphqlApiExceptionFilter, + }, + ], exports: [ DataSourceModule, FieldMetadataModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts index c4235b09a0..5e3b015ad7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts @@ -148,13 +148,20 @@ export class NavigationMenuItemService { { workspaceId }, ); - const { flatNavigationMenuItemMaps: existingFlatNavigationMenuItemMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatNavigationMenuItemMaps'], - }, - ); + const { + flatNavigationMenuItemMaps: existingFlatNavigationMenuItemMaps, + flatObjectMetadataMaps, + flatViewMaps, + } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatNavigationMenuItemMaps', + 'flatObjectMetadataMaps', + 'flatViewMaps', + ], + }, + ); const normalizedInput: CreateNavigationMenuItemInput = { ...input, @@ -168,8 +175,10 @@ export class NavigationMenuItemService { fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate({ createNavigationMenuItemInput: normalizedInput, workspaceId, - applicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, flatNavigationMenuItemMaps: existingFlatNavigationMenuItemMaps, + flatObjectMetadataMaps, + flatViewMaps, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 2e50c5e1cc..99de995c06 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -397,21 +397,35 @@ export class ObjectMetadataService extends TypeOrmQueryService { const { flatNavigationMenuItemMaps } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ @@ -613,12 +640,17 @@ export class ObjectMetadataService extends TypeOrmQueryService tab), newPageLayoutId: newFlatPageLayout.id, workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatPageLayoutMaps: optimisticFlatPageLayoutMaps, }); + const optimisticFlatPageLayoutTabMaps = newFlatTabs.reduce( + (maps, flatTab) => + addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: flatTab, + flatEntityMaps: maps, + }), + createEmptyFlatEntityMaps(), + ); + const newFlatWidgets = this.createDuplicatedWidgets({ originalTabsWithWidgets, originalTabIdToNewTabIdMap, workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatPageLayoutTabMaps: optimisticFlatPageLayoutTabMaps, + flatObjectMetadataMaps, }); const validateAndBuildResult = @@ -146,11 +169,7 @@ export class PageLayoutDuplicationService { ); } - private async getPageLayoutFlatEntityMaps(workspaceId: string): Promise<{ - flatPageLayoutMaps: FlatPageLayoutMaps; - flatPageLayoutTabMaps: FlatPageLayoutTabMaps; - flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps; - }> { + private async getPageLayoutFlatEntityMaps(workspaceId: string) { return this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, @@ -158,6 +177,7 @@ export class PageLayoutDuplicationService { 'flatPageLayoutMaps', 'flatPageLayoutTabMaps', 'flatPageLayoutWidgetMaps', + 'flatObjectMetadataMaps', ], }, ); @@ -224,12 +244,14 @@ export class PageLayoutDuplicationService { originalTabs, newPageLayoutId, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatPageLayoutMaps, }: { originalTabs: FlatPageLayoutTab[]; newPageLayoutId: string; workspaceId: string; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; + flatPageLayoutMaps: AllFlatEntityMaps['flatPageLayoutMaps']; }): { newFlatTabs: FlatPageLayoutTab[]; originalTabIdToNewTabIdMap: Map; @@ -245,7 +267,8 @@ export class PageLayoutDuplicationService { pageLayoutId: newPageLayoutId, }, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatPageLayoutMaps, }); originalTabIdToNewTabIdMap.set(originalTab.id, newFlatTab.id); @@ -260,7 +283,9 @@ export class PageLayoutDuplicationService { originalTabsWithWidgets, originalTabIdToNewTabIdMap, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, }: { originalTabsWithWidgets: { tab: FlatPageLayoutTab; @@ -268,7 +293,9 @@ export class PageLayoutDuplicationService { }[]; originalTabIdToNewTabIdMap: Map; workspaceId: string; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; + flatPageLayoutTabMaps: AllFlatEntityMaps['flatPageLayoutTabMaps']; + flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; }): FlatPageLayoutWidget[] { return originalTabsWithWidgets.flatMap(({ tab, widgets }) => { const newTabId = originalTabIdToNewTabIdMap.get(tab.id)!; @@ -284,7 +311,9 @@ export class PageLayoutDuplicationService { pageLayoutTabId: newTabId, }, workspaceId, - workspaceCustomApplicationId, + flatApplication, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, }), ); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts index a787ce288e..1414e761d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts @@ -6,8 +6,10 @@ import { v4 } from 'uuid'; import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant'; import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type'; import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; @@ -109,14 +111,28 @@ export class PageLayoutUpdateService { flatPageLayoutTabMaps, workspaceId, workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + workspaceCustomApplicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, }); + const { flatObjectMetadataMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatObjectMetadataMaps'], + }, + ); + const { widgetsToCreate, widgetsToUpdate, widgetsToDelete } = this.computeWidgetOperationsForAllTabs({ tabs, flatPageLayoutWidgetMaps, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, workspaceId, workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + workspaceCustomApplicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, }); const validateAndBuildResult = @@ -196,12 +212,14 @@ export class PageLayoutUpdateService { flatPageLayoutTabMaps, workspaceId, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, }: { existingPageLayout: FlatPageLayout; tabs: UpdatePageLayoutTabWithWidgetsInput[]; flatPageLayoutTabMaps: FlatPageLayoutTabMaps; workspaceId: string; workspaceCustomApplicationId: string; + workspaceCustomApplicationUniversalIdentifier: string; }): { tabsToCreate: FlatPageLayoutTab[]; tabsToUpdate: FlatPageLayoutTab[]; @@ -238,13 +256,17 @@ export class PageLayoutUpdateService { title: tabInput.title, position: tabInput.position, pageLayoutId: existingPageLayout.id, + pageLayoutUniversalIdentifier: existingPageLayout.universalIdentifier, workspaceId, createdAt: now.toISOString(), updatedAt: now.toISOString(), deletedAt: null, universalIdentifier: tabId, applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, widgetIds: [], + widgetUniversalIdentifiers: [], icon: null, layoutMode: PageLayoutTabLayoutMode.GRID, }; @@ -316,13 +338,19 @@ export class PageLayoutUpdateService { private computeWidgetOperationsForAllTabs({ tabs, flatPageLayoutWidgetMaps, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, workspaceId, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, }: { tabs: UpdatePageLayoutTabWithWidgetsInput[]; flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps; + flatPageLayoutTabMaps: FlatPageLayoutTabMaps; + flatObjectMetadataMaps: MetadataFlatEntityMaps<'objectMetadata'>; workspaceId: string; workspaceCustomApplicationId: string; + workspaceCustomApplicationUniversalIdentifier: string; }): { widgetsToCreate: FlatPageLayoutWidget[]; widgetsToUpdate: FlatPageLayoutWidget[]; @@ -337,8 +365,11 @@ export class PageLayoutUpdateService { tabId: tabInput.id, widgets: tabInput.widgets, flatPageLayoutWidgetMaps, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, workspaceId, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, }); allWidgetsToCreate.push(...widgetsToCreate); @@ -356,14 +387,20 @@ export class PageLayoutUpdateService { tabId, widgets, flatPageLayoutWidgetMaps, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, workspaceId, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, }: { tabId: string; widgets: UpdatePageLayoutWidgetWithIdInput[]; flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps; + flatPageLayoutTabMaps: FlatPageLayoutTabMaps; + flatObjectMetadataMaps: MetadataFlatEntityMaps<'objectMetadata'>; workspaceId: string; workspaceCustomApplicationId: string; + workspaceCustomApplicationUniversalIdentifier: string; }): { widgetsToCreate: FlatPageLayoutWidget[]; widgetsToUpdate: FlatPageLayoutWidget[]; @@ -394,12 +431,29 @@ export class PageLayoutUpdateService { (widgetInput) => { const widgetId = widgetInput.id ?? v4(); + const { + pageLayoutTabUniversalIdentifier, + objectMetadataUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayoutWidget', + foreignKeyValues: { + pageLayoutTabId: widgetInput.pageLayoutTabId, + objectMetadataId: widgetInput.objectMetadataId, + }, + flatEntityMaps: { + flatPageLayoutTabMaps, + flatObjectMetadataMaps, + }, + }); + return { id: widgetId, pageLayoutTabId: widgetInput.pageLayoutTabId, + pageLayoutTabUniversalIdentifier, title: widgetInput.title, type: widgetInput.type, objectMetadataId: widgetInput.objectMetadataId ?? null, + objectMetadataUniversalIdentifier, gridPosition: widgetInput.gridPosition, position: widgetInput.position ?? null, configuration: widgetInput.configuration ?? null, @@ -409,6 +463,8 @@ export class PageLayoutUpdateService { deletedAt: null, universalIdentifier: widgetId, applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, conditionalDisplay: null, }; }, diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts index 7315375cee..b5f6f98d38 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts @@ -190,11 +190,20 @@ export class PageLayoutService { { workspaceId }, ); + const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatObjectMetadataMaps'], + }, + ); + const flatPageLayoutToCreate = fromCreatePageLayoutInputToFlatPageLayoutToCreate({ createPageLayoutInput, workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, }); const validateAndBuildResult = @@ -251,11 +260,14 @@ export class PageLayoutService { { workspaceId }, ); - const { flatPageLayoutMaps: existingFlatPageLayoutMaps } = + const { + flatPageLayoutMaps: existingFlatPageLayoutMaps, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, - flatMapsKeys: ['flatPageLayoutMaps'], + flatMapsKeys: ['flatPageLayoutMaps', 'flatObjectMetadataMaps'], }, ); @@ -268,6 +280,7 @@ export class PageLayoutService { fromUpdatePageLayoutInputToFlatPageLayoutToUpdateOrThrow({ updatePageLayoutInput, flatPageLayoutMaps: existingFlatPageLayoutMaps, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/role-target/services/role-target.service.ts b/packages/twenty-server/src/engine/metadata-modules/role-target/services/role-target.service.ts index 6e4bc11944..44322089fc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role-target/services/role-target.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role-target/services/role-target.service.ts @@ -4,8 +4,8 @@ import { isDefined } from 'twenty-shared/utils'; import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type'; import { CreateRoleTargetInput } from 'src/engine/metadata-modules/role-target/types/create-role-target.input'; import { fromCreateRoleTargetInputToFlatRoleTargetToCreate } from 'src/engine/metadata-modules/role-target/utils/from-create-role-target-input-to-flat-role-target-to-create.util'; @@ -56,11 +56,15 @@ export class RoleTargetService { return []; } - const { flatRoleTargetMaps } = + const { flatRoleTargetMaps, flatApplicationMaps, flatRoleMaps } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, - flatMapsKeys: ['flatRoleTargetMaps'], + flatMapsKeys: [ + 'flatRoleTargetMaps', + 'flatApplicationMaps', + 'flatRoleMaps', + ], }, ); @@ -75,16 +79,17 @@ export class RoleTargetService { const allFlatRoleTargetsToDelete: FlatRoleTarget[] = []; for (const createRoleTargetInput of createRoleTargetInputs) { + const flatApplication = isDefined(createRoleTargetInput.applicationId) + ? flatApplicationMaps.byId[createRoleTargetInput.applicationId] + : undefined; + const { flatRoleTargetToCreate, flatRoleTargetsToDelete } = fromCreateRoleTargetInputToFlatRoleTargetToCreate({ - createRoleTargetInput: { - ...createRoleTargetInput, - applicationId: - createRoleTargetInput.applicationId ?? - workspaceCustomFlatApplication.id, - }, + createRoleTargetInput, flatRoleTargetMaps, + flatRoleMaps, workspaceId, + flatApplication: flatApplication ?? workspaceCustomFlatApplication, }); allFlatRoleTargetsToCreate.push(flatRoleTargetToCreate); diff --git a/packages/twenty-server/src/engine/metadata-modules/role-target/utils/from-create-role-target-input-to-flat-role-target-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/role-target/utils/from-create-role-target-input-to-flat-role-target-to-create.util.ts index f3f074e38b..953681edc3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role-target/utils/from-create-role-target-input-to-flat-role-target-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role-target/utils/from-create-role-target-input-to-flat-role-target-to-create.util.ts @@ -1,7 +1,9 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type'; import { findFlatRoleTargetFromForeignKey } from 'src/engine/metadata-modules/flat-role-target/utils/find-flat-role-target-from-foreign-key.util'; import { type CreateRoleTargetInput } from 'src/engine/metadata-modules/role-target/types/create-role-target.input'; @@ -10,10 +12,13 @@ export const fromCreateRoleTargetInputToFlatRoleTargetToCreate = ({ createRoleTargetInput, workspaceId, flatRoleTargetMaps, + flatRoleMaps, + flatApplication, }: { - createRoleTargetInput: CreateRoleTargetInput & { applicationId: string }; + createRoleTargetInput: CreateRoleTargetInput; workspaceId: string; -} & Pick): { + flatApplication: FlatApplication; +} & Pick): { flatRoleTargetToCreate: FlatRoleTarget; flatRoleTargetsToDelete: FlatRoleTarget[]; } => { @@ -21,9 +26,18 @@ export const fromCreateRoleTargetInputToFlatRoleTargetToCreate = ({ const { roleId, targetId, targetMetadataForeignKey, universalIdentifier } = createRoleTargetInput; + const { roleUniversalIdentifier } = resolveEntityRelationUniversalIdentifiers( + { + metadataName: 'roleTarget', + foreignKeyValues: { roleId }, + flatEntityMaps: { flatRoleMaps }, + }, + ); + const flatRoleTargetToCreate: FlatRoleTarget = { id: v4(), roleId, + roleUniversalIdentifier, userWorkspaceId: null, agentId: null, apiKeyId: null, @@ -31,7 +45,8 @@ export const fromCreateRoleTargetInputToFlatRoleTargetToCreate = ({ updatedAt: now.toISOString(), universalIdentifier: universalIdentifier ?? v4(), workspaceId, - applicationId: createRoleTargetInput.applicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, [targetMetadataForeignKey]: targetId, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index 5d25aafa90..dd15213fc5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -15,6 +15,7 @@ import { import { msg } from '@lingui/core/macro'; import { PermissionFlagType } from 'twenty-shared/constants'; +import { isDefined } from 'twenty-shared/utils'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; @@ -32,6 +33,10 @@ import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.g import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service'; +import { + AgentException, + AgentExceptionCode, +} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util'; import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto'; @@ -64,6 +69,7 @@ import { UpsertRowLevelPermissionPredicatesResultDTO } from 'src/engine/metadata import { RowLevelPermissionPredicateGroupService } from 'src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate-group.service'; import { RowLevelPermissionPredicateService } from 'src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -91,6 +97,7 @@ export class RoleResolver { private readonly applicationService: ApplicationService, private readonly rowLevelPermissionPredicateService: RowLevelPermissionPredicateService, private readonly rowLevelPermissionPredicateGroupService: RowLevelPermissionPredicateGroupService, + private readonly workspaceCacheService: WorkspaceCacheService, ) {} @Query(() => [RoleDTO]) @@ -307,16 +314,32 @@ export class RoleResolver { workspace.id, ); - return agents.map((agentEntity) => - fromFlatAgentWithRoleIdToAgentDto({ + const { flatApplicationMaps } = + await this.workspaceCacheService.getOrRecompute(workspace.id, [ + 'flatApplicationMaps', + ]); + + return agents.map((agentEntity) => { + const flatApplication = + flatApplicationMaps.byId[agentEntity.applicationId]; + + if (!isDefined(flatApplication)) { + throw new AgentException( + `Application not found for agent ${agentEntity.id}`, + AgentExceptionCode.AGENT_NOT_FOUND, + ); + } + + return fromFlatAgentWithRoleIdToAgentDto({ ...agentEntity, createdAt: agentEntity.createdAt.toISOString(), updatedAt: agentEntity.updatedAt.toISOString(), deletedAt: agentEntity.deletedAt?.toISOString() ?? null, universalIdentifier: agentEntity.universalIdentifier, + applicationUniversalIdentifier: flatApplication.universalIdentifier, roleId: role.id, - }), - ); + }); + }); } @ResolveField('apiKeys', () => [ApiKeyForRoleDTO]) diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts index 66a1296135..060c289b42 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts @@ -111,7 +111,7 @@ export class RoleService { const flatRoleToCreate = fromCreateRoleInputToFlatRoleToCreate({ createRoleInput: input, workspaceId, - applicationId: ownerFlatApplication.id, + flatApplication: ownerFlatApplication, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts index 4c041657c5..84d3b631f6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts @@ -85,7 +85,7 @@ export class RowLevelPermissionPredicateGroupEntity { onDelete: 'CASCADE' }, ) @JoinColumn({ name: 'parentRowLevelPermissionPredicateGroupId' }) - parentRowLevelPermissionPredicateGroup: Relation; + parentRowLevelPermissionPredicateGroup: Relation | null; @OneToMany( () => RowLevelPermissionPredicateGroupEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity.ts index 47c175cf4d..71d3361c95 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity.ts @@ -87,7 +87,7 @@ export class RowLevelPermissionPredicateEntity onDelete: 'SET NULL', }) @JoinColumn({ name: 'workspaceMemberFieldMetadataId' }) - workspaceMemberFieldMetadata: Relation; + workspaceMemberFieldMetadata: Relation | null; @Column({ nullable: true, type: 'uuid' }) rowLevelPermissionPredicateGroupId: string | null; @@ -120,5 +120,5 @@ export class RowLevelPermissionPredicateEntity }, ) @JoinColumn({ name: 'rowLevelPermissionPredicateGroupId' }) - rowLevelPermissionPredicateGroup: Relation; + rowLevelPermissionPredicateGroup: Relation | null; } diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts index a29ff85247..fe78b765c1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts @@ -9,9 +9,11 @@ import { ApplicationService } from 'src/engine/core-modules/application/services import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.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 { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { fromFlatRowLevelPermissionPredicateGroupToDto } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-flat-row-level-permission-predicate-group-to-dto.util'; import { fromFlatRowLevelPermissionPredicateToDto } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-flat-row-level-permission-predicate-to-dto.util'; import { @@ -165,6 +167,9 @@ export class RowLevelPermissionPredicateService { const { flatRowLevelPermissionPredicateMaps, flatRowLevelPermissionPredicateGroupMaps, + flatRoleMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { @@ -172,6 +177,9 @@ export class RowLevelPermissionPredicateService { flatMapsKeys: [ 'flatRowLevelPermissionPredicateMaps', 'flatRowLevelPermissionPredicateGroupMaps', + 'flatRoleMaps', + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', ], }, ); @@ -205,7 +213,11 @@ export class RowLevelPermissionPredicateService { roleId, workspaceId, flatRowLevelPermissionPredicateGroupMaps, + flatRoleMaps, + flatObjectMetadataMaps, workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + workspaceCustomApplicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, }); const { predicatesToCreate, predicatesToUpdate, predicatesToDelete } = @@ -216,7 +228,13 @@ export class RowLevelPermissionPredicateService { objectMetadataId, workspaceId, flatRowLevelPermissionPredicateMaps, + flatRowLevelPermissionPredicateGroupMaps, + flatRoleMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + workspaceCustomApplicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, }); await this.runUpsertMigration({ @@ -277,14 +295,20 @@ export class RowLevelPermissionPredicateService { roleId, workspaceId, flatRowLevelPermissionPredicateGroupMaps, + flatRoleMaps, + flatObjectMetadataMaps, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, }: { existingGroups: FlatRowLevelPermissionPredicateGroup[]; inputGroups: RowLevelPermissionPredicateGroupInput[]; roleId: string; workspaceId: string; flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; + flatRoleMaps: AllFlatEntityMaps['flatRoleMaps']; + flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; workspaceCustomApplicationId: string; + workspaceCustomApplicationUniversalIdentifier: string; }): { groupsToCreate: FlatRowLevelPermissionPredicateGroup[]; groupsToUpdate: FlatRowLevelPermissionPredicateGroup[]; @@ -295,6 +319,13 @@ export class RowLevelPermissionPredicateService { const inputGroupIds = new Set(); + const { roleUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicateGroup', + foreignKeyValues: { roleId }, + flatEntityMaps: { flatRoleMaps }, + }); + for (const inputGroup of inputGroups) { const groupId = inputGroup.id ?? v4(); const createdAt = new Date().toISOString(); @@ -307,33 +338,69 @@ export class RowLevelPermissionPredicateService { }); if (isDefined(existingGroup) && existingGroup.deletedAt === null) { + const { parentRowLevelPermissionPredicateGroupUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicateGroup', + foreignKeyValues: { + parentRowLevelPermissionPredicateGroupId: + inputGroup.parentRowLevelPermissionPredicateGroupId, + }, + flatEntityMaps: { + flatRowLevelPermissionPredicateGroupMaps, + }, + }); + groupsToUpdate.push({ ...existingGroup, logicalOperator: inputGroup.logicalOperator, parentRowLevelPermissionPredicateGroupId: inputGroup.parentRowLevelPermissionPredicateGroupId ?? null, + parentRowLevelPermissionPredicateGroupUniversalIdentifier, positionInRowLevelPermissionPredicateGroup: inputGroup.positionInRowLevelPermissionPredicateGroup ?? null, updatedAt: createdAt, }); } else { + const { + objectMetadataUniversalIdentifier, + parentRowLevelPermissionPredicateGroupUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicateGroup', + foreignKeyValues: { + objectMetadataId: inputGroup.objectMetadataId, + parentRowLevelPermissionPredicateGroupId: + inputGroup.parentRowLevelPermissionPredicateGroupId, + }, + flatEntityMaps: { + flatObjectMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }, + }); + groupsToCreate.push({ id: groupId, workspaceId, roleId, + roleUniversalIdentifier, objectMetadataId: inputGroup.objectMetadataId, + objectMetadataUniversalIdentifier, logicalOperator: inputGroup.logicalOperator, parentRowLevelPermissionPredicateGroupId: inputGroup.parentRowLevelPermissionPredicateGroupId ?? null, + parentRowLevelPermissionPredicateGroupUniversalIdentifier, positionInRowLevelPermissionPredicateGroup: inputGroup.positionInRowLevelPermissionPredicateGroup ?? null, childRowLevelPermissionPredicateGroupIds: [], + childRowLevelPermissionPredicateGroupUniversalIdentifiers: [], rowLevelPermissionPredicateIds: [], + rowLevelPermissionPredicateUniversalIdentifiers: [], createdAt, updatedAt: createdAt, deletedAt: null, universalIdentifier: groupId, applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, }); } } @@ -360,7 +427,12 @@ export class RowLevelPermissionPredicateService { objectMetadataId, workspaceId, flatRowLevelPermissionPredicateMaps, + flatRowLevelPermissionPredicateGroupMaps, + flatRoleMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, workspaceCustomApplicationId, + workspaceCustomApplicationUniversalIdentifier, }: { existingPredicates: FlatRowLevelPermissionPredicate[]; inputPredicates: RowLevelPermissionPredicateInput[]; @@ -368,7 +440,12 @@ export class RowLevelPermissionPredicateService { objectMetadataId: string; workspaceId: string; flatRowLevelPermissionPredicateMaps: FlatEntityMaps; + flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; + flatRoleMaps: AllFlatEntityMaps['flatRoleMaps']; + flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; + flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps']; workspaceCustomApplicationId: string; + workspaceCustomApplicationUniversalIdentifier: string; }): { predicatesToCreate: FlatRowLevelPermissionPredicate[]; predicatesToUpdate: FlatRowLevelPermissionPredicate[]; @@ -379,6 +456,13 @@ export class RowLevelPermissionPredicateService { const inputPredicateIds = new Set(); + const { roleUniversalIdentifier, objectMetadataUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicate', + foreignKeyValues: { roleId, objectMetadataId }, + flatEntityMaps: { flatRoleMaps, flatObjectMetadataMaps }, + }); + for (const inputPredicate of inputPredicates) { const predicateId = inputPredicate.id ?? v4(); const createdAt = new Date().toISOString(); @@ -390,6 +474,25 @@ export class RowLevelPermissionPredicateService { flatEntityMaps: flatRowLevelPermissionPredicateMaps, }); + const { + fieldMetadataUniversalIdentifier, + rowLevelPermissionPredicateGroupUniversalIdentifier, + workspaceMemberFieldMetadataUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicate', + foreignKeyValues: { + fieldMetadataId: inputPredicate.fieldMetadataId, + rowLevelPermissionPredicateGroupId: + inputPredicate.rowLevelPermissionPredicateGroupId, + workspaceMemberFieldMetadataId: + inputPredicate.workspaceMemberFieldMetadataId, + }, + flatEntityMaps: { + flatFieldMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }, + }); + if ( isDefined(existingPredicate) && existingPredicate.deletedAt === null @@ -397,15 +500,18 @@ export class RowLevelPermissionPredicateService { predicatesToUpdate.push({ ...existingPredicate, fieldMetadataId: inputPredicate.fieldMetadataId, + fieldMetadataUniversalIdentifier, operand: inputPredicate.operand, value: inputPredicate.value ?? null, subFieldName: inputPredicate.subFieldName ?? null, workspaceMemberFieldMetadataId: inputPredicate.workspaceMemberFieldMetadataId ?? null, + workspaceMemberFieldMetadataUniversalIdentifier, workspaceMemberSubFieldName: inputPredicate.workspaceMemberSubFieldName ?? null, rowLevelPermissionPredicateGroupId: inputPredicate.rowLevelPermissionPredicateGroupId ?? null, + rowLevelPermissionPredicateGroupUniversalIdentifier, positionInRowLevelPermissionPredicateGroup: inputPredicate.positionInRowLevelPermissionPredicateGroup ?? null, updatedAt: createdAt, @@ -415,17 +521,22 @@ export class RowLevelPermissionPredicateService { id: predicateId, workspaceId, roleId, + roleUniversalIdentifier, objectMetadataId, + objectMetadataUniversalIdentifier, fieldMetadataId: inputPredicate.fieldMetadataId, + fieldMetadataUniversalIdentifier, operand: inputPredicate.operand, value: inputPredicate.value ?? null, subFieldName: inputPredicate.subFieldName ?? null, workspaceMemberFieldMetadataId: inputPredicate.workspaceMemberFieldMetadataId ?? null, + workspaceMemberFieldMetadataUniversalIdentifier, workspaceMemberSubFieldName: inputPredicate.workspaceMemberSubFieldName ?? null, rowLevelPermissionPredicateGroupId: inputPredicate.rowLevelPermissionPredicateGroupId ?? null, + rowLevelPermissionPredicateGroupUniversalIdentifier, positionInRowLevelPermissionPredicateGroup: inputPredicate.positionInRowLevelPermissionPredicateGroup ?? null, createdAt, @@ -433,6 +544,8 @@ export class RowLevelPermissionPredicateService { deletedAt: null, universalIdentifier: predicateId, applicationId: workspaceCustomApplicationId, + applicationUniversalIdentifier: + workspaceCustomApplicationUniversalIdentifier, }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/skill/skill.service.ts b/packages/twenty-server/src/engine/metadata-modules/skill/skill.service.ts index f51e275576..3c2a4b9d73 100644 --- a/packages/twenty-server/src/engine/metadata-modules/skill/skill.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/skill/skill.service.ts @@ -77,7 +77,7 @@ export class SkillService { const flatSkillToCreate = fromCreateSkillInputToFlatSkillToCreate({ createSkillInput: input, workspaceId, - applicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field-v2.service.ts index 317059c83d..f8327040d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field-v2.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field-v2.service.ts @@ -76,12 +76,22 @@ export class ViewFieldV2Service { }, ); + const { flatFieldMetadataMaps, flatViewMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatFieldMetadataMaps', 'flatViewMaps'], + }, + ); + const flatViewFieldsToCreate = createViewFieldInputs.map( (createViewFieldInput) => fromCreateViewFieldInputToFlatViewFieldToCreate({ createViewFieldInput, workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatFieldMetadataMaps, + flatViewMaps, }), ); diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts index c39f71166b..ce7ab02d04 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts @@ -68,7 +68,7 @@ export class ViewFilterGroupEntity extends SyncableEntity { }, ) @JoinColumn({ name: 'parentViewFilterGroupId' }) - parentViewFilterGroup: Relation; + parentViewFilterGroup: Relation | null; @OneToMany( () => ViewFilterGroupEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/services/view-filter-group.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/services/view-filter-group.service.ts index b10f9115ff..0551e31d39 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/services/view-filter-group.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/services/view-filter-group.service.ts @@ -45,11 +45,24 @@ export class ViewFilterGroupService { }, ); + const { + flatViewMaps: existingFlatViewMaps, + flatViewFilterGroupMaps: existingFlatViewFilterGroupMaps, + } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatViewMaps', 'flatViewFilterGroupMaps'], + }, + ); + const flatViewFilterGroupToCreate = fromCreateViewFilterGroupInputToFlatViewFilterGroupToCreate({ createViewFilterGroupInput, workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatViewMaps: existingFlatViewMaps, + flatViewFilterGroupMaps: existingFlatViewFilterGroupMaps, }); const buildAndRunResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts index 055e564681..f387d689e9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts @@ -85,5 +85,5 @@ export class ViewFilterEntity }, ) @JoinColumn({ name: 'viewFilterGroupId' }) - viewFilterGroup: Relation; + viewFilterGroup: Relation | null; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/services/view-filter.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/services/view-filter.service.ts index 518d0ef3d7..cdf897fa9b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/services/view-filter.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/services/view-filter.service.ts @@ -45,11 +45,26 @@ export class ViewFilterService { }, ); + const { flatFieldMetadataMaps, flatViewMaps, flatViewFilterGroupMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatFieldMetadataMaps', + 'flatViewMaps', + 'flatViewFilterGroupMaps', + ], + }, + ); + const flatViewFilterToCreate = fromCreateViewFilterInputToFlatViewFilterToCreate({ createViewFilterInput, workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatFieldMetadataMaps, + flatViewMaps, + flatViewFilterGroupMaps, }); const buildAndRunResult = @@ -106,18 +121,27 @@ export class ViewFilterService { }, ); - const { flatViewFilterMaps: existingFlatViewFilterMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatViewFilterMaps'], - }, - ); + const { + flatViewFilterMaps: existingFlatViewFilterMaps, + flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + flatViewFilterGroupMaps: existingFlatViewFilterGroupMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatViewFilterMaps', + 'flatFieldMetadataMaps', + 'flatViewFilterGroupMaps', + ], + }, + ); const optimisticallyUpdatedFlatViewFilter = fromUpdateViewFilterInputToFlatViewFilterToUpdateOrThrow({ flatViewFilterMaps: existingFlatViewFilterMaps, updateViewFilterInput, + flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + flatViewFilterGroupMaps: existingFlatViewFilterGroupMaps, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts index dd1c57b5ee..ab248dddb3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts @@ -102,7 +102,8 @@ export class ViewGroupService { return fromCreateViewGroupInputToFlatViewGroupToCreate({ createViewGroupInput, workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, + flatViewMaps, }); }, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts index 57c4eb3280..284a2535dd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts @@ -118,7 +118,7 @@ export class ViewEntity extends SyncableEntity implements Required { }, ) @JoinColumn({ name: 'kanbanAggregateOperationFieldMetadataId' }) - kanbanAggregateOperationFieldMetadata: Relation; + kanbanAggregateOperationFieldMetadata: Relation | null; @Column({ type: 'enum', @@ -140,7 +140,7 @@ export class ViewEntity extends SyncableEntity implements Required { }, ) @JoinColumn({ name: 'calendarFieldMetadataId' }) - calendarFieldMetadata: Relation; + calendarFieldMetadata: Relation | null; @Column({ nullable: true, type: 'uuid' }) mainGroupByFieldMetadataId: string | null; @@ -154,7 +154,7 @@ export class ViewEntity extends SyncableEntity implements Required { }, ) @JoinColumn({ name: 'mainGroupByFieldMetadataId' }) - mainGroupByFieldMetadata: Relation; + mainGroupByFieldMetadata: Relation | null; @Column({ nullable: false, default: false, type: 'boolean' }) shouldHideEmptyGroups: boolean; diff --git a/packages/twenty-server/src/engine/metadata-modules/view/services/view.service.ts b/packages/twenty-server/src/engine/metadata-modules/view/services/view.service.ts index 86569f5dd1..2d7446b392 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/services/view.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/services/view.service.ts @@ -52,11 +52,14 @@ export class ViewService { }, ); - const { flatFieldMetadataMaps: existingFlatFieldMetadataMaps } = + const { + flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, - flatMapsKeys: ['flatFieldMetadataMaps'], + flatMapsKeys: ['flatFieldMetadataMaps', 'flatObjectMetadataMaps'], }, ); @@ -65,8 +68,9 @@ export class ViewService { createViewInput, workspaceId, createdByUserWorkspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, }); const validateAndBuildResult = diff --git a/packages/twenty-server/src/engine/metadata-modules/webhook/webhook.service.ts b/packages/twenty-server/src/engine/metadata-modules/webhook/webhook.service.ts index 475b31b51b..31213d332b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/webhook/webhook.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/webhook/webhook.service.ts @@ -111,7 +111,7 @@ export class WebhookService { targetUrl: normalizedTargetUrl, }, workspaceId, - applicationId: workspaceCustomFlatApplication.id, + flatApplication: workspaceCustomFlatApplication, }); const validateAndBuildResult = 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 08fa5e3420..1c81d265fd 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 @@ -5,7 +5,6 @@ import { isDefined } from 'twenty-shared/utils'; import { ApplicationService } from 'src/engine/core-modules/application/services/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'; import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util'; @@ -29,7 +28,6 @@ export class TwentyStandardApplicationService { private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, private readonly workspaceCacheService: WorkspaceCacheService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} // Note: To remove and handle natively in validateBuildAndRun after favorite migration to metadata diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-agent-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-agent-flat-metadata.util.ts index 9b77c6bbd4..017a88d9e4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-agent-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-agent-flat-metadata.util.ts @@ -1,6 +1,7 @@ import { v4 } from 'uuid'; import { type AgentResponseFormat } from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { type ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration'; import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const'; import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; @@ -61,6 +62,8 @@ export const createStandardAgentFlatMetadata = ({ evaluationInputs, workspaceId, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, createdAt: now, updatedAt: now, deletedAt: null, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts index 9cf9358be1..dd94535404 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/create-standard-index-flat-metadata.util.ts @@ -1,6 +1,6 @@ +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; -import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; @@ -10,6 +10,7 @@ import { } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types'; import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type'; import { type AllStandardObjectIndexName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-index-name.type'; import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type'; @@ -80,11 +81,14 @@ export const createStandardIndexFlatMetadata = < flatIndex: { createdAt: now, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, indexType, indexWhereClause, isCustom: false, isUnique, objectMetadataId, + objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier, universalIdentifier: indexDefinition.universalIdentifier, updatedAt: now, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts index 56220714b3..b3c7faf053 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/navigation-menu-item/create-standard-navigation-menu-item-flat-metadata.util.ts @@ -1,6 +1,7 @@ import { isDefined } from 'twenty-shared/utils'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type'; import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; @@ -51,12 +52,17 @@ export const createStandardNavigationMenuItemFlatMetadata = ({ id: navigationMenuItemId, universalIdentifier: navigationMenuItemDefinition.universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, userWorkspaceId: null, targetRecordId: null, targetObjectMetadataId: null, + targetObjectMetadataUniversalIdentifier: null, viewId: flatView.id, + viewUniversalIdentifier: flatView.universalIdentifier, folderId: null, + folderUniversalIdentifier: null, name: null, position, createdAt: now, 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 3e13d49b37..7aedc4d505 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 @@ -4,6 +4,7 @@ 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 StandardPageLayoutTabDefinition } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util'; export type CreateStandardPageLayoutTabContext = { @@ -33,7 +34,15 @@ export const createStandardPageLayoutTabFlatMetadata = ({ const layoutIds = standardPageLayoutMetadataRelatedEntityIds[layoutName]; const layout = STANDARD_PAGE_LAYOUTS[ layoutName as keyof typeof STANDARD_PAGE_LAYOUTS - ] as { tabs: Record }; + ] as { + universalIdentifier: string; + tabs: Record< + string, + StandardPageLayoutTabDefinition & { + widgets: Record; + } + >; + }; const tabDefinition = layout.tabs[tabTitle]; if (!isDefined(tabDefinition)) { @@ -42,16 +51,23 @@ export const createStandardPageLayoutTabFlatMetadata = ({ const tabIds = layoutIds.tabs[tabTitle]; const widgetIds = Object.values(tabIds.widgets).map((widget) => widget.id); + const widgetUniversalIdentifiers = Object.values(tabDefinition.widgets).map( + (widget) => widget.universalIdentifier, + ); return { id: tabIds.id, universalIdentifier: tabDefinition.universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, title, position, pageLayoutId: layoutIds.id, + pageLayoutUniversalIdentifier: layout.universalIdentifier, widgetIds, + widgetUniversalIdentifiers, createdAt: now, updatedAt: now, deletedAt: null, 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 5937756358..091db87515 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 @@ -74,6 +74,11 @@ const computeRecordPageWidgets = ({ ? layoutObjectMetadataId : null; + const objectMetadataUniversalIdentifier = + RECORD_PAGE_LAYOUT_WIDGET_TYPES.includes(widget.type) + ? (layout.objectUniversalIdentifier ?? null) + : null; + allWidgets.push( createStandardPageLayoutWidgetFlatMetadata({ now, @@ -81,6 +86,7 @@ const computeRecordPageWidgets = ({ twentyStandardApplicationId, standardObjectMetadataRelatedEntityIds, standardPageLayoutMetadataRelatedEntityIds, + objectMetadataUniversalIdentifier, context: { layoutName, tabTitle, 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 index 4c1c4a7871..dd3478395b 100644 --- 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 @@ -1,6 +1,7 @@ import { CalendarStartDay } from 'twenty-shared/constants'; import { PageLayoutTabLayoutMode } from 'twenty-shared/types'; import { v4 } from 'uuid'; +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; 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'; @@ -59,6 +60,7 @@ const createWelcomeRichText = ({ }): FlatPageLayoutWidget => createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: null, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', @@ -233,6 +235,8 @@ const createDealsByCompany = ({ return createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS.opportunity.universalIdentifier, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', @@ -278,6 +282,8 @@ const createPipelineValueByStage = ({ return createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS.opportunity.universalIdentifier, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', @@ -329,6 +335,8 @@ const createRevenueTimeline = ({ return createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS.opportunity.universalIdentifier, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', @@ -375,6 +383,8 @@ const createOpportunitiesByOwner = ({ return createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS.opportunity.universalIdentifier, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', @@ -423,6 +433,7 @@ const createStockMarketIframe = ({ }): FlatPageLayoutWidget => createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: null, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', @@ -457,6 +468,8 @@ const createDealsCreatedThisMonth = ({ return createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS.opportunity.universalIdentifier, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', @@ -507,6 +520,8 @@ const createDealValueCreatedThisMonth = ({ return createStandardPageLayoutWidgetFlatMetadata({ ...args, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS.opportunity.universalIdentifier, context: { layoutName: 'myFirstDashboard', tabTitle: 'tab1', 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 16f5498d99..e1fc08e1ba 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 @@ -10,6 +10,7 @@ import { type StandardPageLayoutTabDefinition, type StandardPageLayoutWidgetDefinition, } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.types'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; 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'; @@ -49,14 +50,25 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({ workspaceId, twentyStandardApplicationId, standardPageLayoutMetadataRelatedEntityIds, + objectMetadataUniversalIdentifier, now, -}: CreateStandardPageLayoutWidgetArgs): FlatPageLayoutWidget => { +}: CreateStandardPageLayoutWidgetArgs & { + objectMetadataUniversalIdentifier: string | null; +}): FlatPageLayoutWidget => { const layoutIds = standardPageLayoutMetadataRelatedEntityIds[layoutName]; const layout = STANDARD_PAGE_LAYOUTS[ layoutName as keyof typeof STANDARD_PAGE_LAYOUTS - ] as { tabs: Record }; + ] as { + tabs: Record< + string, + StandardPageLayoutTabDefinition & { + universalIdentifier: string; + } + >; + }; + const tabDefinition = layout.tabs[tabTitle]; const widgetDef: StandardPageLayoutWidgetDefinition = - layout.tabs[tabTitle].widgets[widgetName]; + tabDefinition.widgets[widgetName]; if (!isDefined(widgetDef)) { throw new Error( @@ -71,14 +83,18 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({ id: widgetIds.id, universalIdentifier: widgetDef.universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, pageLayoutTabId: tabIds.id, + pageLayoutTabUniversalIdentifier: tabDefinition.universalIdentifier, title, type, gridPosition, position, configuration, objectMetadataId, + objectMetadataUniversalIdentifier, createdAt: now, updatedAt: now, deletedAt: null, 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 594b8d093d..bd0e06b55c 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 @@ -2,6 +2,7 @@ import { isDefined } from 'twenty-shared/utils'; import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; 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 StandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util'; @@ -111,14 +112,20 @@ export const createStandardPageLayoutFlatMetadata = ({ id: layoutIds.id, universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, name, type, objectMetadataId, + objectMetadataUniversalIdentifier: objectUniversalIdentifier, tabIds: [], + tabUniversalIdentifiers: [], createdAt: now, updatedAt: now, deletedAt: null, defaultTabToFocusOnMobileAndSidePanelId, + defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: + defaultTabUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts index 893ddce4c7..7d7810d384 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/create-standard-role-flat-metadata.util.ts @@ -1,6 +1,7 @@ import { v4 } from 'uuid'; import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { STANDARD_ROLE } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-role.constant'; import { type AllStandardRoleName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-role-name.type'; import { type StandardBuilderArgs } from 'src/engine/workspace-manager/twenty-standard-application/types/metadata-standard-buillder-args.type'; @@ -67,13 +68,18 @@ export const createStandardRoleFlatMetadata = ({ canBeAssignedToApiKeys, workspaceId, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, createdAt: now, updatedAt: now, permissionFlagIds: [], fieldPermissionIds: [], objectPermissionIds: [], roleTargetIds: [], + roleTargetUniversalIdentifiers: [], rowLevelPermissionPredicateIds: [], rowLevelPermissionPredicateGroupIds: [], + rowLevelPermissionPredicateGroupUniversalIdentifiers: [], + rowLevelPermissionPredicateUniversalIdentifiers: [], }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-skill-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-skill-flat-metadata.util.ts index 8d7703828f..692e2e9f59 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-skill-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-skill-flat-metadata.util.ts @@ -1,6 +1,7 @@ import { v4 } from 'uuid'; import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { STANDARD_SKILL } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-skill.constant'; import { type AllStandardSkillName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-skill-name.type'; import { type StandardBuilderArgs } from 'src/engine/workspace-manager/twenty-standard-application/types/metadata-standard-buillder-args.type'; @@ -39,6 +40,8 @@ export const createStandardSkillFlatMetadata = ({ isActive: true, workspaceId, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, createdAt: now, updatedAt: now, }; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts index d6e8a09873..43bc511a3a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts @@ -1,9 +1,10 @@ +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; -import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type'; import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type'; import { type AllStandardObjectViewFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-view-field-name.type'; @@ -50,12 +51,24 @@ export const createStandardViewFieldFlatMetadata = < twentyStandardApplicationId, now, }: CreateStandardViewFieldArgs): FlatViewField => { - // @ts-expect-error ignore - const viewFieldDefinition = STANDARD_OBJECTS[objectName].views[viewName] - .viewFields[viewFieldName] as { - universalIdentifier: string; + const objectDefinition = STANDARD_OBJECTS[objectName] as { + fields: Record< + AllStandardObjectFieldName, + { universalIdentifier: string } + >; + views: Record< + V, + { + universalIdentifier: string; + viewFields: Record; + } + >; }; + const viewDefinition = objectDefinition.views[viewName]; + const viewFieldDefinition = viewDefinition.viewFields[viewFieldName]; + const fieldDefinition = objectDefinition.fields[fieldName]; + if (!isDefined(viewFieldDefinition)) { throw new Error( `Invalid configuration ${objectName} ${viewName.toString()} ${viewFieldName}`, @@ -66,11 +79,15 @@ export const createStandardViewFieldFlatMetadata = < id: v4(), universalIdentifier: viewFieldDefinition.universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, viewId: standardObjectMetadataRelatedEntityIds[objectName].views[viewName].id, + viewUniversalIdentifier: viewDefinition.universalIdentifier, fieldMetadataId: standardObjectMetadataRelatedEntityIds[objectName].fields[fieldName].id, + fieldMetadataUniversalIdentifier: fieldDefinition.universalIdentifier, position, isVisible, size, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-filter/create-standard-view-filter-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-filter/create-standard-view-filter-flat-metadata.util.ts index 6a362e3d12..13ab6b6d67 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-filter/create-standard-view-filter-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-filter/create-standard-view-filter-flat-metadata.util.ts @@ -1,9 +1,10 @@ +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type ViewFilterOperand } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; -import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type'; import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type'; import { type AllStandardObjectViewFilterName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-view-filter-name.type'; @@ -52,12 +53,25 @@ export const createStandardViewFilterFlatMetadata = < twentyStandardApplicationId, now, }: CreateStandardViewFilterArgs): FlatViewFilter => { - // @ts-expect-error ignore - const viewFilterDefinition = STANDARD_OBJECTS[objectName].views[viewName] - .viewFilters[viewFilterName] as { - universalIdentifier: string; + const objectDefinition = STANDARD_OBJECTS[objectName] as { + fields: Record< + AllStandardObjectFieldName, + { universalIdentifier: string } + >; + views: Record< + V, + { + universalIdentifier: string; + viewFields: Record; + viewFilters: Record; + } + >; }; + const viewDefinition = objectDefinition.views[viewName]; + const viewFilterDefinition = viewDefinition.viewFilters[viewFilterName]; + const fieldDefinition = objectDefinition.fields[fieldName]; + if (!isDefined(viewFilterDefinition)) { throw new Error( `Invalid configuration ${objectName} ${viewName.toString()} ${viewFilterName}`, @@ -68,15 +82,20 @@ export const createStandardViewFilterFlatMetadata = < id: v4(), universalIdentifier: viewFilterDefinition.universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, viewId: standardObjectMetadataRelatedEntityIds[objectName].views[viewName].id, + viewUniversalIdentifier: viewDefinition.universalIdentifier, fieldMetadataId: standardObjectMetadataRelatedEntityIds[objectName].fields[fieldName].id, + fieldMetadataUniversalIdentifier: fieldDefinition.universalIdentifier, + viewFilterGroupId, + viewFilterGroupUniversalIdentifier: null, operand, value, subFieldName, - viewFilterGroupId, positionInViewFilterGroup, createdAt: now, updatedAt: now, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-group/create-standard-view-group-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-group/create-standard-view-group-flat-metadata.util.ts index 920a0b6517..cf1f32b0da 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-group/create-standard-view-group-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-group/create-standard-view-group-flat-metadata.util.ts @@ -1,8 +1,9 @@ +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; -import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type'; import { type AllStandardObjectViewGroupName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-view-group-name.type'; import { type AllStandardObjectViewName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-view-name.type'; @@ -39,11 +40,13 @@ export const createStandardViewGroupFlatMetadata = < now, }: CreateStandardViewGroupArgs): FlatViewGroup => { // @ts-expect-error ignore - const viewGroupDefinition = STANDARD_OBJECTS[objectName].views[viewName] - .viewGroups[viewGroupName] as { + const viewDefinition = STANDARD_OBJECTS[objectName].views[viewName] as { universalIdentifier: string; + viewGroups: Record; }; + const viewGroupDefinition = viewDefinition.viewGroups[viewGroupName]; + if (!isDefined(viewGroupDefinition)) { throw new Error( `Invalid configuration ${objectName} ${viewName.toString()} ${viewGroupName}`, @@ -54,9 +57,12 @@ export const createStandardViewGroupFlatMetadata = < id: v4(), universalIdentifier: viewGroupDefinition.universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, viewId: standardObjectMetadataRelatedEntityIds[objectName].views[viewName].id, + viewUniversalIdentifier: viewDefinition.universalIdentifier, isVisible, fieldValue, position, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view/create-standard-view-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view/create-standard-view-flat-metadata.util.ts index 73741727a2..f33161eda0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view/create-standard-view-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view/create-standard-view-flat-metadata.util.ts @@ -7,6 +7,7 @@ import { type ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.en import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in'; import { type ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'; import { ViewVisibility } from 'src/engine/metadata-modules/view/enums/view-visibility.enum'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type'; import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type'; import { type AllStandardObjectViewName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-view-name.type'; @@ -70,6 +71,8 @@ export const createStandardViewFlatMetadata = < const objectMetadataId = standardObjectMetadataRelatedEntityIds[objectName].id; + const objectMetadataUniversalIdentifier = + STANDARD_OBJECTS[objectName].universalIdentifier; const kanbanAggregateOperationFieldMetadataId = kanbanAggregateOperationFieldName @@ -91,9 +94,15 @@ export const createStandardViewFlatMetadata = < : null; return { + calendarFieldMetadataUniversalIdentifier: null, + kanbanAggregateOperationFieldMetadataUniversalIdentifier: null, + mainGroupByFieldMetadataUniversalIdentifier: null, + objectMetadataUniversalIdentifier, id: standardObjectMetadataRelatedEntityIds[objectName].views[viewName].id, universalIdentifier: viewDefinition.universalIdentifier, applicationId: twentyStandardApplicationId, + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, workspaceId, objectMetadataId, name, @@ -114,9 +123,13 @@ export const createStandardViewFlatMetadata = < visibility: ViewVisibility.WORKSPACE, createdByUserWorkspaceId: null, viewFieldIds: [], + viewFieldUniversalIdentifiers: [], viewFilterIds: [], + viewFilterUniversalIdentifiers: [], viewGroupIds: [], + viewGroupUniversalIdentifiers: [], viewFilterGroupIds: [], + viewFilterGroupUniversalIdentifiers: [], createdAt: now, updatedAt: now, deletedAt: null, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor.ts index 98bdf55657..db2910d4c5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor.ts @@ -10,6 +10,11 @@ import { type Observable, catchError } from 'rxjs'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; +import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { + FlatEntityMapsException, + FlatEntityMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; import { workspaceMigrationBuilderExceptionFormatter } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-builder-exception-formatter'; import { workspaceMigrationRunnerExceptionFormatter } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-runner-exception-formatter'; @@ -29,6 +34,17 @@ export class WorkspaceMigrationGraphqlApiExceptionInterceptor return next.handle().pipe( catchError((error) => { + if (error instanceof FlatEntityMapsException) { + switch (error.code) { + case FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND: + throw new NotFoundError(error); + case FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS: + case FlatEntityMapsExceptionCode.ENTITY_MALFORMED: + case FlatEntityMapsExceptionCode.INTERNAL_SERVER_ERROR: + throw error; + } + } + if (error instanceof WorkspaceMigrationBuilderException) { workspaceMigrationBuilderExceptionFormatter(error, i18n); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts index ce9f9c6784..ecb5189c66 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts @@ -32,6 +32,8 @@ type ValidateBuildAndRunWorkspaceMigrationFromMatriceArgs = { [P in AllMetadataName]?: FlatEntityToCreateDeleteUpdate

; }; isSystemBuild?: boolean; + // TODO remove once application synchronization do not consume services atomically anymore + // Should always be the universal workspace custom app id applicationUniversalIdentifier: string; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-agent-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-agent-validator.service.ts index 13053c2eaf..019e6139b8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-agent-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-agent-validator.service.ts @@ -100,12 +100,10 @@ export class FlatAgentValidatorService { if ( !buildOptions.isSystemBuild && - // TODO refactor once agent has been migrated to universal pattern - isDefined(existingAgent.__universal) && belongsToTwentyStandardApp({ universalIdentifier: existingAgent.universalIdentifier, applicationUniversalIdentifier: - existingAgent.__universal.applicationUniversalIdentifier, + existingAgent.applicationUniversalIdentifier, }) ) { validationResult.errors.push({ @@ -154,12 +152,10 @@ export class FlatAgentValidatorService { if ( !buildOptions.isSystemBuild && - // TODO refactor once agent has been migrated to universal pattern - isDefined(fromFlatAgent.__universal) && belongsToTwentyStandardApp({ universalIdentifier: fromFlatAgent.universalIdentifier, applicationUniversalIdentifier: - fromFlatAgent.__universal.applicationUniversalIdentifier, + fromFlatAgent.applicationUniversalIdentifier, }) ) { validationResult.errors.push({ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-skill-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-skill-validator.service.ts index c7838ea443..84d0bb2e1b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-skill-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-skill-validator.service.ts @@ -93,12 +93,10 @@ export class FlatSkillValidatorService { if ( !buildOptions.isSystemBuild && - // TODO refactor once skill has been migrated to universal pattern - isDefined(existingSkill.__universal) && belongsToTwentyStandardApp({ universalIdentifier: existingSkill.universalIdentifier, applicationUniversalIdentifier: - existingSkill.__universal.applicationUniversalIdentifier, + existingSkill.applicationUniversalIdentifier, }) ) { validationResult.errors.push({ @@ -151,14 +149,11 @@ export class FlatSkillValidatorService { (key) => key !== 'isActive', ); - // TODO refactor once skill has been migrated to universal pattern - const isTwentyStandardSkill = - isDefined(fromFlatSkill.__universal) && - belongsToTwentyStandardApp({ - universalIdentifier: fromFlatSkill.universalIdentifier, - applicationUniversalIdentifier: - fromFlatSkill.__universal.applicationUniversalIdentifier, - }); + const isTwentyStandardSkill = belongsToTwentyStandardApp({ + universalIdentifier: fromFlatSkill.universalIdentifier, + applicationUniversalIdentifier: + fromFlatSkill.applicationUniversalIdentifier, + }); if ( !buildOptions.isSystemBuild && diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts index 339afeffb1..8bf0aa5e9e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts @@ -311,6 +311,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { isTool: false, universalIdentifier: 'universal-id', applicationId: 'application-id', + applicationUniversalIdentifier: 'application-universal-id', cronTriggerSettings: null, databaseEventTriggerSettings: null, httpRouteTriggerSettings: null, @@ -381,6 +382,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { isTool: false, universalIdentifier: 'existing-universal-id', applicationId: 'application-id', + applicationUniversalIdentifier: 'application-universal-id', cronTriggerSettings: null, databaseEventTriggerSettings: null, httpRouteTriggerSettings: null, @@ -404,6 +406,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { isTool: false, universalIdentifier: 'universal-id', applicationId: 'application-id', + applicationUniversalIdentifier: 'application-universal-id', cronTriggerSettings: null, databaseEventTriggerSettings: null, httpRouteTriggerSettings: null, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap index 95e57a803a..3cadbffdcb 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap @@ -3,78 +3,11 @@ exports[`View Field Resolver - Failing Create Many Operations - v2 should accumulate multiple validation errors when some inputs are invalid 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewField": [ - { - "errors": [ - { - "code": "INVALID_VIEW_DATA", - "message": "Field metadata not found", - "userFriendlyMessage": "Field metadata not found", - }, - ], - "flatEntityMinimalInformation": { - "fieldMetadataId": Any, - "id": Any, - "universalIdentifier": Any, - "viewId": Any, - }, - "metadataName": "viewField", - "status": "fail", - "type": "create", - }, - { - "errors": [ - { - "code": "INVALID_VIEW_DATA", - "message": "View not found", - "userFriendlyMessage": "View not found", - }, - ], - "flatEntityMinimalInformation": { - "fieldMetadataId": Any, - "id": Any, - "universalIdentifier": Any, - "viewId": Any, - }, - "metadataName": "viewField", - "status": "fail", - "type": "create", - }, - { - "errors": [ - { - "code": "INVALID_VIEW_DATA", - "message": "Field metadata not found", - "userFriendlyMessage": "Field metadata not found", - }, - { - "code": "INVALID_VIEW_DATA", - "message": "View not found", - "userFriendlyMessage": "View not found", - }, - ], - "flatEntityMinimalInformation": { - "fieldMetadataId": Any, - "id": Any, - "universalIdentifier": Any, - "viewId": Any, - }, - "metadataName": "viewField", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 3 viewFields", - "summary": { - "totalErrors": 3, - "viewField": 3, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view fields", - "name": "GraphQLError", + "message": "Could not find fieldMetadata for given fieldMetadataId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap index 3d3719f524..496b7cba47 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap @@ -41,75 +41,23 @@ exports[`View Field Resolver - Failing Create Operation - v2 Should fail to crea exports[`View Field Resolver - Failing Create Operation - v2 should fail to create view field when non-existent field metadata 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewField": [ - { - "errors": [ - { - "code": "INVALID_VIEW_DATA", - "message": "Field metadata not found", - "userFriendlyMessage": "Field metadata not found", - }, - ], - "flatEntityMinimalInformation": { - "fieldMetadataId": Any, - "id": Any, - "universalIdentifier": Any, - "viewId": Any, - }, - "metadataName": "viewField", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewField", - "summary": { - "totalErrors": 1, - "viewField": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view fields", - "name": "GraphQLError", + "message": "Could not find fieldMetadata for given fieldMetadataId", + "name": "NotFoundError", } `; exports[`View Field Resolver - Failing Create Operation - v2 should fail to create view field when non-existent view metadata 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewField": [ - { - "errors": [ - { - "code": "INVALID_VIEW_DATA", - "message": "View not found", - "userFriendlyMessage": "View not found", - }, - ], - "flatEntityMinimalInformation": { - "fieldMetadataId": Any, - "id": Any, - "universalIdentifier": Any, - "viewId": Any, - }, - "metadataName": "viewField", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewField", - "summary": { - "totalErrors": 1, - "viewField": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view fields", - "name": "GraphQLError", + "message": "Could not find view for given viewId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap index 8c0578e391..856061fb1b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap @@ -45,21 +45,6 @@ exports[`NavigationMenuItem creation should fail when creating with invalid fold } `; -exports[`NavigationMenuItem creation should fail when creating with invalid userWorkspaceId (not a UUID) 1`] = ` -{ - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED", - "http": { - "status": 400, - }, - "userFriendlyMessage": "An error occurred.", - "value": "not-a-valid-uuid", - }, - "message": "Invalid UUID", - "name": "ValidationError", -} -`; - exports[`NavigationMenuItem creation should fail when creating with invalid targetObjectMetadataId (not a UUID) 1`] = ` { "extensions": { @@ -90,6 +75,21 @@ exports[`NavigationMenuItem creation should fail when creating with invalid targ } `; +exports[`NavigationMenuItem creation should fail when creating with invalid userWorkspaceId (not a UUID) 1`] = ` +{ + "extensions": { + "code": "GRAPHQL_VALIDATION_FAILED", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + "value": "not-a-valid-uuid", + }, + "message": "Invalid UUID", + "name": "ValidationError", +} +`; + exports[`NavigationMenuItem creation should fail when creating with missing targetObjectMetadataId 1`] = ` { "extensions": { @@ -134,76 +134,23 @@ exports[`NavigationMenuItem creation should fail when creating with missing targ exports[`NavigationMenuItem creation should fail when creating with missing targetRecordId 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "navigationMenuItem": [ - { - "errors": [ - { - "code": "INVALID_NAVIGATION_MENU_ITEM_INPUT", - "message": "targetRecordId is required when targetObjectMetadataId is provided", - "userFriendlyMessage": "targetRecordId is required when targetObjectMetadataId is provided", - }, - { - "code": "INVALID_NAVIGATION_MENU_ITEM_INPUT", - "message": "Navigation menu item must be either a folder (with name), a view link (with viewId), or a record link (with targetRecordId and targetObjectMetadataId)", - "userFriendlyMessage": "Navigation menu item must be either a folder (with name), a view link (with viewId), or a record link (with targetRecordId and targetObjectMetadataId)", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "navigationMenuItem", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 navigationMenuItem", - "summary": { - "navigationMenuItem": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating navigation menu item", - "name": "GraphQLError", + "message": "Could not find objectMetadata for given targetObjectMetadataId", + "name": "NotFoundError", } `; exports[`NavigationMenuItem creation should fail when creating with negative position 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "navigationMenuItem": [ - { - "errors": [ - { - "code": "INVALID_NAVIGATION_MENU_ITEM_INPUT", - "message": "Position must be a non-negative integer", - "userFriendlyMessage": "Position must be a non-negative integer", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "navigationMenuItem", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 navigationMenuItem", - "summary": { - "navigationMenuItem": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating navigation menu item", - "name": "GraphQLError", + "message": "Could not find objectMetadata for given targetObjectMetadataId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap index b9dc3ec405..9cacb62f7b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap @@ -64,13 +64,15 @@ exports[`Object metadata update should fail when labelIdentifier is not a TEXT o exports[`Object metadata update should fail when labelIdentifier is not a known field metadata id 1`] = ` [ { + "eventId": Any, "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "INVALID_OBJECT_INPUT", - "userFriendlyMessage": "Invalid object input.", + "code": "INTERNAL_SERVER_ERROR", + "exceptionEventId": Any, + "subCode": "ENTITY_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "New label identifier field not found for object metadata", - "name": "UserInputError", + "message": "Could not find flat entity in maps", + "name": "InternalServerError", }, ] `; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/failing-page-layout-tab-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/failing-page-layout-tab-creation.integration-spec.ts.snap index dd52b440b9..260ce2bb58 100644 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/failing-page-layout-tab-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/failing-page-layout-tab-creation.integration-spec.ts.snap @@ -3,37 +3,12 @@ exports[`Page layout tab creation should fail when pageLayoutId references non-existent layout 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "pageLayoutTab": [ - { - "errors": [ - { - "code": "PAGE_LAYOUT_NOT_FOUND", - "message": "Page layout not found", - "userFriendlyMessage": "Page layout not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "title": "Tab With Non-Existent Layout", - "universalIdentifier": Any, - }, - "metadataName": "pageLayoutTab", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 pageLayoutTab", - "summary": { - "pageLayoutTab": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating page layout tab", - "name": "GraphQLError", + "message": "Could not find pageLayout for given pageLayoutId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/failing-page-layout-widget-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/failing-page-layout-widget-creation.integration-spec.ts.snap index f7a362e427..eb0314a193 100644 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/failing-page-layout-widget-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/failing-page-layout-widget-creation.integration-spec.ts.snap @@ -94,37 +94,12 @@ exports[`Page layout widget creation should fail General validation failures whe exports[`Page layout widget creation should fail General validation failures when pageLayoutTabId references non-existent tab 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "pageLayoutWidget": [ - { - "errors": [ - { - "code": "PAGE_LAYOUT_TAB_NOT_FOUND", - "message": "Page layout tab not found", - "userFriendlyMessage": "Page layout tab not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "pageLayoutTabId": Any, - "universalIdentifier": Any, - }, - "metadataName": "pageLayoutWidget", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 pageLayoutWidget", - "summary": { - "pageLayoutWidget": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating page layout widget", - "name": "GraphQLError", + "message": "Could not find pageLayoutTab for given pageLayoutTabId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap index d87168e31c..b416b938d0 100644 --- a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap @@ -3,36 +3,12 @@ exports[`Row Level Permission Predicate upsert should fail when fieldMetadataId in predicate does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "rowLevelPermissionPredicate": [ - { - "errors": [ - { - "code": "FIELD_METADATA_NOT_FOUND", - "message": "Field metadata not found", - "userFriendlyMessage": "Field metadata not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "rowLevelPermissionPredicate", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 rowLevelPermissionPredicate", - "summary": { - "rowLevelPermissionPredicate": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Validation errors occurred while upserting row level permission predicates", - "name": "GraphQLError", + "message": "Could not find fieldMetadata for given fieldMetadataId", + "name": "NotFoundError", } `; @@ -54,36 +30,12 @@ exports[`Row Level Permission Predicate upsert should fail when fieldMetadataId exports[`Row Level Permission Predicate upsert should fail when objectMetadataId in predicate group does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "rowLevelPermissionPredicateGroup": [ - { - "errors": [ - { - "code": "OBJECT_METADATA_NOT_FOUND", - "message": "Object metadata not found", - "userFriendlyMessage": "Object metadata not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "rowLevelPermissionPredicateGroup", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 rowLevelPermissionPredicateGroup", - "summary": { - "rowLevelPermissionPredicateGroup": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Validation errors occurred while upserting row level permission predicates", - "name": "GraphQLError", + "message": "Could not find objectMetadata for given objectMetadataId", + "name": "NotFoundError", } `; @@ -120,36 +72,12 @@ exports[`Row Level Permission Predicate upsert should fail when objectMetadataId exports[`Row Level Permission Predicate upsert should fail when parentRowLevelPermissionPredicateGroupId in group does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "rowLevelPermissionPredicateGroup": [ - { - "errors": [ - { - "code": "INVALID_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_DATA", - "message": "Parent row level permission predicate group not found", - "userFriendlyMessage": "Parent row level permission predicate group not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "rowLevelPermissionPredicateGroup", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 rowLevelPermissionPredicateGroup", - "summary": { - "rowLevelPermissionPredicateGroup": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Validation errors occurred while upserting row level permission predicates", - "name": "GraphQLError", + "message": "Could not find rowLevelPermissionPredicateGroup for given parentRowLevelPermissionPredicateGroupId", + "name": "NotFoundError", } `; @@ -171,35 +99,11 @@ exports[`Row Level Permission Predicate upsert should fail when roleId is not a exports[`Row Level Permission Predicate upsert should fail when rowLevelPermissionPredicateGroupId in predicate does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "rowLevelPermissionPredicate": [ - { - "errors": [ - { - "code": "INVALID_ROW_LEVEL_PERMISSION_PREDICATE_DATA", - "message": "Row level permission predicate group not found", - "userFriendlyMessage": "Row level permission predicate group not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "rowLevelPermissionPredicate", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 rowLevelPermissionPredicate", - "summary": { - "rowLevelPermissionPredicate": 1, - "totalErrors": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Validation errors occurred while upserting row level permission predicates", - "name": "GraphQLError", + "message": "Could not find rowLevelPermissionPredicateGroup for given rowLevelPermissionPredicateGroupId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation-circular-dependency.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation-circular-dependency.integration-spec.ts.snap index b34eace4ed..f517daca37 100644 --- a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation-circular-dependency.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation-circular-dependency.integration-spec.ts.snap @@ -3,41 +3,12 @@ exports[`View Filter Group creation should fail with circular dependency when id equals parentViewFilterGroupId (self-reference) 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilterGroup": [ - { - "errors": [ - { - "code": "CIRCULAR_DEPENDENCY", - "message": "View filter group cannot be its own parent", - "userFriendlyMessage": "View filter group cannot be its own parent", - }, - { - "code": "VIEW_FILTER_GROUP_NOT_FOUND", - "message": "Parent view filter group not found", - "userFriendlyMessage": "Parent view filter group not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilterGroup", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewFilterGroup", - "summary": { - "totalErrors": 1, - "viewFilterGroup": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view filter group", - "name": "GraphQLError", + "message": "Could not find viewFilterGroup for given parentViewFilterGroupId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap index 4234c116c9..1f97f5625f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap @@ -3,113 +3,36 @@ exports[`View Filter Group creation should fail when parentViewFilterGroupId does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilterGroup": [ - { - "errors": [ - { - "code": "VIEW_FILTER_GROUP_NOT_FOUND", - "message": "Parent view filter group not found", - "userFriendlyMessage": "Parent view filter group not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilterGroup", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewFilterGroup", - "summary": { - "totalErrors": 1, - "viewFilterGroup": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view filter group", - "name": "GraphQLError", + "message": "Could not find viewFilterGroup for given parentViewFilterGroupId", + "name": "NotFoundError", } `; exports[`View Filter Group creation should fail when parentViewFilterGroupId equals id (self-reference) 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilterGroup": [ - { - "errors": [ - { - "code": "CIRCULAR_DEPENDENCY", - "message": "View filter group cannot be its own parent", - "userFriendlyMessage": "View filter group cannot be its own parent", - }, - { - "code": "VIEW_FILTER_GROUP_NOT_FOUND", - "message": "Parent view filter group not found", - "userFriendlyMessage": "Parent view filter group not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilterGroup", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewFilterGroup", - "summary": { - "totalErrors": 1, - "viewFilterGroup": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view filter group", - "name": "GraphQLError", + "message": "Could not find viewFilterGroup for given parentViewFilterGroupId", + "name": "NotFoundError", } `; exports[`View Filter Group creation should fail when viewId does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilterGroup": [ - { - "errors": [ - { - "code": "VIEW_NOT_FOUND", - "message": "View not found", - "userFriendlyMessage": "View not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilterGroup", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewFilterGroup", - "summary": { - "totalErrors": 1, - "viewFilterGroup": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view filter group", - "name": "GraphQLError", + "message": "Could not find view for given viewId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-update.integration-spec.ts.snap index fc328e1579..8bda1bcab6 100644 --- a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-update.integration-spec.ts.snap @@ -3,36 +3,12 @@ exports[`View Filter Group update should fail when parentViewFilterGroupId does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilterGroup": [ - { - "errors": [ - { - "code": "VIEW_FILTER_GROUP_NOT_FOUND", - "message": "Parent view filter group not found", - "userFriendlyMessage": "Parent view filter group not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilterGroup", - "status": "fail", - "type": "update", - }, - ], - }, - "message": "Validation failed for 1 viewFilterGroup", - "summary": { - "totalErrors": 1, - "viewFilterGroup": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while updating view filter group", - "name": "GraphQLError", + "message": "Could not find viewFilterGroup for given parentViewFilterGroupId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/view-filter/__snapshots__/failing-view-filter-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/view-filter/__snapshots__/failing-view-filter-creation.integration-spec.ts.snap index fdf593dd11..5b1d1a47df 100644 --- a/packages/twenty-server/test/integration/metadata/suites/view-filter/__snapshots__/failing-view-filter-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/view-filter/__snapshots__/failing-view-filter-creation.integration-spec.ts.snap @@ -3,117 +3,35 @@ exports[`View Filter creation should fail when fieldMetadataId does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilter": [ - { - "errors": [ - { - "code": "INVALID_VIEW_FILTER_DATA", - "message": "Field metadata not found", - "userFriendlyMessage": "Field metadata not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilter", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewFilter", - "summary": { - "totalErrors": 1, - "viewFilter": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view filter", - "name": "GraphQLError", + "message": "Could not find fieldMetadata for given fieldMetadataId", + "name": "NotFoundError", } `; exports[`View Filter creation should fail when viewFilterGroupId does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilter": [ - { - "errors": [ - { - "code": "INVALID_VIEW_FILTER_DATA", - "message": "Field metadata not found", - "userFriendlyMessage": "Field metadata not found", - }, - { - "code": "INVALID_VIEW_FILTER_DATA", - "message": "View filter group not found", - "userFriendlyMessage": "View filter group not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilter", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewFilter", - "summary": { - "totalErrors": 1, - "viewFilter": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view filter", - "name": "GraphQLError", + "message": "Could not find fieldMetadata for given fieldMetadataId", + "name": "NotFoundError", } `; exports[`View Filter creation should fail when viewId does not exist 1`] = ` { "extensions": { - "code": "METADATA_VALIDATION_FAILED", - "errors": { - "viewFilter": [ - { - "errors": [ - { - "code": "INVALID_VIEW_FILTER_DATA", - "message": "View not found", - "userFriendlyMessage": "View not found", - }, - { - "code": "INVALID_VIEW_FILTER_DATA", - "message": "Field metadata not found", - "userFriendlyMessage": "Field metadata not found", - }, - ], - "flatEntityMinimalInformation": { - "id": Any, - "universalIdentifier": Any, - }, - "metadataName": "viewFilter", - "status": "fail", - "type": "create", - }, - ], - }, - "message": "Validation failed for 1 viewFilter", - "summary": { - "totalErrors": 1, - "viewFilter": 1, - }, - "userFriendlyMessage": "Metadata validation failed", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Multiple validation errors occurred while creating view filter", - "name": "GraphQLError", + "message": "Could not find fieldMetadata for given fieldMetadataId", + "name": "NotFoundError", } `;