diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonObjectMetadata.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonObjectMetadata.ts index 077a092bceb..c297e8c5be2 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonObjectMetadata.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonObjectMetadata.ts @@ -132,36 +132,43 @@ export const mockPersonFlatObjectMetadataMaps = ( export const mockPersonFlatObjectMetadata = ( duplicateCriteria: WorkspaceEntityDuplicateCriteria[], -): FlatObjectMetadata => - ({ - id: objectMetadataId, - icon: 'Icon123', - standardId: '', - nameSingular: 'person', - namePlural: 'people', - labelSingular: 'Person', - labelPlural: 'People', - targetTableName: 'person', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: true, - isSearchable: true, - duplicateCriteria: duplicateCriteria, - labelIdentifierFieldMetadataId: '', - imageIdentifierFieldMetadataId: '', - workspaceId, - universalIdentifier: objectMetadataId, - indexMetadataIds: [], - fieldIds: mockFieldMetadatas.map((field) => field.id), - viewIds: [], - applicationId: 'test-application-id', - isLabelSyncedWithName: false, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - shortcut: null, - description: null, - standardOverrides: null, - isUIReadOnly: false, - }) as FlatObjectMetadata; +): FlatObjectMetadata => ({ + id: objectMetadataId, + icon: 'Icon123', + standardId: '', + nameSingular: 'person', + namePlural: 'people', + labelSingular: 'Person', + labelPlural: 'People', + targetTableName: 'person', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: true, + isSearchable: true, + duplicateCriteria: duplicateCriteria, + labelIdentifierFieldMetadataId: '', + imageIdentifierFieldMetadataId: '', + workspaceId, + universalIdentifier: objectMetadataId, + indexMetadataIds: [], + fieldIds: mockFieldMetadatas.map((field) => field.id), + viewIds: [], + applicationId: 'test-application-id', + isLabelSyncedWithName: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + shortcut: null, + description: null, + standardOverrides: null, + isUIReadOnly: false, + applicationUniversalIdentifier: 'test-application-id', + fieldUniversalIdentifiers: mockFieldMetadatas.map( + (field) => field.universalIdentifier, + ), + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier: null, + imageIdentifierFieldMetadataUniversalIdentifier: null, +}); diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts index 0d063fa4d88..058fb068b52 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts @@ -3,7 +3,7 @@ import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/typ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -const mockObjectMetadata = { +const mockObjectMetadata: FlatObjectMetadata = { id: '1', icon: 'Icon123', nameSingular: 'Object', @@ -34,7 +34,13 @@ const mockObjectMetadata = { labelIdentifierFieldMetadataId: null, imageIdentifierFieldMetadataId: null, duplicateCriteria: null, -} as FlatObjectMetadata; + applicationUniversalIdentifier: 'test-application-id', + fieldUniversalIdentifiers: [], + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier: null, + imageIdentifierFieldMetadataUniversalIdentifier: null, +}; const mockFlatFieldMetadataMaps: FlatEntityMaps = { byId: {}, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts index f7719964c8e..ab151a763ef 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts @@ -14,6 +14,7 @@ export const FieldMetadataExceptionCode = appendCommonExceptionCode({ FIELD_MUTATION_NOT_ALLOWED: 'FIELD_MUTATION_NOT_ALLOWED', FIELD_ALREADY_EXISTS: 'FIELD_ALREADY_EXISTS', OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND', + APPLICATION_NOT_FOUND: 'APPLICATION_NOT_FOUND', FIELD_METADATA_RELATION_NOT_ENABLED: 'FIELD_METADATA_RELATION_NOT_ENABLED', FIELD_METADATA_RELATION_MALFORMED: 'FIELD_METADATA_RELATION_MALFORMED', LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND: @@ -43,6 +44,8 @@ const getFieldMetadataExceptionUserFriendlyMessage = ( return msg`A field with this name already exists.`; case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND: return msg`Object not found.`; + case FieldMetadataExceptionCode.APPLICATION_NOT_FOUND: + return msg`Application not found.`; case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED: return msg`Relation is not enabled for this field.`; case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED: diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts index ab8945d8dff..c443086d76c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts @@ -31,6 +31,7 @@ import { ViewModule } from 'src/engine/metadata-modules/view/view.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { FieldMetadataEntity } from './field-metadata.entity'; @@ -62,6 +63,7 @@ import { UpdateFieldInput } from './dtos/update-field.input'; FlatFieldMetadataModule, IndexMetadataModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, + WorkspaceCacheModule, ], services: [FieldMetadataService], resolvers: [ diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts index 03b26660c13..11229aafbdb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts @@ -22,6 +22,7 @@ import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/met import { fromDeleteFieldInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util'; import { fromUpdateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util'; import { throwOnFieldInputTranspilationsError } from 'src/engine/metadata-modules/flat-field-metadata/utils/throw-on-field-input-transpilations-error.util'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { EMPTY_ORCHESTRATOR_FAILURE_REPORT } from 'src/engine/workspace-manager/workspace-migration/constant/empty-orchestrator-failure-report.constant'; import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; @@ -34,6 +35,7 @@ export class FieldMetadataService extends TypeOrmQueryService @@ -325,10 +340,10 @@ export class FieldMetadataService extends TypeOrmQueryService; entity: MetadataEntity<'fieldMetadata'>; }; objectMetadata: { @@ -150,6 +176,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteObjectAction; }; flatEntity: FlatObjectMetadata; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'objectMetadata'>; }; view: { @@ -159,6 +186,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteViewAction; }; flatEntity: FlatView; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'view'>; }; viewField: { @@ -168,6 +196,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteViewFieldAction; }; flatEntity: FlatViewField; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'viewField'>; }; viewGroup: { @@ -177,6 +206,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteViewGroupAction; }; flatEntity: FlatViewGroup; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'viewGroup'>; }; rowLevelPermissionPredicate: { @@ -186,6 +216,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteRowLevelPermissionPredicateAction; }; flatEntity: FlatRowLevelPermissionPredicate; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'rowLevelPermissionPredicate'>; }; rowLevelPermissionPredicateGroup: { @@ -195,6 +226,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteRowLevelPermissionPredicateGroupAction; }; flatEntity: FlatRowLevelPermissionPredicateGroup; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'rowLevelPermissionPredicateGroup'>; }; viewFilterGroup: { @@ -204,6 +236,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteViewFilterGroupAction; }; flatEntity: FlatViewFilterGroup; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'viewFilterGroup'>; }; index: { @@ -213,6 +246,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteIndexAction; }; flatEntity: FlatIndexMetadata; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'index'>; }; logicFunction: { @@ -222,6 +256,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteLogicFunctionAction; }; flatEntity: FlatLogicFunction; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'logicFunction'>; }; viewFilter: { @@ -231,6 +266,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteViewFilterAction; }; flatEntity: FlatViewFilter; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'viewFilter'>; }; role: { @@ -240,6 +276,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteRoleAction; }; flatEntity: FlatRole; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'role'>; }; roleTarget: { @@ -249,6 +286,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteRoleTargetAction; }; flatEntity: FlatRoleTarget; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'roleTarget'>; }; agent: { @@ -258,6 +296,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteAgentAction; }; flatEntity: FlatAgent; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'agent'>; }; skill: { @@ -267,6 +306,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteSkillAction; }; flatEntity: FlatSkill; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'skill'>; }; commandMenuItem: { @@ -276,6 +316,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteCommandMenuItemAction; }; flatEntity: FlatCommandMenuItem; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'commandMenuItem'>; }; navigationMenuItem: { @@ -285,6 +326,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteNavigationMenuItemAction; }; flatEntity: FlatNavigationMenuItem; + universalFlatEntity: UniversalFlatEntityFrom; entity: NavigationMenuItemEntity; }; pageLayout: { @@ -294,6 +336,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeletePageLayoutAction; }; flatEntity: FlatPageLayout; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'pageLayout'>; }; pageLayoutWidget: { @@ -303,6 +346,10 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeletePageLayoutWidgetAction; }; flatEntity: FlatPageLayoutWidget; + universalFlatEntity: UniversalFlatEntityFrom< + PageLayoutWidgetEntity, + 'pageLayoutWidget' + >; entity: MetadataEntity<'pageLayoutWidget'>; }; pageLayoutTab: { @@ -312,6 +359,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeletePageLayoutTabAction; }; flatEntity: FlatPageLayoutTab; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'pageLayoutTab'>; }; frontComponent: { @@ -321,6 +369,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteFrontComponentAction; }; flatEntity: FlatFrontComponent; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'frontComponent'>; }; webhook: { @@ -330,6 +379,7 @@ export type AllFlatEntityTypesByMetadataName = { delete: DeleteWebhookAction; }; flatEntity: FlatWebhook; + universalFlatEntity: UniversalFlatEntityFrom; entity: MetadataEntity<'webhook'>; }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type.ts new file mode 100644 index 00000000000..a6e1d2c3ade --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type.ts @@ -0,0 +1,15 @@ +import { type AllMetadataName } from 'twenty-shared/metadata'; + +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; + +/** + * Currently under migration but aims to replace FlatEntity afterwards + */ +export type FlatEntityFromV2< + TEntity, + TMetadataName extends AllMetadataName | undefined = undefined, + TInnerFlatEntity extends { __universal?: unknown } = FlatEntityFrom< + TEntity, + TMetadataName + >, +> = Omit & TInnerFlatEntity['__universal']; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts index a456f32a9f5..0ed5719251d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-from.type.ts @@ -16,6 +16,7 @@ export type SyncableFlatEntity = Omit< export type FlatEntityFrom< TEntity, + // Required to be passed for narrowed type TMetadataName extends AllMetadataName | undefined = undefined, > = Omit< TEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type.ts new file mode 100644 index 00000000000..5bd96d349a7 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type.ts @@ -0,0 +1,6 @@ +import { type AllMetadataName } from 'twenty-shared/metadata'; + +import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name'; + +export type MetadataUniversalFlatEntity = + AllFlatEntityTypesByMetadataName[T]['universalFlatEntity']; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts index 0b26f0d3a76..c6b27709dac 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts @@ -45,8 +45,16 @@ export const getFlatFieldMetadataMock = ( applicationId: faker.string.uuid(), relationTargetFieldMetadataId: null, relationTargetObjectMetadataId: null, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - __universal: {} as any, + applicationUniversalIdentifier: faker.string.uuid(), + objectMetadataUniversalIdentifier: faker.string.uuid(), + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, ...overrides, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts index 73316193836..d565eda2769 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts @@ -65,6 +65,15 @@ export const getRelationTargetFlatFieldMetadataMock = ({ defaultValue: null, options: null, applicationId: faker.string.uuid(), - __universal: undefined, + applicationUniversalIdentifier: faker.string.uuid(), + objectMetadataUniversalIdentifier: faker.string.uuid(), + relationTargetObjectMetadataUniversalIdentifier: faker.string.uuid(), + relationTargetFieldMetadataUniversalIdentifier: faker.string.uuid(), + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: settings, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap index 35ad6c9c42b..b1d965a62f5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload has different relation types 1`] = ` { @@ -117,7 +117,9 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "flatFieldMetadatas": [ { "applicationId": Any, + "applicationUniversalIdentifier": Any, "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": Any, "defaultValue": null, "description": "new field description", @@ -131,14 +133,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "newFieldLabel", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": Any, "name": "newFieldPets", "objectMetadataId": Any, + "objectMetadataUniversalIdentifier": Any, "options": null, "relationTargetFieldMetadataId": Any, + "relationTargetFieldMetadataUniversalIdentifier": Any, "relationTargetObjectMetadataId": Any, + "relationTargetObjectMetadataUniversalIdentifier": Any, "settings": { "relationType": "ONE_TO_MANY", }, @@ -146,14 +153,21 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "standardOverrides": null, "type": "MORPH_RELATION", "universalIdentifier": Any, + "universalSettings": { + "relationType": "ONE_TO_MANY", + }, "updatedAt": Any, "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": Any, }, { "applicationId": Any, + "applicationUniversalIdentifier": Any, "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": Any, "defaultValue": null, "description": "new field description", @@ -167,14 +181,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "Pet", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": null, "name": "pet", "objectMetadataId": Any, + "objectMetadataUniversalIdentifier": Any, "options": null, "relationTargetFieldMetadataId": Any, + "relationTargetFieldMetadataUniversalIdentifier": Any, "relationTargetObjectMetadataId": Any, + "relationTargetObjectMetadataUniversalIdentifier": Any, "settings": { "joinColumnName": "petId", "onDelete": "SET_NULL", @@ -184,14 +203,23 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "standardOverrides": null, "type": "RELATION", "universalIdentifier": Any, + "universalSettings": { + "joinColumnName": "petId", + "onDelete": "SET_NULL", + "relationType": "MANY_TO_ONE", + }, "updatedAt": Any, "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": Any, }, { "applicationId": Any, + "applicationUniversalIdentifier": Any, "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": Any, "defaultValue": null, "description": "new field description", @@ -205,14 +233,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "newFieldLabel", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": Any, "name": "newFieldCompanies", "objectMetadataId": Any, + "objectMetadataUniversalIdentifier": Any, "options": null, "relationTargetFieldMetadataId": Any, + "relationTargetFieldMetadataUniversalIdentifier": Any, "relationTargetObjectMetadataId": Any, + "relationTargetObjectMetadataUniversalIdentifier": Any, "settings": { "relationType": "ONE_TO_MANY", }, @@ -220,14 +253,21 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "standardOverrides": null, "type": "MORPH_RELATION", "universalIdentifier": Any, + "universalSettings": { + "relationType": "ONE_TO_MANY", + }, "updatedAt": Any, "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": Any, }, { "applicationId": Any, + "applicationUniversalIdentifier": Any, "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": Any, "defaultValue": null, "description": "new field description", @@ -241,14 +281,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "Company", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": null, "name": "company", "objectMetadataId": Any, + "objectMetadataUniversalIdentifier": Any, "options": null, "relationTargetFieldMetadataId": Any, + "relationTargetFieldMetadataUniversalIdentifier": Any, "relationTargetObjectMetadataId": Any, + "relationTargetObjectMetadataUniversalIdentifier": Any, "settings": { "joinColumnName": "companyId", "onDelete": "SET_NULL", @@ -258,9 +303,16 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "standardOverrides": null, "type": "RELATION", "universalIdentifier": Any, + "universalSettings": { + "joinColumnName": "companyId", + "onDelete": "SET_NULL", + "relationType": "MANY_TO_ONE", + }, "updatedAt": Any, "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": Any, }, ], diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts index a9425521132..dc9a6238efb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts @@ -5,8 +5,11 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { fromCreateFieldInputToFlatFieldMetadatasToCreate, type FromCreateFieldInputToFlatObjectMetadataArgs, @@ -16,6 +19,24 @@ import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/fla import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock'; import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock'; +const MOCK_FLAT_APPLICATION: FlatApplication = { + id: '20202020-81ee-42da-a281-668632f32fe7', + universalIdentifier: '20202020-81ee-42da-a281-668632f32fe7', + workspaceId: 'mock-workspace-id', + name: 'Workspace Custom Application', + description: null, + version: null, + sourceType: 'local', + sourcePath: '', + logicFunctionLayerId: null, + defaultLogicFunctionRoleId: null, + defaultLogicFunctionRole: null, + canBeUninstalled: false, + createdAt: new Date(), + updatedAt: new Date(), + deletedAt: null, +}; + const flatObjectMetadataMaps = [ COMPANY_FLAT_OBJECT_MOCK, ROCKET_FLAT_OBJECT_MOCK, @@ -27,6 +48,9 @@ const flatObjectMetadataMaps = [ }); }, createEmptyFlatEntityMaps()); +const emptyFlatFieldMetadataMaps: FlatEntityMaps = + createEmptyFlatEntityMaps(); + type TestCase = EachTestingContext<{ input: FromCreateFieldInputToFlatObjectMetadataArgs; expected: 'success' | 'fail'; @@ -42,8 +66,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s 'should create morph relation field metadata with valid input on rocket object to pet object', context: { input: { - workspaceCustomApplicationId: - '20202020-81ee-42da-a281-668632f32fe7', + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'newField', label: 'newFieldLabel', @@ -67,6 +90,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s }, workspaceId: mockWorkspaceId, flatObjectMetadataMaps, + flatFieldMetadataMaps: emptyFlatFieldMetadataMaps, }, expected: 'success', }, @@ -102,8 +126,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s title: 'should fail when morphRelationsCreationPayload is missing', context: { input: { - workspaceCustomApplicationId: - '20202020-81ee-42da-a281-668632f32fe7', + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'newField', label: 'newFieldLabel', @@ -114,6 +137,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s }, workspaceId: mockWorkspaceId, flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatFieldMetadataMaps: emptyFlatFieldMetadataMaps, }, expected: 'fail', }, @@ -122,8 +146,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s title: 'should fail when morphRelationsCreationPayload is empty array', context: { input: { - workspaceCustomApplicationId: - '20202020-81ee-42da-a281-668632f32fe7', + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'newField', label: 'newFieldLabel', @@ -134,6 +157,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s }, workspaceId: mockWorkspaceId, flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatFieldMetadataMaps: emptyFlatFieldMetadataMaps, }, expected: 'fail', }, @@ -143,8 +167,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s 'should fail when morphRelationsCreationPayload has different relation types', context: { input: { - workspaceCustomApplicationId: - '20202020-81ee-42da-a281-668632f32fe7', + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'newField', label: 'newFieldLabel', @@ -168,6 +191,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s }, workspaceId: mockWorkspaceId, flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatFieldMetadataMaps: emptyFlatFieldMetadataMaps, }, expected: 'fail', }, @@ -177,8 +201,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s 'should fail when morphRelationsCreationPayload has several references to same object metadata', context: { input: { - workspaceCustomApplicationId: - '20202020-81ee-42da-a281-668632f32fe7', + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'newField', label: 'newFieldLabel', @@ -202,6 +225,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s }, workspaceId: mockWorkspaceId, flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatFieldMetadataMaps: emptyFlatFieldMetadataMaps, }, expected: 'fail', }, @@ -211,8 +235,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s 'should fail when morphRelationsCreationPayload has invalid relation payload', context: { input: { - workspaceCustomApplicationId: - '20202020-81ee-42da-a281-668632f32fe7', + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'newField', label: 'newFieldLabel', @@ -229,6 +252,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s }, workspaceId: mockWorkspaceId, flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatFieldMetadataMaps: emptyFlatFieldMetadataMaps, }, expected: 'fail', }, @@ -237,8 +261,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s title: 'should fail when target object metadata is not found', context: { input: { - workspaceCustomApplicationId: - '20202020-81ee-42da-a281-668632f32fe7', + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'newField', label: 'newFieldLabel', @@ -256,6 +279,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s }, workspaceId: mockWorkspaceId, flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatFieldMetadataMaps: emptyFlatFieldMetadataMaps, }, expected: 'fail', }, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/__tests__/flat-field-metadata.test-type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/__tests__/flat-field-metadata.test-type.ts index e074b47c547..4e928a189da 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/__tests__/flat-field-metadata.test-type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/__tests__/flat-field-metadata.test-type.ts @@ -71,3 +71,48 @@ type FlatTransformationAssertions = [ Expect>, Expect>, ]; + +// Any narrowed flatFieldMetadata type should be assignable to non narrowed flatFieldMetadata +type AbstractFlatFieldMetadata = FlatFieldMetadata; + +const _assertion: Record = { + // Primitive types + uuid: {} as FlatFieldMetadata, + text: {} as FlatFieldMetadata, + number: {} as FlatFieldMetadata, + boolean: {} as FlatFieldMetadata, + numeric: {} as FlatFieldMetadata, + position: {} as FlatFieldMetadata, + + // Date types + date: {} as FlatFieldMetadata, + dateTime: {} as FlatFieldMetadata, + + // Complex types + currency: {} as FlatFieldMetadata, + fullName: {} as FlatFieldMetadata, + address: {} as FlatFieldMetadata, + links: {} as FlatFieldMetadata, + emails: {} as FlatFieldMetadata, + phones: {} as FlatFieldMetadata, + actor: {} as FlatFieldMetadata, + + // Select types + rating: {} as FlatFieldMetadata, + select: {} as FlatFieldMetadata, + multiSelect: {} as FlatFieldMetadata, + + // JSON/Array types + rawJson: {} as FlatFieldMetadata, + array: {} as FlatFieldMetadata, + richText: {} as FlatFieldMetadata, + richTextV2: {} as FlatFieldMetadata, + + // Relation types + relation: {} as FlatFieldMetadata, + morphRelation: {} as FlatFieldMetadata, + + // Special types + files: {} as FlatFieldMetadata, + tsVector: {} as FlatFieldMetadata, +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts index 680e76aad7c..74b6d89a34b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts @@ -1,10 +1,10 @@ import { type FieldMetadataType } from 'twenty-shared/types'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; +import { type FlatEntityFromV2 } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type'; export type FlatFieldMetadata = - FlatEntityFrom< + FlatEntityFromV2< Omit< FieldMetadataEntity, 'relationTargetFieldMetadata' | 'relationTargetObjectMetadata' diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/__tests__/generate-morph-or-relation-flat-field-metadata-pair.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/__tests__/generate-morph-or-relation-flat-field-metadata-pair.spec.ts index 157bd032487..fc0e95d4895 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/__tests__/generate-morph-or-relation-flat-field-metadata-pair.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/__tests__/generate-morph-or-relation-flat-field-metadata-pair.spec.ts @@ -6,6 +6,7 @@ import { RelationType, } from 'twenty-shared/types'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { generateMorphOrRelationFlatFieldMetadataPair, type SourceTargetMorphOrRelationFlatFieldAndFlatIndex, @@ -13,6 +14,24 @@ import { import { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock'; import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock'; +const MOCK_FLAT_APPLICATION: FlatApplication = { + id: '20202020-81ee-42da-a281-668632f32fe7', + universalIdentifier: '20202020-81ee-42da-a281-668632f32fe7', + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + name: 'Workspace Custom Application', + description: null, + version: null, + sourceType: 'local', + sourcePath: '', + logicFunctionLayerId: null, + defaultLogicFunctionRoleId: null, + defaultLogicFunctionRole: null, + canBeUninstalled: false, + createdAt: new Date(), + updatedAt: new Date(), + deletedAt: null, +}; + type GenerateMorphOrRelationFlatFieldMetadataPairTestInput = Parameters< typeof generateMorphOrRelationFlatFieldMetadataPair >[0]; @@ -28,8 +47,6 @@ type TestCase = EachTestingContext<{ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => { const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419'; - const mockWorkspaceCustomApplicationId = - '20202020-81ee-42da-a281-668632f32fe7'; describe('Success cases', () => { const testCases: TestCase[] = [ @@ -43,7 +60,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => targetFlatFieldMetadataType: FieldMetadataType.RELATION, sourceFlatObjectMetadataJoinColumnName: 'petId', workspaceId: mockWorkspaceId, - workspaceCustomApplicationId: mockWorkspaceCustomApplicationId, + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'pets', label: 'Pets', @@ -79,7 +96,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => targetFlatFieldMetadataType: FieldMetadataType.RELATION, sourceFlatObjectMetadataJoinColumnName: 'petId', workspaceId: mockWorkspaceId, - workspaceCustomApplicationId: mockWorkspaceCustomApplicationId, + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'pets', label: 'Pets', @@ -115,7 +132,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => targetFlatFieldMetadataType: FieldMetadataType.RELATION, sourceFlatObjectMetadataJoinColumnName: 'petId', workspaceId: mockWorkspaceId, - workspaceCustomApplicationId: mockWorkspaceCustomApplicationId, + flatApplication: MOCK_FLAT_APPLICATION, createFieldInput: { name: 'pets', label: 'Pets', @@ -151,7 +168,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => targetFlatFieldMetadataType: FieldMetadataType.RELATION, sourceFlatObjectMetadataJoinColumnName: 'targetPetId', workspaceId: mockWorkspaceId, - workspaceCustomApplicationId: mockWorkspaceCustomApplicationId, + flatApplication: MOCK_FLAT_APPLICATION, morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1', createFieldInput: { name: 'targetPet', @@ -188,7 +205,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => targetFlatFieldMetadataType: FieldMetadataType.RELATION, sourceFlatObjectMetadataJoinColumnName: 'targetPetId', workspaceId: mockWorkspaceId, - workspaceCustomApplicationId: mockWorkspaceCustomApplicationId, + flatApplication: MOCK_FLAT_APPLICATION, morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1', createFieldInput: { name: 'targetPet', @@ -225,7 +242,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => targetFlatFieldMetadataType: FieldMetadataType.MORPH_RELATION, sourceFlatObjectMetadataJoinColumnName: 'targetPetId', workspaceId: mockWorkspaceId, - workspaceCustomApplicationId: mockWorkspaceCustomApplicationId, + flatApplication: MOCK_FLAT_APPLICATION, morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1', createFieldInput: { name: 'targetPet', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts index 671f5331ccf..a14e692aaeb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts @@ -4,17 +4,19 @@ import { } from 'twenty-shared/types'; import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util'; import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util'; import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util'; type ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs = { - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; flatFieldMetadataMaps: FlatEntityMaps; morphRelationsUpdatePayload?: RelationUpdatePayload[]; fieldMetadataToUpdate: FlatFieldMetadata; @@ -22,7 +24,7 @@ type ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs = { }; export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({ - workspaceCustomApplicationId, + flatApplication, flatFieldMetadataMaps, morphRelationsUpdatePayload, fieldMetadataToUpdate, @@ -68,6 +70,13 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({ const commonTargetFieldName = initialTargetFieldMetadata.name; const commonObjectMetadataId = initialTargetFieldMetadata.objectMetadataId; + const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings( + fieldMetadataToUpdate.settings, + ); + const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId) + ? flatFieldMetadataMaps.byId[junctionTargetFieldId] + : undefined; + morphRelationsUpdatePayload.forEach((morphRelationUpdatePayload) => { const { targetObjectMetadataId } = morphRelationUpdatePayload; @@ -101,13 +110,14 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({ targetFlatObjectMetadata: newTargetObjectMetadata, targetFlatFieldMetadataType: FieldMetadataType.RELATION, workspaceId: fieldMetadataToUpdate.workspaceId, - workspaceCustomApplicationId, + flatApplication, sourceFlatObjectMetadataJoinColumnName: computeMorphOrRelationFieldJoinColumnName({ name: computedMorphName, }), morphId: fieldMetadataToUpdate.morphId, targetFieldName: commonTargetFieldName, + junctionTargetFlatFieldMetadata, }); flatFieldMetadatasToCreate.push(...flatFieldMetadatas); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util.ts index c7978c7e939..310f9858f67 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util.ts @@ -5,6 +5,7 @@ type JunctionTargetSettings = { junctionTargetFieldId?: string; }; +// TODO refactor using either type predicate or FieldMetadataType discriminating union // Extracts junction target settings from untyped settings input // This function handles the boundary where settings come from external API input export const extractJunctionTargetSettingsFromSettings = ( diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts index f981f22b78c..8b5511cd82b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util.ts @@ -10,6 +10,7 @@ import { } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.util'; @@ -25,14 +26,15 @@ import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-m export type FromCreateFieldInputToFlatObjectMetadataArgs = { createFieldInput: Omit; workspaceId: string; - workspaceCustomApplicationId: string; -} & Pick; + flatApplication: FlatApplication; +} & Pick; export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({ createFieldInput: rawCreateFieldInput, workspaceId, flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - workspaceCustomApplicationId, + flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + flatApplication, }: FromCreateFieldInputToFlatObjectMetadataArgs): Promise< FieldInputTranspilationResult<{ flatFieldMetadatas: FlatFieldMetadata[]; @@ -77,7 +79,9 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({ createFieldInput, workspaceId, fieldMetadataId, - workspaceCustomApplicationId, + flatApplication, + objectMetadataUniversalIdentifier: + parentFlatObjectMetadata.universalIdentifier, }); switch (createFieldInput.type) { @@ -88,21 +92,23 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({ type: createFieldInput.type, }, existingFlatObjectMetadataMaps, + existingFlatFieldMetadataMaps, sourceFlatObjectMetadata: parentFlatObjectMetadata, workspaceId, - workspaceCustomApplicationId, + flatApplication, }); } case FieldMetadataType.RELATION: { return await fromRelationCreateFieldInputToFlatFieldMetadatas({ existingFlatObjectMetadataMaps, + existingFlatFieldMetadataMaps, sourceFlatObjectMetadata: parentFlatObjectMetadata, createFieldInput: { ...createFieldInput, type: createFieldInput.type, }, workspaceId, - workspaceCustomApplicationId, + flatApplication, }); } case FieldMetadataType.RATING: { 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 index 33b7b683930..30b8a2bffec 100644 --- 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 @@ -106,33 +106,27 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = ({ [], viewFieldIds: fieldMetadataEntity.viewFields.map(({ id }) => id), viewFilterIds: fieldMetadataEntity.viewFilters.map(({ id }) => id), - __universal: { - universalIdentifier: fieldMetadataWithoutRelations.universalIdentifier, - applicationUniversalIdentifier, - objectMetadataUniversalIdentifier, - relationTargetObjectMetadataUniversalIdentifier, - relationTargetFieldMetadataUniversalIdentifier, - viewFieldUniversalIdentifiers: fieldMetadataEntity.viewFields.map( + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier, + relationTargetFieldMetadataUniversalIdentifier, + viewFieldUniversalIdentifiers: fieldMetadataEntity.viewFields.map( + ({ universalIdentifier }) => universalIdentifier, + ), + viewFilterUniversalIdentifiers: fieldMetadataEntity.viewFilters.map( + ({ universalIdentifier }) => universalIdentifier, + ), + kanbanAggregateOperationViewUniversalIdentifiers: + fieldMetadataEntity.kanbanAggregateOperationViews.map( ({ universalIdentifier }) => universalIdentifier, ), - viewFilterUniversalIdentifiers: fieldMetadataEntity.viewFilters.map( + calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map( + ({ universalIdentifier }) => universalIdentifier, + ), + mainGroupByFieldMetadataViewUniversalIdentifiers: + fieldMetadataEntity.mainGroupByFieldMetadataViews?.map( ({ universalIdentifier }) => universalIdentifier, - ), - kanbanAggregateOperationViewUniversalIdentifiers: - fieldMetadataEntity.kanbanAggregateOperationViews.map( - ({ universalIdentifier }) => universalIdentifier, - ), - calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map( - ({ universalIdentifier }) => universalIdentifier, - ), - mainGroupByFieldMetadataViewUniversalIdentifiers: - fieldMetadataEntity.mainGroupByFieldMetadataViews?.map( - ({ universalIdentifier }) => universalIdentifier, - ) ?? [], - settings: settingsWithUniversalIdentifiers, - defaultValue: fieldMetadataWithoutRelations.defaultValue, - options: fieldMetadataWithoutRelations.options, - standardOverrides: fieldMetadataWithoutRelations.standardOverrides, - }, + ) ?? [], + universalSettings: settingsWithUniversalIdentifiers, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts index a3a13014bad..cfb25597e32 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts @@ -3,12 +3,14 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util'; import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util'; import { validateMorphRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util'; import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; @@ -19,16 +21,18 @@ type FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs = { type: FieldMetadataType.MORPH_RELATION; }; existingFlatObjectMetadataMaps: FlatEntityMaps; + existingFlatFieldMetadataMaps: FlatEntityMaps; sourceFlatObjectMetadata: FlatObjectMetadata; workspaceId: string; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; }; export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({ createFieldInput, existingFlatObjectMetadataMaps, + existingFlatFieldMetadataMaps, sourceFlatObjectMetadata, workspaceId, - workspaceCustomApplicationId, + flatApplication, }: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise< FieldInputTranspilationResult<{ flatFieldMetadatas: FlatFieldMetadata[]; @@ -66,6 +70,13 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({ return morphRelationCreationPayloadValidation; } + const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings( + createFieldInput.settings, + ); + const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId) + ? existingFlatFieldMetadataMaps.byId[junctionTargetFieldId] + : undefined; + const morphRelationCreationPayload = morphRelationCreationPayloadValidation.result; const morphId = v4(); @@ -95,7 +106,8 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({ targetFlatFieldMetadataType: FieldMetadataType.RELATION, workspaceId, morphId, - workspaceCustomApplicationId, + flatApplication, + junctionTargetFlatFieldMetadata, }); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts index c245f9d8f52..ef0fd7c9b1a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts @@ -2,11 +2,14 @@ import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util'; import { generateMorphOrRelationFlatFieldMetadataPair, type SourceTargetMorphOrRelationFlatFieldAndFlatIndex, @@ -19,16 +22,18 @@ type FromRelationCreateFieldInputToFlatFieldMetadataArgs = { type: FieldMetadataType.RELATION; }; existingFlatObjectMetadataMaps: FlatEntityMaps; + existingFlatFieldMetadataMaps: FlatEntityMaps; sourceFlatObjectMetadata: FlatObjectMetadata; workspaceId: string; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; }; export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({ existingFlatObjectMetadataMaps, + existingFlatFieldMetadataMaps, sourceFlatObjectMetadata, createFieldInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, }: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise< FieldInputTranspilationResult > => { @@ -59,6 +64,13 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({ const { relationCreationPayload, targetFlatObjectMetadata } = relationValidationResult.result; + const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings( + createFieldInput.settings, + ); + const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId) + ? existingFlatFieldMetadataMaps.byId[junctionTargetFieldId] + : undefined; + const generateResult = generateMorphOrRelationFlatFieldMetadataPair({ createFieldInput: { ...createFieldInput, @@ -72,7 +84,8 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({ targetFlatObjectMetadata, targetFlatFieldMetadataType: FieldMetadataType.RELATION, workspaceId, - workspaceCustomApplicationId, + flatApplication, + junctionTargetFlatFieldMetadata, }); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts index 71c3548197c..1aa935b88a3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts @@ -5,6 +5,7 @@ import { isDefined, } from 'twenty-shared/utils'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; @@ -23,7 +24,7 @@ import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-fiel type FromUpdateFieldInputToFlatFieldMetadataArgs = { updateFieldInput: UpdateFieldInput; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; isSystemBuild: boolean; } & Pick< AllFlatEntityMaps, @@ -41,7 +42,7 @@ type FlatFieldMetadataAndIndexToUpdate = { flatFieldMetadatasToCreate: FlatFieldMetadata[]; } & FlatFieldMetadataUpdateSideEffects; export const fromUpdateFieldInputToFlatFieldMetadata = ({ - workspaceCustomApplicationId, + flatApplication, flatIndexMaps, flatObjectMetadataMaps: existingFlatObjectMetadataMaps, flatFieldMetadataMaps, @@ -109,7 +110,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({ FieldMetadataType.MORPH_RELATION, ) ? computeFlatFieldToUpdateFromMorphRelationUpdatePayload({ - workspaceCustomApplicationId, + flatApplication, morphRelationsUpdatePayload: rawUpdateFieldInput?.morphRelationsUpdatePayload, flatFieldMetadataMaps: flatFieldMetadataMaps, @@ -149,7 +150,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({ toFlatFieldMetadata, flatViewMaps, flatViewFieldMaps, - workspaceCustomApplicationId, + flatApplication, }); if (sideEffectResult.status === 'fail') { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts index a98cc5aa788..06197856095 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts @@ -6,11 +6,11 @@ import { } from 'twenty-shared/types'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util'; import { generateIndexForFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util'; import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util'; import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; @@ -22,23 +22,39 @@ type ComputeFieldMetadataRelationSettingsForRelationTypeArgs = { relationType: RelationType; joinColumnName: string; junctionTargetFieldId?: string; + junctionTargetFieldUniversalIdentifier?: string; }; + const computeFieldMetadataRelationSettingsForRelationType = ({ relationType, joinColumnName, junctionTargetFieldId, -}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs) => { + junctionTargetFieldUniversalIdentifier, +}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs): Pick< + FlatFieldMetadata, + 'settings' | 'universalSettings' +> => { if (relationType === RelationType.MANY_TO_ONE) { - return { + const settings = { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.SET_NULL, joinColumnName, }; + + return { settings, universalSettings: settings }; } return { - relationType: RelationType.ONE_TO_MANY, - ...(junctionTargetFieldId && { junctionTargetFieldId }), + settings: { + relationType: RelationType.ONE_TO_MANY, + ...(junctionTargetFieldId && { junctionTargetFieldId }), + }, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + ...(junctionTargetFieldUniversalIdentifier && { + junctionTargetFieldUniversalIdentifier, + }), + }, }; }; @@ -53,8 +69,9 @@ type GenerateMorphOrRelationFlatFieldMetadataPairArgs = { > & { type: MorphOrRelationFieldMetadataType }; workspaceId: string; morphId?: string | null; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; targetFieldName?: string; + junctionTargetFlatFieldMetadata?: FlatFieldMetadata; }; export type SourceTargetMorphOrRelationFlatFieldAndFlatIndex = { @@ -68,27 +85,30 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ targetFlatObjectMetadata, targetFlatFieldMetadataType, workspaceId, - workspaceCustomApplicationId, + flatApplication, sourceFlatObjectMetadataJoinColumnName, morphId = null, targetFieldName, + junctionTargetFlatFieldMetadata, }: GenerateMorphOrRelationFlatFieldMetadataPairArgs): SourceTargetMorphOrRelationFlatFieldAndFlatIndex => { const sourceFlatFieldMetadataType = createFieldInput.type; const { relationCreationPayload } = createFieldInput; - const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings( - createFieldInput.settings, - ); - - const sourceFlatFieldMetadataSettings = - computeFieldMetadataRelationSettingsForRelationType({ - joinColumnName: sourceFlatObjectMetadataJoinColumnName, - relationType: relationCreationPayload.type, - junctionTargetFieldId, - }); + const { + settings: sourceFlatFieldMetadataSettings, + universalSettings: sourceFlatFieldMetadataUniversalSettings, + } = computeFieldMetadataRelationSettingsForRelationType({ + joinColumnName: sourceFlatObjectMetadataJoinColumnName, + relationType: relationCreationPayload.type, + junctionTargetFieldId: junctionTargetFlatFieldMetadata?.id, + junctionTargetFieldUniversalIdentifier: + junctionTargetFlatFieldMetadata?.universalIdentifier, + }); const targetRelationTargetFieldMetadataId = v4(); const sourceRelationTargetFieldMetadataId = v4(); + const sourceFieldUniversalIdentifier = v4(); + const targetFieldUniversalIdentifier = v4(); const defaultDescriptionFromField = buildDescriptionForRelationFieldMetadataOnFromField({ @@ -105,10 +125,15 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ 'flatRelationTargetFieldMetadata' > = { ...getDefaultFlatFieldMetadata({ - createFieldInput, + createFieldInput: { + ...createFieldInput, + universalIdentifier: sourceFieldUniversalIdentifier, + }, workspaceId, fieldMetadataId: sourceRelationTargetFieldMetadataId, - workspaceCustomApplicationId, + flatApplication, + objectMetadataUniversalIdentifier: + sourceFlatObjectMetadata.universalIdentifier, }), morphId: sourceFlatFieldMetadataType === FieldMetadataType.MORPH_RELATION @@ -121,9 +146,14 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ createFieldInput.description ?? defaultDescriptionFromField.description, defaultValue: null, settings: sourceFlatFieldMetadataSettings, + universalSettings: sourceFlatFieldMetadataUniversalSettings, options: null, relationTargetFieldMetadataId: targetRelationTargetFieldMetadataId, relationTargetObjectMetadataId: targetFlatObjectMetadata.id, + relationTargetObjectMetadataUniversalIdentifier: + targetFlatObjectMetadata.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: + targetFieldUniversalIdentifier, }; const targetCreateFieldInput: CreateFieldInput = { @@ -141,25 +171,32 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ workspaceId, isSystem: createFieldInput.isSystem ?? false, }; - const targetFlatFieldMetadataSettings = - computeFieldMetadataRelationSettingsForRelationType({ - joinColumnName: computeMorphOrRelationFieldJoinColumnName({ - name: targetCreateFieldInput.name, - }), - relationType: - relationCreationPayload.type === RelationType.ONE_TO_MANY - ? RelationType.MANY_TO_ONE - : RelationType.ONE_TO_MANY, - }); + const { + settings: targetFlatFieldMetadataSettings, + universalSettings: targetFlatFieldMetadataUniversalSettings, + } = computeFieldMetadataRelationSettingsForRelationType({ + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ + name: targetCreateFieldInput.name, + }), + relationType: + relationCreationPayload.type === RelationType.ONE_TO_MANY + ? RelationType.MANY_TO_ONE + : RelationType.ONE_TO_MANY, + }); const targetFlatFieldMetadata: FlatFieldMetadata< typeof targetFlatFieldMetadataType > = { ...getDefaultFlatFieldMetadata({ - createFieldInput: targetCreateFieldInput, + createFieldInput: { + ...targetCreateFieldInput, + universalIdentifier: targetFieldUniversalIdentifier, + }, workspaceId, fieldMetadataId: targetRelationTargetFieldMetadataId, - workspaceCustomApplicationId, + flatApplication, + objectMetadataUniversalIdentifier: + targetFlatObjectMetadata.universalIdentifier, }), morphId: targetFlatFieldMetadataType === FieldMetadataType.MORPH_RELATION @@ -168,9 +205,14 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ type: targetFlatFieldMetadataType, defaultValue: null, settings: targetFlatFieldMetadataSettings, + universalSettings: targetFlatFieldMetadataUniversalSettings, options: null, relationTargetFieldMetadataId: sourceRelationTargetFieldMetadataId, relationTargetObjectMetadataId: sourceFlatObjectMetadata.id, + relationTargetObjectMetadataUniversalIdentifier: + sourceFlatObjectMetadata.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: + sourceFieldUniversalIdentifier, }; const indexMetadata: FlatIndexMetadata = generateIndexForFlatFieldMetadata({ diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts index a68b8d3b484..52250eed651 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts @@ -1,6 +1,7 @@ import { extractAndSanitizeObjectStringFields } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { generateDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/generate-default-value'; import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable'; @@ -10,13 +11,15 @@ type GetDefaultFlatFieldMetadataArgs = { fieldMetadataId: string; createFieldInput: Omit; workspaceId: string; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; + objectMetadataUniversalIdentifier: string; }; export const getDefaultFlatFieldMetadata = ({ createFieldInput, fieldMetadataId, workspaceId, - workspaceCustomApplicationId, + flatApplication, + objectMetadataUniversalIdentifier, }: GetDefaultFlatFieldMetadataArgs) => { const { defaultValue, settings } = extractAndSanitizeObjectStringFields( createFieldInput, @@ -58,9 +61,18 @@ export const getDefaultFlatFieldMetadata = ({ updatedAt: createdAt, isUIReadOnly: createFieldInput.isUIReadOnly ?? false, morphId: null, - applicationId: - createFieldInput.applicationId ?? workspaceCustomApplicationId, + applicationId: flatApplication.id, viewFilterIds: [], kanbanAggregateOperationViewIds: [], + applicationUniversalIdentifier: flatApplication.universalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, } as const satisfies FlatFieldMetadata; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-flat-field-metadata-update-side-effect.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-flat-field-metadata-update-side-effect.util.ts index 0170339b305..6fde0d0618e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-flat-field-metadata-update-side-effect.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-flat-field-metadata-update-side-effect.util.ts @@ -1,6 +1,7 @@ import { type FromTo } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; @@ -41,7 +42,7 @@ type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo< | 'flatViewMaps' | 'flatViewFieldMaps' > & { - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; }; export const FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS: FlatFieldMetadataUpdateSideEffects = @@ -70,7 +71,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({ flatViewGroupMaps, flatViewMaps, flatViewFieldMaps, - workspaceCustomApplicationId, + flatApplication, }: HandleFlatFieldMetadataUpdateSideEffectArgs): FieldInputTranspilationResult => { const sideEffectResult = structuredClone( FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS, @@ -130,7 +131,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({ flatIndexMaps, flatObjectMetadataMaps, flatFieldMetadataMaps, - workspaceCustomApplicationId, + flatApplication, }); if (indexChangesSideEffectResult.status === 'fail') { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-index-changes-during-field-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-index-changes-during-field-update.util.ts index 5df63d8b391..231db1361ad 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-index-changes-during-field-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/handle-index-changes-during-field-update.util.ts @@ -2,6 +2,7 @@ import { msg } from '@lingui/core/macro'; import { type FromTo } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; @@ -28,7 +29,7 @@ type FromToFlatFieldMetadataAndFlatEntityMaps = FromTo< AllFlatEntityMaps, 'flatObjectMetadataMaps' | 'flatFieldMetadataMaps' | 'flatIndexMaps' > & { - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; }; const FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT: FieldMetadataUpdateIndexSideEffect = { @@ -43,7 +44,7 @@ export const handleIndexChangesDuringFieldUpdate = ({ flatIndexMaps, flatObjectMetadataMaps, flatFieldMetadataMaps, - workspaceCustomApplicationId, + flatApplication, }: FromToFlatFieldMetadataAndFlatEntityMaps): FieldInputTranspilationResult => { if ( !hasIndexRelevantChanges({ @@ -81,7 +82,7 @@ export const handleIndexChangesDuringFieldUpdate = ({ relatedIndexes, flatObjectMetadata, flatFieldMetadataMaps, - workspaceCustomApplicationId, + flatApplication, }); }; @@ -127,13 +128,13 @@ const handleExistingIndexes = ({ relatedIndexes, flatObjectMetadata, flatFieldMetadataMaps, - workspaceCustomApplicationId, + flatApplication, }: { relatedIndexes: FlatIndexMetadata[]; flatObjectMetadata: FlatObjectMetadata; flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps']; } & FromTo & { - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; }): FieldInputTranspilationResult => { if ( toFlatFieldMetadata.isUnique === false && @@ -155,7 +156,7 @@ const handleExistingIndexes = ({ if ( isDefined(uniqueIndexToDelete) && ((isDefined(uniqueIndexToDelete.applicationId) && - uniqueIndexToDelete.applicationId !== workspaceCustomApplicationId) || + uniqueIndexToDelete.applicationId !== flatApplication.id) || !uniqueIndexToDelete.isCustom) ) { return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts index c957c08586d..f9ac28fdf43 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts @@ -10,6 +10,11 @@ export const getFlatObjectMetadataMock = ( overrides: FlatObjectMetadataOverrides, ): FlatObjectMetadata => { const createdAt = '2024-01-01T00:00:00.000Z'; + const applicationId = overrides.applicationId ?? faker.string.uuid(); + const labelIdentifierFieldMetadataId = + overrides.labelIdentifierFieldMetadataId ?? faker.string.uuid(); + const imageIdentifierFieldMetadataId = + overrides.imageIdentifierFieldMetadataId ?? faker.string.uuid(); return { viewIds: [], @@ -18,7 +23,7 @@ export const getFlatObjectMetadataMock = ( description: 'default flat object metadata description', icon: 'icon', id: faker.string.uuid(), - imageIdentifierFieldMetadataId: faker.string.uuid(), + imageIdentifierFieldMetadataId, isActive: true, isAuditLogged: true, isCustom: true, @@ -27,20 +32,28 @@ export const getFlatObjectMetadataMock = ( isSearchable: true, isSystem: false, isUIReadOnly: false, - labelIdentifierFieldMetadataId: faker.string.uuid(), + labelIdentifierFieldMetadataId, labelPlural: 'default flat object metadata label plural', labelSingular: 'default flat object metadata label singular', namePlural: 'defaultflatObjectMetadataNamePlural', nameSingular: 'defaultflatObjectMetadataNameSingular', shortcut: 'shortcut', standardId: null, - applicationId: faker.string.uuid(), + applicationId, standardOverrides: null, targetTableName: '', workspaceId: faker.string.uuid(), createdAt, updatedAt: createdAt, duplicateCriteria: null, + applicationUniversalIdentifier: applicationId, + fieldUniversalIdentifiers: [], + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier: + labelIdentifierFieldMetadataId, + imageIdentifierFieldMetadataUniversalIdentifier: + imageIdentifierFieldMetadataId, ...overrides, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts index 1413e01cf8e..e2361aa29f1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts @@ -1,13 +1,12 @@ -import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; +import { type FlatEntityFromV2 } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -type BaseFlatObjectMetadata = FlatEntityFrom< +type BaseFlatObjectMetadata = FlatEntityFromV2< Omit >; -export type FlatObjectMetadata = Omit & { - __universal?: BaseFlatObjectMetadata['__universal'] & { - // TODO remove once https://github.com/twentyhq/core-team-issues/issues/2172 has been resolved - labelIdentifierFieldMetadataUniversalIdentifier: string | null; - imageIdentifierFieldMetadataUniversalIdentifier: string | null; - }; +export type FlatObjectMetadata = BaseFlatObjectMetadata & { + // NOTE: below fields are not reflected on the final UniversalFlatEntity either they should we should define a common source + // TODO remove once https://github.com/twentyhq/core-team-issues/issues/2172 has been resolved + labelIdentifierFieldMetadataUniversalIdentifier: string | null; + imageIdentifierFieldMetadataUniversalIdentifier: string | null; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts index a1ca19d957b..ea37a51e10d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts @@ -6,6 +6,7 @@ import { v4 } from 'uuid'; import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; @@ -19,14 +20,14 @@ type FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs { createObjectInput: CreateObjectInput; workspaceId: string; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; existingFeatureFlagsMap: FeatureFlagMap; } & Pick; export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate = ({ createObjectInput: rawCreateObjectInput, workspaceId, - workspaceCustomApplicationId, + flatApplication, flatObjectMetadataMaps: existingFlatObjectMetadataMaps, existingFeatureFlagsMap, }: FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs): { @@ -50,11 +51,14 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre ); const objectMetadataId = v4(); + const universalIdentifier = createObjectInput.universalIdentifier ?? v4(); const defaultFlatFieldForCustomObjectMaps = buildDefaultFlatFieldMetadatasForCustomObject({ flatObjectMetadata: { id: objectMetadataId, - applicationId: workspaceCustomApplicationId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + universalIdentifier, }, workspaceId, skipNameField: createObjectInput.skipNameField, @@ -62,11 +66,12 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre const createdAt = new Date().toISOString(); // Use nameField.id if it exists, otherwise use idField.id (for junction tables without name) - const nameField = defaultFlatFieldForCustomObjectMaps.fields.nameField as - | FlatFieldMetadata - | undefined; + const nameField = defaultFlatFieldForCustomObjectMaps.fields.nameField; const labelIdentifierFieldMetadataId = nameField?.id ?? defaultFlatFieldForCustomObjectMaps.fields.idField.id; + const labelIdentifierFieldMetadataUniversalIdentifier = + nameField?.universalIdentifier ?? + defaultFlatFieldForCustomObjectMaps.fields.idField.universalIdentifier; const flatObjectMetadataToCreate: FlatObjectMetadata = { fieldIds: [], @@ -95,10 +100,16 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre shortcut: createObjectInput.shortcut ?? null, standardId: createObjectInput.standardId ?? null, standardOverrides: null, - applicationId: workspaceCustomApplicationId, - universalIdentifier: createObjectInput.universalIdentifier ?? v4(), + applicationId: flatApplication.id, + universalIdentifier, targetTableName: 'DEPRECATED', workspaceId, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + fieldUniversalIdentifiers: [], + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier, + imageIdentifierFieldMetadataUniversalIdentifier: null, }; const { @@ -108,7 +119,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre existingFlatObjectMetadataMaps, sourceFlatObjectMetadata: flatObjectMetadataToCreate, workspaceId, - workspaceCustomApplicationId, + flatApplication, existingFeatureFlagsMap, }); 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 index 1430a2f203e..da0cefc8802 100644 --- 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 @@ -76,24 +76,17 @@ export const fromObjectMetadataEntityToFlatObjectMetadata = ({ viewIds: objectMetadataEntity.views.map(({ id }) => id), indexMetadataIds: objectMetadataEntity.indexMetadatas.map(({ id }) => id), fieldIds: objectMetadataEntity.fields.map(({ id }) => id), - __universal: { - universalIdentifier: - objectMetadataEntityWithoutRelations.universalIdentifier, - applicationUniversalIdentifier, - labelIdentifierFieldMetadataUniversalIdentifier, - imageIdentifierFieldMetadataUniversalIdentifier, - fieldUniversalIdentifiers: objectMetadataEntity.fields.map( - ({ universalIdentifier }) => universalIdentifier, - ), - indexMetadataUniversalIdentifiers: - objectMetadataEntity.indexMetadatas.map( - ({ universalIdentifier }) => universalIdentifier, - ), - viewUniversalIdentifiers: objectMetadataEntity.views.map( - ({ universalIdentifier }) => universalIdentifier, - ), - standardOverrides: objectMetadataEntityWithoutRelations.standardOverrides, - duplicateCriteria: objectMetadataEntityWithoutRelations.duplicateCriteria, - }, + applicationUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier, + imageIdentifierFieldMetadataUniversalIdentifier, + fieldUniversalIdentifiers: objectMetadataEntity.fields.map( + ({ universalIdentifier }) => universalIdentifier, + ), + indexMetadataUniversalIdentifiers: objectMetadataEntity.indexMetadatas.map( + ({ universalIdentifier }) => universalIdentifier, + ), + viewUniversalIdentifiers: objectMetadataEntity.views.map( + ({ universalIdentifier }) => universalIdentifier, + ), }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts index 6bb9ee06e48..39f91d7e183 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts @@ -8,7 +8,7 @@ type PageLayoutWidgetConfiguration = PageLayoutWidgetEntity['configuration']; type UniversalPageLayoutWidgetConfiguration = NonNullable< FlatPageLayoutWidget['__universal'] ->['configuration']; +>['universalConfiguration']; // Field metadata IDs in widget configurations don't need to reference existing entities const getFieldMetadataUniversalIdentifier = ({ diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts index 21c95e751fc..e036b7b20b0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts @@ -85,10 +85,7 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({ applicationUniversalIdentifier, pageLayoutTabUniversalIdentifier, objectMetadataUniversalIdentifier, - gridPosition: pageLayoutWidgetEntityWithoutRelations.gridPosition, - configuration: configurationWithUniversalIdentifiers, - conditionalDisplay: - pageLayoutWidgetEntityWithoutRelations.conditionalDisplay, + universalConfiguration: configurationWithUniversalIdentifiers, }, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts index be17c2083d2..f61f2ed0eca 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util.ts @@ -129,7 +129,6 @@ export const fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredic roleUniversalIdentifier, workspaceMemberFieldMetadataUniversalIdentifier, rowLevelPermissionPredicateGroupUniversalIdentifier, - value: rowLevelPermissionPredicateEntityWithoutRelations.value, }, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts index 88421d3d6a1..0f8d4df1db7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts @@ -81,7 +81,6 @@ export const fromViewFilterEntityToFlatViewFilter = ({ fieldMetadataUniversalIdentifier, viewFilterGroupUniversalIdentifier, viewUniversalIdentifier, - value: viewFilterEntityWithoutRelations.value, }, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts index 0bc5535e77e..10d268b7621 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util.ts @@ -37,12 +37,6 @@ export const fromLogicFunctionEntityToFlatLogicFunction = ({ __universal: { universalIdentifier: logicFunctionEntity.universalIdentifier, applicationUniversalIdentifier, - publishedVersions: logicFunctionEntity.publishedVersions, - toolInputSchema: logicFunctionEntity.toolInputSchema, - cronTriggerSettings: logicFunctionEntity.cronTriggerSettings, - databaseEventTriggerSettings: - logicFunctionEntity.databaseEventTriggerSettings, - httpRouteTriggerSettings: logicFunctionEntity.httpRouteTriggerSettings, }, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts index 212e1f0c62e..2dff44ddb5e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts @@ -10,6 +10,7 @@ export enum ObjectMetadataExceptionCode { INVALID_OBJECT_INPUT = 'INVALID_OBJECT_INPUT', OBJECT_MUTATION_NOT_ALLOWED = 'OBJECT_MUTATION_NOT_ALLOWED', OBJECT_ALREADY_EXISTS = 'OBJECT_ALREADY_EXISTS', + APPLICATION_NOT_FOUND = 'APPLICATION_NOT_FOUND', MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD = 'MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD', INVALID_ORM_OUTPUT = 'INVALID_ORM_OUTPUT', INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', @@ -28,6 +29,8 @@ const getObjectMetadataExceptionUserFriendlyMessage = ( return msg`This object cannot be modified.`; case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS: return msg`An object with this name already exists.`; + case ObjectMetadataExceptionCode.APPLICATION_NOT_FOUND: + return msg`Application not found.`; case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD: return msg`Custom object is missing a label identifier field.`; case ObjectMetadataExceptionCode.INVALID_ORM_OUTPUT: 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 cbe1a35773c..20e8b60f739 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 @@ -331,11 +331,24 @@ export class ObjectMetadataService extends TypeOrmQueryService + Pick< + FlatObjectMetadata, + | 'id' + | 'applicationId' + | 'universalIdentifier' + | 'applicationUniversalIdentifier' + > >; skipNameField?: boolean; }; @@ -26,7 +32,12 @@ export type DefaultFlatFieldForCustomObjectMaps = ReturnType< // This could be replaced totally by an import schema + its transpilation when it's ready export const buildDefaultFlatFieldMetadatasForCustomObject = ({ workspaceId, - flatObjectMetadata: { id: objectMetadataId, applicationId }, + flatObjectMetadata: { + id: objectMetadataId, + applicationId, + applicationUniversalIdentifier, + universalIdentifier: objectMetadataUniversalIdentifier, + }, skipNameField = false, }: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => { const createdAt = new Date().toISOString(); @@ -65,6 +76,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const nameFieldId = skipNameField ? null : v4(); @@ -105,6 +126,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const createdAtFieldId = v4(); @@ -142,6 +173,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const updatedAtFieldId = v4(); @@ -179,6 +220,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const deletedAtFieldId = v4(); @@ -216,6 +267,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const createdByFieldId = v4(); @@ -252,6 +313,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const updatedByFieldId = v4(); @@ -288,6 +359,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const positionFieldId = v4(); @@ -325,6 +406,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ settings: null, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const searchVectorFieldId = v4(); @@ -367,6 +458,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ }, morphId: null, applicationId, + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; return { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts index 949cbe6223a..6fdd60039a6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts @@ -5,6 +5,7 @@ import { capitalize, isDefined } from 'twenty-shared/utils'; import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; @@ -53,7 +54,7 @@ export type BuildDefaultRelationFieldsForCustomObjectArgs = { existingFlatObjectMetadataMaps: FlatEntityMaps; workspaceId: string; sourceFlatObjectMetadata: FlatObjectMetadata; - workspaceCustomApplicationId: string; + flatApplication: FlatApplication; }; type SourceAndTargetFlatFieldMetadatasRecord = { @@ -71,7 +72,7 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({ existingFlatObjectMetadataMaps, sourceFlatObjectMetadata, workspaceId, - workspaceCustomApplicationId, + flatApplication, }: BuildDefaultRelationFieldsForCustomObjectArgs): SourceAndTargetFlatFieldMetadatasRecord => { const objectIdByNameSingular = Object.values( existingFlatObjectMetadataMaps.byId, @@ -156,7 +157,7 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({ ? FieldMetadataType.MORPH_RELATION : FieldMetadataType.RELATION, workspaceId, - workspaceCustomApplicationId, + flatApplication, sourceFlatObjectMetadataJoinColumnName: joinColumnName, morphId, targetFieldName: fieldName, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts index b2e12bd8ab2..b86a09af708 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts @@ -43,6 +43,7 @@ export const objectMetadataGraphqlApiExceptionHandler = ( case ObjectMetadataExceptionCode.INVALID_ORM_OUTPUT: throw new InternalServerError(error); case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD: + case ObjectMetadataExceptionCode.APPLICATION_NOT_FOUND: throw error; default: { return assertUnreachable(error.code); 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 b97ce00a2e3..c0e9c760eca 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 @@ -123,6 +123,12 @@ describe('WorkspaceEntityManager', () => { duplicateCriteria: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + applicationUniversalIdentifier: 'test-application-id', + fieldUniversalIdentifiers: ['field-id'], + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier: null, + imageIdentifierFieldMetadataUniversalIdentifier: null, }; (getObjectMetadataFromEntityTarget as jest.Mock).mockReturnValue( @@ -162,6 +168,16 @@ describe('WorkspaceEntityManager', () => { relationTargetObjectMetadataId: null, morphId: null, applicationId: 'application-id', + applicationUniversalIdentifier: 'application-id', + objectMetadataUniversalIdentifier: 'test-entity-id', + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; const flatObjectMetadataMaps: FlatEntityMaps = { diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/__tests__/workspace.repository.spec.ts b/packages/twenty-server/src/engine/twenty-orm/repository/__tests__/workspace.repository.spec.ts index cdeff22aae0..4affc8476e6 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/__tests__/workspace.repository.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/__tests__/workspace.repository.spec.ts @@ -97,6 +97,16 @@ describe('WorkspaceRepository', () => { kanbanAggregateOperationViewIds: [], viewFieldIds: [], mainGroupByFieldMetadataViewIds: [], + applicationUniversalIdentifier: 'application-id', + objectMetadataUniversalIdentifier: 'test-metadata-id', + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; mockInternalContext = { diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-column-name-to-field-metadata-id.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-column-name-to-field-metadata-id.util.spec.ts index b818fab63cb..ae19329d145 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-column-name-to-field-metadata-id.util.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-column-name-to-field-metadata-id.util.spec.ts @@ -8,39 +8,44 @@ import { getColumnNameToFieldMetadataIdMap } from 'src/engine/twenty-orm/utils/g describe('getColumnNameToFieldMetadataIdMap', () => { const createMockFlatObjectMetadata = ( fieldIds: string[], - ): FlatObjectMetadata => - ({ - id: 'test-object-id', - nameSingular: 'test', - namePlural: 'tests', - labelSingular: 'Test', - labelPlural: 'Tests', - icon: 'IconTest', - targetTableName: 'test', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: false, - isSearchable: false, - workspaceId: 'test-workspace-id', - universalIdentifier: 'test-object-id', - indexMetadataIds: [], - fieldIds, - viewIds: [], - applicationId: 'test-application-id', - isLabelSyncedWithName: false, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - shortcut: null, - description: null, - standardOverrides: null, - isUIReadOnly: false, - standardId: null, - labelIdentifierFieldMetadataId: null, - imageIdentifierFieldMetadataId: null, - duplicateCriteria: null, - }) as FlatObjectMetadata; + ): FlatObjectMetadata => ({ + id: 'test-object-id', + nameSingular: 'test', + namePlural: 'tests', + labelSingular: 'Test', + labelPlural: 'Tests', + icon: 'IconTest', + targetTableName: 'test', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: false, + isSearchable: false, + workspaceId: 'test-workspace-id', + universalIdentifier: 'test-object-id', + indexMetadataIds: [], + fieldIds, + viewIds: [], + applicationId: 'test-application-id', + isLabelSyncedWithName: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + shortcut: null, + description: null, + standardOverrides: null, + isUIReadOnly: false, + standardId: null, + labelIdentifierFieldMetadataId: null, + imageIdentifierFieldMetadataId: null, + duplicateCriteria: null, + applicationUniversalIdentifier: 'test-application-id', + fieldUniversalIdentifiers: fieldIds, + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier: null, + imageIdentifierFieldMetadataUniversalIdentifier: null, + }); const createMockFlatFieldMetadata = ( id: string, diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-field-metadata-id-to-column-names-map.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-field-metadata-id-to-column-names-map.util.spec.ts index bbed1e77b8a..461fa4c3f65 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-field-metadata-id-to-column-names-map.util.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-field-metadata-id-to-column-names-map.util.spec.ts @@ -8,39 +8,44 @@ import { getFieldMetadataIdToColumnNamesMap } from 'src/engine/twenty-orm/utils/ describe('getFieldMetadataIdToColumnNamesMap', () => { const createMockFlatObjectMetadata = ( fieldIds: string[], - ): FlatObjectMetadata => - ({ - id: 'test-object-id', - nameSingular: 'test', - namePlural: 'tests', - labelSingular: 'Test', - labelPlural: 'Tests', - icon: 'IconTest', - targetTableName: 'test', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: false, - isSearchable: false, - workspaceId: 'test-workspace-id', - universalIdentifier: 'test-object-id', - indexMetadataIds: [], - fieldIds, - viewIds: [], - applicationId: 'test-application-id', - isLabelSyncedWithName: false, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - shortcut: null, - description: null, - standardOverrides: null, - isUIReadOnly: false, - standardId: null, - labelIdentifierFieldMetadataId: null, - imageIdentifierFieldMetadataId: null, - duplicateCriteria: null, - }) as FlatObjectMetadata; + ): FlatObjectMetadata => ({ + id: 'test-object-id', + nameSingular: 'test', + namePlural: 'tests', + labelSingular: 'Test', + labelPlural: 'Tests', + icon: 'IconTest', + targetTableName: 'test', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: false, + isSearchable: false, + workspaceId: 'test-workspace-id', + universalIdentifier: 'test-object-id', + indexMetadataIds: [], + fieldIds, + viewIds: [], + applicationId: 'test-application-id', + isLabelSyncedWithName: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + shortcut: null, + description: null, + standardOverrides: null, + isUIReadOnly: false, + standardId: null, + labelIdentifierFieldMetadataId: null, + imageIdentifierFieldMetadataId: null, + duplicateCriteria: null, + applicationUniversalIdentifier: 'test-application-id', + fieldUniversalIdentifiers: fieldIds, + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier: null, + imageIdentifierFieldMetadataUniversalIdentifier: null, + }); const createMockFlatFieldMetadata = ( id: string, diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/is-record-matching-rls-row-level-permission-predicate.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/is-record-matching-rls-row-level-permission-predicate.util.spec.ts index 98bdb3d48cf..0a7e7bd66d5 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/is-record-matching-rls-row-level-permission-predicate.util.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/is-record-matching-rls-row-level-permission-predicate.util.spec.ts @@ -8,39 +8,44 @@ import { isRecordMatchingRLSRowLevelPermissionPredicate } from 'src/engine/twent describe('isRecordMatchingRLSRowLevelPermissionPredicate', () => { const createMockFlatObjectMetadata = ( fieldIds: string[], - ): FlatObjectMetadata => - ({ - id: 'test-object-id', - nameSingular: 'test', - namePlural: 'tests', - labelSingular: 'Test', - labelPlural: 'Tests', - icon: 'IconTest', - targetTableName: 'test', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: false, - isSearchable: false, - workspaceId: 'test-workspace-id', - universalIdentifier: 'test-object-id', - indexMetadataIds: [], - fieldIds, - viewIds: [], - applicationId: 'test-application-id', - isLabelSyncedWithName: false, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - shortcut: null, - description: null, - standardOverrides: null, - isUIReadOnly: false, - standardId: null, - labelIdentifierFieldMetadataId: null, - imageIdentifierFieldMetadataId: null, - duplicateCriteria: null, - }) as FlatObjectMetadata; + ): FlatObjectMetadata => ({ + id: 'test-object-id', + nameSingular: 'test', + namePlural: 'tests', + labelSingular: 'Test', + labelPlural: 'Tests', + icon: 'IconTest', + targetTableName: 'test', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: false, + isSearchable: false, + workspaceId: 'test-workspace-id', + universalIdentifier: 'test-object-id', + indexMetadataIds: [], + fieldIds, + viewIds: [], + applicationId: 'test-application-id', + isLabelSyncedWithName: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + shortcut: null, + description: null, + standardOverrides: null, + isUIReadOnly: false, + standardId: null, + labelIdentifierFieldMetadataId: null, + imageIdentifierFieldMetadataId: null, + duplicateCriteria: null, + applicationUniversalIdentifier: 'test-application-id', + fieldUniversalIdentifiers: fieldIds, + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier: null, + imageIdentifierFieldMetadataUniversalIdentifier: null, + }); const createMockFlatFieldMetadata = ( id: string, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-field-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-field-flat-metadata.util.ts index 192b3d280c5..c2d3725b8fe 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-field-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-field-flat-metadata.util.ts @@ -100,5 +100,16 @@ export const createStandardFieldFlatMetadata = < mainGroupByFieldMetadataViewIds: [], createdAt: now, updatedAt: now, + applicationUniversalIdentifier: twentyStandardApplicationId, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS[objectName].universalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: null, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-relation-field-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-relation-field-flat-metadata.util.ts index da570b23773..0dafe4acd90 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-relation-field-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-relation-field-flat-metadata.util.ts @@ -83,6 +83,10 @@ export const createStandardRelationFieldFlatMetadata = < const targetFieldIds = standardObjectMetadataRelatedEntityIds[targetObjectName].fields; + const targetObjectFields = STANDARD_OBJECTS[targetObjectName].fields; + const targetFieldDefinition = + targetObjectFields[targetFieldName as keyof typeof targetObjectFields]; + return { id: fieldIds[fieldName as keyof typeof fieldIds].id, universalIdentifier: fieldDefinition.universalIdentifier, @@ -117,5 +121,18 @@ export const createStandardRelationFieldFlatMetadata = < mainGroupByFieldMetadataViewIds: [], createdAt: now, updatedAt: now, + applicationUniversalIdentifier: twentyStandardApplicationId, + objectMetadataUniversalIdentifier: + STANDARD_OBJECTS[objectName].universalIdentifier, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECTS[targetObjectName].universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: + targetFieldDefinition.universalIdentifier, + viewFilterUniversalIdentifiers: [], + viewFieldUniversalIdentifiers: [], + kanbanAggregateOperationViewUniversalIdentifiers: [], + calendarViewUniversalIdentifiers: [], + mainGroupByFieldMetadataViewUniversalIdentifiers: [], + universalSettings: null, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts index eb0de670a7c..e63eda1c44b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts @@ -1,4 +1,5 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant'; import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type'; import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type'; import { type StandardBuilderArgs } from 'src/engine/workspace-manager/twenty-standard-application/types/metadata-standard-buillder-args.type'; @@ -52,42 +53,56 @@ export const createStandardObjectFlatMetadata = < standardObjectMetadataRelatedEntityIds, twentyStandardApplicationId, now, -}: CreateStandardObjectArgs): FlatObjectMetadata => ({ - universalIdentifier, - standardId: universalIdentifier, - applicationId: twentyStandardApplicationId, - workspaceId, - nameSingular, - namePlural, - labelSingular, - labelPlural, - description, - icon, - isCustom: false, - isRemote: false, - isActive: true, - isSystem, - isSearchable, - isAuditLogged, - isUIReadOnly, - isLabelSyncedWithName: false, - standardOverrides: null, - duplicateCriteria, - shortcut, - labelIdentifierFieldMetadataId: - standardObjectMetadataRelatedEntityIds[nameSingular].fields[ +}: CreateStandardObjectArgs): FlatObjectMetadata => { + const labelIdentifierFieldMetadataUniversalIdentifier = + // @ts-expect-error ignore + STANDARD_OBJECTS[nameSingular as keyof typeof STANDARD_OBJECTS].fields[ labelIdentifierFieldMetadataName - ].id, - imageIdentifierFieldMetadataId: imageIdentifierFieldMetadataName - ? standardObjectMetadataRelatedEntityIds[nameSingular].fields[ - imageIdentifierFieldMetadataName - ].id - : null, - targetTableName: 'DEPRECATED', - fieldIds: [], - indexMetadataIds: [], - viewIds: [], - createdAt: now, - updatedAt: now, - id: standardObjectMetadataRelatedEntityIds[nameSingular].id, -}); + ].universalIdentifier; + + return { + universalIdentifier, + standardId: universalIdentifier, + applicationId: twentyStandardApplicationId, + workspaceId, + nameSingular, + namePlural, + labelSingular, + labelPlural, + description, + icon, + isCustom: false, + isRemote: false, + isActive: true, + isSystem, + isSearchable, + isAuditLogged, + isUIReadOnly, + isLabelSyncedWithName: false, + standardOverrides: null, + duplicateCriteria, + shortcut, + labelIdentifierFieldMetadataId: + standardObjectMetadataRelatedEntityIds[nameSingular].fields[ + labelIdentifierFieldMetadataName + ].id, + imageIdentifierFieldMetadataId: imageIdentifierFieldMetadataName + ? standardObjectMetadataRelatedEntityIds[nameSingular].fields[ + imageIdentifierFieldMetadataName + ].id + : null, + targetTableName: 'DEPRECATED', + fieldIds: [], + indexMetadataIds: [], + viewIds: [], + createdAt: now, + updatedAt: now, + id: standardObjectMetadataRelatedEntityIds[nameSingular].id, + applicationUniversalIdentifier: twentyStandardApplicationId, + fieldUniversalIdentifiers: [], + viewUniversalIdentifiers: [], + indexMetadataUniversalIdentifiers: [], + labelIdentifierFieldMetadataUniversalIdentifier, + imageIdentifierFieldMetadataUniversalIdentifier: null, + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-by-metadata-name.constant.ts deleted file mode 100644 index 35dfc3215af..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-by-metadata-name.constant.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { type AllMetadataName } from 'twenty-shared/metadata'; - -import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type'; -import { type ExtractJsonbProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type'; - -export const ALL_JSONB_PROPERTIES_BY_METADATA_NAME = { - fieldMetadata: { - defaultValue: 'defaultValue', - standardOverrides: 'standardOverrides', - options: 'options', - settings: 'settings', - }, - objectMetadata: { - standardOverrides: 'standardOverrides', - duplicateCriteria: 'duplicateCriteria', - }, - view: {}, - viewField: {}, - viewGroup: {}, - viewFilter: { value: 'value' }, - viewFilterGroup: {}, - index: {}, - role: {}, - roleTarget: {}, - rowLevelPermissionPredicate: { value: 'value' }, - rowLevelPermissionPredicateGroup: {}, - logicFunction: { - cronTriggerSettings: 'cronTriggerSettings', - databaseEventTriggerSettings: 'databaseEventTriggerSettings', - httpRouteTriggerSettings: 'httpRouteTriggerSettings', - publishedVersions: 'publishedVersions', - toolInputSchema: 'toolInputSchema', - }, - webhook: {}, - agent: { - responseFormat: 'responseFormat', - modelConfiguration: 'modelConfiguration', - }, - skill: {}, - pageLayout: {}, - pageLayoutTab: {}, - pageLayoutWidget: { - gridPosition: 'gridPosition', - configuration: 'configuration', - conditionalDisplay: 'conditionalDisplay', - }, - commandMenuItem: {}, - navigationMenuItem: {}, - frontComponent: {}, -} as const satisfies { - [P in AllMetadataName]: { - [K in ExtractJsonbProperties>]: K; - }; -}; - -export type AllJsonbPropertiesForMetadataName = - keyof (typeof ALL_JSONB_PROPERTIES_BY_METADATA_NAME)[T]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant.ts new file mode 100644 index 00000000000..a46c460bd9a --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant.ts @@ -0,0 +1,42 @@ +import { type AllMetadataName } from 'twenty-shared/metadata'; + +import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type'; +import { type ExtractJsonbProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type'; + +export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = { + fieldMetadata: { + settings: 'settings', + }, + objectMetadata: {}, + view: {}, + viewField: {}, + viewGroup: {}, + viewFilter: {}, + viewFilterGroup: {}, + index: {}, + role: {}, + roleTarget: {}, + rowLevelPermissionPredicate: {}, + rowLevelPermissionPredicateGroup: {}, + logicFunction: {}, + webhook: {}, + agent: {}, + skill: {}, + pageLayout: {}, + pageLayoutTab: {}, + pageLayoutWidget: { + configuration: 'configuration', + }, + commandMenuItem: {}, + navigationMenuItem: {}, + frontComponent: {}, +} as const satisfies { + [P in AllMetadataName]: Partial<{ + [K in ExtractJsonbProperties>]: K; + }>; +}; + +export type AllJsonbPropertiesWithSerializedPropertiesForMetadataName< + T extends AllMetadataName, +> = + keyof (typeof ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME)[T]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/contains-serialized-relation.type-test.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/contains-serialized-relation.type-test.ts new file mode 100644 index 00000000000..2232377ecc4 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/contains-serialized-relation.type-test.ts @@ -0,0 +1,146 @@ +import { type Equal, type Expect } from 'twenty-shared/testing'; +import { type SerializedRelation } from 'twenty-shared/types'; + +import { type ContainsSerializedRelation } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/contains-serialized-relation.type'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +type EmptyObject = {}; + +// ContainsSerializedRelation checks for SerializedRelation in object properties +// It recurses into nested objects and arrays, but stops at primitives + +// Direct property tests +// eslint-disable-next-line unused-imports/no-unused-vars +type DirectPropertyAssertions = [ + // Direct SerializedRelation property + Expect< + Equal, true> + >, + + // Nullable SerializedRelation property + Expect< + Equal< + ContainsSerializedRelation<{ targetId: SerializedRelation | null }>, + true + > + >, + + // Optional SerializedRelation property + Expect< + Equal, true> + >, + + // Array of SerializedRelation + Expect< + Equal, true> + >, + + // Plain properties only - no SerializedRelation + Expect< + Equal, false> + >, + + // Empty object + Expect, false>>, +]; + +// Nested object tests - should recurse +// eslint-disable-next-line unused-imports/no-unused-vars +type NestedObjectAssertions = [ + // Nested object with SerializedRelation + Expect< + Equal< + ContainsSerializedRelation<{ + nested: { targetId: SerializedRelation }; + }>, + true + > + >, + + // Deeply nested SerializedRelation + Expect< + Equal< + ContainsSerializedRelation<{ + level1: { level2: { level3: { id: SerializedRelation } } }; + }>, + true + > + >, + + // Nested object without SerializedRelation + Expect< + Equal< + ContainsSerializedRelation<{ + nested: { name: string; count: number }; + }>, + false + > + >, +]; + +// Nested array tests - should recurse +// eslint-disable-next-line unused-imports/no-unused-vars +type NestedArrayAssertions = [ + // Array of objects with SerializedRelation + Expect< + Equal< + ContainsSerializedRelation<{ + items: { targetId: SerializedRelation }[]; + }>, + true + > + >, + + // 2D array of SerializedRelation + Expect< + Equal, true> + >, + + // Array of plain objects + Expect< + Equal< + ContainsSerializedRelation<{ + items: { name: string }[]; + }>, + false + > + >, + + // 2D array of strings + Expect, false>>, +]; + +// Primitive types - should return false (not objects) +// eslint-disable-next-line unused-imports/no-unused-vars +type PrimitiveAssertions = [ + Expect, false>>, + Expect, false>>, + Expect, false>>, + Expect, false>>, + Expect, false>>, +]; + +// Real-world tests +// eslint-disable-next-line unused-imports/no-unused-vars +type RealWorldAssertions = [ + // Settings with SerializedRelation + Expect< + Equal< + ContainsSerializedRelation<{ + relationType?: string; + junctionTargetFieldId?: SerializedRelation; + }>, + true + > + >, + + // Workflow config with nested SerializedRelation in array + Expect< + Equal< + ContainsSerializedRelation<{ + steps: { assigneeId: SerializedRelation; action: string }[]; + }>, + true + > + >, +]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-field-metadata.test-type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-field-metadata.test-type.ts index cabed4764d1..11adb61d6dc 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-field-metadata.test-type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-field-metadata.test-type.ts @@ -4,10 +4,8 @@ import { type HasAllProperties, } from 'twenty-shared/testing'; import { - type FieldMetadataDefaultOption, type FieldMetadataType, type FieldNumberVariant, - type LinkMetadata, type NullablePartial, type NumberDataType, type RelationOnDeleteAction, @@ -91,8 +89,9 @@ type UniversalFlatTransformationAssertions = [ >, ]; +// JSONB properties are now prefixed with 'universal' in UniversalFlatFieldMetadata type NarrowedTestCase = - UniversalFlatFieldMetadata['settings']; + UniversalFlatFieldMetadata['universalSettings']; type NarrowedExpectedResult = { relationType: RelationType; @@ -107,7 +106,7 @@ type NarrowedExpectedResult = { type SettingsTestCase = UniversalFlatFieldMetadata< FieldMetadataType.RELATION | FieldMetadataType.NUMBER | FieldMetadataType.TEXT ->['settings']; +>['universalSettings']; type SettingsExpectedResult = | { @@ -132,39 +131,8 @@ type SettingsExpectedResult = } | null; -type DefaultValueTestCase = UniversalFlatFieldMetadata< - | FieldMetadataType.RELATION - | FieldMetadataType.NUMBER - | FieldMetadataType.TEXT - | FieldMetadataType.LINKS - | FieldMetadataType.CURRENCY ->['defaultValue']; - -type DefaultValueExpectedResult = - | string - | number - | null - | { - amountMicros: string | null; - currencyCode: string | null; - __JsonbPropertyBrand__?: undefined; - } - | { - primaryLinkLabel: string | null; - primaryLinkUrl: string | null; - secondaryLinks: LinkMetadata[] | null; - __JsonbPropertyBrand__?: undefined; - }; - -type OptionsTestCase = - UniversalFlatFieldMetadata['options']; - -type OptionsExpectedResult = FieldMetadataDefaultOption[]; - // eslint-disable-next-line unused-imports/no-unused-vars type Assertions = [ Expect>, Expect>, - Expect>, - Expect>, ]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/contains-serialized-relation.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/contains-serialized-relation.type.ts new file mode 100644 index 00000000000..5964365db89 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/contains-serialized-relation.type.ts @@ -0,0 +1,24 @@ +import { + type IsEmptyObject, + type IsNever, + type IsSerializedRelation, +} from 'twenty-shared/types'; + +type ContainsSerializedRelationInner = T extends unknown + ? IsNever extends true + ? false + : unknown extends T + ? false + : IsSerializedRelation extends true + ? true + : T extends readonly (infer U)[] + ? ContainsSerializedRelationInner + : T extends object + ? IsEmptyObject extends true + ? false + : ContainsSerializedRelationInner + : false + : never; + +export type ContainsSerializedRelation = + true extends ContainsSerializedRelationInner ? true : false; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type.ts index 11d18aeeb71..a501c16b206 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type.ts @@ -1,12 +1,4 @@ -import { type JSONB_PROPERTY_BRAND } from './jsonb-property.type'; - -export type HasJsonbPropertyBrand = - typeof JSONB_PROPERTY_BRAND extends keyof T ? true : false; - -// Distributive check: returns `true` if any member of a union has the brand -type HasJsonbBrandInUnion = T extends unknown - ? HasJsonbPropertyBrand - : never; +import { type HasJsonbBrandInUnion } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand-in-union.type'; export type ExtractJsonbProperties = NonNullable< { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand-in-union.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand-in-union.type.ts new file mode 100644 index 00000000000..8d254168430 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand-in-union.type.ts @@ -0,0 +1,5 @@ +import { type HasJsonbPropertyBrand } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand.type'; + +export type HasJsonbBrandInUnion = T extends unknown + ? HasJsonbPropertyBrand + : never; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand.type.ts new file mode 100644 index 00000000000..96952958fe7 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand.type.ts @@ -0,0 +1,4 @@ +import { type JSONB_PROPERTY_BRAND } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type'; + +export type HasJsonbPropertyBrand = + typeof JSONB_PROPERTY_BRAND extends keyof T ? true : false; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type.ts index 8bc4ce5cbfa..a34f7aab0b3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type.ts @@ -7,7 +7,8 @@ import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-m import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type'; import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type'; import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; -import { type AllJsonbPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-by-metadata-name.constant'; +import { type AllJsonbPropertiesWithSerializedPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant'; +import { type ContainsSerializedRelation } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/contains-serialized-relation.type'; import { type FormatRecordSerializedRelationProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/format-record-serialized-relation-properties.type'; export type UniversalSyncableFlatEntity = Omit< @@ -19,6 +20,7 @@ export type UniversalSyncableFlatEntity = Omit< export type UniversalFlatEntityExtraProperties< TEntity extends SyncableEntity, + // Required to be passed for narrowed type TMetadataName extends AllMetadataName = FromMetadataEntityToMetadataName, > = AddSuffixToEntityOneToManyProperties & @@ -29,8 +31,13 @@ export type UniversalFlatEntityExtraProperties< > & { applicationUniversalIdentifier: string; } & { - [P in AllJsonbPropertiesForMetadataName & - keyof TEntity]: FormatRecordSerializedRelationProperties; + [P in AllJsonbPropertiesWithSerializedPropertiesForMetadataName & + keyof TEntity & + string as `universal${Capitalize

}`]: true extends ContainsSerializedRelation< + NonNullable + > + ? FormatRecordSerializedRelationProperties + : null; }; export type UniversalFlatEntityFrom< @@ -46,7 +53,7 @@ export type UniversalFlatEntityFrom< | ExtractEntityRelatedEntityProperties | Extract, keyof TEntity> | keyof CastRecordTypeOrmDatePropertiesToString - | AllJsonbPropertiesForMetadataName + | AllJsonbPropertiesWithSerializedPropertiesForMetadataName > & CastRecordTypeOrmDatePropertiesToString & UniversalFlatEntityExtraProperties; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/__snapshots__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/__snapshots__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts.snap index 2d065b31c6a..ed2ae33435b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/__snapshots__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/__snapshots__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts.snap @@ -1,13 +1,14 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a created entity 1`] = ` { "createdFlatEntityMaps": { "byId": { "field-id-1": { - "__universal": {}, "applicationId": "application-id-1", + "applicationUniversalIdentifier": "application-universal-identifier-1", "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": "2024-01-01T00:00:00.000Z", "defaultValue": null, "description": "default flat field metadata description", @@ -21,22 +22,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a crea "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "flat field metadata label", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": null, "name": "flatFieldMetadataName", "objectMetadataId": "object-metadata-id-1", + "objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1", "options": null, "relationTargetFieldMetadataId": null, + "relationTargetFieldMetadataUniversalIdentifier": null, "relationTargetObjectMetadataId": null, + "relationTargetObjectMetadataUniversalIdentifier": null, "settings": null, "standardId": null, "standardOverrides": null, "type": "TEXT", "universalIdentifier": "universal-identifier-1", + "universalSettings": null, "updatedAt": "2024-01-01T00:00:00.000Z", "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": "workspace-id-1", }, }, @@ -70,9 +79,10 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele "deletedFlatEntityMaps": { "byId": { "field-id-1": { - "__universal": {}, "applicationId": "application-id-1", + "applicationUniversalIdentifier": "application-universal-identifier-1", "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": "2024-01-01T00:00:00.000Z", "defaultValue": null, "description": "default flat field metadata description", @@ -86,22 +96,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "flat field metadata label", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": null, "name": "flatFieldMetadataName", "objectMetadataId": "object-metadata-id-1", + "objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1", "options": null, "relationTargetFieldMetadataId": null, + "relationTargetFieldMetadataUniversalIdentifier": null, "relationTargetObjectMetadataId": null, + "relationTargetObjectMetadataUniversalIdentifier": null, "settings": null, "standardId": null, "standardOverrides": null, "type": "TEXT", "universalIdentifier": "universal-identifier-1", + "universalSettings": null, "updatedAt": "2024-01-01T00:00:00.000Z", "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": "workspace-id-1", }, }, @@ -153,9 +171,10 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create "createdFlatEntityMaps": { "byId": { "field-id-3": { - "__universal": {}, "applicationId": "application-id-1", + "applicationUniversalIdentifier": "application-universal-identifier-1", "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": "2024-01-01T00:00:00.000Z", "defaultValue": null, "description": "default flat field metadata description", @@ -169,22 +188,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "flat field metadata label", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": null, "name": "flatFieldMetadataName", "objectMetadataId": "object-metadata-id-1", + "objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1", "options": null, "relationTargetFieldMetadataId": null, + "relationTargetFieldMetadataUniversalIdentifier": null, "relationTargetObjectMetadataId": null, + "relationTargetObjectMetadataUniversalIdentifier": null, "settings": null, "standardId": null, "standardOverrides": null, "type": "TEXT", "universalIdentifier": "universal-identifier-3", + "universalSettings": null, "updatedAt": "2024-01-01T00:00:00.000Z", "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": "workspace-id-1", }, }, @@ -200,9 +227,10 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create "deletedFlatEntityMaps": { "byId": { "field-id-2": { - "__universal": {}, "applicationId": "application-id-1", + "applicationUniversalIdentifier": "application-universal-identifier-1", "calendarViewIds": [], + "calendarViewUniversalIdentifiers": [], "createdAt": "2024-01-01T00:00:00.000Z", "defaultValue": null, "description": "default flat field metadata description", @@ -216,22 +244,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create "isUIReadOnly": false, "isUnique": false, "kanbanAggregateOperationViewIds": [], + "kanbanAggregateOperationViewUniversalIdentifiers": [], "label": "flat field metadata label", "mainGroupByFieldMetadataViewIds": [], + "mainGroupByFieldMetadataViewUniversalIdentifiers": [], "morphId": null, "name": "flatFieldMetadataName", "objectMetadataId": "object-metadata-id-1", + "objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1", "options": null, "relationTargetFieldMetadataId": null, + "relationTargetFieldMetadataUniversalIdentifier": null, "relationTargetObjectMetadataId": null, + "relationTargetObjectMetadataUniversalIdentifier": null, "settings": null, "standardId": null, "standardOverrides": null, "type": "TEXT", "universalIdentifier": "universal-identifier-2", + "universalSettings": null, "updatedAt": "2024-01-01T00:00:00.000Z", "viewFieldIds": [], + "viewFieldUniversalIdentifiers": [], "viewFilterIds": [], + "viewFilterUniversalIdentifiers": [], "workspaceId": "workspace-id-1", }, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts index c62f1d82713..069a14e3ea4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/utils/__tests__/flat-entity-deleted-created-updated-matrix-dispatcher.util.spec.ts @@ -30,6 +30,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { applicationId: 'application-id-1', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', }), ], metadataName: 'fieldMetadata', @@ -52,6 +56,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { applicationId: 'application-id-1', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', }), ], to: [], @@ -76,6 +84,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', isActive: false, + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', }), ], to: [ @@ -89,6 +101,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', isActive: true, + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', }), ], metadataName: 'fieldMetadata', @@ -111,6 +127,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { applicationId: 'application-id-1', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', isActive: true, }), getFlatFieldMetadataMock({ @@ -122,6 +142,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { applicationId: 'application-id-1', createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', }), ], to: [ @@ -133,6 +157,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { workspaceId: 'workspace-id-1', applicationId: 'application-id-1', createdAt: '2024-01-01T00:00:00.000Z', + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', updatedAt: '2024-01-01T00:00:00.000Z', isActive: false, }), @@ -144,6 +172,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { workspaceId: 'workspace-id-1', applicationId: 'application-id-1', createdAt: '2024-01-01T00:00:00.000Z', + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', updatedAt: '2024-01-01T00:00:00.000Z', }), ], @@ -167,6 +199,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => { workspaceId: 'workspace-id-1', applicationId: 'application-id-1', createdAt: '2024-01-01T00:00:00.000Z', + applicationUniversalIdentifier: + 'application-universal-identifier-1', + objectMetadataUniversalIdentifier: + 'object-metadata-universal-identifier-1', updatedAt: '2024-01-01T00:00:00.000Z', }), ], diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/delete-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/delete-field-action-handler.service.ts index 5cbe386f817..211fc97dfe3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/delete-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/delete-field-action-handler.service.ts @@ -1,17 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { isDefined } from 'twenty-shared/utils'; - import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; import { type DeleteFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action'; -import { - WorkspaceMigrationActionExecutionException, - WorkspaceMigrationActionExecutionExceptionCode, -} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/exceptions/workspace-migration-action-execution.exception'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/generate-column-definitions.util'; import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/get-workspace-schema-context-for-migration.util'; @@ -70,19 +64,9 @@ export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerAct universalIdentifier, }); - const objectMetadataUniversalIdentifier = - fieldMetadata.__universal?.objectMetadataUniversalIdentifier; - - if (!isDefined(objectMetadataUniversalIdentifier)) { - throw new WorkspaceMigrationActionExecutionException({ - message: `objectMetadataUniversalIdentifier is not defined for field metadata ${universalIdentifier}`, - code: WorkspaceMigrationActionExecutionExceptionCode.NOT_SUPPORTED, - }); - } - const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({ flatEntityMaps: flatObjectMetadataMaps, - universalIdentifier: objectMetadataUniversalIdentifier, + universalIdentifier: fieldMetadata.objectMetadataUniversalIdentifier, }); const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts index 537c6a3093f..4a63b1c200b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts @@ -97,19 +97,10 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct flatEntityMaps: flatFieldMetadataMaps, }); - const objectMetadataUniversalIdentifier = - currentFlatFieldMetadata.__universal?.objectMetadataUniversalIdentifier; - - if (!isDefined(objectMetadataUniversalIdentifier)) { - throw new WorkspaceMigrationActionExecutionException({ - message: `objectMetadataUniversalIdentifier is not defined for field metadata ${entityId}`, - code: WorkspaceMigrationActionExecutionExceptionCode.NOT_SUPPORTED, - }); - } - const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({ flatEntityMaps: flatObjectMetadataMaps, - universalIdentifier: objectMetadataUniversalIdentifier, + universalIdentifier: + currentFlatFieldMetadata.objectMetadataUniversalIdentifier, }); const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ diff --git a/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts b/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts index d0bb4a0c698..665d6cf0d14 100644 --- a/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts +++ b/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts @@ -25,6 +25,7 @@ export const extractRecordIdsAndDatesAsExpectAny = ( if ( key.endsWith('Id') || + key.endsWith('UniversalIdentifier') || key === 'universalIdentifier' || key === 'id' || key === 'updatedAt' || diff --git a/packages/twenty-shared/src/types/IsEmptyObject.type.ts b/packages/twenty-shared/src/types/IsEmptyObject.type.ts new file mode 100644 index 00000000000..e4d7a2dd92d --- /dev/null +++ b/packages/twenty-shared/src/types/IsEmptyObject.type.ts @@ -0,0 +1,3 @@ +export type IsEmptyObject = [keyof T] extends [never] + ? true + : false; diff --git a/packages/twenty-shared/src/types/IsNever.type.ts b/packages/twenty-shared/src/types/IsNever.type.ts new file mode 100644 index 00000000000..082b19bddca --- /dev/null +++ b/packages/twenty-shared/src/types/IsNever.type.ts @@ -0,0 +1 @@ +export type IsNever = [T] extends [never] ? true : false; diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index f70471c9805..7c8b71415a5 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -105,8 +105,10 @@ export { FILTERABLE_FIELD_TYPES } from './FilterableFieldType'; export { FirstDayOfTheWeek } from './FirstDayOfTheWeek'; export type { FromTo } from './FromToType'; export { HTTPMethod } from './HttpMethod'; +export type { IsEmptyObject } from './IsEmptyObject.type'; export type { IsEmptyRecord } from './IsEmptyRecord.type'; export type { IsExactly } from './IsExactly'; +export type { IsNever } from './IsNever.type'; export type { IsSerializedRelation } from './IsSerializedRelation.type'; export type { LogicFunctionEvent } from './LogicFunctionEvent'; export { MessageParticipantRole } from './MessageParticipantRole';