From 3dc5b162c78371d23e5b24dc680ae6018a5a1d99 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:02:02 +0100 Subject: [PATCH] Spread in parent and requires `FlatEntity.__universal` (#17753) # Introduction Requiring the spreaded `__universal` record that aggregates all the universal identifier ( relations fk and aggregators ) of an entity to its root It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be finalized because if we don't we would have to migrated all related entities at once in order for them to always have the universal properties ## `resolveEntityRelationUniversalIdentifiers` Introduced `resolveEntityRelationUniversalIdentifiers` a centralized utility that resolves foreign key IDs to universal identifiers using ALL_METADATA_RELATIONS metadata. It provides strict typing for both input (foreign keys) and output (universal identifiers), with nullability dynamically inferred from entity relation types. Strictly and dynamically typed for both output and input To do so added a new type and const/runtime grain to ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from the entity relation property types. And fixed incorrectly typed typeorm entities ### Usage ```ts const { availabilityObjectMetadataUniversalIdentifier, frontComponentUniversalIdentifier, } = resolveEntityRelationUniversalIdentifiers({ metadataName: 'commandMenuItem', foreignKeyValues: { availabilityObjectMetadataId: createCommandMenuItemInput.availabilityObjectMetadataId, frontComponentId: createCommandMenuItemInput.frontComponentId, }, flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps }, }); ``` --- ...orites-to-navigation-menu-items.command.ts | 68 +++++++- .../ai/ai-agent/agent.service.ts | 25 ++- ...m-create-agent-input-to-flat-agent.util.ts | 62 ++++--- ...gent-input-to-flat-agent-to-update.util.ts | 18 ++- .../command-menu-item.service.ts | 20 ++- ...ansform-agent-entity-to-flat-agent.util.ts | 5 +- ...m-entity-to-flat-command-menu-item.util.ts | 9 +- ...o-flat-command-menu-item-to-create.util.ts | 32 +++- ...mmand-menu-item-to-update-or-throw.util.ts | 29 +++- .../all-metadata-relations.constant.ts | 40 ++++- .../exceptions/flat-entity-maps.exception.ts | 3 + ...ntity-maps-graphql-api-exception.filter.ts | 28 ++++ .../types/flat-entity-from-v2.type.ts | 15 -- .../types/flat-entity-from.type.ts | 19 +-- ...elation-universal-identifiers.util.spec.ts | 151 ++++++++++++++++++ ...y-by-universal-identifier-or-throw.util.ts | 7 +- ...lat-entity-by-universal-identifier.util.ts | 3 +- ...niversal-flat-entity-maps-or-throw.util.ts | 23 +++ ...ity-relation-universal-identifiers.util.ts | 131 +++++++++++++++ ...ifier-from-flat-entity-id-or-throw.util.ts | 60 +++++++ ...lat-field-metadatas-to-create.spec.ts.snap | 6 +- .../types/flat-field-metadata.type.ts | 4 +- ...rate-index-for-flat-field-metadata.util.ts | 4 + ...metadata-deactivation-side-effects.util.ts | 18 ++- ...-field-metadata-is-nullable-update.util.ts | 36 +++-- ...flat-field-metadata-options-update.util.ts | 61 ++++--- ...-to-flat-front-component-to-create.util.ts | 8 +- ...ent-entity-to-flat-front-component.util.ts | 5 +- .../__mocks__/get-flat-index-metadata.mock.ts | 8 +- ...data-entity-to-flat-index-metadata.util.ts | 7 +- ...lat-navigation-menu-item-to-create.util.ts | 39 ++++- ...ntity-to-flat-navigation-menu-item.util.ts | 11 +- ...ation-menu-item-to-update-or-throw.util.ts | 19 ++- .../types/flat-object-metadata.type.ts | 4 +- ...object-metadata-update-side-effect.util.ts | 1 + ...fter-flat-object-identifier-update.util.ts | 18 ++- ...-to-flat-page-layout-tab-to-create.util.ts | 26 ++- ...tab-entity-to-flat-page-layout-tab.util.ts | 13 +- .../flat-page-layout-widget-maps.type.ts | 4 +- .../types/flat-page-layout-widget.type.ts | 2 +- ...-flat-page-layout-widget-to-create.util.ts | 31 +++- ...uration-to-universal-configuration.util.ts | 6 +- ...-entity-to-flat-page-layout-widget.util.ts | 24 ++- ...e-layout-widget-to-update-or-throw.util.ts | 28 +++- ...nput-to-flat-page-layout-to-create.util.ts | 25 ++- ...lat-page-layout-to-update-or-throw.util.ts | 25 ++- ...-layout-entity-to-flat-page-layout.util.ts | 15 +- ...-target-entity-to-flat-role-target.util.ts | 7 +- ...-role-input-to-flat-role-to-create.util.ts | 11 +- .../from-role-entity-to-flat-role.util.ts | 23 ++- ...lat-row-level-permission-predicate.util.ts | 16 +- ...w-level-permission-predicate-group.util.ts | 28 ++-- ...kill-input-to-flat-skill-to-create.util.ts | 8 +- .../from-skill-entity-to-flat-skill.util.ts | 5 +- ...input-to-flat-view-field-to-create.util.ts | 23 ++- ...ew-field-entity-to-flat-view-field.util.ts | 9 +- ...o-flat-view-filter-group-to-create.util.ts | 32 +++- ...ew-filter-group-to-update-or-throw.util.ts | 22 ++- ...p-entity-to-flat-view-filter-group.util.ts | 25 ++- ...nput-to-flat-view-filter-to-create.util.ts | 39 ++++- ...lat-view-filter-to-update-or-throw.util.ts | 43 ++++- ...-filter-entity-to-flat-view-filter.util.ts | 11 +- ...te-flat-view-groups-on-view-create.util.ts | 8 + ...input-to-flat-view-group-to-create.util.ts | 22 ++- ...ew-group-entity-to-flat-view-group.util.ts | 7 +- ...reduce-flat-view-groups-by-view-id.util.ts | 39 ----- ...roups-by-view-universal-identifier.util.ts | 49 ++++++ ...-view-input-to-flat-view-to-create.util.ts | 37 ++++- ...ut-to-flat-view-to-update-or-throw.util.ts | 48 ++++++ .../from-view-entity-to-flat-view.util.ts | 37 ++--- ...andle-flat-view-update-side-effect.util.ts | 1 + ...ok-input-to-flat-webhook-to-create.util.ts | 8 +- ...rom-webhook-entity-to-flat-webhook.util.ts | 5 +- .../front-component.service.ts | 2 +- ...ction-input-to-flat-logic-function.util.ts | 5 +- ...tion-entity-to-flat-logic-function.util.ts | 5 +- .../metadata-engine.module.ts | 17 +- .../navigation-menu-item.service.ts | 25 ++- .../object-metadata.service.ts | 48 +++++- ...ld-default-index-for-custom-object.util.ts | 5 +- .../services/page-layout-tab.service.ts | 11 +- .../services/page-layout-widget.service.ts | 21 ++- .../page-layout-duplication.service.ts | 57 +++++-- .../services/page-layout-update.service.ts | 56 +++++++ .../services/page-layout.service.ts | 19 ++- .../services/role-target.service.ts | 23 +-- ...nput-to-flat-role-target-to-create.util.ts | 21 ++- .../metadata-modules/role/role.resolver.ts | 31 +++- .../metadata-modules/role/role.service.ts | 2 +- ...level-permission-predicate-group.entity.ts | 2 +- .../row-level-permission-predicate.entity.ts | 4 +- .../row-level-permission-predicate.service.ts | 115 ++++++++++++- .../metadata-modules/skill/skill.service.ts | 2 +- .../services/view-field-v2.service.ts | 12 +- .../entities/view-filter-group.entity.ts | 2 +- .../services/view-filter-group.service.ts | 15 +- .../entities/view-filter.entity.ts | 2 +- .../services/view-filter.service.ts | 40 ++++- .../view-group/services/view-group.service.ts | 3 +- .../view/entities/view.entity.ts | 6 +- .../view/services/view.service.ts | 10 +- .../webhook/webhook.service.ts | 2 +- .../twenty-standard-application.service.ts | 2 - ...reate-standard-agent-flat-metadata.util.ts | 3 + ...reate-standard-index-flat-metadata.util.ts | 6 +- ...navigation-menu-item-flat-metadata.util.ts | 6 + ...dard-page-layout-tab-flat-metadata.util.ts | 18 ++- ...t-page-layout-widget-metadata-maps.util.ts | 6 + ...compute-my-first-dashboard-widgets.util.ts | 15 ++ ...d-page-layout-widget-flat-metadata.util.ts | 22 ++- ...standard-page-layout-flat-metadata.util.ts | 7 + ...create-standard-role-flat-metadata.util.ts | 6 + ...reate-standard-skill-flat-metadata.util.ts | 3 + ...-standard-view-field-flat-metadata.util.ts | 27 +++- ...standard-view-filter-flat-metadata.util.ts | 31 +++- ...-standard-view-group-flat-metadata.util.ts | 12 +- ...create-standard-view-flat-metadata.util.ts | 13 ++ ...ation-graphql-api-exception.interceptor.ts | 16 ++ ...igration-validate-build-and-run-service.ts | 2 + .../services/flat-agent-validator.service.ts | 8 +- .../services/flat-skill-validator.service.ts | 17 +- ...-step-operations.workspace-service.spec.ts | 3 + ...ny-view-fields-v2.integration-spec.ts.snap | 77 +-------- ...ate-view-field-v2.integration-spec.ts.snap | 72 ++------- ...enu-item-creation.integration-spec.ts.snap | 103 +++--------- ...e-object-metadata.integration-spec.ts.snap | 12 +- ...yout-tab-creation.integration-spec.ts.snap | 35 +--- ...t-widget-creation.integration-spec.ts.snap | 35 +--- ...-predicate-upsert.integration-spec.ts.snap | 136 +++------------- ...rcular-dependency.integration-spec.ts.snap | 39 +---- ...er-group-creation.integration-spec.ts.snap | 107 ++----------- ...lter-group-update.integration-spec.ts.snap | 34 +--- ...w-filter-creation.integration-spec.ts.snap | 112 ++----------- 133 files changed, 2141 insertions(+), 1166 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-graphql-api-exception.filter.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/__tests__/resolve-entity-relation-universal-identifiers.util.spec.ts create mode 100644 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 create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/resolve-universal-identifier-from-flat-entity-id-or-throw.util.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-universal-identifier.util.ts 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", } `;