From e755bf040ee6da682970ffacfcf7209e0a2563cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Wed, 13 Apr 2022 11:59:25 +0100 Subject: [PATCH 1/2] feat: scenario cloning adjustments --- .../application/export-scenario.handler.ts | 32 +++++++-- .../scenario-metadata.piece-importer.ts | 71 +++++++++++++------ ...enario-metadata.piece-importer.e2e-spec.ts | 48 ++++++++++--- 3 files changed, 116 insertions(+), 35 deletions(-) diff --git a/api/apps/api/src/modules/clone/export/application/export-scenario.handler.ts b/api/apps/api/src/modules/clone/export/application/export-scenario.handler.ts index 4c1cd7fdb0..cecaf1849c 100644 --- a/api/apps/api/src/modules/clone/export/application/export-scenario.handler.ts +++ b/api/apps/api/src/modules/clone/export/application/export-scenario.handler.ts @@ -1,18 +1,19 @@ +import { ResourceKind } from '@marxan/cloning/domain'; import { CommandHandler, EventPublisher, IInferredCommandHandler, } from '@nestjs/cqrs'; - -import { ResourceKind } from '@marxan/cloning/domain'; -import { Export, ExportId } from '../domain'; - +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Scenario } from '../../../scenarios/scenario.api.entity'; +import { Export } from '../domain'; +import { ExportRepository } from './export-repository.port'; +import { ExportResourcePieces } from './export-resource-pieces.port'; import { ExportScenario, ExportScenarioCommandResult, } from './export-scenario.command'; -import { ExportResourcePieces } from './export-resource-pieces.port'; -import { ExportRepository } from './export-repository.port'; @CommandHandler(ExportScenario) export class ExportScenarioHandler @@ -21,8 +22,22 @@ export class ExportScenarioHandler private readonly resourcePieces: ExportResourcePieces, private readonly exportRepository: ExportRepository, private readonly eventPublisher: EventPublisher, + @InjectRepository(Scenario) + private readonly scenarioRepo: Repository, ) {} + private async createScenarioShell( + existingScenarioId: string, + newScenarioId: string, + ) { + const scenario = await this.scenarioRepo.findOneOrFail(existingScenarioId); + await this.scenarioRepo.save({ + id: newScenarioId, + name: '', + projectId: scenario.projectId, + }); + } + async execute({ scenarioId, projectId, @@ -39,6 +54,11 @@ export class ExportScenarioHandler exportRequest.commit(); + await this.createScenarioShell( + scenarioId.value, + exportRequest.importResourceId!.value, + ); + return { exportId: exportRequest.id, importResourceId: exportRequest.importResourceId!, diff --git a/api/apps/geoprocessing/src/import/pieces-importers/scenario-metadata.piece-importer.ts b/api/apps/geoprocessing/src/import/pieces-importers/scenario-metadata.piece-importer.ts index 60829ec076..9e54c97c20 100644 --- a/api/apps/geoprocessing/src/import/pieces-importers/scenario-metadata.piece-importer.ts +++ b/api/apps/geoprocessing/src/import/pieces-importers/scenario-metadata.piece-importer.ts @@ -29,6 +29,48 @@ export class ScenarioMetadataPieceImporter implements ImportPieceProcessor { return piece === ClonePiece.ScenarioMetadata; } + private updateScenario( + em: EntityManager, + scenarioId: string, + values: ScenarioMetadataContent, + ) { + return em + .createQueryBuilder() + .update('scenarios') + .set({ + id: scenarioId, + name: values.name, + description: values.description, + blm: values.blm, + number_of_runs: values.numberOfRuns, + metadata: values.metadata, + }) + .where('id = :scenarioId', { scenarioId }) + .execute(); + } + + private createScenario( + em: EntityManager, + scenarioId: string, + projectId: string, + values: ScenarioMetadataContent, + ) { + return em + .createQueryBuilder() + .insert() + .into('scenarios') + .values({ + id: scenarioId, + name: values.name, + description: values.description, + blm: values.blm, + number_of_runs: values.numberOfRuns, + metadata: values.metadata, + project_id: projectId, + }) + .execute(); + } + async run(input: ImportJobInput): Promise { const { pieceResourceId: scenarioId, @@ -66,31 +108,18 @@ export class ScenarioMetadataPieceImporter implements ImportPieceProcessor { throw new Error(errorMessage); } - const { - name, - blm, - description, - metadata, - numberOfRuns, - }: ScenarioMetadataContent = JSON.parse( + const metadata: ScenarioMetadataContent = JSON.parse( stringScenarioMetadataOrError.right, ); + const scenarioCloning = resourceKind === ResourceKind.Scenario; + await this.entityManager.transaction(async (em) => { - await em - .createQueryBuilder() - .insert() - .into('scenarios') - .values({ - id: scenarioId, - name, - description, - blm, - number_of_runs: numberOfRuns, - metadata, - project_id: projectId, - }) - .execute(); + if (scenarioCloning) { + await this.updateScenario(em, scenarioId, metadata); + } else { + await this.createScenario(em, scenarioId, projectId, metadata); + } if (resourceKind === ResourceKind.Scenario) { await em diff --git a/api/apps/geoprocessing/test/integration/cloning/piece-importers/scenario-metadata.piece-importer.e2e-spec.ts b/api/apps/geoprocessing/test/integration/cloning/piece-importers/scenario-metadata.piece-importer.e2e-spec.ts index 41323861b4..fad7d6b826 100644 --- a/api/apps/geoprocessing/test/integration/cloning/piece-importers/scenario-metadata.piece-importer.e2e-spec.ts +++ b/api/apps/geoprocessing/test/integration/cloning/piece-importers/scenario-metadata.piece-importer.e2e-spec.ts @@ -18,6 +18,7 @@ import { v4 } from 'uuid'; import { DeleteProjectAndOrganization, GivenProjectExists, + GivenScenarioExists, GivenUserExists, PrepareZipFile, } from '../fixtures'; @@ -49,17 +50,38 @@ describe(ScenarioMetadataPieceImporter, () => { it('fails when the file cannot be retrieved from file repo', async () => { const archiveLocation = fixtures.GivenNoScenarioMetadataFileIsAvailable(); - const input = fixtures.GivenJobInput(archiveLocation); + const input = fixtures.GivenJobInput(archiveLocation, ResourceKind.Project); await fixtures .WhenPieceImporterIsInvoked(input) .ThenADataNotAvailableErrorShouldBeThrown(); }); - it('imports scenario metadata', async () => { + it('imports scenario metadata creating a new scenario (project import process)', async () => { + const resourceKind = ResourceKind.Project; await fixtures.GivenProject(); await fixtures.GivenUser(); - const archiveLocation = await fixtures.GivenValidScenarioMetadataFile(); - const input = fixtures.GivenJobInput(archiveLocation); + + const archiveLocation = await fixtures.GivenValidScenarioMetadataFile( + resourceKind, + ); + const input = fixtures.GivenJobInput(archiveLocation, resourceKind); + await fixtures + .WhenPieceImporterIsInvoked(input) + .ThenScenarioMetadataShouldBeImported(); + }); + + it('imports scenario metadata updating an existing scenario (scenario cloning process)', async () => { + const resourceKind = ResourceKind.Scenario; + await fixtures.GivenScenario(); + await fixtures.GivenUser(); + + const archiveLocation = await fixtures.GivenValidScenarioMetadataFile( + resourceKind, + ); + const input = fixtures.GivenJobInput( + archiveLocation, + ResourceKind.Scenario, + ); await fixtures .WhenPieceImporterIsInvoked(input) .ThenScenarioMetadataShouldBeImported(); @@ -86,7 +108,6 @@ const getFixtures = async () => { const scenarioId = v4(); const projectId = v4(); const organizationId = v4(); - const resourceKind = ResourceKind.Project; const oldScenarioId = v4(); const userId = v4(); @@ -116,7 +137,18 @@ const getFixtures = async () => { GivenProject: () => { return GivenProjectExists(entityManager, projectId, organizationId); }, - GivenJobInput: (archiveLocation: ArchiveLocation): ImportJobInput => { + GivenScenario: () => { + return GivenScenarioExists( + entityManager, + scenarioId, + projectId, + organizationId, + ); + }, + GivenJobInput: ( + archiveLocation: ArchiveLocation, + resourceKind: ResourceKind, + ): ImportJobInput => { const [ uri, ] = ClonePieceUrisResolver.resolveFor( @@ -142,7 +174,7 @@ const getFixtures = async () => { importId: v4(), projectId, piece: ClonePiece.ScenarioMetadata, - resourceKind, + resourceKind: ResourceKind.Project, uris: [], ownerId: userId, }; @@ -150,7 +182,7 @@ const getFixtures = async () => { GivenNoScenarioMetadataFileIsAvailable: () => { return new ArchiveLocation('not found'); }, - GivenValidScenarioMetadataFile: async () => { + GivenValidScenarioMetadataFile: async (resourceKind: ResourceKind) => { const [ { relativePath }, ] = ClonePieceUrisResolver.resolveFor( From bc25d55c961ae06352df9685f3258a05e4baf2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Wed, 13 Apr 2022 12:50:51 +0100 Subject: [PATCH 2/2] fix: DI issue --- .../clone/export/application/export-application.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/apps/api/src/modules/clone/export/application/export-application.module.ts b/api/apps/api/src/modules/clone/export/application/export-application.module.ts index 4be150af65..627696bc04 100644 --- a/api/apps/api/src/modules/clone/export/application/export-application.module.ts +++ b/api/apps/api/src/modules/clone/export/application/export-application.module.ts @@ -8,6 +8,7 @@ import { import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Project } from '../../../projects/project.api.entity'; +import { Scenario } from '../../../scenarios/scenario.api.entity'; import { AllPiecesReadySaga } from './all-pieces-ready.saga'; import { CompleteExportPieceHandler } from './complete-export-piece.handler'; import { ExportProjectHandler } from './export-project.handler'; @@ -22,7 +23,7 @@ export class ExportApplicationModule { module: ExportApplicationModule, imports: [ CqrsModule, - TypeOrmModule.forFeature([Project]), + TypeOrmModule.forFeature([Project, Scenario]), ...(adapters ?? []), ], providers: [