Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api): keep only current specification data in the table for proce… #493

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { API_EVENT_KINDS } from '@marxan/api-events';
import { ApiEventsService } from '@marxan-api/modules/api-events/api-events.service';
import { SpecificationApiEntity } from '@marxan-api/modules/specification/adapters/specification.api.entity';
import { SpecificationActivated } from '../domain';
import { SpecificationProcessingFinishedEvent } from './specification-processing-finished.event';

@EventsHandler(SpecificationActivated)
@EventsHandler(SpecificationProcessingFinishedEvent)
export class SpecificationActivatedHandler
implements IEventHandler<SpecificationActivated> {
constructor(
private readonly apiEvents: ApiEventsService,
@InjectRepository(SpecificationApiEntity)
private readonly specifications: Repository<SpecificationApiEntity>,
) {}
implements IEventHandler<SpecificationProcessingFinishedEvent> {
constructor(private readonly apiEvents: ApiEventsService) {}

async handle(event: SpecificationActivated) {
const specification = await this.specifications.findOne(
event.specificationId.value,
);
if (!specification) return;
async handle(event: SpecificationProcessingFinishedEvent) {
await this.apiEvents.create({
kind: API_EVENT_KINDS.scenario__specification__finished__v1__alpha1,
topic: specification.scenarioId,
topic: event.scenarioId,
data: { ...event },
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IEvent } from '@nestjs/cqrs';

export class SpecificationProcessingFinishedEvent implements IEvent {
constructor(
public readonly scenarioId: string,
public readonly specificationId: string,
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const getFixtures = async () => {
},
ThenSpecificationIsActivated: () => {
expect(events).toEqual([
new SpecificationActivated(currentCandidateSpecificationId),
new SpecificationActivated(scenarioId, currentCandidateSpecificationId),
]);
},
ThenSpecificationIsNotActivated: async (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const getFixtures = () => {
expect(result.right).toEqual(void 0);
expect(scenarioSpecification.getUncommittedEvents()).toEqual([
new SpecificationActivated(
scenarioSpecification.scenarioId,
scenarioSpecification.currentActiveSpecification!,
),
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ import { IEvent } from '@nestjs/cqrs';
import { SpecificationId } from '../specification.id';

export class SpecificationActivated implements IEvent {
constructor(public readonly specificationId: SpecificationId) {}
constructor(
public readonly scenarioId: string,
public readonly specificationId: SpecificationId,
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class ScenarioSpecification extends AggregateRoot {

this.#active = this.#candidate;
this.#candidate = undefined;
this.apply(new SpecificationActivated(this.#active));
this.apply(new SpecificationActivated(this.scenarioId, this.#active));
return right(void 0);
}

Expand Down
2 changes: 2 additions & 0 deletions api/apps/api/src/modules/scenario-specification/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { ScenarioSpecificationModule } from './scenario-specification.module';
export { SpecificationActivated } from './domain';
export { SpecificationProcessingFinishedEvent } from './adapters/specification-processing-finished.event';
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class CopyOperation {

async copy(data: {
scenarioId: string;
specificationId: string;
input: FeatureConfigCopy;
}): Promise<{ id: string }[]> {
await this.events.create({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import { FeatureConfigCopy } from '@marxan-api/modules/specification';
@Injectable()
export class CopyQuery {
public prepareStatement(
command: { scenarioId: string; input: FeatureConfigCopy },
command: {
scenarioId: string;
specificationId: string;
input: FeatureConfigCopy;
},
planningAreaLocation: { id: string; tableName: string } | undefined,
protectedAreaFilterByIds: string[],
project: Pick<Project, 'bbox'>,
): { query: string; parameters: (string | number)[] } {
const parameters: (string | number)[] = [];
const fields = {
scenarioId: `$${parameters.push(command.scenarioId)}`,
specificationId: `$${parameters.push(command.specificationId)}`,
fpf: isDefined(command.input.fpf)
? `$${parameters.push(command.input.fpf)}`
: `NULL`,
Expand Down Expand Up @@ -63,15 +68,17 @@ export class CopyQuery {
? `left join ${planningAreaLocation.tableName} as pa on pa.id = ${fields.planningAreaId}`
: ``;
const query = `
insert into scenario_features_data as sfd (feature_class_id,
scenario_id,
fpf,
target,
prop,
total_area,
current_pa)
insert into scenario_features_preparation as sfp (feature_class_id,
scenario_id,
specification_id,
fpf,
target,
prop,
total_area,
current_pa)
select fd.id,
${fields.scenarioId},
${fields.specificationId},
${fields.fpf},
${fields.target},
${fields.prop},
Expand All @@ -88,7 +95,7 @@ export class CopyQuery {
${fields.bbox[3]},
4326
), fd.the_geom)
returning sfd.id as id;
returning sfp.id as id;
`;
return { parameters, query };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class CreateFeaturesHandler
case SpecificationOperation.Copy: {
const ids = await this.copyOperation.copy({
scenarioId: command.scenarioId,
specificationId: command.specificationId,
input: command.input,
});
this.eventBus.publish(
Expand All @@ -43,7 +44,8 @@ export class CreateFeaturesHandler
}
case SpecificationOperation.Split: {
const ids = await this.splitOperation.split({
...command,
scenarioId: command.scenarioId,
specificationId: command.specificationId,
input: command.input,
});
this.eventBus.publish(
Expand Down
1 change: 1 addition & 0 deletions api/apps/api/src/modules/scenarios-features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { ScenarioFeaturesModule } from './scenario-features.module';
export { ScenarioFeaturesService } from './scenario-features.service';
export { ScenariosFeaturesView } from './scenario-features.dto';
export { FeaturesCreated } from './features-created.event';
export { SpecificationProcessingFinishedEvent } from '@marxan-api/modules/scenario-specification/adapters/specification-processing-finished.event';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this event a part of adapters really?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really, but boundaries between two modules is artificial a bit, and I need to reflect the state of the specification by the event

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Command } from '@nestjs-architects/typed-cqrs';

export class MoveDataFromPreparationCommand extends Command<void> {
constructor(
public readonly scenarioId: string,
public readonly specificationId: string,
) {
super();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
CommandHandler,
EventBus,
IInferredCommandHandler,
} from '@nestjs/cqrs';
import { EntityManager } from 'typeorm';
import { InjectEntityManager } from '@nestjs/typeorm';
import {
ScenarioFeaturesPreparation,
ScenarioFeaturesData,
} from '@marxan/features';
import { DbConnections } from '@marxan-api/ormconfig.connections';
import { SpecificationProcessingFinishedEvent } from '@marxan-api/modules/scenario-specification';
import { MoveDataFromPreparationCommand } from './move-data-from-preparation.command';

@CommandHandler(MoveDataFromPreparationCommand)
export class MoveDataFromPreparationHandler
implements IInferredCommandHandler<MoveDataFromPreparationCommand> {
constructor(
@InjectEntityManager(DbConnections.geoprocessingDB)
private readonly entityManager: EntityManager,
private readonly eventBus: EventBus,
) {}

async execute(command: MoveDataFromPreparationCommand): Promise<void> {
await this.entityManager.transaction(async (transactionalEntityManager) => {
await transactionalEntityManager.delete(ScenarioFeaturesData, {
scenarioId: command.scenarioId,
});
await transactionalEntityManager.query(
`
insert into scenario_features_data as sfd (id,
feature_class_id,
scenario_id,
total_area,
current_pa,
fpf,
target,
prop,
target2,
targetocc,
sepnum,
created_by,
metadata,
specification_id)
select id,
feature_class_id,
scenario_id,
total_area,
current_pa,
fpf,
target,
prop,
target2,
targetocc,
sepnum,
created_by,
metadata,
specification_id
from scenario_features_preparation sfp
where sfp.specification_id = $1
`,
[command.specificationId],
);
await transactionalEntityManager.delete(ScenarioFeaturesPreparation, {
specificationId: command.specificationId,
});
});

this.eventBus.publish(
new SpecificationProcessingFinishedEvent(
command.scenarioId,
command.specificationId,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ICommand, ofType, Saga } from '@nestjs/cqrs';
import { Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SpecificationActivated } from '@marxan-api/modules/scenario-specification';
import { MoveDataFromPreparationCommand } from './move-data-from-preparation.command';

@Injectable()
export class MoveDataFromPreparationSaga {
@Saga()
specificationActivated = (events$: Observable<any>): Observable<ICommand> => {
return events$.pipe(
ofType(SpecificationActivated),
map(
(event) =>
new MoveDataFromPreparationCommand(
event.scenarioId,
event.specificationId.value,
),
),
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { CopyDataProvider, CopyOperation, CopyQuery } from './copy';
import { SplitDataProvider, SplitOperation, SplitQuery } from './split';
import { ScenarioFeaturesGapDataService } from './scenario-features-gap-data.service';
import { ScenarioFeaturesOutputGapDataService } from './scenario-features-output-gap-data.service';
import { MoveDataFromPreparationSaga } from './move-data-from-preparation.saga';
import { MoveDataFromPreparationHandler } from './move-data-from-preparation.handler';

@Module({
imports: [
Expand Down Expand Up @@ -49,6 +51,8 @@ import { ScenarioFeaturesOutputGapDataService } from './scenario-features-output
SplitQuery,
SplitDataProvider,
SplitOperation,
MoveDataFromPreparationSaga,
MoveDataFromPreparationHandler,
],
exports: [
ScenarioFeaturesService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ export class SplitOperation {
private readonly events: ApiEventsService,
) {}

async split(data: { scenarioId: string; input: FeatureConfigSplit }) {
async split(data: {
scenarioId: string;
specificationId: string;
input: FeatureConfigSplit;
}) {
await this.events.create({
topic: data.scenarioId,
kind: API_EVENT_KINDS.scenario__geofeatureSplit__submitted__v1__alpha1,
Expand All @@ -36,6 +40,7 @@ export class SplitOperation {
const { parameters, query } = this.splitQuery.prepareQuery(
data.input,
data.scenarioId,
data.specificationId,
planningAreaLocation,
protectedAreaFilterByIds,
project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class SplitQuery {
prepareQuery(
input: FeatureConfigSplit,
scenarioId: string,
specificationId: string,
planningAreaLocation: { id: string; tableName: string } | undefined,
protectedAreaFilterByIds: string[],
project: Pick<Project, 'bbox'>,
Expand All @@ -19,6 +20,7 @@ export class SplitQuery {
),
splitByProperty: `$${parameters.push(input.splitByProperty)}`,
scenarioId: `$${parameters.push(scenarioId)}`,
specificationId: `$${parameters.push(specificationId)}`,
planningAreaId: isDefined(planningAreaLocation)
? `$${parameters.push(planningAreaLocation.id)}`
: `NULL`,
Expand Down Expand Up @@ -53,13 +55,14 @@ export class SplitQuery {

const hasSubSetFilter = (input.selectSubSets ?? []).length > 0;
const query = `
insert into scenario_features_data as sfd (feature_class_id,
scenario_id,
fpf,
target,
prop,
total_area,
current_pa)
insert into scenario_features_preparation as sfp (feature_class_id,
scenario_id,
specification_id,
fpf,
target,
prop,
total_area,
current_pa)
WITH split as (
WITH subsets as (
select value as sub_value, target, fpf, prop
Expand All @@ -86,6 +89,7 @@ export class SplitQuery {
)
select fd.id,
${fields.scenarioId},
${fields.specificationId},
split.fpf,
split.target,
split.prop,
Expand All @@ -103,7 +107,7 @@ export class SplitQuery {
${fields.bbox[3]},
4326
), fd.the_geom)
returning sfd.id as id;
returning sfp.id as id;
`;
return { parameters, query };
}
Expand Down
Loading