diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f9e30204e6..4af448020c 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -757,7 +757,8 @@ export enum FeatureFlagKey { IS_TWO_FACTOR_AUTHENTICATION_ENABLED = 'IS_TWO_FACTOR_AUTHENTICATION_ENABLED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', IS_WORKFLOW_FILTERING_ENABLED = 'IS_WORKFLOW_FILTERING_ENABLED', - IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED = 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED' + IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED = 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED', + IS_WORKSPACE_MIGRATION_V2_ENABLED = 'IS_WORKSPACE_MIGRATION_V2_ENABLED' } export type Field = { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 2833477ba8..a49a9d0d0d 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -721,7 +721,8 @@ export enum FeatureFlagKey { IS_TWO_FACTOR_AUTHENTICATION_ENABLED = 'IS_TWO_FACTOR_AUTHENTICATION_ENABLED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', IS_WORKFLOW_FILTERING_ENABLED = 'IS_WORKFLOW_FILTERING_ENABLED', - IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED = 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED' + IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED = 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED', + IS_WORKSPACE_MIGRATION_V2_ENABLED = 'IS_WORKSPACE_MIGRATION_V2_ENABLED' } export type Field = { diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index ca52079985..ddc01281fe 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -13,4 +13,5 @@ export enum FeatureFlagKey { IS_FIELDS_PERMISSIONS_ENABLED = 'IS_FIELDS_PERMISSIONS_ENABLED', IS_CORE_VIEW_SYNCING_ENABLED = 'IS_CORE_VIEW_SYNCING_ENABLED', IS_TWO_FACTOR_AUTHENTICATION_ENABLED = 'IS_TWO_FACTOR_AUTHENTICATION_ENABLED', + IS_WORKSPACE_MIGRATION_V2_ENABLED = 'IS_WORKSPACE_MIGRATION_V2_ENABLED', } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts index 6d0646f71a..3c40c45464 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts @@ -76,7 +76,7 @@ export type FieldMetadataDefaultValue< ? FieldMetadataDefaultValueForAnyType | null // Could be improved to be | unknown : T extends keyof FieldMetadataDefaultValueMapping ? FieldMetadataDefaultValueForType - : never; + : never | null; type FieldMetadataDefaultValueExtractedTypes = { [K in keyof FieldMetadataDefaultValueMapping]: ExtractValueType< diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface.ts index 5bdb9ece92..eaac7d46c3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface.ts @@ -18,4 +18,4 @@ export type FieldMetadataOptions< ? null | (FieldMetadataDefaultOption[] | FieldMetadataComplexOption[]) // Could be improved to be | unknown : T extends keyof FieldMetadataOptionsMapping ? FieldMetadataOptionsMapping[T] - : never; + : never | null; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts index 9fb86ea656..56e1a3e5e0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts @@ -60,4 +60,4 @@ export type FieldMetadataSettings< ? null | AllFieldMetadataSettings // Could be improved to be | unknown : T extends keyof FieldMetadataSettingsMapping ? FieldMetadataSettingsMapping[T] | null - : never; + : never | null; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/assign-type-if-is-relation-field-metadata-type.type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/assign-type-if-is-relation-field-metadata-type.type.ts index 8b3639c13f..daa8334e93 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/assign-type-if-is-relation-field-metadata-type.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/assign-type-if-is-relation-field-metadata-type.type.ts @@ -10,4 +10,4 @@ export type AssignTypeIfIsRelationFieldMetadataType< ? Ttype : T extends FieldMetadataType.MORPH_RELATION ? Ttype - : never; + : never | null; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts index 24dfe30e57..1a72cfcad7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts @@ -2,6 +2,10 @@ import { Expect, HasAllProperties } from 'twenty-shared/testing'; import { FieldMetadataType, NullablePartial } from 'twenty-shared/types'; import { Relation as TypeOrmRelation } from 'typeorm'; +import { + FieldMetadataDefaultValueForAnyType, + FieldMetadataDefaultValueForType, +} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; import { AllFieldMetadataSettings, FieldMetadataDateSettings, @@ -10,10 +14,6 @@ import { FieldMetadataRelationSettings, FieldMetadataTextSettings, } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; -import { - FieldMetadataDefaultValueForAnyType, - FieldMetadataDefaultValueForType, -} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; import { FieldMetadataComplexOption, @@ -30,10 +30,10 @@ type DefinedRelationRecord = { }; type NotDefinedRelationRecord = { - relationTargetFieldMetadataId: never; - relationTargetFieldMetadata: never; - relationTargetObjectMetadataId: never; - relationTargetObjectMetadata: never; + relationTargetFieldMetadataId: never | null; + relationTargetFieldMetadata: never | null; + relationTargetObjectMetadataId: never | null; + relationTargetObjectMetadata: never | null; }; type AbstractFieldMetadata = FieldMetadataEntity; @@ -115,23 +115,27 @@ type RelationAssertions = [ >, ]; +type NotDefinedSettings = { + settings: never | null; +}; + // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars type SettingsAssertions = [ - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, Expect< HasAllProperties< @@ -328,8 +332,12 @@ type DefaultValueAssertions = [ > >, - Expect>, - Expect>, + Expect< + HasAllProperties + >, + Expect< + HasAllProperties + >, Expect< HasAllProperties< @@ -339,6 +347,10 @@ type DefaultValueAssertions = [ >, ]; +type NotDefinedOptions = { + options: never | null; +}; + // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars type OptionsAssertions = [ Expect< @@ -360,25 +372,24 @@ type OptionsAssertions = [ > >, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - Expect>, - - Expect>, - Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, + Expect>, Expect< HasAllProperties< diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/prepare-custom-field-metadata-for-options.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/prepare-custom-field-metadata-for-options.util.ts index 9cd9eb2267..5fa9ece600 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/prepare-custom-field-metadata-for-options.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/prepare-custom-field-metadata-for-options.util.ts @@ -1,11 +1,11 @@ import { v4 } from 'uuid'; +import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; import { FieldMetadataComplexOption, FieldMetadataDefaultOption, } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties'; export const prepareCustomFieldMetadataOptions = ( options: FieldMetadataDefaultOption[] | FieldMetadataComplexOption[], diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-field-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts similarity index 79% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-field-metadata.mock.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts index 938af6b374..3785134d4d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-field-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts @@ -1,7 +1,7 @@ import { faker } from '@faker-js/faker'; import { FieldMetadataType } from 'twenty-shared/types'; -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; type FlatFieldMetadataOverrides< T extends FieldMetadataType = FieldMetadataType, @@ -35,10 +35,10 @@ export const getFlatFieldMetadataMock = ( standardOverrides: null, updatedAt: createdAt, workspaceId: faker.string.uuid(), - flatRelationTargetFieldMetadata: undefined as never, - relationTargetFieldMetadataId: undefined as never, - flatRelationTargetObjectMetadata: undefined as never, - relationTargetObjectMetadataId: undefined as never, + flatRelationTargetFieldMetadata: null, + relationTargetFieldMetadataId: null, + flatRelationTargetObjectMetadata: null, + relationTargetObjectMetadataId: null, ...overrides, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts similarity index 82% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts index 1855ab0591..a920cf727e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts @@ -2,9 +2,9 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { AssignTypeIfIsRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/assign-type-if-is-relation-field-metadata-type.type'; -import { FlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; +import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -const fieldMetadataRelationProperties = [ +export const fieldMetadataRelationProperties = [ 'relationTargetFieldMetadata', 'relationTargetObjectMetadata', 'fieldPermissions', @@ -12,7 +12,7 @@ const fieldMetadataRelationProperties = [ 'object', ] as const satisfies (keyof FieldMetadataEntity)[]; -type FieldMetadataEntityRelationProperties = +export type FieldMetadataEntityRelationProperties = (typeof fieldMetadataRelationProperties)[number]; export type FlatFieldMetadata = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-field-metadata-comparator.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util.ts similarity index 87% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-field-metadata-comparator.util.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util.ts index 3078c5bb10..edf7871b74 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-field-metadata-comparator.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util.ts @@ -1,10 +1,9 @@ import diff from 'microdiff'; -import { FieldMetadataType } from 'twenty-shared/types'; +import { FieldMetadataType, FromTo } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; import { UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; @@ -20,10 +19,6 @@ const flatFieldMetadataPropertiesToCompare = [ 'options', 'standardOverrides', 'settings', - // To reactivate once we authorize relation edition, see https://github.com/twentyhq/twenty/commit/39f6f3c4bb101272a9014e142a842d0801a3c33b - // 'relationTargetFieldMetadataId', - // 'relationTargetObjectMetadataId', - /// ] as const satisfies (keyof FlatFieldMetadata)[]; export type FlatFieldMetadataPropertiesToCompare = @@ -47,6 +42,9 @@ const shouldNotOverrideDefaultValue = (type: FieldMetadataType) => { }; type GetWorkspaceMigrationUpdateFieldActionArgs = FromTo; +/** + * This comparator handles update on colliding uniqueIdentifier flatFieldMetadata + */ export const compareTwoFlatFieldMetadata = ({ from, to, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts new file mode 100644 index 0000000000..56e4966716 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts @@ -0,0 +1,77 @@ +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils'; + +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + FlatFieldMetadata, + fieldMetadataRelationProperties, +} from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util'; +import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; +import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util'; + +export const fromFieldMetadataEntityToFlatFieldMetadata = < + T extends FieldMetadataType, +>( + fieldMetadataEntity: FieldMetadataEntity, + /** + * private depth bottleneck + */ + _depth?: number, + // This is intended to be abstract +): FlatFieldMetadata => { + if (isDefined(_depth) && _depth > 1) { + throw new Error( + 'fromFieldMetadataEntityToFlatFieldMetadata entering a possible infinite loop', + ); + } + + const fieldMetadataWithoutRelations = removePropertiesFromRecord( + fieldMetadataEntity, + fieldMetadataRelationProperties, + ); + + if ( + isFieldMetadataEntityOfType( + fieldMetadataEntity, + FieldMetadataType.RELATION, + ) || + isFieldMetadataEntityOfType( + fieldMetadataEntity, + FieldMetadataType.MORPH_RELATION, + ) + ) { + const newDepth = isDefined(_depth) ? _depth + 1 : 1; + const flatRelationTargetFieldMetadata = + fromFieldMetadataEntityToFlatFieldMetadata( + fieldMetadataEntity.relationTargetFieldMetadata, + newDepth, + ); + const flatObjectTargetFieldMetadata = + fromObjectMetadataEntityToFlatObjectMetadata( + fieldMetadataEntity.relationTargetObjectMetadata, + ); + const flatRelationTargetObjectMetadata = + fromFlatObjectMetadataToFlatObjectMetadataWithoutFields( + flatObjectTargetFieldMetadata, + ); + + return { + ...fieldMetadataWithoutRelations, + uniqueIdentifier: + fieldMetadataWithoutRelations.standardId ?? + fieldMetadataWithoutRelations.id, + flatRelationTargetFieldMetadata, + flatRelationTargetObjectMetadata, + } as FlatFieldMetadata; + } + + return { + ...fieldMetadataWithoutRelations, + uniqueIdentifier: + fieldMetadataWithoutRelations.standardId ?? + fieldMetadataWithoutRelations.id, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/is-flat-field-metadata-of-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util.ts similarity index 74% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/is-flat-field-metadata-of-type.util.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util.ts index 99a57de68f..739c563042 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/is-flat-field-metadata-of-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util.ts @@ -1,6 +1,6 @@ import { FieldMetadataType } from 'twenty-shared/types'; -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; export function isFlatFieldMetadataEntityOfType< Field extends FlatFieldMetadata, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util.ts new file mode 100644 index 0000000000..51d8415765 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util.ts @@ -0,0 +1,34 @@ +import { + deepMerge, + fromArrayToUniqueKeyRecord, + isDefined, +} from 'twenty-shared/utils'; + +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { ToMerge } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type'; + +export const mergeTwoFlatFieldMetadatas = ({ + destFlatFieldMetadatas, + toMergeFlatFieldMetadatas, +}: ToMerge): FlatFieldMetadata[] => { + const initialRecordAccumulator = fromArrayToUniqueKeyRecord({ + array: destFlatFieldMetadatas, + uniqueKey: 'uniqueIdentifier', + }); + + const mergedUniqueIdentifierFlatFieldMetadataRecord = + toMergeFlatFieldMetadatas.reduce((acc, flatFieldToMerge) => { + const fieldUniqueIdentifier = flatFieldToMerge.uniqueIdentifier; + const destFieldMetadata: FlatFieldMetadata | undefined = + acc[fieldUniqueIdentifier]; + + return { + ...acc, + [fieldUniqueIdentifier]: isDefined(destFieldMetadata) + ? deepMerge(destFieldMetadata, flatFieldToMerge) + : flatFieldToMerge, + }; + }, initialRecordAccumulator); + + return Object.values(mergedUniqueIdentifierFlatFieldMetadataRecord); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-index-field-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-field-metadata.mock.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-index-field-metadata.mock.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-field-metadata.mock.ts diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-index-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts similarity index 89% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-index-metadata.mock.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts index 13ae038a18..2f364fb44d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-index-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts @@ -1,7 +1,7 @@ import { faker } from '@faker-js/faker'; +import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types'; -import { FlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata'; type FlatIndexMetadataOverrides = Required< Pick diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-field-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-field-metadata.type.ts new file mode 100644 index 0000000000..5e42b93758 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-field-metadata.type.ts @@ -0,0 +1,15 @@ +import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; +import { ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; +import { MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type'; + +type IndexFieldMetadataEntityRelationProperties = + ExtractRecordTypeOrmRelationProperties< + IndexFieldMetadataEntity, + MetadataEntitiesRelationTarget + >; + +export type FlatIndexFieldMetadata = Partial< + Omit +> & { + uniqueIdentifier: string; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-index-metadata-comparator.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/compare-two-flat-index-metadata.util.ts similarity index 87% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-index-metadata-comparator.util.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/compare-two-flat-index-metadata.util.ts index 3c26b5007f..ca38861bfc 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-index-metadata-comparator.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/compare-two-flat-index-metadata.util.ts @@ -1,7 +1,7 @@ import diff from 'microdiff'; +import { FromTo } from 'twenty-shared/types'; -import { FlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; const flatIndexMetadataPropertiesToCompare = [ @@ -16,6 +16,9 @@ type FlatIndexMetadataPropertiesToCompare = (typeof flatIndexMetadataPropertiesToCompare)[number]; // Should also handle indexFieldMetadata comparison ? +/** + * This comparator handles update on colliding uniqueIdentifier flatIndexMetadata + */ export const compareTwoFlatIndexMetadata = ({ from, to, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-object-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts similarity index 93% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-object-metadata.mock.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts index f43401020d..f07a57517d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-object-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts @@ -1,6 +1,6 @@ import { faker } from '@faker-js/faker'; -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; type FlatObjectMetadataOverrides = Required< Pick diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts similarity index 66% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts index 8f7ba929fa..65282fdbfa 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts @@ -1,9 +1,18 @@ +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; -import { FlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata'; import { MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type'; +export const objectMetadataEntityRelationProperties = [ + 'fields', + 'indexMetadatas', + 'targetRelationFields', + 'dataSource', + 'objectPermissions', + 'fieldPermissions', +] as const satisfies ObjectMetadataRelationProperties[]; + type ObjectMetadataRelationProperties = ExtractRecordTypeOrmRelationProperties< ObjectMetadataEntity, MetadataEntitiesRelationTarget diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-object-metadata-comparator.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util.ts similarity index 90% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-object-metadata-comparator.util.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util.ts index 3b3e292990..1619afe8c9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/flat-object-metadata-comparator.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util.ts @@ -1,9 +1,9 @@ import omit from 'lodash.omit'; import diff from 'microdiff'; +import { FromTo } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { UpdateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; @@ -22,6 +22,9 @@ const flatObjectMetadataPropertiesToCompare = [ export type FlatObjectMetadataPropertiesToCompare = (typeof flatObjectMetadataPropertiesToCompare)[number]; +/** + * This comparator handles update on colliding uniqueIdentifier flatObjectdMetadata + */ export const compareTwoFlatObjectMetadata = ({ from, to, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util.ts new file mode 100644 index 0000000000..2785265ea5 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util.ts @@ -0,0 +1,28 @@ +import { removePropertiesFromRecord } from 'twenty-shared/utils'; + +import { fromFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util'; +import { + FlatObjectMetadata, + objectMetadataEntityRelationProperties, +} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; + +export const fromObjectMetadataEntityToFlatObjectMetadata = ( + objectMetadataEntity: ObjectMetadataEntity, +): FlatObjectMetadata => { + const objectMetadataEntityWithoutRelations = removePropertiesFromRecord( + objectMetadataEntity, + objectMetadataEntityRelationProperties, + ); + + return { + ...objectMetadataEntityWithoutRelations, + flatIndexMetadatas: [], // TODO prastoin handle indexes + uniqueIdentifier: + objectMetadataEntityWithoutRelations.standardId ?? + objectMetadataEntityWithoutRelations.id, + flatFieldMetadatas: objectMetadataEntity.fields.map( + fromFieldMetadataEntityToFlatFieldMetadata, + ), + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util.ts new file mode 100644 index 0000000000..2a48f97953 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util.ts @@ -0,0 +1,21 @@ +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; + +export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = ( + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, +): FlatObjectMetadata => { + const { + fieldsById, + fieldIdByJoinColumnName: _fieldIdByJoinColumnName, + fieldIdByName: _fieldIdByName, + indexMetadatas, + ...rest + } = objectMetadataItemWithFieldMaps; + + return fromObjectMetadataEntityToFlatObjectMetadata({ + ...rest, + fields: Object.values(fieldsById), + indexMetadatas, + }); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts new file mode 100644 index 0000000000..7a7ea4030a --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts @@ -0,0 +1,21 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; + +export const fromObjectMetadataMapsToFlatObjectMetadatas = ( + objectMetadataMaps: ObjectMetadataMaps, +): FlatObjectMetadata[] => { + const objectMetadataIds = Object.keys(objectMetadataMaps.byId); + + return objectMetadataIds.flatMap((objectMetadataId) => { + const occurrence = objectMetadataMaps.byId[objectMetadataId]; + + if (!isDefined(occurrence)) { + return []; + } + + return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata(occurrence); + }); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util.ts new file mode 100644 index 0000000000..747c903267 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util.ts @@ -0,0 +1,64 @@ +import { + deepMerge, + fromArrayToUniqueKeyRecord, + isDefined, +} from 'twenty-shared/utils'; + +import { mergeTwoFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { ToMerge } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type'; + +export const mergeTwoFlatFieldObjectMetadatas = ({ + destFlatObjectMetadatas, + toMergeFlatObjectMetadatas, +}: ToMerge): FlatObjectMetadata[] => { + const initialObjectAccumulator = fromArrayToUniqueKeyRecord({ + array: destFlatObjectMetadatas, + uniqueKey: 'uniqueIdentifier', + }); + + const mergedUniqueIdentifierFlatObjectMetadataRecord = + toMergeFlatObjectMetadatas.reduce>( + (acc, toMergeFlatObjectMetadata) => { + const flatObjectUniqueIdentifier = + toMergeFlatObjectMetadata.uniqueIdentifier; + const accumulatorCurrentOccurrence: FlatObjectMetadata | undefined = + initialObjectAccumulator[flatObjectUniqueIdentifier]; + + if (!isDefined(accumulatorCurrentOccurrence)) { + return { + ...acc, + [flatObjectUniqueIdentifier]: toMergeFlatObjectMetadata, + }; + } + + const { + flatFieldMetadatas: destFlatFieldMetadatas, + ...destFlatObjectMetadataRest + } = accumulatorCurrentOccurrence; + const { + flatFieldMetadatas: toMergeFlatFieldMetadatas, + ...toMergeFlatObjectMetadataRest + } = toMergeFlatObjectMetadata; + const mergedFlatObjectMetadata: FlatObjectMetadata = { + ...deepMerge( + destFlatObjectMetadataRest, + toMergeFlatObjectMetadataRest, + ), + // TODO prastoin handle indexes + flatFieldMetadatas: mergeTwoFlatFieldMetadatas({ + destFlatFieldMetadatas, + toMergeFlatFieldMetadatas, + }), + }; + + return { + ...acc, + [flatObjectUniqueIdentifier]: mergedFlatObjectMetadata, + }; + }, + initialObjectAccumulator, + ); + + return Object.values(mergedUniqueIdentifierFlatObjectMetadataRecord); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/create-object.input.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/create-object.input.ts index 51f6e2d77c..dc78198b90 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/create-object.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/create-object.input.ts @@ -4,11 +4,16 @@ import { BeforeCreateOne } from '@ptc-org/nestjs-query-graphql'; import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import GraphQLJSON from 'graphql-type-json'; import { FieldMetadataType } from 'twenty-shared/types'; +import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; +import { v4 } from 'uuid'; import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { BeforeCreateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-create-one-object.hook'; +import { buildDefaultFlatFieldMetadataForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util'; @InputType() @BeforeCreateOne(BeforeCreateOneObject) @@ -71,6 +76,68 @@ export class CreateObjectInput { @IsBoolean() @IsOptional() - @Field({ nullable: true }) + @Field({ nullable: true }) // Not nullable to me isLabelSyncedWithName?: boolean; } + +export const fromCreateObjectInputToFlatObjectMetadata = ( + rawCreateObjectInput: CreateObjectInput, +): FlatObjectMetadata => { + if (rawCreateObjectInput.isRemote) { + throw new UserInputError('Remote objects are not supported yet'); + } + + const createObjectInput = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + rawCreateObjectInput, + [ + 'description', + 'icon', + 'labelPlural', + 'labelSingular', + 'namePlural', + 'nameSingular', + 'shortcut', + ], + ); + + const objectMetadataId = v4(); + const createdAt = new Date(); + const baseCustomFlatFieldMetadatas = + buildDefaultFlatFieldMetadataForCustomObject({ + createdAt, + objectMetadataId, + workspaceId: createObjectInput.workspaceId, + }); + + return { + createdAt, + updatedAt: createdAt, + dataSourceId: createObjectInput.dataSourceId, // TODO is it enough ? + description: createObjectInput.description ?? null, + duplicateCriteria: [], // TODO is it enough ? + flatFieldMetadatas: Object.values(baseCustomFlatFieldMetadatas), + flatIndexMetadatas: [], // TODO is it enough ? + icon: createObjectInput.icon ?? null, + id: objectMetadataId, + imageIdentifierFieldMetadataId: null, + isActive: true, + isAuditLogged: true, + isCustom: true, + isLabelSyncedWithName: createObjectInput.isLabelSyncedWithName ?? false, + isRemote: false, + isSearchable: true, + isSystem: false, + labelIdentifierFieldMetadataId: baseCustomFlatFieldMetadatas.nameField.id, + labelPlural: createObjectInput.labelPlural ?? null, + labelSingular: createObjectInput.labelSingular ?? null, + namePlural: createObjectInput.namePlural ?? null, + nameSingular: createObjectInput.nameSingular ?? null, + shortcut: createObjectInput.shortcut ?? null, + standardId: null, + standardOverrides: null, + uniqueIdentifier: objectMetadataId, + targetTableName: 'DEPRECATED', + workspaceId: createObjectInput.workspaceId, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts index b2102d4fa0..359c266e4f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts @@ -10,6 +10,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; @@ -33,6 +34,8 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; +import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module'; +import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module'; import { ObjectMetadataEntity } from './object-metadata.entity'; import { ObjectMetadataService } from './object-metadata.service'; @@ -63,6 +66,9 @@ import { UpdateObjectPayload } from './dtos/update-object.input'; WorkspaceCacheStorageModule, WorkspaceMetadataCacheModule, WorkspaceDataSourceModule, + FeatureFlagModule, + WorkspaceMigrationBuilderV2Module, + WorkspaceMigrationRunnerV2Module, ], services: [ ObjectMetadataService, 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 a19c8f763c..43d7894961 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 @@ -13,8 +13,12 @@ import { Repository, } from 'typeorm'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util'; +import { mergeTwoFlatFieldObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util'; import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service'; import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; import { @@ -46,12 +50,17 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; +import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; +import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { isSearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util'; import { ObjectMetadataEntity } from './object-metadata.entity'; -import { CreateObjectInput } from './dtos/create-object.input'; +import { + CreateObjectInput, + fromCreateObjectInputToFlatObjectMetadata, +} from './dtos/create-object.input'; @Injectable() export class ObjectMetadataService extends TypeOrmQueryService { @@ -70,6 +79,9 @@ export class ObjectMetadataService extends TypeOrmQueryService { + const idField: FlatFieldMetadata = { + type: FieldMetadataType.UUID, + createdAt, + id: v4(), + isLabelSyncedWithName: false, // TO CHECK + isUnique: true, // Was false before but unsure if normal ? + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.id, + updatedAt: createdAt, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id, + name: 'id', + label: 'Id', + icon: 'Icon123', + description: 'Id', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: true, + defaultValue: 'uuid', + + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const nameField: FlatFieldMetadata = { + type: FieldMetadataType.TEXT, + createdAt, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, + updatedAt: createdAt, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, + name: 'name', + label: 'Name', + icon: 'IconAbc', + description: 'Name', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: "'Untitled'", + + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const createdAtField: FlatFieldMetadata = { + type: FieldMetadataType.DATE_TIME, + createdAt, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + updatedAt: createdAt, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + name: 'createdAt', + label: 'Creation date', + icon: 'IconCalendar', + description: 'Creation date', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: 'now', + + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const updatedAtField: FlatFieldMetadata = { + type: FieldMetadataType.DATE_TIME, + createdAt, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt, + updatedAt: createdAt, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt, + name: 'updatedAt', + label: 'Last update', + icon: 'IconCalendarClock', + description: 'Last time the record was changed', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: 'now', + + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const deletedAtField: FlatFieldMetadata = { + type: FieldMetadataType.DATE_TIME, + createdAt, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, + updatedAt: createdAt, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, + name: 'deletedAt', + label: 'Deleted at', + icon: 'IconCalendarClock', + description: 'Deletion date', + isNullable: true, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: null, + + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const createdByField: FlatFieldMetadata = { + type: FieldMetadataType.ACTOR, + createdAt, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy, + updatedAt: createdAt, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy, + name: 'createdBy', + label: 'Created by', + icon: 'IconCreativeCommonsSa', + description: 'The creator of the record', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: { name: "''", source: "'MANUAL'" }, + + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const positionField: FlatFieldMetadata = { + type: FieldMetadataType.POSITION, + createdAt, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position, + updatedAt: createdAt, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position, + name: 'position', + label: 'Position', + icon: 'IconHierarchy2', + description: 'Position', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: true, + defaultValue: 0, + + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + return { + idField, + nameField, + createdAtField, + updatedAtField, + deletedAtField, + createdByField, + positionField, + } as const; +}; diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index 2d75e245b9..6a72c047e9 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -112,6 +112,7 @@ describe('WorkspaceEntityManager', () => { IS_FIELDS_PERMISSIONS_ENABLED: false, IS_CORE_VIEW_SYNCING_ENABLED: false, IS_TWO_FACTOR_AUTHENTICATION_ENABLED: false, + IS_WORKSPACE_MIGRATION_V2_ENABLED: false, }, eventEmitterService: { emitMutationEvent: jest.fn(), diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts index f70978d700..108ec60e7b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts @@ -60,6 +60,11 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspaceId: workspaceId, + value: false, + }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts index 68667909cb..4e6d77948f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts @@ -1,4 +1,4 @@ -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { FromTo } from 'twenty-shared/types'; export type DeletedCreatedUpdatedMatrix = { created: T[]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap index 6ceb9ab979..c3301fa78b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap @@ -9,8 +9,8 @@ exports[`Workspace migration builder field actions test suite It should build a "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -23,8 +23,8 @@ exports[`Workspace migration builder field actions test suite It should build a "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -73,8 +73,8 @@ exports[`Workspace migration builder field actions test suite It should build an "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -87,8 +87,8 @@ exports[`Workspace migration builder field actions test suite It should build an "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -141,8 +141,8 @@ exports[`Workspace migration builder field actions test suite It should build an "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -155,8 +155,8 @@ exports[`Workspace migration builder field actions test suite It should build an "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -257,8 +257,8 @@ exports[`Workspace migration builder field actions test suite It should build an "createdAt": Any, "defaultValue": null, "description": "new description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "new icon", "id": Any, "isActive": false, @@ -271,8 +271,8 @@ exports[`Workspace migration builder field actions test suite It should build an "name": "new name", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -344,8 +344,8 @@ exports[`Workspace migration builder field actions test suite It should build an "createdAt": Any, "defaultValue": null, "description": "new description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": false, @@ -552,8 +552,8 @@ exports[`Workspace migration builder object actions test suite It should build a "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -566,8 +566,8 @@ exports[`Workspace migration builder object actions test suite It should build a "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -611,8 +611,8 @@ exports[`Workspace migration builder object actions test suite It should build a "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -625,8 +625,8 @@ exports[`Workspace migration builder object actions test suite It should build a "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -670,8 +670,8 @@ exports[`Workspace migration builder object actions test suite It should build a "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -684,8 +684,8 @@ exports[`Workspace migration builder object actions test suite It should build a "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -729,8 +729,8 @@ exports[`Workspace migration builder object actions test suite It should build a "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -743,8 +743,8 @@ exports[`Workspace migration builder object actions test suite It should build a "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, @@ -788,8 +788,8 @@ exports[`Workspace migration builder object actions test suite It should build a "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": undefined, - "flatRelationTargetObjectMetadata": undefined, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, "icon": "icon", "id": Any, "isActive": true, @@ -802,8 +802,8 @@ exports[`Workspace migration builder object actions test suite It should build a "name": "flatFieldMetadataName", "objectMetadataId": Any, "options": null, - "relationTargetFieldMetadataId": undefined, - "relationTargetObjectMetadataId": undefined, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, "settings": null, "standardId": null, "standardOverrides": null, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-field-test-case.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-field-test-case.ts index 969b90d68d..4fd009a8be 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-field-test-case.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-field-test-case.ts @@ -3,8 +3,8 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { getFlatFieldMetadataMock } from 'src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-field-metadata.mock'; -import { getFlatObjectMetadataMock } from 'src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-object-metadata.mock'; +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 { WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type'; const basicObjectMetadataId = faker.string.uuid(); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-index-test-case.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-index-test-case.ts index 9e39a565b4..9bf8f91360 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-index-test-case.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-index-test-case.ts @@ -1,7 +1,7 @@ import { faker } from '@faker-js/faker'; -import { getFlatIndexMetadataMock } from 'src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-index-metadata.mock'; -import { getFlatObjectMetadataMock } from 'src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-object-metadata.mock'; +import { getFlatIndexMetadataMock } from 'src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock'; +import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; import { WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type'; // Should test more things such as flatFieldIndex diffing diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts index f88fc64cb0..425e387dd2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts @@ -1,9 +1,9 @@ import { faker } from '@faker-js/faker'; import { FieldMetadataType } from 'twenty-shared/types'; -import { getFlatFieldMetadataMock } from 'src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-field-metadata.mock'; -import { getFlatIndexMetadataMock } from 'src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-index-metadata.mock'; -import { getFlatObjectMetadataMock } from 'src/engine/workspace-manager/workspace-migration-v2/__tests__/get-flat-object-metadata.mock'; +import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; +import { getFlatIndexMetadataMock } from 'src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock'; +import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; import { WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type'; export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type.ts new file mode 100644 index 0000000000..97669040a0 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type.ts @@ -0,0 +1,3 @@ +export type ToMerge = { + [P in ['toMerge', 'dest'][number] as `${P}Flat${Capitalize}Metadatas`]: T; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts index 7f9985de69..d7beb70e1f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts @@ -1,8 +1,9 @@ +import { FromTo } from 'twenty-shared/types'; + import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; -import { FlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; -import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-field-metadata-comparator.util'; +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util'; +import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; export type CreateFieldAction = { type: 'create_field'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts index 46f89dff4b..5162ad460a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts @@ -1,4 +1,4 @@ -import { FlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata'; +import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; export type CreateIndexAction = { type: 'create_index'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts index 5e6ef53cc6..b7169863a9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts @@ -1,7 +1,8 @@ +import { FromTo } from 'twenty-shared/types'; + +import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { FlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; -import { FlatObjectMetadataPropertiesToCompare } from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-object-metadata-comparator.util'; import { CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; export type CreateObjectAction = { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts index b7f51664b7..7bd64758a2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts @@ -1,6 +1,7 @@ -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { FromTo } from 'twenty-shared/types'; + +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { CustomDeletedCreatedUpdatedMatrix, deletedCreatedUpdatedMatrixDispatcher, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util.ts index e600975a14..62cdcc6c38 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util.ts @@ -1,9 +1,10 @@ -import { FlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata'; +import { FromTo } from 'twenty-shared/types'; + +import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { FlatObjectMetadata, FlatObjectMetadataWithoutFields, -} from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { CustomDeletedCreatedUpdatedMatrix, deletedCreatedUpdatedMatrixDispatcher, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util.ts index 722e235d6b..5f7a75c899 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util.ts @@ -1,7 +1,7 @@ import { FlatObjectMetadata, FlatObjectMetadataWithoutFields, -} from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; +} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; export const fromFlatObjectMetadataToFlatObjectMetadataWithoutFields = ({ flatFieldMetadatas: _flatFieldMetadatas, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts index 420c4a776c..9e08947b27 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts @@ -1,5 +1,5 @@ -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { CreateFieldAction, DeleteFieldAction, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts index 849888cc5f..bf44b333be 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts @@ -1,4 +1,4 @@ -import { FlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-metadata'; +import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { CreateIndexAction, DeleteIndexAction, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts index f755f2399a..f6c64aa4a3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts @@ -1,4 +1,4 @@ -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { CreateObjectAction, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts index 98fe79fbfa..b3ea213d6b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { FromTo } from 'twenty-shared/types'; + +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util'; import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; import { computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts index a8ed6d979e..8e37d4f7b4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts @@ -1,4 +1,4 @@ -import { compareTwoFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-field-metadata-comparator.util'; +import { compareTwoFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util'; import { UpdateFieldAction, WorkspaceMigrationFieldActionV2, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-index-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-index-actions-builder.ts index ebc16fa99c..a5eeddb1f3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-index-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-index-actions-builder.ts @@ -1,4 +1,4 @@ -import { compareTwoFlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-index-metadata-comparator.util'; +import { compareTwoFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/compare-two-flat-index-metadata.util'; import { WorkspaceMigrationIndexActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2'; import { UpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util'; import { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts index c1ff695ba4..f0e461ccca 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts @@ -1,6 +1,6 @@ -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { compareTwoFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util'; import { CustomDeletedCreatedUpdatedMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util'; -import { compareTwoFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-object-metadata-comparator.util'; import { UpdateObjectAction, WorkspaceMigrationObjectActionV2, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts index f6ed5f8376..2c4d922639 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts @@ -1,8 +1,8 @@ import { In } from 'typeorm'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-field-metadata'; -import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-field-metadata-comparator.util'; +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util'; import { CreateFieldAction, DeleteFieldAction, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts index a68cb2a708..ea89cfb549 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { FlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-object-metadata'; -import { FlatObjectMetadataPropertiesToCompare } from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-object-metadata-comparator.util'; import { CreateObjectAction, DeleteObjectAction, diff --git a/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts b/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts index 9007891cca..3cd2317caf 100644 --- a/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts +++ b/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts @@ -28,10 +28,10 @@ export const getMockFieldMetadataEntity = < isSystem: false, isUnique: null, object: {} as ObjectMetadataEntity, - relationTargetFieldMetadata: null as never, - relationTargetFieldMetadataId: null as never, - relationTargetObjectMetadata: null as never, - relationTargetObjectMetadataId: null as never, + relationTargetFieldMetadata: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadata: null, + relationTargetObjectMetadataId: null, standardId: null, standardOverrides: null, id: faker.string.uuid(), diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/from-to.type.ts b/packages/twenty-shared/src/types/FromToType.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/from-to.type.ts rename to packages/twenty-shared/src/types/FromToType.ts diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index ec3b148557..bb95c46cc4 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -10,6 +10,7 @@ export type { ConfigVariableValue } from './ConfigVariableValue'; export { ConnectedAccountProvider } from './ConnectedAccountProvider'; export { FieldMetadataType } from './FieldMetadataType'; +export type { FromTo } from './FromToType'; export type { IsExactly } from './IsExactly'; export type { NullablePartial } from './NullablePartial'; export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions'; diff --git a/packages/twenty-shared/src/utils/__tests__/deepMerge.test.ts b/packages/twenty-shared/src/utils/__tests__/deepMerge.test.ts new file mode 100644 index 0000000000..2762d0608d --- /dev/null +++ b/packages/twenty-shared/src/utils/__tests__/deepMerge.test.ts @@ -0,0 +1,283 @@ +import { EachTestingContext } from '@/testing/types/EachTestingContext.type'; +import { deepMerge } from '@/utils'; + +type DeepMergeTestCase = { + source: Required; + target: Required; + expected: T; +}; + +describe('deepMerge', () => { + describe('primitive values', () => { + type PrimitiveValue = { value: string | number | boolean }; + + const primitiveTestCases: EachTestingContext>[] = [ + { + title: 'should override string values', + context: { + source: { value: 'hello' }, + target: { value: 'world' }, + expected: { value: 'world' }, + }, + }, + { + title: 'should override number values', + context: { + source: { value: 42 }, + target: { value: 24 }, + expected: { value: 24 }, + }, + }, + { + title: 'should override boolean values', + context: { + source: { value: true }, + target: { value: false }, + expected: { value: false }, + }, + }, + ]; + + it.each(primitiveTestCases)('$title', ({ context: { source, target, expected } }) => { + expect(deepMerge(source, target)).toEqual(expected); + }); + }); + + describe('null and undefined handling', () => { + type NullableValue = { value: string | null }; + + const nullTestCases: EachTestingContext>[] = [ + { + title: 'should preserve null values from target', + context: { + source: { value: 'hello' }, + target: { value: null }, + expected: { value: null }, + }, + }, + { + title: 'should ignore undefined values from target', + context: { + source: { value: 'hello' }, + target: { value: undefined as unknown as null }, + expected: { value: 'hello' }, + }, + }, + { + title: 'should override null values from source', + context: { + source: { value: null }, + target: { value: 'world' }, + expected: { value: 'world' }, + }, + }, + ]; + + it.each(nullTestCases)('$title', ({ context: { source, target, expected } }) => { + expect(deepMerge(source, target)).toEqual(expected); + }); + + type MixedNullValue = { a: number | null; b: number }; + + const mixedNullTestCase: EachTestingContext> = { + title: 'should handle mixed null and undefined values', + context: { + source: { a: 1, b: 2 }, + target: { a: null, b: 2 }, + expected: { a: null, b: 2 }, + }, + }; + + it(mixedNullTestCase.title, () => { + const { source, target, expected } = mixedNullTestCase.context; + expect(deepMerge(source, target)).toEqual(expected); + }); + }); + + describe('array handling', () => { + type ArrayValue = { arr: Array | null }; + + const arrayTestCases: EachTestingContext>[] = [ + { + title: 'should concatenate arrays', + context: { + source: { arr: [1, 2] }, + target: { arr: [3, 4] }, + expected: { arr: [1, 2, 3, 4] }, + }, + }, + { + title: 'should handle empty target array', + context: { + source: { arr: [1, 2] }, + target: { arr: [] }, + expected: { arr: [1, 2] }, + }, + }, + { + title: 'should handle empty source array', + context: { + source: { arr: [] }, + target: { arr: [1, 2] }, + expected: { arr: [1, 2] }, + }, + }, + { + title: 'should handle null target array', + context: { + source: { arr: ['a', 'b'] }, + target: { arr: null }, + expected: { arr: null }, + }, + }, + ]; + + it.each(arrayTestCases)('$title', ({ context: { source, target, expected } }) => { + expect(deepMerge(source, target)).toEqual(expected); + }); + }); + + describe('nested objects', () => { + type NestedValue = { + nested: { + a: number; + b: number; + deep?: { + a: number; + b: number; + }; + arr: Array; + obj: { + a: number; + b: number; + }; + } | null; + }; + + const nestedTestCases: EachTestingContext>[] = [ + { + title: 'should merge nested objects', + context: { + source: { nested: { a: 1, b: 2, arr: [], obj: { a: 1, b: 2 } } }, + target: { nested: { a: 1, b: 3, arr: [], obj: { a: 1, b: 2 } } }, + expected: { nested: { a: 1, b: 3, arr: [], obj: { a: 1, b: 2 } } }, + }, + }, + { + title: 'should merge deeply nested objects', + context: { + source: { nested: { a: 1, b: 2, deep: { a: 1, b: 2 }, arr: [], obj: { a: 1, b: 2 } } }, + target: { nested: { a: 1, b: 2, deep: { a: 1, b: 3 }, arr: [], obj: { a: 1, b: 2 } } }, + expected: { nested: { a: 1, b: 2, deep: { a: 1, b: 3 }, arr: [], obj: { a: 1, b: 2 } } }, + }, + }, + { + title: 'should handle null nested object in target', + context: { + source: { nested: { a: 1, b: 2, arr: [], obj: { a: 1, b: 2 } } }, + target: { nested: null }, + expected: { nested: null }, + }, + }, + { + title: 'should handle complex nested structures', + context: { + source: { + nested: { + a: 1, + b: 2, + arr: [1, 2], + obj: { a: 1, b: 2 }, + }, + }, + target: { + nested: { + a: 1, + b: 2, + arr: [3, 4], + obj: { a: 1, b: 3 }, + }, + }, + expected: { + nested: { + a: 1, + b: 2, + arr: [1, 2, 3, 4], + obj: { a: 1, b: 3 }, + }, + }, + }, + }, + ]; + + it.each(nestedTestCases)('$title', ({ context: { source, target, expected } }) => { + expect(deepMerge(source, target)).toEqual(expected); + }); + }); + + describe('edge cases', () => { + type EdgeValue = { + a: number | Date | RegExp; + b: number | Date | RegExp; + }; + + const edgeTestCases: EachTestingContext>[] = [ + { + title: 'should handle Date objects by replacing them', + context: { + source: { a: new Date('2023-01-01'), b: new Date('2023-01-01') }, + target: { a: new Date('2023-12-31'), b: new Date('2023-12-31') }, + expected: { a: new Date('2023-12-31'), b: new Date('2023-12-31') }, + }, + }, + { + title: 'should handle RegExp objects by replacing them', + context: { + source: { a: /test1/, b: /test1/ }, + target: { a: /test2/, b: /test2/ }, + expected: { a: /test2/, b: /test2/ }, + }, + }, + { + title: 'should handle mixed Date and RegExp objects', + context: { + source: { a: new Date('2023-01-01'), b: /test1/ }, + target: { a: new Date('2023-12-31'), b: /test2/ }, + expected: { a: new Date('2023-12-31'), b: /test2/ }, + }, + }, + ]; + + it.each(edgeTestCases)('$title', ({ context: { source, target, expected } }) => { + expect(deepMerge(source, target)).toEqual(expected); + }); + + type NestedDateValue = { + a: { value: Date }; + b: { value: Date }; + }; + + const nestedDateTestCase: EachTestingContext> = { + title: 'should handle Date objects in nested structures', + context: { + source: { + a: { value: new Date('2023-01-01') }, + b: { value: new Date('2023-01-01') }, + }, + target: { + a: { value: new Date('2023-12-31') }, + b: { value: new Date('2023-12-31') }, + }, + expected: { + a: { value: new Date('2023-12-31') }, + b: { value: new Date('2023-12-31') }, + }, + }, + }; + + it(nestedDateTestCase.title, () => { + const { source, target, expected } = nestedDateTestCase.context; + expect(deepMerge(source, target)).toEqual(expected); + }); + }); +}); diff --git a/packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-object-string-properties.spec.ts b/packages/twenty-shared/src/utils/__tests__/trim-and-remove-duplicated-whitespaces-from-object-string-properties.test.ts similarity index 94% rename from packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-object-string-properties.spec.ts rename to packages/twenty-shared/src/utils/__tests__/trim-and-remove-duplicated-whitespaces-from-object-string-properties.test.ts index 9e5a9957ec..07a44c0664 100644 --- a/packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-object-string-properties.spec.ts +++ b/packages/twenty-shared/src/utils/__tests__/trim-and-remove-duplicated-whitespaces-from-object-string-properties.test.ts @@ -1,6 +1,5 @@ -import { EachTestingContext } from 'twenty-shared/testing'; - -import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties'; +import { EachTestingContext } from '@/testing/types/EachTestingContext.type'; +import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from '../trim-and-remove-duplicated-whitespaces-from-object-string-properties'; type SanitizeObjectStringPropertiesTestCase = EachTestingContext<{ input: Record; diff --git a/packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-string.spec.ts b/packages/twenty-shared/src/utils/__tests__/trim-and-remove-duplicated-whitespaces-from-string.test.ts similarity index 89% rename from packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-string.spec.ts rename to packages/twenty-shared/src/utils/__tests__/trim-and-remove-duplicated-whitespaces-from-string.test.ts index 4030447ef2..0617f764f9 100644 --- a/packages/twenty-server/src/utils/__test__/trim-and-remove-duplicated-whitespaces-from-string.spec.ts +++ b/packages/twenty-shared/src/utils/__tests__/trim-and-remove-duplicated-whitespaces-from-string.test.ts @@ -1,6 +1,5 @@ -import { EachTestingContext } from 'twenty-shared/testing'; - -import { trimAndRemoveDuplicatedWhitespacesFromString } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-string'; +import { EachTestingContext } from '@/testing/types/EachTestingContext.type'; +import { trimAndRemoveDuplicatedWhitespacesFromString } from '../trim-and-remove-duplicated-whitespaces-from-string'; type TrimAndRemoveWhitespacesTestCase = EachTestingContext<{ input: string; diff --git a/packages/twenty-shared/src/utils/deepMerge.ts b/packages/twenty-shared/src/utils/deepMerge.ts new file mode 100644 index 0000000000..f10b40ad81 --- /dev/null +++ b/packages/twenty-shared/src/utils/deepMerge.ts @@ -0,0 +1,79 @@ +/** + * Deep merges two objects or arrays recursively + * - Objects are merged by combining their properties + * - Arrays are merged by concatenating them + * - Primitive values from target override source + * - Null values from target are preserved + * - Undefined values from target are ignored + * - Date and RegExp objects are treated as primitives (replaced, not merged) + * + * @param source The source object to merge from + * @param target The target object to merge into + * @returns A new merged object + */ +export const deepMerge = ( + source: Required, + target: Required, +): T => { + // Handle null/undefined cases + if (!source) return target as T; + if (!target) return source; + + // Create a new object to avoid mutations + const output = { ...source }; + + // Iterate through all keys in target + Object.keys(target).forEach((key) => { + const sourceValue = source[key as keyof T]; + const targetValue = target[key as keyof T]; + + // Skip undefined values in target + if (targetValue === undefined) { + return; + } + + // Handle null values - explicitly assign them + if (targetValue === null) { + output[key as keyof T] = null as T[keyof T]; + return; + } + + // Handle arrays - concatenate them + if (Array.isArray(sourceValue) && Array.isArray(targetValue)) { + output[key as keyof T] = [...sourceValue, ...targetValue] as T[keyof T]; + return; + } + + // Handle Date and RegExp objects - treat them as primitives + if ( + targetValue instanceof Date || + targetValue instanceof RegExp || + sourceValue instanceof Date || + sourceValue instanceof RegExp + ) { + output[key as keyof T] = targetValue as T[keyof T]; + return; + } + + // Handle nested objects - recurse + if ( + sourceValue && + targetValue && + typeof sourceValue === 'object' && + typeof targetValue === 'object' && + !Array.isArray(sourceValue) && + !Array.isArray(targetValue) + ) { + output[key as keyof T] = deepMerge( + sourceValue as object, + targetValue as object, + ) as T[keyof T]; + return; + } + + // For primitives + output[key as keyof T] = targetValue as T[keyof T]; + }); + + return output; +}; \ No newline at end of file diff --git a/packages/twenty-shared/src/utils/from-array-to-unique-key-record.util.ts b/packages/twenty-shared/src/utils/from-array-to-unique-key-record.util.ts new file mode 100644 index 0000000000..c5df9b5453 --- /dev/null +++ b/packages/twenty-shared/src/utils/from-array-to-unique-key-record.util.ts @@ -0,0 +1,25 @@ +import { StringPropertyKeys } from '@/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties'; +import { isDefined } from '@/utils/validation'; + +export const fromArrayToUniqueKeyRecord = ({ + array, + uniqueKey, +}: { + array: T[]; + uniqueKey: StringPropertyKeys; +}) => { + return array.reduce>((acc, occurence) => { + const currentUniqueKey = occurence[uniqueKey] as string; + + if (isDefined(acc[currentUniqueKey])) { + throw new Error( + `Should never occur, flat array contains twice the same unique key ${occurence[uniqueKey]}`, + ); + } + + return { + ...acc, + [currentUniqueKey]: occurence, + }; + }, {}); +}; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index 8c4b1c4f00..aa7e279442 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -8,7 +8,9 @@ */ export { assertUnreachable } from './assertUnreachable'; +export { deepMerge } from './deepMerge'; export { isFieldMetadataDateKind } from './fieldMetadata/isFieldMetadataDateKind'; +export { fromArrayToUniqueKeyRecord } from './from-array-to-unique-key-record.util'; export { getURLSafely } from './getURLSafely'; export { getImageAbsoluteURI } from './image/getImageAbsoluteURI'; export { @@ -17,10 +19,14 @@ export { } from './image/getLogoUrlFromDomainName'; export { getUniqueConstraintsFields } from './indexMetadata/getUniqueConstraintsFields'; export { parseJson } from './parseJson'; +export { removePropertiesFromRecord } from './removePropertiesFromRecord'; export { removeUndefinedFields } from './removeUndefinedFields'; export { getGenericOperationName } from './sentry/getGenericOperationName'; export { getHumanReadableNameFromCode } from './sentry/getHumanReadableNameFromCode'; export { capitalize } from './strings/capitalize'; +export type { StringPropertyKeys } from './trim-and-remove-duplicated-whitespaces-from-object-string-properties'; +export { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from './trim-and-remove-duplicated-whitespaces-from-object-string-properties'; +export { trimAndRemoveDuplicatedWhitespacesFromString } from './trim-and-remove-duplicated-whitespaces-from-string'; export { absoluteUrlSchema } from './url/absoluteUrlSchema'; export { buildSignedPath } from './url/buildSignedPath'; export { getAbsoluteUrl } from './url/getAbsoluteUrl'; diff --git a/packages/twenty-shared/src/utils/removePropertiesFromRecord.ts b/packages/twenty-shared/src/utils/removePropertiesFromRecord.ts new file mode 100644 index 0000000000..7e829b07b3 --- /dev/null +++ b/packages/twenty-shared/src/utils/removePropertiesFromRecord.ts @@ -0,0 +1,12 @@ +export const removePropertiesFromRecord = ( + record: T, + keysToRemove: K[], +): Omit => { + const result = { ...record }; + + for (const key of keysToRemove) { + delete result[key]; + } + + return result; +}; diff --git a/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts b/packages/twenty-shared/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts similarity index 80% rename from packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts rename to packages/twenty-shared/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts index 0e5d736532..67bf8af866 100644 --- a/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts +++ b/packages/twenty-shared/src/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties.ts @@ -1,8 +1,8 @@ -import { trimAndRemoveDuplicatedWhitespacesFromString } from 'src/utils/trim-and-remove-duplicated-whitespaces-from-string'; +import { trimAndRemoveDuplicatedWhitespacesFromString } from '@/utils/trim-and-remove-duplicated-whitespaces-from-string'; type OnlyStringPropertiesKey = Extract; -type StringPropertyKeys = { +export type StringPropertyKeys = { [K in OnlyStringPropertiesKey]: T[K] extends string | undefined ? K : never; diff --git a/packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts b/packages/twenty-shared/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts similarity index 100% rename from packages/twenty-server/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts rename to packages/twenty-shared/src/utils/trim-and-remove-duplicated-whitespaces-from-string.ts