feat: add targetFieldMetadataId and migration script for relations (#9793)

Fix https://github.com/twentyhq/core-team-issues/issues/238 and
https://github.com/twentyhq/core-team-issues/issues/239
This commit is contained in:
Jérémy M
2025-01-22 17:01:54 +01:00
committed by GitHub
parent 984dc4dec0
commit b662609948
9 changed files with 260 additions and 0 deletions

View File

@ -0,0 +1,140 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command } from 'nest-commander';
import { FieldMetadataType } from 'twenty-shared';
import { Repository } from 'typeorm';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { isCommandLogger } from 'src/database/commands/logger';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
deduceRelationDirection,
RelationDirection,
} from 'src/engine/utils/deduce-relation-direction.util';
@Command({
name: 'upgrade-0.41:migrate-relations-to-field-metadata',
description: 'Migrate relations to field metadata',
})
export class MigrateRelationsToFieldMetadataCommand extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
) {
super(workspaceRepository);
}
async executeActiveWorkspacesCommand(
_passedParam: string[],
options: ActiveWorkspacesCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log('Running command to create many to one relations');
if (isCommandLogger(this.logger)) {
this.logger.setVerbose(options.verbose ?? false);
}
try {
for (const [index, workspaceId] of workspaceIds.entries()) {
await this.processWorkspace(workspaceId, index, workspaceIds.length);
}
this.logger.log(chalk.green('Command completed!'));
} catch (error) {
this.logger.log(chalk.red('Error in workspace'));
}
}
private async processWorkspace(
workspaceId: string,
index: number,
total: number,
): Promise<void> {
try {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const fieldMetadataCollection = (await this.fieldMetadataRepository.find({
where: { workspaceId, type: FieldMetadataType.RELATION },
relations: ['fromRelationMetadata', 'toRelationMetadata'],
})) as unknown as FieldMetadataEntity<FieldMetadataType.RELATION>[];
if (!fieldMetadataCollection.length) {
this.logger.log(
chalk.yellow(
`No relation field metadata found for workspace ${workspaceId}.`,
),
);
return;
}
const fieldMetadataToUpdateCollection = fieldMetadataCollection.map(
(fieldMetadata) => this.mapFieldMetadata(fieldMetadata),
);
if (fieldMetadataToUpdateCollection.length > 0) {
await this.fieldMetadataRepository.save(
fieldMetadataToUpdateCollection,
);
}
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
} catch {
this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`));
}
}
private mapFieldMetadata(
fieldMetadata: FieldMetadataEntity<FieldMetadataType.RELATION>,
): FieldMetadataEntity<FieldMetadataType.RELATION> {
const relationMetadata =
fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata;
const relationDirection = deduceRelationDirection(
fieldMetadata,
relationMetadata,
);
let relationType = relationMetadata.relationType as unknown as RelationType;
if (
relationDirection === RelationDirection.TO &&
relationType === RelationType.ONE_TO_MANY
) {
relationType = RelationType.MANY_TO_ONE;
}
const relationTargetFieldMetadataId =
relationDirection === RelationDirection.FROM
? relationMetadata.toFieldMetadataId
: relationMetadata.fromFieldMetadataId;
const relationTargetObjectMetadataId =
relationDirection === RelationDirection.FROM
? relationMetadata.toObjectMetadataId
: relationMetadata.fromObjectMetadataId;
return {
...fieldMetadata,
settings: {
relationType,
onDelete: relationMetadata.onDeleteAction,
},
relationTargetFieldMetadataId,
relationTargetObjectMetadataId,
};
}
}

View File

@ -5,6 +5,7 @@ import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version/0-41/0-41-migrate-relations-to-field-metadata.command';
import { SeedWorkflowViewsCommand } from 'src/database/commands/upgrade-version/0-41/0-41-seed-workflow-views.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
@ -19,6 +20,7 @@ export class UpgradeTo0_41Command extends ActiveWorkspacesCommandRunner {
protected readonly workspaceRepository: Repository<Workspace>,
private readonly seedWorkflowViewsCommand: SeedWorkflowViewsCommand,
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
private readonly migrateRelationsToFieldMetadata: MigrateRelationsToFieldMetadataCommand,
) {
super(workspaceRepository);
}
@ -44,5 +46,11 @@ export class UpgradeTo0_41Command extends ActiveWorkspacesCommandRunner {
options,
workspaceIds,
);
await this.migrateRelationsToFieldMetadata.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
}
}

View File

@ -1,11 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version/0-41/0-41-migrate-relations-to-field-metadata.command';
import { SeedWorkflowViewsCommand } from 'src/database/commands/upgrade-version/0-41/0-41-seed-workflow-views.command';
import { UpgradeTo0_41Command } from 'src/database/commands/upgrade-version/0-41/0-41-upgrade-version.command';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
import { SyncWorkspaceLoggerService } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/services/sync-workspace-logger.service';
@ -16,6 +18,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
TypeORMModule,
DataSourceModule,
ObjectMetadataModule,
@ -28,6 +31,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
SyncWorkspaceMetadataCommand,
SeedWorkflowViewsCommand,
UpgradeTo0_41Command,
MigrateRelationsToFieldMetadataCommand,
],
})
export class UpgradeTo0_41CommandModule {}

View File

@ -0,0 +1,55 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddRelationTargetFieldAndObjectToFieldMetadata1737561084251
implements MigrationInterface
{
name = 'AddRelationTargetFieldAndObjectToFieldMetadata1737561084251';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" ADD "relationTargetFieldMetadataId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" ADD CONSTRAINT "UQ_47a6c57e1652b6475f8248cff78" UNIQUE ("relationTargetFieldMetadataId")`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" ADD "relationTargetObjectMetadataId" uuid`,
);
await queryRunner.query(
`CREATE INDEX "IndexOnRelationTargetObjectMetadataId" ON "metadata"."fieldMetadata" ("relationTargetObjectMetadataId") `,
);
await queryRunner.query(
`CREATE INDEX "IndexOnRelationTargetFieldMetadataId" ON "metadata"."fieldMetadata" ("relationTargetFieldMetadataId") `,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" ADD CONSTRAINT "FK_47a6c57e1652b6475f8248cff78" FOREIGN KEY ("relationTargetFieldMetadataId") REFERENCES "metadata"."fieldMetadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" ADD CONSTRAINT "FK_6f6c87ec32cca956d8be321071c" FOREIGN KEY ("relationTargetObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" DROP CONSTRAINT "FK_6f6c87ec32cca956d8be321071c"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" DROP CONSTRAINT "FK_47a6c57e1652b6475f8248cff78"`,
);
await queryRunner.query(
`DROP INDEX "metadata"."IndexOnRelationTargetFieldMetadataId"`,
);
await queryRunner.query(
`DROP INDEX "metadata"."IndexOnRelationTargetObjectMetadataId"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "relationTargetObjectMetadataId"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" DROP CONSTRAINT "UQ_47a6c57e1652b6475f8248cff78"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "relationTargetFieldMetadataId"`,
);
}
}