Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TabContentReview } from './TabContentReview'
const mockUseRole = jest.fn()
const mockTableAppealsForSubmitter = jest.fn()
const mockTableAppealsResponse = jest.fn()
const mockTableReview = jest.fn()
const mockTableReviewForSubmitter = jest.fn()

jest.mock('~/config', () => ({
Expand Down Expand Up @@ -78,7 +79,15 @@ jest.mock('../TableNoRecord', () => ({
}))

jest.mock('../TableReview', () => ({
TableReview: () => <div>Reviewer reviews</div>,
TableReview: (props: { datas: SubmissionInfo[] }) => {
mockTableReview(props)
return (
<div>
{props.datas.map(submission => submission.id)
.join(',')}
</div>
)
},
}))

jest.mock('../TableReviewForSubmitter', () => ({
Expand Down Expand Up @@ -208,4 +217,63 @@ describe('TabContentReview submitter Appeals ownership', () => {
],
}))
})

it('passes both finite-limit Design reviews for one member to the reviewer table', () => {
const olderSubmission = {
id: 'member-submission-older',
isLatest: false,
memberId: 'member-shared',
review: {
phaseName: 'Review',
reviewType: 'Review',
},
submittedDate: '2026-08-12T10:00:00Z',
type: 'CONTEST_SUBMISSION',
} as SubmissionInfo
const latestSubmission = {
...olderSubmission,
id: 'member-submission-latest',
isLatest: true,
submittedDate: '2026-08-12T11:00:00Z',
}
const reviewerChallengeInfo = {
...challengeInfo,
metadata: [{
name: 'submissionLimit',
value: JSON.stringify({ count: '2', limit: 'true', unlimited: 'false' }),
}],
submissions: [olderSubmission, latestSubmission],
track: {
id: 'design-track',
name: 'Design',
},
} as ChallengeInfo
const reviewerContext = {
...challengeContext,
challengeInfo: reviewerChallengeInfo,
myResources: [],
myRoles: ['Reviewer'],
} as unknown as ChallengeDetailContextModel
mockUseRole.mockReturnValue({
actionChallengeRole: 'Reviewer',
hasApproverRole: false,
isPrivilegedRole: true,
})

render(
<ChallengeDetailContext.Provider value={reviewerContext}>
<TabContentReview
{...commonProps}
isActiveChallenge
reviews={[olderSubmission, latestSubmission]}
selectedTab='Review'
/>
</ChallengeDetailContext.Provider>,
)

expect(mockTableReview)
.toHaveBeenLastCalledWith(expect.objectContaining({
datas: [olderSubmission, latestSubmission],
}))
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -770,23 +770,23 @@ export const TabContentReview: FC<Props> = (props: Props) => {
)
const reviewerRowsForReviewTab = useMemo(
() => (shouldSortReviewTabByScore
? sortSubmissionsByReviewScoreDesc(filteredReviews, useAggregateReviewScore)
: filteredReviews),
[filteredReviews, shouldSortReviewTabByScore, useAggregateReviewScore],
? sortSubmissionsByReviewScoreDesc(resolvedReviewsWithSubmitter, useAggregateReviewScore)
: resolvedReviewsWithSubmitter),
[resolvedReviewsWithSubmitter, shouldSortReviewTabByScore, useAggregateReviewScore],
)
const submitterRowsForReviewTab = useMemo(
() => (shouldSortReviewTabByScore
? sortSubmissionsByReviewScoreDesc(filteredSubmitterReviews, useAggregateReviewScore)
: filteredSubmitterReviews),
[filteredSubmitterReviews, shouldSortReviewTabByScore, useAggregateReviewScore],
? sortSubmissionsByReviewScoreDesc(resolvedSubmitterReviews, useAggregateReviewScore)
: resolvedSubmitterReviews),
[resolvedSubmitterReviews, shouldSortReviewTabByScore, useAggregateReviewScore],
)
const hideHandleColumn = props.isActiveChallenge
&& actionChallengeRole === REVIEWER

// show loading ui when fetching data
const reviewRows = isSubmitterView
? (shouldSortReviewTabByScore ? submitterRowsForReviewTab : filteredSubmitterReviews)
: (shouldSortReviewTabByScore ? reviewerRowsForReviewTab : filteredReviews)
? submitterRowsForReviewTab
: reviewerRowsForReviewTab

if (props.isLoadingReview) {
return <TableLoading />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,15 +204,16 @@ export const TabContentSubmissions: FC<Props> = props => {
return
}

const key = getSubmissionHistoryKey(memberId, submissionId)
const submissionType = submissionInfoById.get(submissionId)?.type
const key = getSubmissionHistoryKey(memberId, submissionId, submissionType)
const entries = historyByMember.get(key) ?? []
if (!entries.length) {
return
}

setHistoryKey(key)
},
[historyByMember],
[historyByMember, submissionInfoById],
)

const handleHistoryButtonClick = useCallback(
Expand Down Expand Up @@ -524,7 +525,11 @@ export const TabContentSubmissions: FC<Props> = props => {
return <span>-</span>
}

const key = getSubmissionHistoryKey(submission.memberId, submission.id)
const key = getSubmissionHistoryKey(
submission.memberId,
submission.id,
submission.type,
)
const historyEntries = historyByMember.get(key) ?? []
if (!historyEntries.length) {
return <span>-</span>
Expand Down
18 changes: 14 additions & 4 deletions src/apps/review/src/lib/components/TableReview/TableReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
} from '../../models'
import {
aggregateSubmissionReviews,
challengeHasSubmissionLimit,
getChallengeSubmissionSelectionLimit,
isMarathonMatchChallenge,
isReviewPhase,
isReviewPhaseCurrentlyOpen,
Expand Down Expand Up @@ -180,6 +180,11 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
[challengeInfo, submissionTypes],
)

const submissionSelectionLimit = useMemo<number | undefined>(
() => getChallengeSubmissionSelectionLimit(challengeInfo),
[challengeInfo],
)

const {
closeHistoryModal,
historyByMember,
Expand All @@ -193,11 +198,12 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
datas: reviewPhaseDatas,
filteredAll: filteredChallengeSubmissions,
isSubmissionTab: true,
maxVisibleSubmissions: submissionSelectionLimit,
})

const restrictToLatest = useMemo<boolean>(
() => challengeHasSubmissionLimit(challengeInfo),
[challengeInfo],
() => submissionSelectionLimit !== undefined,
[submissionSelectionLimit],
)
const useAggregateReviewScore = useMemo<boolean>(
() => isMarathonMatchChallenge(challengeInfo),
Expand Down Expand Up @@ -650,7 +656,11 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
)
}

const historyKeyForRow = getSubmissionHistoryKey(submission.memberId, submission.id)
const historyKeyForRow = getSubmissionHistoryKey(
submission.memberId,
submission.id,
submission.type,
)
const rowHistory = historyByMember.get(historyKeyForRow) ?? []

const buildHistoryAction = (): JSX.Element | undefined => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import type {
} from '../common/types'
import {
aggregateSubmissionReviews,
challengeHasSubmissionLimit,
getChallengeSubmissionSelectionLimit,
getSubmissionHistoryKey,
isAppealsPhase,
isAppealsResponsePhase,
Expand Down Expand Up @@ -155,6 +155,11 @@ export const TableReviewForSubmitter: FC<TableReviewForSubmitterProps> = (props:
[challengeInfo?.submissions, datas, submissionTypes],
)

const submissionSelectionLimit = useMemo<number | undefined>(
() => getChallengeSubmissionSelectionLimit(challengeInfo),
[challengeInfo],
)

const {
closeHistoryModal,
historyByMember,
Expand All @@ -168,11 +173,12 @@ export const TableReviewForSubmitter: FC<TableReviewForSubmitterProps> = (props:
datas,
filteredAll,
isSubmissionTab: true,
maxVisibleSubmissions: submissionSelectionLimit,
})

const restrictToLatest = useMemo<boolean>(
() => challengeHasSubmissionLimit(challengeInfo),
[challengeInfo],
() => submissionSelectionLimit !== undefined,
[submissionSelectionLimit],
)
const useAggregateReviewScore = useMemo<boolean>(
() => isMarathonMatchChallenge(challengeInfo),
Expand Down Expand Up @@ -524,6 +530,7 @@ export const TableReviewForSubmitter: FC<TableReviewForSubmitterProps> = (props:
const historyKeyForSubmission = getSubmissionHistoryKey(
submission.memberId,
submission.id,
submission.type,
)
const historyEntries = historyByMember.get(historyKeyForSubmission) ?? []
const filteredHistory = restrictToLatest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
import { TableWrapper } from '../TableWrapper'
import { SubmissionHistoryModal } from '../SubmissionHistoryModal'
import {
challengeHasSubmissionLimit,
getChallengeSubmissionSelectionLimit,
getHandleUrl,
getSubmissionHistoryKey,
isReviewPhaseCurrentlyOpen,
Expand Down Expand Up @@ -400,7 +400,11 @@ const createHistoryAction = ({
return undefined
}

const historyKeyForRow = getSubmissionHistoryKey(data.memberId, data.submissionId)
const historyKeyForRow = getSubmissionHistoryKey(
data.memberId,
data.submissionId,
data.type,
)
const historyEntries = historyByMember.get(historyKeyForRow) ?? []
if (!historyEntries.length) {
return undefined
Expand Down Expand Up @@ -945,9 +949,16 @@ export const TableSubmissionScreening: FC<Props> = (props: Props) => {
[submissionMetaById],
)

const submissionSelectionLimit = useMemo<number | undefined>(
() => getChallengeSubmissionSelectionLimit(challengeInfo),
[challengeInfo],
)

const submissionHistory = useMemo(
() => partitionSubmissionHistory(primarySubmissionInfos, historySourceSubmissions),
[historySourceSubmissions, primarySubmissionInfos],
() => partitionSubmissionHistory(primarySubmissionInfos, historySourceSubmissions, {
visibleSubmissionCount: submissionSelectionLimit,
}),
[historySourceSubmissions, primarySubmissionInfos, submissionSelectionLimit],
)

const { historyByMember, latestSubmissionIds }: SubmissionHistoryPartition = submissionHistory
Expand All @@ -957,16 +968,14 @@ export const TableSubmissionScreening: FC<Props> = (props: Props) => {
rows: visibleScreenings,
}: ScreeningRowsSelection = useMemo(
() => selectVisibleScreeningRows({
hasSubmissionLimit: challengeHasSubmissionLimit(challengeInfo),
latestSubmissionIds,
screeningRows: props.screenings,
submissionInfos: primarySubmissionInfos,
submissionLimit: submissionSelectionLimit,
}),
[
challengeInfo,
latestSubmissionIds,
primarySubmissionInfos,
props.screenings,
submissionSelectionLimit,
],
)

Expand Down Expand Up @@ -1051,15 +1060,16 @@ export const TableSubmissionScreening: FC<Props> = (props: Props) => {

const openHistoryModal = useCallback(
(memberId: string | undefined, submissionId: string): void => {
const key = getSubmissionHistoryKey(memberId, submissionId)
const submissionType = submissionMetaById.get(submissionId)?.type
const key = getSubmissionHistoryKey(memberId, submissionId, submissionType)
const historyEntries = historyByMember.get(key)
if (!historyEntries || historyEntries.length === 0) {
return
}

setHistoryKey(key)
},
[historyByMember],
[historyByMember, submissionMetaById],
)

const openReopenDialog = useCallback(
Expand Down
24 changes: 19 additions & 5 deletions src/apps/review/src/lib/hooks/useSubmissionHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import {
import type { SubmissionHistoryPartition } from '../utils/submissionHistory'

interface UseSubmissionHistoryParams {
/** Primary table submissions, including review or screening details. */
datas: SubmissionInfo[]
/** Complete matching challenge history used to rank submissions. */
filteredAll: SubmissionInfo[]
/** Whether the consuming table supports submission-history actions. */
isSubmissionTab: boolean
/** Positive latest-submission count per member/type group. Defaults to one. */
maxVisibleSubmissions?: number
}

export interface UseSubmissionHistoryResult {
Expand All @@ -26,16 +31,23 @@ export interface UseSubmissionHistoryResult {
}

/**
* Encapsulates submission history modal state and derived metadata for tables.
* Encapsulate submission-history ranking and modal state for Review tables.
*
* @param params - Primary rows, complete matching history, table mode, and visible count.
* @returns Latest selected rows and IDs, older member/type history, and modal callbacks.
* @throws Does not throw; invalid visible counts are normalized by the partition utility.
*/
export function useSubmissionHistory({
datas,
filteredAll,
isSubmissionTab,
maxVisibleSubmissions,
}: UseSubmissionHistoryParams): UseSubmissionHistoryResult {
const submissionHistory = useMemo<SubmissionHistoryPartition>(
() => partitionSubmissionHistory(datas, filteredAll),
[datas, filteredAll],
() => partitionSubmissionHistory(datas, filteredAll, {
visibleSubmissionCount: maxVisibleSubmissions,
}),
[datas, filteredAll, maxVisibleSubmissions],
)

const {
Expand All @@ -58,15 +70,17 @@ export function useSubmissionHistory({

const openHistoryModal: (memberId: string | undefined, submissionId: string) => void = useCallback(
(memberId: string | undefined, submissionId: string): void => {
const key = getSubmissionHistoryKey(memberId, submissionId)
const submissionType = datas.find(submission => submission.id === submissionId)?.type
?? filteredAll.find(submission => submission.id === submissionId)?.type
const key = getSubmissionHistoryKey(memberId, submissionId, submissionType)
const entries = historyByMember.get(key)
if (!entries || entries.length === 0) {
return
}

setHistoryKey(key)
},
[historyByMember],
[datas, filteredAll, historyByMember],
)

const closeHistoryModal = useCallback((): void => {
Expand Down
Loading
Loading