diff --git a/.circleci/config.yml b/.circleci/config.yml index 264ac37..9f8e1b4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,6 @@ workflows: branches: only: - develop - - pm-3734 # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/src/common/profileTemplate.js b/src/common/profileTemplate.js index 56b19f9..b6e752a 100644 --- a/src/common/profileTemplate.js +++ b/src/common/profileTemplate.js @@ -496,13 +496,12 @@ 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) 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 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 }, diff --git a/src/services/MemberService.js b/src/services/MemberService.js index 3ffb5d3..f1093b6 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,19 @@ 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 }, + const winnerRows = await challengesPrisma.ChallengeWinner.findMany({ + where: { + userId: numUserId, + type: { in: ['PLACEMENT', 'PASSED_REVIEW'] } + }, include: { challenge: { include: { track: true } @@ -1547,18 +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]) { - 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 + if (w.type === 'PLACEMENT') row.wins += 1 + if (w.type === 'PASSED_REVIEW') row.submissions += 1 } // 2) Resources: registrations (distinct challenges) by track @@ -1592,13 +1590,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 +1601,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 +1721,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) 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')) {