@@ -29,8 +29,10 @@ import { ChallengeDetailContext, ReviewAppContext } from '../../contexts'
2929import { getHandleUrl , isReviewPhase } from '../../utils'
3030import { TableWrapper } from '../TableWrapper'
3131import { ProgressBar } from '../ProgressBar'
32- import { useSubmissionDownloadAccess } from '../../hooks'
32+ import { useRole , useSubmissionDownloadAccess } from '../../hooks'
33+ import type { useRoleProps } from '../../hooks'
3334import type { UseSubmissionDownloadAccessResult } from '../../hooks/useSubmissionDownloadAccess'
35+ import { SUBMITTER } from '../../../config/index.config'
3436// no-op
3537
3638import styles from './TableIterativeReview.module.scss'
@@ -113,6 +115,115 @@ const hasActiveReview = (review: ReviewInfo | undefined): boolean => (
113115 Boolean ( review ?. id )
114116)
115117
118+ const DOWNLOAD_OWN_SUBMISSION_TOOLTIP = 'You can only download your own submissions.'
119+ const VIEW_OWN_SCORECARD_TOOLTIP = 'You can only view scorecards for your own submissions.'
120+
121+ interface CompletedReviewRenderParams {
122+ canAccessReview : boolean
123+ outcomeLabel ?: string
124+ reviewId ?: string
125+ reviewPath ?: string
126+ reviewScore ?: number
127+ }
128+
129+ const renderCompletedReviewCell = ( {
130+ canAccessReview,
131+ outcomeLabel,
132+ reviewId,
133+ reviewPath,
134+ reviewScore,
135+ } : CompletedReviewRenderParams ) : JSX . Element | undefined => {
136+ if ( reviewScore !== undefined ) {
137+ const normalisedOutcome = outcomeLabel ?. toLowerCase ( )
138+ const isPassOutcome = normalisedOutcome === 'pass'
139+ const isFailOutcome = normalisedOutcome === 'fail'
140+ const outcomeIndicator = isPassOutcome ? (
141+ < span
142+ className = { classNames ( styles . statusIcon , styles . passIcon ) }
143+ aria-label = 'Pass'
144+ >
145+ < IconOutline . CheckCircleIcon aria-hidden />
146+ </ span >
147+ ) : isFailOutcome ? (
148+ < span
149+ className = { classNames ( styles . statusIcon , styles . failIcon ) }
150+ aria-label = 'Fail'
151+ >
152+ < IconOutline . XCircleIcon aria-hidden />
153+ </ span >
154+ ) : undefined
155+ const scoreDisplay = formatScore ( reviewScore )
156+ const scoreElement = canAccessReview && reviewPath ? (
157+ < Link
158+ to = { reviewPath }
159+ className = { styles . scoreLink }
160+ >
161+ { scoreDisplay }
162+ </ Link >
163+ ) : (
164+ < span className = { styles . scoreLink } >
165+ { scoreDisplay }
166+ </ span >
167+ )
168+ const renderedScoreElement = ! canAccessReview && reviewPath ? (
169+ < Tooltip
170+ content = { VIEW_OWN_SCORECARD_TOOLTIP }
171+ triggerOn = 'click-hover'
172+ >
173+ < span className = { styles . tooltipTrigger } >
174+ { scoreElement }
175+ </ span >
176+ </ Tooltip >
177+ ) : (
178+ scoreElement
179+ )
180+
181+ return (
182+ < div className = { styles . reviewCell } >
183+ < div className = { styles . scoreRow } >
184+ { outcomeIndicator }
185+ { renderedScoreElement }
186+ </ div >
187+ { ! outcomeIndicator && outcomeLabel ? (
188+ < span className = { styles . outcome } >
189+ { outcomeLabel }
190+ </ span >
191+ ) : undefined }
192+ </ div >
193+ )
194+ }
195+
196+ if ( ! reviewId || ! reviewPath ) {
197+ return undefined
198+ }
199+
200+ const viewScorecardContent = canAccessReview ? (
201+ < Link
202+ to = { reviewPath }
203+ className = { styles . scoreLink }
204+ >
205+ View Scorecard
206+ </ Link >
207+ ) : (
208+ < span className = { styles . scoreLink } >
209+ View Scorecard
210+ </ span >
211+ )
212+
213+ return ! canAccessReview ? (
214+ < Tooltip
215+ content = { VIEW_OWN_SCORECARD_TOOLTIP }
216+ triggerOn = 'click-hover'
217+ >
218+ < span className = { styles . tooltipTrigger } >
219+ { viewScorecardContent }
220+ </ span >
221+ </ Tooltip >
222+ ) : (
223+ viewScorecardContent
224+ )
225+ }
226+
116227export const TableIterativeReview : FC < Props > = ( props : Props ) => {
117228 const className = props . className
118229 const datas = props . datas
@@ -132,6 +243,16 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
132243 const { width : screenWidth } : WindowSize = useWindowSize ( )
133244 const isTablet = useMemo ( ( ) => screenWidth <= 744 , [ screenWidth ] )
134245 const { loginUserInfo } : ReviewAppContextModel = useContext ( ReviewAppContext )
246+ const { actionChallengeRole, myChallengeResources } : useRoleProps = useRole ( )
247+ const isSubmitterView = actionChallengeRole === SUBMITTER
248+ const ownedMemberIds : Set < string > = useMemo (
249+ ( ) : Set < string > => new Set (
250+ myChallengeResources
251+ . map ( resource => resource . memberId )
252+ . filter ( ( memberId ) : memberId is string => Boolean ( memberId ) ) ,
253+ ) ,
254+ [ myChallengeResources ] ,
255+ )
135256
136257 const isAdmin = useMemo (
137258 ( ) => loginUserInfo ?. roles ?. some (
@@ -181,13 +302,16 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
181302 label : 'Submission ID' ,
182303 propertyName : 'id' ,
183304 renderer : ( data : SubmissionInfo ) => {
305+ const isOwnedSubmission = data . memberId
306+ ? ownedMemberIds . has ( data . memberId )
307+ : false
308+ const isOwnershipRestricted = isSubmitterView && ! isOwnedSubmission
184309 const isRestrictedForMember = isSubmissionDownloadRestrictedForMember (
185310 data . memberId ,
186311 )
187- const tooltipMessage = getRestrictionMessageForMember (
312+ const memberRestrictionMessage = getRestrictionMessageForMember (
188313 data . memberId ,
189- ) ?? restrictionMessage
190-
314+ )
191315 const failedScan = ( data as SubmissionInfo ) . virusScan === false
192316 const isButtonDisabled = Boolean (
193317 isDownloading [ data . id ]
@@ -198,7 +322,11 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
198322 const downloadButton = (
199323 < button
200324 onClick = { function onClick ( ) {
201- if ( isRestrictedForMember || failedScan ) {
325+ if (
326+ isRestrictedForMember
327+ || failedScan
328+ || isOwnershipRestricted
329+ ) {
202330 return
203331 }
204332
@@ -228,21 +356,36 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
228356 } )
229357 }
230358
231- const shouldShowTooltip = isRestrictedForMember
232- || isSubmissionDownloadRestricted
233- || failedScan
359+ let tooltipContent : string | undefined
360+ if ( failedScan ) {
361+ tooltipContent = 'Submission failed virus scan'
362+ } else if ( isRestrictedForMember ) {
363+ tooltipContent = memberRestrictionMessage ?? restrictionMessage
364+ } else if ( isOwnershipRestricted ) {
365+ tooltipContent = DOWNLOAD_OWN_SUBMISSION_TOOLTIP
366+ } else if ( isSubmissionDownloadRestricted && restrictionMessage ) {
367+ tooltipContent = restrictionMessage
368+ }
369+
370+ const downloadControl = isOwnershipRestricted ? (
371+ < span className = { styles . textBlue } >
372+ { data . id }
373+ </ span >
374+ ) : (
375+ downloadButton
376+ )
234377
235- const renderedDownloadButton = shouldShowTooltip ? (
378+ const renderedDownloadButton = tooltipContent ? (
236379 < Tooltip
237- content = { failedScan ? 'Submission failed virus scan' : tooltipMessage }
380+ content = { tooltipContent }
238381 triggerOn = 'click-hover'
239382 >
240383 < span className = { styles . tooltipTrigger } >
241- { downloadButton }
384+ { downloadControl }
242385 </ span >
243386 </ Tooltip >
244387 ) : (
245- downloadButton
388+ downloadControl
246389 )
247390
248391 return (
@@ -270,6 +413,8 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
270413 downloadSubmission ,
271414 isDownloading ,
272415 restrictionMessage ,
416+ isSubmitterView ,
417+ ownedMemberIds ,
273418 ] ,
274419 )
275420
@@ -310,6 +455,10 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
310455 label : columnLabel ,
311456 renderer : ( data : SubmissionInfo ) => {
312457 const review = data . review
458+ const isOwnedSubmission = data . memberId
459+ ? ownedMemberIds . has ( data . memberId )
460+ : false
461+ const canAccessReview = ! isSubmitterView || isOwnedSubmission
313462
314463 if ( ! hasActiveReview ( review ) ) {
315464 return (
@@ -325,64 +474,17 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
325474 ( review ?. metadata as ScoreMetadata | undefined ) ?. outcome ,
326475 )
327476 const reviewId = review ?. id
477+ const reviewPath = reviewId ? `./../review/${ reviewId } ` : undefined
478+ const isCompleted = [ 'COMPLETED' , 'SUBMITTED' ] . includes ( status )
328479
329- if ( [ 'COMPLETED' , 'SUBMITTED' ] . includes ( status ) && reviewScore !== undefined ) {
330- if ( ! reviewId ) {
331- return undefined
332- }
333-
334- const normalisedOutcome = outcomeLabel ?. toLowerCase ( )
335- const isPassOutcome = normalisedOutcome === 'pass'
336- const isFailOutcome = normalisedOutcome === 'fail'
337- const outcomeIndicator = isPassOutcome ? (
338- < span
339- className = { classNames ( styles . statusIcon , styles . passIcon ) }
340- aria-label = 'Pass'
341- >
342- < IconOutline . CheckCircleIcon aria-hidden />
343- </ span >
344- ) : isFailOutcome ? (
345- < span
346- className = { classNames ( styles . statusIcon , styles . failIcon ) }
347- aria-label = 'Fail'
348- >
349- < IconOutline . XCircleIcon aria-hidden />
350- </ span >
351- ) : undefined
352-
353- return (
354- < div className = { styles . reviewCell } >
355- < div className = { styles . scoreRow } >
356- { outcomeIndicator }
357- < Link
358- to = { `./../review/${ reviewId } ` }
359- className = { styles . scoreLink }
360- >
361- { formatScore ( reviewScore ) }
362- </ Link >
363- </ div >
364- { ! outcomeIndicator && outcomeLabel ? (
365- < span className = { styles . outcome } >
366- { outcomeLabel }
367- </ span >
368- ) : undefined }
369- </ div >
370- )
371- }
372-
373- if ( [ 'COMPLETED' , 'SUBMITTED' ] . includes ( status ) ) {
374- if ( ! reviewId ) {
375- return undefined
376- }
377-
378- return (
379- < Link
380- to = { `./../review/${ reviewId } ` }
381- className = { styles . scoreLink }
382- >
383- View Scorecard
384- </ Link >
385- )
480+ if ( isCompleted ) {
481+ return renderCompletedReviewCell ( {
482+ canAccessReview,
483+ outcomeLabel,
484+ reviewId,
485+ reviewPath,
486+ reviewScore,
487+ } )
386488 }
387489
388490 if ( review ?. reviewProgress ) {
@@ -412,7 +514,7 @@ export const TableIterativeReview: FC<Props> = (props: Props) => {
412514 } ,
413515 type : 'element' ,
414516 } ) ,
415- [ columnLabel ] ,
517+ [ columnLabel , isSubmitterView , ownedMemberIds ] ,
416518 )
417519
418520 const reviewDateColumn : TableColumn < SubmissionInfo > = useMemo (
0 commit comments