diff --git a/.circleci/config.yml b/.circleci/config.yml index 10086b4..10c4841 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,6 +66,7 @@ workflows: - develop - pm-1127_1 - PM-4305 + - PM-4490 - PM-4491-fix - PM-3497_talent-search diff --git a/sql/reports/identity/users-by-handles.sql b/sql/reports/identity/users-by-handles.sql index 6e547e5..be1985c 100644 --- a/sql/reports/identity/users-by-handles.sql +++ b/sql/reports/identity/users-by-handles.sql @@ -7,6 +7,9 @@ WITH input_handles AS ( SELECT ih.handle_input AS "handle", u.user_id AS "userId", + NULLIF(TRIM(mem."firstName"), '') AS "firstName", + NULLIF(TRIM(mem."lastName"), '') AS "lastName", + NULLIF(TRIM(ph.phone_number::text), '') AS "contactNumber", pe.address AS "email", COALESCE( NULLIF(BTRIM(mem."competitionCountryCode"), ''), @@ -24,6 +27,18 @@ LEFT JOIN LATERAL ( LIMIT 1 ) AS pe ON TRUE +LEFT JOIN LATERAL ( + SELECT p."number" AS phone_number + FROM members."memberPhone" AS p + WHERE p."userId" = u.user_id + AND NULLIF(TRIM(p."number"::text), '') IS NOT NULL + ORDER BY + (p."type" = 'HOME') DESC, + p."createdAt" DESC NULLS LAST, + p."id" ASC + LIMIT 1 +) AS ph + ON TRUE LEFT JOIN members."member" AS mem ON mem."userId" = u.user_id ORDER BY ih.ordinality; diff --git a/src/reports/identity/dtos/identity-users.dto.ts b/src/reports/identity/dtos/identity-users.dto.ts index cc7ddf6..70d2465 100644 --- a/src/reports/identity/dtos/identity-users.dto.ts +++ b/src/reports/identity/dtos/identity-users.dto.ts @@ -45,6 +45,9 @@ export class UsersByGroupQueryDto { export interface IdentityUserDto { userId: number | null; handle: string; + firstName: string | null; + lastName: string | null; + contactNumber: string | null; email: string | null; country: string | null; } diff --git a/src/reports/identity/identity-reports.controller.ts b/src/reports/identity/identity-reports.controller.ts index a009e76..a4692e0 100644 --- a/src/reports/identity/identity-reports.controller.ts +++ b/src/reports/identity/identity-reports.controller.ts @@ -90,7 +90,7 @@ export class IdentityReportsController { @ApiBearerAuth() @ApiOperation({ summary: - "Export user details (ID, handle, email, country) for a list of handles", + "Export user details (ID, handle, firstName, lastName, contactNumber, email, country) for a list of handles", }) @ApiConsumes("application/json", "multipart/form-data") @ApiBody({ diff --git a/src/reports/identity/identity-reports.service.ts b/src/reports/identity/identity-reports.service.ts index e9976b4..9521b1f 100644 --- a/src/reports/identity/identity-reports.service.ts +++ b/src/reports/identity/identity-reports.service.ts @@ -16,6 +16,9 @@ const SUPPORTED_HANDLES_UPLOAD_EXTENSIONS = new Set([".txt", ".csv"]); type UsersByHandlesRow = { userId: number | null; handle: string; + firstName: string | null; + lastName: string | null; + contactNumber: string | null; email: string | null; country: string | null; }; @@ -114,6 +117,9 @@ export class IdentityReportsService { return results.map((row) => ({ userId: row.userId, handle: row.handle, + firstName: row.firstName, + lastName: row.lastName, + contactNumber: row.contactNumber, email: row.email, country: alpha3ToCountryName(row.country) ?? row.country, })); diff --git a/src/reports/member/member-search.controller.ts b/src/reports/member/member-search.controller.ts index 6c42b0c..5702a98 100644 --- a/src/reports/member/member-search.controller.ts +++ b/src/reports/member/member-search.controller.ts @@ -1,5 +1,17 @@ -import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from "@nestjs/common"; -import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { + Body, + Controller, + HttpCode, + HttpStatus, + Post, + UseGuards, +} from "@nestjs/common"; +import { + ApiBearerAuth, + ApiOperation, + ApiResponse, + ApiTags, +} from "@nestjs/swagger"; import { MemberSearchBodyDto } from "./dto/member-search.dto"; import { MemberSearchResponseDto } from "./dto/member-search-response.dto"; import { MemberSearchService } from "./member-search.service"; diff --git a/src/reports/member/member-search.service.spec.ts b/src/reports/member/member-search.service.spec.ts index 13dca55..faf35c6 100644 --- a/src/reports/member/member-search.service.spec.ts +++ b/src/reports/member/member-search.service.spec.ts @@ -56,7 +56,9 @@ describe("MemberSearchService", () => { expect(dataSql).toContain("WITH recently_active AS"); expect(dataSql).not.toContain("requested_skills AS"); - expect(dataSql).toContain('ORDER BY "matchIndex" DESC NULLS LAST, m.handle ASC'); + expect(dataSql).toContain( + 'ORDER BY "matchIndex" DESC NULLS LAST, m.handle ASC', + ); expect(countSql).toContain("SELECT COUNT(*)::integer AS total"); @@ -85,7 +87,9 @@ describe("MemberSearchService", () => { }); it("uses filter params for count query but excludes pagination params", async () => { - mockDbService.query.mockResolvedValueOnce([]).mockResolvedValueOnce([{ total: 0 }]); + mockDbService.query + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([{ total: 0 }]); await service.search({ country: "us", @@ -99,7 +103,9 @@ describe("MemberSearchService", () => { const dataParams = mockDbService.query.mock.calls[0][1] as unknown[]; const countParams = mockDbService.query.mock.calls[1][1] as unknown[]; - expect(dataSql).toContain('ORDER BY m.handle ASC, "matchIndex" DESC NULLS LAST'); + expect(dataSql).toContain( + 'ORDER BY m.handle ASC, "matchIndex" DESC NULLS LAST', + ); expect(dataParams).toEqual(["us", 5, 5]); expect(countParams).toEqual(["us"]); }); diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index 93d2ab6..2470f8a 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -81,10 +81,13 @@ skill_event_stats AS ( SELECT se.user_id, se.skill_id, - COUNT(*) FILTER (WHERE LOWER(set_t.name) = 'challenge_win') AS wins, + COUNT(*) FILTER ( + WHERE LOWER(set_t.name) IN ('challenge_win', 'challenge_2nd_place', 'challenge_3rd_place', 'gig_completion') OR sest.name='engagement' + ) AS wins, COUNT(*) AS submitted FROM skills.skill_event se JOIN skills.skill_event_type set_t ON set_t.id = se.skill_event_type_id + JOIN skills.source_type sest ON sest.id = se.source_type_id WHERE se.skill_id = ANY(${pSkillIds}::uuid[]) GROUP BY se.user_id, se.skill_id ), @@ -182,17 +185,17 @@ member_address AS ( // ------------------------------------------------- dynamic WHERE const where: string[] = [`m.status = 'ACTIVE'`]; - if (openToWork === true) { + if (typeof openToWork === "boolean") { where.push(`m."availableForGigs" = true`); } - if (recentlyActive === true) { + if (typeof recentlyActive === "boolean") { where.push( `EXISTS (SELECT 1 FROM recently_active ra WHERE ra.user_id = m."userId")`, ); } - if (verifiedProfile === true) { + if (typeof verifiedProfile === "boolean") { where.push( `(COALESCE(m.verified, false) = true OR EXISTS (SELECT 1 FROM verified_via_trolley vt WHERE vt.user_id = m."userId"))`, ); @@ -235,7 +238,7 @@ SELECT COALESCE(m."availableForGigs", false) AS "openToWork", TRIM( COALESCE(maddr.city || ' ', '') || - COALESCE(m.country, COALESCE(m."competitionCountryCode", COALESCE(m."homeCountryCode", ''))) + COALESCE(m."homeCountryCode", COALESCE(m.country, COALESCE(m."competitionCountryCode", ''))) ) AS location, ${matchedSkillsExpr} AS "matchedSkills", ${matchIndexExpr} AS "matchIndex" diff --git a/src/reports/report-directory.data.ts b/src/reports/report-directory.data.ts index 6542ac1..bc12790 100644 --- a/src/reports/report-directory.data.ts +++ b/src/reports/report-directory.data.ts @@ -431,7 +431,7 @@ const REGISTERED_REPORTS_DIRECTORY: RegisteredReportsDirectory = { identityPostReport( "Users by Handles", "/identity/users-by-handles", - "Export user ID, handle, email, and country for each supplied handle; unknown handles return empty fields", + "Export user ID, handle, firstName, lastName, contactNumber, email, and country for each supplied handle; unknown handles return empty fields", AppScopes.Identity.UsersByHandles, [handlesBodyParam], ),