diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index a2157b3c85b..6c6c01a5fce 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -2269,7 +2269,7 @@ export type Mutation = { startChannelSync: ChannelSyncSuccess; switchBillingPlan: BillingUpdateOutput; switchSubscriptionInterval: BillingUpdateOutput; - syncApplication: Scalars['Boolean']; + syncApplication: WorkspaceMigrationDto; trackAnalytics: Analytics; uninstallApplication: Scalars['Boolean']; updateApiKey?: Maybe; @@ -5174,6 +5174,12 @@ export enum WorkspaceMigrationActionType { update = 'update' } +export type WorkspaceMigrationDto = { + __typename?: 'WorkspaceMigrationDTO'; + actions: Scalars['JSON']; + applicationUniversalIdentifier: Scalars['String']; +}; + export type WorkspaceMigrationDeleteActionInput = { metadataName: AllMetadataName; type: WorkspaceMigrationActionType; diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/first.object.ts b/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/first.object.ts index 2a1ca954c00..f5e67153bc6 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/first.object.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/first.object.ts @@ -10,6 +10,8 @@ export default defineObject({ labelPlural: 'First objects', description: 'First object with duplicate ID', icon: 'IconBox', + labelIdentifierFieldMetadataUniversalIdentifier: + 'first-field-0000-0000-000000000001', fields: [ { universalIdentifier: 'first-field-0000-0000-000000000001', diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/second.object.ts b/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/second.object.ts index bc0e3d9d716..2e77292758c 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/second.object.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/invalid-app/src/second.object.ts @@ -10,6 +10,8 @@ export default defineObject({ labelPlural: 'Second objects', description: 'Second object with duplicate ID', icon: 'IconBox', + labelIdentifierFieldMetadataUniversalIdentifier: + 'second-field-0000-0000-000000000001', fields: [ { universalIdentifier: 'second-field-0000-0000-000000000001', diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts index 69b28f10ca4..2c255d0a09f 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts @@ -123,6 +123,8 @@ export const EXPECTED_MANIFEST: Manifest = { }, ], icon: 'IconNote', + labelIdentifierFieldMetadataUniversalIdentifier: + 'b0b1b2b3-b4b5-4000-8000-000000000002', labelPlural: 'Root notes', labelSingular: 'Root note', namePlural: 'rootNotes', @@ -199,6 +201,8 @@ export const EXPECTED_MANIFEST: Manifest = { }, ], icon: 'IconMail', + labelIdentifierFieldMetadataUniversalIdentifier: + '58a0a314-d7ea-4865-9850-7fb84e72f30b', labelPlural: 'Post cards', labelSingular: 'Post card', namePlural: 'postCards', diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/objects/postCard.object.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/objects/postCard.object.ts index c2a7f02f3de..286ad8a781b 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/objects/postCard.object.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/objects/postCard.object.ts @@ -20,6 +20,8 @@ export default defineObject({ labelPlural: 'Post cards', description: 'A post card object', icon: 'IconMail', + labelIdentifierFieldMetadataUniversalIdentifier: + CONTENT_FIELD_UNIVERSAL_IDENTIFIER, fields: [ { universalIdentifier: CONTENT_FIELD_UNIVERSAL_IDENTIFIER, diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/root.object.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/root.object.ts index 780d123fdb2..5382ead3fac 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/root.object.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/src/root.object.ts @@ -8,6 +8,8 @@ export default defineObject({ labelPlural: 'Root notes', description: 'A simple root-level object', icon: 'IconNote', + labelIdentifierFieldMetadataUniversalIdentifier: + 'b0b1b2b3-b4b5-4000-8000-000000000002', fields: [ { universalIdentifier: 'b0b1b2b3-b4b5-4000-8000-000000000002', diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts index e056aeb02fe..a75956ee3f3 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts @@ -22,6 +22,8 @@ export const EXPECTED_MANIFEST: Manifest = { labelPlural: 'My notes', description: 'A simple root-level object', icon: 'IconNote', + labelIdentifierFieldMetadataUniversalIdentifier: + 'e1e2e3e4-e5e6-4000-8000-000000000031', fields: [ { universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000031', diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/root-app/my.object.ts b/packages/twenty-sdk/src/cli/__tests__/apps/root-app/my.object.ts index 21670d3bd55..8adbc4719ce 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/root-app/my.object.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/root-app/my.object.ts @@ -8,6 +8,8 @@ export default defineObject({ labelPlural: 'My notes', description: 'A simple root-level object', icon: 'IconNote', + labelIdentifierFieldMetadataUniversalIdentifier: + 'e1e2e3e4-e5e6-4000-8000-000000000031', fields: [ { universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000031', diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts index 9d19629d779..f67c6b4be23 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts @@ -157,6 +157,7 @@ describe('manifestValidate', () => { namePlural: 'myObjects', labelSingular: 'My Object', labelPlural: 'My Objects', + labelIdentifierFieldMetadataUniversalIdentifier: sharedId, fields: [ { universalIdentifier: sharedId, diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts index 7730d35f003..66d216a6b43 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts @@ -10,6 +10,7 @@ import { type FrontComponentConfig, type LogicFunctionConfig, } from '@/sdk'; +import { type ObjectConfig } from '@/sdk/objects/object-config'; import { glob } from 'fast-glob'; import { readFile } from 'fs-extra'; import { basename, extname, relative } from 'path'; @@ -24,8 +25,8 @@ import { type ObjectManifest, type RoleManifest, } from 'twenty-shared/application'; -import { assertUnreachable } from 'twenty-shared/utils'; import { getInputSchemaFromSourceCode } from 'twenty-shared/logic-function'; +import { assertUnreachable } from 'twenty-shared/utils'; const loadSources = async (appPath: string): Promise => { return await glob(['**/*.ts', '**/*.tsx'], { @@ -98,11 +99,23 @@ export const buildManifest = async ( break; } case ManifestEntityKey.Objects: { - const extract = await extractManifestFromFile({ + const extract = await extractManifestFromFile({ appPath, filePath, }); - objects.push(extract.config); + + const { labelIdentifierFieldMetadataUniversalIdentifier, ...rest } = + extract.config; + + const objectManifest: ObjectManifest = { + ...rest, + labelIdentifierFieldMetadataUniversalIdentifier: + // TODO replace by system id universal identifier once we've refactored it + labelIdentifierFieldMetadataUniversalIdentifier ?? '', + }; + + objects.push(objectManifest); + errors.push(...extract.errors); objectsFilePaths.push(relativePath); break; diff --git a/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts b/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts index 7895f1ca317..d541501cc63 100644 --- a/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts +++ b/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts @@ -1,9 +1,9 @@ import { type ApplicationConfig } from '@/sdk/application/application-config'; import { type FrontComponentConfig } from '@/sdk/front-component-config'; import { type LogicFunctionConfig } from '@/sdk/logic-functions/logic-function-config'; +import { type ObjectConfig } from '@/sdk/objects/object-config'; import { type FieldManifest, - type ObjectManifest, type RoleManifest, } from 'twenty-shared/application'; @@ -15,14 +15,12 @@ export type ValidationResult = { export type DefinableEntity = | ApplicationConfig - | ObjectManifest + | ObjectConfig | FieldManifest | FrontComponentConfig | LogicFunctionConfig | RoleManifest; -export type DefineEntity = < - T extends C, ->( +export type DefineEntity = ( config: T, ) => ValidationResult; diff --git a/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-logic-function.spec.ts b/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-logic-function.spec.ts index 4bcc33555a5..42fcd801dc7 100644 --- a/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-logic-function.spec.ts +++ b/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-logic-function.spec.ts @@ -34,7 +34,7 @@ describe('defineLogicFunction', () => { const result = defineLogicFunction(config as any); - expect(result.config.cronTriggerSettings.pattern).toBeDefined(); + expect(result.config.cronTriggerSettings?.pattern).toBeDefined(); }); it('should accept databaseEventTriggerSettings', () => { @@ -49,7 +49,7 @@ describe('defineLogicFunction', () => { const result = defineLogicFunction(config as any); - expect(result.config.databaseEventTriggerSettings.eventName).toBeDefined(); + expect(result.config.databaseEventTriggerSettings?.eventName).toBeDefined(); }); it('should pass through optional fields', () => { diff --git a/packages/twenty-sdk/src/sdk/objects/__tests__/define-object.spec.ts b/packages/twenty-sdk/src/sdk/objects/__tests__/define-object.spec.ts index 086bf4c1e81..e728da46b1c 100644 --- a/packages/twenty-sdk/src/sdk/objects/__tests__/define-object.spec.ts +++ b/packages/twenty-sdk/src/sdk/objects/__tests__/define-object.spec.ts @@ -1,6 +1,6 @@ import { defineObject } from '@/sdk'; -import { FieldMetadataType } from 'twenty-shared/types'; import { type ObjectManifest } from 'twenty-shared/application'; +import { FieldMetadataType } from 'twenty-shared/types'; describe('defineObject', () => { const validConfig: ObjectManifest = { @@ -10,6 +10,8 @@ describe('defineObject', () => { labelSingular: 'Post Card', labelPlural: 'Post Cards', icon: 'IconMail', + labelIdentifierFieldMetadataUniversalIdentifier: + '58a0a314-d7ea-4865-9850-7fb84e72f30b', fields: [ { universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', @@ -111,17 +113,6 @@ describe('defineObject', () => { expect(result.config.fields).toEqual([]); }); - it('should accept missing fields', () => { - const config = { - ...validConfig, - fields: undefined, - }; - - const result = defineObject(config as any); - - expect(result.config.fields).toBeUndefined(); - }); - it('should return error when field is missing label', () => { const config = { ...validConfig, @@ -239,4 +230,19 @@ describe('defineObject', () => { expect(result.config.fields[0].options).toHaveLength(2); }); + + it('should return error when labelIdentifierFieldMetadataUniversalIdentifier references non-existent field', () => { + const config: ObjectManifest = { + ...validConfig, + labelIdentifierFieldMetadataUniversalIdentifier: + 'non-existent-field-uuid', + }; + + const result = defineObject(config); + + expect(result.success).toBe(false); + expect(result.errors).toContain( + 'labelIdentifierFieldMetadataUniversalIdentifier must reference a field defined in the fields array', + ); + }); }); diff --git a/packages/twenty-sdk/src/sdk/objects/define-object.ts b/packages/twenty-sdk/src/sdk/objects/define-object.ts index c3ad960518d..1a17ef69b77 100644 --- a/packages/twenty-sdk/src/sdk/objects/define-object.ts +++ b/packages/twenty-sdk/src/sdk/objects/define-object.ts @@ -1,10 +1,10 @@ -import { type ObjectManifest } from 'twenty-shared/application'; - -import { createValidationResult } from '@/sdk/common/utils/create-validation-result'; import { type DefineEntity } from '@/sdk/common/types/define-entity.type'; +import { createValidationResult } from '@/sdk/common/utils/create-validation-result'; import { validateFields } from '@/sdk/fields/validate-fields'; +import { type ObjectConfig } from '@/sdk/objects/object-config'; +import { isDefined } from 'twenty-shared/utils'; -export const defineObject: DefineEntity = (config) => { +export const defineObject: DefineEntity = (config) => { const errors = []; if (!config.universalIdentifier) { @@ -31,6 +31,19 @@ export const defineObject: DefineEntity = (config) => { errors.push(...fieldErrors); + if ( + isDefined(config.labelIdentifierFieldMetadataUniversalIdentifier) && + !config.fields.some( + (field) => + field.universalIdentifier === + config.labelIdentifierFieldMetadataUniversalIdentifier, + ) + ) { + errors.push( + 'labelIdentifierFieldMetadataUniversalIdentifier must reference a field defined in the fields array', + ); + } + return createValidationResult({ config, errors, diff --git a/packages/twenty-sdk/src/sdk/objects/object-config.ts b/packages/twenty-sdk/src/sdk/objects/object-config.ts new file mode 100644 index 00000000000..ad5c14d7789 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/objects/object-config.ts @@ -0,0 +1,8 @@ +import { type ObjectManifest } from 'twenty-shared/application'; + +export type ObjectConfig = Omit< + ObjectManifest, + 'labelIdentifierFieldMetadataUniversalIdentifier' +> & { + labelIdentifierFieldMetadataUniversalIdentifier?: string; +}; 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 2b6e7d77cd3..1eb35306789 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 @@ -753,7 +753,7 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new Error( `Failed to create navigation menu items: ${JSON.stringify(validateAndBuildResult, null, 2)}`, ); diff --git a/packages/twenty-server/src/engine/core-modules/application/dtos/workspace-migration.dto.ts b/packages/twenty-server/src/engine/core-modules/application/dtos/workspace-migration.dto.ts new file mode 100644 index 00000000000..7ff2d0e5016 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/dtos/workspace-migration.dto.ts @@ -0,0 +1,12 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import GraphQLJSON from 'graphql-type-json'; + +@ObjectType() +export class WorkspaceMigrationDTO { + @Field(() => String) + applicationUniversalIdentifier: string; + + @Field(() => GraphQLJSON) + actions: unknown[]; +} diff --git a/packages/twenty-server/src/engine/core-modules/application/resolvers/application-development.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/resolvers/application-development.resolver.ts index 623e0698509..96904c554ad 100644 --- a/packages/twenty-server/src/engine/core-modules/application/resolvers/application-development.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/application/resolvers/application-development.resolver.ts @@ -23,6 +23,7 @@ import { ApplicationInput } from 'src/engine/core-modules/application/dtos/appli import { CreateApplicationInput } from 'src/engine/core-modules/application/dtos/create-application.input'; import { GenerateApplicationTokenInput } from 'src/engine/core-modules/application/dtos/generate-application-token.input'; import { UploadApplicationFileInput } from 'src/engine/core-modules/application/dtos/uploadApplicationFileInput'; +import { WorkspaceMigrationDTO } from 'src/engine/core-modules/application/dtos/workspace-migration.dto'; import { ApplicationSyncService } from 'src/engine/core-modules/application/services/application-sync.service'; import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; import { AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto'; @@ -69,18 +70,23 @@ export class ApplicationDevelopmentResolver { }); } - @Mutation(() => Boolean) + @Mutation(() => WorkspaceMigrationDTO) @RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED) async syncApplication( @Args() { manifest }: ApplicationInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, - ) { - await this.applicationSyncService.synchronizeFromManifest({ - workspaceId, - manifest, - }); + ): Promise { + const workspaceMigration = + await this.applicationSyncService.synchronizeFromManifest({ + workspaceId, + manifest, + }); - return true; + return { + applicationUniversalIdentifier: + workspaceMigration.applicationUniversalIdentifier, + actions: workspaceMigration.actions, + }; } @Mutation(() => ApplicationDTO) diff --git a/packages/twenty-server/src/engine/core-modules/application/resolvers/application.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/resolvers/application.resolver.ts index fe2b83d9e29..13902ca6cde 100644 --- a/packages/twenty-server/src/engine/core-modules/application/resolvers/application.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/application/resolvers/application.resolver.ts @@ -144,10 +144,12 @@ export class ApplicationResolver { ); await this.workspaceMigrationRunnerService.run({ - actions, + workspaceMigration: { + actions, + applicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, + }, workspaceId, - applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, }); return true; diff --git a/packages/twenty-server/src/engine/core-modules/application/services/application-manifest-migration.service.ts b/packages/twenty-server/src/engine/core-modules/application/services/application-manifest-migration.service.ts index a6ce72286a3..c3fd22d0dda 100644 --- a/packages/twenty-server/src/engine/core-modules/application/services/application-manifest-migration.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/services/application-manifest-migration.service.ts @@ -20,6 +20,7 @@ import { PermissionFlagService } from 'src/engine/metadata-modules/permission-fl import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; +import { WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type'; @Injectable() export class ApplicationManifestMigrationService { @@ -44,7 +45,7 @@ export class ApplicationManifestMigrationService { manifest: Manifest; workspaceId: string; ownerFlatApplication: FlatApplication; - }) { + }): Promise { const now = new Date().toISOString(); const toAllUniversalFlatEntityMaps = @@ -88,7 +89,7 @@ export class ApplicationManifestMigrationService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Validation errors occurred while syncing application manifest metadata', @@ -104,6 +105,8 @@ export class ApplicationManifestMigrationService { workspaceId, ownerFlatApplication, }); + + return validateAndBuildResult.workspaceMigration; } /** diff --git a/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts b/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts index 371070c04bd..de20034a5fd 100644 --- a/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts @@ -23,6 +23,7 @@ import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-e import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; +import { WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type'; import { streamToBuffer } from 'src/utils/stream-to-buffer'; @Injectable() @@ -43,7 +44,7 @@ export class ApplicationSyncService { manifest, }: ApplicationInput & { workspaceId: string; - }) { + }): Promise { const application = await this.syncApplication({ workspaceId, manifest, @@ -51,13 +52,16 @@ export class ApplicationSyncService { const ownerFlatApplication: FlatApplication = application; - await this.applicationManifestMigrationService.syncMetadataFromManifest({ - manifest, - workspaceId, - ownerFlatApplication, - }); + const workspaceMigration = + await this.applicationManifestMigrationService.syncMetadataFromManifest({ + manifest, + workspaceId, + ownerFlatApplication, + }); this.logger.log('✅ Application sync from manifest completed'); + + return workspaceMigration; } private async syncApplication({ @@ -129,7 +133,7 @@ export class ApplicationSyncService { }: { workspaceId: string; applicationUniversalIdentifier: string; - }) { + }): Promise { const application = await this.applicationService.findByUniversalIdentifier( { universalIdentifier: applicationUniversalIdentifier, workspaceId }, ); @@ -180,7 +184,7 @@ export class ApplicationSyncService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Validation errors occurred while uninstalling application', @@ -191,5 +195,7 @@ export class ApplicationSyncService { applicationUniversalIdentifier, workspaceId, ); + + return validateAndBuildResult.workspaceMigration; } } diff --git a/packages/twenty-server/src/engine/core-modules/application/utils/from-object-manifest-to-universal-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/core-modules/application/utils/from-object-manifest-to-universal-flat-object-metadata.util.ts index ee1dde3f4d3..8b88c579709 100644 --- a/packages/twenty-server/src/engine/core-modules/application/utils/from-object-manifest-to-universal-flat-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/utils/from-object-manifest-to-universal-flat-object-metadata.util.ts @@ -35,7 +35,8 @@ export const fromObjectManifestToUniversalFlatObjectMetadata = ({ fieldUniversalIdentifiers: [], indexMetadataUniversalIdentifiers: [], viewUniversalIdentifiers: [], - labelIdentifierFieldMetadataUniversalIdentifier: null, + labelIdentifierFieldMetadataUniversalIdentifier: + objectManifest.labelIdentifierFieldMetadataUniversalIdentifier, imageIdentifierFieldMetadataUniversalIdentifier: null, createdAt: now, updatedAt: now, 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 7ae3ea566c8..a2b7325faae 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 @@ -158,7 +158,7 @@ export class AgentService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating agent', @@ -242,7 +242,7 @@ export class AgentService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating agent', @@ -357,7 +357,7 @@ export class AgentService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, `Multiple validation errors occurred while deleting agent${ids.length > 1 ? 's' : ''}`, 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 f3ba8329c36..5b433ac4ab5 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 @@ -127,7 +127,7 @@ export class CommandMenuItemService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating command menu item', @@ -194,7 +194,7 @@ export class CommandMenuItemService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating command menu item', @@ -254,7 +254,7 @@ export class CommandMenuItemService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting command menu item', diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts index 6e3194cd66a..501a6985d22 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts @@ -141,7 +141,7 @@ export class FieldMetadataService extends TypeOrmQueryService, - Expect< - Equal< - BaseUniversalCreateWorkspaceMigrationAction<'objectMetadata'>['flatEntity'], - UniversalFlatObjectMetadata - > - >, - Expect< - Equal< - BaseFlatCreateWorkspaceMigrationAction<'view'>['flatEntity'], - FlatView - > - >, ]; 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 27759d8e4b1..3fa242c49b2 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 @@ -114,7 +114,7 @@ export class FrontComponentService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating front component', @@ -185,7 +185,7 @@ export class FrontComponentService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating front component', @@ -262,7 +262,7 @@ export class FrontComponentService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while destroying front component', diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts index 90c21c5e69f..d395dbbee0a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts @@ -118,7 +118,7 @@ export class LogicFunctionMetadataService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating logic function', @@ -178,7 +178,7 @@ export class LogicFunctionMetadataService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating logic function', @@ -267,7 +267,7 @@ export class LogicFunctionMetadataService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while destroying logic function', 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 5e3b015ad7f..5ece8beffa3 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 @@ -198,7 +198,7 @@ export class NavigationMenuItemService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating navigation menu item', @@ -287,7 +287,7 @@ export class NavigationMenuItemService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating navigation menu item', @@ -367,7 +367,7 @@ export class NavigationMenuItemService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting navigation menu item', diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-deletion.service.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-deletion.service.ts index 8064f7acdd9..a78bd1ffcf6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-deletion.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-deletion.service.ts @@ -74,7 +74,7 @@ export class NavigationMenuItemDeletionService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting navigation menu items for deleted records', 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 13d5ea969ac..7dc53ac2b47 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 @@ -158,7 +158,7 @@ export class ObjectMetadataService extends TypeOrmQueryService 1 ? 's' : ''}`, @@ -560,7 +560,7 @@ export class ObjectMetadataService extends TypeOrmQueryService 1 ? 's' : ''}`, 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 c1edb80eb51..bbcfca7379c 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 @@ -540,7 +540,7 @@ export class RowLevelPermissionPredicateService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Validation errors occurred while upserting row level permission predicates', 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 f7acff2257e..3e405af3849 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 @@ -97,7 +97,7 @@ export class SkillService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating skill', @@ -159,7 +159,7 @@ export class SkillService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating skill', @@ -218,7 +218,7 @@ export class SkillService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting skill', @@ -308,7 +308,7 @@ export class SkillService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while activating skill', @@ -373,7 +373,7 @@ export class SkillService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deactivating skill', diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/view-field-group.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/view-field-group.service.ts index 11f2a134909..67077f3e910 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/view-field-group.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field-group/services/view-field-group.service.ts @@ -110,7 +110,7 @@ export class ViewFieldGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating view field groups', @@ -176,7 +176,7 @@ export class ViewFieldGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating view field group', @@ -247,7 +247,7 @@ export class ViewFieldGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting view field group', @@ -322,7 +322,7 @@ export class ViewFieldGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while destroying view field group', diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts index fa0dd8103de..f69d829cd1c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/services/view-field.service.ts @@ -116,7 +116,7 @@ export class ViewFieldService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating view fields', @@ -186,7 +186,7 @@ export class ViewFieldService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating view field', @@ -254,7 +254,7 @@ export class ViewFieldService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting view field', @@ -328,7 +328,7 @@ export class ViewFieldService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting view field', 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 386b7162ee9..bb143030930 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 @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { isDefined } from 'twenty-shared/utils'; import { IsNull, type Repository } from 'typeorm'; import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; @@ -82,7 +81,7 @@ export class ViewFilterGroupService { }, ); - if (isDefined(buildAndRunResult)) { + if (buildAndRunResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( buildAndRunResult, 'Multiple validation errors occurred while creating view filter group', @@ -154,7 +153,7 @@ export class ViewFilterGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating view filter group', @@ -227,7 +226,7 @@ export class ViewFilterGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting view filter group', @@ -305,7 +304,7 @@ export class ViewFilterGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while destroying view filter group', 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 143183f0ec7..cd60e64d9ad 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 @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { isDefined } from 'twenty-shared/utils'; import { IsNull, Repository } from 'typeorm'; import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; @@ -84,7 +83,7 @@ export class ViewFilterService { }, ); - if (isDefined(buildAndRunResult)) { + if (buildAndRunResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( buildAndRunResult, 'Multiple validation errors occurred while creating view filter', @@ -161,7 +160,7 @@ export class ViewFilterService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating view filter', @@ -232,7 +231,7 @@ export class ViewFilterService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting view filter', @@ -306,7 +305,7 @@ export class ViewFilterService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while destroying view filter', 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 111580740ad..55d2174af33 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 @@ -124,7 +124,7 @@ export class ViewGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating view groups', @@ -190,7 +190,7 @@ export class ViewGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating view group', @@ -261,7 +261,7 @@ export class ViewGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting view group', @@ -335,7 +335,7 @@ export class ViewGroupService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while destroying view group', 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 a0b8852a2b7..db37c5e3742 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 @@ -96,7 +96,7 @@ export class ViewService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating view', @@ -181,7 +181,7 @@ export class ViewService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating view', @@ -249,7 +249,7 @@ export class ViewService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting view', @@ -321,7 +321,7 @@ export class ViewService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while destroying view', 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 6ae2019f431..de09af4aeee 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 @@ -130,7 +130,7 @@ export class WebhookService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while creating webhook', @@ -202,7 +202,7 @@ export class WebhookService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while updating webhook', @@ -260,7 +260,7 @@ export class WebhookService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while deleting webhook', 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 82aa9987cd4..b1cdad517dd 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 @@ -125,7 +125,7 @@ export class TwentyStandardApplicationService { }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { throw new WorkspaceMigrationBuilderException( validateAndBuildResult, 'Multiple validation errors occurred while synchronizing twenty-standard application', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts index 656c3063cc9..3c8da258811 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util.ts @@ -1,20 +1,19 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; -import { type UniversalCreateFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action'; import { type UniversalCreateObjectAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action'; import { type UniversalCreatePageLayoutAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/types/workspace-migration-page-layout-action.type'; -import { type WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration'; +import { type WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type'; export type IdByUniversalIdentifierByMetadataName = { [P in AllMetadataName]?: Record; }; -const buildFieldIdByUniversalIdentifier = ({ +const buildFieldIdByUniversalIdentifierForObjectAction = ({ action, fieldMetadataIdByUniversalIdentifier, }: { - action: UniversalCreateObjectAction | UniversalCreateFieldAction; + action: UniversalCreateObjectAction; fieldMetadataIdByUniversalIdentifier: Record; }): Record | undefined => { const fieldIdByUniversalIdentifier = { @@ -96,7 +95,7 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({ const fieldIdByUniversalIdentifier = isDefined( fieldMetadataIdByUniversalIdentifier, ) - ? buildFieldIdByUniversalIdentifier({ + ? buildFieldIdByUniversalIdentifierForObjectAction({ action, fieldMetadataIdByUniversalIdentifier, }) @@ -113,12 +112,20 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({ return action; } + const relatedFieldId = isDefined( + action.relatedUniversalFlatFieldMetadata, + ) + ? fieldMetadataIdByUniversalIdentifier[ + action.relatedUniversalFlatFieldMetadata.universalIdentifier + ] + : undefined; + return { ...action, - fieldIdByUniversalIdentifier: buildFieldIdByUniversalIdentifier({ - action, - fieldMetadataIdByUniversalIdentifier, - }), + id: fieldMetadataIdByUniversalIdentifier[ + action.flatEntity.universalIdentifier + ], + relatedFieldId, }; } case 'pageLayout': { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts index a0abc73c115..aa3f953e791 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts @@ -30,8 +30,8 @@ import { WorkspaceMigrationRoleActionsBuilderService } from 'src/engine/workspac import { WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate-group/workspace-migration-row-level-permission-predicate-group-actions-builder.service'; import { WorkspaceMigrationRowLevelPermissionPredicateActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate/workspace-migration-row-level-permission-predicate-actions-builder.service'; import { WorkspaceMigrationSkillActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/skill/workspace-migration-skill-actions-builder.service'; -import { WorkspaceMigrationViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/workspace-migration-view-field-actions-builder.service'; import { WorkspaceMigrationViewFieldGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/workspace-migration-view-field-group-actions-builder.service'; +import { WorkspaceMigrationViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/workspace-migration-view-field-actions-builder.service'; import { WorkspaceMigrationViewFilterGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-filter-group/workspace-migration-view-filter-group-actions-builder.service'; import { WorkspaceMigrationViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-filter/workspace-migration-view-filter-actions-builder.service'; import { WorkspaceMigrationViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-group/workspace-migration-view-group-actions-builder.service'; @@ -1138,7 +1138,6 @@ export class WorkspaceMigrationBuildOrchestratorService { ...aggregatedOrchestratorActionsReport.webhook.update, /// ], - workspaceId, }, }; } 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 fe5a298dd8c..85c8c943697 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 @@ -27,6 +27,7 @@ import { FromToAllUniversalFlatEntityMaps, WorkspaceMigrationOrchestratorBuildArgs, WorkspaceMigrationOrchestratorFailedResult, + WorkspaceMigrationOrchestratorSuccessfulResult, } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type'; import { computeUniversalFlatEntityMapsFromTo } from 'src/engine/workspace-manager/workspace-migration/utils/compute-universal-flat-entity-maps-from-to.util'; import { InferDeletionFromMissingEntities } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/infer-deletion-from-missing-entities.type'; @@ -217,7 +218,10 @@ export class WorkspaceMigrationValidateBuildAndRunService { args: WorkspaceMigrationOrchestratorBuildArgs & { idByUniversalIdentifierByMetadataName?: IdByUniversalIdentifierByMetadataName; }, - ) { + ): Promise< + | WorkspaceMigrationOrchestratorFailedResult + | WorkspaceMigrationOrchestratorSuccessfulResult + > { const { idByUniversalIdentifierByMetadataName, ...buildArgs } = args; const validateAndBuildResult = @@ -239,10 +243,6 @@ export class WorkspaceMigrationValidateBuildAndRunService { return validateAndBuildResult; } - if (validateAndBuildResult.workspaceMigration.actions.length === 0) { - return undefined; - } - const workspaceMigration = isDefined(idByUniversalIdentifierByMetadataName) ? enrichCreateWorkspaceMigrationActionsWithIds({ idByUniversalIdentifierByMetadataName, @@ -250,13 +250,24 @@ export class WorkspaceMigrationValidateBuildAndRunService { }) : validateAndBuildResult.workspaceMigration; - const { metadataEvents } = - await this.workspaceMigrationRunnerService.run(workspaceMigration); + if (workspaceMigration.actions.length > 0) { + const { metadataEvents } = await this.workspaceMigrationRunnerService.run( + { + workspaceId: args.workspaceId, + workspaceMigration, + }, + ); - this.metadataEventEmitter.emitMetadataEvents({ - metadataEvents, - workspaceId: args.workspaceId, - }); + this.metadataEventEmitter.emitMetadataEvents({ + metadataEvents, + workspaceId: args.workspaceId, + }); + } + + return { + status: 'success', + workspaceMigration, + }; } public async validateBuildAndRunWorkspaceMigration({ @@ -265,7 +276,8 @@ export class WorkspaceMigrationValidateBuildAndRunService { isSystemBuild = false, applicationUniversalIdentifier, }: ValidateBuildAndRunWorkspaceMigrationFromMatriceArgs): Promise< - WorkspaceMigrationOrchestratorFailedResult | undefined + | WorkspaceMigrationOrchestratorFailedResult + | WorkspaceMigrationOrchestratorSuccessfulResult > { const { fromToAllFlatEntityMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type.ts index 8aacc5c4d39..6118c682952 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type.ts @@ -8,8 +8,8 @@ import { import { type WorkspaceMigrationBuilderAdditionalCacheDataMaps } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-builder-additional-cache-data-maps.type'; import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type'; import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type'; -import { type WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration'; import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-builder-options.type'; +import { type WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type'; export type FromToAllUniversalFlatEntityMaps = { [P in keyof AllUniversalFlatEntityMaps]?: FromTo< diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/__snapshots__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/__snapshots__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts.snap new file mode 100644 index 00000000000..a3c7a792ddc --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/__snapshots__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should match snapshot 1`] = ` +{ + "agent": [], + "commandMenuItem": [], + "fieldMetadata": [ + "calendarViewUniversalIdentifiers", + "kanbanAggregateOperationViewUniversalIdentifiers", + "mainGroupByFieldMetadataViewUniversalIdentifiers", + "viewFieldUniversalIdentifiers", + "viewFilterUniversalIdentifiers", + ], + "frontComponent": [], + "index": [], + "logicFunction": [], + "navigationMenuItem": [], + "objectMetadata": [ + "fieldUniversalIdentifiers", + "viewUniversalIdentifiers", + "indexMetadataUniversalIdentifiers", + ], + "pageLayout": [ + "tabUniversalIdentifiers", + ], + "pageLayoutTab": [ + "widgetUniversalIdentifiers", + ], + "pageLayoutWidget": [], + "role": [ + "roleTargetUniversalIdentifiers", + ], + "roleTarget": [], + "rowLevelPermissionPredicate": [], + "rowLevelPermissionPredicateGroup": [ + "childRowLevelPermissionPredicateGroupUniversalIdentifiers", + ], + "skill": [], + "view": [ + "viewFieldUniversalIdentifiers", + "viewFieldGroupUniversalIdentifiers", + "viewFilterUniversalIdentifiers", + "viewGroupUniversalIdentifiers", + "viewFilterGroupUniversalIdentifiers", + ], + "viewField": [], + "viewFieldGroup": [ + "viewFieldUniversalIdentifiers", + ], + "viewFilter": [], + "viewFilterGroup": [ + "viewFilterUniversalIdentifiers", + "childViewFilterGroupUniversalIdentifiers", + ], + "viewGroup": [], + "webhook": [], +} +`; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts new file mode 100644 index 00000000000..9d284c0a27b --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/__tests__/all-universal-flat-entity-foreign-key-aggregator-properties.constant.spec.ts @@ -0,0 +1,14 @@ +import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; + +import { ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; + +describe('ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES', () => { + it('should match snapshot', () => { + expect( + Object.keys(ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES), + ).toMatchObject(Object.values(ALL_METADATA_NAME)); + expect( + ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES, + ).toMatchSnapshot(); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant.ts new file mode 100644 index 00000000000..0bd9ccce7b9 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant.ts @@ -0,0 +1,71 @@ +import { + ALL_METADATA_NAME, + type AllMetadataName, +} from 'twenty-shared/metadata'; +import { isDefined } from 'twenty-shared/utils'; + +import { ALL_UNIVERSAL_METADATA_RELATIONS } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant'; + +type ExtractForeignKeyAggregatorFromManyToOneRelations< + ManyToOneRelations, + TargetMetadataName extends AllMetadataName, +> = { + [K in keyof ManyToOneRelations]: ManyToOneRelations[K] extends { + metadataName: TargetMetadataName; + universalFlatEntityForeignKeyAggregator: infer Agg; + } + ? Agg extends string + ? Agg + : never + : never; +}[keyof ManyToOneRelations]; + +export type ExtractUniversalForeignKeyAggregatorForMetadataName< + T extends AllMetadataName, +> = { + [M in AllMetadataName]: ExtractForeignKeyAggregatorFromManyToOneRelations< + (typeof ALL_UNIVERSAL_METADATA_RELATIONS)[M]['manyToOne'], + T + >; +}[AllMetadataName]; + +type UniversalFlatEntityForeignKeyAggregatorProperties = { + [P in AllMetadataName]: ExtractUniversalForeignKeyAggregatorForMetadataName

[]; +}; + +const computeForeignKeyAggregatorProperties = ( + metadataName: T, +): ExtractUniversalForeignKeyAggregatorForMetadataName[] => { + const aggregatorProperties: ExtractUniversalForeignKeyAggregatorForMetadataName[] = + []; + + for (const relationsEntry of Object.values( + ALL_UNIVERSAL_METADATA_RELATIONS, + )) { + for (const relation of Object.values(relationsEntry.manyToOne)) { + if (!isDefined(relation)) { + continue; + } + + if ( + relation.metadataName === metadataName && + isDefined(relation.universalFlatEntityForeignKeyAggregator) + ) { + aggregatorProperties.push( + relation.universalFlatEntityForeignKeyAggregator as ExtractUniversalForeignKeyAggregatorForMetadataName, + ); + } + } + } + + return aggregatorProperties; +}; + +export const ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES = + Object.values(ALL_METADATA_NAME).reduce( + (acc, metadataName) => ({ + ...acc, + [metadataName]: computeForeignKeyAggregatorProperties(metadataName), + }), + {} as UniversalFlatEntityForeignKeyAggregatorProperties, + ); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/__tests__/delete-universal-flat-entity-foreign-key-aggregators.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/__tests__/delete-universal-flat-entity-foreign-key-aggregators.util.spec.ts new file mode 100644 index 00000000000..17a6610e157 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/__tests__/delete-universal-flat-entity-foreign-key-aggregators.util.spec.ts @@ -0,0 +1,36 @@ +import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; +import { deleteUniversalFlatEntityForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-foreign-key-aggregators.util'; + +describe('deleteUniversalFlatEntityForeignKeyAggregators', () => { + it('should delete all aggregator keys from the entity', () => { + const objectMetadata = getFlatObjectMetadataMock({ + universalIdentifier: 'object-1', + fieldUniversalIdentifiers: ['field-1', 'field-2'], + viewUniversalIdentifiers: ['view-1'], + indexMetadataUniversalIdentifiers: ['index-1'], + }); + + const result = deleteUniversalFlatEntityForeignKeyAggregators({ + universalFlatEntity: objectMetadata, + metadataName: 'objectMetadata', + }); + + expect(result).not.toHaveProperty('fieldUniversalIdentifiers'); + expect(result).not.toHaveProperty('viewUniversalIdentifiers'); + expect(result).not.toHaveProperty('indexMetadataUniversalIdentifiers'); + expect(result.universalIdentifier).toBe('object-1'); + }); + + it('should return the same reference for metadata with no aggregators', () => { + const entity = getFlatObjectMetadataMock({ + universalIdentifier: 'logic-1', + }); + + const result = deleteUniversalFlatEntityForeignKeyAggregators({ + universalFlatEntity: entity, + metadataName: 'logicFunction' as any, + }); + + expect(result).toBe(entity); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/__tests__/reset-universal-flat-entity-foreign-key-aggregators.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/__tests__/reset-universal-flat-entity-foreign-key-aggregators.util.spec.ts new file mode 100644 index 00000000000..add5d9f7608 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/__tests__/reset-universal-flat-entity-foreign-key-aggregators.util.spec.ts @@ -0,0 +1,50 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; +import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; +import { resetUniversalFlatEntityForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; + +describe('resetUniversalFlatEntityForeignKeyAggregators', () => { + it('should reset aggregator properties to empty arrays for objectMetadata', () => { + const objectMetadata = getFlatObjectMetadataMock({ + universalIdentifier: 'object-1', + fieldUniversalIdentifiers: ['field-1', 'field-2'], + viewUniversalIdentifiers: ['view-1'], + indexMetadataUniversalIdentifiers: ['index-1'], + }); + + const result = resetUniversalFlatEntityForeignKeyAggregators({ + universalFlatEntity: objectMetadata, + metadataName: 'objectMetadata', + }); + + expect(result).toMatchObject({ + fieldUniversalIdentifiers: [], + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + }); + }); + + it('should reset aggregator properties to empty arrays for fieldMetadata', () => { + const fieldMetadata = getFlatFieldMetadataMock({ + universalIdentifier: 'field-1', + objectMetadataId: 'object-1', + type: FieldMetadataType.TEXT, + viewFieldUniversalIdentifiers: ['vf-1'], + viewFilterUniversalIdentifiers: ['filter-1'], + }); + + const result = resetUniversalFlatEntityForeignKeyAggregators({ + universalFlatEntity: fieldMetadata, + metadataName: 'fieldMetadata', + }); + + expect(result).toMatchObject({ + viewFieldUniversalIdentifiers: [], + viewFilterUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + }); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-foreign-key-aggregators.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-foreign-key-aggregators.util.ts new file mode 100644 index 00000000000..7f4b3ff4eb6 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-foreign-key-aggregators.util.ts @@ -0,0 +1,29 @@ +import { type AllMetadataName } from 'twenty-shared/metadata'; + +import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; +import { ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; + +export const deleteUniversalFlatEntityForeignKeyAggregators = < + T extends AllMetadataName, +>({ + universalFlatEntity, + metadataName, +}: { + universalFlatEntity: MetadataUniversalFlatEntity; + metadataName: T; +}): MetadataUniversalFlatEntity => { + const aggregatorProperties = + ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES[metadataName]; + + if (aggregatorProperties.length === 0) { + return universalFlatEntity; + } + + const result = { ...universalFlatEntity }; + + for (const aggregatorProperty of aggregatorProperties) { + delete (result as Record)[aggregatorProperty]; + } + + return result; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util.ts new file mode 100644 index 00000000000..6424de80f96 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util.ts @@ -0,0 +1,48 @@ +import { type AllMetadataName } from 'twenty-shared/metadata'; + +import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; +import { + ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES, + type ExtractUniversalForeignKeyAggregatorForMetadataName, +} from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; + +export const getUniversalFlatEntityEmptyForeignKeyAggregators = < + T extends AllMetadataName, +>({ + metadataName, +}: { + metadataName: T; +}) => { + const aggregatorProperties = + ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES[metadataName]; + + const emptyAggregatorsRecord = {} as Record< + ExtractUniversalForeignKeyAggregatorForMetadataName, + string[] + >; + + for (const aggregatorProperty of aggregatorProperties) { + emptyAggregatorsRecord[aggregatorProperty] = []; + } + + return emptyAggregatorsRecord; +}; + +export const resetUniversalFlatEntityForeignKeyAggregators = < + T extends AllMetadataName, +>({ + universalFlatEntity, + metadataName, +}: { + universalFlatEntity: MetadataUniversalFlatEntity; + metadataName: T; +}): MetadataUniversalFlatEntity => { + const overrides = getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName, + }); + + return { + ...universalFlatEntity, + ...overrides, + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-non-relation-fields-into-object-actions.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-non-relation-fields-into-object-actions.util.spec.ts index a8af6529f36..a2e296d4944 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-non-relation-fields-into-object-actions.util.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-non-relation-fields-into-object-actions.util.spec.ts @@ -9,10 +9,18 @@ import { type UniversalCreateFieldAction } from 'src/engine/workspace-manager/wo import { type UniversalCreateObjectAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action'; describe('aggregateNonRelationFieldsIntoObjectActions', () => { - it('should merge non-relation fields into matching create-object actions', () => { + it('should merge non-relation field into matching create-object action', () => { const objectUniversalId = 'object-1'; const fieldUniversalId = 'field-1'; + const flatFieldMetadata = getFlatFieldMetadataMock({ + universalIdentifier: fieldUniversalId, + objectMetadataId: 'object-metadata-id', + objectMetadataUniversalIdentifier: objectUniversalId, + type: FieldMetadataType.TEXT, + name: 'testField', + }); + const input: OrchestratorActionsReport = { ...createEmptyOrchestratorActionsReport(), objectMetadata: { @@ -36,18 +44,8 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: fieldUniversalId, - objectMetadataId: 'object-metadata-id', - objectMetadataUniversalIdentifier: objectUniversalId, - type: FieldMetadataType.TEXT, - name: 'testField', - }), - ], - fieldIdByUniversalIdentifier: { - [fieldUniversalId]: 'generated-field-id', - }, + flatEntity: flatFieldMetadata, + id: 'generated-field-id', } satisfies UniversalCreateFieldAction, ], update: [], @@ -75,11 +73,21 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { expect(result.fieldMetadata.create).toHaveLength(0); }); - it('should keep relation fields in separate create-field actions', () => { + it('should keep relation field in separate create-field action', () => { const objectUniversalId = 'object-1'; const relationFieldUniversalId = 'relation-field-1'; const targetFieldUniversalId = 'target-field-1'; + const flatRelationFieldMetadata = getFlatFieldMetadataMock({ + universalIdentifier: relationFieldUniversalId, + objectMetadataId: 'object-metadata-id', + objectMetadataUniversalIdentifier: objectUniversalId, + type: FieldMetadataType.RELATION, + name: 'relationField', + relationTargetFieldMetadataUniversalIdentifier: targetFieldUniversalId, + relationTargetObjectMetadataUniversalIdentifier: 'other-object', + }); + const input: OrchestratorActionsReport = { ...createEmptyOrchestratorActionsReport(), objectMetadata: { @@ -103,21 +111,8 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: relationFieldUniversalId, - objectMetadataId: 'object-metadata-id', - objectMetadataUniversalIdentifier: objectUniversalId, - type: FieldMetadataType.RELATION, - name: 'relationField', - relationTargetFieldMetadataUniversalIdentifier: - targetFieldUniversalId, - relationTargetObjectMetadataUniversalIdentifier: 'other-object', - }), - ], - fieldIdByUniversalIdentifier: { - [relationFieldUniversalId]: 'generated-relation-field-id', - }, + flatEntity: flatRelationFieldMetadata, + id: 'generated-relation-field-id', } satisfies UniversalCreateFieldAction, ], update: [], @@ -137,17 +132,23 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { // Relation field should remain in field actions expect(result.fieldMetadata.create).toMatchObject([ { - universalFlatFieldMetadatas: [ - { universalIdentifier: relationFieldUniversalId }, - ], + flatEntity: { universalIdentifier: relationFieldUniversalId }, }, ]); }); - it('should keep fields for existing objects in separate create-field actions', () => { + it('should keep field for existing object in separate create-field action', () => { const existingObjectUniversalId = 'existing-object'; const fieldUniversalId = 'field-for-existing-object'; + const flatFieldMetadata = getFlatFieldMetadataMock({ + universalIdentifier: fieldUniversalId, + objectMetadataId: 'existing-object-metadata-id', + objectMetadataUniversalIdentifier: existingObjectUniversalId, + type: FieldMetadataType.TEXT, + name: 'fieldForExistingObject', + }); + const input: OrchestratorActionsReport = { ...createEmptyOrchestratorActionsReport(), objectMetadata: { @@ -160,18 +161,8 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: fieldUniversalId, - objectMetadataId: 'existing-object-metadata-id', - objectMetadataUniversalIdentifier: existingObjectUniversalId, - type: FieldMetadataType.TEXT, - name: 'fieldForExistingObject', - }), - ], - fieldIdByUniversalIdentifier: { - [fieldUniversalId]: 'generated-field-id', - }, + flatEntity: flatFieldMetadata, + id: 'generated-field-id', } satisfies UniversalCreateFieldAction, ], update: [], @@ -189,18 +180,34 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { // Field should remain in field actions (no matching object to merge into) expect(result.fieldMetadata.create).toMatchObject([ { - universalFlatFieldMetadatas: [ - { universalIdentifier: fieldUniversalId }, - ], + flatEntity: { universalIdentifier: fieldUniversalId }, }, ]); }); - it('should handle mixed relation and non-relation fields', () => { + it('should handle mixed relation and non-relation fields as separate actions', () => { const objectUniversalId = 'object-1'; const textFieldUniversalId = 'text-field'; const relationFieldUniversalId = 'relation-field'; + const flatTextFieldMetadata = getFlatFieldMetadataMock({ + universalIdentifier: textFieldUniversalId, + objectMetadataId: 'object-metadata-id', + objectMetadataUniversalIdentifier: objectUniversalId, + type: FieldMetadataType.TEXT, + name: 'textField', + }); + + const flatRelationFieldMetadata = getFlatFieldMetadataMock({ + universalIdentifier: relationFieldUniversalId, + objectMetadataId: 'object-metadata-id', + objectMetadataUniversalIdentifier: objectUniversalId, + type: FieldMetadataType.RELATION, + name: 'relationField', + relationTargetFieldMetadataUniversalIdentifier: 'target-field', + relationTargetObjectMetadataUniversalIdentifier: 'other-object', + }); + const input: OrchestratorActionsReport = { ...createEmptyOrchestratorActionsReport(), objectMetadata: { @@ -224,28 +231,14 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: textFieldUniversalId, - objectMetadataId: 'object-metadata-id', - objectMetadataUniversalIdentifier: objectUniversalId, - type: FieldMetadataType.TEXT, - name: 'textField', - }), - getFlatFieldMetadataMock({ - universalIdentifier: relationFieldUniversalId, - objectMetadataId: 'object-metadata-id', - objectMetadataUniversalIdentifier: objectUniversalId, - type: FieldMetadataType.RELATION, - name: 'relationField', - relationTargetFieldMetadataUniversalIdentifier: 'target-field', - relationTargetObjectMetadataUniversalIdentifier: 'other-object', - }), - ], - fieldIdByUniversalIdentifier: { - [textFieldUniversalId]: 'text-field-id', - [relationFieldUniversalId]: 'relation-field-id', - }, + flatEntity: flatTextFieldMetadata, + id: 'text-field-id', + } satisfies UniversalCreateFieldAction, + { + type: 'create', + metadataName: 'fieldMetadata', + flatEntity: flatRelationFieldMetadata, + id: 'relation-field-id', } satisfies UniversalCreateFieldAction, ], update: [], @@ -269,9 +262,7 @@ describe('aggregateNonRelationFieldsIntoObjectActions', () => { // Relation field should remain in field actions expect(result.fieldMetadata.create).toMatchObject([ { - universalFlatFieldMetadatas: [ - { universalIdentifier: relationFieldUniversalId }, - ], + flatEntity: { universalIdentifier: relationFieldUniversalId }, }, ]); }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-relation-field-pairs.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-relation-field-pairs.util.spec.ts index a7cd724585d..c3b10ea5c60 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-relation-field-pairs.util.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/aggregate-relation-field-pairs.util.spec.ts @@ -7,7 +7,7 @@ import { aggregateRelationFieldPairs } from 'src/engine/workspace-manager/worksp import { type UniversalCreateFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action'; describe('aggregateRelationFieldPairs', () => { - it('should bundle relation field pairs into single action with merged fieldIdByUniversalIdentifier', () => { + it('should bundle relation field pairs into single action with relatedFieldId', () => { const attachmentObjectId = 'attachment-object'; const taskObjectId = 'task-object'; const targetTaskFieldId = 'target-task-field'; @@ -20,41 +20,32 @@ describe('aggregateRelationFieldPairs', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: targetTaskFieldId, - objectMetadataId: 'attachment-object-metadata-id', - objectMetadataUniversalIdentifier: attachmentObjectId, - type: FieldMetadataType.RELATION, - name: 'targetTask', - relationTargetFieldMetadataUniversalIdentifier: - attachmentsFieldId, - relationTargetObjectMetadataUniversalIdentifier: taskObjectId, - }), - ], - fieldIdByUniversalIdentifier: { - [targetTaskFieldId]: 'target-task-generated-id', - }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: targetTaskFieldId, + objectMetadataId: 'attachment-object-metadata-id', + objectMetadataUniversalIdentifier: attachmentObjectId, + type: FieldMetadataType.RELATION, + name: 'targetTask', + relationTargetFieldMetadataUniversalIdentifier: + attachmentsFieldId, + relationTargetObjectMetadataUniversalIdentifier: taskObjectId, + }), + id: 'target-task-generated-id', } satisfies UniversalCreateFieldAction, { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: attachmentsFieldId, - objectMetadataId: 'task-object-metadata-id', - objectMetadataUniversalIdentifier: taskObjectId, - type: FieldMetadataType.RELATION, - name: 'attachments', - relationTargetFieldMetadataUniversalIdentifier: - targetTaskFieldId, - relationTargetObjectMetadataUniversalIdentifier: - attachmentObjectId, - }), - ], - fieldIdByUniversalIdentifier: { - [attachmentsFieldId]: 'attachments-generated-id', - }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: attachmentsFieldId, + objectMetadataId: 'task-object-metadata-id', + objectMetadataUniversalIdentifier: taskObjectId, + type: FieldMetadataType.RELATION, + name: 'attachments', + relationTargetFieldMetadataUniversalIdentifier: targetTaskFieldId, + relationTargetObjectMetadataUniversalIdentifier: + attachmentObjectId, + }), + id: 'attachments-generated-id', } satisfies UniversalCreateFieldAction, ], update: [], @@ -66,17 +57,15 @@ describe('aggregateRelationFieldPairs', () => { orchestratorActionsReport: input, }); - // Should be bundled into a single action with both fields and merged ID map + // Should be bundled into a single action with flatEntity and relatedUniversalFlatFieldMetadata expect(result.fieldMetadata.create).toMatchObject([ { - universalFlatFieldMetadatas: [ - { universalIdentifier: targetTaskFieldId }, - { universalIdentifier: attachmentsFieldId }, - ], - fieldIdByUniversalIdentifier: { - [targetTaskFieldId]: 'target-task-generated-id', - [attachmentsFieldId]: 'attachments-generated-id', + flatEntity: { universalIdentifier: targetTaskFieldId }, + id: 'target-task-generated-id', + relatedUniversalFlatFieldMetadata: { + universalIdentifier: attachmentsFieldId, }, + relatedFieldId: 'attachments-generated-id', }, ]); }); @@ -92,18 +81,14 @@ describe('aggregateRelationFieldPairs', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: fieldUniversalId, - objectMetadataId: 'some-object-metadata-id', - objectMetadataUniversalIdentifier: objectUniversalId, - type: FieldMetadataType.TEXT, - name: 'standaloneField', - }), - ], - fieldIdByUniversalIdentifier: { - [fieldUniversalId]: 'standalone-field-id', - }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: fieldUniversalId, + objectMetadataId: 'some-object-metadata-id', + objectMetadataUniversalIdentifier: objectUniversalId, + type: FieldMetadataType.TEXT, + name: 'standaloneField', + }), + id: 'standalone-field-id', } satisfies UniversalCreateFieldAction, ], update: [], @@ -115,14 +100,16 @@ describe('aggregateRelationFieldPairs', () => { orchestratorActionsReport: input, }); - // Should remain as single action + // Should remain as single action without relatedUniversalFlatFieldMetadata expect(result.fieldMetadata.create).toMatchObject([ { - universalFlatFieldMetadatas: [ - { universalIdentifier: fieldUniversalId }, - ], + flatEntity: { universalIdentifier: fieldUniversalId }, }, ]); + expect( + result.fieldMetadata.create[0].relatedUniversalFlatFieldMetadata, + ).toBeUndefined(); + expect(result.fieldMetadata.create[0].relatedFieldId).toBeUndefined(); }); it('should handle relation field with target not being created (existing field)', () => { @@ -137,21 +124,17 @@ describe('aggregateRelationFieldPairs', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: relationFieldId, - objectMetadataId: 'some-object-metadata-id', - objectMetadataUniversalIdentifier: objectUniversalId, - type: FieldMetadataType.RELATION, - name: 'relationToExisting', - relationTargetFieldMetadataUniversalIdentifier: - existingTargetFieldId, - relationTargetObjectMetadataUniversalIdentifier: 'other-object', - }), - ], - fieldIdByUniversalIdentifier: { - [relationFieldId]: 'new-relation-field-id', - }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: relationFieldId, + objectMetadataId: 'some-object-metadata-id', + objectMetadataUniversalIdentifier: objectUniversalId, + type: FieldMetadataType.RELATION, + name: 'relationToExisting', + relationTargetFieldMetadataUniversalIdentifier: + existingTargetFieldId, + relationTargetObjectMetadataUniversalIdentifier: 'other-object', + }), + id: 'new-relation-field-id', } satisfies UniversalCreateFieldAction, ], update: [], @@ -163,12 +146,16 @@ describe('aggregateRelationFieldPairs', () => { orchestratorActionsReport: input, }); - // Should remain as single action (target not being created) + // Should remain as single action without relatedUniversalFlatFieldMetadata (target not being created) expect(result.fieldMetadata.create).toMatchObject([ { - universalFlatFieldMetadatas: [{ universalIdentifier: relationFieldId }], + flatEntity: { universalIdentifier: relationFieldId }, }, ]); + expect( + result.fieldMetadata.create[0].relatedUniversalFlatFieldMetadata, + ).toBeUndefined(); + expect(result.fieldMetadata.create[0].relatedFieldId).toBeUndefined(); }); it('should handle multiple independent relation pairs', () => { @@ -184,66 +171,58 @@ describe('aggregateRelationFieldPairs', () => { { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: pair1FieldA, - objectMetadataId: 'object-1-metadata-id', - objectMetadataUniversalIdentifier: 'object-1', - type: FieldMetadataType.RELATION, - name: 'pair1FieldA', - relationTargetFieldMetadataUniversalIdentifier: pair1FieldB, - relationTargetObjectMetadataUniversalIdentifier: 'object-2', - }), - ], - fieldIdByUniversalIdentifier: { [pair1FieldA]: 'id-1a' }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: pair1FieldA, + objectMetadataId: 'object-1-metadata-id', + objectMetadataUniversalIdentifier: 'object-1', + type: FieldMetadataType.RELATION, + name: 'pair1FieldA', + relationTargetFieldMetadataUniversalIdentifier: pair1FieldB, + relationTargetObjectMetadataUniversalIdentifier: 'object-2', + }), + id: 'id-1a', } satisfies UniversalCreateFieldAction, { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: pair1FieldB, - objectMetadataId: 'object-2-metadata-id', - objectMetadataUniversalIdentifier: 'object-2', - type: FieldMetadataType.RELATION, - name: 'pair1FieldB', - relationTargetFieldMetadataUniversalIdentifier: pair1FieldA, - relationTargetObjectMetadataUniversalIdentifier: 'object-1', - }), - ], - fieldIdByUniversalIdentifier: { [pair1FieldB]: 'id-1b' }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: pair1FieldB, + objectMetadataId: 'object-2-metadata-id', + objectMetadataUniversalIdentifier: 'object-2', + type: FieldMetadataType.RELATION, + name: 'pair1FieldB', + relationTargetFieldMetadataUniversalIdentifier: pair1FieldA, + relationTargetObjectMetadataUniversalIdentifier: 'object-1', + }), + id: 'id-1b', } satisfies UniversalCreateFieldAction, { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: pair2FieldA, - objectMetadataId: 'object-3-metadata-id', - objectMetadataUniversalIdentifier: 'object-3', - type: FieldMetadataType.RELATION, - name: 'pair2FieldA', - relationTargetFieldMetadataUniversalIdentifier: pair2FieldB, - relationTargetObjectMetadataUniversalIdentifier: 'object-4', - }), - ], - fieldIdByUniversalIdentifier: { [pair2FieldA]: 'id-2a' }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: pair2FieldA, + objectMetadataId: 'object-3-metadata-id', + objectMetadataUniversalIdentifier: 'object-3', + type: FieldMetadataType.RELATION, + name: 'pair2FieldA', + relationTargetFieldMetadataUniversalIdentifier: pair2FieldB, + relationTargetObjectMetadataUniversalIdentifier: 'object-4', + }), + id: 'id-2a', } satisfies UniversalCreateFieldAction, { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [ - getFlatFieldMetadataMock({ - universalIdentifier: pair2FieldB, - objectMetadataId: 'object-4-metadata-id', - objectMetadataUniversalIdentifier: 'object-4', - type: FieldMetadataType.RELATION, - name: 'pair2FieldB', - relationTargetFieldMetadataUniversalIdentifier: pair2FieldA, - relationTargetObjectMetadataUniversalIdentifier: 'object-3', - }), - ], - fieldIdByUniversalIdentifier: { [pair2FieldB]: 'id-2b' }, + flatEntity: getFlatFieldMetadataMock({ + universalIdentifier: pair2FieldB, + objectMetadataId: 'object-4-metadata-id', + objectMetadataUniversalIdentifier: 'object-4', + type: FieldMetadataType.RELATION, + name: 'pair2FieldB', + relationTargetFieldMetadataUniversalIdentifier: pair2FieldA, + relationTargetObjectMetadataUniversalIdentifier: 'object-3', + }), + id: 'id-2b', } satisfies UniversalCreateFieldAction, ], update: [], @@ -255,27 +234,23 @@ describe('aggregateRelationFieldPairs', () => { orchestratorActionsReport: input, }); - // Should result in 2 bundled actions (one per pair), each with merged fieldIdByUniversalIdentifier + // Should result in 2 bundled actions (one per pair) expect(result.fieldMetadata.create).toMatchObject([ { - universalFlatFieldMetadatas: [ - { universalIdentifier: pair1FieldA }, - { universalIdentifier: pair1FieldB }, - ], - fieldIdByUniversalIdentifier: { - [pair1FieldA]: 'id-1a', - [pair1FieldB]: 'id-1b', + flatEntity: { universalIdentifier: pair1FieldA }, + id: 'id-1a', + relatedUniversalFlatFieldMetadata: { + universalIdentifier: pair1FieldB, }, + relatedFieldId: 'id-1b', }, { - universalFlatFieldMetadatas: [ - { universalIdentifier: pair2FieldA }, - { universalIdentifier: pair2FieldB }, - ], - fieldIdByUniversalIdentifier: { - [pair2FieldA]: 'id-2a', - [pair2FieldB]: 'id-2b', + flatEntity: { universalIdentifier: pair2FieldA }, + id: 'id-2a', + relatedUniversalFlatFieldMetadata: { + universalIdentifier: pair2FieldB, }, + relatedFieldId: 'id-2b', }, ]); }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-non-relation-fields-into-object-actions.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-non-relation-fields-into-object-actions.util.ts index 1f28af8f427..0604c1a258f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-non-relation-fields-into-object-actions.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-non-relation-fields-into-object-actions.util.ts @@ -1,10 +1,8 @@ import { isDefined } from 'twenty-shared/utils'; -import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util'; +import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util'; import { type AggregateOrchestratorActionsReportArgs } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-aggregate-orchestrator-actions-report-args.type'; import { type OrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type'; -import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; -import { mergeFieldIdByUniversalIdentifier } from 'src/engine/workspace-manager/workspace-migration/utils/merge-field-id-by-universal-identifier.util'; import { type UniversalCreateFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action'; import { type UniversalCreateObjectAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action'; @@ -29,64 +27,29 @@ export const aggregateNonRelationFieldsIntoObjectActions = ({ const remainingCreateFieldActions: UniversalCreateFieldAction[] = []; for (const createFieldAction of createFieldActions) { - const nonRelationFields: UniversalFlatFieldMetadata[] = []; - const relationFields: UniversalFlatFieldMetadata[] = []; + const flatFieldMetadata = createFieldAction.flatEntity; - for (const field of createFieldAction.universalFlatFieldMetadatas) { - if (isMorphOrRelationUniversalFlatFieldMetadata(field)) { - relationFields.push(field); - } else { - nonRelationFields.push(field); - } + if (isMorphOrRelationFieldMetadataType(flatFieldMetadata.type)) { + remainingCreateFieldActions.push(createFieldAction); + continue; } - const nonRelationFieldsByObjectUniversalIdentifier = new Map< - string, - UniversalFlatFieldMetadata[] - >(); + const objectUniversalId = + flatFieldMetadata.objectMetadataUniversalIdentifier; + const matchingObjectAction = + createObjectActionByObjectUniversalIdentifier.get(objectUniversalId); - for (const field of nonRelationFields) { - const objectUniversalId = field.objectMetadataUniversalIdentifier; - const existing = - nonRelationFieldsByObjectUniversalIdentifier.get(objectUniversalId); + if (isDefined(matchingObjectAction)) { + matchingObjectAction.universalFlatFieldMetadatas.push(flatFieldMetadata); - if (isDefined(existing)) { - existing.push(field); - } else { - nonRelationFieldsByObjectUniversalIdentifier.set(objectUniversalId, [ - field, - ]); + if (isDefined(createFieldAction.id)) { + matchingObjectAction.fieldIdByUniversalIdentifier = { + ...matchingObjectAction.fieldIdByUniversalIdentifier, + [flatFieldMetadata.universalIdentifier]: createFieldAction.id, + }; } - } - - const unmergedNonRelationFields: UniversalFlatFieldMetadata[] = []; - - for (const [ - objectUniversalId, - fields, - ] of nonRelationFieldsByObjectUniversalIdentifier) { - const matchingObjectAction = - createObjectActionByObjectUniversalIdentifier.get(objectUniversalId); - - if (isDefined(matchingObjectAction)) { - matchingObjectAction.universalFlatFieldMetadatas.push(...fields); - matchingObjectAction.fieldIdByUniversalIdentifier = - mergeFieldIdByUniversalIdentifier( - matchingObjectAction.fieldIdByUniversalIdentifier, - createFieldAction.fieldIdByUniversalIdentifier, - ); - } else { - unmergedNonRelationFields.push(...fields); - } - } - - const remainingFields = [...unmergedNonRelationFields, ...relationFields]; - - if (remainingFields.length > 0) { - remainingCreateFieldActions.push({ - ...createFieldAction, - universalFlatFieldMetadatas: remainingFields, - }); + } else { + remainingCreateFieldActions.push(createFieldAction); } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-relation-field-pairs.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-relation-field-pairs.util.ts index 1829bcbfc03..f4f73e5b6db 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-relation-field-pairs.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/aggregate-relation-field-pairs.util.ts @@ -2,29 +2,23 @@ import { isDefined } from 'twenty-shared/utils'; import { type AggregateOrchestratorActionsReportArgs } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-aggregate-orchestrator-actions-report-args.type'; import { type OrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type'; -import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; -import { mergeFieldIdByUniversalIdentifier } from 'src/engine/workspace-manager/workspace-migration/utils/merge-field-id-by-universal-identifier.util'; import { type UniversalCreateFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action'; -type FieldWithActionContext = { - field: UniversalFlatFieldMetadata; - fieldIdByUniversalIdentifier: Record | undefined; -}; - export const aggregateRelationFieldPairs = ({ orchestratorActionsReport, }: AggregateOrchestratorActionsReportArgs): OrchestratorActionsReport => { const createFieldActions = orchestratorActionsReport.fieldMetadata.create; - const fieldByUniversalIdentifier = new Map(); + const actionByFieldUniversalIdentifier = new Map< + string, + UniversalCreateFieldAction + >(); for (const action of createFieldActions) { - for (const field of action.universalFlatFieldMetadatas) { - fieldByUniversalIdentifier.set(field.universalIdentifier, { - field, - fieldIdByUniversalIdentifier: action.fieldIdByUniversalIdentifier, - }); - } + actionByFieldUniversalIdentifier.set( + action.flatEntity.universalIdentifier, + action, + ); } const processedFieldUniversalIdentifiers = new Set(); @@ -32,45 +26,43 @@ export const aggregateRelationFieldPairs = ({ for (const [ universalIdentifier, - fieldContext, - ] of fieldByUniversalIdentifier) { + action, + ] of actionByFieldUniversalIdentifier) { if (processedFieldUniversalIdentifiers.has(universalIdentifier)) { continue; } - const { field, fieldIdByUniversalIdentifier } = fieldContext; - processedFieldUniversalIdentifiers.add(universalIdentifier); - const fieldsToBundle: UniversalFlatFieldMetadata[] = [field]; - let mergedFieldIdMap = fieldIdByUniversalIdentifier; - const targetUniversalIdentifier = - field.relationTargetFieldMetadataUniversalIdentifier; + action.flatEntity.relationTargetFieldMetadataUniversalIdentifier; - if (isDefined(targetUniversalIdentifier)) { - const targetFieldContext = fieldByUniversalIdentifier.get( - targetUniversalIdentifier, - ); - - if ( - isDefined(targetFieldContext) && - !processedFieldUniversalIdentifiers.has(targetUniversalIdentifier) - ) { - fieldsToBundle.push(targetFieldContext.field); - processedFieldUniversalIdentifiers.add(targetUniversalIdentifier); - mergedFieldIdMap = mergeFieldIdByUniversalIdentifier( - mergedFieldIdMap, - targetFieldContext.fieldIdByUniversalIdentifier, - ); - } + if (!isDefined(targetUniversalIdentifier)) { + aggregatedCreateFieldActions.push(action); + continue; } + const targetAction = actionByFieldUniversalIdentifier.get( + targetUniversalIdentifier, + ); + + if ( + !isDefined(targetAction) || + processedFieldUniversalIdentifiers.has(targetUniversalIdentifier) + ) { + aggregatedCreateFieldActions.push(action); + continue; + } + + processedFieldUniversalIdentifiers.add(targetUniversalIdentifier); + aggregatedCreateFieldActions.push({ type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: fieldsToBundle, - fieldIdByUniversalIdentifier: mergedFieldIdMap, + flatEntity: action.flatEntity, + id: action.id, + relatedUniversalFlatFieldMetadata: targetAction.flatEntity, + relatedFieldId: targetAction.id, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/merge-field-id-by-universal-identifier.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/merge-field-id-by-universal-identifier.util.ts deleted file mode 100644 index 68fc37f16de..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/merge-field-id-by-universal-identifier.util.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { isDefined } from 'twenty-shared/utils'; - -export const mergeFieldIdByUniversalIdentifier = ( - existing: Record | undefined, - incoming: Record | undefined, -): Record | undefined => { - if (!isDefined(existing) && !isDefined(incoming)) { - return undefined; - } - - return { - ...existing, - ...incoming, - }; -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action.ts index 4d6710092c2..d42dcae8a46 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action.ts @@ -1,4 +1,5 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type ExtractUniversalForeignKeyAggregatorForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type'; import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type'; @@ -8,14 +9,16 @@ import { type BaseUniversalDeleteWorkspaceMigrationAction } from 'src/engine/wor import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-update-workspace-migration-action.type'; // Universal action types (always use universal identifiers) -export type UniversalCreateFieldAction = Omit< - BaseUniversalCreateWorkspaceMigrationAction<'fieldMetadata'>, - 'flatEntity' -> & { - universalFlatFieldMetadatas: UniversalFlatFieldMetadata[]; - // Optional map to provide specific IDs for field creation (for API metadata). - fieldIdByUniversalIdentifier?: Record; -}; +export type UniversalCreateFieldAction = + BaseUniversalCreateWorkspaceMigrationAction<'fieldMetadata'> & { + // For relation fields, the related field on the other side of the relation. + relatedUniversalFlatFieldMetadata?: Omit< + UniversalFlatFieldMetadata, + ExtractUniversalForeignKeyAggregatorForMetadataName<'fieldMetadata'> + >; + // Optional ID for the related field (for API metadata). + relatedFieldId?: string; + }; export type UniversalUpdateFieldAction = BaseUniversalUpdateWorkspaceMigrationAction<'fieldMetadata'>; @@ -24,12 +27,10 @@ export type UniversalDeleteFieldAction = BaseUniversalDeleteWorkspaceMigrationAction<'fieldMetadata'>; // Flat action types (always use entity IDs) -export type FlatCreateFieldAction = Omit< - BaseFlatCreateWorkspaceMigrationAction<'fieldMetadata'>, - 'flatEntity' -> & { - flatFieldMetadatas: FlatFieldMetadata[]; -}; +export type FlatCreateFieldAction = + BaseFlatCreateWorkspaceMigrationAction<'fieldMetadata'> & { + relatedFlatFieldMetadata?: FlatFieldMetadata; + }; export type FlatUpdateFieldAction = BaseFlatUpdateWorkspaceMigrationAction<'fieldMetadata'>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service.ts index f3a5b5a2af2..c946637541f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service.ts @@ -44,7 +44,7 @@ export class WorkspaceMigrationFieldActionsBuilderService extends WorkspaceEntit action: { type: 'create', metadataName: 'fieldMetadata', - universalFlatFieldMetadatas: [flatFieldMetadataToValidate], + flatEntity: flatFieldMetadataToValidate, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action.ts index e1014c375da..01e0f28cbc2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/object/types/workspace-migration-object-action.ts @@ -1,4 +1,5 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type ExtractUniversalForeignKeyAggregatorForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type'; import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type'; @@ -10,7 +11,10 @@ import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/wor // Universal action types (always use universal identifiers) export type UniversalCreateObjectAction = BaseUniversalCreateWorkspaceMigrationAction<'objectMetadata'> & { - universalFlatFieldMetadatas: UniversalFlatFieldMetadata[]; + universalFlatFieldMetadatas: Omit< + UniversalFlatFieldMetadata, + ExtractUniversalForeignKeyAggregatorForMetadataName<'fieldMetadata'> + >[]; // Optional map to provide specific IDs for field creation (for API metadata). fieldIdByUniversalIdentifier?: Record; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts index 7248f35c3bb..ec611b94543 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service.ts @@ -10,6 +10,7 @@ import { FlatEntityMapsExceptionCode, } from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; import { MetadataUniversalFlatEntityAndRelatedFlatEntityMapsForValidation } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-and-related-flat-entity-maps-for-validation.type'; +import { MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type'; import { MetadataValidationRelatedUniversalFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type'; import { MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; @@ -18,9 +19,11 @@ import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-e import { WorkspaceMigrationBuilderAdditionalCacheDataMaps } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-builder-additional-cache-data-maps.type'; import { MetadataUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-maps.type'; import { addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util'; +import { deleteUniversalFlatEntityForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-foreign-key-aggregators.util'; import { deleteUniversalFlatEntityFromUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-from-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util'; import { deleteUniversalFlatEntityFromUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-from-universal-flat-entity-maps-through-mutation-or-throw.util'; import { replaceUniversalFlatEntityInUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/replace-universal-flat-entity-in-universal-flat-entity-maps-through-mutation-or-throw.util'; +import { resetUniversalFlatEntityForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { flatEntityDeletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/universal-flat-entity-deleted-created-updated-matrix-dispatcher.util'; import { getMetadataEmptyWorkspaceMigrationActionRecord } from 'src/engine/workspace-manager/workspace-migration/utils/get-metadata-empty-workspace-migration-action-record.util'; import { shouldInferDeletionFromMissingEntities } from 'src/engine/workspace-manager/workspace-migration/utils/should-infer-deletion-from-missing-entities.util'; @@ -29,6 +32,7 @@ import { SuccessfulFlatEntityValidateAndBuild } from 'src/engine/workspace-manag import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type'; +import { AllUniversalWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common'; import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-builder-options.type'; export type ValidateAndBuildArgs = { @@ -111,23 +115,31 @@ export abstract class WorkspaceEntityMigrationBuilderService< 'creation validation', ); for (const flatEntityToCreateUniversalIdentifier in createdFlatEntityMaps.byUniversalIdentifier) { - const flatEntityToCreate = findFlatEntityByUniversalIdentifierOrThrow({ - universalIdentifier: flatEntityToCreateUniversalIdentifier, - flatEntityMaps: createdFlatEntityMaps, - }); + const rawUniversalflatEntityToCreate = + findFlatEntityByUniversalIdentifierOrThrow({ + universalIdentifier: flatEntityToCreateUniversalIdentifier, + flatEntityMaps: createdFlatEntityMaps, + }); - const flatEntityToCreateId = flatEntityToCreate.universalIdentifier; + const universalFlatEntityToCreate = + resetUniversalFlatEntityForeignKeyAggregators({ + metadataName: this.metadataName, + universalFlatEntity: rawUniversalflatEntityToCreate, + }); + + const universalIdentifierToDelete = + universalFlatEntityToCreate.universalIdentifier; deleteUniversalFlatEntityFromUniversalFlatEntityMapsThroughMutationOrThrow( { - universalIdentifierToDelete: flatEntityToCreateId, + universalIdentifierToDelete, universalFlatEntityMapsToMutate: remainingFlatEntityMapsToCreate, }, ); const validationResult = await this.validateFlatEntityCreation({ additionalCacheDataMaps, - flatEntityToValidate: flatEntityToCreate, + flatEntityToValidate: universalFlatEntityToCreate, workspaceId, optimisticFlatEntityMapsAndRelatedFlatEntityMaps, remainingFlatEntityMapsToValidate: remainingFlatEntityMapsToCreate, @@ -141,18 +153,26 @@ export abstract class WorkspaceEntityMigrationBuilderService< addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow( { - universalFlatEntity: flatEntityToCreate, + universalFlatEntity: universalFlatEntityToCreate, universalFlatEntityAndRelatedMapsToMutate: optimisticFlatEntityMapsAndRelatedFlatEntityMaps, metadataName: this.metadataName, }, ); - actionsResult.create.push( - ...(Array.isArray(validationResult.action) - ? validationResult.action - : [validationResult.action]), - ); + const formattedNewCreateAction: AllUniversalWorkspaceMigrationAction< + 'create', + typeof this.metadataName + > = { + ...validationResult.action, + flatEntity: deleteUniversalFlatEntityForeignKeyAggregators({ + metadataName: this.metadataName, + universalFlatEntity: validationResult.action + .flatEntity as MetadataFlatEntity, + }), + }; + + actionsResult.create.push(formattedNewCreateAction); } this.logger.timeEnd( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type.ts index b093d6d0195..05f0f3a5fb0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type.ts @@ -1,12 +1,16 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; +import { type ExtractUniversalForeignKeyAggregatorForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; import { type WORKSPACE_MIGRATION_ACTION_TYPE } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/constants/workspace-migration-action-type.constant'; export type BaseUniversalCreateWorkspaceMigrationAction< T extends AllMetadataName, > = { - flatEntity: MetadataUniversalFlatEntity; + flatEntity: Omit< + MetadataUniversalFlatEntity, + ExtractUniversalForeignKeyAggregatorForMetadataName + >; type: typeof WORKSPACE_MIGRATION_ACTION_TYPE.create; metadataName: T; // Optional ID to use when creating the entity (for API metadata). diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type.ts similarity index 94% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type.ts index 3231fc9b5ff..4286309c95c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type.ts @@ -5,6 +5,5 @@ export type WorkspaceMigration< AllUniversalWorkspaceMigrationAction = AllUniversalWorkspaceMigrationAction, > = { actions: TActions[]; - workspaceId: string; applicationUniversalIdentifier: string; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service.ts index 0bceff79131..eab4059b427 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { FlatCreateAgentAction, UniversalCreateAgentAction, @@ -27,6 +28,11 @@ export class CreateAgentActionHandlerService extends WorkspaceMigrationRunnerAct flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'agent', + }); + return { ...action, flatEntity: { @@ -34,6 +40,7 @@ export class CreateAgentActionHandlerService extends WorkspaceMigrationRunnerAct applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/create-command-menu-item-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/create-command-menu-item-action-handler.service.ts index 04ce06508d9..b4a1d0f9a22 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/create-command-menu-item-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/create-command-menu-item-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateCommandMenuItemAction, @@ -36,6 +37,11 @@ export class CreateCommandMenuItemActionHandlerService extends WorkspaceMigratio universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'commandMenuItem', + }); + return { ...action, flatEntity: { @@ -45,6 +51,7 @@ export class CreateCommandMenuItemActionHandlerService extends WorkspaceMigratio applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts index bf0a4c992c1..31b84551852 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts @@ -47,40 +47,44 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct context: WorkspaceMigrationActionRunnerArgs, ): Promise { const { action, allFlatEntityMaps } = context; - const { universalFlatFieldMetadatas, fieldIdByUniversalIdentifier } = - action; const allFieldIdToBeCreatedInActionByUniversalIdentifierMap = new Map< string, string >(); - for (const universalFlatFieldMetadata of universalFlatFieldMetadatas) { - const providedId = - fieldIdByUniversalIdentifier?.[ - universalFlatFieldMetadata.universalIdentifier - ]; + allFieldIdToBeCreatedInActionByUniversalIdentifierMap.set( + action.flatEntity.universalIdentifier, + action.id ?? v4(), + ); + if (isDefined(action.relatedUniversalFlatFieldMetadata)) { allFieldIdToBeCreatedInActionByUniversalIdentifierMap.set( - universalFlatFieldMetadata.universalIdentifier, - providedId ?? v4(), + action.relatedUniversalFlatFieldMetadata.universalIdentifier, + action.relatedFieldId ?? v4(), ); } + const universalFlatFieldMetadatas = isDefined( + action.relatedUniversalFlatFieldMetadata, + ) + ? [action.flatEntity, action.relatedUniversalFlatFieldMetadata] + : [action.flatEntity]; - const flatFieldMetadatas = universalFlatFieldMetadatas.map( - (universalFlatFieldMetadata) => + const [flatFieldMetadata, relatedFlatFieldMetadata] = + universalFlatFieldMetadatas.map((universalFlatFieldMetadata) => fromUniversalFlatFieldMetadataToFlatFieldMetadata({ universalFlatFieldMetadata, allFieldIdToBeCreatedInActionByUniversalIdentifierMap, allFlatEntityMaps, context, }), - ); + ); return { type: action.type, metadataName: action.metadataName, - flatFieldMetadatas, + flatEntity: flatFieldMetadata, + relatedFlatFieldMetadata, }; } @@ -88,11 +92,11 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct context: WorkspaceMigrationActionRunnerContext, ): Promise { const { queryRunner, flatAction } = context; - const { flatFieldMetadatas } = flatAction; + const { flatEntity, relatedFlatFieldMetadata } = flatAction; await this.insertFlatEntitiesInRepository({ queryRunner, - flatEntities: flatFieldMetadatas, + flatEntities: [flatEntity, relatedFlatFieldMetadata].filter(isDefined), }); } @@ -105,14 +109,14 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct allFlatEntityMaps: { flatObjectMetadataMaps }, workspaceId, } = context; - const { flatFieldMetadatas } = flatAction; + const { flatEntity, relatedFlatFieldMetadata } = flatAction; - const fieldsByObjectMetadataId = new Map< - string, - typeof flatFieldMetadatas - >(); + const fieldsByObjectMetadataId = new Map(); - for (const flatFieldMetadata of flatFieldMetadatas) { + for (const flatFieldMetadata of [ + flatEntity, + relatedFlatFieldMetadata, + ].filter(isDefined)) { const existingFields = fieldsByObjectMetadataId.get( flatFieldMetadata.objectMetadataId, ); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/utils/from-universal-flat-field-metadata-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/utils/from-universal-flat-field-metadata-to-flat-field-metadata.util.ts index 3dd8e55278e..7be53bb9b04 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/utils/from-universal-flat-field-metadata-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/utils/from-universal-flat-field-metadata-to-flat-field-metadata.util.ts @@ -2,7 +2,9 @@ import { isDefined } from 'twenty-shared/utils'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type ExtractUniversalForeignKeyAggregatorForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { type AllUniversalWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common'; import { findFieldMetadataIdInCreateFieldContext } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/utils/find-field-metadata-id-in-create-field-context.util'; import { fromUniversalSettingsToFlatFieldMetadataSettings } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/utils/from-universal-settings-to-flat-field-metadata-settings.util'; @@ -10,7 +12,10 @@ import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-ma export type FromUniversalFlatFieldMetadataToFlatFieldMetadataArgs = { allFieldIdToBeCreatedInActionByUniversalIdentifierMap: Map; - universalFlatFieldMetadata: UniversalFlatFieldMetadata; + universalFlatFieldMetadata: Omit< + UniversalFlatFieldMetadata, + ExtractUniversalForeignKeyAggregatorForMetadataName<'fieldMetadata'> + >; allFlatEntityMaps: AllFlatEntityMaps; context: Pick< WorkspaceMigrationActionRunnerArgs, @@ -35,13 +40,6 @@ export const fromUniversalFlatFieldMetadataToFlatFieldMetadata = ({ objectMetadataUniversalIdentifier, relationTargetFieldMetadataUniversalIdentifier, relationTargetObjectMetadataUniversalIdentifier, - viewFilterUniversalIdentifiers: _viewFilterUniversalIdentifiers, - viewFieldUniversalIdentifiers: _viewFieldUniversalIdentifiers, - kanbanAggregateOperationViewUniversalIdentifiers: - _kanbanAggregateOperationViewUniversalIdentifiers, - calendarViewUniversalIdentifiers: _calendarViewUniversalIdentifiers, - mainGroupByFieldMetadataViewUniversalIdentifiers: - _mainGroupByFieldMetadataViewUniversalIdentifiers, universalSettings, ...restProperties } = universalFlatFieldMetadata; @@ -107,6 +105,11 @@ export const fromUniversalFlatFieldMetadataToFlatFieldMetadata = ({ flatFieldMetadataMaps: allFlatEntityMaps.flatFieldMetadataMaps, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'fieldMetadata', + }); + return { ...restProperties, settings, @@ -130,10 +133,6 @@ export const fromUniversalFlatFieldMetadataToFlatFieldMetadata = ({ calendarViewIds: [], mainGroupByFieldMetadataViewIds: [], kanbanAggregateOperationViewIds: [], - viewFieldUniversalIdentifiers: [], - viewFilterUniversalIdentifiers: [], - calendarViewUniversalIdentifiers: [], - mainGroupByFieldMetadataViewUniversalIdentifiers: [], - kanbanAggregateOperationViewUniversalIdentifiers: [], + ...emptyUniversalForeignKeyAggregators, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/front-component/services/create-front-component-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/front-component/services/create-front-component-action-handler.service.ts index 1edf083b823..00d4a649ca1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/front-component/services/create-front-component-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/front-component/services/create-front-component-action-handler.service.ts @@ -11,6 +11,7 @@ import { FrontComponentException, FrontComponentExceptionCode, } from 'src/engine/metadata-modules/front-component/front-component.exception'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { FlatCreateFrontComponentAction, UniversalCreateFrontComponentAction, @@ -34,6 +35,11 @@ export class CreateFrontComponentActionHandlerService extends WorkspaceMigration flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'frontComponent', + }); + return { ...action, flatEntity: { @@ -41,6 +47,7 @@ export class CreateFrontComponentActionHandlerService extends WorkspaceMigration applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts index e4899fddfbe..d41a28ecd42 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { FlatCreateLogicFunctionAction, UniversalCreateLogicFunctionAction, @@ -23,6 +24,11 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'logicFunction', + }); + return { ...action, flatEntity: { @@ -30,6 +36,7 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts index fc62659f12f..cc2563c7e79 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/navigation-menu-item/services/create-navigation-menu-item-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateNavigationMenuItemAction, @@ -36,6 +37,11 @@ export class CreateNavigationMenuItemActionHandlerService extends WorkspaceMigra universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'navigationMenuItem', + }); + return { ...action, flatEntity: { @@ -46,6 +52,7 @@ export class CreateNavigationMenuItemActionHandlerService extends WorkspaceMigra applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/utils/from-universal-flat-object-metadata-to-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/utils/from-universal-flat-object-metadata-to-flat-object-metadata.util.ts index 8b3145ef900..3222f9fc593 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/utils/from-universal-flat-object-metadata-to-flat-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/utils/from-universal-flat-object-metadata-to-flat-object-metadata.util.ts @@ -2,12 +2,17 @@ import { isDefined } from 'twenty-shared/utils'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { type ExtractUniversalForeignKeyAggregatorForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-flat-entity-foreign-key-aggregator-properties.constant'; import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { type AllUniversalWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; export type FromUniversalFlatObjectMetadataToFlatObjectMetadataArgs = { - universalFlatObjectMetadata: UniversalFlatObjectMetadata; + universalFlatObjectMetadata: Omit< + UniversalFlatObjectMetadata, + ExtractUniversalForeignKeyAggregatorForMetadataName<'objectMetadata'> + >; generatedId: string; allFlatEntityMaps: AllFlatEntityMaps; allFieldIdToBeCreatedInActionByUniversalIdentifierMap: Map; @@ -60,9 +65,6 @@ export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({ applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier, imageIdentifierFieldMetadataUniversalIdentifier, - viewUniversalIdentifiers: _viewUniversalIdentifiers, - indexMetadataUniversalIdentifiers: _indexMetadataUniversalIdentifiers, - fieldUniversalIdentifiers: _fieldUniversalIdentifiers, ...restProperties } = universalFlatObjectMetadata; @@ -98,6 +100,11 @@ export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({ } } + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'objectMetadata', + }); + return { ...restProperties, dataSourceId, @@ -115,8 +122,6 @@ export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({ fieldIds: [], viewIds: [], indexMetadataIds: [], - fieldUniversalIdentifiers: [], - viewUniversalIdentifiers: [], - indexMetadataUniversalIdentifiers: [], + ...emptyUniversalForeignKeyAggregators, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-tab/services/create-page-layout-tab-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-tab/services/create-page-layout-tab-action-handler.service.ts index eef30805aec..091548dc788 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-tab/services/create-page-layout-tab-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-tab/services/create-page-layout-tab-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreatePageLayoutTabAction, @@ -35,6 +36,11 @@ export class CreatePageLayoutTabActionHandlerService extends WorkspaceMigrationR universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'pageLayoutTab', + }); + return { ...action, flatEntity: { @@ -44,6 +50,7 @@ export class CreatePageLayoutTabActionHandlerService extends WorkspaceMigrationR id: action.id ?? v4(), workspaceId, widgetIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-widget/services/create-page-layout-widget-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-widget/services/create-page-layout-widget-action-handler.service.ts index f5ccde47d8c..8f992eb2d14 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-widget/services/create-page-layout-widget-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-widget/services/create-page-layout-widget-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreatePageLayoutWidgetAction, @@ -43,6 +44,11 @@ export class CreatePageLayoutWidgetActionHandlerService extends WorkspaceMigrati flatFieldMetadataMaps: allFlatEntityMaps.flatFieldMetadataMaps, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'pageLayoutWidget', + }); + return { ...action, flatEntity: { @@ -53,6 +59,7 @@ export class CreatePageLayoutWidgetActionHandlerService extends WorkspaceMigrati applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts index c000d61b5b4..5554ae35d29 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout/services/create-page-layout-action-handler.service.ts @@ -9,6 +9,7 @@ import { FlatEntityMapsException, FlatEntityMapsExceptionCode, } from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreatePageLayoutAction, @@ -70,6 +71,11 @@ export class CreatePageLayoutActionHandlerService extends WorkspaceMigrationRunn } } + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'pageLayout', + }); + return { ...action, flatEntity: { @@ -80,6 +86,7 @@ export class CreatePageLayoutActionHandlerService extends WorkspaceMigrationRunn id: action.id ?? v4(), workspaceId, tabIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/create-role-target-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/create-role-target-action-handler.service.ts index 0d876da3c7a..a33a81d099f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/create-role-target-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/create-role-target-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateRoleTargetAction, @@ -31,6 +32,11 @@ export class CreateRoleTargetActionHandlerService extends WorkspaceMigrationRunn universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'roleTarget', + }); + return { ...action, flatEntity: { @@ -39,6 +45,7 @@ export class CreateRoleTargetActionHandlerService extends WorkspaceMigrationRunn applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts index 78fbe8ac9c7..963780d797b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role/services/create-role-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { FlatCreateRoleAction, UniversalCreateRoleAction, @@ -27,6 +28,11 @@ export class CreateRoleActionHandlerService extends WorkspaceMigrationRunnerActi flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'role', + }); + return { ...action, flatEntity: { @@ -40,6 +46,7 @@ export class CreateRoleActionHandlerService extends WorkspaceMigrationRunnerActi objectPermissionIds: [], permissionFlagIds: [], fieldPermissionIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate-group/services/create-row-level-permission-predicate-group-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate-group/services/create-row-level-permission-predicate-group-action-handler.service.ts index cff6391fc84..397be233cee 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate-group/services/create-row-level-permission-predicate-group-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate-group/services/create-row-level-permission-predicate-group-action-handler.service.ts @@ -6,6 +6,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateRowLevelPermissionPredicateGroupAction, @@ -37,6 +38,11 @@ export class CreateRowLevelPermissionPredicateGroupActionHandlerService extends universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'rowLevelPermissionPredicateGroup', + }); + return { ...action, flatEntity: { @@ -49,6 +55,7 @@ export class CreateRowLevelPermissionPredicateGroupActionHandlerService extends workspaceId, rowLevelPermissionPredicateIds: [], childRowLevelPermissionPredicateGroupIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate/services/create-row-level-permission-predicate-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate/services/create-row-level-permission-predicate-action-handler.service.ts index a6e09a94a5c..daf76a568b4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate/services/create-row-level-permission-predicate-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/row-level-permission-predicate/services/create-row-level-permission-predicate-action-handler.service.ts @@ -6,6 +6,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateRowLevelPermissionPredicateAction, @@ -39,6 +40,11 @@ export class CreateRowLevelPermissionPredicateActionHandlerService extends Works universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'rowLevelPermissionPredicate', + }); + return { ...action, flatEntity: { @@ -51,6 +57,7 @@ export class CreateRowLevelPermissionPredicateActionHandlerService extends Works applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/skill/services/create-skill-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/skill/services/create-skill-action-handler.service.ts index 656d530ecca..8a5b5632041 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/skill/services/create-skill-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/skill/services/create-skill-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { FlatCreateSkillAction, UniversalCreateSkillAction, @@ -27,6 +28,11 @@ export class CreateSkillActionHandlerService extends WorkspaceMigrationRunnerAct flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'skill', + }); + return { ...action, flatEntity: { @@ -34,6 +40,7 @@ export class CreateSkillActionHandlerService extends WorkspaceMigrationRunnerAct applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field-group/services/create-view-field-group-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field-group/services/create-view-field-group-action-handler.service.ts index f5997416bcc..26b51083589 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field-group/services/create-view-field-group-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field-group/services/create-view-field-group-action-handler.service.ts @@ -5,6 +5,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateViewFieldGroupAction, @@ -32,16 +33,21 @@ export class CreateViewFieldGroupActionHandlerService extends WorkspaceMigration universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'viewFieldGroup', + }); + return { ...action, flatEntity: { ...action.flatEntity, viewId, - viewFieldIds: [], - viewFieldUniversalIdentifiers: [], id: action.id ?? v4(), applicationId: flatApplication.id, workspaceId, + viewFieldIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/create-view-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/create-view-field-action-handler.service.ts index 8e9f13c6b30..f077f6dd4a8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/create-view-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/create-view-field-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateViewFieldAction, @@ -36,6 +37,11 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'viewField', + }); + return { ...action, flatEntity: { @@ -46,6 +52,7 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne id: action.id ?? v4(), applicationId: flatApplication.id, workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter-group/services/create-view-filter-group-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter-group/services/create-view-filter-group-action-handler.service.ts index 9ea3d9be31a..785537d8f26 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter-group/services/create-view-filter-group-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter-group/services/create-view-filter-group-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { type FlatCreateViewFilterGroupAction, @@ -32,6 +33,11 @@ export class CreateViewFilterGroupActionHandlerService extends WorkspaceMigratio universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'viewFilterGroup', + }); + return { ...action, flatEntity: { @@ -43,6 +49,7 @@ export class CreateViewFilterGroupActionHandlerService extends WorkspaceMigratio workspaceId, childViewFilterGroupIds: [], viewFilterIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter/services/create-view-filter-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter/services/create-view-filter-action-handler.service.ts index 4fd5f2ded07..bcd5739e47a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter/services/create-view-filter-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-filter/services/create-view-filter-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateViewFilterAction, @@ -32,6 +33,11 @@ export class CreateViewFilterActionHandlerService extends WorkspaceMigrationRunn universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'viewFilter', + }); + return { ...action, flatEntity: { @@ -42,6 +48,7 @@ export class CreateViewFilterActionHandlerService extends WorkspaceMigrationRunn id: action.id ?? v4(), applicationId: flatApplication.id, workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-group/services/create-view-group-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-group/services/create-view-group-action-handler.service.ts index f48518e713e..dcdc1406cd2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-group/services/create-view-group-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-group/services/create-view-group-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateViewGroupAction, @@ -31,6 +32,11 @@ export class CreateViewGroupActionHandlerService extends WorkspaceMigrationRunne universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'viewGroup', + }); + return { ...action, flatEntity: { @@ -39,6 +45,7 @@ export class CreateViewGroupActionHandlerService extends WorkspaceMigrationRunne id: action.id ?? v4(), applicationId: flatApplication.id, workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/create-view-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/create-view-action-handler.service.ts index 048b2572e62..c1ec8dd36a5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/create-view-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/create-view-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util'; import { FlatCreateViewAction, @@ -40,6 +41,11 @@ export class CreateViewActionHandlerService extends WorkspaceMigrationRunnerActi universalForeignKeyValues: action.flatEntity, }); + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'view', + }); + return { ...action, flatEntity: { @@ -56,6 +62,7 @@ export class CreateViewActionHandlerService extends WorkspaceMigrationRunnerActi viewGroupIds: [], viewFilterIds: [], viewFilterGroupIds: [], + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/create-webhook-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/create-webhook-action-handler.service.ts index 28003716cc8..1ab12fd520f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/create-webhook-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/create-webhook-action-handler.service.ts @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util'; import { FlatCreateWebhookAction, UniversalCreateWebhookAction, @@ -27,6 +28,11 @@ export class CreateWebhookActionHandlerService extends WorkspaceMigrationRunnerA flatApplication, workspaceId, }: WorkspaceMigrationActionRunnerArgs): Promise { + const emptyUniversalForeignKeyAggregators = + getUniversalFlatEntityEmptyForeignKeyAggregators({ + metadataName: 'webhook', + }); + return { ...action, flatEntity: { @@ -34,6 +40,7 @@ export class CreateWebhookActionHandlerService extends WorkspaceMigrationRunnerA applicationId: flatApplication.id, id: action.id ?? v4(), workspaceId, + ...emptyUniversalForeignKeyAggregators, }, }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts index 73471e0298a..775af783b9a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts @@ -15,7 +15,7 @@ import { FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION } from 'src/engine/metadata-modul import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; -import { WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration'; +import { WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type'; import { WorkspaceMigrationRunnerException, WorkspaceMigrationRunnerExceptionCode, @@ -150,10 +150,12 @@ export class WorkspaceMigrationRunnerService { } run = async ({ - actions, - applicationUniversalIdentifier, + workspaceMigration: { actions, applicationUniversalIdentifier }, workspaceId, - }: WorkspaceMigration): Promise<{ + }: { + workspaceMigration: WorkspaceMigration; + workspaceId: string; + }): Promise<{ allFlatEntityMaps: AllFlatEntityMaps; metadataEvents: MetadataEvent[]; }> => { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts index 02f7e6e2ead..708ea02d181 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/derive-metadata-events-from-create-action.util.ts @@ -1,4 +1,4 @@ -import { assertUnreachable } from 'twenty-shared/utils'; +import { assertUnreachable, isDefined } from 'twenty-shared/utils'; import { type AllFlatWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common'; import { @@ -12,7 +12,12 @@ export const deriveMetadataEventsFromCreateAction = ( ): MetadataEvent[] => { switch (flatAction.metadataName) { case 'fieldMetadata': { - return flatAction.flatFieldMetadatas.map( + const flatFieldMetadatas = [ + flatAction.flatEntity, + flatAction.relatedFlatFieldMetadata, + ].filter(isDefined); + + return flatFieldMetadatas.map( (flatFieldMetadata): CreateMetadataEvent<'fieldMetadata'> => ({ type: 'created', recordId: flatFieldMetadata.id, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts index ebcb4ac0211..69d3fbedc22 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/optimistically-apply-create-action-on-all-flat-entity-maps.util.ts @@ -1,4 +1,4 @@ -import { assertUnreachable } from 'twenty-shared/utils'; +import { assertUnreachable, isDefined } from 'twenty-shared/utils'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-and-related-entity-maps-through-mutation-or-throw.util'; @@ -15,7 +15,12 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = ({ }: OptimisticallyApplyCreateActionOnAllFlatEntityMapsArgs): AllFlatEntityMaps => { switch (flatAction.metadataName) { case 'fieldMetadata': { - flatAction.flatFieldMetadatas.forEach((flatEntity) => + const flatFieldMetadatas = [ + flatAction.flatEntity, + flatAction.relatedFlatFieldMetadata, + ].filter(isDefined); + + flatFieldMetadatas.forEach((flatEntity) => addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow({ flatEntity, flatEntityAndRelatedMapsToMutate: allFlatEntityMaps, diff --git a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/successful-sync-application-workspace-migration.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/successful-sync-application-workspace-migration.integration-spec.ts.snap new file mode 100644 index 00000000000..7a88499af87 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/successful-sync-application-workspace-migration.integration-spec.ts.snap @@ -0,0 +1,138 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`syncApplication should return workspace migration actions on initial sync then on second sync with field rename and new role 1`] = ` +{ + "syncApplication": { + "actions": [ + { + "flatEntity": { + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "description": "A support ticket", + "duplicateCriteria": null, + "icon": "IconTicket", + "imageIdentifierFieldMetadataUniversalIdentifier": null, + "isActive": true, + "isAuditLogged": true, + "isCustom": true, + "isLabelSyncedWithName": true, + "isRemote": false, + "isSearchable": false, + "isSystem": false, + "isUIReadOnly": false, + "labelIdentifierFieldMetadataUniversalIdentifier": Any, + "labelPlural": "Tickets", + "labelSingular": "Ticket", + "namePlural": "tickets", + "nameSingular": "ticket", + "shortcut": null, + "standardOverrides": null, + "targetTableName": "DEPRECATED", + "universalIdentifier": Any, + "updatedAt": Any, + }, + "metadataName": "objectMetadata", + "type": "create", + "universalFlatFieldMetadatas": [ + { + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "defaultValue": null, + "description": "Ticket description", + "icon": "IconFileDescription", + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUIReadOnly": false, + "isUnique": false, + "label": "Description", + "morphId": null, + "name": "description", + "objectMetadataUniversalIdentifier": Any, + "options": null, + "relationTargetFieldMetadataUniversalIdentifier": null, + "relationTargetObjectMetadataUniversalIdentifier": null, + "standardOverrides": null, + "type": "TEXT", + "universalIdentifier": Any, + "universalSettings": null, + "updatedAt": Any, + }, + ], + }, + { + "flatEntity": { + "applicationUniversalIdentifier": Any, + "canAccessAllTools": false, + "canBeAssignedToAgents": true, + "canBeAssignedToApiKeys": true, + "canBeAssignedToUsers": true, + "canDestroyAllObjectRecords": false, + "canReadAllObjectRecords": false, + "canSoftDeleteAllObjectRecords": false, + "canUpdateAllObjectRecords": false, + "canUpdateAllSettings": false, + "createdAt": Any, + "description": "A test role", + "icon": null, + "isEditable": true, + "label": "Test Role", + "rowLevelPermissionPredicateGroupUniversalIdentifiers": [], + "rowLevelPermissionPredicateUniversalIdentifiers": [], + "universalIdentifier": Any, + "updatedAt": Any, + }, + "metadataName": "role", + "type": "create", + }, + ], + "applicationUniversalIdentifier": Any, + }, +} +`; + +exports[`syncApplication should return workspace migration actions on initial sync then on second sync with field rename and new role 2`] = ` +{ + "syncApplication": { + "actions": [ + { + "metadataName": "fieldMetadata", + "type": "update", + "universalIdentifier": Any, + "update": { + "label": "Body", + "name": "body", + }, + }, + { + "flatEntity": { + "applicationUniversalIdentifier": Any, + "canAccessAllTools": false, + "canBeAssignedToAgents": true, + "canBeAssignedToApiKeys": true, + "canBeAssignedToUsers": true, + "canDestroyAllObjectRecords": false, + "canReadAllObjectRecords": false, + "canSoftDeleteAllObjectRecords": false, + "canUpdateAllObjectRecords": false, + "canUpdateAllSettings": false, + "createdAt": Any, + "description": "A read-only role", + "icon": null, + "isEditable": true, + "label": "Viewer Role", + "rowLevelPermissionPredicateGroupUniversalIdentifiers": [], + "rowLevelPermissionPredicateUniversalIdentifiers": [], + "universalIdentifier": Any, + "updatedAt": Any, + }, + "metadataName": "role", + "type": "create", + }, + ], + "applicationUniversalIdentifier": Any, + }, +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/application/successful-sync-application-workspace-migration.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/successful-sync-application-workspace-migration.integration-spec.ts new file mode 100644 index 00000000000..9a40691d7bc --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/successful-sync-application-workspace-migration.integration-spec.ts @@ -0,0 +1,155 @@ +import { type Manifest } from 'twenty-shared/application'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { v4 as uuidv4 } from 'uuid'; +import { createOneApplication } from 'test/integration/metadata/suites/application/utils/create-one-application.util'; +import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util'; +import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util'; +import { uploadApplicationFile } from 'test/integration/metadata/suites/application/utils/upload-application-file.util'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; + +const TEST_APP_ID = uuidv4(); +const TEST_ROLE_ID = uuidv4(); +const TEST_SECOND_ROLE_ID = uuidv4(); +const TEST_OBJECT_ID = uuidv4(); +const TEST_FIELD_ID = uuidv4(); + +describe('syncApplication', () => { + let appCreated = false; + + beforeAll(async () => { + await createOneApplication({ + universalIdentifier: TEST_APP_ID, + name: 'Test Application', + description: 'A test application', + version: '1.0.0', + sourcePath: 'test-sync', + expectToFail: false, + }); + + appCreated = true; + + // File upload uses multipart which requires real timers + jest.useRealTimers(); + + const packageJson = JSON.stringify({ + name: 'test-application', + version: '1.0.0', + }); + + await uploadApplicationFile({ + applicationUniversalIdentifier: TEST_APP_ID, + fileFolder: 'Dependencies', + filePath: 'package.json', + fileBuffer: Buffer.from(packageJson), + filename: 'package.json', + expectToFail: false, + }); + + jest.useFakeTimers(); + }, 60000); + + afterAll(async () => { + if (!appCreated) { + return; + } + + await uninstallApplication({ + universalIdentifier: TEST_APP_ID, + expectToFail: false, + }); + }); + + it('should return workspace migration actions on initial sync then on second sync with field rename and new role', async () => { + const initialManifest: Manifest = { + application: { + universalIdentifier: TEST_APP_ID, + defaultRoleUniversalIdentifier: TEST_ROLE_ID, + displayName: 'Test Application', + description: 'A test application for workspace migration', + icon: 'IconTestPipe', + applicationVariables: {}, + packageJsonChecksum: null, + yarnLockChecksum: null, + }, + roles: [ + { + universalIdentifier: TEST_ROLE_ID, + label: 'Test Role', + description: 'A test role', + }, + ], + objects: [ + { + labelIdentifierFieldMetadataUniversalIdentifier: TEST_FIELD_ID, + universalIdentifier: TEST_OBJECT_ID, + nameSingular: 'ticket', + namePlural: 'tickets', + labelSingular: 'Ticket', + labelPlural: 'Tickets', + description: 'A support ticket', + icon: 'IconTicket', + fields: [], + }, + ], + fields: [ + { + universalIdentifier: TEST_FIELD_ID, + type: FieldMetadataType.TEXT, + name: 'description', + label: 'Description', + description: 'Ticket description', + icon: 'IconFileDescription', + objectUniversalIdentifier: TEST_OBJECT_ID, + }, + ], + logicFunctions: [], + frontComponents: [], + publicAssets: [], + }; + + const { data: firstSyncData } = await syncApplication({ + manifest: initialManifest, + expectToFail: false, + }); + + expect(firstSyncData).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(firstSyncData), + ); + + const updatedManifest: Manifest = { + ...initialManifest, + roles: [ + { + universalIdentifier: TEST_ROLE_ID, + label: 'Test Role', + description: 'A test role', + }, + { + universalIdentifier: TEST_SECOND_ROLE_ID, + label: 'Viewer Role', + description: 'A read-only role', + }, + ], + fields: [ + { + universalIdentifier: TEST_FIELD_ID, + type: FieldMetadataType.TEXT, + name: 'body', + label: 'Body', + description: 'Ticket description', + icon: 'IconFileDescription', + objectUniversalIdentifier: TEST_OBJECT_ID, + }, + ], + }; + + const { data: secondSyncData } = await syncApplication({ + manifest: updatedManifest, + expectToFail: false, + }); + + expect(secondSyncData).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(secondSyncData), + ); + }, 60000); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/create-one-application-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/create-one-application-query-factory.util.ts new file mode 100644 index 00000000000..c9577e91651 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/create-one-application-query-factory.util.ts @@ -0,0 +1,34 @@ +import gql from 'graphql-tag'; + +export const createOneApplicationQueryFactory = ({ + universalIdentifier, + name, + description, + version, + sourcePath, +}: { + universalIdentifier: string; + name: string; + description?: string; + version: string; + sourcePath: string; +}) => ({ + query: gql` + mutation CreateOneApplication($input: CreateApplicationInput!) { + createOneApplication(input: $input) { + id + universalIdentifier + name + } + } + `, + variables: { + input: { + universalIdentifier, + name, + description, + version, + sourcePath, + }, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/create-one-application.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/create-one-application.util.ts new file mode 100644 index 00000000000..557af6987aa --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/create-one-application.util.ts @@ -0,0 +1,57 @@ +import { createOneApplicationQueryFactory } from 'test/integration/metadata/suites/application/utils/create-one-application-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +type CreatedApplication = { + id: string; + universalIdentifier: string; + name: string; +}; + +export const createOneApplication = async ({ + universalIdentifier, + name, + description, + version, + sourcePath, + expectToFail = false, + token, +}: { + universalIdentifier: string; + name: string; + description?: string; + version: string; + sourcePath: string; + expectToFail?: boolean; + token?: string; +}): CommonResponseBody<{ + createOneApplication: CreatedApplication; +}> => { + const graphqlOperation = createOneApplicationQueryFactory({ + universalIdentifier, + name, + description, + version, + sourcePath, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Create one application should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Create one application has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/sync-application-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/sync-application-query-factory.util.ts new file mode 100644 index 00000000000..ffb9c4f51b6 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/sync-application-query-factory.util.ts @@ -0,0 +1,20 @@ +import gql from 'graphql-tag'; +import { type Manifest } from 'twenty-shared/application'; + +export const syncApplicationQueryFactory = ({ + manifest, +}: { + manifest: Manifest; +}) => ({ + query: gql` + mutation SyncApplication($manifest: JSON!) { + syncApplication(manifest: $manifest) { + applicationUniversalIdentifier + actions + } + } + `, + variables: { + manifest, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/sync-application.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/sync-application.util.ts new file mode 100644 index 00000000000..7a053e1d50b --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/sync-application.util.ts @@ -0,0 +1,45 @@ +import { type Manifest } from 'twenty-shared/application'; +import { syncApplicationQueryFactory } from 'test/integration/metadata/suites/application/utils/sync-application-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +type WorkspaceMigration = { + applicationUniversalIdentifier: string; + actions: unknown[]; +}; + +export const syncApplication = async ({ + manifest, + expectToFail = false, + token, +}: { + manifest: Manifest; + expectToFail?: boolean; + token?: string; +}): CommonResponseBody<{ + syncApplication: WorkspaceMigration; +}> => { + const graphqlOperation = syncApplicationQueryFactory({ + manifest, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Sync application should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Sync application has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/uninstall-application-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/uninstall-application-query-factory.util.ts new file mode 100644 index 00000000000..0b6f76f1567 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/uninstall-application-query-factory.util.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const uninstallApplicationQueryFactory = ({ + universalIdentifier, +}: { + universalIdentifier: string; +}) => ({ + query: gql` + mutation UninstallApplication($universalIdentifier: String!) { + uninstallApplication(universalIdentifier: $universalIdentifier) + } + `, + variables: { + universalIdentifier, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/uninstall-application.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/uninstall-application.util.ts new file mode 100644 index 00000000000..7edda6fbb13 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/uninstall-application.util.ts @@ -0,0 +1,39 @@ +import { uninstallApplicationQueryFactory } from 'test/integration/metadata/suites/application/utils/uninstall-application-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +export const uninstallApplication = async ({ + universalIdentifier, + expectToFail = false, + token, +}: { + universalIdentifier: string; + expectToFail?: boolean; + token?: string; +}): CommonResponseBody<{ + uninstallApplication: boolean; +}> => { + const graphqlOperation = uninstallApplicationQueryFactory({ + universalIdentifier, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Uninstall application should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Uninstall application has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/upload-application-file-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/upload-application-file-query-factory.util.ts new file mode 100644 index 00000000000..d13d18fadb7 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/upload-application-file-query-factory.util.ts @@ -0,0 +1,36 @@ +import gql from 'graphql-tag'; + +export const uploadApplicationFileQueryFactory = ({ + applicationUniversalIdentifier, + fileFolder, + filePath, +}: { + applicationUniversalIdentifier: string; + fileFolder: string; + filePath: string; +}) => ({ + query: gql` + mutation UploadApplicationFile( + $file: Upload! + $applicationUniversalIdentifier: String! + $fileFolder: FileFolder! + $filePath: String! + ) { + uploadApplicationFile( + file: $file + applicationUniversalIdentifier: $applicationUniversalIdentifier + fileFolder: $fileFolder + filePath: $filePath + ) { + id + path + } + } + `, + variables: { + file: null, + applicationUniversalIdentifier, + fileFolder, + filePath, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/utils/upload-application-file.util.ts b/packages/twenty-server/test/integration/metadata/suites/application/utils/upload-application-file.util.ts new file mode 100644 index 00000000000..07f554f332f --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/utils/upload-application-file.util.ts @@ -0,0 +1,65 @@ +import { uploadApplicationFileQueryFactory } from 'test/integration/metadata/suites/application/utils/upload-application-file-query-factory.util'; +import { makeMetadataAPIRequestWithFileUpload } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-file-upload.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +type UploadedFile = { + id: string; + path: string; +}; + +export const uploadApplicationFile = async ({ + applicationUniversalIdentifier, + fileFolder, + filePath, + fileBuffer, + filename, + contentType = 'application/json', + expectToFail = false, + token, +}: { + applicationUniversalIdentifier: string; + fileFolder: string; + filePath: string; + fileBuffer: Buffer; + filename: string; + contentType?: string; + expectToFail?: boolean; + token?: string; +}): CommonResponseBody<{ + uploadApplicationFile: UploadedFile; +}> => { + const graphqlOperation = uploadApplicationFileQueryFactory({ + applicationUniversalIdentifier, + fileFolder, + filePath, + }); + + const response = await makeMetadataAPIRequestWithFileUpload( + graphqlOperation, + { + field: 'file', + buffer: fileBuffer, + filename, + contentType, + }, + token, + ); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Upload application file should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Upload application file has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-shared/src/application/objectManifestType.ts b/packages/twenty-shared/src/application/objectManifestType.ts index b870b796301..380517ca3e0 100644 --- a/packages/twenty-shared/src/application/objectManifestType.ts +++ b/packages/twenty-shared/src/application/objectManifestType.ts @@ -1,5 +1,5 @@ -import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType'; import { type ObjectFieldManifest } from '@/application/objectFieldManifest.type'; +import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType'; export type ObjectManifest = SyncableEntityOptions & { nameSingular: string; @@ -9,4 +9,5 @@ export type ObjectManifest = SyncableEntityOptions & { description?: string; icon?: string; fields: ObjectFieldManifest[]; + labelIdentifierFieldMetadataUniversalIdentifier: string; };