From 1536ed343478ec46219a923b3648b7c2e8a68a00 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Mon, 21 Jul 2025 17:53:17 +0200 Subject: [PATCH] Deprecate ObjectMetadataInterface and improve entity typing (#13310) # Introduction Following `FieldMetadataInterface` deprecation in https://github.com/twentyhq/twenty/pull/13264 As for the previous PR will rename and remove all the file in a secondary PR to avoid conflicts and over loading this one ## Improvements Removed optional properties from the `objectMetadataEntity` model and added utils to retrieve test data ## Notes By touching to `ObjectMetadataDTO` I would have expected a twenty-front codegenerated types mutation, but it does not seem to be granular enough to null/undefined coercion --- .../__mocks__/mockPersonObjectMetadata.ts | 211 ++-- .../mocks/opportunity-field-maps.mock.ts | 907 +++++++++--------- .../compute-cursor-arg-filter.utils.spec.ts | 125 +-- .../mockObjectMetadataItemsWithFieldMaps.ts | 389 ++++---- .../object-record-changed-values.spec.ts | 6 +- .../open-api/utils/components.utils.ts | 2 +- .../engine/dataloaders/dataloader.service.ts | 4 +- .../field-metadata/field-metadata.resolver.ts | 17 +- .../interfaces/object-metadata.interface.ts | 29 +- ...data-entity-to-field-metadata-dto.util.ts} | 1 - ...data-entity-to-object-metadata-dto.util.ts | 28 + .../dtos/object-metadata.dto.ts | 6 +- .../hooks/before-update-one-object.hook.ts | 6 +- .../object-metadata/object-metadata.entity.ts | 25 +- ...object-metadata-related-records.service.ts | 6 +- .../factories/standard-object.factory.ts | 6 +- .../partial-object-metadata.interface.ts | 24 +- .../generate-fake-object-record-event.ts | 5 +- .../utils/generate-fake-object-record.ts | 3 +- .../database-event-trigger.listener.spec.ts | 50 +- .../get-object-metadata-entity.mock.ts | 48 + ...ect-metadata-item-with-fields-maps.mock.ts | 37 + .../agent/utils/agent-tool-test-utils.ts | 5 +- 23 files changed, 1031 insertions(+), 909 deletions(-) rename packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/{from-field-metadata-entity-to-fieldMetadata-dto.util.ts => from-field-metadata-entity-to-field-metadata-dto.util.ts} (88%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util.ts create mode 100644 packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts create mode 100644 packages/twenty-server/src/utils/__test__/get-object-metadata-item-with-fields-maps.mock.ts 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 6fc1faf0f..cbc495d4e 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 @@ -2,114 +2,115 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock'; +import { getMockObjectMetadataItemWithFieldsMaps } from 'src/utils/__test__/get-object-metadata-item-with-fields-maps.mock'; const workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419'; export const mockPersonObjectMetadataWithFieldMaps = ( duplicateCriteria: WorkspaceEntityDuplicateCriteria[], -): ObjectMetadataItemWithFieldMaps => ({ - id: '', - icon: 'Icon123', - standardId: '', - nameSingular: 'person', - namePlural: 'people', - labelSingular: 'Person', - labelPlural: 'People', - description: 'A person', - targetTableName: 'DEPRECATED', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: true, - isSearchable: true, - duplicateCriteria: duplicateCriteria, - labelIdentifierFieldMetadataId: '', - imageIdentifierFieldMetadataId: '', - workspaceId, - indexMetadatas: [], - fieldIdByName: { - name: 'name-id', - emails: 'emails-id', - linkedinLink: 'linkedinLink-id', - jobTitle: 'jobTitle-id', - }, - fieldIdByJoinColumnName: {}, - fieldsById: { - 'name-id': getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '', - id: 'name-id', - type: FieldMetadataType.FULL_NAME, - name: 'name', - label: 'Name', - defaultValue: { - lastName: "''", - firstName: "''", - }, - description: "Contact's name", - isCustom: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - 'emails-id': getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '', - id: 'emails-id', - type: FieldMetadataType.EMAILS, - name: 'emails', - label: 'Emails', - defaultValue: { - primaryEmail: "''", - additionalEmails: null, - }, - description: "Contact's Emails", - isCustom: false, - isNullable: true, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - 'linkedinLink-id': getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '', - id: 'linkedinLink-id', - type: FieldMetadataType.LINKS, - name: 'linkedinLink', - label: 'Linkedin', - defaultValue: { - primaryLinkUrl: "''", - secondaryLinks: [], - primaryLinkLabel: "''", - }, - description: "Contact's Linkedin account", - isCustom: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - 'jobTitle-id': getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '', - id: 'jobTitle-id', - type: FieldMetadataType.TEXT, - name: 'jobTitle', - label: 'Job Title', - defaultValue: "''", - description: "Contact's job title", - isCustom: false, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - }, -}); +) => + getMockObjectMetadataItemWithFieldsMaps({ + id: '', + icon: 'Icon123', + standardId: '', + nameSingular: 'person', + namePlural: 'people', + labelSingular: 'Person', + labelPlural: 'People', + description: 'A person', + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: true, + isSearchable: true, + duplicateCriteria: duplicateCriteria, + labelIdentifierFieldMetadataId: '', + imageIdentifierFieldMetadataId: '', + workspaceId, + indexMetadatas: [], + fieldIdByName: { + name: 'name-id', + emails: 'emails-id', + linkedinLink: 'linkedinLink-id', + jobTitle: 'jobTitle-id', + }, + fieldIdByJoinColumnName: {}, + fieldsById: { + 'name-id': getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '', + id: 'name-id', + type: FieldMetadataType.FULL_NAME, + name: 'name', + label: 'Name', + defaultValue: { + lastName: "''", + firstName: "''", + }, + description: "Contact's name", + isCustom: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + 'emails-id': getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '', + id: 'emails-id', + type: FieldMetadataType.EMAILS, + name: 'emails', + label: 'Emails', + defaultValue: { + primaryEmail: "''", + additionalEmails: null, + }, + description: "Contact's Emails", + isCustom: false, + isNullable: true, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + 'linkedinLink-id': getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '', + id: 'linkedinLink-id', + type: FieldMetadataType.LINKS, + name: 'linkedinLink', + label: 'Linkedin', + defaultValue: { + primaryLinkUrl: "''", + secondaryLinks: [], + primaryLinkLabel: "''", + }, + description: "Contact's Linkedin account", + isCustom: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + 'jobTitle-id': getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '', + id: 'jobTitle-id', + type: FieldMetadataType.TEXT, + name: 'jobTitle', + label: 'Job Title', + defaultValue: "''", + description: "Contact's job title", + isCustom: false, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + }, + }); diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/mocks/opportunity-field-maps.mock.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/mocks/opportunity-field-maps.mock.ts index 894f1f811..af78bf96d 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/mocks/opportunity-field-maps.mock.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/mocks/opportunity-field-maps.mock.ts @@ -5,467 +5,468 @@ import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metada import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock'; +import { getMockObjectMetadataItemWithFieldsMaps } from 'src/utils/__test__/get-object-metadata-item-with-fields-maps.mock'; const workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419'; const objectMetadataId = '20202020-6e2c-42f6-a83c-cc58d776af88'; -export const OPPORTUNITY_WITH_FIELDS_MAPS = { - id: objectMetadataId, - nameSingular: 'opportunity', - namePlural: 'opportunities', - labelSingular: 'Opportunity', - labelPlural: 'Opportunities', - description: 'An opportunity', - icon: 'IconTargetArrow', - targetTableName: 'DEPRECATED', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: true, - isSearchable: true, - labelIdentifierFieldMetadataId: '20202020-c2f1-4435-adca-22931f8b41b6', - imageIdentifierFieldMetadataId: null, - workspaceId, - indexMetadatas: [], - fieldsById: { - '20202020-c2f1-4435-adca-22931f8b41b6': getMockFieldMetadataEntity({ - id: '20202020-c2f1-4435-adca-22931f8b41b6', - workspaceId, - objectMetadataId, - type: FieldMetadataType.TEXT, - name: 'name', - label: 'Name', - defaultValue: "''", - description: 'The opportunity name', - icon: 'IconTargetArrow', - isCustom: false, - isActive: true, - isSystem: false, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-5eef-417a-b517-ebeedaa8e10b': getMockFieldMetadataEntity({ - id: '20202020-5eef-417a-b517-ebeedaa8e10b', - workspaceId, - objectMetadataId, - type: FieldMetadataType.CURRENCY, - name: 'amount', - label: 'Amount', - defaultValue: { amountMicros: null, currencyCode: "''" }, - description: 'Opportunity amount', - icon: 'IconCurrencyDollar', - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-597c-44d3-98ec-ea71aea5256b': getMockFieldMetadataEntity({ - id: '20202020-597c-44d3-98ec-ea71aea5256b', - workspaceId, - objectMetadataId, - type: FieldMetadataType.DATE_TIME, - name: 'closeDate', - label: 'Close date', - defaultValue: null, - description: 'Opportunity close date', - icon: 'IconCalendarEvent', - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-9b94-454a-94ca-8afb09c68faf': getMockFieldMetadataEntity({ - id: '20202020-9b94-454a-94ca-8afb09c68faf', - workspaceId, - objectMetadataId, - type: FieldMetadataType.SELECT, - name: 'stage', - label: 'Stage', - defaultValue: "'NEW'", - description: 'Opportunity stage', - icon: 'IconProgressCheck', - options: [ - { - id: '20202020-dba8-4975-81bd-b29a41c0a387', - color: 'red', - label: 'New', - value: 'NEW', - position: 0, +export const OPPORTUNITY_WITH_FIELDS_MAPS = + getMockObjectMetadataItemWithFieldsMaps({ + id: objectMetadataId, + nameSingular: 'opportunity', + namePlural: 'opportunities', + labelSingular: 'Opportunity', + labelPlural: 'Opportunities', + description: 'An opportunity', + icon: 'IconTargetArrow', + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: true, + isSearchable: true, + labelIdentifierFieldMetadataId: '20202020-c2f1-4435-adca-22931f8b41b6', + imageIdentifierFieldMetadataId: null, + workspaceId, + indexMetadatas: [], + fieldsById: { + '20202020-c2f1-4435-adca-22931f8b41b6': getMockFieldMetadataEntity({ + id: '20202020-c2f1-4435-adca-22931f8b41b6', + workspaceId, + objectMetadataId, + type: FieldMetadataType.TEXT, + name: 'name', + label: 'Name', + defaultValue: "''", + description: 'The opportunity name', + icon: 'IconTargetArrow', + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-5eef-417a-b517-ebeedaa8e10b': getMockFieldMetadataEntity({ + id: '20202020-5eef-417a-b517-ebeedaa8e10b', + workspaceId, + objectMetadataId, + type: FieldMetadataType.CURRENCY, + name: 'amount', + label: 'Amount', + defaultValue: { amountMicros: null, currencyCode: "''" }, + description: 'Opportunity amount', + icon: 'IconCurrencyDollar', + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-597c-44d3-98ec-ea71aea5256b': getMockFieldMetadataEntity({ + id: '20202020-597c-44d3-98ec-ea71aea5256b', + workspaceId, + objectMetadataId, + type: FieldMetadataType.DATE_TIME, + name: 'closeDate', + label: 'Close date', + defaultValue: null, + description: 'Opportunity close date', + icon: 'IconCalendarEvent', + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-9b94-454a-94ca-8afb09c68faf': getMockFieldMetadataEntity({ + id: '20202020-9b94-454a-94ca-8afb09c68faf', + workspaceId, + objectMetadataId, + type: FieldMetadataType.SELECT, + name: 'stage', + label: 'Stage', + defaultValue: "'NEW'", + description: 'Opportunity stage', + icon: 'IconProgressCheck', + options: [ + { + id: '20202020-dba8-4975-81bd-b29a41c0a387', + color: 'red', + label: 'New', + value: 'NEW', + position: 0, + }, + { + id: '20202020-1c9d-490c-940c-bf47addcd6a1', + color: 'purple', + label: 'Screening', + value: 'SCREENING', + position: 1, + }, + { + id: '20202020-1368-438b-8702-6d5e5727a888', + color: 'sky', + label: 'Meeting', + value: 'MEETING', + position: 2, + }, + { + id: '20202020-41e4-4b4e-a038-f02645f55767', + color: 'turquoise', + label: 'Proposal', + value: 'PROPOSAL', + position: 3, + }, + { + id: '20202020-8acf-4934-8519-42f7c9133cd5', + color: 'yellow', + label: 'Customer', + value: 'CUSTOMER', + position: 4, + }, + ], + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-30a5-4d8e-9b93-12d31ece0aaa': getMockFieldMetadataEntity({ + id: '20202020-30a5-4d8e-9b93-12d31ece0aaa', + workspaceId, + objectMetadataId, + type: FieldMetadataType.POSITION, + name: 'position', + label: 'Position', + defaultValue: 0, + description: 'Opportunity record position', + icon: 'IconHierarchy2', + isCustom: false, + isActive: true, + isSystem: true, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-f95f-424f-ab32-65961e8e9635': getMockFieldMetadataEntity({ + id: '20202020-f95f-424f-ab32-65961e8e9635', + workspaceId, + objectMetadataId, + type: FieldMetadataType.ACTOR, + name: 'createdBy', + label: 'Created by', + defaultValue: { name: "'System'", source: "'MANUAL'" }, + description: 'The creator of the record', + icon: 'IconCreativeCommonsSa', + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-5e10-4780-babb-38a465ac546c': getMockFieldMetadataEntity({ + id: '20202020-5e10-4780-babb-38a465ac546c', + workspaceId, + objectMetadataId, + type: FieldMetadataType.TEXT, + name: 'searchVector', + label: 'Search vector', + defaultValue: null, + description: 'Field used for full-text search', + icon: 'IconUser', + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-8f4a-4f8d-822e-90fe72f75b79': getMockFieldMetadataEntity({ + id: '20202020-8f4a-4f8d-822e-90fe72f75b79', + workspaceId, + objectMetadataId, + type: FieldMetadataType.UUID, + name: 'id', + label: 'Id', + defaultValue: 'uuid', + description: 'Id', + icon: 'Icon123', + isCustom: false, + isActive: true, + isSystem: true, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-f120-4b59-b239-f7f1d8eb243e': getMockFieldMetadataEntity({ + id: '20202020-f120-4b59-b239-f7f1d8eb243e', + workspaceId, + objectMetadataId, + type: FieldMetadataType.DATE_TIME, + name: 'createdAt', + label: 'Creation date', + defaultValue: 'now', + description: 'Creation date', + icon: 'IconCalendar', + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: false, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-dcc8-4318-9756-b87377692561': getMockFieldMetadataEntity({ + id: '20202020-dcc8-4318-9756-b87377692561', + workspaceId, + objectMetadataId, + type: FieldMetadataType.DATE_TIME, + name: 'updatedAt', + label: 'Last update', + defaultValue: 'now', + description: 'Last time the record was changed', + icon: 'IconCalendarClock', + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + isLabelSyncedWithName: false, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-1694-4f8b-8760-61a5ff330022': getMockFieldMetadataEntity({ + id: '20202020-1694-4f8b-8760-61a5ff330022', + workspaceId, + objectMetadataId, + type: FieldMetadataType.DATE_TIME, + name: 'deletedAt', + label: 'Deletion date', + defaultValue: null, + description: 'Record deletion date', + icon: 'IconCalendarOff', + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: false, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-4f52-4dea-a116-723f9bf7f082': getMockFieldMetadataEntity({ + id: '20202020-4f52-4dea-a116-723f9bf7f082', + workspaceId, + objectMetadataId, + type: FieldMetadataType.RELATION, + name: 'pointOfContact', + label: 'Point of Contact', + defaultValue: null, + description: 'The point of contact for this opportunity', + icon: 'IconUser', + settings: { + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'pointOfContactId', + onDelete: RelationOnDeleteAction.CASCADE, }, - { - id: '20202020-1c9d-490c-940c-bf47addcd6a1', - color: 'purple', - label: 'Screening', - value: 'SCREENING', - position: 1, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-fc02-4be2-be1a-e121daf5400d': getMockFieldMetadataEntity({ + id: '20202020-fc02-4be2-be1a-e121daf5400d', + workspaceId, + objectMetadataId, + type: FieldMetadataType.RELATION, + name: 'company', + label: 'Company', + defaultValue: null, + description: 'The company this opportunity is associated with', + icon: 'IconBuildingSkyscraper', + settings: { + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'companyId', + onDelete: RelationOnDeleteAction.CASCADE, }, - { - id: '20202020-1368-438b-8702-6d5e5727a888', - color: 'sky', - label: 'Meeting', - value: 'MEETING', - position: 2, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-fd9f-48f0-bd5f-5b0fec6a5de4': getMockFieldMetadataEntity({ + id: '20202020-fd9f-48f0-bd5f-5b0fec6a5de4', + workspaceId, + objectMetadataId, + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Users who favorited this opportunity', + icon: 'IconStar', + settings: { + relationType: RelationType.ONE_TO_MANY, + onDelete: RelationOnDeleteAction.CASCADE, }, - { - id: '20202020-41e4-4b4e-a038-f02645f55767', - color: 'turquoise', - label: 'Proposal', - value: 'PROPOSAL', - position: 3, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-88ab-4138-98ce-80533bb423e3': getMockFieldMetadataEntity({ + id: '20202020-88ab-4138-98ce-80533bb423e3', + workspaceId, + objectMetadataId, + type: FieldMetadataType.RELATION, + name: 'taskTargets', + label: 'Task Targets', + defaultValue: null, + description: 'Tasks targeting this opportunity', + icon: 'IconCheckbox', + settings: { + relationType: RelationType.ONE_TO_MANY, + onDelete: RelationOnDeleteAction.CASCADE, }, - { - id: '20202020-8acf-4934-8519-42f7c9133cd5', - color: 'yellow', - label: 'Customer', - value: 'CUSTOMER', - position: 4, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-4258-422b-b35b-db3f090af8da': getMockFieldMetadataEntity({ + id: '20202020-4258-422b-b35b-db3f090af8da', + workspaceId, + objectMetadataId, + type: FieldMetadataType.RELATION, + name: 'noteTargets', + label: 'Note Targets', + defaultValue: null, + description: 'Notes targeting this opportunity', + icon: 'IconNotes', + settings: { + relationType: RelationType.ONE_TO_MANY, + onDelete: RelationOnDeleteAction.CASCADE, }, - ], - isCustom: false, - isActive: true, - isSystem: false, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-30a5-4d8e-9b93-12d31ece0aaa': getMockFieldMetadataEntity({ - id: '20202020-30a5-4d8e-9b93-12d31ece0aaa', - workspaceId, - objectMetadataId, - type: FieldMetadataType.POSITION, - name: 'position', - label: 'Position', - defaultValue: 0, - description: 'Opportunity record position', - icon: 'IconHierarchy2', - isCustom: false, - isActive: true, - isSystem: true, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-f95f-424f-ab32-65961e8e9635': getMockFieldMetadataEntity({ - id: '20202020-f95f-424f-ab32-65961e8e9635', - workspaceId, - objectMetadataId, - type: FieldMetadataType.ACTOR, - name: 'createdBy', - label: 'Created by', - defaultValue: { name: "'System'", source: "'MANUAL'" }, - description: 'The creator of the record', - icon: 'IconCreativeCommonsSa', - isCustom: false, - isActive: true, - isSystem: false, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-5e10-4780-babb-38a465ac546c': getMockFieldMetadataEntity({ - id: '20202020-5e10-4780-babb-38a465ac546c', - workspaceId, - objectMetadataId, - type: FieldMetadataType.TEXT, - name: 'searchVector', - label: 'Search vector', - defaultValue: null, - description: 'Field used for full-text search', - icon: 'IconUser', - isCustom: false, - isActive: true, - isSystem: true, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-8f4a-4f8d-822e-90fe72f75b79': getMockFieldMetadataEntity({ + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-16ca-40a7-a1ba-712975c916cd': getMockFieldMetadataEntity({ + id: '20202020-16ca-40a7-a1ba-712975c916cd', + workspaceId, + objectMetadataId, + type: FieldMetadataType.RELATION, + name: 'attachments', + label: 'Attachments', + defaultValue: null, + description: 'Attachments for this opportunity', + icon: 'IconPaperclip', + settings: { + relationType: RelationType.ONE_TO_MANY, + onDelete: RelationOnDeleteAction.CASCADE, + }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + '20202020-92a5-47bf-a38d-c1c72b2c3e4d': getMockFieldMetadataEntity({ + id: '20202020-92a5-47bf-a38d-c1c72b2c3e4d', + workspaceId, + objectMetadataId, + type: FieldMetadataType.RELATION, + name: 'timelineActivities', + label: 'Timeline Activities', + defaultValue: null, + description: 'Timeline activities for this opportunity', + icon: 'IconTimeline', + settings: { + relationType: RelationType.ONE_TO_MANY, + onDelete: RelationOnDeleteAction.CASCADE, + }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date('2025-06-27T12:55:13.271Z'), + updatedAt: new Date('2025-06-27T12:55:13.271Z'), + }) as FieldMetadataEntity, + }, + fieldIdByName: { + name: '20202020-c2f1-4435-adca-22931f8b41b6', + amount: '20202020-5eef-417a-b517-ebeedaa8e10b', + closeDate: '20202020-597c-44d3-98ec-ea71aea5256b', + stage: '20202020-9b94-454a-94ca-8afb09c68faf', + position: '20202020-30a5-4d8e-9b93-12d31ece0aaa', + createdBy: '20202020-f95f-424f-ab32-65961e8e9635', + searchVector: '20202020-5e10-4780-babb-38a465ac546c', id: '20202020-8f4a-4f8d-822e-90fe72f75b79', - workspaceId, - objectMetadataId, - type: FieldMetadataType.UUID, - name: 'id', - label: 'Id', - defaultValue: 'uuid', - description: 'Id', - icon: 'Icon123', - isCustom: false, - isActive: true, - isSystem: true, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-f120-4b59-b239-f7f1d8eb243e': getMockFieldMetadataEntity({ - id: '20202020-f120-4b59-b239-f7f1d8eb243e', - workspaceId, - objectMetadataId, - type: FieldMetadataType.DATE_TIME, - name: 'createdAt', - label: 'Creation date', - defaultValue: 'now', - description: 'Creation date', - icon: 'IconCalendar', - settings: { displayFormat: DateDisplayFormat.RELATIVE }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: false, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-dcc8-4318-9756-b87377692561': getMockFieldMetadataEntity({ - id: '20202020-dcc8-4318-9756-b87377692561', - workspaceId, - objectMetadataId, - type: FieldMetadataType.DATE_TIME, - name: 'updatedAt', - label: 'Last update', - defaultValue: 'now', - description: 'Last time the record was changed', - icon: 'IconCalendarClock', - settings: { displayFormat: DateDisplayFormat.RELATIVE }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: false, - isUnique: false, - isLabelSyncedWithName: false, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-1694-4f8b-8760-61a5ff330022': getMockFieldMetadataEntity({ - id: '20202020-1694-4f8b-8760-61a5ff330022', - workspaceId, - objectMetadataId, - type: FieldMetadataType.DATE_TIME, - name: 'deletedAt', - label: 'Deletion date', - defaultValue: null, - description: 'Record deletion date', - icon: 'IconCalendarOff', - settings: { displayFormat: DateDisplayFormat.RELATIVE }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: false, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-4f52-4dea-a116-723f9bf7f082': getMockFieldMetadataEntity({ - id: '20202020-4f52-4dea-a116-723f9bf7f082', - workspaceId, - objectMetadataId, - type: FieldMetadataType.RELATION, - name: 'pointOfContact', - label: 'Point of Contact', - defaultValue: null, - description: 'The point of contact for this opportunity', - icon: 'IconUser', - settings: { - relationType: RelationType.MANY_TO_ONE, - joinColumnName: 'pointOfContactId', - onDelete: RelationOnDeleteAction.CASCADE, - }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-fc02-4be2-be1a-e121daf5400d': getMockFieldMetadataEntity({ - id: '20202020-fc02-4be2-be1a-e121daf5400d', - workspaceId, - objectMetadataId, - type: FieldMetadataType.RELATION, - name: 'company', - label: 'Company', - defaultValue: null, - description: 'The company this opportunity is associated with', - icon: 'IconBuildingSkyscraper', - settings: { - relationType: RelationType.MANY_TO_ONE, - joinColumnName: 'companyId', - onDelete: RelationOnDeleteAction.CASCADE, - }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-fd9f-48f0-bd5f-5b0fec6a5de4': getMockFieldMetadataEntity({ - id: '20202020-fd9f-48f0-bd5f-5b0fec6a5de4', - workspaceId, - objectMetadataId, - type: FieldMetadataType.RELATION, - name: 'favorites', - label: 'Favorites', - defaultValue: null, - description: 'Users who favorited this opportunity', - icon: 'IconStar', - settings: { - relationType: RelationType.ONE_TO_MANY, - onDelete: RelationOnDeleteAction.CASCADE, - }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-88ab-4138-98ce-80533bb423e3': getMockFieldMetadataEntity({ - id: '20202020-88ab-4138-98ce-80533bb423e3', - workspaceId, - objectMetadataId, - type: FieldMetadataType.RELATION, - name: 'taskTargets', - label: 'Task Targets', - defaultValue: null, - description: 'Tasks targeting this opportunity', - icon: 'IconCheckbox', - settings: { - relationType: RelationType.ONE_TO_MANY, - onDelete: RelationOnDeleteAction.CASCADE, - }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-4258-422b-b35b-db3f090af8da': getMockFieldMetadataEntity({ - id: '20202020-4258-422b-b35b-db3f090af8da', - workspaceId, - objectMetadataId, - type: FieldMetadataType.RELATION, - name: 'noteTargets', - label: 'Note Targets', - defaultValue: null, - description: 'Notes targeting this opportunity', - icon: 'IconNotes', - settings: { - relationType: RelationType.ONE_TO_MANY, - onDelete: RelationOnDeleteAction.CASCADE, - }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-16ca-40a7-a1ba-712975c916cd': getMockFieldMetadataEntity({ - id: '20202020-16ca-40a7-a1ba-712975c916cd', - workspaceId, - objectMetadataId, - type: FieldMetadataType.RELATION, - name: 'attachments', - label: 'Attachments', - defaultValue: null, - description: 'Attachments for this opportunity', - icon: 'IconPaperclip', - settings: { - relationType: RelationType.ONE_TO_MANY, - onDelete: RelationOnDeleteAction.CASCADE, - }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - '20202020-92a5-47bf-a38d-c1c72b2c3e4d': getMockFieldMetadataEntity({ - id: '20202020-92a5-47bf-a38d-c1c72b2c3e4d', - workspaceId, - objectMetadataId, - type: FieldMetadataType.RELATION, - name: 'timelineActivities', - label: 'Timeline Activities', - defaultValue: null, - description: 'Timeline activities for this opportunity', - icon: 'IconTimeline', - settings: { - relationType: RelationType.ONE_TO_MANY, - onDelete: RelationOnDeleteAction.CASCADE, - }, - isCustom: false, - isActive: true, - isSystem: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date('2025-06-27T12:55:13.271Z'), - updatedAt: new Date('2025-06-27T12:55:13.271Z'), - }) as FieldMetadataEntity, - }, - fieldIdByName: { - name: '20202020-c2f1-4435-adca-22931f8b41b6', - amount: '20202020-5eef-417a-b517-ebeedaa8e10b', - closeDate: '20202020-597c-44d3-98ec-ea71aea5256b', - stage: '20202020-9b94-454a-94ca-8afb09c68faf', - position: '20202020-30a5-4d8e-9b93-12d31ece0aaa', - createdBy: '20202020-f95f-424f-ab32-65961e8e9635', - searchVector: '20202020-5e10-4780-babb-38a465ac546c', - id: '20202020-8f4a-4f8d-822e-90fe72f75b79', - createdAt: '20202020-f120-4b59-b239-f7f1d8eb243e', - updatedAt: '20202020-dcc8-4318-9756-b87377692561', - deletedAt: '20202020-1694-4f8b-8760-61a5ff330022', - pointOfContact: '20202020-4f52-4dea-a116-723f9bf7f082', - company: '20202020-fc02-4be2-be1a-e121daf5400d', - favorites: '20202020-fd9f-48f0-bd5f-5b0fec6a5de4', - taskTargets: '20202020-88ab-4138-98ce-80533bb423e3', - noteTargets: '20202020-4258-422b-b35b-db3f090af8da', - attachments: '20202020-16ca-40a7-a1ba-712975c916cd', - timelineActivities: '20202020-92a5-47bf-a38d-c1c72b2c3e4d', - }, - fieldIdByJoinColumnName: { - pointOfContactId: '20202020-4f52-4dea-a116-723f9bf7f082', - companyId: '20202020-fc02-4be2-be1a-e121daf5400d', - }, -} as const satisfies ObjectMetadataItemWithFieldMaps; + createdAt: '20202020-f120-4b59-b239-f7f1d8eb243e', + updatedAt: '20202020-dcc8-4318-9756-b87377692561', + deletedAt: '20202020-1694-4f8b-8760-61a5ff330022', + pointOfContact: '20202020-4f52-4dea-a116-723f9bf7f082', + company: '20202020-fc02-4be2-be1a-e121daf5400d', + favorites: '20202020-fd9f-48f0-bd5f-5b0fec6a5de4', + taskTargets: '20202020-88ab-4138-98ce-80533bb423e3', + noteTargets: '20202020-4258-422b-b35b-db3f090af8da', + attachments: '20202020-16ca-40a7-a1ba-712975c916cd', + timelineActivities: '20202020-92a5-47bf-a38d-c1c72b2c3e4d', + }, + fieldIdByJoinColumnName: { + pointOfContactId: '20202020-4f52-4dea-a116-723f9bf7f082', + companyId: '20202020-fc02-4be2-be1a-e121daf5400d', + }, + }); diff --git a/packages/twenty-server/src/engine/api/utils/__tests__/compute-cursor-arg-filter.utils.spec.ts b/packages/twenty-server/src/engine/api/utils/__tests__/compute-cursor-arg-filter.utils.spec.ts index fc60ca1ae..8deb8ef57 100644 --- a/packages/twenty-server/src/engine/api/utils/__tests__/compute-cursor-arg-filter.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/utils/__tests__/compute-cursor-arg-filter.utils.spec.ts @@ -5,71 +5,72 @@ import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock'; +import { getMockObjectMetadataItemWithFieldsMaps } from 'src/utils/__test__/get-object-metadata-item-with-fields-maps.mock'; describe('computeCursorArgFilter', () => { - const objectMetadataItemWithFieldMaps = { - id: 'object-id', - workspaceId: 'workspace-id', - nameSingular: 'person', - namePlural: 'people', - isCustom: false, - isRemote: false, - labelSingular: 'Person', - labelPlural: 'People', - targetTableName: 'person', - indexMetadatas: [], - isSystem: false, - isActive: true, - isAuditLogged: false, - isSearchable: false, - fieldIdByJoinColumnName: {}, - icon: 'Icon123', - fieldIdByName: { - name: 'name-id', - age: 'age-id', - fullName: 'fullname-id', - }, - fieldsById: { - 'name-id': getMockFieldMetadataEntity({ - workspaceId: 'workspace-id', - objectMetadataId: 'object-id', - id: 'name-id', - type: FieldMetadataType.TEXT, - name: 'name', - label: 'Name', - isLabelSyncedWithName: true, - isNullable: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - 'age-id': getMockFieldMetadataEntity({ - workspaceId: 'workspace-id', - objectMetadataId: 'object-id', - id: 'age-id', - type: FieldMetadataType.NUMBER, - name: 'age', - label: 'Age', - isLabelSyncedWithName: true, - isNullable: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - 'fullname-id': getMockFieldMetadataEntity({ - workspaceId: 'workspace-id', - objectMetadataId: 'object-id', - id: 'fullname-id', - type: FieldMetadataType.FULL_NAME, - name: 'fullName', - label: 'Full Name', - isLabelSyncedWithName: true, - isNullable: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - }, - } satisfies ObjectMetadataItemWithFieldMaps; + const objectMetadataItemWithFieldMaps = + getMockObjectMetadataItemWithFieldsMaps({ + id: 'object-id', + workspaceId: 'workspace-id', + nameSingular: 'person', + namePlural: 'people', + isCustom: false, + isRemote: false, + labelSingular: 'Person', + labelPlural: 'People', + targetTableName: 'person', + indexMetadatas: [], + isSystem: false, + isActive: true, + isAuditLogged: false, + isSearchable: false, + fieldIdByJoinColumnName: {}, + icon: 'Icon123', + fieldIdByName: { + name: 'name-id', + age: 'age-id', + fullName: 'fullname-id', + }, + fieldsById: { + 'name-id': getMockFieldMetadataEntity({ + workspaceId: 'workspace-id', + objectMetadataId: 'object-id', + id: 'name-id', + type: FieldMetadataType.TEXT, + name: 'name', + label: 'Name', + isLabelSyncedWithName: true, + isNullable: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + 'age-id': getMockFieldMetadataEntity({ + workspaceId: 'workspace-id', + objectMetadataId: 'object-id', + id: 'age-id', + type: FieldMetadataType.NUMBER, + name: 'age', + label: 'Age', + isLabelSyncedWithName: true, + isNullable: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + 'fullname-id': getMockFieldMetadataEntity({ + workspaceId: 'workspace-id', + objectMetadataId: 'object-id', + id: 'fullname-id', + type: FieldMetadataType.FULL_NAME, + name: 'fullName', + label: 'Full Name', + isLabelSyncedWithName: true, + isNullable: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + }, + }); describe('basic cursor filtering', () => { it('should return empty array when cursor is empty', () => { diff --git a/packages/twenty-server/src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps.ts b/packages/twenty-server/src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps.ts index 0c42e239e..0ed9df107 100644 --- a/packages/twenty-server/src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps.ts +++ b/packages/twenty-server/src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps.ts @@ -1,206 +1,205 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock'; +import { getMockObjectMetadataItemWithFieldsMaps } from 'src/utils/__test__/get-object-metadata-item-with-fields-maps.mock'; const workspaceId = '20202020-0000-0000-0000-000000000000'; -export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMaps[] = - [ - { - id: '20202020-8dec-43d5-b2ff-6eef05095bec', - standardId: '', - nameSingular: 'person', - namePlural: 'people', - labelSingular: 'Person', - labelPlural: 'People', - description: 'A person', - icon: 'test-person-icon', - targetTableName: 'DEPRECATED', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: true, - isSearchable: true, - labelIdentifierFieldMetadataId: 'nameFieldMetadataId', - imageIdentifierFieldMetadataId: '', - workspaceId, - indexMetadatas: [], - fieldsById: { - nameFieldMetadataId: getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '20202020-8dec-43d5-b2ff-6eef05095bec', - id: 'nameFieldMetadataId', - type: FieldMetadataType.FULL_NAME, - icon: 'test-field-icon', - name: 'name', - label: 'Name', - defaultValue: { - lastName: "''", - firstName: "''", - }, - description: "Contact's name", - isCustom: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - }, - fieldIdByName: { - name: 'nameFieldMetadataId', - }, - fieldIdByJoinColumnName: {}, +export const mockObjectMetadataItemsWithFieldMaps = [ + getMockObjectMetadataItemWithFieldsMaps({ + id: '20202020-8dec-43d5-b2ff-6eef05095bec', + standardId: '', + nameSingular: 'person', + namePlural: 'people', + labelSingular: 'Person', + labelPlural: 'People', + description: 'A person', + icon: 'test-person-icon', + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: true, + isSearchable: true, + labelIdentifierFieldMetadataId: 'nameFieldMetadataId', + imageIdentifierFieldMetadataId: '', + workspaceId, + indexMetadatas: [], + fieldsById: { + nameFieldMetadataId: getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '20202020-8dec-43d5-b2ff-6eef05095bec', + id: 'nameFieldMetadataId', + type: FieldMetadataType.FULL_NAME, + icon: 'test-field-icon', + name: 'name', + label: 'Name', + defaultValue: { + lastName: "''", + firstName: "''", + }, + description: "Contact's name", + isCustom: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, }, - { - id: '20202020-c03c-45d6-a4b0-04afe1357c5c', - standardId: '', - nameSingular: 'company', - namePlural: 'companies', - labelSingular: 'Company', - labelPlural: 'Companies', - description: 'A company', - icon: 'test-company-icon', - targetTableName: 'DEPRECATED', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: true, - isSearchable: true, - labelIdentifierFieldMetadataId: 'nameFieldMetadataId', - imageIdentifierFieldMetadataId: '', - workspaceId, - indexMetadatas: [], - fieldsById: { - nameFieldMetadataId: getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c', - id: 'nameFieldMetadataId', - type: FieldMetadataType.TEXT, - icon: 'test-field-icon', - name: 'name', - label: 'Name', - defaultValue: '', - isCustom: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - domainNameFieldMetadataId: getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c', - id: 'domainNameFieldMetadataId', - type: FieldMetadataType.LINKS, - icon: 'test-field-icon', - name: 'domainName', - label: 'Domain Name', - defaultValue: { - primaryLinkLabel: '', - primaryLinkUrl: '', - secondaryLinks: [], - }, - isCustom: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - }, - fieldIdByName: { - name: 'nameFieldMetadataId', - domainName: 'domainNameFieldMetadataId', - }, - fieldIdByJoinColumnName: {}, + fieldIdByName: { + name: 'nameFieldMetadataId', }, - { - id: '20202020-3d75-4aab-bacd-ee176c5f63ca', - standardId: '', - nameSingular: 'regular-custom-object', - namePlural: 'regular-custom-objects', - labelSingular: 'Regular Custom Object', - labelPlural: 'Regular Custom Objects', - description: 'A regular custom object', - icon: 'test-regular-custom-object-icon', - targetTableName: 'DEPRECATED', - isCustom: true, - isRemote: false, - isActive: true, - isSystem: false, - isAuditLogged: true, - isSearchable: true, - labelIdentifierFieldMetadataId: 'nameFieldMetadataId', - imageIdentifierFieldMetadataId: 'imageIdentifierFieldMetadataId', - workspaceId, - indexMetadatas: [], - fieldsById: { - nameFieldMetadataId: getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '20202020-3d75-4aab-bacd-ee176c5f63ca', - id: 'nameFieldMetadataId', - type: FieldMetadataType.TEXT, - icon: 'test-field-icon', - name: 'name', - label: 'Name', - defaultValue: '', - isCustom: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - imageIdentifierFieldMetadataId: getMockFieldMetadataEntity({ - workspaceId, - objectMetadataId: '20202020-3d75-4aab-bacd-ee176c5f63ca', - id: 'imageIdentifierFieldMetadataId', - type: FieldMetadataType.TEXT, - icon: 'test-field-icon', - name: 'imageIdentifierFieldName', - label: 'Image Identifier Field Name', - defaultValue: '', - isCustom: false, - isNullable: true, - isUnique: false, - isLabelSyncedWithName: true, - createdAt: new Date(), - updatedAt: new Date(), - }) as FieldMetadataEntity, - }, - fieldIdByName: { - name: 'nameFieldMetadataId', - imageIdentifierFieldName: 'imageIdentifierFieldMetadataId', - }, - fieldIdByJoinColumnName: {}, + fieldIdByJoinColumnName: {}, + }), + getMockObjectMetadataItemWithFieldsMaps({ + id: '20202020-c03c-45d6-a4b0-04afe1357c5c', + standardId: '', + nameSingular: 'company', + namePlural: 'companies', + labelSingular: 'Company', + labelPlural: 'Companies', + description: 'A company', + icon: 'test-company-icon', + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: true, + isSearchable: true, + labelIdentifierFieldMetadataId: 'nameFieldMetadataId', + imageIdentifierFieldMetadataId: '', + workspaceId, + indexMetadatas: [], + fieldsById: { + nameFieldMetadataId: getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c', + id: 'nameFieldMetadataId', + type: FieldMetadataType.TEXT, + icon: 'test-field-icon', + name: 'name', + label: 'Name', + defaultValue: '', + isCustom: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + domainNameFieldMetadataId: getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c', + id: 'domainNameFieldMetadataId', + type: FieldMetadataType.LINKS, + icon: 'test-field-icon', + name: 'domainName', + label: 'Domain Name', + defaultValue: { + primaryLinkLabel: '', + primaryLinkUrl: '', + secondaryLinks: [], + }, + isCustom: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, }, - { - id: '20202020-540c-4397-b872-2a90ea2fb809', - standardId: '', - nameSingular: 'non-searchable-object', - namePlural: 'non-searchable-objects', - labelSingular: '', - labelPlural: '', - description: '', - icon: 'test-non-searchable-object-icon', - targetTableName: 'DEPRECATED', - isCustom: false, - isRemote: false, - isActive: true, - isSystem: true, - isAuditLogged: true, - isSearchable: false, - labelIdentifierFieldMetadataId: '', - imageIdentifierFieldMetadataId: '', - workspaceId, - indexMetadatas: [], - fieldsById: {}, - fieldIdByName: {}, - fieldIdByJoinColumnName: {}, + fieldIdByName: { + name: 'nameFieldMetadataId', + domainName: 'domainNameFieldMetadataId', }, - ]; + fieldIdByJoinColumnName: {}, + }), + getMockObjectMetadataItemWithFieldsMaps({ + id: '20202020-3d75-4aab-bacd-ee176c5f63ca', + standardId: '', + nameSingular: 'regular-custom-object', + namePlural: 'regular-custom-objects', + labelSingular: 'Regular Custom Object', + labelPlural: 'Regular Custom Objects', + description: 'A regular custom object', + icon: 'test-regular-custom-object-icon', + targetTableName: 'DEPRECATED', + isCustom: true, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: true, + isSearchable: true, + labelIdentifierFieldMetadataId: 'nameFieldMetadataId', + imageIdentifierFieldMetadataId: 'imageIdentifierFieldMetadataId', + workspaceId, + indexMetadatas: [], + fieldsById: { + nameFieldMetadataId: getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '20202020-3d75-4aab-bacd-ee176c5f63ca', + id: 'nameFieldMetadataId', + type: FieldMetadataType.TEXT, + icon: 'test-field-icon', + name: 'name', + label: 'Name', + defaultValue: '', + isCustom: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + imageIdentifierFieldMetadataId: getMockFieldMetadataEntity({ + workspaceId, + objectMetadataId: '20202020-3d75-4aab-bacd-ee176c5f63ca', + id: 'imageIdentifierFieldMetadataId', + type: FieldMetadataType.TEXT, + icon: 'test-field-icon', + name: 'imageIdentifierFieldName', + label: 'Image Identifier Field Name', + defaultValue: '', + isCustom: false, + isNullable: true, + isUnique: false, + isLabelSyncedWithName: true, + createdAt: new Date(), + updatedAt: new Date(), + }) as FieldMetadataEntity, + }, + fieldIdByName: { + name: 'nameFieldMetadataId', + imageIdentifierFieldName: 'imageIdentifierFieldMetadataId', + }, + fieldIdByJoinColumnName: {}, + }), + getMockObjectMetadataItemWithFieldsMaps({ + id: '20202020-540c-4397-b872-2a90ea2fb809', + standardId: '', + nameSingular: 'non-searchable-object', + namePlural: 'non-searchable-objects', + labelSingular: '', + labelPlural: '', + description: '', + icon: 'test-non-searchable-object-icon', + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: true, + isAuditLogged: true, + isSearchable: false, + labelIdentifierFieldMetadataId: '', + imageIdentifierFieldMetadataId: '', + workspaceId, + indexMetadatas: [], + fieldsById: {}, + fieldIdByName: {}, + fieldIdByJoinColumnName: {}, + }), +]; 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 32a2c01e3..5eb7d1e27 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 @@ -1,7 +1,7 @@ import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { getMockObjectMetadataItemWithFieldsMaps } from 'src/utils/__test__/get-object-metadata-item-with-fields-maps.mock'; -const mockObjectMetadata: ObjectMetadataItemWithFieldMaps = { +const mockObjectMetadata = getMockObjectMetadataItemWithFieldsMaps({ id: '1', icon: 'Icon123', nameSingular: 'Object', @@ -21,7 +21,7 @@ const mockObjectMetadata: ObjectMetadataItemWithFieldMaps = { isSearchable: true, indexMetadatas: [], fieldIdByJoinColumnName: {}, -}; +}); describe('objectRecordChangedValues', () => { it('detects changes in scalar values correctly', () => { diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 3818f07c9..d15cff09c 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -142,7 +142,7 @@ const computeSchemaComponent = ({ const result: OpenAPIV3_1.SchemaObject = { type: 'object', - description: item.description, + description: item.description ?? undefined, properties: convertObjectMetadataToSchemaProperties({ item, forResponse, diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts index 83b965809..f647fb896 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts @@ -7,13 +7,13 @@ import { isDefined } from 'twenty-shared/utils'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { filterMorphRelationDuplicateFieldsDTO } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataMorphRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-morph-relation.service'; import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service'; -import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-fieldMetadata-dto.util'; +import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util'; import { resolveFieldMetadataStandardOverride } from 'src/engine/metadata-modules/field-metadata/utils/resolve-field-metadata-standard-override.util'; import { IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto'; import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto'; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts index d64b19923..ebad2db19 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts @@ -39,7 +39,8 @@ import { import { BeforeUpdateOneField } from 'src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook'; import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service'; import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util'; -import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-fieldMetadata-dto.util'; +import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util'; +import { fromObjectMetadataEntityToObjectMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util'; import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { isMorphRelationFieldMetadataType } from 'src/engine/utils/is-morph-relation-field-metadata-type.util'; @@ -162,8 +163,10 @@ export class FieldMetadataResolver { return { type: fieldMetadata.settings.relationType, - sourceObjectMetadata, - targetObjectMetadata, + sourceObjectMetadata: + fromObjectMetadataEntityToObjectMetadataDto(sourceObjectMetadata), + targetObjectMetadata: + fromObjectMetadataEntityToObjectMetadataDto(targetObjectMetadata), sourceFieldMetadata: fromFieldMetadataEntityToFieldMetadataDto(sourceFieldMetadata), targetFieldMetadata: @@ -203,8 +206,12 @@ export class FieldMetadataResolver { return morphRelations.map((morphRelation) => ({ type: settings.relationType, - sourceObjectMetadata: morphRelation.sourceObjectMetadata, - targetObjectMetadata: morphRelation.targetObjectMetadata, + sourceObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto( + morphRelation.sourceObjectMetadata, + ), + targetObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto( + morphRelation.targetObjectMetadata, + ), sourceFieldMetadata: fromFieldMetadataEntityToFieldMetadataDto( morphRelation.sourceFieldMetadata, ), diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts index 68bb2af90..0ec3ae3cb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts @@ -1,28 +1,3 @@ -import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; - -export interface ObjectMetadataInterface { - id: string; - standardId?: string | null; - workspaceId: string; - nameSingular: string; - namePlural: string; - labelSingular: string; - labelPlural: string; - description?: string; - icon: string; - targetTableName: string; - fields: FieldMetadataEntity[]; - indexMetadatas: IndexMetadataInterface[]; - isSystem: boolean; - isCustom: boolean; - isActive: boolean; - isRemote: boolean; - isAuditLogged: boolean; - isSearchable: boolean; - duplicateCriteria?: WorkspaceEntityDuplicateCriteria[]; - labelIdentifierFieldMetadataId?: string | null; - imageIdentifierFieldMetadataId?: string | null; -} +export interface ObjectMetadataInterface extends ObjectMetadataEntity {} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-fieldMetadata-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util.ts similarity index 88% rename from packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-fieldMetadata-dto.util.ts rename to packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util.ts index 1774e7a9f..579b13889 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-fieldMetadata-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util.ts @@ -18,7 +18,6 @@ export const fromFieldMetadataEntityToFieldMetadataDto = ( return { ...rest, - // Should we ? seems to be typed a dateString from classValidator, should be typed as string in TypeScript ? createdAt: new Date(createdAt), updatedAt: new Date(updatedAt), description: description ?? undefined, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util.ts new file mode 100644 index 000000000..793d04bdf --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util.ts @@ -0,0 +1,28 @@ +import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; + +export const fromObjectMetadataEntityToObjectMetadataDto = ( + objectMetadataEntity: ObjectMetadataEntity, +): ObjectMetadataDTO => { + const { + createdAt, + updatedAt, + description, + icon, + standardOverrides, + shortcut, + duplicateCriteria, + ...rest + } = objectMetadataEntity; + + return { + ...rest, + createdAt: new Date(createdAt), + updatedAt: new Date(updatedAt), + description: description ?? undefined, + icon: icon ?? undefined, + standardOverrides: standardOverrides ?? undefined, + shortcut: shortcut ?? undefined, + duplicateCriteria: duplicateCriteria ?? undefined, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts index b3d7d1142..26cb85701 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts @@ -51,16 +51,16 @@ export class ObjectMetadataDTO { labelPlural: string; @Field({ nullable: true }) - description: string; + description?: string; @Field({ nullable: true }) - icon: string; + icon?: string; @Field(() => ObjectStandardOverridesDTO, { nullable: true }) standardOverrides?: ObjectStandardOverridesDTO; @Field({ nullable: true }) - shortcut: string; + shortcut?: string; @FilterableField() isCustom: boolean; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts index 37dce526c..2e955033e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts @@ -195,7 +195,7 @@ export class BeforeUpdateOneObject update: StandardObjectUpdate; overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon'; newValue: string; - originalValue: string; + originalValue: string | null; locale?: keyof typeof APP_LOCALES | undefined; }): boolean { if (locale && locale !== SOURCE_LOCALE) { @@ -224,7 +224,7 @@ export class BeforeUpdateOneObject update: StandardObjectUpdate, overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon', newValue: string, - originalValue: string, + originalValue: string | null, locale: keyof typeof APP_LOCALES, ): boolean { const messageId = generateMessageId(originalValue ?? ''); @@ -254,7 +254,7 @@ export class BeforeUpdateOneObject update: StandardObjectUpdate, overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon', newValue: string, - originalValue: string, + originalValue: string | null, ): boolean { if (newValue !== originalValue) { return false; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts index 5e8eb2c0e..24e6fb489 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts @@ -10,8 +10,6 @@ import { UpdateDateColumn, } from 'typeorm'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; - import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -29,7 +27,7 @@ import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permi 'namePlural', 'workspaceId', ]) -export class ObjectMetadataEntity implements ObjectMetadataInterface { +export class ObjectMetadataEntity { @PrimaryGeneratedColumn('uuid') id: string; @@ -52,14 +50,17 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface { labelPlural: string; @Column({ nullable: true, type: 'text' }) - description: string; + description: string | null; - @Column({ nullable: true }) - icon: string; + @Column({ nullable: true, type: 'varchar' }) + icon: string | null; @Column({ type: 'jsonb', nullable: true }) - standardOverrides?: ObjectStandardOverridesDTO; + standardOverrides: ObjectStandardOverridesDTO | null; + /** + * @deprecated + */ @Column({ nullable: false }) targetTableName: string; @@ -82,16 +83,16 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface { isSearchable: boolean; @Column({ type: 'jsonb', nullable: true }) - duplicateCriteria?: WorkspaceEntityDuplicateCriteria[]; + duplicateCriteria: WorkspaceEntityDuplicateCriteria[] | null; - @Column({ nullable: true }) - shortcut: string; + @Column({ nullable: true, type: 'varchar' }) + shortcut: string | null; @Column({ nullable: true, type: 'uuid' }) - labelIdentifierFieldMetadataId?: string | null; + labelIdentifierFieldMetadataId: string | null; @Column({ nullable: true, type: 'uuid' }) - imageIdentifierFieldMetadataId?: string | null; + imageIdentifierFieldMetadataId: string | null; @Column({ default: false }) isLabelSyncedWithName: boolean; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service.ts index 27a02eaac..787ce315e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@nestjs/common'; +import { isDefined } from 'twenty-shared/utils'; + import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; @@ -99,7 +101,9 @@ export class ObjectMetadataRelatedRecordsService { { objectMetadataId: updatedObjectMetadata.id, key: 'INDEX' }, { name: `All ${updatedObjectMetadata.labelPlural}`, - icon: updatedObjectMetadata.icon, + ...(isDefined(updatedObjectMetadata.icon) + ? { icon: updatedObjectMetadata.icon } + : {}), }, ); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts index efce23d2f..b4f1a91e4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts @@ -44,8 +44,12 @@ export class StandardObjectFactory { } return { - ...workspaceEntityMetadataArgs, // TODO: Remove targetTableName when we remove the old metadata + labelIdentifierFieldMetadataId: null, + imageIdentifierFieldMetadataId: null, + duplicateCriteria: [], + ...workspaceEntityMetadataArgs, + description: workspaceEntityMetadataArgs.description ?? null, targetTableName: 'DEPRECATED', workspaceId: context.workspaceId, dataSourceId: context.dataSourceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface.ts index 986f0e3db..33a79968c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface.ts @@ -5,17 +5,29 @@ import { PartialFieldMetadata, } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; -export type PartialWorkspaceEntity = Omit< +export type PartialWorkspaceEntity = Pick< ObjectMetadataInterface, - 'id' | 'standardId' | 'fields' | 'isActive' + | 'workspaceId' + | 'nameSingular' + | 'namePlural' + | 'labelSingular' + | 'labelPlural' + | 'description' + | 'icon' + | 'targetTableName' + | 'indexMetadatas' + | 'isSystem' + | 'isCustom' + | 'isRemote' + | 'isAuditLogged' + | 'isSearchable' + | 'duplicateCriteria' + | 'labelIdentifierFieldMetadataId' + | 'imageIdentifierFieldMetadataId' > & { standardId: string; - icon?: string; - workspaceId: string; dataSourceId: string; fields: (PartialFieldMetadata | PartialComputedFieldMetadata)[]; - labelIdentifierStandardId?: string | null; - imageIdentifierStandardId?: string | null; }; export type ComputedPartialWorkspaceEntity = Omit< diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event.ts index 044dab8e0..fc8406dc7 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event.ts @@ -1,9 +1,9 @@ import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { BaseOutputSchema, RecordOutputSchema, } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type'; -import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields'; const generateFakeObjectRecordEventWithPrefix = ({ @@ -26,7 +26,8 @@ const generateFakeObjectRecordEventWithPrefix = ({ return { object: { isLeaf: true, - icon: objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon, + icon: + objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon ?? undefined, label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular, value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description, nameSingular: diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts index 242fa755b..55cbd3a9a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts @@ -12,7 +12,8 @@ export const generateFakeObjectRecord = ({ return { object: { isLeaf: true, - icon: objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon, + icon: + objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon ?? undefined, label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular, value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description, nameSingular: diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts index 1029775f7..ae1a9bb33 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts @@ -1,12 +1,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { AutomatedTriggerType } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { DatabaseEventTriggerListener } from 'src/modules/workflow/workflow-trigger/automated-trigger/listeners/database-event-trigger.listener'; import { WorkflowTriggerJob } from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job'; +import { getMockObjectMetadataEntity } from 'src/utils/__test__/get-object-metadata-entity.mock'; +import { getMockObjectMetadataItemWithFieldsMaps } from 'src/utils/__test__/get-object-metadata-item-with-fields-maps.mock'; describe('DatabaseEventTriggerListener', () => { let listener: DatabaseEventTriggerListener; @@ -54,27 +55,28 @@ describe('DatabaseEventTriggerListener', () => { }, }, }, - objectMetadataItemWithFieldsMaps: { - id: 'test-object-metadata', - workspaceId: 'test-workspace', - nameSingular: 'testObject', - namePlural: 'testObjects', - labelSingular: 'Test Object', - labelPlural: 'Test Objects', - description: 'Test object for testing', - fieldIdByJoinColumnName: {}, - fieldsById: {}, - fieldIdByName: {}, - indexMetadatas: [], - targetTableName: 'test_objects', - isSystem: false, - isCustom: false, - isActive: true, - isRemote: false, - isAuditLogged: true, - isSearchable: true, - icon: 'Icon123', - } satisfies ObjectMetadataItemWithFieldMaps, + objectMetadataItemWithFieldsMaps: + getMockObjectMetadataItemWithFieldsMaps({ + id: 'test-object-metadata', + workspaceId: 'test-workspace', + nameSingular: 'testObject', + namePlural: 'testObjects', + labelSingular: 'Test Object', + labelPlural: 'Test Objects', + description: 'Test object for testing', + indexMetadatas: [], + targetTableName: 'test_objects', + isSystem: false, + isCustom: false, + isActive: true, + isRemote: false, + isAuditLogged: true, + isSearchable: true, + icon: 'Icon123', + fieldIdByJoinColumnName: {}, + fieldsById: {}, + fieldIdByName: {}, + }), }), }, }, @@ -97,7 +99,7 @@ describe('DatabaseEventTriggerListener', () => { events: [ { recordId: 'test-record', - objectMetadata: { + objectMetadata: getMockObjectMetadataEntity({ id: 'test-object-metadata', workspaceId, nameSingular: 'testObject', @@ -117,7 +119,7 @@ describe('DatabaseEventTriggerListener', () => { fields: [], indexMetadatas: [], icon: 'Icon123', - }, + }), properties: { updatedFields: ['field1', 'field2'], before: { field1: 'old', field2: 'old' }, diff --git a/packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts b/packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts new file mode 100644 index 000000000..fb707277f --- /dev/null +++ b/packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts @@ -0,0 +1,48 @@ +import { faker } from '@faker-js/faker'; + +import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; + +export type GetMockObjectMetadataEntityOverride = + Partial & + Required< + Pick< + ObjectMetadataEntity, + 'nameSingular' | 'namePlural' | 'id' | 'workspaceId' + > + >; + +export const getMockObjectMetadataEntity = ( + overrides: GetMockObjectMetadataEntityOverride, +): ObjectMetadataEntity => { + return { + createdAt: new Date(), + updatedAt: new Date(), + dataSource: {} as DataSourceEntity, + dataSourceId: faker.string.uuid(), + description: 'default object metadata description', + duplicateCriteria: [], + fieldPermissions: [], + fields: [], + icon: null, + imageIdentifierFieldMetadataId: null, + labelIdentifierFieldMetadataId: null, + indexMetadatas: [], + isActive: true, + isAuditLogged: true, + isCustom: true, + isLabelSyncedWithName: false, + isRemote: false, + isSearchable: true, + isSystem: false, + labelPlural: 'Default mock plural label', + labelSingular: 'Default mock plural singular', + objectPermissions: [], + shortcut: null, + standardId: null, + targetRelationFields: [], + standardOverrides: null, + targetTableName: faker.string.uuid(), + ...overrides, + }; +}; diff --git a/packages/twenty-server/src/utils/__test__/get-object-metadata-item-with-fields-maps.mock.ts b/packages/twenty-server/src/utils/__test__/get-object-metadata-item-with-fields-maps.mock.ts new file mode 100644 index 000000000..99ee0cb35 --- /dev/null +++ b/packages/twenty-server/src/utils/__test__/get-object-metadata-item-with-fields-maps.mock.ts @@ -0,0 +1,37 @@ +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { + GetMockObjectMetadataEntityOverride, + getMockObjectMetadataEntity, +} from 'src/utils/__test__/get-object-metadata-entity.mock'; + +type GetMockObjectMetadataItemWithFielsMapsOverride = + GetMockObjectMetadataEntityOverride & + Required< + Pick< + ObjectMetadataItemWithFieldMaps, + | 'fieldsById' + | 'fieldIdByJoinColumnName' + | 'fieldIdByName' + | 'indexMetadatas' + > + >; + +export const getMockObjectMetadataItemWithFieldsMaps = ({ + fieldsById, + fieldIdByJoinColumnName, + fieldIdByName, + indexMetadatas, + ...objectMetadataOverrides +}: GetMockObjectMetadataItemWithFielsMapsOverride): ObjectMetadataItemWithFieldMaps => { + const { fields: _fields, ...objectMetadata } = getMockObjectMetadataEntity( + objectMetadataOverrides, + ); + + return { + ...objectMetadata, + fieldsById, + fieldIdByJoinColumnName, + fieldIdByName, + indexMetadatas, + }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts index 4e5a2b49e..48ea47921 100644 --- a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts @@ -13,6 +13,7 @@ import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metada import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { getMockObjectMetadataEntity } from 'src/utils/__test__/get-object-metadata-entity.mock'; export interface AgentToolTestContext { module: TestingModule; @@ -130,7 +131,7 @@ export const createAgentToolTestModule = isEditable: true, } as RoleEntity; - const testObjectMetadata = { + const testObjectMetadata = getMockObjectMetadataEntity({ id: 'test-object-id', standardId: null, dataSourceId: 'test-data-source-id', @@ -158,7 +159,7 @@ export const createAgentToolTestModule = dataSource: {} as any, objectPermissions: [], fieldPermissions: [], - }; + }); return { module,