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

@ -39,7 +39,8 @@ import {
import { BeforeUpdateOneField } from 'src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-fieldMetadata-dto.util';
import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util';
import { fromObjectMetadataEntityToObjectMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util';
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { isMorphRelationFieldMetadataType } from 'src/engine/utils/is-morph-relation-field-metadata-type.util';
@ -162,8 +163,10 @@ export class FieldMetadataResolver {
return {
type: fieldMetadata.settings.relationType,
sourceObjectMetadata,
targetObjectMetadata,
sourceObjectMetadata:
fromObjectMetadataEntityToObjectMetadataDto(sourceObjectMetadata),
targetObjectMetadata:
fromObjectMetadataEntityToObjectMetadataDto(targetObjectMetadata),
sourceFieldMetadata:
fromFieldMetadataEntityToFieldMetadataDto(sourceFieldMetadata),
targetFieldMetadata:
@ -203,8 +206,12 @@ export class FieldMetadataResolver {
return morphRelations.map<RelationDTO>((morphRelation) => ({
type: settings.relationType,
sourceObjectMetadata: morphRelation.sourceObjectMetadata,
targetObjectMetadata: morphRelation.targetObjectMetadata,
sourceObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto(
morphRelation.sourceObjectMetadata,
),
targetObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto(
morphRelation.targetObjectMetadata,
),
sourceFieldMetadata: fromFieldMetadataEntityToFieldMetadataDto(
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';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export interface ObjectMetadataInterface {
id: string;
standardId?: string | null;
workspaceId: string;
nameSingular: string;
namePlural: string;
labelSingular: string;
labelPlural: string;
description?: string;
icon: string;
targetTableName: string;
fields: FieldMetadataEntity[];
indexMetadatas: IndexMetadataInterface[];
isSystem: boolean;
isCustom: boolean;
isActive: boolean;
isRemote: boolean;
isAuditLogged: boolean;
isSearchable: boolean;
duplicateCriteria?: WorkspaceEntityDuplicateCriteria[];
labelIdentifierFieldMetadataId?: string | null;
imageIdentifierFieldMetadataId?: string | null;
}
export interface ObjectMetadataInterface extends ObjectMetadataEntity {}

View File

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

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;
@Field({ nullable: true })
description: string;
description?: string;
@Field({ nullable: true })
icon: string;
icon?: string;
@Field(() => ObjectStandardOverridesDTO, { nullable: true })
standardOverrides?: ObjectStandardOverridesDTO;
@Field({ nullable: true })
shortcut: string;
shortcut?: string;
@FilterableField()
isCustom: boolean;

View File

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

View File

@ -10,8 +10,6 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@ -29,7 +27,7 @@ import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permi
'namePlural',
'workspaceId',
])
export class ObjectMetadataEntity implements ObjectMetadataInterface {
export class ObjectMetadataEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@ -52,14 +50,17 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
labelPlural: string;
@Column({ nullable: true, type: 'text' })
description: string;
description: string | null;
@Column({ nullable: true })
icon: string;
@Column({ nullable: true, type: 'varchar' })
icon: string | null;
@Column({ type: 'jsonb', nullable: true })
standardOverrides?: ObjectStandardOverridesDTO;
standardOverrides: ObjectStandardOverridesDTO | null;
/**
* @deprecated
*/
@Column({ nullable: false })
targetTableName: string;
@ -82,16 +83,16 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
isSearchable: boolean;
@Column({ type: 'jsonb', nullable: true })
duplicateCriteria?: WorkspaceEntityDuplicateCriteria[];
duplicateCriteria: WorkspaceEntityDuplicateCriteria[] | null;
@Column({ nullable: true })
shortcut: string;
@Column({ nullable: true, type: 'varchar' })
shortcut: string | null;
@Column({ nullable: true, type: 'uuid' })
labelIdentifierFieldMetadataId?: string | null;
labelIdentifierFieldMetadataId: string | null;
@Column({ nullable: true, type: 'uuid' })
imageIdentifierFieldMetadataId?: string | null;
imageIdentifierFieldMetadataId: string | null;
@Column({ default: false })
isLabelSyncedWithName: boolean;

View File

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