Skip to content

Commit cbc0120

Browse files
authored
Merge pull request #1278 from topcoder-platform/feat/v6
QA fixes for reviewers seeing screenings even if they aren't a screener
2 parents a67129b + 3a8bb21 commit cbc0120

File tree

7 files changed

+109
-37
lines changed

7 files changed

+109
-37
lines changed

src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentScreening.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,29 @@ export const TabContentScreening: FC<Props> = (props: Props) => {
7171
const filteredScreening = useMemo<Screening[]>(
7272
() => {
7373
const baseRows = props.screening ?? []
74+
// Defensive filtering: exclude any entries that don't belong to Screening phase
75+
const phaseValidatedRows = baseRows.filter(row => {
76+
if (!row.reviewId) {
77+
return true
78+
}
79+
80+
const normalizedPhaseName = row.phaseName
81+
?.toLowerCase()
82+
.trim()
83+
84+
return normalizedPhaseName === 'screening'
85+
})
7486
const canSeeAll = isPrivilegedRole || hasReviewerRole
7587

7688
if (isChallengeCompleted && !canSeeAll && !hasPassedScreeningThreshold) {
7789
return []
7890
}
7991

8092
if (canSeeAll || (isChallengeCompleted && hasPassedScreeningThreshold)) {
81-
return baseRows
93+
return phaseValidatedRows
8294
}
8395

84-
return baseRows.filter(row => {
96+
return phaseValidatedRows.filter(row => {
8597
if (row.myReviewResourceId
8698
&& (screenerResourceIds.has(row.myReviewResourceId)
8799
|| reviewerResourceIds.has(row.myReviewResourceId))) {

src/apps/review/src/lib/components/TableRegistration/TableRegistration.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ export const TableRegistration: FC<Props> = (props: Props) => {
5858
[myRoles],
5959
)
6060

61+
const hasManagerRole = useMemo(
62+
() => (myRoles ?? [])
63+
.some(role => role
64+
?.trim()
65+
.toLowerCase() === 'manager'),
66+
[myRoles],
67+
)
68+
6169
const isAdmin = useMemo(
6270
() => loginUserInfo?.roles?.some(
6371
role => typeof role === 'string'
@@ -66,7 +74,7 @@ export const TableRegistration: FC<Props> = (props: Props) => {
6674
[loginUserInfo?.roles],
6775
)
6876

69-
const shouldDisplayEmail = hasCopilotRole || isAdmin
77+
const shouldDisplayEmail = hasCopilotRole || hasManagerRole || isAdmin
7078

7179
const columns = useMemo<TableColumn<BackendResource>[]>(
7280
() => {

src/apps/review/src/lib/components/TableSubmissionScreening/TableSubmissionScreening.tsx

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ import {
4444
import { ChallengeDetailContext, ReviewAppContext } from '../../contexts'
4545
import { updateReview } from '../../services'
4646
import { ConfirmModal } from '../ConfirmModal'
47-
import { useSubmissionDownloadAccess } from '../../hooks'
47+
import { useRole, useSubmissionDownloadAccess } from '../../hooks'
4848
import type { UseSubmissionDownloadAccessResult } from '../../hooks/useSubmissionDownloadAccess'
49+
import type { useRoleProps } from '../../hooks/useRole'
4950

5051
import styles from './TableSubmissionScreening.module.scss'
5152

@@ -424,48 +425,79 @@ const isScreeningReviewInProgress = (entry: Screening): boolean => (
424425
|| isInProgressStatus(entry.myReviewStatus)
425426
)
426427

428+
/**
429+
* Creates columns for displaying screening review data.
430+
*
431+
* Note: This function assumes that the input data has been validated
432+
* to contain only Screening phase reviews. Defensive filtering is
433+
* performed in TabContentScreening to ensure phase data isolation.
434+
*
435+
* @param config - Configuration for scorecard access and score masking
436+
* @returns Array of table columns for screening data
437+
*/
427438
const createScreeningColumns = ({
428439
canViewScorecard,
429440
shouldMaskScore,
430441
}: ScreeningColumnConfig): TableColumn<Screening>[] => [
431442
{
432443
label: 'Screener',
433-
propertyName: 'screenerHandle',
434-
renderer: (data: Screening) => (data.screener?.memberHandle ? (
435-
<a
436-
href={getHandleUrl(data.screener)}
437-
target='_blank'
438-
rel='noreferrer'
439-
style={{
440-
color: data.screener?.handleColor,
441-
}}
442-
onClick={function onClick() {
443-
window.open(
444-
getHandleUrl(data.screener),
445-
'_blank',
446-
)
447-
}}
448-
>
449-
{data.screener?.memberHandle ?? ''}
450-
</a>
451-
) : (
452-
<span
453-
style={{
454-
color: data.screener?.handleColor,
455-
}}
456-
>
457-
{data.screener?.memberHandle ?? ''}
458-
</span>
459-
)),
444+
propertyName: 'screener',
445+
renderer: (data: Screening) => {
446+
const normalizedPhaseName = data.phaseName
447+
?.toLowerCase()
448+
.trim()
449+
450+
if (normalizedPhaseName && normalizedPhaseName !== 'screening') {
451+
return <span />
452+
}
453+
454+
// Display the screener handle from the Screening phase review
455+
// (Review phase data is filtered out in TabContentScreening)
456+
return data.screener?.memberHandle ? (
457+
<a
458+
href={getHandleUrl(data.screener)}
459+
target='_blank'
460+
rel='noreferrer'
461+
style={{
462+
color: data.screener?.handleColor,
463+
}}
464+
onClick={function onClick() {
465+
window.open(
466+
getHandleUrl(data.screener),
467+
'_blank',
468+
)
469+
}}
470+
>
471+
{data.screener?.memberHandle ?? ''}
472+
</a>
473+
) : (
474+
<span
475+
style={{
476+
color: data.screener?.handleColor,
477+
}}
478+
>
479+
{data.screener?.memberHandle ?? ''}
480+
</span>
481+
)
482+
},
460483
type: 'element',
461484
},
462485
{
463486
label: 'Screening Score',
464487
propertyName: 'score',
465488
renderer: (data: Screening) => {
489+
const normalizedPhaseName = data.phaseName
490+
?.toLowerCase()
491+
.trim()
492+
// Link to the Screening phase scorecard
493+
// (Review phase scorecards are filtered out upstream)
466494
const maskScore = shouldMaskScore(data)
467495
const scoreValue = maskScore ? '--' : (data.score ?? '-')
468496

497+
if (normalizedPhaseName && normalizedPhaseName !== 'screening') {
498+
return <span>{scoreValue}</span>
499+
}
500+
469501
if (!data.reviewId || maskScore) {
470502
return <span>{scoreValue}</span>
471503
}
@@ -631,6 +663,7 @@ export const TableSubmissionScreening: FC<Props> = (props: Props) => {
631663
getRestrictionMessageForMember,
632664
currentMemberId,
633665
}: UseSubmissionDownloadAccessResult = useSubmissionDownloadAccess()
666+
const { hasReviewerRole }: useRoleProps = useRole()
634667

635668
const normalisedRoles = useMemo(
636669
() => (myRoles ?? []).map(role => role.toLowerCase()),
@@ -971,7 +1004,7 @@ export const TableSubmissionScreening: FC<Props> = (props: Props) => {
9711004
return false
9721005
}
9731006

974-
if (canViewAllScorecards) {
1007+
if (canViewAllScorecards || hasReviewerRole) {
9751008
return true
9761009
}
9771010

@@ -996,6 +1029,7 @@ export const TableSubmissionScreening: FC<Props> = (props: Props) => {
9961029
},
9971030
[
9981031
canViewAllScorecards,
1032+
hasReviewerRole,
9991033
currentMemberId,
10001034
myResourceIds,
10011035
],

src/apps/review/src/lib/hooks/useFetchScreeningReview.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,7 @@ export function useFetchScreeningReview(): useFetchScreeningReviewProps {
11491149
myReviewId: myAssignment?.id,
11501150
myReviewResourceId: myAssignment?.resourceId,
11511151
myReviewStatus: myAssignment?.status ?? undefined,
1152+
phaseName: matchedReview?.phaseName ?? undefined,
11521153
result,
11531154
reviewId: matchedReview?.id,
11541155
reviewPhaseId: resolveReviewPhaseId(matchedReview),

src/apps/review/src/lib/models/Screening.model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ export interface Screening {
5757
* Submission type (e.g. CONTEST_SUBMISSION, CHECKPOINT_SUBMISSION).
5858
*/
5959
type?: string
60+
/**
61+
* The phase name of the associated review (e.g., 'Screening', 'Review').
62+
* Used for defensive filtering to ensure phase data isolation.
63+
*/
64+
phaseName?: string
6065
}
6166

6267
/**

src/apps/review/src/lib/utils/reviewMatching.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,14 @@ function resolvePhaseOrTypeMatch({
118118
return true
119119
}
120120

121-
debugLog('reviewMatchesPhase.phaseNameFallback', {
121+
debugLog('reviewMatchesPhase.phaseNameMismatchRejected', {
122122
normalizedPhaseName,
123123
normalizedReviewPhaseName,
124124
reviewId: review.id,
125125
reviewPhaseName: truncateForLog(reviewPhaseName ?? ''),
126126
})
127+
128+
return false
127129
}
128130

129131
if (normalizedReviewTypeAlpha && reviewType && normalizedPhaseNameForReviewType) {
@@ -351,14 +353,15 @@ const doesReviewMatchScorecard = (
351353
}
352354

353355
/**
354-
* Determines whether a review matches the supplied phase context by evaluating scorecard,
355-
* phase identifier, phase name, type, and metadata criteria in priority order.
356+
* Determines whether a review matches the supplied phase context. Reviews that include an explicit
357+
* phase name must match the target phase name exactly; otherwise scorecard, phase identifier, type,
358+
* and metadata criteria are evaluated in order.
356359
*
357360
* @param review - Review to evaluate.
358361
* @param scorecardId - Scorecard identifier associated with the target phase.
359362
* @param phaseIds - Set of phase identifiers collected for the target phase.
360363
* @param phaseName - Optional phase name for matching when available.
361-
* @returns True when the review satisfies any of the matching criteria.
364+
* @returns True when the review satisfies the matching criteria with phase names taking precedence.
362365
*/
363366
export function reviewMatchesPhase(
364367
review: BackendReview | undefined,
@@ -428,6 +431,8 @@ export function reviewMatchesPhase(
428431
normalizedReviewPhaseName,
429432
reviewId: review.id,
430433
})
434+
435+
return false
431436
}
432437

433438
const reviewTypeId = getNormalizedLowerCase(review.typeId ?? undefined)

src/apps/review/src/pages/active-review-assignements/ChallengeDetailsPage/ChallengeDetailsPage.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,13 +1045,20 @@ export const ChallengeDetailsPage: FC<Props> = (props: Props) => {
10451045
}),
10461046
[myResources],
10471047
)
1048+
const hasManagerRole = useMemo(
1049+
() => (myResources ?? [])
1050+
.some(resource => (resource.roleName ?? '')
1051+
.trim()
1052+
.toLowerCase() === 'manager'),
1053+
[myResources],
1054+
)
10481055
const isAdmin = useMemo(
10491056
() => loginUserInfo?.roles?.some(
10501057
role => typeof role === 'string' && role.toLowerCase() === 'administrator',
10511058
) ?? false,
10521059
[loginUserInfo?.roles],
10531060
)
1054-
const shouldShowResourcesSection = hasCopilotRole || isAdmin
1061+
const shouldShowResourcesSection = hasCopilotRole || hasManagerRole || isAdmin
10551062
const canManagePhases = useMemo(
10561063
() => !isPastReviewDetail && (hasCopilotRole || isAdmin),
10571064
[hasCopilotRole, isAdmin, isPastReviewDetail],

0 commit comments

Comments
 (0)