diff --git a/.circleci/config.yml b/.circleci/config.yml index 10c4841..6a40302 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -69,6 +69,7 @@ workflows: - PM-4490 - PM-4491-fix - PM-3497_talent-search + - PM-4886 # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/src/reports/member/dto/member-search.dto.ts b/src/reports/member/dto/member-search.dto.ts index e0f36fa..d84e299 100644 --- a/src/reports/member/dto/member-search.dto.ts +++ b/src/reports/member/dto/member-search.dto.ts @@ -77,12 +77,14 @@ export class MemberSearchBodyDto { @ApiPropertyOptional({ description: - "Filter by country name or code as stored in the member location (case-insensitive).", - example: "Australia", + "Filter by multiple country names or country codes (case-insensitive).", + type: [String], + example: ["US", "Australia"], }) @IsOptional() - @IsString() - country?: string; + @IsArray() + @IsString({ each: true }) + countries?: string[]; @ApiPropertyOptional({ description: diff --git a/src/reports/member/member-search.controller.spec.ts b/src/reports/member/member-search.controller.spec.ts index 1a79ef4..0c1b3f0 100644 --- a/src/reports/member/member-search.controller.spec.ts +++ b/src/reports/member/member-search.controller.spec.ts @@ -33,7 +33,7 @@ describe("MemberSearchController", () => { it("delegates search requests to the service and returns response", async () => { const body = { - country: "Australia", + countries: ["Australia"], sortBy: "handle" as const, sortOrder: "asc" as const, page: 2, diff --git a/src/reports/member/member-search.service.spec.ts b/src/reports/member/member-search.service.spec.ts index faf35c6..f33239c 100644 --- a/src/reports/member/member-search.service.spec.ts +++ b/src/reports/member/member-search.service.spec.ts @@ -92,7 +92,7 @@ describe("MemberSearchService", () => { .mockResolvedValueOnce([{ total: 0 }]); await service.search({ - country: "us", + countries: ["us"], page: 2, limit: 5, sortBy: "handle", @@ -106,8 +106,25 @@ describe("MemberSearchService", () => { expect(dataSql).toContain( 'ORDER BY m.handle ASC, "matchIndex" DESC NULLS LAST', ); - expect(dataParams).toEqual(["us", 5, 5]); - expect(countParams).toEqual(["us"]); + expect(dataSql).toContain( + 'LOWER(m."homeCountryCode") = ANY($1::text[])', + ); + expect(dataParams).toEqual([["us"], 5, 5]); + expect(countParams).toEqual([["us"]]); + }); + + it("treats empty countries as no country filter", async () => { + mockDbService.query + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([{ total: 0 }]); + + await service.search({ countries: [] }); + + const dataSql = mockDbService.query.mock.calls[0][0] as string; + const countParams = mockDbService.query.mock.calls[1][1] as unknown[]; + + expect(dataSql).not.toContain('LOWER(m."homeCountryCode") = ANY('); + expect(countParams).toEqual([]); }); it("deduplicates skills and keeps last wins value when building skill query", async () => { diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index 2470f8a..b37807b 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -31,7 +31,7 @@ export class MemberSearchService { openToWork, recentlyActive, verifiedProfile, - country, + countries, sortBy = "matchIndex", sortOrder = "desc", page = 1, @@ -201,10 +201,24 @@ member_address AS ( ); } - if (country) { - const pCountry = p(country); + const normalizedCountries = Array.isArray(countries) + ? [ + ...new Set( + countries + .map((value) => String(value).trim().toLowerCase()) + .filter(Boolean), + ), + ] + : []; + + if (normalizedCountries.length > 0) { + const pCountries = p(normalizedCountries); where.push( - `(LOWER(m."homeCountryCode") = LOWER(${pCountry}) OR LOWER(m."competitionCountryCode") = LOWER(${pCountry}) OR LOWER(m.country) = LOWER(${pCountry}))`, + `( + LOWER(m."homeCountryCode") = ANY(${pCountries}::text[]) + OR LOWER(m."competitionCountryCode") = ANY(${pCountries}::text[]) + OR LOWER(m.country) = ANY(${pCountries}::text[]) + )`, ); }