Seed morph metadata (#14149)

in thie PR we create the tooling to allow the relation and morph
relation creation during the seed process.

We use the ObjectMetadataMaps in order to fasten the process

Fixes https://github.com/twentyhq/core-team-issues/issues/1179
This commit is contained in:
Guillim
2025-08-29 15:04:08 +02:00
committed by GitHub
parent e6c87a360d
commit f6843fcdef
5 changed files with 150 additions and 0 deletions
@@ -83,6 +83,10 @@ export const fromCachedFieldMetadataEntityToFlatFieldMetadata = <
!isCachedFieldMetadataEntityOfType(
relationTargetCachedFieldMetadata,
FieldMetadataType.RELATION,
) &&
!isCachedFieldMetadataEntityOfType(
relationTargetCachedFieldMetadata,
FieldMetadataType.MORPH_RELATION,
)
) {
throw new WorkspaceMetadataCacheException(
@@ -12,6 +12,7 @@ import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadat
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
@@ -38,6 +39,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
TypeOrmModule.forFeature([Workspace, ObjectMetadataEntity]),
ObjectPermissionModule,
WorkspacePermissionsCacheModule,
WorkspaceMetadataCacheModule,
],
exports: [DevSeederService],
providers: [
@@ -0,0 +1,23 @@
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { type FieldMetadataSeed } from 'src/engine/workspace-manager/dev-seeder/metadata/types/field-metadata-seed.type';
export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & {
targetObjectMetadataNames: string[];
})[] = [
{
type: FieldMetadataType.MORPH_RELATION,
label: 'Owner of the Pet (rocket or survey)',
name: 'owner',
icon: 'IconRelationManyToOne',
morphRelationsCreationPayload: [
{
type: RelationType.MANY_TO_ONE,
targetObjectMetadataId: 'to-be-resolved-later',
targetFieldLabel: 'Owned by',
targetFieldIcon: 'IconRelationOneToMany',
},
],
targetObjectMetadataNames: ['surveyResult', 'rocket'],
},
];
@@ -1,11 +1,15 @@
import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { DataSource } from 'typeorm';
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import {
SEED_APPLE_WORKSPACE_ID,
SEED_YCOMBINATOR_WORKSPACE_ID,
@@ -13,6 +17,7 @@ import {
import { COMPANY_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/company-custom-field-seeds.constant';
import { PERSON_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/person-custom-field-seeds.constant';
import { PET_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-field-seeds.constant';
import { PET_CUSTOM_RELATION_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-relation-field-seeds.constant';
import { SURVEY_RESULT_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/survey-results-field-seeds.constant';
import { PET_CUSTOM_OBJECT_SEED } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-objects/constants/pet-custom-object-seed.constant';
import { ROCKET_CUSTOM_OBJECT_SEED } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-objects/constants/rocket-custom-object-seed.constant';
@@ -26,6 +31,7 @@ export class DevSeederMetadataService {
constructor(
private readonly objectMetadataService: ObjectMetadataService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {}
@@ -35,6 +41,10 @@ export class DevSeederMetadataService {
{
objects: { seed: ObjectMetadataSeed; fields?: FieldMetadataSeed[] }[];
fields: { objectName: string; seeds: FieldMetadataSeed[] }[];
relations?: {
objectName: string;
seeds: (FieldMetadataSeed & { targetObjectMetadataNames: string[] })[];
}[];
}
> = {
[SEED_APPLE_WORKSPACE_ID]: {
@@ -50,6 +60,9 @@ export class DevSeederMetadataService {
{ objectName: 'company', seeds: COMPANY_CUSTOM_FIELD_SEEDS },
{ objectName: 'person', seeds: PERSON_CUSTOM_FIELD_SEEDS },
],
relations: [
{ objectName: 'pet', seeds: PET_CUSTOM_RELATION_FIELD_SEEDS },
],
},
[SEED_YCOMBINATOR_WORKSPACE_ID]: {
objects: [
@@ -169,4 +182,108 @@ export class DevSeederMetadataService {
schemaName: dataSourceMetadata.schema,
});
}
public async seedRelations({ workspaceId }: { workspaceId: string }) {
const config = this.workspaceConfigs[workspaceId];
if (!config) {
throw new Error(
`Workspace configuration not found for workspaceId: ${workspaceId}`,
);
}
if (!config.relations) {
return;
}
for (const relation of config.relations) {
await this.seedCustomRelation({
workspaceId,
relation,
});
}
}
private async seedCustomRelation({
workspaceId,
relation,
}: {
workspaceId: string;
relation: {
objectName: string;
seeds: (FieldMetadataSeed & { targetObjectMetadataNames: string[] })[];
};
}): Promise<void> {
const { objectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
{
workspaceId,
},
);
const relationFieldInputs = this.createFieldInputs({
relation,
objectMetadataMaps,
workspaceId,
});
await this.fieldMetadataService.createMany(relationFieldInputs);
}
private createFieldInputs({
relation,
objectMetadataMaps,
workspaceId,
}: {
relation: {
objectName: string;
seeds: (FieldMetadataSeed & { targetObjectMetadataNames: string[] })[];
};
objectMetadataMaps: ObjectMetadataMaps;
workspaceId: string;
}): CreateFieldInput[] {
const objectMetadataId =
objectMetadataMaps.idByNameSingular[relation.objectName];
if (!isDefined(objectMetadataId)) {
throw new Error(
`Object metadata id not found for: ${relation.objectName}`,
);
}
const relationFieldInputs = relation.seeds.map((seed) => ({
type: seed.type,
label: seed.label,
name: seed.name,
objectMetadataId,
workspaceId,
morphRelationsCreationPayload: seed.targetObjectMetadataNames.map(
(targetObjectMetadataName) => {
const targetObjectMetadataId =
objectMetadataMaps.idByNameSingular[targetObjectMetadataName];
if (!isDefined(targetObjectMetadataId)) {
throw new Error(
`Target object metadata id not found for: ${targetObjectMetadataName}`,
);
}
if (!isDefined(seed.morphRelationsCreationPayload)) {
throw new Error('Morph relations creation payload is not defined');
}
return {
type: seed.morphRelationsCreationPayload[0].type,
targetFieldLabel:
seed.morphRelationsCreationPayload[0].targetFieldLabel,
targetFieldIcon:
seed.morphRelationsCreationPayload[0].targetFieldIcon,
targetObjectMetadataId,
};
},
),
}));
return relationFieldInputs;
}
}
@@ -66,6 +66,10 @@ export class DevSeederService {
workspaceId,
});
await this.devSeederMetadataService.seedRelations({
workspaceId,
});
await this.devSeederPermissionsService.initPermissions(workspaceId);
await this.devSeederDataService.seed({