Skip to content

Commit 50b3167

Browse files
committed
Merge branch 'dev' of github.com:topcoder-platform/platform-ui into feat/ai-workflows
2 parents 9a11080 + 44f5417 commit 50b3167

File tree

8 files changed

+469
-49
lines changed

8 files changed

+469
-49
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
"sass": "^1.79.0",
110110
"styled-components": "^5.3.6",
111111
"swr": "^1.3.0",
112-
"tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.27",
112+
"tc-auth-lib": "topcoder-platform/tc-auth-lib#master",
113113
"tinymce": "^7.9.1",
114114
"typescript": "^4.8.4",
115115
"universal-navigation": "https://github.com/topcoder-platform/universal-navigation#9fc50d938be7182",

src/apps/admin/src/permission-management/PermissionGroupsPage/PermissionGroupsPage.module.scss

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,29 @@
1919
text-align: center;
2020
}
2121

22-
.btnNewGroup {
23-
margin: $sp-8 $sp-8 $sp-4 $sp-8;
22+
.actions {
23+
display: flex;
24+
align-items: flex-end;
25+
gap: $sp-4;
26+
margin: $sp-8 $sp-8 $sp-4;
2427

2528
@include ltelg {
29+
flex-direction: column;
30+
align-items: stretch;
2631
margin: $sp-4;
32+
gap: $sp-3;
33+
}
34+
}
35+
36+
.searchField {
37+
flex: 1;
38+
}
39+
40+
.btnNewGroup {
41+
margin: 0;
42+
min-width: max-content;
43+
44+
@include ltelg {
45+
width: 100%;
2746
}
2847
}

src/apps/admin/src/permission-management/PermissionGroupsPage/PermissionGroupsPage.tsx

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/**
22
* Permission groups page.
33
*/
4-
import { FC, useContext, useState } from 'react'
4+
import { ChangeEvent, FC, useContext, useMemo, useState } from 'react'
55
import classNames from 'classnames'
66

7-
import { Button, LoadingSpinner, PageTitle } from '~/libs/ui'
7+
import { Button, InputText, LoadingSpinner, PageTitle } from '~/libs/ui'
88
import { PlusIcon } from '@heroicons/react/solid'
99

1010
import { DialogAddGroup } from '../../lib/components/DialogAddGroup'
@@ -24,6 +24,7 @@ const pageTitle = 'Groups'
2424

2525
export const PermissionGroupsPage: FC<Props> = (props: Props) => {
2626
const [openDialogAddGroup, setOpenDialogAddGroup] = useState(false)
27+
const [searchTerm, setSearchTerm] = useState('')
2728
const { loadUser, cancelLoadUser, usersMapping }: AdminAppContextType
2829
= useContext(AdminAppContext)
2930
const {
@@ -37,6 +38,26 @@ export const PermissionGroupsPage: FC<Props> = (props: Props) => {
3738
usersMapping,
3839
)
3940

41+
const filteredGroups = useMemo(() => {
42+
const normalized = searchTerm
43+
.trim()
44+
.toLowerCase()
45+
if (!normalized) {
46+
return groups
47+
}
48+
49+
return groups.filter(group => {
50+
const id = group.id ? group.id.toLowerCase() : ''
51+
const name = group.name ? group.name.toLowerCase() : ''
52+
53+
return id.includes(normalized) || name.includes(normalized)
54+
})
55+
}, [groups, searchTerm])
56+
const hasSearchTerm = useMemo(
57+
() => searchTerm.trim().length > 0,
58+
[searchTerm],
59+
)
60+
4061
return (
4162
<div className={classNames(styles.container, props.className)}>
4263
<PageTitle>{pageTitle}</PageTitle>
@@ -51,24 +72,40 @@ export const PermissionGroupsPage: FC<Props> = (props: Props) => {
5172
</div>
5273
) : (
5374
<>
54-
<Button
55-
primary
56-
size='lg'
57-
icon={PlusIcon}
58-
iconToLeft
59-
label='new group'
60-
onClick={function onClick() {
61-
setOpenDialogAddGroup(true)
62-
}}
63-
className={styles.btnNewGroup}
64-
/>
65-
{groups.length === 0 ? (
75+
<div className={styles.actions}>
76+
<InputText
77+
name='groupSearch'
78+
type='text'
79+
label='Search groups'
80+
placeholder='Search by name or ID'
81+
value={searchTerm}
82+
onChange={function onChange(event: ChangeEvent<HTMLInputElement>) {
83+
setSearchTerm(event.target.value)
84+
}}
85+
forceUpdateValue
86+
classNameWrapper={styles.searchField}
87+
/>
88+
<Button
89+
primary
90+
size='lg'
91+
icon={PlusIcon}
92+
iconToLeft
93+
label='new group'
94+
onClick={function onClick() {
95+
setOpenDialogAddGroup(true)
96+
}}
97+
className={styles.btnNewGroup}
98+
/>
99+
</div>
100+
{filteredGroups.length === 0 ? (
66101
<p className={styles.noRecordFound}>
67-
{MSG_NO_RECORD_FOUND}
102+
{hasSearchTerm
103+
? 'No groups match your search.'
104+
: MSG_NO_RECORD_FOUND}
68105
</p>
69106
) : (
70107
<GroupsTable
71-
datas={groups}
108+
datas={filteredGroups}
72109
usersMapping={usersMapping}
73110
/>
74111
)}

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ActionLoading } from '~/apps/admin/src/lib'
99
import { ChallengeDetailContext } from '../../contexts'
1010
import {
1111
BackendSubmission,
12-
ChallengeInfo,
12+
ChallengeDetailContextModel,
1313
MappingReviewAppeal,
1414
Screening,
1515
SubmissionInfo,
@@ -120,6 +120,7 @@ const buildScreeningRows = ({
120120

121121
interface SubmissionTabParams {
122122
selectedTabNormalized: string
123+
allowTopgearSubmissionList: boolean
123124
submissions: BackendSubmission[]
124125
screeningRows: Screening[]
125126
screeningMinimumPassingScore: number | null | undefined
@@ -132,6 +133,7 @@ interface SubmissionTabParams {
132133

133134
const renderSubmissionTab = ({
134135
selectedTabNormalized,
136+
allowTopgearSubmissionList,
135137
submissions,
136138
screeningRows,
137139
screeningMinimumPassingScore,
@@ -151,7 +153,7 @@ const renderSubmissionTab = ({
151153
submission => normalizeType(submission.type) === 'contestsubmission',
152154
)
153155
: submissions
154-
const canShowSubmissionList = !isTopgearSubmissionTab
156+
const canShowSubmissionList = (allowTopgearSubmissionList || !isTopgearSubmissionTab)
155157
&& selectedTabNormalized !== 'screening'
156158
&& visibleSubmissions.length > 0
157159

@@ -181,8 +183,23 @@ const renderSubmissionTab = ({
181183
}
182184

183185
export const ChallengeDetailsContent: FC<Props> = (props: Props) => {
184-
const { challengeInfo }: { challengeInfo?: ChallengeInfo } = useContext(ChallengeDetailContext)
186+
const {
187+
challengeInfo,
188+
myResources,
189+
}: ChallengeDetailContextModel = useContext(ChallengeDetailContext)
185190
const { actionChallengeRole }: useRoleProps = useRole()
191+
const hasIterativeReviewerRole = useMemo(
192+
() => myResources.some(
193+
resource => resource.roleName
194+
?.toLowerCase()
195+
.includes('iterative reviewer'),
196+
),
197+
[myResources],
198+
)
199+
const allowTopgearSubmissionList = useMemo(
200+
() => actionChallengeRole !== SUBMITTER || hasIterativeReviewerRole,
201+
[actionChallengeRole, hasIterativeReviewerRole],
202+
)
186203
const { currentMemberId }: UseSubmissionDownloadAccessResult = useSubmissionDownloadAccess()
187204
const {
188205
isLoading: isDownloadingSubmission,
@@ -370,6 +387,7 @@ export const ChallengeDetailsContent: FC<Props> = (props: Props) => {
370387
aiReviewers: (
371388
challengeInfo?.reviewers?.filter(r => !!r.aiWorkflowId) as { aiWorkflowId: string }[]
372389
) ?? [],
390+
allowTopgearSubmissionList,
373391
downloadSubmission: handleSubmissionDownload,
374392
isActiveChallenge: props.isActiveChallenge,
375393
isDownloadingSubmission,

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

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,115 @@ interface Props {
5555
isActiveChallenge: boolean
5656
}
5757

58+
const normalizeTabLabel = (value?: string): string => (
59+
value
60+
? value
61+
.toLowerCase()
62+
.replace(/[^a-z]/g, '')
63+
: ''
64+
)
65+
66+
const parseScoreValue = (value: unknown): number | undefined => {
67+
if (typeof value === 'number') {
68+
return Number.isFinite(value) ? value : undefined
69+
}
70+
71+
if (typeof value === 'string') {
72+
const trimmed = value.trim()
73+
if (!trimmed.length) {
74+
return undefined
75+
}
76+
77+
const parsed = Number.parseFloat(trimmed)
78+
return Number.isFinite(parsed) ? parsed : undefined
79+
}
80+
81+
return undefined
82+
}
83+
84+
const resolveSubmissionReviewScore = (submission: SubmissionInfo): number | undefined => {
85+
const reviewResultScores = Array.isArray(submission.reviews)
86+
? submission.reviews
87+
.map(review => parseScoreValue(review?.score))
88+
.filter((score): score is number => typeof score === 'number')
89+
: []
90+
91+
if (reviewResultScores.length) {
92+
const total = reviewResultScores.reduce((sum, score) => sum + score, 0)
93+
return total / reviewResultScores.length
94+
}
95+
96+
const aggregateScore = parseScoreValue(submission.aggregateScore)
97+
if (aggregateScore !== undefined) {
98+
return aggregateScore
99+
}
100+
101+
const finalScore = parseScoreValue(submission.review?.finalScore)
102+
if (finalScore !== undefined) {
103+
return finalScore
104+
}
105+
106+
const initialScore = parseScoreValue(submission.review?.initialScore)
107+
if (initialScore !== undefined) {
108+
return initialScore
109+
}
110+
111+
return undefined
112+
}
113+
114+
type SubmissionScoreEntry = {
115+
index: number
116+
score?: number
117+
submission: SubmissionInfo
118+
}
119+
120+
const sortSubmissionsByReviewScoreDesc = (rows: SubmissionInfo[]): SubmissionInfo[] => {
121+
const entries: SubmissionScoreEntry[] = rows.map((submission, index) => ({
122+
index,
123+
score: resolveSubmissionReviewScore(submission),
124+
submission,
125+
}))
126+
127+
entries.sort((a: SubmissionScoreEntry, b: SubmissionScoreEntry) => {
128+
const scoreA: number | undefined = a.score
129+
const scoreB: number | undefined = b.score
130+
const indexA: number = a.index
131+
const indexB: number = b.index
132+
133+
if (scoreA === undefined && scoreB === undefined) {
134+
return indexA - indexB
135+
}
136+
137+
if (scoreA === undefined) {
138+
return 1
139+
}
140+
141+
if (scoreB === undefined) {
142+
return -1
143+
}
144+
145+
if (scoreB !== scoreA) {
146+
return scoreB - scoreA
147+
}
148+
149+
return indexA - indexB
150+
})
151+
152+
return entries.map(entry => entry.submission)
153+
}
154+
58155
export const TabContentReview: FC<Props> = (props: Props) => {
59156
const selectedTab = props.selectedTab
60157
const providedReviews = props.reviews
61158
const providedSubmitterReviews = props.submitterReviews
159+
const normalizedSelectedTab = useMemo(
160+
() => normalizeTabLabel(selectedTab),
161+
[selectedTab],
162+
)
163+
const shouldSortReviewTabByScore = useMemo(
164+
() => !props.isActiveChallenge && normalizedSelectedTab === 'review',
165+
[normalizedSelectedTab, props.isActiveChallenge],
166+
)
62167
const {
63168
challengeInfo,
64169
challengeSubmissions: backendChallengeSubmissions,
@@ -546,13 +651,27 @@ export const TabContentReview: FC<Props> = (props: Props) => {
546651
},
547652
[resolvedSubmitterReviews],
548653
)
654+
const reviewerRowsForReviewTab = useMemo(
655+
() => (shouldSortReviewTabByScore
656+
? sortSubmissionsByReviewScoreDesc(filteredReviews)
657+
: filteredReviews),
658+
[filteredReviews, shouldSortReviewTabByScore],
659+
)
660+
const submitterRowsForReviewTab = useMemo(
661+
() => (shouldSortReviewTabByScore
662+
? sortSubmissionsByReviewScoreDesc(filteredSubmitterReviews)
663+
: filteredSubmitterReviews),
664+
[filteredSubmitterReviews, shouldSortReviewTabByScore],
665+
)
549666
const hideHandleColumn = props.isActiveChallenge
550667
&& actionChallengeRole === REVIEWER
551668

552669
// show loading ui when fetching data
553670
const isSubmitterView = actionChallengeRole === SUBMITTER
554671
&& selectedTab !== APPROVAL
555-
const reviewRows = isSubmitterView ? filteredSubmitterReviews : filteredReviews
672+
const reviewRows = isSubmitterView
673+
? (shouldSortReviewTabByScore ? submitterRowsForReviewTab : filteredSubmitterReviews)
674+
: (shouldSortReviewTabByScore ? reviewerRowsForReviewTab : filteredReviews)
556675

557676
if (props.isLoadingReview) {
558677
return <TableLoading />
@@ -596,14 +715,14 @@ export const TabContentReview: FC<Props> = (props: Props) => {
596715

597716
return isSubmitterView ? (
598717
<TableReviewForSubmitter
599-
datas={filteredSubmitterReviews}
718+
datas={reviewRows}
600719
isDownloading={props.isDownloading}
601720
downloadSubmission={props.downloadSubmission}
602721
mappingReviewAppeal={props.mappingReviewAppeal}
603722
/>
604723
) : (
605724
<TableReview
606-
datas={filteredReviews}
725+
datas={reviewRows}
607726
isDownloading={props.isDownloading}
608727
downloadSubmission={props.downloadSubmission}
609728
mappingReviewAppeal={props.mappingReviewAppeal}

0 commit comments

Comments
 (0)