Skip to content

[TM-2120] change 3 alpha code for landscape query params #207

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

Merged
merged 2 commits into from
Jun 19, 2025
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
Expand Up @@ -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<T extends Model<T> = Project> {
protected findOptions: FindOptions<Attributes<T>> = {
Expand Down Expand Up @@ -46,7 +47,10 @@ export class DashboardProjectsQueryBuilder<T extends Model<T> = 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,15 @@ 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",
projectUuid: "uuid-123"
};

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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe("TotalSectionHeaderService - filters", () => {
country: "BJ",
programmes: ["terrafund"],
projectUuid: "uuid",
landscapes: ["terrafund"]
landscapes: ["gcb"]
};

const mockBuilder = {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)"));
Expand All @@ -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);
Expand All @@ -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" } },
Expand All @@ -172,7 +178,7 @@ describe("TreeRestorationGoalService", () => {
country: "CMR",
programmes: ["terrafund", "ppc"],
projectUuid: "uuid-123",
landscapes: ["landscape1"],
landscapes: ["gcb"],
cohort: "cohort-2024"
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ describe("TreeRestorationGoalController", () => {
country: "CMR",
programmes: ["terrafund", "ppc"],
projectUuid: "uuid-123",
landscapes: ["landscape1", "landscape2"],
landscapes: ["gcb", "grv"],
cohort: "cohort-2024"
};

Expand Down Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion apps/entity-service/src/entities/dto/entity-query.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 11 additions & 1 deletion libs/database/src/lib/constants/landscapes.ts
Original file line number Diff line number Diff line change
@@ -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);
}