Deprecate FieldMetadataInterface (#13264)

# Introduction

From the moment replaced the FieldMetadataInterface definition to:
```ts
import { FieldMetadataType } from 'twenty-shared/types';

import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';

export type FieldMetadataInterface<
  T extends FieldMetadataType = FieldMetadataType,
> = FieldMetadataEntity<T>;
```
After this PR merge will create a new one removing the type and
replacing it to `FieldMetadataEntity`.
Did not renamed it here to avoid conflicts on naming + type issues fixs
within the same PR

## Field metadata entity RELATION or MORPH
Relations fields cannot be null for those field metadata entity instance
anymore, but are never for the others see
`packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts`
( introduced TypeScript tests )

## Concerns
- TS_VECTOR is the most at risk with the `generatedType` and
`asExpression` removal from interface

## What's next
- `FielMetadataInterface` removal and rename ( see introduction )
- Depcrecating `ObjectMetadataInterface`
- Refactor `FieldMetadataEntity` optional fiels to be nullable only
- TO DIG `never` occurences on settings, defaultValue etc
- Some interfaces will be replaced by the `FlatFieldMetadata` when
deprecating the current sync and comparators tools
This commit is contained in:
Paul Rastoin
2025-07-21 11:30:18 +02:00
committed by GitHub
parent c2a5f95675
commit 47b60bd49f
67 changed files with 1780 additions and 769 deletions

View File

@ -34,6 +34,7 @@ import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/wo
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { formatResult as formatGetManyData } from 'src/engine/twenty-orm/utils/format-result.util';
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
export interface PageInfo {
hasNextPage?: boolean;
@ -159,7 +160,7 @@ export abstract class RestApiBaseHandler {
Object.values(objectMetadata.objectMetadataMapItem.fieldsById).forEach(
(field) => {
if (field.type === FieldMetadataType.RELATION) {
if (isFieldMetadataInterfaceOfType(field, FieldMetadataType.RELATION)) {
if (
depth === MAX_DEPTH &&
isDefined(field.relationTargetObjectMetadataId)

View File

@ -1,30 +1,31 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { getFieldType } from 'src/engine/api/rest/core/query-builder/utils/get-field-type.utils';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
describe('getFieldType', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
const completeFieldNumberMock = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
});
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
'field-number-id': completeFieldNumberMock as FieldMetadataEntity,
};
const mockObjectMetadataWithFieldMaps = {

View File

@ -1,7 +1,6 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataDefaultSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import {
@ -11,54 +10,59 @@ import {
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
describe('mapFieldMetadataToGraphqlQuery', () => {
const typedFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
const typedFieldNumberMock = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: '20202020-0000-0000-0000-000000000002',
name: fieldNumberMock.name,
type: fieldNumberMock.type,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
});
const typedFieldTextMock: FieldMetadataInterface = {
id: 'field-text-id',
const typedFieldTextMock = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: '20202020-0000-0000-0000-000000000003',
name: fieldTextMock.name,
type: fieldTextMock.type,
label: 'Field Text',
objectMetadataId: 'object-metadata-id',
isNullable: fieldTextMock.isNullable,
defaultValue: fieldTextMock.defaultValue,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
});
const typedFieldCurrencyMock: FieldMetadataInterface = {
id: 'field-currency-id',
const typedFieldCurrencyMock = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: '20202020-0000-0000-0000-000000000004',
name: fieldCurrencyMock.name,
type: fieldCurrencyMock.type,
label: 'Field Currency',
objectMetadataId: 'object-metadata-id',
isNullable: fieldCurrencyMock.isNullable,
defaultValue: fieldCurrencyMock.defaultValue,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
});
const fieldsById: FieldMetadataMap = {
'field-number-id': typedFieldNumberMock,
'field-text-id': typedFieldTextMock,
'field-currency-id': typedFieldCurrencyMock,
'field-number-id': typedFieldNumberMock as FieldMetadataEntity,
'field-text-id': typedFieldTextMock as FieldMetadataEntity,
'field-currency-id': typedFieldCurrencyMock as FieldMetadataEntity,
};
const typedObjectMetadataItem: ObjectMetadataItemWithFieldMaps = {
@ -86,19 +90,19 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
expect(
mapFieldMetadataToGraphqlQuery(
objectMetadataMapsMock,
typedFieldNumberMock,
typedFieldNumberMock as FieldMetadataEntity,
),
).toEqual('fieldNumber');
expect(
mapFieldMetadataToGraphqlQuery(
objectMetadataMapsMock,
typedFieldTextMock,
typedFieldTextMock as FieldMetadataEntity,
),
).toEqual('fieldText');
expect(
mapFieldMetadataToGraphqlQuery(
objectMetadataMapsMock,
typedFieldCurrencyMock,
typedFieldCurrencyMock as FieldMetadataEntity,
),
).toEqual(`
fieldCurrency
@ -112,29 +116,25 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
describe('should handle all field metadata types', () => {
Object.values(FieldMetadataType).forEach((fieldMetadataType) => {
it(`with field type ${fieldMetadataType}`, () => {
const field: FieldMetadataInterface = {
id: 'test-field-id',
const field = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: '20202020-0000-0000-0000-000000000005',
type: fieldMetadataType,
name: 'toObjectMetadataName',
label: 'Test Field',
objectMetadataId: 'object-metadata-id',
isNullable: true,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
if (fieldMetadataType === FieldMetadataType.RELATION) {
field.settings = {
relationType: RelationType.MANY_TO_ONE,
} as FieldMetadataDefaultSettings;
}
if (fieldMetadataType === FieldMetadataType.MORPH_RELATION) {
field.settings = {
relationType: RelationType.MANY_TO_ONE,
} as FieldMetadataDefaultSettings;
}
settings:
fieldMetadataType === FieldMetadataType.RELATION ||
fieldMetadataType === FieldMetadataType.MORPH_RELATION
? ({
relationType: RelationType.MANY_TO_ONE,
} as FieldMetadataDefaultSettings)
: undefined,
});
expect(
mapFieldMetadataToGraphqlQuery(objectMetadataMapsMock, field),

View File

@ -1,11 +1,18 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { DateDisplayFormat } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.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 { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
const workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
const objectMetadataId = '20202020-6e2c-42f6-a83c-cc58d776af88';
export const OPPORTUNITY_WITH_FIELDS_MAPS = {
id: '20202020-6e2c-42f6-a83c-cc58d776af88',
id: objectMetadataId,
nameSingular: 'opportunity',
namePlural: 'opportunities',
labelSingular: 'Opportunity',
@ -21,12 +28,13 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSearchable: true,
labelIdentifierFieldMetadataId: '20202020-c2f1-4435-adca-22931f8b41b6',
imageIdentifierFieldMetadataId: null,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
indexMetadatas: [], // unused
workspaceId,
indexMetadatas: [],
fieldsById: {
'20202020-c2f1-4435-adca-22931f8b41b6': {
'20202020-c2f1-4435-adca-22931f8b41b6': getMockFieldMetadataEntity({
id: '20202020-c2f1-4435-adca-22931f8b41b6',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.TEXT,
name: 'name',
label: 'Name',
@ -38,14 +46,14 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: false,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-5eef-417a-b517-ebeedaa8e10b': {
}) as FieldMetadataEntity,
'20202020-5eef-417a-b517-ebeedaa8e10b': getMockFieldMetadataEntity({
id: '20202020-5eef-417a-b517-ebeedaa8e10b',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.CURRENCY,
name: 'amount',
label: 'Amount',
@ -57,14 +65,14 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-597c-44d3-98ec-ea71aea5256b': {
}) as FieldMetadataEntity,
'20202020-597c-44d3-98ec-ea71aea5256b': getMockFieldMetadataEntity({
id: '20202020-597c-44d3-98ec-ea71aea5256b',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.DATE_TIME,
name: 'closeDate',
label: 'Close date',
@ -76,14 +84,14 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-9b94-454a-94ca-8afb09c68faf': {
}) as FieldMetadataEntity,
'20202020-9b94-454a-94ca-8afb09c68faf': getMockFieldMetadataEntity({
id: '20202020-9b94-454a-94ca-8afb09c68faf',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.SELECT,
name: 'stage',
label: 'Stage',
@ -132,14 +140,14 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: false,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-30a5-4d8e-9b93-12d31ece0aaa': {
}) as FieldMetadataEntity,
'20202020-30a5-4d8e-9b93-12d31ece0aaa': getMockFieldMetadataEntity({
id: '20202020-30a5-4d8e-9b93-12d31ece0aaa',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.POSITION,
name: 'position',
label: 'Position',
@ -151,18 +159,18 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: true,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-f95f-424f-ab32-65961e8e9635': {
}) as FieldMetadataEntity,
'20202020-f95f-424f-ab32-65961e8e9635': getMockFieldMetadataEntity({
id: '20202020-f95f-424f-ab32-65961e8e9635',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.ACTOR,
name: 'createdBy',
label: 'Created by',
defaultValue: { name: "'System'", source: "'MANUAL'", context: {} },
defaultValue: { name: "'System'", source: "'MANUAL'" },
description: 'The creator of the record',
icon: 'IconCreativeCommonsSa',
isCustom: false,
@ -170,14 +178,14 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: false,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-5e10-4780-babb-38a465ac546c': {
}) as FieldMetadataEntity,
'20202020-5e10-4780-babb-38a465ac546c': getMockFieldMetadataEntity({
id: '20202020-5e10-4780-babb-38a465ac546c',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.TEXT,
name: 'searchVector',
label: 'Search vector',
@ -189,14 +197,14 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: true,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-8f4a-4f8d-822e-90fe72f75b79': {
}) as FieldMetadataEntity,
'20202020-8f4a-4f8d-822e-90fe72f75b79': getMockFieldMetadataEntity({
id: '20202020-8f4a-4f8d-822e-90fe72f75b79',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.UUID,
name: 'id',
label: 'Id',
@ -208,233 +216,233 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
isSystem: true,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-f120-4b59-b239-f7f1d8eb243e': {
}) as FieldMetadataEntity,
'20202020-f120-4b59-b239-f7f1d8eb243e': getMockFieldMetadataEntity({
id: '20202020-f120-4b59-b239-f7f1d8eb243e',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.DATE_TIME,
name: 'createdAt',
label: 'Creation date',
defaultValue: 'now',
description: 'Creation date',
icon: 'IconCalendar',
settings: { displayFormat: 'RELATIVE' } as any,
settings: { displayFormat: DateDisplayFormat.RELATIVE },
isCustom: false,
isActive: true,
isSystem: false,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: false,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-dcc8-4318-9756-b87377692561': {
}) as FieldMetadataEntity,
'20202020-dcc8-4318-9756-b87377692561': getMockFieldMetadataEntity({
id: '20202020-dcc8-4318-9756-b87377692561',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.DATE_TIME,
name: 'updatedAt',
label: 'Last update',
defaultValue: 'now',
description: 'Last time the record was changed',
icon: 'IconCalendarClock',
settings: { displayFormat: 'RELATIVE' } as any,
settings: { displayFormat: DateDisplayFormat.RELATIVE },
isCustom: false,
isActive: true,
isSystem: false,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: false,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-1694-4f8b-8760-61a5ff330022': {
}) as FieldMetadataEntity,
'20202020-1694-4f8b-8760-61a5ff330022': getMockFieldMetadataEntity({
id: '20202020-1694-4f8b-8760-61a5ff330022',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.DATE_TIME,
name: 'deletedAt',
label: 'Deleted at',
label: 'Deletion date',
defaultValue: null,
description: 'Date when the record was deleted',
icon: 'IconCalendarMinus',
settings: { displayFormat: 'RELATIVE' } as any,
description: 'Record deletion date',
icon: 'IconCalendarOff',
settings: { displayFormat: DateDisplayFormat.RELATIVE },
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
isLabelSyncedWithName: false,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-4f52-4dea-a116-723f9bf7f082': {
}) as FieldMetadataEntity,
'20202020-4f52-4dea-a116-723f9bf7f082': getMockFieldMetadataEntity({
id: '20202020-4f52-4dea-a116-723f9bf7f082',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.RELATION,
name: 'pointOfContact',
label: 'Point of Contact',
defaultValue: null,
description: 'Opportunity point of contact',
description: 'The point of contact for this opportunity',
icon: 'IconUser',
settings: {
onDelete: 'SET_NULL',
relationType: 'MANY_TO_ONE',
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'pointOfContactId',
} as any,
onDelete: RelationOnDeleteAction.CASCADE,
},
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
relationTargetFieldMetadataId: '20202020-a36b-4889-97d4-63a578423688',
relationTargetObjectMetadataId: '20202020-6799-4a38-92d3-8e844a7ea8ab',
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-fc02-4be2-be1a-e121daf5400d': {
}) as FieldMetadataEntity,
'20202020-fc02-4be2-be1a-e121daf5400d': getMockFieldMetadataEntity({
id: '20202020-fc02-4be2-be1a-e121daf5400d',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.RELATION,
name: 'company',
label: 'Company',
defaultValue: null,
description: 'Opportunity company',
description: 'The company this opportunity is associated with',
icon: 'IconBuildingSkyscraper',
settings: {
onDelete: 'SET_NULL',
relationType: 'MANY_TO_ONE',
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'companyId',
} as any,
onDelete: RelationOnDeleteAction.CASCADE,
},
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
relationTargetFieldMetadataId: '20202020-bd16-4f63-8165-0a7f5d78170d',
relationTargetObjectMetadataId: '20202020-0be8-4764-8e0d-7a2e1c66f78c',
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-fd9f-48f0-bd5f-5b0fec6a5de4': {
}) as FieldMetadataEntity,
'20202020-fd9f-48f0-bd5f-5b0fec6a5de4': getMockFieldMetadataEntity({
id: '20202020-fd9f-48f0-bd5f-5b0fec6a5de4',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.RELATION,
name: 'favorites',
label: 'Favorites',
defaultValue: null,
description: 'Favorites linked to the opportunity',
icon: 'IconHeart',
settings: { relationType: RelationType.ONE_TO_MANY } as any,
description: 'Users who favorited this opportunity',
icon: 'IconStar',
settings: {
relationType: RelationType.ONE_TO_MANY,
onDelete: RelationOnDeleteAction.CASCADE,
},
isCustom: false,
isActive: true,
isSystem: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
relationTargetFieldMetadataId: '20202020-7c3c-4149-b400-5a958910c7a2',
relationTargetObjectMetadataId: '20202020-df10-42dc-baed-ea77db7ad96c',
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-88ab-4138-98ce-80533bb423e3': {
}) as FieldMetadataEntity,
'20202020-88ab-4138-98ce-80533bb423e3': getMockFieldMetadataEntity({
id: '20202020-88ab-4138-98ce-80533bb423e3',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.RELATION,
name: 'taskTargets',
label: 'Tasks',
label: 'Task Targets',
defaultValue: null,
description: 'Tasks tied to the opportunity',
description: 'Tasks targeting this opportunity',
icon: 'IconCheckbox',
settings: { relationType: RelationType.ONE_TO_MANY } as any,
settings: {
relationType: RelationType.ONE_TO_MANY,
onDelete: RelationOnDeleteAction.CASCADE,
},
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: false,
relationTargetFieldMetadataId: '20202020-eb77-4b1c-b6a6-d5dcd13b1634',
relationTargetObjectMetadataId: '20202020-3af1-4c4f-90f4-cd43c53f7f41',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-4258-422b-b35b-db3f090af8da': {
}) as FieldMetadataEntity,
'20202020-4258-422b-b35b-db3f090af8da': getMockFieldMetadataEntity({
id: '20202020-4258-422b-b35b-db3f090af8da',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.RELATION,
name: 'noteTargets',
label: 'Notes',
label: 'Note Targets',
defaultValue: null,
description: 'Notes tied to the opportunity',
description: 'Notes targeting this opportunity',
icon: 'IconNotes',
settings: { relationType: RelationType.ONE_TO_MANY } as any,
settings: {
relationType: RelationType.ONE_TO_MANY,
onDelete: RelationOnDeleteAction.CASCADE,
},
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: false,
relationTargetFieldMetadataId: '20202020-d927-4c91-9893-28cea5aff979',
relationTargetObjectMetadataId: '20202020-8a5e-4dea-868b-71611e718a73',
isLabelSyncedWithName: true,
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-16ca-40a7-a1ba-712975c916cd': {
}) as FieldMetadataEntity,
'20202020-16ca-40a7-a1ba-712975c916cd': getMockFieldMetadataEntity({
id: '20202020-16ca-40a7-a1ba-712975c916cd',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.RELATION,
name: 'attachments',
label: 'Attachments',
defaultValue: null,
description: 'Attachments linked to the opportunity',
icon: 'IconFileImport',
settings: { relationType: RelationType.ONE_TO_MANY } as any,
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,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
relationTargetFieldMetadataId: '20202020-9236-427a-8a8a-a2296b93b542',
relationTargetObjectMetadataId: '20202020-3005-4c93-a04c-2941f7424f54',
createdAt: new Date('2025-06-27T12:55:13.271Z'),
updatedAt: new Date('2025-06-27T12:55:13.271Z'),
},
'20202020-92a5-47bf-a38d-c1c72b2c3e4d': {
}) as FieldMetadataEntity,
'20202020-92a5-47bf-a38d-c1c72b2c3e4d': getMockFieldMetadataEntity({
id: '20202020-92a5-47bf-a38d-c1c72b2c3e4d',
objectMetadataId: '20202020-6e2c-42f6-a83c-cc58d776af88',
workspaceId,
objectMetadataId,
type: FieldMetadataType.RELATION,
name: 'timelineActivities',
label: 'Timeline Activities',
defaultValue: null,
description: 'Timeline Activities linked to the opportunity.',
icon: 'IconTimelineEvent',
settings: { relationType: RelationType.ONE_TO_MANY } as any,
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,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
relationTargetFieldMetadataId: '20202020-2d54-41c7-b886-29deca3c28d5',
relationTargetObjectMetadataId: '20202020-a89e-4e4d-b3d9-c3f99e7c7483',
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',

View File

@ -1,31 +1,32 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldSelectMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { checkFilterEnumValues } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-enum-values';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
describe('checkFilterEnumValues', () => {
const completeFieldSelectMock: FieldMetadataInterface = {
const completeFieldSelectMock = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: 'field-select-id',
type: fieldSelectMock.type,
name: fieldSelectMock.name,
label: 'Field Select',
objectMetadataId: 'object-metadata-id',
isNullable: fieldSelectMock.isNullable,
defaultValue: fieldSelectMock.defaultValue,
options: fieldSelectMock.options,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
});
const fieldsById: FieldMetadataMap = {
'field-select-id': completeFieldSelectMock,
'field-select-id': completeFieldSelectMock as FieldMetadataEntity,
};
const mockObjectMetadataWithFieldMaps = {

View File

@ -1,44 +1,46 @@
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
fieldTextMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
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';
describe('parseFilter', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
const completeFieldNumberMock = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: '20202020-0000-0000-0000-000000000002',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
});
const completeFieldTextMock: FieldMetadataInterface = {
id: 'field-text-id',
const completeFieldTextMock = getMockFieldMetadataEntity({
workspaceId: '20202020-0000-0000-0000-000000000000',
objectMetadataId: '20202020-0000-0000-0000-000000000001',
id: '20202020-0000-0000-0000-000000000003',
type: fieldTextMock.type,
name: fieldTextMock.name,
label: 'Field Text',
objectMetadataId: 'object-metadata-id',
isNullable: fieldTextMock.isNullable,
defaultValue: fieldTextMock.defaultValue,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
});
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
'field-text-id': completeFieldTextMock,
'field-number-id': completeFieldNumberMock as FieldMetadataEntity,
'field-text-id': completeFieldTextMock as FieldMetadataEntity,
};
const mockObjectMetadataWithFieldMaps: ObjectMetadataItemWithFieldMaps = {
@ -59,7 +61,7 @@ describe('parseFilter', () => {
mockObjectMetadataWithFieldMaps,
),
).toEqual({
and: [{ fieldNumber: { eq: 1 } }, { fieldNumber: { eq: 2 } }],
and: [{ fieldNumber: { eq: '1' } }, { fieldNumber: { eq: '2' } }],
});
});
@ -71,8 +73,8 @@ describe('parseFilter', () => {
),
).toEqual({
and: [
{ fieldNumber: { eq: 1 } },
{ or: [{ fieldNumber: { eq: 2 } }, { fieldNumber: { eq: 3 } }] },
{ fieldNumber: { eq: '1' } },
{ or: [{ fieldNumber: { eq: '2' } }, { fieldNumber: { eq: '3' } }] },
],
});
});
@ -85,15 +87,17 @@ describe('parseFilter', () => {
),
).toEqual({
and: [
{ fieldNumber: { eq: 1 } },
{ fieldNumber: { eq: '1' } },
{
or: [
{ fieldNumber: { eq: 2 } },
{ fieldNumber: { eq: 3 } },
{ and: [{ fieldNumber: { eq: 6 } }, { fieldNumber: { eq: 7 } }] },
{ fieldNumber: { eq: '2' } },
{ fieldNumber: { eq: '3' } },
{
and: [{ fieldNumber: { eq: '6' } }, { fieldNumber: { eq: '7' } }],
},
],
},
{ or: [{ fieldNumber: { eq: 4 } }, { fieldNumber: { eq: 5 } }] },
{ or: [{ fieldNumber: { eq: '4' } }, { fieldNumber: { eq: '5' } }] },
],
});
});
@ -113,13 +117,13 @@ describe('parseFilter', () => {
{ not: { fieldText: { startsWith: 'val' } } },
{
and: [
{ fieldNumber: { eq: 6 } },
{ fieldNumber: { eq: '6' } },
{ fieldText: { ilike: '%val%' } },
],
},
],
},
{ or: [{ fieldNumber: { eq: 4 } }, { fieldText: { is: 'NULL' } }] },
{ or: [{ fieldNumber: { eq: '4' } }, { fieldText: { is: 'NULL' } }] },
],
});
});
@ -132,9 +136,9 @@ describe('parseFilter', () => {
),
).toEqual({
and: [
{ fieldNumber: { eq: 1 } },
{ fieldNumber: { eq: '1' } },
{
not: { fieldNumber: { eq: 2 } },
not: { fieldNumber: { eq: '2' } },
},
],
});

View File

@ -1,7 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldCurrencyMock,
fieldNumberMock,
@ -9,11 +7,16 @@ import {
objectMetadataMapItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
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';
describe('FilterInputFactory', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
const workspaceId = '20202020-cc80-4306-ad69-da9e11997292';
const completeFieldNumberMock = getMockFieldMetadataEntity({
workspaceId,
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
@ -24,9 +27,10 @@ describe('FilterInputFactory', () => {
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
}) as FieldMetadataEntity;
const completeFieldTextMock: FieldMetadataInterface = {
const completeFieldTextMock = getMockFieldMetadataEntity({
workspaceId,
id: 'field-text-id',
type: fieldTextMock.type,
name: fieldTextMock.name,
@ -37,9 +41,10 @@ describe('FilterInputFactory', () => {
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
}) as FieldMetadataEntity;
const completeFieldCurrencyMock: FieldMetadataInterface = {
const completeFieldCurrencyMock = getMockFieldMetadataEntity({
workspaceId,
id: 'field-currency-id',
type: fieldCurrencyMock.type,
name: fieldCurrencyMock.name,
@ -50,7 +55,7 @@ describe('FilterInputFactory', () => {
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
};
}) as FieldMetadataEntity;
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,