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

@ -1,10 +1,15 @@
import { FieldMetadataType } from 'twenty-shared/types';
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 { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
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';
export const FIELD_LINKS_MOCK_NAME = 'fieldLinks';
export const FIELD_CURRENCY_MOCK_NAME = 'fieldCurrency';
@ -13,194 +18,292 @@ export const FIELD_ACTOR_MOCK_NAME = 'fieldActor';
export const FIELD_FULL_NAME_MOCK_NAME = 'fieldFullName';
export const FIELD_PHONES_MOCK_NAME = 'fieldPhones';
export const fieldNumberMock = {
const workspaceId = '20202020-0000-0000-0000-000000000000';
const objectMetadataId = '20202020-0000-0000-0000-000000000001';
export const fieldNumberMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldNumberId',
name: 'fieldNumber',
type: FieldMetadataType.NUMBER,
label: 'Field Number',
isNullable: false,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
export const fieldTextMock = {
export const fieldTextMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldTextId',
name: 'fieldText',
type: FieldMetadataType.TEXT,
label: 'Field Text',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
export const fieldCurrencyMock = {
export const fieldCurrencyMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldCurrencyId',
name: FIELD_CURRENCY_MOCK_NAME,
type: FieldMetadataType.CURRENCY,
label: 'Field Currency',
isNullable: true,
defaultValue: { amountMicros: null, currencyCode: "''" },
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
export const fieldSelectMock = {
export const fieldSelectMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldSelectId',
name: 'fieldSelect',
type: FieldMetadataType.SELECT,
label: 'Field Select',
isNullable: true,
defaultValue: 'OPTION_1',
options: [
{
id: '9a519a86-422b-4598-88ae-78751353f683',
color: 'red',
label: 'Opt 1',
value: 'OPTION_1',
position: 0,
color: 'red',
},
{
id: '33f28d51-bc82-4e1d-ae4b-d9e4c0ed0ab4',
color: 'purple',
label: 'Opt 2',
value: 'OPTION_2',
position: 1,
color: 'purple',
},
],
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldMultiSelectMock = {
export const fieldMultiSelectMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldMultiSelectId',
name: 'fieldMultiSelect',
type: FieldMetadataType.MULTI_SELECT,
label: 'Field Multi Select',
isNullable: true,
defaultValue: "{'OPTION_1'}",
defaultValue: ['OPTION_1'],
options: [
{
id: '9a519a86-422b-4598-88ae-78751353f683',
color: 'red',
label: 'Opt 1',
value: 'OPTION_1',
position: 0,
color: 'red',
},
{
id: '33f28d51-bc82-4e1d-ae4b-d9e4c0ed0ab4',
color: 'purple',
label: 'Opt 2',
value: 'OPTION_2',
position: 1,
color: 'purple',
},
],
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
export const fieldRelationMock = {
export const fieldRelationMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldRelationId',
name: 'fieldRelation',
type: FieldMetadataType.RELATION,
label: 'Field Relation',
isNullable: true,
defaultValue: null,
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'fieldRelationId',
onDelete: 'CASCADE',
onDelete: RelationOnDeleteAction.CASCADE,
},
relationTargetObjectMetadata: {
id: 'relationTargetObjectId',
nameSingular: 'relationTargetObject',
namePlural: 'relationTargetObjects',
},
} as ObjectMetadataEntity,
relationTargetFieldMetadata: {
id: 'relationTargetFieldId',
name: 'relationTargetField',
},
isNullable: true,
defaultValue: null,
};
} as FieldMetadataEntity,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldLinksMock = {
export const fieldLinksMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldLinksId',
name: FIELD_LINKS_MOCK_NAME,
type: FieldMetadataType.LINKS,
label: 'Field Links',
isNullable: false,
defaultValue: [
{ primaryLinkLabel: '', primaryLinkUrl: '', secondaryLinks: [] },
],
};
defaultValue: {
primaryLinkLabel: '',
primaryLinkUrl: '',
secondaryLinks: [],
},
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldUuidMock = {
export const fieldUuidMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldUuidId',
name: 'fieldUuid',
type: FieldMetadataType.UUID,
label: 'Field UUID',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldDateTimeMock = {
export const fieldDateTimeMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldDateTimeId',
name: 'fieldDateTime',
type: FieldMetadataType.DATE_TIME,
label: 'Field Date Time',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldDateMock = {
export const fieldDateMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldDateId',
name: 'fieldDate',
type: FieldMetadataType.DATE,
label: 'Field Date',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldBooleanMock = {
export const fieldBooleanMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldBooleanId',
name: 'fieldBoolean',
type: FieldMetadataType.BOOLEAN,
label: 'Field Boolean',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldNumericMock = {
export const fieldNumericMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldNumericId',
name: 'fieldNumeric',
type: FieldMetadataType.NUMERIC,
label: 'Field Numeric',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldFullNameMock = {
export const fieldFullNameMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldFullNameId',
name: FIELD_FULL_NAME_MOCK_NAME,
type: FieldMetadataType.FULL_NAME,
label: 'Field Full Name',
isNullable: true,
defaultValue: { firstName: '', lastName: '' },
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldRatingMock = {
export const fieldRatingMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldRatingId',
name: 'fieldRating',
type: FieldMetadataType.RATING,
label: 'Field Rating',
isNullable: true,
defaultValue: 'RATING_1',
options: [
{
id: '9a519a86-422b-4598-88ae-78751353f683',
color: 'red',
label: 'Opt 1',
value: 'RATING_1',
position: 0,
color: 'red',
},
{
id: '33f28d51-bc82-4e1d-ae4b-d9e4c0ed0ab4',
color: 'purple',
label: 'Opt 2',
value: 'RATING_2',
position: 1,
color: 'purple',
},
],
};
] as FieldMetadataComplexOption[],
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldPositionMock = {
export const fieldPositionMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldPositionId',
name: 'fieldPosition',
type: FieldMetadataType.POSITION,
label: 'Field Position',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldAddressMock = {
export const fieldAddressMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldAddressId',
name: FIELD_ADDRESS_MOCK_NAME,
type: FieldMetadataType.ADDRESS,
label: 'Field Address',
isNullable: true,
defaultValue: {
addressStreet1: '',
@ -212,65 +315,105 @@ const fieldAddressMock = {
addressLat: null,
addressLng: null,
},
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldRawJsonMock = {
export const fieldRawJsonMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldRawJsonId',
name: 'fieldRawJson',
type: FieldMetadataType.RAW_JSON,
label: 'Field Raw JSON',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldRichTextMock = {
export const fieldRichTextMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldRichTextId',
name: 'fieldRichText',
type: FieldMetadataType.RICH_TEXT,
label: 'Field Rich Text',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldActorMock = {
export const fieldActorMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldActorId',
name: FIELD_ACTOR_MOCK_NAME,
type: FieldMetadataType.ACTOR,
label: 'Field Actor',
isNullable: true,
defaultValue: {
source: FieldActorSource.MANUAL,
name: '',
},
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldEmailsMock = {
export const fieldEmailsMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldEmailsId',
name: 'fieldEmails',
type: FieldMetadataType.EMAILS,
label: 'Field Emails',
isNullable: false,
defaultValue: [{ primaryEmail: '', additionalEmails: {} }],
};
defaultValue: {
primaryEmail: '',
additionalEmails: {},
},
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldArrayMock = {
export const fieldArrayMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldArrayId',
name: 'fieldArray',
type: FieldMetadataType.ARRAY,
label: 'Field Array',
isNullable: true,
defaultValue: null,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
const fieldPhonesMock = {
export const fieldPhonesMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldPhonesId',
name: FIELD_PHONES_MOCK_NAME,
type: FieldMetadataType.PHONES,
label: 'Field Phones',
isNullable: false,
defaultValue: [
{
primaryPhoneNumber: '',
primaryPhoneCountryCode: '',
primaryPhoneCallingCode: '',
additionalPhones: {},
},
],
};
defaultValue: {
primaryPhoneNumber: '',
primaryPhoneCountryCode: '',
primaryPhoneCallingCode: '',
additionalPhones: {},
},
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
export const fields = [
fieldUuidMock,
@ -297,46 +440,47 @@ export const fields = [
fieldArrayMock,
];
export const objectMetadataItemMock = {
targetTableName: 'testingObject',
id: 'mockObjectId',
export const objectMetadataItemMock: ObjectMetadataEntity = {
id: objectMetadataId,
workspaceId,
nameSingular: 'objectName',
namePlural: 'objectsName',
namePlural: 'objectNames',
labelSingular: 'Object Name',
labelPlural: 'Object Names',
description: 'Object description',
icon: 'Icon123',
targetTableName: 'DEPRECATED',
isCustom: false,
isRemote: false,
isActive: true,
isSystem: false,
isAuditLogged: true,
isSearchable: true,
fields,
createdAt: new Date(),
updatedAt: new Date(),
} as ObjectMetadataEntity;
export const objectMetadataMapItemMock = {
id: 'mockObjectId',
icon: 'Icon123',
nameSingular: 'objectName',
namePlural: 'objectsName',
fieldsById: fields.reduce((acc, field) => {
// @ts-expect-error legacy noImplicitAny
acc[field.id] = field;
return acc;
}, {}),
fieldIdByName: fields.reduce((acc, field) => {
// @ts-expect-error legacy noImplicitAny
acc[field.name] = field;
return acc;
}, {}),
export const objectMetadataMapItemMock: ObjectMetadataItemWithFieldMaps = {
...objectMetadataItemMock,
fieldsById: fields.reduce(
(acc, field) => ({
...acc,
[field.id]: field,
}),
{},
),
fieldIdByName: fields.reduce(
(acc, field) => ({
...acc,
[field.name]: field.id,
}),
{},
),
fieldIdByJoinColumnName: {},
labelSingular: 'Object',
labelPlural: 'Objects',
workspaceId: 'mockWorkspaceId',
isCustom: false,
isSystem: false,
targetTableName: '',
indexMetadatas: [],
isActive: true,
isRemote: false,
isAuditLogged: false,
isSearchable: false,
} satisfies ObjectMetadataItemWithFieldMaps;
export const objectMetadataMapsMock = {
};
export const objectMetadataMapsMock: ObjectMetadataMaps = {
byId: {
[objectMetadataMapItemMock.id || 'mock-id']: objectMetadataMapItemMock,
},

View File

@ -1,7 +1,11 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
const workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
export const mockPersonObjectMetadataWithFieldMaps = (
duplicateCriteria: WorkspaceEntityDuplicateCriteria[],
@ -24,7 +28,7 @@ export const mockPersonObjectMetadataWithFieldMaps = (
duplicateCriteria: duplicateCriteria,
labelIdentifierFieldMetadataId: '',
imageIdentifierFieldMetadataId: '',
workspaceId: '',
workspaceId,
indexMetadatas: [],
fieldIdByName: {
name: 'name-id',
@ -34,9 +38,10 @@ export const mockPersonObjectMetadataWithFieldMaps = (
},
fieldIdByJoinColumnName: {},
fieldsById: {
'name-id': {
id: '',
'name-id': getMockFieldMetadataEntity({
workspaceId,
objectMetadataId: '',
id: 'name-id',
type: FieldMetadataType.FULL_NAME,
name: 'name',
label: 'Name',
@ -44,18 +49,18 @@ export const mockPersonObjectMetadataWithFieldMaps = (
lastName: "''",
firstName: "''",
},
description: 'Contacts name',
description: "Contact's name",
isCustom: false,
isNullable: true,
isUnique: false,
workspaceId: '',
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
},
'emails-id': {
id: '',
}) as FieldMetadataEntity,
'emails-id': getMockFieldMetadataEntity({
workspaceId,
objectMetadataId: '',
id: 'emails-id',
type: FieldMetadataType.EMAILS,
name: 'emails',
label: 'Emails',
@ -63,49 +68,48 @@ export const mockPersonObjectMetadataWithFieldMaps = (
primaryEmail: "''",
additionalEmails: null,
},
description: 'Contacts Emails',
description: "Contact's Emails",
isCustom: false,
isNullable: true,
workspaceId: '',
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
},
'linkedinLink-id': {
id: '',
}) as FieldMetadataEntity,
'linkedinLink-id': getMockFieldMetadataEntity({
workspaceId,
objectMetadataId: '',
id: 'linkedinLink-id',
type: FieldMetadataType.LINKS,
name: 'linkedinLink',
label: 'Linkedin',
defaultValue: {
primaryLinkUrl: "''",
secondaryLinks: "'[]'",
secondaryLinks: [],
primaryLinkLabel: "''",
},
description: 'Contacts Linkedin account',
description: "Contact's Linkedin account",
isCustom: false,
isNullable: true,
isUnique: false,
workspaceId: '',
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
},
'jobTitle-id': {
id: '',
}) as FieldMetadataEntity,
'jobTitle-id': getMockFieldMetadataEntity({
workspaceId,
objectMetadataId: '',
id: 'jobTitle-id',
type: FieldMetadataType.TEXT,
name: 'jobTitle',
label: 'Job Title',
defaultValue: "''",
description: 'Contacts job title',
description: "Contact's job title",
isCustom: false,
isNullable: false,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
},
}) as FieldMetadataEntity,
},
});

View File

@ -1,6 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { GraphQLEnumType } from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
@ -54,11 +55,13 @@ export class EnumTypeDefinitionFactory {
): GraphQLEnumType {
// FixMe: It's a hack until Typescript get fixed on union types for reduce function
// https://github.com/microsoft/TypeScript/issues/36390
const enumOptions = transformEnumValue(fieldMetadata.options) as Array<
FieldMetadataDefaultOption | FieldMetadataComplexOption
>;
const enumOptions = transformEnumValue(
fieldMetadata.options ?? undefined,
) as
| Array<FieldMetadataDefaultOption | FieldMetadataComplexOption>
| undefined;
if (!enumOptions) {
if (!isDefined(enumOptions)) {
this.logger.error(
`Enum options are not defined for ${fieldMetadata.name}`,
{

View File

@ -117,7 +117,7 @@ export class RelationConnectInputTypeDefinitionFactory {
} else {
const scalarType = this.typeMapperService.mapToScalarType(
field.type,
field.settings,
field.settings ?? undefined,
field.name === 'id',
);

View File

@ -108,7 +108,7 @@ const getTypeFactoryOptions = <T extends FieldMetadataType>(
) => {
return isInputTypeDefinitionKind(kind)
? {
nullable: fieldMetadata.isNullable,
nullable: fieldMetadata.isNullable ?? undefined,
defaultValue: fieldMetadata.defaultValue,
isArray:
kind !== InputTypeDefinitionKind.Filter &&
@ -117,7 +117,7 @@ const getTypeFactoryOptions = <T extends FieldMetadataType>(
isIdField: fieldMetadata.name === 'id',
}
: {
nullable: fieldMetadata.isNullable,
nullable: fieldMetadata.isNullable ?? undefined,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
// Scalar type is already defined in the entity itself.

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,

View File

@ -4,7 +4,9 @@ import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder
import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
describe('computeCursorArgFilter', () => {
const objectMetadataItemWithFieldMaps = {
@ -30,39 +32,42 @@ describe('computeCursorArgFilter', () => {
fullName: 'fullname-id',
},
fieldsById: {
'name-id': {
type: FieldMetadataType.TEXT,
'name-id': getMockFieldMetadataEntity({
workspaceId: 'workspace-id',
objectMetadataId: 'object-id',
id: 'name-id',
type: FieldMetadataType.TEXT,
name: 'name',
label: 'Name',
objectMetadataId: 'object-id',
isLabelSyncedWithName: true,
isNullable: true,
createdAt: new Date(),
updatedAt: new Date(),
},
'age-id': {
type: FieldMetadataType.NUMBER,
}) as FieldMetadataEntity,
'age-id': getMockFieldMetadataEntity({
workspaceId: 'workspace-id',
objectMetadataId: 'object-id',
id: 'age-id',
type: FieldMetadataType.NUMBER,
name: 'age',
label: 'Age',
objectMetadataId: 'object-id',
isLabelSyncedWithName: true,
isNullable: true,
createdAt: new Date(),
updatedAt: new Date(),
},
'fullname-id': {
type: FieldMetadataType.FULL_NAME,
}) as FieldMetadataEntity,
'fullname-id': getMockFieldMetadataEntity({
workspaceId: 'workspace-id',
objectMetadataId: 'object-id',
id: 'fullname-id',
type: FieldMetadataType.FULL_NAME,
name: 'fullName',
label: 'Full Name',
objectMetadataId: 'object-id',
isLabelSyncedWithName: true,
isNullable: true,
createdAt: new Date(),
updatedAt: new Date(),
},
}) as FieldMetadataEntity,
},
} satisfies ObjectMetadataItemWithFieldMaps;