Skip to content
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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 6 additions & 4 deletions src/reports/member/dto/member-search.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Comment on lines 84 to +86
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

countries is optional, but @ArrayNotEmpty() will reject requests that send an empty array (common for multi-select UIs where an empty selection is represented as []). Since the service already treats an empty array as “no filter”, consider removing @ArrayNotEmpty() or transforming [] to undefined so empty selections don’t cause 400 responses.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@himaniraghav3 this one makes sense

countries?: string[];
Comment thread
vas3a marked this conversation as resolved.

@ApiPropertyOptional({
description:
Expand Down
2 changes: 1 addition & 1 deletion src/reports/member/member-search.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 20 additions & 3 deletions src/reports/member/member-search.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe("MemberSearchService", () => {
.mockResolvedValueOnce([{ total: 0 }]);

await service.search({
country: "us",
countries: ["us"],
page: 2,
limit: 5,
sortBy: "handle",
Expand All @@ -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 () => {
Expand Down
22 changes: 18 additions & 4 deletions src/reports/member/member-search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class MemberSearchService {
openToWork,
recentlyActive,
verifiedProfile,
country,
countries,
sortBy = "matchIndex",
sortOrder = "desc",
page = 1,
Expand Down Expand Up @@ -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[])
)`,
Comment on lines +217 to +221
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The country filter repeats unnest(${pCountries}::text[]) and lowercasing three times. You can simplify and likely improve planning/execution by normalizing the input list to lowercase once in TypeScript (and optionally deduping) and then using LOWER(column) = ANY($n::text[]) for each column, avoiding the repeated subselect/unnest.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@himaniraghav3 This sounds good optimization. you can more than likely pass this comment to AI to apply this refactor.

);
}

Expand Down
Loading