Skip to content

Commit ef42e7e

Browse files
committed
Additional fixes for returning screenings if a user is a reviewer
1 parent e0bbc81 commit ef42e7e

File tree

4 files changed

+306
-7
lines changed

4 files changed

+306
-7
lines changed

src/api/review/review.service.spec.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,139 @@ describe('ReviewService.getReviews reviewer visibility', () => {
16861686
expect(otherReview?.reviewerMaxRating).toBe(1800);
16871687
});
16881688

1689+
it('exposes screening review items and scores to other reviewers', async () => {
1690+
const now = new Date();
1691+
const reviewerUser: JwtUser = {
1692+
userId: '101',
1693+
roles: [],
1694+
isMachine: false,
1695+
};
1696+
1697+
const reviewerResource = {
1698+
...buildResource('Reviewer', reviewerUser.userId),
1699+
id: 'resource-self',
1700+
};
1701+
1702+
resourceApiServiceMock.getMemberResourcesRoles.mockResolvedValue([
1703+
reviewerResource,
1704+
]);
1705+
1706+
const reviewRecords = [
1707+
{
1708+
id: 'review-self',
1709+
resourceId: 'resource-self',
1710+
submissionId: baseSubmission.id,
1711+
phaseId: 'phase-review',
1712+
scorecardId: 'scorecard-1',
1713+
typeId: 'type-1',
1714+
status: ReviewStatus.COMPLETED,
1715+
reviewDate: now,
1716+
committed: true,
1717+
metadata: null,
1718+
reviewItems: [
1719+
{
1720+
id: 'item-self',
1721+
scorecardQuestionId: 'question-1',
1722+
initialAnswer: 'Yes',
1723+
finalAnswer: 'Yes',
1724+
managerComment: null,
1725+
createdAt: now,
1726+
createdBy: 'reviewer',
1727+
updatedAt: now,
1728+
updatedBy: 'reviewer',
1729+
reviewItemComments: [],
1730+
},
1731+
],
1732+
createdAt: now,
1733+
createdBy: 'creator',
1734+
updatedAt: now,
1735+
updatedBy: 'updater',
1736+
finalScore: 98,
1737+
initialScore: 96,
1738+
submission: {
1739+
id: baseSubmission.id,
1740+
memberId: '301',
1741+
challengeId: 'challenge-1',
1742+
},
1743+
},
1744+
{
1745+
id: 'review-screening',
1746+
resourceId: 'resource-other',
1747+
submissionId: baseSubmission.id,
1748+
phaseId: 'phase-screening',
1749+
scorecardId: 'scorecard-2',
1750+
typeId: 'type-2',
1751+
status: ReviewStatus.COMPLETED,
1752+
reviewDate: now,
1753+
committed: true,
1754+
metadata: null,
1755+
reviewItems: [
1756+
{
1757+
id: 'item-screening',
1758+
scorecardQuestionId: 'question-3',
1759+
initialAnswer: 'Yes',
1760+
finalAnswer: 'Yes',
1761+
managerComment: null,
1762+
createdAt: now,
1763+
createdBy: 'other-reviewer',
1764+
updatedAt: now,
1765+
updatedBy: 'other-reviewer',
1766+
reviewItemComments: [],
1767+
},
1768+
],
1769+
createdAt: now,
1770+
createdBy: 'creator',
1771+
updatedAt: now,
1772+
updatedBy: 'updater',
1773+
finalScore: 82,
1774+
initialScore: 80,
1775+
submission: {
1776+
id: baseSubmission.id,
1777+
memberId: '301',
1778+
challengeId: 'challenge-1',
1779+
},
1780+
},
1781+
];
1782+
1783+
prismaMock.review.findMany.mockResolvedValue(reviewRecords);
1784+
prismaMock.review.count.mockResolvedValue(reviewRecords.length);
1785+
1786+
resourcePrismaMock.resource.findMany.mockResolvedValue([
1787+
{ id: 'resource-self', memberId: '101' },
1788+
{ id: 'resource-other', memberId: '202' },
1789+
]);
1790+
1791+
memberPrismaMock.member.findMany.mockResolvedValue([
1792+
{
1793+
userId: BigInt(101),
1794+
handle: 'selfHandle',
1795+
maxRating: { rating: 2400 },
1796+
},
1797+
{
1798+
userId: BigInt(202),
1799+
handle: 'screeningHandle',
1800+
maxRating: { rating: 2100 },
1801+
},
1802+
]);
1803+
1804+
const response = await service.getReviews(
1805+
reviewerUser,
1806+
undefined,
1807+
'challenge-1',
1808+
);
1809+
1810+
expect(response.data).toHaveLength(2);
1811+
const screeningReview = response.data.find(
1812+
(review) => review.id === 'review-screening',
1813+
);
1814+
1815+
expect(screeningReview?.reviewItems).toHaveLength(1);
1816+
expect(screeningReview?.finalScore).toBe(82);
1817+
expect(screeningReview?.initialScore).toBe(80);
1818+
expect(screeningReview?.reviewerHandle).toBe('screeningHandle');
1819+
expect(screeningReview?.reviewerMaxRating).toBe(2100);
1820+
});
1821+
16891822
it('returns empty results for submitters when reviews are not yet accessible', async () => {
16901823
const submitterUser: JwtUser = {
16911824
userId: 'submitter-1',

src/api/review/review.service.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3434,7 +3434,9 @@ export class ReviewService {
34343434
!isPrivilegedRequester &&
34353435
reviewerResourceIdSet.size > 0 &&
34363436
!isReviewerForReview &&
3437-
!this.isCompletedOrCancelledStatus(challengeStatusForReview);
3437+
!this.isCompletedOrCancelledStatus(challengeStatusForReview) &&
3438+
normalizedPhaseName !== 'screening' &&
3439+
normalizedPhaseName !== 'checkpoint screening';
34383440

34393441
const sanitizeScores =
34403442
shouldMaskReviewDetails ||
@@ -3456,11 +3458,16 @@ export class ReviewService {
34563458
!isOwnSubmission &&
34573459
normalizedPhaseName === 'iterative review' &&
34583460
this.isFirst2FinishChallenge(challengeForReview);
3461+
const shouldStripOtherReviewerContent =
3462+
shouldMaskOtherReviewerDetails &&
3463+
!['screening', 'checkpoint screening'].includes(
3464+
normalizedPhaseName ?? '',
3465+
);
34593466
const shouldStripReviewItems =
34603467
shouldMaskReviewDetails ||
34613468
shouldTrimIterativeReviewForOtherSubmitters ||
34623469
shouldLimitNonOwnerVisibility ||
3463-
shouldMaskOtherReviewerDetails;
3470+
shouldStripOtherReviewerContent;
34643471

34653472
if (!isThin) {
34663473
sanitizedReview.reviewItems = shouldStripReviewItems

src/api/submission/submission.service.spec.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,7 @@ describe('SubmissionService', () => {
997997
id: 'review-self',
998998
resourceId: 'resource-self',
999999
submissionId: 'submission-1',
1000+
phaseId: 'phase-123',
10001001
finalScore: 95,
10011002
initialScore: 92,
10021003
reviewItems: [
@@ -1017,6 +1018,7 @@ describe('SubmissionService', () => {
10171018
id: 'review-other',
10181019
resourceId: 'resource-other',
10191020
submissionId: 'submission-1',
1021+
phaseId: 'phase-123',
10201022
finalScore: 80,
10211023
initialScore: 78,
10221024
reviewItems: [
@@ -1100,5 +1102,144 @@ describe('SubmissionService', () => {
11001102
expect(otherReview?.reviewerHandle).toBe('otherHandle');
11011103
expect(otherReview?.reviewerMaxRating).toBe(1800);
11021104
});
1105+
1106+
it('preserves screening review items and scores for other reviewers', async () => {
1107+
const now = new Date('2025-01-06T10:00:00Z');
1108+
challengeApiServiceMock.getChallengeDetail.mockResolvedValue({
1109+
id: 'challenge-1',
1110+
status: ChallengeStatus.ACTIVE,
1111+
type: 'Challenge',
1112+
legacy: {},
1113+
phases: [
1114+
{
1115+
id: 'phase-review',
1116+
phaseId: 'legacy-phase-review',
1117+
name: 'Review',
1118+
},
1119+
{
1120+
id: 'phase-screening',
1121+
phaseId: 'legacy-phase-screening',
1122+
name: 'Screening',
1123+
},
1124+
],
1125+
});
1126+
resourceApiServiceListMock.getMemberResourcesRoles.mockResolvedValue([
1127+
{
1128+
roleName: 'Reviewer',
1129+
id: 'resource-self',
1130+
memberId: '101',
1131+
},
1132+
]);
1133+
1134+
const submissions = [
1135+
{
1136+
id: 'submission-2',
1137+
challengeId: 'challenge-1',
1138+
memberId: 'submitter-1',
1139+
submittedDate: now,
1140+
createdAt: now,
1141+
updatedAt: now,
1142+
type: SubmissionType.CONTEST_SUBMISSION,
1143+
status: SubmissionStatus.ACTIVE,
1144+
review: [
1145+
{
1146+
id: 'review-self',
1147+
resourceId: 'resource-self',
1148+
submissionId: 'submission-2',
1149+
phaseId: 'phase-review',
1150+
finalScore: 90,
1151+
initialScore: 88,
1152+
reviewItems: [
1153+
{
1154+
id: 'item-self',
1155+
scorecardQuestionId: 'q1',
1156+
initialAnswer: 'YES',
1157+
finalAnswer: 'YES',
1158+
reviewItemComments: [],
1159+
},
1160+
],
1161+
createdAt: now,
1162+
createdBy: 'reviewer',
1163+
updatedAt: now,
1164+
updatedBy: 'reviewer',
1165+
},
1166+
{
1167+
id: 'review-screening',
1168+
resourceId: 'resource-other',
1169+
submissionId: 'submission-2',
1170+
phaseId: 'phase-screening',
1171+
finalScore: 75,
1172+
initialScore: 70,
1173+
reviewItems: [
1174+
{
1175+
id: 'item-screening',
1176+
scorecardQuestionId: 'q2',
1177+
initialAnswer: 'NO',
1178+
finalAnswer: 'NO',
1179+
reviewItemComments: [],
1180+
},
1181+
],
1182+
createdAt: now,
1183+
createdBy: 'screening-reviewer',
1184+
updatedAt: now,
1185+
updatedBy: 'screening-reviewer',
1186+
},
1187+
],
1188+
reviewSummation: [],
1189+
legacyChallengeId: null,
1190+
prizeId: null,
1191+
},
1192+
];
1193+
1194+
prismaMock.submission.findMany.mockResolvedValue(
1195+
submissions.map((entry) => ({ ...entry })),
1196+
);
1197+
prismaMock.submission.count.mockResolvedValue(submissions.length);
1198+
prismaMock.submission.findFirst.mockResolvedValue({
1199+
id: 'submission-2',
1200+
});
1201+
1202+
resourcePrismaListMock.resource.findMany.mockResolvedValue([
1203+
{ id: 'resource-self', memberId: '101' },
1204+
{ id: 'resource-other', memberId: '202' },
1205+
]);
1206+
1207+
memberPrismaMock.member.findMany.mockResolvedValue([
1208+
{
1209+
userId: BigInt(101),
1210+
handle: 'selfHandle',
1211+
maxRating: { rating: 2500 },
1212+
},
1213+
{
1214+
userId: BigInt(202),
1215+
handle: 'screeningHandle',
1216+
maxRating: { rating: 2000 },
1217+
},
1218+
]);
1219+
1220+
const result = await listService.listSubmission(
1221+
{
1222+
userId: '101',
1223+
isMachine: false,
1224+
roles: [UserRole.Reviewer],
1225+
} as any,
1226+
{ challengeId: 'challenge-1' } as any,
1227+
{ page: 1, perPage: 50 } as any,
1228+
);
1229+
1230+
const submissionResult = result.data.find(
1231+
(entry) => entry.id === 'submission-2',
1232+
);
1233+
const screeningReview = submissionResult?.review?.find(
1234+
(review) => review.id === 'review-screening',
1235+
);
1236+
1237+
expect(screeningReview).toBeDefined();
1238+
expect(screeningReview?.initialScore).toBe(70);
1239+
expect(screeningReview?.finalScore).toBe(75);
1240+
expect(screeningReview?.reviewItems).toHaveLength(1);
1241+
expect(screeningReview?.reviewerHandle).toBe('screeningHandle');
1242+
expect(screeningReview?.reviewerMaxRating).toBe(2000);
1243+
});
11031244
});
11041245
});

src/api/submission/submission.service.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2409,14 +2409,32 @@ export class SubmissionService {
24092409
const ownsReview =
24102410
resourceId.length > 0 &&
24112411
roleSummary.reviewerResourceIds.includes(resourceId);
2412+
const resolvedPhaseName = this.getPhaseNameFromId(
2413+
challenge,
2414+
(review as any).phaseId ?? null,
2415+
);
2416+
const normalizedPhaseName =
2417+
this.normalizePhaseName(resolvedPhaseName);
2418+
const isScreeningPhase =
2419+
normalizedPhaseName === 'screening' ||
2420+
normalizedPhaseName === 'checkpoint screening';
24122421

24132422
if (!ownsReview) {
2414-
review.initialScore = null;
2415-
review.finalScore = null;
2416-
if (Array.isArray(review.reviewItems)) {
2417-
review.reviewItems = [];
2423+
if (!isScreeningPhase) {
2424+
review.initialScore = null;
2425+
review.finalScore = null;
2426+
review.reviewItems = Array.isArray(review.reviewItems)
2427+
? []
2428+
: [];
24182429
} else {
2419-
review.reviewItems = [];
2430+
review.initialScore =
2431+
typeof review.initialScore === 'number'
2432+
? review.initialScore
2433+
: (review.initialScore ?? null);
2434+
review.finalScore =
2435+
typeof review.finalScore === 'number'
2436+
? review.finalScore
2437+
: (review.finalScore ?? null);
24202438
}
24212439
}
24222440
}

0 commit comments

Comments
 (0)