Skip to content

Commit c394963

Browse files
authored
Merge pull request #132 from topcoder-platform/PM-5223-3
PM-5223: route QA challenge stats to Testing
2 parents cb4533e + 6fe8fdf commit c394963

6 files changed

Lines changed: 271 additions & 70 deletions

File tree

src/common/statsDimensionHelper.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const TRACK_NAMES = {
1010
const TYPE_NAMES = {
1111
CHALLENGE: 'Challenge',
1212
CODE: 'CODE',
13+
BUG_HUNT: 'BUG_HUNT',
14+
TEST_SCENARIOS: 'TEST_SCENARIOS',
15+
TEST_SUITES: 'TEST_SUITES',
1316
FIRST2FINISH: 'First2Finish',
1417
TASK: 'Task',
1518
SRM: 'SRM',
@@ -175,6 +178,9 @@ function buildChallengeDimensionLookup (trackRows, typeRows) {
175178
lookup.typeIds = {
176179
CHALLENGE: resolveTypeIdFromLookup(lookup, TYPE_NAMES.CHALLENGE) || null,
177180
CODE: resolveTypeIdFromLookup(lookup, TYPE_NAMES.CODE) || null,
181+
BUG_HUNT: resolveTypeIdFromLookup(lookup, TYPE_NAMES.BUG_HUNT) || null,
182+
TEST_SCENARIOS: resolveTypeIdFromLookup(lookup, TYPE_NAMES.TEST_SCENARIOS) || null,
183+
TEST_SUITES: resolveTypeIdFromLookup(lookup, TYPE_NAMES.TEST_SUITES) || null,
178184
FIRST2FINISH: resolveTypeIdFromLookup(lookup, TYPE_NAMES.FIRST2FINISH) || null,
179185
TASK: resolveTypeIdFromLookup(lookup, TYPE_NAMES.TASK) || null,
180186
SRM: resolveTypeIdFromLookup(lookup, TYPE_NAMES.SRM) || null,

src/scripts/recalculateMemberStats.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,8 @@ function isMarathonMatchTypeId (typeId) {
365365
/**
366366
* Normalize challenge metadata dimensions into the public stats dimensions.
367367
* Marathon Match stats/history belong to DATA_SCIENCE even when the source
368-
* challenge row was imported with a Development track.
368+
* challenge row was imported with a Development track. QA Challenge rows belong
369+
* to the legacy Testing surface under DEVELOP / BUG_HUNT.
369370
* @param {*} trackId raw ChallengeTrack id
370371
* @param {*} typeId raw ChallengeType id
371372
* @returns {Object} normalized trackId/typeId pair
@@ -379,6 +380,20 @@ function normalizeChallengeStatsDimension (trackId, typeId) {
379380
}
380381
}
381382

383+
const trackName = resolveTrackNameFromLookup(legacyLookupCache, trackId)
384+
const typeName = resolveTypeNameFromLookup(legacyLookupCache, normalizedTypeId) || resolveTypeName(normalizedTypeId)
385+
const normalizedTrackName = normalizeLookupKey(trackName).replace(/[\s-]+/g, '_')
386+
if (legacyLookupCache &&
387+
(normalizedTrackName === 'QUALITY_ASSURANCE' || normalizedTrackName === 'QA') &&
388+
typeName === TYPE_NAMES.CHALLENGE &&
389+
legacyLookupCache.trackIds.DEVELOP &&
390+
legacyLookupCache.typeIds.BUG_HUNT) {
391+
return {
392+
trackId: legacyLookupCache.trackIds.DEVELOP,
393+
typeId: legacyLookupCache.typeIds.BUG_HUNT
394+
}
395+
}
396+
382397
return {
383398
trackId: String(trackId),
384399
typeId: normalizedTypeId

src/services/StatisticsService.js

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ if (!_.includes(SUPPORTED_STATS_READ_SOURCES, configuredStatsReadSource)) {
6767
const USE_LEGACY_STATS_READS = configuredStatsReadSource === LEGACY_STATS_READ_SOURCE
6868
const RATING_SOURCE_DEVELOPMENT = 'DEVELOPMENT_CHALLENGE'
6969
const RATING_SOURCE_DATA_SCIENCE_CHALLENGE = 'DATA_SCIENCE_CHALLENGE'
70+
const RATING_SOURCE_QUALITY_ASSURANCE_CHALLENGE = 'QUALITY_ASSURANCE_CHALLENGE'
7071
const RATING_SOURCE_MARATHON_MATCH = 'MARATHON_MATCH'
7172
const RERATE_MARATHON_ACTOR = 'rerate-mm-stats'
7273
const CHALLENGE_TRACK_QUALITY_ASSURANCE = 'QUALITY_ASSURANCE'
@@ -379,7 +380,7 @@ function normalizeChallengeSourceTrack (value) {
379380

380381
/**
381382
* Check whether a challenge source track is Quality Assurance.
382-
* QA Challenge results are rated in the public Data Science Challenge bucket.
383+
* QA Challenge results are surfaced in the legacy Testing bucket.
383384
* @param {*} value raw challenge track label, enum value, or abbreviation
384385
* @returns {boolean} true when the source track is QA
385386
*/
@@ -390,11 +391,18 @@ function isQualityAssuranceChallengeSourceTrack (value) {
390391

391392
/**
392393
* Build the source tracks replayed for Data Science Challenge ratings.
393-
* QA Challenges share the public Data Science Challenge rating bucket.
394394
* @returns {Array<string>} challenge source track labels
395395
*/
396396
function getDataScienceChallengeSourceTrackNames () {
397-
return [TRACK_NAMES.DATA_SCIENCE, CHALLENGE_TRACK_QUALITY_ASSURANCE]
397+
return [TRACK_NAMES.DATA_SCIENCE]
398+
}
399+
400+
/**
401+
* Build the source tracks replayed for Quality Assurance Challenge ratings.
402+
* @returns {Array<string>} challenge source track labels
403+
*/
404+
function getQualityAssuranceChallengeSourceTrackNames () {
405+
return [CHALLENGE_TRACK_QUALITY_ASSURANCE]
398406
}
399407

400408
/**
@@ -474,8 +482,11 @@ function resolveChallengeRatingSource (challenge) {
474482
return RATING_SOURCE_DEVELOPMENT
475483
}
476484

477-
if ((trackName === TRACK_NAMES.DATA_SCIENCE || isQualityAssuranceChallengeSourceTrack(rawTrackName)) &&
478-
typeName === TYPE_NAMES.CHALLENGE) {
485+
if (isQualityAssuranceChallengeSourceTrack(rawTrackName) && typeName === TYPE_NAMES.CHALLENGE) {
486+
return RATING_SOURCE_QUALITY_ASSURANCE_CHALLENGE
487+
}
488+
489+
if (trackName === TRACK_NAMES.DATA_SCIENCE && typeName === TYPE_NAMES.CHALLENGE) {
479490
return RATING_SOURCE_DATA_SCIENCE_CHALLENGE
480491
}
481492

@@ -513,6 +524,14 @@ function buildBaseRatingJob (challenge, source) {
513524
}
514525
}
515526

527+
if (source === RATING_SOURCE_QUALITY_ASSURANCE_CHALLENGE) {
528+
return {
529+
source,
530+
trackId: TRACK_NAMES.DEVELOP,
531+
typeId: TYPE_NAMES.BUG_HUNT
532+
}
533+
}
534+
516535
if (source === RATING_SOURCE_MARATHON_MATCH) {
517536
return {
518537
source,
@@ -650,7 +669,9 @@ async function fetchMarathonMatchParticipantIds (reviewDbClient, challengeId) {
650669
*/
651670
async function fetchRatingParticipantIds (reviewDbClient, challengeClient, challengeId, source) {
652671
const challengeResultUserIds = await fetchChallengeResultParticipantIds(reviewDbClient, challengeId)
653-
if (source === RATING_SOURCE_DEVELOPMENT || source === RATING_SOURCE_DATA_SCIENCE_CHALLENGE) {
672+
if (source === RATING_SOURCE_DEVELOPMENT ||
673+
source === RATING_SOURCE_DATA_SCIENCE_CHALLENGE ||
674+
source === RATING_SOURCE_QUALITY_ASSURANCE_CHALLENGE) {
654675
return _.uniqBy(
655676
challengeResultUserIds.concat(await fetchChallengeWinnerParticipantIds(challengeClient, challengeId)),
656677
stringifyUserId
@@ -726,15 +747,24 @@ async function rerateChallengeRatingJobForMember (challengeClient, reviewDbClien
726747
}
727748

728749
if (job.source === RATING_SOURCE_DEVELOPMENT ||
729-
job.source === RATING_SOURCE_DATA_SCIENCE_CHALLENGE) {
730-
const rerateOptions = job.source === RATING_SOURCE_DATA_SCIENCE_CHALLENGE
731-
? {
750+
job.source === RATING_SOURCE_DATA_SCIENCE_CHALLENGE ||
751+
job.source === RATING_SOURCE_QUALITY_ASSURANCE_CHALLENGE) {
752+
let rerateOptions
753+
if (job.source === RATING_SOURCE_DATA_SCIENCE_CHALLENGE) {
754+
rerateOptions = {
732755
targetTrackName: TRACK_NAMES.DATA_SCIENCE,
733756
targetTypeName: TYPE_NAMES.CHALLENGE,
734757
challengeTrackNames: getDataScienceChallengeSourceTrackNames(),
735758
challengeTypeNames: [TYPE_NAMES.CHALLENGE]
736759
}
737-
: undefined
760+
} else if (job.source === RATING_SOURCE_QUALITY_ASSURANCE_CHALLENGE) {
761+
rerateOptions = {
762+
targetTrackName: TRACK_NAMES.DEVELOP,
763+
targetTypeName: TYPE_NAMES.BUG_HUNT,
764+
challengeTrackNames: getQualityAssuranceChallengeSourceTrackNames(),
765+
challengeTypeNames: [TYPE_NAMES.CHALLENGE]
766+
}
767+
}
738768

739769
return rerateDevTrack(
740770
prisma,
@@ -963,8 +993,9 @@ function isMarathonMatchType (typeName) {
963993

964994
/**
965995
* Resolve the public stats dimensions for a challenge-backed row.
966-
* Marathon Match and QA Challenge rows are part of the public DATA_SCIENCE
967-
* bucket even when source challenge metadata uses a different track.
996+
* Marathon Match rows are part of the public DATA_SCIENCE bucket even when
997+
* source challenge metadata uses a different track. QA Challenge rows retain
998+
* the legacy Testing surface under DEVELOP / BUG_HUNT.
968999
* @param {Object} row row containing trackId and typeId
9691000
* @param {Object} dimensionLookup shared challenge dimension lookup
9701001
* @returns {Object} normalized track/type ids and names
@@ -983,12 +1014,16 @@ function resolveStatsDimensionForChallengeRow (row, dimensionLookup) {
9831014

9841015
const trackName = resolveTrackNameFromLookup(dimensionLookup, row.trackId)
9851016
if (typeName === TYPE_NAMES.CHALLENGE && isQualityAssuranceChallengeSourceTrack(trackName)) {
986-
const dataScienceTrackId = resolveTrackIdFromLookup(dimensionLookup, TRACK_NAMES.DATA_SCIENCE)
987-
return {
988-
trackId: dataScienceTrackId || row.trackId,
989-
typeId: row.typeId,
990-
trackName: TRACK_NAMES.DATA_SCIENCE,
991-
typeName
1017+
const developTrackId = resolveTrackIdFromLookup(dimensionLookup, TRACK_NAMES.DEVELOP)
1018+
const bugHuntTypeId = resolveTypeIdFromLookup(dimensionLookup, TYPE_NAMES.BUG_HUNT)
1019+
1020+
if (developTrackId && bugHuntTypeId) {
1021+
return {
1022+
trackId: developTrackId,
1023+
typeId: bugHuntTypeId,
1024+
trackName: TRACK_NAMES.DEVELOP,
1025+
typeName: TYPE_NAMES.BUG_HUNT
1026+
}
9921027
}
9931028
}
9941029

@@ -1236,9 +1271,10 @@ function filterUnifiedHistoryRowsToCompletedChallenges (rows, challengeMetadataB
12361271
* the response payload consumed by the profiles UI.
12371272
* @param {Array<Object>} rows unified history rows loaded from members.memberStatsHistory
12381273
* @param {Map<string, Object>} challengeMetadataById challenge metadata keyed by UUID and legacy ids
1274+
* @param {Object} dimensionLookup shared challenge dimension lookup
12391275
* @returns {Array<Object>} rows enriched with canonical challenge ids and names when available
12401276
*/
1241-
function enrichUnifiedHistoryRowsWithChallengeMetadata (rows, challengeMetadataById) {
1277+
function enrichUnifiedHistoryRowsWithChallengeMetadata (rows, challengeMetadataById, dimensionLookup) {
12421278
return _.map(rows || [], (row) => {
12431279
const challengeId = _.isNil(row.challengeId) ? null : String(row.challengeId).trim()
12441280
if (!challengeId) {
@@ -1257,9 +1293,16 @@ function enrichUnifiedHistoryRowsWithChallengeMetadata (rows, challengeMetadataB
12571293
: _.get(challenge, 'legacyRecord.legacySystemId')
12581294
)
12591295
const preserveLegacyChallengeId = isLegacyNumericMarathonHistoryRow(row)
1296+
const dimension = challenge.trackId && challenge.typeId
1297+
? resolveStatsDimensionForChallengeRow({
1298+
trackId: String(challenge.trackId),
1299+
typeId: String(challenge.typeId)
1300+
}, dimensionLookup)
1301+
: {}
12601302

12611303
return {
12621304
...row,
1305+
...dimension,
12631306
challengeId: preserveLegacyChallengeId ? challengeId : canonicalChallengeId,
12641307
canonicalChallengeId,
12651308
legacyChallengeId,
@@ -1532,10 +1575,7 @@ function buildAggregatedStatsFromReviewResults (reviewRows, challengeMetadataByI
15321575
return
15331576
}
15341577

1535-
const dimension = resolveStatsDimensionForChallengeRow({
1536-
trackId: String(challenge.trackId),
1537-
typeId: String(challenge.typeId)
1538-
}, dimensionLookup)
1578+
const dimension = resolveStatsDimensionForChallengeRow(challenge, dimensionLookup)
15391579
const trackId = dimension.trackId
15401580
const typeId = dimension.typeId
15411581
if (!trackId || !typeId) {
@@ -1647,10 +1687,7 @@ function buildFallbackHistoryRowsFromReviewResults (reviewRows, challengeMetadat
16471687
return
16481688
}
16491689

1650-
const dimension = resolveStatsDimensionForChallengeRow({
1651-
trackId: String(challenge.trackId),
1652-
typeId: String(challenge.typeId)
1653-
}, dimensionLookup)
1690+
const dimension = resolveStatsDimensionForChallengeRow(challenge, dimensionLookup)
16541691
const trackId = dimension.trackId
16551692
const typeId = dimension.typeId
16561693
const pairKey = buildStatsTrackTypeKey(trackId, typeId)
@@ -1731,10 +1768,7 @@ function buildFallbackHistoryRowsFromChallengeWinners (winnerRows, dimensionLook
17311768
return
17321769
}
17331770

1734-
const dimension = resolveStatsDimensionForChallengeRow({
1735-
trackId: String(challenge.trackId),
1736-
typeId: String(challenge.typeId)
1737-
}, dimensionLookup)
1771+
const dimension = resolveStatsDimensionForChallengeRow(challenge, dimensionLookup)
17381772
const trackId = dimension.trackId
17391773
const typeId = dimension.typeId
17401774
const pairKey = buildStatsTrackTypeKey(trackId, typeId)
@@ -3425,7 +3459,8 @@ async function getHistoryStats (currentUser, handle, query) {
34253459
let annotatedRows = dedupeUnifiedHistoryRows(filterUnifiedHistoryRowsToCompletedChallenges(
34263460
enrichUnifiedHistoryRowsWithChallengeMetadata(
34273461
annotateUnifiedDimensionRows(historyRows, dimensionLookup),
3428-
challengeMetadataById
3462+
challengeMetadataById,
3463+
dimensionLookup
34293464
),
34303465
challengeMetadataById
34313466
))
@@ -4318,7 +4353,7 @@ refreshMemberStats.schema = {
43184353
* rating dimensions. This includes the native challenge track/type rating when
43194354
* supported and any configured named rating paths whose tags/skills match the
43204355
* challenge, such as the default AI path. Quality Assurance Challenge rows are
4321-
* replayed into the public DATA_SCIENCE / Challenge rating bucket.
4356+
* replayed into the legacy Testing bucket under DEVELOP / BUG_HUNT.
43224357
* @param {Object} currentUser the user who performs operation
43234358
* @param {Object} data rerate payload containing the completed challenge id
43244359
* @returns {Object} summary of participants, rating jobs, updates, and per-member failures
@@ -4481,8 +4516,6 @@ rerateChallengeSubmitterRatings.schema = {
44814516
* Trigger a DEVELOPMENT / Challenge, DATA_SCIENCE / Challenge,
44824517
* DATA_SCIENCE / MARATHON_MATCH, or configured tag- or skill-based rating path
44834518
* re-rating pass beginning with the supplied challenge.
4484-
* DATA_SCIENCE / Challenge rerates also replay Quality Assurance Challenge
4485-
* source rows because QA history is surfaced in that public rating bucket.
44864519
* The relevant review-api results are reprocessed in chronological order and
44874520
* persisted into the existing unified rating tables for the member.
44884521
* @param {Object} currentUser the user who performs operation

test/unit/DevelopRatingEngine.test.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const DEVELOP_TRACK_ID = 'track-develop-id'
1919
const DATA_SCIENCE_TRACK_ID = 'track-data-science-id'
2020
const CHALLENGE_TYPE_ID = 'type-challenge-id'
2121
const CODE_TYPE_ID = 'type-code-id'
22+
const BUG_HUNT_TYPE_ID = 'type-bug-hunt-id'
2223
const MARATHON_MATCH_TYPE_ID = 'type-marathon-match-id'
2324

2425
function isBigIntValue (value) {
@@ -371,6 +372,7 @@ function createChallengeClient (metadataById, winnerRows = []) {
371372
return [
372373
{ id: CHALLENGE_TYPE_ID, name: 'Challenge', abbreviation: 'CH', legacyId: null, isTask: false },
373374
{ id: CODE_TYPE_ID, name: 'Code', abbreviation: 'CODE', legacyId: null, isTask: false },
375+
{ id: BUG_HUNT_TYPE_ID, name: 'BUG_HUNT', abbreviation: 'LBGH', legacyId: 120, isTask: false },
374376
{ id: MARATHON_MATCH_TYPE_ID, name: 'Marathon Match', abbreviation: 'MM', legacyId: null, isTask: false }
375377
]
376378
}
@@ -598,7 +600,7 @@ describe('develop rating engine unit tests', () => {
598600
members.state.rankRecalculationCalls[0].typeId.should.equal(CHALLENGE_TYPE_ID)
599601
})
600602

601-
it('rerateDevTrack should include QA ChallengeWinner placements in Data Science Challenge rerates', async () => {
603+
it('rerateDevTrack should include QA ChallengeWinner placements in Testing rerates', async () => {
602604
const targetUserId = toBigInt(89770374)
603605
const opponentUserId = toBigInt(100000039)
604606
const challengeId = 'qa-rating-jun-2'
@@ -648,9 +650,9 @@ describe('develop rating engine unit tests', () => {
648650
targetUserId,
649651
challengeId,
650652
{
651-
targetTrackName: 'DATA_SCIENCE',
652-
targetTypeName: 'Challenge',
653-
challengeTrackNames: ['DATA_SCIENCE', 'QUALITY_ASSURANCE'],
653+
targetTrackName: 'DEVELOP',
654+
targetTypeName: 'BUG_HUNT',
655+
challengeTrackNames: ['QUALITY_ASSURANCE'],
654656
challengeTypeNames: ['Challenge']
655657
}
656658
)
@@ -660,8 +662,8 @@ describe('develop rating engine unit tests', () => {
660662

661663
const statsRow = members.state.statsRows.find((row) =>
662664
String(row.userId) === String(targetUserId) &&
663-
row.trackId === DATA_SCIENCE_TRACK_ID &&
664-
row.typeId === CHALLENGE_TYPE_ID
665+
row.trackId === DEVELOP_TRACK_ID &&
666+
row.typeId === BUG_HUNT_TYPE_ID
665667
)
666668
should.exist(statsRow)
667669
statsRow.rating.should.equal(expectedTarget.rating)
@@ -671,16 +673,16 @@ describe('develop rating engine unit tests', () => {
671673

672674
const historyRow = findHistoryRow(members.state.historyRows, targetUserId, challengeId)
673675
should.exist(historyRow)
674-
historyRow.trackId.should.equal(DATA_SCIENCE_TRACK_ID)
675-
historyRow.typeId.should.equal(CHALLENGE_TYPE_ID)
676+
historyRow.trackId.should.equal(DEVELOP_TRACK_ID)
677+
historyRow.typeId.should.equal(BUG_HUNT_TYPE_ID)
676678
historyRow.newRating.should.equal(expectedTarget.rating)
677679
historyRow.mostRecent.should.equal(true)
678680

679681
const maxRatingRow = members.state.maxRatingRows.find((row) => String(row.userId) === String(targetUserId))
680682
should.exist(maxRatingRow)
681683
maxRatingRow.rating.should.equal(expectedTarget.rating)
682-
maxRatingRow.track.should.equal('DATA_SCIENCE')
683-
maxRatingRow.subTrack.should.equal('Challenge')
684+
maxRatingRow.track.should.equal('DEVELOP')
685+
maxRatingRow.subTrack.should.equal('BUG_HUNT')
684686
maxRatingRow.ratingColor.should.equal(getRatingColor(expectedTarget.rating))
685687
})
686688

0 commit comments

Comments
 (0)