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,14 +2,15 @@ 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 => ({ ) =>
getMockObjectMetadataItemWithFieldsMaps({
id: '', id: '',
icon: 'Icon123', icon: 'Icon123',
standardId: '', standardId: '',

View File

@ -5,13 +5,14 @@ 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 =
getMockObjectMetadataItemWithFieldsMaps({
id: objectMetadataId, id: objectMetadataId,
nameSingular: 'opportunity', nameSingular: 'opportunity',
namePlural: 'opportunities', namePlural: 'opportunities',
@ -468,4 +469,4 @@ export const OPPORTUNITY_WITH_FIELDS_MAPS = {
pointOfContactId: '20202020-4f52-4dea-a116-723f9bf7f082', pointOfContactId: '20202020-4f52-4dea-a116-723f9bf7f082',
companyId: '20202020-fc02-4be2-be1a-e121daf5400d', companyId: '20202020-fc02-4be2-be1a-e121daf5400d',
}, },
} as const satisfies ObjectMetadataItemWithFieldMaps; });

View File

@ -5,11 +5,12 @@ 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 =
getMockObjectMetadataItemWithFieldsMaps({
id: 'object-id', id: 'object-id',
workspaceId: 'workspace-id', workspaceId: 'workspace-id',
nameSingular: 'person', nameSingular: 'person',
@ -69,7 +70,7 @@ describe('computeCursorArgFilter', () => {
updatedAt: new Date(), updatedAt: new Date(),
}) as FieldMetadataEntity, }) 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,14 +1,13 @@
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',
@ -54,8 +53,8 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
name: 'nameFieldMetadataId', name: 'nameFieldMetadataId',
}, },
fieldIdByJoinColumnName: {}, fieldIdByJoinColumnName: {},
}, }),
{ getMockObjectMetadataItemWithFieldsMaps({
id: '20202020-c03c-45d6-a4b0-04afe1357c5c', id: '20202020-c03c-45d6-a4b0-04afe1357c5c',
standardId: '', standardId: '',
nameSingular: 'company', nameSingular: 'company',
@ -118,8 +117,8 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
domainName: 'domainNameFieldMetadataId', domainName: 'domainNameFieldMetadataId',
}, },
fieldIdByJoinColumnName: {}, fieldIdByJoinColumnName: {},
}, }),
{ getMockObjectMetadataItemWithFieldsMaps({
id: '20202020-3d75-4aab-bacd-ee176c5f63ca', id: '20202020-3d75-4aab-bacd-ee176c5f63ca',
standardId: '', standardId: '',
nameSingular: 'regular-custom-object', nameSingular: 'regular-custom-object',
@ -178,8 +177,8 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
imageIdentifierFieldName: 'imageIdentifierFieldMetadataId', imageIdentifierFieldName: 'imageIdentifierFieldMetadataId',
}, },
fieldIdByJoinColumnName: {}, fieldIdByJoinColumnName: {},
}, }),
{ getMockObjectMetadataItemWithFieldsMaps({
id: '20202020-540c-4397-b872-2a90ea2fb809', id: '20202020-540c-4397-b872-2a90ea2fb809',
standardId: '', standardId: '',
nameSingular: 'non-searchable-object', nameSingular: 'non-searchable-object',
@ -202,5 +201,5 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
fieldsById: {}, fieldsById: {},
fieldIdByName: {}, fieldIdByName: {},
fieldIdByJoinColumnName: {}, 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,7 +55,8 @@ describe('DatabaseEventTriggerListener', () => {
}, },
}, },
}, },
objectMetadataItemWithFieldsMaps: { objectMetadataItemWithFieldsMaps:
getMockObjectMetadataItemWithFieldsMaps({
id: 'test-object-metadata', id: 'test-object-metadata',
workspaceId: 'test-workspace', workspaceId: 'test-workspace',
nameSingular: 'testObject', nameSingular: 'testObject',
@ -62,9 +64,6 @@ describe('DatabaseEventTriggerListener', () => {
labelSingular: 'Test Object', labelSingular: 'Test Object',
labelPlural: 'Test Objects', labelPlural: 'Test Objects',
description: 'Test object for testing', description: 'Test object for testing',
fieldIdByJoinColumnName: {},
fieldsById: {},
fieldIdByName: {},
indexMetadatas: [], indexMetadatas: [],
targetTableName: 'test_objects', targetTableName: 'test_objects',
isSystem: false, isSystem: false,
@ -74,7 +73,10 @@ describe('DatabaseEventTriggerListener', () => {
isAuditLogged: true, isAuditLogged: true,
isSearchable: true, isSearchable: true,
icon: 'Icon123', icon: 'Icon123',
} satisfies ObjectMetadataItemWithFieldMaps, fieldIdByJoinColumnName: {},
fieldsById: {},
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,