Skip to content

Conversation

@kkartunov
Copy link
Collaborator

@kkartunov kkartunov commented Dec 11, 2025

Related JIRA Ticket:

What's in this PR?

vas3a and others added 27 commits December 4, 2025 13:24
fix(PM-3141): allow only admins and reviewer to respond to appeals
fix(PM-2662): hide other submitters submissions if challenge is configured to hide it
…orecard-ui

Cleanup old code for the review scorecard ui
fix(PM-2662): Show only users submission in review tab if challenge is configured to hide submissions
@kkartunov kkartunov requested a review from jmgasper as a code owner December 11, 2025 07:59
)
}, [challengeInfo])

const isSubmissionsViewable = useMemo(() => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The isSubmissionsViewable function uses challengeInfo.metadata.some(...) which could potentially throw an error if challengeInfo.metadata is not an array. Consider adding a check to ensure challengeInfo.metadata is an array before calling .some().

const filteredSubmissions = useMemo<BackendSubmission[]>(
() => {

const filterFunc = (submissions: BackendSubmission[]): BackendSubmission[] => submissions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
The filterFunc is defined inside the useMemo hook but is used twice within the same scope. Consider defining filterFunc outside of useMemo to avoid redefining it unnecessarily, which could improve readability and maintainability.

addAppeal,
isSavingAppeal,
}: ScorecardViewerContextValue = useScorecardViewerContext()
const { isAdmin, hasReviewerRole }: UseRolePermissionsResult = useRolePermissions()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ security]
The useRolePermissions hook is used to determine role-based permissions. Ensure that this hook is correctly implemented and returns accurate role information, as incorrect role data could lead to unauthorized access or actions.

reviewItem={props.reviewItem}
scorecardQuestion={props.question}
canRespondToAppeal={isReviewerRole}
canRespondToAppeal={isAdmin || hasReviewerRole}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The condition isAdmin || hasReviewerRole is used to determine if a user can respond to an appeal. Ensure that this logic aligns with the intended business rules, as incorrect permission checks could lead to unauthorized actions.

type='text'
className={classNames(props.className, styles.container)}
>
{/* @ts-expect-error: TS2786: DatePicker cannot be used as a JSX component */}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Using @ts-expect-error to suppress TypeScript errors can hide potential issues. Investigate why DatePicker cannot be used as a JSX component and address the root cause instead of suppressing the error.

readonly forceFocusStyle?: boolean
}

// @ts-expect-error: TS2322: Type mismatch for ForwardRefExoticComponent

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Using @ts-expect-error to suppress TypeScript errors can hide potential issues. Consider addressing the underlying type mismatch instead of suppressing it.

}

const PageTitle: FC<PageTitleProps> = (props: PageTitleProps) => (
// @ts-expect-error: TS2786: Helmet cannot be used as a JSX component

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Using @ts-expect-error to suppress TypeScript errors should be avoided if possible, as it can hide potential issues. Consider investigating the cause of the error TS2786 and resolving it, or documenting why this suppression is necessary and safe.

portalRef?: MutableRefObject<HTMLElement>,
}

// @ts-expect-error: TS2322: Type mismatch for FC return type

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Using // @ts-expect-error to suppress TypeScript errors can hide potential issues. Investigate the root cause of the type mismatch and address it instead of suppressing the error. This will improve type safety and maintainability.

error: fetchAppealsError,
}: SWRResponse<AppealInfo[], Error> = useSWR<AppealInfo[], Error>(
`EnvironmentConfig.API.V6/appeals/resourceId/${resourceId}`,
`${EnvironmentConfig.API.V6}/appeals/resourceId/${resourceId}`,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ security]
The URL string is constructed using template literals, which is correct. However, ensure that resourceId is always a valid and sanitized string to prevent potential injection attacks or malformed URLs.

