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
This commit is contained in:
Paul Rastoin
2025-07-21 17:53:17 +02:00
committed by GitHub
parent 79f3fbb016
commit 1536ed3434
23 changed files with 1031 additions and 909 deletions

View File

@ -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 { 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 { 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 { 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 workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
export const mockPersonObjectMetadataWithFieldMaps = ( export const mockPersonObjectMetadataWithFieldMaps = (
duplicateCriteria: WorkspaceEntityDuplicateCriteria[], duplicateCriteria: WorkspaceEntityDuplicateCriteria[],
): ObjectMetadataItemWithFieldMaps => ({ ) =>
id: '', getMockObjectMetadataItemWithFieldsMaps({
icon: 'Icon123', id: '',
standardId: '', icon: 'Icon123',
nameSingular: 'person', standardId: '',
namePlural: 'people', nameSingular: 'person',
labelSingular: 'Person', namePlural: 'people',
labelPlural: 'People', labelSingular: 'Person',
description: 'A person', labelPlural: 'People',
targetTableName: 'DEPRECATED', description: 'A person',
isCustom: false, targetTableName: 'DEPRECATED',
isRemote: false, isCustom: false,
isActive: true, isRemote: false,
isSystem: false, isActive: true,
isAuditLogged: true, isSystem: false,
isSearchable: true, isAuditLogged: true,
duplicateCriteria: duplicateCriteria, isSearchable: true,
labelIdentifierFieldMetadataId: '', duplicateCriteria: duplicateCriteria,
imageIdentifierFieldMetadataId: '', labelIdentifierFieldMetadataId: '',
workspaceId, imageIdentifierFieldMetadataId: '',
indexMetadatas: [], workspaceId,
fieldIdByName: { indexMetadatas: [],
name: 'name-id', fieldIdByName: {
emails: 'emails-id', name: 'name-id',
linkedinLink: 'linkedinLink-id', emails: 'emails-id',
jobTitle: 'jobTitle-id', linkedinLink: 'linkedinLink-id',
}, jobTitle: 'jobTitle-id',
fieldIdByJoinColumnName: {}, },
fieldsById: { fieldIdByJoinColumnName: {},
'name-id': getMockFieldMetadataEntity({ fieldsById: {
workspaceId, 'name-id': getMockFieldMetadataEntity({
objectMetadataId: '', workspaceId,
id: 'name-id', objectMetadataId: '',
type: FieldMetadataType.FULL_NAME, id: 'name-id',
name: 'name', type: FieldMetadataType.FULL_NAME,
label: 'Name', name: 'name',
defaultValue: { label: 'Name',
lastName: "''", defaultValue: {
firstName: "''", lastName: "''",
}, firstName: "''",
description: "Contact's name", },
isCustom: false, description: "Contact's name",
isNullable: true, isCustom: false,
isUnique: false, isNullable: true,
isLabelSyncedWithName: true, isUnique: false,
createdAt: new Date(), isLabelSyncedWithName: true,
updatedAt: new Date(), createdAt: new Date(),
}) as FieldMetadataEntity, updatedAt: new Date(),
'emails-id': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
workspaceId, 'emails-id': getMockFieldMetadataEntity({
objectMetadataId: '', workspaceId,
id: 'emails-id', objectMetadataId: '',
type: FieldMetadataType.EMAILS, id: 'emails-id',
name: 'emails', type: FieldMetadataType.EMAILS,
label: 'Emails', name: 'emails',
defaultValue: { label: 'Emails',
primaryEmail: "''", defaultValue: {
additionalEmails: null, primaryEmail: "''",
}, additionalEmails: null,
description: "Contact's Emails", },
isCustom: false, description: "Contact's Emails",
isNullable: true, isCustom: false,
isLabelSyncedWithName: true, isNullable: true,
createdAt: new Date(), isLabelSyncedWithName: true,
updatedAt: new Date(), createdAt: new Date(),
}) as FieldMetadataEntity, updatedAt: new Date(),
'linkedinLink-id': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
workspaceId, 'linkedinLink-id': getMockFieldMetadataEntity({
objectMetadataId: '', workspaceId,
id: 'linkedinLink-id', objectMetadataId: '',
type: FieldMetadataType.LINKS, id: 'linkedinLink-id',
name: 'linkedinLink', type: FieldMetadataType.LINKS,
label: 'Linkedin', name: 'linkedinLink',
defaultValue: { label: 'Linkedin',
primaryLinkUrl: "''", defaultValue: {
secondaryLinks: [], primaryLinkUrl: "''",
primaryLinkLabel: "''", secondaryLinks: [],
}, primaryLinkLabel: "''",
description: "Contact's Linkedin account", },
isCustom: false, description: "Contact's Linkedin account",
isNullable: true, isCustom: false,
isUnique: false, isNullable: true,
isLabelSyncedWithName: true, isUnique: false,
createdAt: new Date(), isLabelSyncedWithName: true,
updatedAt: new Date(), createdAt: new Date(),
}) as FieldMetadataEntity, updatedAt: new Date(),
'jobTitle-id': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
workspaceId, 'jobTitle-id': getMockFieldMetadataEntity({
objectMetadataId: '', workspaceId,
id: 'jobTitle-id', objectMetadataId: '',
type: FieldMetadataType.TEXT, id: 'jobTitle-id',
name: 'jobTitle', type: FieldMetadataType.TEXT,
label: 'Job Title', name: 'jobTitle',
defaultValue: "''", label: 'Job Title',
description: "Contact's job title", defaultValue: "''",
isCustom: false, description: "Contact's job title",
isNullable: false, isCustom: false,
isUnique: false, isNullable: false,
isLabelSyncedWithName: true, isUnique: false,
createdAt: new Date(), isLabelSyncedWithName: true,
updatedAt: new Date(), createdAt: new Date(),
}) as FieldMetadataEntity, updatedAt: new Date(),
}, }) as FieldMetadataEntity,
}); },
});

View File

@ -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 { 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 { 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 { 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 workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
const objectMetadataId = '20202020-6e2c-42f6-a83c-cc58d776af88'; const objectMetadataId = '20202020-6e2c-42f6-a83c-cc58d776af88';
export const OPPORTUNITY_WITH_FIELDS_MAPS = { export const OPPORTUNITY_WITH_FIELDS_MAPS =
id: objectMetadataId, getMockObjectMetadataItemWithFieldsMaps({
nameSingular: 'opportunity', id: objectMetadataId,
namePlural: 'opportunities', nameSingular: 'opportunity',
labelSingular: 'Opportunity', namePlural: 'opportunities',
labelPlural: 'Opportunities', labelSingular: 'Opportunity',
description: 'An opportunity', labelPlural: 'Opportunities',
icon: 'IconTargetArrow', description: 'An opportunity',
targetTableName: 'DEPRECATED', icon: 'IconTargetArrow',
isCustom: false, targetTableName: 'DEPRECATED',
isRemote: false, isCustom: false,
isActive: true, isRemote: false,
isSystem: false, isActive: true,
isAuditLogged: true, isSystem: false,
isSearchable: true, isAuditLogged: true,
labelIdentifierFieldMetadataId: '20202020-c2f1-4435-adca-22931f8b41b6', isSearchable: true,
imageIdentifierFieldMetadataId: null, labelIdentifierFieldMetadataId: '20202020-c2f1-4435-adca-22931f8b41b6',
workspaceId, imageIdentifierFieldMetadataId: null,
indexMetadatas: [], workspaceId,
fieldsById: { indexMetadatas: [],
'20202020-c2f1-4435-adca-22931f8b41b6': getMockFieldMetadataEntity({ fieldsById: {
id: '20202020-c2f1-4435-adca-22931f8b41b6', '20202020-c2f1-4435-adca-22931f8b41b6': getMockFieldMetadataEntity({
workspaceId, id: '20202020-c2f1-4435-adca-22931f8b41b6',
objectMetadataId, workspaceId,
type: FieldMetadataType.TEXT, objectMetadataId,
name: 'name', type: FieldMetadataType.TEXT,
label: 'Name', name: 'name',
defaultValue: "''", label: 'Name',
description: 'The opportunity name', defaultValue: "''",
icon: 'IconTargetArrow', description: 'The opportunity name',
isCustom: false, icon: 'IconTargetArrow',
isActive: true, isCustom: false,
isSystem: false, isActive: true,
isNullable: false, isSystem: false,
isUnique: false, isNullable: false,
isLabelSyncedWithName: true, isUnique: false,
createdAt: new Date('2025-06-27T12:55:13.271Z'), isLabelSyncedWithName: true,
updatedAt: new Date('2025-06-27T12:55:13.271Z'), createdAt: new Date('2025-06-27T12:55:13.271Z'),
}) as FieldMetadataEntity, updatedAt: new Date('2025-06-27T12:55:13.271Z'),
'20202020-5eef-417a-b517-ebeedaa8e10b': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
id: '20202020-5eef-417a-b517-ebeedaa8e10b', '20202020-5eef-417a-b517-ebeedaa8e10b': getMockFieldMetadataEntity({
workspaceId, id: '20202020-5eef-417a-b517-ebeedaa8e10b',
objectMetadataId, workspaceId,
type: FieldMetadataType.CURRENCY, objectMetadataId,
name: 'amount', type: FieldMetadataType.CURRENCY,
label: 'Amount', name: 'amount',
defaultValue: { amountMicros: null, currencyCode: "''" }, label: 'Amount',
description: 'Opportunity amount', defaultValue: { amountMicros: null, currencyCode: "''" },
icon: 'IconCurrencyDollar', description: 'Opportunity amount',
isCustom: false, icon: 'IconCurrencyDollar',
isActive: true, isCustom: false,
isSystem: false, isActive: true,
isNullable: true, isSystem: false,
isUnique: false, isNullable: true,
isLabelSyncedWithName: true, isUnique: false,
createdAt: new Date('2025-06-27T12:55:13.271Z'), isLabelSyncedWithName: true,
updatedAt: new Date('2025-06-27T12:55:13.271Z'), createdAt: new Date('2025-06-27T12:55:13.271Z'),
}) as FieldMetadataEntity, updatedAt: new Date('2025-06-27T12:55:13.271Z'),
'20202020-597c-44d3-98ec-ea71aea5256b': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
id: '20202020-597c-44d3-98ec-ea71aea5256b', '20202020-597c-44d3-98ec-ea71aea5256b': getMockFieldMetadataEntity({
workspaceId, id: '20202020-597c-44d3-98ec-ea71aea5256b',
objectMetadataId, workspaceId,
type: FieldMetadataType.DATE_TIME, objectMetadataId,
name: 'closeDate', type: FieldMetadataType.DATE_TIME,
label: 'Close date', name: 'closeDate',
defaultValue: null, label: 'Close date',
description: 'Opportunity close date', defaultValue: null,
icon: 'IconCalendarEvent', description: 'Opportunity close date',
isCustom: false, icon: 'IconCalendarEvent',
isActive: true, isCustom: false,
isSystem: false, isActive: true,
isNullable: true, isSystem: false,
isUnique: false, isNullable: true,
isLabelSyncedWithName: true, isUnique: false,
createdAt: new Date('2025-06-27T12:55:13.271Z'), isLabelSyncedWithName: true,
updatedAt: new Date('2025-06-27T12:55:13.271Z'), createdAt: new Date('2025-06-27T12:55:13.271Z'),
}) as FieldMetadataEntity, updatedAt: new Date('2025-06-27T12:55:13.271Z'),
'20202020-9b94-454a-94ca-8afb09c68faf': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
id: '20202020-9b94-454a-94ca-8afb09c68faf', '20202020-9b94-454a-94ca-8afb09c68faf': getMockFieldMetadataEntity({
workspaceId, id: '20202020-9b94-454a-94ca-8afb09c68faf',
objectMetadataId, workspaceId,
type: FieldMetadataType.SELECT, objectMetadataId,
name: 'stage', type: FieldMetadataType.SELECT,
label: 'Stage', name: 'stage',
defaultValue: "'NEW'", label: 'Stage',
description: 'Opportunity stage', defaultValue: "'NEW'",
icon: 'IconProgressCheck', description: 'Opportunity stage',
options: [ icon: 'IconProgressCheck',
{ options: [
id: '20202020-dba8-4975-81bd-b29a41c0a387', {
color: 'red', id: '20202020-dba8-4975-81bd-b29a41c0a387',
label: 'New', color: 'red',
value: 'NEW', label: 'New',
position: 0, 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,
}, },
{ isCustom: false,
id: '20202020-1c9d-490c-940c-bf47addcd6a1', isActive: true,
color: 'purple', isSystem: false,
label: 'Screening', isNullable: true,
value: 'SCREENING', isUnique: false,
position: 1, 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,
id: '20202020-1368-438b-8702-6d5e5727a888', isActive: true,
color: 'sky', isSystem: false,
label: 'Meeting', isNullable: true,
value: 'MEETING', isUnique: false,
position: 2, 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,
id: '20202020-41e4-4b4e-a038-f02645f55767', isActive: true,
color: 'turquoise', isSystem: false,
label: 'Proposal', isNullable: true,
value: 'PROPOSAL', isUnique: false,
position: 3, 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,
id: '20202020-8acf-4934-8519-42f7c9133cd5', isActive: true,
color: 'yellow', isSystem: false,
label: 'Customer', isNullable: true,
value: 'CUSTOMER', isUnique: false,
position: 4, 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,
isCustom: false, isActive: true,
isActive: true, isSystem: false,
isSystem: false, isNullable: true,
isNullable: false, isUnique: false,
isUnique: false, isLabelSyncedWithName: true,
isLabelSyncedWithName: true, createdAt: new Date('2025-06-27T12:55:13.271Z'),
createdAt: new Date('2025-06-27T12:55:13.271Z'), updatedAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'), }) as FieldMetadataEntity,
}) as FieldMetadataEntity, '20202020-16ca-40a7-a1ba-712975c916cd': getMockFieldMetadataEntity({
'20202020-30a5-4d8e-9b93-12d31ece0aaa': getMockFieldMetadataEntity({ id: '20202020-16ca-40a7-a1ba-712975c916cd',
id: '20202020-30a5-4d8e-9b93-12d31ece0aaa', workspaceId,
workspaceId, objectMetadataId,
objectMetadataId, type: FieldMetadataType.RELATION,
type: FieldMetadataType.POSITION, name: 'attachments',
name: 'position', label: 'Attachments',
label: 'Position', defaultValue: null,
defaultValue: 0, description: 'Attachments for this opportunity',
description: 'Opportunity record position', icon: 'IconPaperclip',
icon: 'IconHierarchy2', settings: {
isCustom: false, relationType: RelationType.ONE_TO_MANY,
isActive: true, onDelete: RelationOnDeleteAction.CASCADE,
isSystem: true, },
isNullable: false, isCustom: false,
isUnique: false, isActive: true,
isLabelSyncedWithName: true, isSystem: false,
createdAt: new Date('2025-06-27T12:55:13.271Z'), isNullable: true,
updatedAt: new Date('2025-06-27T12:55:13.271Z'), isUnique: false,
}) as FieldMetadataEntity, isLabelSyncedWithName: true,
'20202020-f95f-424f-ab32-65961e8e9635': getMockFieldMetadataEntity({ createdAt: new Date('2025-06-27T12:55:13.271Z'),
id: '20202020-f95f-424f-ab32-65961e8e9635', updatedAt: new Date('2025-06-27T12:55:13.271Z'),
workspaceId, }) as FieldMetadataEntity,
objectMetadataId, '20202020-92a5-47bf-a38d-c1c72b2c3e4d': getMockFieldMetadataEntity({
type: FieldMetadataType.ACTOR, id: '20202020-92a5-47bf-a38d-c1c72b2c3e4d',
name: 'createdBy', workspaceId,
label: 'Created by', objectMetadataId,
defaultValue: { name: "'System'", source: "'MANUAL'" }, type: FieldMetadataType.RELATION,
description: 'The creator of the record', name: 'timelineActivities',
icon: 'IconCreativeCommonsSa', label: 'Timeline Activities',
isCustom: false, defaultValue: null,
isActive: true, description: 'Timeline activities for this opportunity',
isSystem: false, icon: 'IconTimeline',
isNullable: false, settings: {
isUnique: false, relationType: RelationType.ONE_TO_MANY,
isLabelSyncedWithName: true, onDelete: RelationOnDeleteAction.CASCADE,
createdAt: new Date('2025-06-27T12:55:13.271Z'), },
updatedAt: new Date('2025-06-27T12:55:13.271Z'), isCustom: false,
}) as FieldMetadataEntity, isActive: true,
'20202020-5e10-4780-babb-38a465ac546c': getMockFieldMetadataEntity({ isSystem: false,
id: '20202020-5e10-4780-babb-38a465ac546c', isNullable: true,
workspaceId, isUnique: false,
objectMetadataId, isLabelSyncedWithName: true,
type: FieldMetadataType.TEXT, createdAt: new Date('2025-06-27T12:55:13.271Z'),
name: 'searchVector', updatedAt: new Date('2025-06-27T12:55:13.271Z'),
label: 'Search vector', }) as FieldMetadataEntity,
defaultValue: null, },
description: 'Field used for full-text search', fieldIdByName: {
icon: 'IconUser', name: '20202020-c2f1-4435-adca-22931f8b41b6',
isCustom: false, amount: '20202020-5eef-417a-b517-ebeedaa8e10b',
isActive: true, closeDate: '20202020-597c-44d3-98ec-ea71aea5256b',
isSystem: true, stage: '20202020-9b94-454a-94ca-8afb09c68faf',
isNullable: true, position: '20202020-30a5-4d8e-9b93-12d31ece0aaa',
isUnique: false, createdBy: '20202020-f95f-424f-ab32-65961e8e9635',
isLabelSyncedWithName: true, searchVector: '20202020-5e10-4780-babb-38a465ac546c',
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', id: '20202020-8f4a-4f8d-822e-90fe72f75b79',
workspaceId, createdAt: '20202020-f120-4b59-b239-f7f1d8eb243e',
objectMetadataId, updatedAt: '20202020-dcc8-4318-9756-b87377692561',
type: FieldMetadataType.UUID, deletedAt: '20202020-1694-4f8b-8760-61a5ff330022',
name: 'id', pointOfContact: '20202020-4f52-4dea-a116-723f9bf7f082',
label: 'Id', company: '20202020-fc02-4be2-be1a-e121daf5400d',
defaultValue: 'uuid', favorites: '20202020-fd9f-48f0-bd5f-5b0fec6a5de4',
description: 'Id', taskTargets: '20202020-88ab-4138-98ce-80533bb423e3',
icon: 'Icon123', noteTargets: '20202020-4258-422b-b35b-db3f090af8da',
isCustom: false, attachments: '20202020-16ca-40a7-a1ba-712975c916cd',
isActive: true, timelineActivities: '20202020-92a5-47bf-a38d-c1c72b2c3e4d',
isSystem: true, },
isNullable: false, fieldIdByJoinColumnName: {
isUnique: false, pointOfContactId: '20202020-4f52-4dea-a116-723f9bf7f082',
isLabelSyncedWithName: true, companyId: '20202020-fc02-4be2-be1a-e121daf5400d',
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;

View File

@ -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 { 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 { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; 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 { 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', () => { describe('computeCursorArgFilter', () => {
const objectMetadataItemWithFieldMaps = { const objectMetadataItemWithFieldMaps =
id: 'object-id', getMockObjectMetadataItemWithFieldsMaps({
workspaceId: 'workspace-id', id: 'object-id',
nameSingular: 'person', workspaceId: 'workspace-id',
namePlural: 'people', nameSingular: 'person',
isCustom: false, namePlural: 'people',
isRemote: false, isCustom: false,
labelSingular: 'Person', isRemote: false,
labelPlural: 'People', labelSingular: 'Person',
targetTableName: 'person', labelPlural: 'People',
indexMetadatas: [], targetTableName: 'person',
isSystem: false, indexMetadatas: [],
isActive: true, isSystem: false,
isAuditLogged: false, isActive: true,
isSearchable: false, isAuditLogged: false,
fieldIdByJoinColumnName: {}, isSearchable: false,
icon: 'Icon123', fieldIdByJoinColumnName: {},
fieldIdByName: { icon: 'Icon123',
name: 'name-id', fieldIdByName: {
age: 'age-id', name: 'name-id',
fullName: 'fullname-id', age: 'age-id',
}, fullName: 'fullname-id',
fieldsById: { },
'name-id': getMockFieldMetadataEntity({ fieldsById: {
workspaceId: 'workspace-id', 'name-id': getMockFieldMetadataEntity({
objectMetadataId: 'object-id', workspaceId: 'workspace-id',
id: 'name-id', objectMetadataId: 'object-id',
type: FieldMetadataType.TEXT, id: 'name-id',
name: 'name', type: FieldMetadataType.TEXT,
label: 'Name', name: 'name',
isLabelSyncedWithName: true, label: 'Name',
isNullable: true, isLabelSyncedWithName: true,
createdAt: new Date(), isNullable: true,
updatedAt: new Date(), createdAt: new Date(),
}) as FieldMetadataEntity, updatedAt: new Date(),
'age-id': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
workspaceId: 'workspace-id', 'age-id': getMockFieldMetadataEntity({
objectMetadataId: 'object-id', workspaceId: 'workspace-id',
id: 'age-id', objectMetadataId: 'object-id',
type: FieldMetadataType.NUMBER, id: 'age-id',
name: 'age', type: FieldMetadataType.NUMBER,
label: 'Age', name: 'age',
isLabelSyncedWithName: true, label: 'Age',
isNullable: true, isLabelSyncedWithName: true,
createdAt: new Date(), isNullable: true,
updatedAt: new Date(), createdAt: new Date(),
}) as FieldMetadataEntity, updatedAt: new Date(),
'fullname-id': getMockFieldMetadataEntity({ }) as FieldMetadataEntity,
workspaceId: 'workspace-id', 'fullname-id': getMockFieldMetadataEntity({
objectMetadataId: 'object-id', workspaceId: 'workspace-id',
id: 'fullname-id', objectMetadataId: 'object-id',
type: FieldMetadataType.FULL_NAME, id: 'fullname-id',
name: 'fullName', type: FieldMetadataType.FULL_NAME,
label: 'Full Name', name: 'fullName',
isLabelSyncedWithName: true, label: 'Full Name',
isNullable: true, isLabelSyncedWithName: true,
createdAt: new Date(), isNullable: true,
updatedAt: new Date(), createdAt: new Date(),
}) as FieldMetadataEntity, updatedAt: new Date(),
}, }) as FieldMetadataEntity,
} satisfies ObjectMetadataItemWithFieldMaps; },
});
describe('basic cursor filtering', () => { describe('basic cursor filtering', () => {
it('should return empty array when cursor is empty', () => { it('should return empty array when cursor is empty', () => {

View File

@ -1,206 +1,205 @@
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; 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 { 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'; const workspaceId = '20202020-0000-0000-0000-000000000000';
export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMaps[] = export const mockObjectMetadataItemsWithFieldMaps = [
[ getMockObjectMetadataItemWithFieldsMaps({
{ id: '20202020-8dec-43d5-b2ff-6eef05095bec',
id: '20202020-8dec-43d5-b2ff-6eef05095bec', standardId: '',
standardId: '', nameSingular: 'person',
nameSingular: 'person', namePlural: 'people',
namePlural: 'people', labelSingular: 'Person',
labelSingular: 'Person', labelPlural: 'People',
labelPlural: 'People', description: 'A person',
description: 'A person', icon: 'test-person-icon',
icon: 'test-person-icon', targetTableName: 'DEPRECATED',
targetTableName: 'DEPRECATED', isCustom: false,
isCustom: false, isRemote: false,
isRemote: false, isActive: true,
isActive: true, isSystem: false,
isSystem: false, isAuditLogged: true,
isAuditLogged: true, isSearchable: true,
isSearchable: true, labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
labelIdentifierFieldMetadataId: 'nameFieldMetadataId', imageIdentifierFieldMetadataId: '',
imageIdentifierFieldMetadataId: '', workspaceId,
workspaceId, indexMetadatas: [],
indexMetadatas: [], fieldsById: {
fieldsById: { nameFieldMetadataId: getMockFieldMetadataEntity({
nameFieldMetadataId: getMockFieldMetadataEntity({ workspaceId,
workspaceId, objectMetadataId: '20202020-8dec-43d5-b2ff-6eef05095bec',
objectMetadataId: '20202020-8dec-43d5-b2ff-6eef05095bec', id: 'nameFieldMetadataId',
id: 'nameFieldMetadataId', type: FieldMetadataType.FULL_NAME,
type: FieldMetadataType.FULL_NAME, icon: 'test-field-icon',
icon: 'test-field-icon', name: 'name',
name: 'name', label: 'Name',
label: 'Name', defaultValue: {
defaultValue: { lastName: "''",
lastName: "''", firstName: "''",
firstName: "''", },
}, description: "Contact's name",
description: "Contact's name", isCustom: false,
isCustom: false, isNullable: true,
isNullable: true, isUnique: false,
isUnique: false, isLabelSyncedWithName: true,
isLabelSyncedWithName: true, createdAt: new Date(),
createdAt: new Date(), updatedAt: new Date(),
updatedAt: new Date(), }) as FieldMetadataEntity,
}) as FieldMetadataEntity,
},
fieldIdByName: {
name: 'nameFieldMetadataId',
},
fieldIdByJoinColumnName: {},
}, },
{ fieldIdByName: {
id: '20202020-c03c-45d6-a4b0-04afe1357c5c', name: 'nameFieldMetadataId',
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: {},
}, },
{ fieldIdByJoinColumnName: {},
id: '20202020-3d75-4aab-bacd-ee176c5f63ca', }),
standardId: '', getMockObjectMetadataItemWithFieldsMaps({
nameSingular: 'regular-custom-object', id: '20202020-c03c-45d6-a4b0-04afe1357c5c',
namePlural: 'regular-custom-objects', standardId: '',
labelSingular: 'Regular Custom Object', nameSingular: 'company',
labelPlural: 'Regular Custom Objects', namePlural: 'companies',
description: 'A regular custom object', labelSingular: 'Company',
icon: 'test-regular-custom-object-icon', labelPlural: 'Companies',
targetTableName: 'DEPRECATED', description: 'A company',
isCustom: true, icon: 'test-company-icon',
isRemote: false, targetTableName: 'DEPRECATED',
isActive: true, isCustom: false,
isSystem: false, isRemote: false,
isAuditLogged: true, isActive: true,
isSearchable: true, isSystem: false,
labelIdentifierFieldMetadataId: 'nameFieldMetadataId', isAuditLogged: true,
imageIdentifierFieldMetadataId: 'imageIdentifierFieldMetadataId', isSearchable: true,
workspaceId, labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
indexMetadatas: [], imageIdentifierFieldMetadataId: '',
fieldsById: { workspaceId,
nameFieldMetadataId: getMockFieldMetadataEntity({ indexMetadatas: [],
workspaceId, fieldsById: {
objectMetadataId: '20202020-3d75-4aab-bacd-ee176c5f63ca', nameFieldMetadataId: getMockFieldMetadataEntity({
id: 'nameFieldMetadataId', workspaceId,
type: FieldMetadataType.TEXT, objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c',
icon: 'test-field-icon', id: 'nameFieldMetadataId',
name: 'name', type: FieldMetadataType.TEXT,
label: 'Name', icon: 'test-field-icon',
defaultValue: '', name: 'name',
isCustom: false, label: 'Name',
isNullable: true, defaultValue: '',
isUnique: false, isCustom: false,
isLabelSyncedWithName: true, isNullable: true,
createdAt: new Date(), isUnique: false,
updatedAt: new Date(), isLabelSyncedWithName: true,
}) as FieldMetadataEntity, createdAt: new Date(),
imageIdentifierFieldMetadataId: getMockFieldMetadataEntity({ updatedAt: new Date(),
workspaceId, }) as FieldMetadataEntity,
objectMetadataId: '20202020-3d75-4aab-bacd-ee176c5f63ca', domainNameFieldMetadataId: getMockFieldMetadataEntity({
id: 'imageIdentifierFieldMetadataId', workspaceId,
type: FieldMetadataType.TEXT, objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c',
icon: 'test-field-icon', id: 'domainNameFieldMetadataId',
name: 'imageIdentifierFieldName', type: FieldMetadataType.LINKS,
label: 'Image Identifier Field Name', icon: 'test-field-icon',
defaultValue: '', name: 'domainName',
isCustom: false, label: 'Domain Name',
isNullable: true, defaultValue: {
isUnique: false, primaryLinkLabel: '',
isLabelSyncedWithName: true, primaryLinkUrl: '',
createdAt: new Date(), secondaryLinks: [],
updatedAt: new Date(), },
}) as FieldMetadataEntity, isCustom: false,
}, isNullable: true,
fieldIdByName: { isUnique: false,
name: 'nameFieldMetadataId', isLabelSyncedWithName: true,
imageIdentifierFieldName: 'imageIdentifierFieldMetadataId', createdAt: new Date(),
}, updatedAt: new Date(),
fieldIdByJoinColumnName: {}, }) as FieldMetadataEntity,
}, },
{ fieldIdByName: {
id: '20202020-540c-4397-b872-2a90ea2fb809', name: 'nameFieldMetadataId',
standardId: '', domainName: 'domainNameFieldMetadataId',
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: {},
}, },
]; 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: {},
}),
];

View File

@ -1,7 +1,7 @@
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; 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', id: '1',
icon: 'Icon123', icon: 'Icon123',
nameSingular: 'Object', nameSingular: 'Object',
@ -21,7 +21,7 @@ const mockObjectMetadata: ObjectMetadataItemWithFieldMaps = {
isSearchable: true, isSearchable: true,
indexMetadatas: [], indexMetadatas: [],
fieldIdByJoinColumnName: {}, fieldIdByJoinColumnName: {},
}; });
describe('objectRecordChangedValues', () => { describe('objectRecordChangedValues', () => {
it('detects changes in scalar values correctly', () => { it('detects changes in scalar values correctly', () => {

View File

@ -142,7 +142,7 @@ const computeSchemaComponent = ({
const result: OpenAPIV3_1.SchemaObject = { const result: OpenAPIV3_1.SchemaObject = {
type: 'object', type: 'object',
description: item.description, description: item.description ?? undefined,
properties: convertObjectMetadataToSchemaProperties({ properties: convertObjectMetadataToSchemaProperties({
item, item,
forResponse, forResponse,

View File

@ -7,13 +7,13 @@ import { isDefined } from 'twenty-shared/utils';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; 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 { 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 { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
import { filterMorphRelationDuplicateFieldsDTO } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util'; 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 { 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 { 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 { 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 { 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 { 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'; import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';

View File

@ -39,7 +39,8 @@ import {
import { BeforeUpdateOneField } from 'src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook'; 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 { 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 { 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 { 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 { 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'; import { isMorphRelationFieldMetadataType } from 'src/engine/utils/is-morph-relation-field-metadata-type.util';
@ -162,8 +163,10 @@ export class FieldMetadataResolver {
return { return {
type: fieldMetadata.settings.relationType, type: fieldMetadata.settings.relationType,
sourceObjectMetadata, sourceObjectMetadata:
targetObjectMetadata, fromObjectMetadataEntityToObjectMetadataDto(sourceObjectMetadata),
targetObjectMetadata:
fromObjectMetadataEntityToObjectMetadataDto(targetObjectMetadata),
sourceFieldMetadata: sourceFieldMetadata:
fromFieldMetadataEntityToFieldMetadataDto(sourceFieldMetadata), fromFieldMetadataEntityToFieldMetadataDto(sourceFieldMetadata),
targetFieldMetadata: targetFieldMetadata:
@ -203,8 +206,12 @@ export class FieldMetadataResolver {
return morphRelations.map<RelationDTO>((morphRelation) => ({ return morphRelations.map<RelationDTO>((morphRelation) => ({
type: settings.relationType, type: settings.relationType,
sourceObjectMetadata: morphRelation.sourceObjectMetadata, sourceObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto(
targetObjectMetadata: morphRelation.targetObjectMetadata, morphRelation.sourceObjectMetadata,
),
targetObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto(
morphRelation.targetObjectMetadata,
),
sourceFieldMetadata: fromFieldMetadataEntityToFieldMetadataDto( sourceFieldMetadata: fromFieldMetadataEntityToFieldMetadataDto(
morphRelation.sourceFieldMetadata, morphRelation.sourceFieldMetadata,
), ),

View File

@ -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'; export interface ObjectMetadataInterface extends ObjectMetadataEntity {}
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;
}

View File

@ -18,7 +18,6 @@ export const fromFieldMetadataEntityToFieldMetadataDto = (
return { return {
...rest, ...rest,
// Should we ? seems to be typed a dateString from classValidator, should be typed as string in TypeScript ?
createdAt: new Date(createdAt), createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt), updatedAt: new Date(updatedAt),
description: description ?? undefined, description: description ?? undefined,

View File

@ -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,
};
};

View File

@ -51,16 +51,16 @@ export class ObjectMetadataDTO {
labelPlural: string; labelPlural: string;
@Field({ nullable: true }) @Field({ nullable: true })
description: string; description?: string;
@Field({ nullable: true }) @Field({ nullable: true })
icon: string; icon?: string;
@Field(() => ObjectStandardOverridesDTO, { nullable: true }) @Field(() => ObjectStandardOverridesDTO, { nullable: true })
standardOverrides?: ObjectStandardOverridesDTO; standardOverrides?: ObjectStandardOverridesDTO;
@Field({ nullable: true }) @Field({ nullable: true })
shortcut: string; shortcut?: string;
@FilterableField() @FilterableField()
isCustom: boolean; isCustom: boolean;

View File

@ -195,7 +195,7 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload>
update: StandardObjectUpdate; update: StandardObjectUpdate;
overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon'; overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon';
newValue: string; newValue: string;
originalValue: string; originalValue: string | null;
locale?: keyof typeof APP_LOCALES | undefined; locale?: keyof typeof APP_LOCALES | undefined;
}): boolean { }): boolean {
if (locale && locale !== SOURCE_LOCALE) { if (locale && locale !== SOURCE_LOCALE) {
@ -224,7 +224,7 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload>
update: StandardObjectUpdate, update: StandardObjectUpdate,
overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon', overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon',
newValue: string, newValue: string,
originalValue: string, originalValue: string | null,
locale: keyof typeof APP_LOCALES, locale: keyof typeof APP_LOCALES,
): boolean { ): boolean {
const messageId = generateMessageId(originalValue ?? ''); const messageId = generateMessageId(originalValue ?? '');
@ -254,7 +254,7 @@ export class BeforeUpdateOneObject<T extends UpdateObjectPayload>
update: StandardObjectUpdate, update: StandardObjectUpdate,
overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon', overrideKey: 'labelSingular' | 'labelPlural' | 'description' | 'icon',
newValue: string, newValue: string,
originalValue: string, originalValue: string | null,
): boolean { ): boolean {
if (newValue !== originalValue) { if (newValue !== originalValue) {
return false; return false;

View File

@ -10,8 +10,6 @@ import {
UpdateDateColumn, UpdateDateColumn,
} from 'typeorm'; } 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 { 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 { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.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', 'namePlural',
'workspaceId', 'workspaceId',
]) ])
export class ObjectMetadataEntity implements ObjectMetadataInterface { export class ObjectMetadataEntity {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
id: string; id: string;
@ -52,14 +50,17 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
labelPlural: string; labelPlural: string;
@Column({ nullable: true, type: 'text' }) @Column({ nullable: true, type: 'text' })
description: string; description: string | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'varchar' })
icon: string; icon: string | null;
@Column({ type: 'jsonb', nullable: true }) @Column({ type: 'jsonb', nullable: true })
standardOverrides?: ObjectStandardOverridesDTO; standardOverrides: ObjectStandardOverridesDTO | null;
/**
* @deprecated
*/
@Column({ nullable: false }) @Column({ nullable: false })
targetTableName: string; targetTableName: string;
@ -82,16 +83,16 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
isSearchable: boolean; isSearchable: boolean;
@Column({ type: 'jsonb', nullable: true }) @Column({ type: 'jsonb', nullable: true })
duplicateCriteria?: WorkspaceEntityDuplicateCriteria[]; duplicateCriteria: WorkspaceEntityDuplicateCriteria[] | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'varchar' })
shortcut: string; shortcut: string | null;
@Column({ nullable: true, type: 'uuid' }) @Column({ nullable: true, type: 'uuid' })
labelIdentifierFieldMetadataId?: string | null; labelIdentifierFieldMetadataId: string | null;
@Column({ nullable: true, type: 'uuid' }) @Column({ nullable: true, type: 'uuid' })
imageIdentifierFieldMetadataId?: string | null; imageIdentifierFieldMetadataId: string | null;
@Column({ default: false }) @Column({ default: false })
isLabelSyncedWithName: boolean; isLabelSyncedWithName: boolean;

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
@ -99,7 +101,9 @@ export class ObjectMetadataRelatedRecordsService {
{ objectMetadataId: updatedObjectMetadata.id, key: 'INDEX' }, { objectMetadataId: updatedObjectMetadata.id, key: 'INDEX' },
{ {
name: `All ${updatedObjectMetadata.labelPlural}`, name: `All ${updatedObjectMetadata.labelPlural}`,
icon: updatedObjectMetadata.icon, ...(isDefined(updatedObjectMetadata.icon)
? { icon: updatedObjectMetadata.icon }
: {}),
}, },
); );
} }

View File

@ -44,8 +44,12 @@ export class StandardObjectFactory {
} }
return { return {
...workspaceEntityMetadataArgs,
// TODO: Remove targetTableName when we remove the old metadata // TODO: Remove targetTableName when we remove the old metadata
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
duplicateCriteria: [],
...workspaceEntityMetadataArgs,
description: workspaceEntityMetadataArgs.description ?? null,
targetTableName: 'DEPRECATED', targetTableName: 'DEPRECATED',
workspaceId: context.workspaceId, workspaceId: context.workspaceId,
dataSourceId: context.dataSourceId, dataSourceId: context.dataSourceId,

View File

@ -5,17 +5,29 @@ import {
PartialFieldMetadata, PartialFieldMetadata,
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
export type PartialWorkspaceEntity = Omit< export type PartialWorkspaceEntity = Pick<
ObjectMetadataInterface, ObjectMetadataInterface,
'id' | 'standardId' | 'fields' | 'isActive' | 'workspaceId'
| 'nameSingular'
| 'namePlural'
| 'labelSingular'
| 'labelPlural'
| 'description'
| 'icon'
| 'targetTableName'
| 'indexMetadatas'
| 'isSystem'
| 'isCustom'
| 'isRemote'
| 'isAuditLogged'
| 'isSearchable'
| 'duplicateCriteria'
| 'labelIdentifierFieldMetadataId'
| 'imageIdentifierFieldMetadataId'
> & { > & {
standardId: string; standardId: string;
icon?: string;
workspaceId: string;
dataSourceId: string; dataSourceId: string;
fields: (PartialFieldMetadata | PartialComputedFieldMetadata)[]; fields: (PartialFieldMetadata | PartialComputedFieldMetadata)[];
labelIdentifierStandardId?: string | null;
imageIdentifierStandardId?: string | null;
}; };
export type ComputedPartialWorkspaceEntity = Omit< export type ComputedPartialWorkspaceEntity = Omit<

View File

@ -1,9 +1,9 @@
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; 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 { import {
BaseOutputSchema, BaseOutputSchema,
RecordOutputSchema, RecordOutputSchema,
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type'; } 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'; import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
const generateFakeObjectRecordEventWithPrefix = ({ const generateFakeObjectRecordEventWithPrefix = ({
@ -26,7 +26,8 @@ const generateFakeObjectRecordEventWithPrefix = ({
return { return {
object: { object: {
isLeaf: true, isLeaf: true,
icon: objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon, icon:
objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon ?? undefined,
label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular, label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular,
value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description, value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description,
nameSingular: nameSingular:

View File

@ -12,7 +12,8 @@ export const generateFakeObjectRecord = ({
return { return {
object: { object: {
isLeaf: true, isLeaf: true,
icon: objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon, icon:
objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon ?? undefined,
label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular, label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular,
value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description, value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description,
nameSingular: nameSingular:

View File

@ -1,12 +1,13 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; 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 { 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 { 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 { 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 { 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 { 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', () => { describe('DatabaseEventTriggerListener', () => {
let listener: DatabaseEventTriggerListener; let listener: DatabaseEventTriggerListener;
@ -54,27 +55,28 @@ describe('DatabaseEventTriggerListener', () => {
}, },
}, },
}, },
objectMetadataItemWithFieldsMaps: { objectMetadataItemWithFieldsMaps:
id: 'test-object-metadata', getMockObjectMetadataItemWithFieldsMaps({
workspaceId: 'test-workspace', id: 'test-object-metadata',
nameSingular: 'testObject', workspaceId: 'test-workspace',
namePlural: 'testObjects', nameSingular: 'testObject',
labelSingular: 'Test Object', namePlural: 'testObjects',
labelPlural: 'Test Objects', labelSingular: 'Test Object',
description: 'Test object for testing', labelPlural: 'Test Objects',
fieldIdByJoinColumnName: {}, description: 'Test object for testing',
fieldsById: {}, indexMetadatas: [],
fieldIdByName: {}, targetTableName: 'test_objects',
indexMetadatas: [], isSystem: false,
targetTableName: 'test_objects', isCustom: false,
isSystem: false, isActive: true,
isCustom: false, isRemote: false,
isActive: true, isAuditLogged: true,
isRemote: false, isSearchable: true,
isAuditLogged: true, icon: 'Icon123',
isSearchable: true, fieldIdByJoinColumnName: {},
icon: 'Icon123', fieldsById: {},
} satisfies ObjectMetadataItemWithFieldMaps, fieldIdByName: {},
}),
}), }),
}, },
}, },
@ -97,7 +99,7 @@ describe('DatabaseEventTriggerListener', () => {
events: [ events: [
{ {
recordId: 'test-record', recordId: 'test-record',
objectMetadata: { objectMetadata: getMockObjectMetadataEntity({
id: 'test-object-metadata', id: 'test-object-metadata',
workspaceId, workspaceId,
nameSingular: 'testObject', nameSingular: 'testObject',
@ -117,7 +119,7 @@ describe('DatabaseEventTriggerListener', () => {
fields: [], fields: [],
indexMetadatas: [], indexMetadatas: [],
icon: 'Icon123', icon: 'Icon123',
}, }),
properties: { properties: {
updatedFields: ['field1', 'field2'], updatedFields: ['field1', 'field2'],
before: { field1: 'old', field2: 'old' }, before: { field1: 'old', field2: 'old' },

View File

@ -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<ObjectMetadataEntity> &
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,
};
};

View File

@ -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,
};
};

View File

@ -13,6 +13,7 @@ import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metada
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; 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 { 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { getMockObjectMetadataEntity } from 'src/utils/__test__/get-object-metadata-entity.mock';
export interface AgentToolTestContext { export interface AgentToolTestContext {
module: TestingModule; module: TestingModule;
@ -130,7 +131,7 @@ export const createAgentToolTestModule =
isEditable: true, isEditable: true,
} as RoleEntity; } as RoleEntity;
const testObjectMetadata = { const testObjectMetadata = getMockObjectMetadataEntity({
id: 'test-object-id', id: 'test-object-id',
standardId: null, standardId: null,
dataSourceId: 'test-data-source-id', dataSourceId: 'test-data-source-id',
@ -158,7 +159,7 @@ export const createAgentToolTestModule =
dataSource: {} as any, dataSource: {} as any,
objectPermissions: [], objectPermissions: [],
fieldPermissions: [], fieldPermissions: [],
}; });
return { return {
module, module,