From f304ff60ac43b0d77550cecc63adebfa7aa8cb52 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 12 Feb 2026 20:04:28 +0100 Subject: [PATCH 1/7] PM-3734 fixed data science track, count 1 as sigular tense --- src/common/profileTemplate.js | 10 ++--- src/services/MemberService.js | 74 ++++++++--------------------------- 2 files changed, 22 insertions(+), 62 deletions(-) diff --git a/src/common/profileTemplate.js b/src/common/profileTemplate.js index 56b19f9..68ae5c3 100644 --- a/src/common/profileTemplate.js +++ b/src/common/profileTemplate.js @@ -496,13 +496,13 @@ function buildProfileTemplate (pdfData) { if (topcoderActivity.specialRole || topcoderActivity.achievements || hasStatsByTrack) { const activityContent = [createSectionHeader('TOPCODER ACTIVITY')] - // Member stats by track first (Development: wins, submissions, challenges; Competitive Programming: rating, wins, competitions) + // Member stats by track (wins, submissions, challenges; singular when count is 1) if (hasStatsByTrack) { const statsItems = topcoderActivity.statsByTrack.map((stat, index) => { - const isCompetitiveProgramming = stat.trackName === 'Competitive Programming' - const valueText = isCompetitiveProgramming - ? `${stat.rating ?? 0} rating, ${stat.wins ?? 0} wins, ${stat.competitions ?? 0} competitions` - : `${stat.wins ?? 0} wins, ${stat.submissions ?? 0} submissions, ${stat.challenges ?? 0} challenges` + const w = stat.wins ?? 0 + const s = stat.submissions ?? 0 + const c = stat.challenges ?? 0 + const valueText = `${w} ${w === 1 ? 'win' : 'wins'}, ${s} ${s === 1 ? 'submission' : 'submissions'}, ${c} ${c === 1 ? 'challenge' : 'challenges'}` return React.createElement( Text, { key: `stats-track-${index}`, style: styles.activityItem }, diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 3ffb5d3..4a73ac4 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -33,7 +33,6 @@ const academyPrisma = prismaManager.getAcademyClient() const resourcesPrisma = prismaManager.getResourcesClient() const engagementsPrisma = prismaManager.getEngagementsClient() const profilePDFService = require('./ProfilePDFService') -const StatisticsService = require('./StatisticsService') const request = require('request') const cityTimezones = require('city-timezones') const moment = require('moment-timezone') @@ -1515,11 +1514,11 @@ async function getMemberRoles (userId) { } } -/** Track enum to display name (for standard tracks: wins, submissions, challenges) */ +/** Track enum to display name (wins, submissions, challenges) */ const TRACK_DISPLAY_NAMES = { DEVELOPMENT: 'Development', DESIGN: 'Design', - DATA_SCIENCE: 'Competitive Programming', + DATA_SCIENCE: 'Data Science', QUALITY_ASSURANCE: 'Quality Assurance' } @@ -1529,17 +1528,16 @@ const TRACK_DISPLAY_NAMES = { * @param {Number} userId member userId * @param {Object} challengesPrisma challenges Prisma client * @param {Object} resourcesPrisma resources Prisma client - * @returns {Promise>} + * @returns {Promise>} */ async function fetchMemberStatsByTrack (userId, challengesPrisma, resourcesPrisma) { - const trackMap = {} // track enum -> { wins, submissions, challenges } or { rating, wins, competitions } for DATA_SCIENCE + const trackMap = {} // track enum -> { wins, submissions, challenges } try { const numUserId = typeof userId === 'bigint' ? helper.bigIntToNumber(userId) : userId - // 1) ChallengeWinner: wins (and submissions if same table) by track const winners = await challengesPrisma.ChallengeWinner.findMany({ - where: { userId: numUserId }, + where: { userId: numUserId, type: 'PLACEMENT' }, include: { challenge: { include: { track: true } @@ -1551,14 +1549,11 @@ async function fetchMemberStatsByTrack (userId, challengesPrisma, resourcesPrism const trackEnum = w.challenge?.track?.track if (!trackEnum) continue if (!trackMap[trackEnum]) { - const isDataScience = trackEnum === 'DATA_SCIENCE' - trackMap[trackEnum] = isDataScience - ? { wins: 0, competitions: 0, rating: undefined } - : { wins: 0, submissions: 0, challenges: 0 } + trackMap[trackEnum] = { wins: 0, submissions: 0, challenges: 0 } } const row = trackMap[trackEnum] - if (row.wins !== undefined) row.wins += 1 - if (row.submissions !== undefined) row.submissions += 1 + row.wins += 1 + row.submissions += 1 } // 2) Resources: registrations (distinct challenges) by track @@ -1592,13 +1587,9 @@ async function fetchMemberStatsByTrack (userId, challengesPrisma, resourcesPrism } for (const [trackEnum, count] of Object.entries(challengesPerTrack)) { if (!trackMap[trackEnum]) { - const isDataScience = trackEnum === 'DATA_SCIENCE' - trackMap[trackEnum] = isDataScience - ? { wins: 0, competitions: count, rating: undefined } - : { wins: 0, submissions: 0, challenges: count } + trackMap[trackEnum] = { wins: 0, submissions: 0, challenges: count } } else { - if (trackMap[trackEnum].challenges !== undefined) trackMap[trackEnum].challenges = count - if (trackMap[trackEnum].competitions !== undefined) trackMap[trackEnum].competitions = count + trackMap[trackEnum].challenges = count } } } @@ -1607,22 +1598,13 @@ async function fetchMemberStatsByTrack (userId, challengesPrisma, resourcesPrism for (const [trackEnum, counts] of Object.entries(trackMap)) { const trackName = TRACK_DISPLAY_NAMES[trackEnum] || trackEnum const hasAny = Object.values(counts).some(v => typeof v === 'number' && v > 0) - if (!hasAny && (counts.rating == null || counts.rating === 0)) continue - if (trackEnum === 'DATA_SCIENCE') { - statsByTrack.push({ - trackName, - rating: counts.rating ?? 0, - wins: counts.wins ?? 0, - competitions: counts.competitions ?? 0 - }) - } else { - statsByTrack.push({ - trackName, - wins: counts.wins ?? 0, - submissions: counts.submissions ?? 0, - challenges: counts.challenges ?? 0 - }) - } + if (!hasAny) continue + statsByTrack.push({ + trackName, + wins: counts.wins ?? 0, + submissions: counts.submissions ?? 0, + challenges: counts.challenges ?? 0 + }) } return statsByTrack } catch (err) { @@ -1736,28 +1718,6 @@ async function aggregatePDFData (currentUser, handle) { logger.warn(`aggregatePDFData: statsByTrack failed for ${handle}: ${err.message}`) } - // Merge Competitive Programming rating from stats endpoint (same source as GET /members/:handle/stats) - try { - const statsResult = await StatisticsService.getMemberStats(currentUser, handle, {}) - const statsResponse = Array.isArray(statsResult) && statsResult.length > 0 ? statsResult[0] : null - if (statsResponse && statsResponse.DATA_SCIENCE) { - const ds = statsResponse.DATA_SCIENCE - const rating = (ds.SRM && ds.SRM.rank && ds.SRM.rank.rating != null) - ? ds.SRM.rank.rating - : (ds.MARATHON_MATCH && ds.MARATHON_MATCH.rank && ds.MARATHON_MATCH.rank.rating != null) - ? ds.MARATHON_MATCH.rank.rating - : 0 - const cpEntry = statsByTrack.find(e => e.trackName === 'Competitive Programming') - if (cpEntry) { - cpEntry.rating = rating - } else if (rating > 0) { - statsByTrack.push({ trackName: 'Competitive Programming', rating, wins: 0, competitions: 0 }) - } - } - } catch (err) { - logger.warn(`aggregatePDFData: getMemberStats for rating failed for ${handle}: ${err.message}`) - } - // Fetch certifications and courses const { certifications, courses } = await fetchCertificationsAndCourses(userId) From 6ef8e800cc892516bdfd5ba237c501bcb8d10cbd Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 12 Feb 2026 20:04:50 +0100 Subject: [PATCH 2/7] PM-3734 #time 2h fixed data science track, count 1 as sigular tense --- src/common/profileTemplate.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/profileTemplate.js b/src/common/profileTemplate.js index 68ae5c3..39fc40c 100644 --- a/src/common/profileTemplate.js +++ b/src/common/profileTemplate.js @@ -496,7 +496,6 @@ function buildProfileTemplate (pdfData) { if (topcoderActivity.specialRole || topcoderActivity.achievements || hasStatsByTrack) { const activityContent = [createSectionHeader('TOPCODER ACTIVITY')] - // Member stats by track (wins, submissions, challenges; singular when count is 1) if (hasStatsByTrack) { const statsItems = topcoderActivity.statsByTrack.map((stat, index) => { const w = stat.wins ?? 0 From fed0c58fed792051977eb92ab12c79d1ad99243a Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 12 Feb 2026 20:05:51 +0100 Subject: [PATCH 3/7] deploy to develop --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e7df356..3fdb52f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ workflows: branches: only: - develop - - pm-3734 + - pm-3734_1 # Production builds are exectuted only on tagged commits to the # master branch. From f948da01005e9f9f4c9b4ec26cdc1a2e0559c814 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 13 Feb 2026 17:44:58 +0100 Subject: [PATCH 4/7] PM-3734 #time 1h used challenge winners table to get submissions with passed review --- src/services/MemberService.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 4a73ac4..f1093b6 100644 --- a/src/services/MemberService.js +++ b/src/services/MemberService.js @@ -1536,8 +1536,11 @@ async function fetchMemberStatsByTrack (userId, challengesPrisma, resourcesPrism try { const numUserId = typeof userId === 'bigint' ? helper.bigIntToNumber(userId) : userId - const winners = await challengesPrisma.ChallengeWinner.findMany({ - where: { userId: numUserId, type: 'PLACEMENT' }, + const winnerRows = await challengesPrisma.ChallengeWinner.findMany({ + where: { + userId: numUserId, + type: { in: ['PLACEMENT', 'PASSED_REVIEW'] } + }, include: { challenge: { include: { track: true } @@ -1545,15 +1548,15 @@ async function fetchMemberStatsByTrack (userId, challengesPrisma, resourcesPrism } }) - for (const w of winners) { + for (const w of winnerRows) { const trackEnum = w.challenge?.track?.track if (!trackEnum) continue if (!trackMap[trackEnum]) { trackMap[trackEnum] = { wins: 0, submissions: 0, challenges: 0 } } const row = trackMap[trackEnum] - row.wins += 1 - row.submissions += 1 + if (w.type === 'PLACEMENT') row.wins += 1 + if (w.type === 'PASSED_REVIEW') row.submissions += 1 } // 2) Resources: registrations (distinct challenges) by track From 14aff5599b8f153ddc7c0aff95e4d2f08f1c10cc Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 13 Feb 2026 18:38:43 +0100 Subject: [PATCH 5/7] fix: review comment --- src/common/profileTemplate.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/profileTemplate.js b/src/common/profileTemplate.js index 39fc40c..b6e752a 100644 --- a/src/common/profileTemplate.js +++ b/src/common/profileTemplate.js @@ -498,10 +498,10 @@ function buildProfileTemplate (pdfData) { if (hasStatsByTrack) { const statsItems = topcoderActivity.statsByTrack.map((stat, index) => { - const w = stat.wins ?? 0 - const s = stat.submissions ?? 0 - const c = stat.challenges ?? 0 - const valueText = `${w} ${w === 1 ? 'win' : 'wins'}, ${s} ${s === 1 ? 'submission' : 'submissions'}, ${c} ${c === 1 ? 'challenge' : 'challenges'}` + const wins = stat.wins ?? 0 + const submissions = stat.submissions ?? 0 + const challenges = stat.challenges ?? 0 + const valueText = `${wins} ${wins === 1 ? 'win' : 'wins'}, ${submissions} ${submissions === 1 ? 'submission' : 'submissions'}, ${challenges} ${challenges === 1 ? 'challenge' : 'challenges'}` return React.createElement( Text, { key: `stats-track-${index}`, style: styles.activityItem }, From 17d2aeb9b23f057c2715831997e156096cfed7a7 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 13 Feb 2026 23:52:12 +0100 Subject: [PATCH 6/7] PM-3847 #time 2h exclude links from personalization trait for other users --- src/services/MemberTraitService.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/services/MemberTraitService.js b/src/services/MemberTraitService.js index c4028f3..15fa695 100644 --- a/src/services/MemberTraitService.js +++ b/src/services/MemberTraitService.js @@ -224,6 +224,16 @@ async function getTraits (currentUser, handle, query) { if (traitIds) { result = _.filter(result, (item) => _.includes(traitIds, item.traitId)) } + // links in personalization are only for the profile owner (self) + if (!isSelf) { + _.forEach(result, (item) => { + if (item.traitId === 'personalization' && item.traits && item.traits.data) { + _.forEach(item.traits.data, (dataEntry) => { + delete dataEntry.links + }) + } + }) + } // convert date time for traits data _.filter(result, (item) => _.forEach(item.traits.data, function (value) { if (value.hasOwnProperty('birthDate')) { From bc313d82ef2207c0864f840b43c3cf9240768bf1 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 13 Feb 2026 23:53:24 +0100 Subject: [PATCH 7/7] deploy to dev --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 264ac37..3534fa9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ workflows: branches: only: - develop - - pm-3734 + - pm-3847 # Production builds are exectuted only on tagged commits to the # master branch.