diff --git a/apps/dashboard-service/src/dashboard/dashboard-query.builder.ts b/apps/dashboard-service/src/dashboard/dashboard-query.builder.ts index 697a9372..cb51625f 100644 --- a/apps/dashboard-service/src/dashboard/dashboard-query.builder.ts +++ b/apps/dashboard-service/src/dashboard/dashboard-query.builder.ts @@ -3,6 +3,7 @@ import { Model, ModelCtor } from "sequelize-typescript"; import { DashboardQueryDto } from "./dto/dashboard-query.dto"; import { isObject, flatten, isEmpty } from "lodash"; import { Project } from "@terramatch-microservices/database/entities"; +import { mapLandscapeCodesToNames } from "@terramatch-microservices/database/constants"; export class DashboardProjectsQueryBuilder = Project> { protected findOptions: FindOptions> = { @@ -46,7 +47,10 @@ export class DashboardProjectsQueryBuilder = Project> { if (!isEmpty(filters?.country)) where["country"] = filters.country; if (!isEmpty(filters?.programmes)) where["frameworkKey"] = { [Op.in]: [filters.programmes] }; if (!isEmpty(filters?.cohort)) where["cohort"] = filters.cohort; - if (!isEmpty(filters?.landscapes)) where["landscape"] = { [Op.in]: [filters.landscapes] }; + if (filters?.landscapes != null && filters.landscapes.length > 0) { + const landscapeNames = mapLandscapeCodesToNames(filters.landscapes); + where["landscape"] = { [Op.in]: landscapeNames }; + } if (!isEmpty(filters?.organisationType)) organisationWhere["type"] = { [Op.in]: [filters.organisationType] }; if (!isEmpty(filters?.projectUuid)) where["uuid"] = Array.isArray(filters.projectUuid) ? { [Op.in]: filters.projectUuid } : filters.projectUuid; diff --git a/apps/dashboard-service/src/dashboard/dto/cache.service.spec.ts b/apps/dashboard-service/src/dashboard/dto/cache.service.spec.ts index ece37cee..1f1b2562 100644 --- a/apps/dashboard-service/src/dashboard/dto/cache.service.spec.ts +++ b/apps/dashboard-service/src/dashboard/dto/cache.service.spec.ts @@ -148,7 +148,7 @@ describe("CacheService", () => { it("should build correct cache key with full query", () => { const query: DashboardQueryDto = { programmes: ["prog2", "prog1"], - landscapes: ["land2", "land1"], + landscapes: ["grv", "gcb"], country: "USA", organisationType: ["non-profit-organization", "for-profit-organization"], cohort: "cohort2025", @@ -156,7 +156,7 @@ describe("CacheService", () => { }; const key = service.getCacheKeyFromQuery(query); - expect(key).toBe("prog1,prog2|land1,land2|USA|all-orgs|cohort2025|uuid-123"); + expect(key).toBe("prog1,prog2|gcb,grv|USA|all-orgs|cohort2025|uuid-123"); }); it("should build cache key with missing fields", () => { diff --git a/apps/dashboard-service/src/dashboard/dto/dashboard-query.dto.ts b/apps/dashboard-service/src/dashboard/dto/dashboard-query.dto.ts index d2b0c250..c3e91a05 100644 --- a/apps/dashboard-service/src/dashboard/dto/dashboard-query.dto.ts +++ b/apps/dashboard-service/src/dashboard/dto/dashboard-query.dto.ts @@ -34,7 +34,8 @@ export class DashboardQueryDto { isArray: true, required: false, name: "landscapes", - description: "Filter results by landscapes" + description: + "Filter results by landscapes using 3-letter codes: gcb (Ghana Cocoa Belt), grv (Greater Rift Valley of Kenya), ikr (Lake Kivu & Rusizi River Basin)" }) @IsOptional() @IsArray() diff --git a/apps/dashboard-service/src/dashboard/dto/total-section-header.service.spec.ts b/apps/dashboard-service/src/dashboard/dto/total-section-header.service.spec.ts index 12019c9d..613db83f 100644 --- a/apps/dashboard-service/src/dashboard/dto/total-section-header.service.spec.ts +++ b/apps/dashboard-service/src/dashboard/dto/total-section-header.service.spec.ts @@ -65,7 +65,7 @@ describe("TotalSectionHeaderService - filters", () => { country: "BJ", programmes: ["terrafund"], projectUuid: "uuid", - landscapes: ["terrafund"] + landscapes: ["gcb"] }; const mockBuilder = { @@ -191,7 +191,7 @@ describe("TotalSectionHeaderService - filters", () => { it("should apply landscapes filter (array)", async () => { const filters: DashboardQueryDto = { - landscapes: ["land1", "land2"] + landscapes: ["gcb", "grv"] }; const mockBuilder = baseMocks(); diff --git a/apps/dashboard-service/src/dashboard/dto/tree-restoration-goal.service.spec.ts b/apps/dashboard-service/src/dashboard/dto/tree-restoration-goal.service.spec.ts index 4cc136ad..2be865b2 100644 --- a/apps/dashboard-service/src/dashboard/dto/tree-restoration-goal.service.spec.ts +++ b/apps/dashboard-service/src/dashboard/dto/tree-restoration-goal.service.spec.ts @@ -84,7 +84,8 @@ describe("TreeRestorationGoalService", () => { it("should return complete tree restoration goal data", async () => { const query: DashboardQueryDto = { country: "BEN", - programmes: ["terrafund"] + programmes: ["terrafund"], + landscapes: ["gcb"] }; mockBuilder.execute.mockResolvedValue(mockProjects); @@ -111,7 +112,9 @@ describe("TreeRestorationGoalService", () => { }); it("should handle empty projects list", async () => { - const query: DashboardQueryDto = {}; + const query: DashboardQueryDto = { + landscapes: ["gcb"] + }; mockBuilder.execute.mockResolvedValue([]); (Site.approvedIdsProjectsSubquery as jest.Mock).mockResolvedValue(literal("(0)")); @@ -138,7 +141,8 @@ describe("TreeRestorationGoalService", () => { it("should filter projects by organisation type correctly", async () => { const query: DashboardQueryDto = { - organisationType: ["non-profit-organization"] + organisationType: ["non-profit-organization"], + landscapes: ["gcb"] }; mockBuilder.execute.mockResolvedValue(mockProjects); @@ -152,7 +156,9 @@ describe("TreeRestorationGoalService", () => { }); it("should handle projects with null treesGrownGoal", async () => { - const query: DashboardQueryDto = {}; + const query: DashboardQueryDto = { + landscapes: ["gcb"] + }; const projectsWithNulls = [ { id: 1, treesGrownGoal: null, organisation: { type: "non-profit-organization" } }, { id: 2, treesGrownGoal: undefined, organisation: { type: "for-profit-organization" } }, @@ -172,7 +178,7 @@ describe("TreeRestorationGoalService", () => { country: "CMR", programmes: ["terrafund", "ppc"], projectUuid: "uuid-123", - landscapes: ["landscape1"], + landscapes: ["gcb"], cohort: "cohort-2024" }; diff --git a/apps/dashboard-service/src/dashboard/tree-restoration-goal.controller.spec.ts b/apps/dashboard-service/src/dashboard/tree-restoration-goal.controller.spec.ts index 13ad2854..12da8998 100644 --- a/apps/dashboard-service/src/dashboard/tree-restoration-goal.controller.spec.ts +++ b/apps/dashboard-service/src/dashboard/tree-restoration-goal.controller.spec.ts @@ -205,7 +205,7 @@ describe("TreeRestorationGoalController", () => { country: "CMR", programmes: ["terrafund", "ppc"], projectUuid: "uuid-123", - landscapes: ["landscape1", "landscape2"], + landscapes: ["gcb", "grv"], cohort: "cohort-2024" }; @@ -318,7 +318,7 @@ describe("TreeRestorationGoalController", () => { }); it("should set timestamp when data is fetched from service", async () => { - const query: DashboardQueryDto = { landscapes: ["test-landscape"] }; + const query: DashboardQueryDto = { landscapes: ["ikr"] }; const mockTimestamp = "2024-12-01T12:00:00.000Z"; cacheService.getCacheKeyFromQuery.mockReturnValue("timestamp-test-key"); diff --git a/apps/entity-service/src/entities/dto/entity-query.dto.ts b/apps/entity-service/src/entities/dto/entity-query.dto.ts index 6089afeb..8e1ef27d 100644 --- a/apps/entity-service/src/entities/dto/entity-query.dto.ts +++ b/apps/entity-service/src/entities/dto/entity-query.dto.ts @@ -67,7 +67,7 @@ export class EntityQueryDto extends IndexQueryDto { @IsOptional() siteUuid?: string; - @ApiProperty({ required: false, isArray: true, description: "Filter by landscape names" }) + @ApiProperty({ required: false, isArray: true, description: "Filter by landscape 3-letter codes: gcb, grv, ikr" }) @IsOptional() @IsArray() landscape?: string[]; diff --git a/apps/entity-service/src/entities/processors/project.processor.spec.ts b/apps/entity-service/src/entities/processors/project.processor.spec.ts index 8ec6178a..7057866e 100644 --- a/apps/entity-service/src/entities/processors/project.processor.spec.ts +++ b/apps/entity-service/src/entities/processors/project.processor.spec.ts @@ -166,17 +166,17 @@ describe("ProjectProcessor", () => { p.organisation = await p.$get("organisation"); } - await expectProjects([project1, project3], { landscape: ["Greater Rift Valley of Kenya"] }); + await expectProjects([project1, project3], { landscape: ["grv"] }); await expectProjects([project1, project2], { cohort: ["terrafund"] }); await expectProjects([project1, project4], { organisationType: ["for-profit-organisation"] }); await expectProjects([project2], { organisationType: ["non-profit-organisation"] }); await expectProjects([project1], { - landscape: ["Greater Rift Valley of Kenya"], + landscape: ["grv"], cohort: ["terrafund"], organisationType: ["for-profit-organisation"] }); await expectProjects([project1, project3, project4], { - landscape: ["Greater Rift Valley of Kenya", "Lake Kivu & Rusizi River Basin"] + landscape: ["grv", "ikr"] }); }); diff --git a/apps/entity-service/src/entities/processors/project.processor.ts b/apps/entity-service/src/entities/processors/project.processor.ts index c8dcc38b..d41f25a0 100644 --- a/apps/entity-service/src/entities/processors/project.processor.ts +++ b/apps/entity-service/src/entities/processors/project.processor.ts @@ -25,6 +25,7 @@ import { DocumentBuilder } from "@terramatch-microservices/common/util"; import { ProjectUpdateAttributes } from "../dto/entity-update.dto"; import { populateDto } from "@terramatch-microservices/common/dto/json-api-attributes"; import { EntityDto } from "../dto/entity.dto"; +import { mapLandscapeCodesToNames } from "@terramatch-microservices/database/constants"; export class ProjectProcessor extends EntityProcessor< Project, @@ -94,7 +95,8 @@ export class ProjectProcessor extends EntityProcessor< } if (query.landscape != null && query.landscape.length > 0) { - builder.where({ landscape: { [Op.in]: query.landscape } }); + const landscapeNames = mapLandscapeCodesToNames(query.landscape); + builder.where({ landscape: { [Op.in]: landscapeNames } }); } if (query.organisationType != null && query.organisationType.length > 0) { diff --git a/libs/database/src/lib/constants/landscapes.ts b/libs/database/src/lib/constants/landscapes.ts index 3aa5cb7b..32e62aa5 100644 --- a/libs/database/src/lib/constants/landscapes.ts +++ b/libs/database/src/lib/constants/landscapes.ts @@ -1,2 +1,12 @@ -export const LANDSCAPE_TYPES = ["Ghana Cocoa Belt", "Greater Rift Valley of Kenya", "Lake Kivu & Rusizi River Basin"]; +export const LANDSCAPE_TYPES = ["gcb", "grv", "ikr"] as const; export type LandscapeType = (typeof LANDSCAPE_TYPES)[number]; + +export const LANDSCAPE_CODE_TO_NAME_MAP = { + gcb: "Ghana Cocoa Belt", + grv: "Greater Rift Valley of Kenya", + ikr: "Lake Kivu & Rusizi River Basin" +} as const; + +export function mapLandscapeCodesToNames(codes: string[]): string[] { + return codes.map(code => LANDSCAPE_CODE_TO_NAME_MAP[code as LandscapeType] ?? code); +}