Skip to content

Commit 8d2bfcd

Browse files
authored
Merge pull request #99 from topcoder-platform/develop
[PROD] HOTFIX improve member search query
2 parents a0406a9 + edddac1 commit 8d2bfcd

2 files changed

Lines changed: 57 additions & 26 deletions

File tree

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ workflows:
6565
only:
6666
- develop
6767
- PM-4931
68+
- improve-member-search-2
6869

6970
# Production builds are exectuted only on tagged commits to the
7071
# master branch.

src/reports/member/member-search.service.ts

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Injectable, NotFoundException } from "@nestjs/common";
1+
import { Injectable, NotFoundException, OnModuleInit } from "@nestjs/common";
22
import { alpha3ToCountryName } from "../../common/country.util";
33
import { DbService } from "../../db/db.service";
44
import { MemberSearchBodyDto } from "./dto/member-search.dto";
@@ -42,9 +42,35 @@ function formatLocation(location: string): string {
4242
}
4343

4444
@Injectable()
45-
export class MemberSearchService {
45+
export class MemberSearchService implements OnModuleInit {
46+
private winEventTypeIds: string[] = [];
47+
private engagementSourceTypeId: string = "";
48+
4649
constructor(private readonly db: DbService) {}
4750

51+
async onModuleInit(): Promise<void> {
52+
const [winRows, engRows] = await Promise.all([
53+
this.db.query<{ id: string }>(
54+
`SELECT id::text FROM skills.skill_event_type
55+
WHERE name = ANY($1::text[])`,
56+
[
57+
[
58+
"challenge_win",
59+
"challenge_2nd_place",
60+
"challenge_3rd_place",
61+
"gig_completion",
62+
],
63+
],
64+
),
65+
this.db.query<{ id: string }>(
66+
`SELECT id::text FROM skills.source_type WHERE name = 'engagement'`,
67+
[],
68+
),
69+
]);
70+
this.winEventTypeIds = winRows.map((r) => r.id);
71+
this.engagementSourceTypeId = engRows[0]?.id ?? "";
72+
}
73+
4874
async search(dto: MemberSearchBodyDto): Promise<MemberSearchResponseDto> {
4975
const {
5076
skills,
@@ -79,8 +105,14 @@ export class MemberSearchService {
79105
// Collect CTE bodies (joined later with commas)
80106
const ctes: string[] = [];
81107

108+
// active_members is always first so later CTEs can reference it
109+
ctes.push(`active_members AS MATERIALIZED (
110+
SELECT m."userId" AS user_id
111+
FROM members.member m
112+
WHERE m.status = 'ACTIVE'
113+
)`);
114+
82115
// Expressions swapped in to the SELECT based on whether skills are requested
83-
let skillJoin = "";
84116
let matchedSkillsExpr = `'[]'::jsonb`;
85117
let matchIndexExpr = "0";
86118

@@ -93,6 +125,8 @@ export class MemberSearchService {
93125
const pMinWins = p(minWins);
94126
const pSearchType = p(skillSearchType);
95127
const pNumSkills = p(deduped.length);
128+
const pWinTypeIds = p(this.winEventTypeIds);
129+
const pEngSourceId = p(this.engagementSourceTypeId);
96130

97131
ctes.push(`requested_skills AS (
98132
SELECT rs.skill_id, rs.min_wins
@@ -104,13 +138,13 @@ skill_event_stats AS (
104138
se.user_id,
105139
se.skill_id,
106140
COUNT(*) FILTER (
107-
WHERE LOWER(set_t.name) IN ('challenge_win', 'challenge_2nd_place', 'challenge_3rd_place', 'gig_completion') OR sest.name='engagement'
141+
WHERE se.skill_event_type_id = ANY(${pWinTypeIds}::uuid[])
142+
OR se.source_type_id = ${pEngSourceId}::uuid
108143
) AS wins,
109144
COUNT(*) AS submitted
110145
FROM skills.skill_event se
111-
JOIN skills.skill_event_type set_t ON set_t.id = se.skill_event_type_id
112-
JOIN skills.source_type sest ON sest.id = se.source_type_id
113146
WHERE se.skill_id = ANY(${pSkillIds}::uuid[])
147+
AND se.user_id IN (SELECT user_id FROM active_members)
114148
GROUP BY se.user_id, se.skill_id
115149
),
116150
deduped_user_skills AS (
@@ -119,6 +153,7 @@ deduped_user_skills AS (
119153
us.skill_id
120154
FROM skills.user_skill us
121155
WHERE us.skill_id = ANY(${pSkillIds}::uuid[])
156+
AND us.user_id IN (SELECT user_id FROM active_members)
122157
),
123158
user_skill_data AS (
124159
SELECT
@@ -182,7 +217,6 @@ user_match_data AS (
182217
GROUP BY usd.user_id
183218
)`);
184219

185-
skillJoin = `INNER JOIN user_match_data umd ON umd.user_id = m."userId"`;
186220
matchedSkillsExpr = `umd.matched_skills`;
187221
matchIndexExpr = `CEIL(
188222
LEAST(
@@ -196,13 +230,13 @@ user_match_data AS (
196230
ctes.push(`recently_active AS (
197231
SELECT DISTINCT r."memberId"::bigint AS user_id
198232
FROM resources."Resource" r
233+
INNER JOIN active_members am ON am.user_id = r."memberId"::bigint
199234
WHERE r."createdAt" >= NOW() - INTERVAL '3 months'
200-
AND r."memberId" ~ '^[0-9]+$'
201235
),
202236
verified_via_trolley AS (
203237
SELECT DISTINCT tr.user_id::bigint AS user_id
204238
FROM finance.trolley_recipient tr
205-
WHERE tr.user_id ~ '^[0-9]+$'
239+
INNER JOIN active_members am ON am.user_id = tr.user_id::bigint
206240
),
207241
member_address AS (
208242
SELECT DISTINCT ON ("userId")
@@ -237,7 +271,7 @@ member_address AS (
237271
? [
238272
...new Set(
239273
countries
240-
.map((value) => String(value).trim().toLowerCase())
274+
.map((value) => String(value).trim().toUpperCase())
241275
.filter(Boolean),
242276
),
243277
]
@@ -247,9 +281,9 @@ member_address AS (
247281
const pCountries = p(normalizedCountries);
248282
where.push(
249283
`(
250-
LOWER(m."homeCountryCode") = ANY(${pCountries}::text[])
251-
OR LOWER(m."competitionCountryCode") = ANY(${pCountries}::text[])
252-
OR LOWER(m.country) = ANY(${pCountries}::text[])
284+
m."homeCountryCode" = ANY(${pCountries}::text[])
285+
OR m."competitionCountryCode" = ANY(${pCountries}::text[])
286+
OR UPPER(m.country) = ANY(${pCountries}::text[])
253287
)`,
254288
);
255289
}
@@ -258,7 +292,7 @@ member_address AS (
258292
ctes.push(`filtered_members AS (
259293
SELECT m."userId" AS user_id
260294
FROM members.member m
261-
${skillJoin}
295+
INNER JOIN user_match_data umd ON umd.user_id = m."userId"
262296
WHERE ${whereClause}
263297
)`);
264298

@@ -311,21 +345,19 @@ member_address AS (
311345
FROM skills.user_skill us2
312346
INNER JOIN skills.user_skill_display_mode usdm2 ON usdm2.id = us2.user_skill_display_mode_id
313347
WHERE us2.user_id = m2."userId"
314-
AND LOWER(usdm2.name) = 'principal'
348+
AND usdm2.name = 'principal'
315349
)
316350
AND EXISTS (
317351
SELECT 1
318352
FROM skills.user_skill us2
319353
INNER JOIN skills.user_skill_display_mode usdm2 ON usdm2.id = us2.user_skill_display_mode_id
320354
WHERE us2.user_id = m2."userId"
321-
AND LOWER(usdm2.name) = 'additional'
355+
AND usdm2.name = 'additional'
322356
)
323357
)`);
324358
}
325359

326-
// Snapshot param count BEFORE adding pagination — count query stops here
327360
const filterParamCount = params.length;
328-
329361
const pLimit = p(limit);
330362
const pOffset = p((page - 1) * limit);
331363

@@ -349,10 +381,7 @@ SELECT
349381
TRIM(COALESCE(m."firstName", '') || ' ' || COALESCE(m."lastName", '')) AS name,
350382
m."photoURL" AS "photoUrl",
351383
EXISTS (SELECT 1 FROM recently_active ra WHERE ra.user_id = m."userId") AS "isRecentlyActive",
352-
(
353-
COALESCE(m.verified, false) = true
354-
OR EXISTS (SELECT 1 FROM verified_via_trolley vt WHERE vt.user_id = m."userId")
355-
) AS "isVerified",
384+
(COALESCE(m.verified, false) = true OR vt.user_id IS NOT NULL) AS "isVerified",
356385
COALESCE(m."availableForGigs", false) AS "openToWork",
357386
TRIM(
358387
COALESCE(maddr.city || ' ', '') ||
@@ -363,15 +392,16 @@ SELECT
363392
FROM members.member m
364393
INNER JOIN filtered_members fm ON fm.user_id = m."userId"
365394
${profileCompleteJoin}
366-
${skillJoin}
395+
LEFT JOIN user_match_data umd ON umd.user_id = m."userId"
396+
LEFT JOIN verified_via_trolley vt ON vt.user_id = m."userId"
367397
LEFT JOIN member_address maddr ON maddr."userId" = m."userId"
368398
ORDER BY ${orderByClause}
369399
LIMIT ${pLimit} OFFSET ${pOffset}`;
370400

371401
const countQuery = `
372-
WITH ${ctesBlock}
373-
SELECT COUNT(*)::integer AS total
374-
FROM ${profileComplete === true ? "profile_complete_filtered pcf" : "filtered_members fm"}`;
402+
WITH ${ctesBlock}
403+
SELECT COUNT(*)::integer AS total
404+
FROM ${profileComplete === true ? "profile_complete_filtered pcf" : "filtered_members fm"}`;
375405

376406
const [rows, countRows] = await Promise.all([
377407
this.db.query<RawMemberRow>(dataQuery, params),

0 commit comments

Comments
 (0)