try {
if (reviewId) {
// re-fetch review data
mutate(`EnvironmentConfig.API.V6/reviews/${reviewId}`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ security]
The mutate function is used to revalidate the SWR cache for the review data. Ensure that reviewId is always a valid and sanitized string to prevent potential issues with cache keys.

: `/${match[1]}/${match[2]}`

return `${basePath}/reviews/${submissionId}?reviewId=${encodedReviewId}`
return `${basePath}/reviews/${submissionId}?reviewId=${encodedReviewId}&respondToAppeals=true`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
Adding the respondToAppeals=true query parameter changes the behavior of the route. Ensure that this change is intentional and that all parts of the application that rely on this route are updated accordingly to handle the new parameter.

const ReviewViewer: FC = () => {
const navigate = useAppNavigate()
const [searchParams] = useSearchParams()
const [respondToAppeals, setRespondToAppeals] = useState(searchParams.get('respondToAppeals') === 'true')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
Using useState with searchParams.get('respondToAppeals') directly ties the initial state to the URL parameter. Consider using useMemo to derive the initial state from searchParams to avoid unnecessary re-renders if searchParams changes but respondToAppeals does not.

setRespondToAppeals(false)
}
}, [canEditScorecard, isManagerEdit])
}, [canEditScorecard, isManagerEdit, respondToAppeals])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The dependency array for this useEffect includes respondToAppeals, which is being set to false inside the effect. This could lead to an infinite loop if not handled carefully. Ensure that the state update logic is correct and doesn't cause unnecessary re-renders.


useEffect(() => {
if (!canEditScorecard && isManagerEdit) {
if (!canEditScorecard && isManagerEdit && !respondToAppeals) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
The condition !respondToAppeals was added to the if statement. Ensure that this change aligns with the intended logic, as it alters when setIsManagerEdit(false) is called. Verify that this does not introduce any unintended behavior, especially if respondToAppeals is expected to influence the manager edit state.

fix(PM-2662): Show only current users winning submission in winners tab if challenge is private
.toLowerCase() === 'true')
}, [challengeInfo])

const canViewSubmissions = useMemo(() => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 readability]
The canViewSubmissions logic might be clearer if the isCompletedDesignChallenge and isSubmissionsViewable checks were combined into a single condition. This could improve readability by reducing the number of nested conditions.

return true
}, [isCompletedDesignChallenge, isSubmissionsViewable, canViewAllSubmissions])

const filterFunc = useCallback((submissions: ProjectResult[]): ProjectResult[] => submissions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
The filterFunc function uses useCallback, but its dependencies include canViewSubmissions and loginUserInfo?.userId, which are derived from context and could change. Ensure these dependencies are stable or consider using useMemo if the function doesn't need to be recreated.

})) as SubmissionRow[]
const rows = aggregatedSubmissionRows
.filter(aggregated => {
const reviews = aggregated.reviews ?? []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
The filter function is used to find submissions with a specific review detail. However, it might be more efficient to use some instead of find within the filter callback to avoid unnecessary iterations once a match is found.

return !!myReviewDetail?.reviewId
})
.map(aggregated => ({
...(aggregated.submission ?? {}),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
The spread operator is used twice on aggregated.submission. This is redundant and could be simplified to a single spread operation.

Copy link
Collaborator

@jmgasper jmgasper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few comments for clarification - nothing major.

.toLowerCase() : ''
const status = challengeInfo.status ? String(challengeInfo.status)
.toLowerCase() : ''
return type === 'design' && (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check the challenge type here as well? This will match F2Fs in addition to challenges.

reviewItem={props.reviewItem}
scorecardQuestion={props.question}
canRespondToAppeal={isReviewerRole}
canRespondToAppeal={isAdmin || hasReviewerRole}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this change for? Are we now allowing admins to respond to appeals? I'm not sure that's something we've done in the past?

}
}, [challengeInfo?.type?.abbreviation, challengeInfo?.type?.name])

const isCompletedDesignChallenge = useMemo(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this out so that we aren't duplicating it multiple places? We added the same exact functionality in src/apps/review/src/lib/components/ChallengeDetailsContent/TabContentSubmissions.tsx

@kkartunov kkartunov merged commit 9a94084 into master Dec 12, 2025
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants