diff --git a/api/apps/api/src/modules/scenarios-features/scenario-features-gap-data.service.ts b/api/apps/api/src/modules/scenarios-features/scenario-features-gap-data.service.ts index 2cee30a418..2d38f772d0 100644 --- a/api/apps/api/src/modules/scenarios-features/scenario-features-gap-data.service.ts +++ b/api/apps/api/src/modules/scenarios-features/scenario-features-gap-data.service.ts @@ -1,4 +1,4 @@ -import { In, Repository, SelectQueryBuilder } from 'typeorm'; +import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FiltersSpecification } from 'nestjs-base-service'; @@ -45,6 +45,48 @@ export class ScenarioFeaturesGapDataService extends AppBaseService< return query; } + async extendFindAllQuery( + query: SelectQueryBuilder, + filters?: FiltersSpecification, + info?: UserSearchCriteria, + ): Promise> { + // DEBT same issue as + // `api/apps/api/src/modules/geo-features/geo-features.service.ts` + // https://github.com/Vizzuality/marxan-cloud/pull/572/files#diff-2b4e531cc05bd4686fb0fc6e5cbf9fd3e2684e40a818179750dc8095bc93d49dR138-R149 + + if (!info?.params?.searchPhrase) { + return query; + } + + const featuresIds = ( + await this.features + .createQueryBuilder('features') + .select('features.id') + .where( + new Brackets((orBuilder) => + orBuilder + .where('feature_class_name like :phrase', { + phrase: `%${info?.params?.searchPhrase}%`, + }) + .orWhere('alias like :phrase', { + phrase: `%${info?.params?.searchPhrase}%`, + }), + ), + ) + .getMany() + ).map((feature) => feature.id); + + if (featuresIds.length > 0) { + query.andWhere(`feature_id IN (:...featuresIds)`, { + featuresIds, + }); + } else { + query.andWhere(`false`); + } + + return query; + } + async extendFindAllResults( entitiesAndCount: [any[], number], ): Promise<[any[], number]> { diff --git a/api/apps/api/src/modules/scenarios-features/scenario-features-output-gap-data.service.ts b/api/apps/api/src/modules/scenarios-features/scenario-features-output-gap-data.service.ts index 1c75daaa9c..2d00b3ca3d 100644 --- a/api/apps/api/src/modules/scenarios-features/scenario-features-output-gap-data.service.ts +++ b/api/apps/api/src/modules/scenarios-features/scenario-features-output-gap-data.service.ts @@ -1,4 +1,4 @@ -import { In, Repository, SelectQueryBuilder } from 'typeorm'; +import { Brackets, In, Repository, SelectQueryBuilder } from 'typeorm'; import { BadRequestException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { @@ -6,12 +6,16 @@ import { JSONAPISerializerConfig, } from '../../utils/app-base.service'; -import { ScenarioFeaturesOutputGapData } from '@marxan/features'; +import { + ScenarioFeaturesGapData, + ScenarioFeaturesOutputGapData, +} from '@marxan/features'; import { UserSearchCriteria } from './search-criteria'; import { AppConfig } from '../../utils/config.utils'; import { DbConnections } from '@marxan-api/ormconfig.connections'; import { GeoFeature } from '../geo-features/geo-feature.api.entity'; import { isInt, isPositive } from 'class-validator'; +import { FiltersSpecification } from 'nestjs-base-service'; const scenarioFeaturesOutputGapDataFilterKeyNames = ['runId'] as const; type ScenarioFeaturesOutputGapDataFilterKeys = keyof Pick< @@ -69,6 +73,48 @@ export class ScenarioFeaturesOutputGapDataService extends AppBaseService< return query; } + async extendFindAllQuery( + query: SelectQueryBuilder, + filters?: FiltersSpecification, + info?: UserSearchCriteria, + ): Promise> { + // DEBT same issue as + // `api/apps/api/src/modules/geo-features/geo-features.service.ts` + // https://github.com/Vizzuality/marxan-cloud/pull/572/files#diff-2b4e531cc05bd4686fb0fc6e5cbf9fd3e2684e40a818179750dc8095bc93d49dR138-R149 + + if (!info?.params?.searchPhrase) { + return query; + } + + const featuresIds = ( + await this.features + .createQueryBuilder('features') + .select('features.id') + .where( + new Brackets((orBuilder) => + orBuilder + .where('feature_class_name like :phrase', { + phrase: `%${info?.params?.searchPhrase}%`, + }) + .orWhere('alias like :phrase', { + phrase: `%${info?.params?.searchPhrase}%`, + }), + ), + ) + .getMany() + ).map((feature) => feature.id); + + if (featuresIds.length > 0) { + query.andWhere(`feature_id IN (:...featuresIds)`, { + featuresIds, + }); + } else { + query.andWhere(`false`); + } + + return query; + } + async extendFindAllResults( entitiesAndCount: [any[], number], ): Promise<[any[], number]> { diff --git a/api/apps/api/src/modules/scenarios-features/search-criteria.ts b/api/apps/api/src/modules/scenarios-features/search-criteria.ts index a16c5a0dbb..341055cbdf 100644 --- a/api/apps/api/src/modules/scenarios-features/search-criteria.ts +++ b/api/apps/api/src/modules/scenarios-features/search-criteria.ts @@ -4,6 +4,7 @@ import { User } from '../users/user.api.entity'; export interface SearchCriteria extends InfoDTO { params?: { scenarioId?: string; + searchPhrase?: string; }; } diff --git a/api/apps/api/src/modules/scenarios/scenarios.controller.ts b/api/apps/api/src/modules/scenarios/scenarios.controller.ts index 6cbe90d26c..5f41956e4b 100644 --- a/api/apps/api/src/modules/scenarios/scenarios.controller.ts +++ b/api/apps/api/src/modules/scenarios/scenarios.controller.ts @@ -387,12 +387,14 @@ export class ScenariosController { async getScenarioFeaturesGapData( @Param('id', ParseUUIDPipe) id: string, @ProcessFetchSpecification() fetchSpecification: FetchSpecification, + @Query('q') featureClassAndAliasFilter?: string, ): Promise[]> { const result = await this.scenarioFeaturesGapDataService.findAllPaginated( fetchSpecification, { params: { scenarioId: id, + searchPhrase: featureClassAndAliasFilter, }, }, ); @@ -591,12 +593,14 @@ export class ScenariosController { async getScenarioFeaturesOutputGapData( @Param('id', ParseUUIDPipe) id: string, @ProcessFetchSpecification() fetchSpecification: FetchSpecification, + @Query('q') featureClassAndAliasFilter?: string, ): Promise[]> { const result = await this.scenarioFeaturesOutputGapDataService.findAllPaginated( fetchSpecification, { params: { scenarioId: id, + searchPhrase: featureClassAndAliasFilter, }, }, );