Skip to content

Commit a372512

Browse files
authored
Merge pull request #1570 from topcoder-platform/Sept_2023_release
Sept 2023 release
2 parents 0f840ae + 6f0bc4c commit a372512

File tree

6 files changed

+191
-53
lines changed

6 files changed

+191
-53
lines changed

src/components/ChallengeEditor/ChallengeViewTabs/index.js

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import LegacyLinks from '../../LegacyLinks'
1313
import ForumLink from '../../ForumLink'
1414
import ResourcesTab from '../Resources'
1515
import Submissions from '../Submissions'
16-
import { checkAdmin, checkEditResourceRoles, checkReadOnlyRoles } from '../../../util/tc'
16+
import {
17+
checkAdmin,
18+
checkEditResourceRoles,
19+
checkReadOnlyRoles,
20+
checkCopilot
21+
} from '../../../util/tc'
1722
import { CHALLENGE_STATUS, MESSAGE } from '../../../config/constants'
1823
import Tooltip from '../../Tooltip'
1924
import CancelDropDown from '../Cancel-Dropdown'
@@ -117,6 +122,21 @@ const ChallengeViewTabs = ({
117122
const isDraft = challenge.status.toUpperCase() === CHALLENGE_STATUS.DRAFT
118123
const isSelfServiceCopilot = challenge.legacy.selfServiceCopilot === loggedInUser.handle
119124
const isAdmin = checkAdmin(token)
125+
126+
// Make sure that the Launch and Mark as completed buttons are hidden
127+
// for tasks that are assigned to the current logged in user, if that user has the copilot role.
128+
const preventCopilotFromActivatingTask = useMemo(() => {
129+
return isTask &&
130+
checkCopilot(token) &&
131+
assignedMemberDetails &&
132+
loggedInUser &&
133+
`${loggedInUser.userId}` === `${assignedMemberDetails.userId}`
134+
}, [
135+
token,
136+
assignedMemberDetails,
137+
loggedInUser
138+
])
139+
120140
const isReadOnly = checkReadOnlyRoles(token)
121141
const canApprove = (isSelfServiceCopilot || enableEdit) && isDraft && isSelfService
122142
const hasBillingAccount = _.get(projectDetail, 'billingAccountId') !== null
@@ -125,10 +145,35 @@ const ChallengeViewTabs = ({
125145
// OR if this isn't a non-self-service draft, permit launching if:
126146
// a) the current user is either the self-service copilot or is an admin AND
127147
// b) the challenge is approved
128-
const canLaunch = enableEdit && hasBillingAccount && !isReadOnly &&
129-
((!isSelfService && isDraft) ||
130-
((isSelfServiceCopilot || isAdmin) &&
131-
challenge.status.toUpperCase() === CHALLENGE_STATUS.APPROVED))
148+
const canLaunch = useMemo(() => {
149+
return enableEdit &&
150+
hasBillingAccount &&
151+
(!isReadOnly) &&
152+
(!preventCopilotFromActivatingTask) &&
153+
(
154+
(
155+
!isSelfService &&
156+
isDraft
157+
) ||
158+
(
159+
(
160+
isSelfServiceCopilot ||
161+
isAdmin
162+
) &&
163+
challenge.status.toUpperCase() === CHALLENGE_STATUS.APPROVED
164+
)
165+
)
166+
}, [
167+
enableEdit,
168+
hasBillingAccount,
169+
isReadOnly,
170+
isSelfService,
171+
isDraft,
172+
isSelfServiceCopilot,
173+
isAdmin,
174+
challenge.status,
175+
preventCopilotFromActivatingTask
176+
])
132177

133178
return (
134179
<div className={styles.list}>
@@ -184,20 +229,26 @@ const ChallengeViewTabs = ({
184229
/>
185230
</div>
186231
)}
187-
{isTask && challenge.status === 'Active' && (
188-
<div className={styles.button}>
189-
{assignedMemberDetails ? (
190-
<Tooltip content={MESSAGE.MARK_COMPLETE}>
191-
<PrimaryButton text={'Mark Complete'} type={'success'} onClick={onCloseTask} />
192-
</Tooltip>
193-
) : (
194-
<Tooltip content={MESSAGE.NO_TASK_ASSIGNEE}>
195-
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
196-
<PrimaryButton text={'Mark Complete'} type={'disabled'} />
197-
</Tooltip>
198-
)}
199-
</div>
200-
)}
232+
{
233+
(
234+
isTask &&
235+
challenge.status === 'Active' &&
236+
!preventCopilotFromActivatingTask
237+
) && (
238+
<div className={styles.button}>
239+
{assignedMemberDetails ? (
240+
<Tooltip content={MESSAGE.MARK_COMPLETE}>
241+
<PrimaryButton text={'Mark Complete'} type={'success'} onClick={onCloseTask} />
242+
</Tooltip>
243+
) : (
244+
<Tooltip content={MESSAGE.NO_TASK_ASSIGNEE}>
245+
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
246+
<PrimaryButton text={'Mark Complete'} type={'disabled'} />
247+
</Tooltip>
248+
)}
249+
</div>
250+
)
251+
}
201252
{enableEdit && !canEditResource && (
202253
<PrimaryButton text={'Edit'} type={'info'} submit link={`./edit`} />
203254
)}

src/components/ChallengeEditor/Submissions/index.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import moment from 'moment'
99
import _ from 'lodash'
1010
import { STUDIO_URL, SUBMISSION_REVIEW_APP_URL, getTCMemberURL } from '../../../config/constants'
1111
import { PrimaryButton } from '../../Buttons'
12+
import AlertModal from '../../Modal/AlertModal'
1213
import cn from 'classnames'
1314
import ReactSVG from 'react-svg'
1415
import {
@@ -20,17 +21,23 @@ import {
2021
checkAdmin
2122
} from '../../../util/tc'
2223
import {
23-
getTopcoderReactLib
24+
getTopcoderReactLib,
25+
isValidDownloadFile
2426
} from '../../../util/topcoder-react-lib'
2527
import {
2628
compressFiles
2729
} from '../../../util/files'
2830
import styles from './Submissions.module.scss'
31+
import modalStyles from '../../../styles/modal.module.scss'
2932
const assets = require.context('../../../assets/images', false, /svg/)
3033
const ArrowDown = './arrow-down.svg'
3134
const Lock = './lock.svg'
3235
const Download = './IconSquareDownload.svg'
3336

37+
const theme = {
38+
container: modalStyles.modalContainer
39+
}
40+
3441
class SubmissionsComponent extends React.Component {
3542
constructor (props) {
3643
super(props)
@@ -42,7 +49,8 @@ class SubmissionsComponent extends React.Component {
4249
isShowInformation: false,
4350
memberOfModal: '',
4451
sortedSubmissions: [],
45-
downloadingAll: false
52+
downloadingAll: false,
53+
alertMessage: ''
4654
}
4755
this.getSubmissionsSortParam = this.getSubmissionsSortParam.bind(this)
4856
this.updateSortedSubmissions = this.updateSortedSubmissions.bind(this)
@@ -222,7 +230,7 @@ class SubmissionsComponent extends React.Component {
222230
const { field, sort } = this.getSubmissionsSortParam()
223231
const revertSort = sort === 'desc' ? 'asc' : 'desc'
224232

225-
const { sortedSubmissions, downloadingAll } = this.state
233+
const { sortedSubmissions, downloadingAll, alertMessage } = this.state
226234

227235
const renderSubmission = s => (
228236
<div className={styles.submission} key={s.id}>
@@ -544,19 +552,27 @@ class SubmissionsComponent extends React.Component {
544552
const submissionsService = getService(token)
545553
submissionsService.downloadSubmission(s.id)
546554
.then((blob) => {
547-
// eslint-disable-next-line no-undef
548-
const url = window.URL.createObjectURL(new Blob([blob]))
549-
const link = document.createElement('a')
550-
link.href = url
551-
let fileName = s.legacySubmissionId
552-
if (!fileName) {
553-
fileName = s.id
554-
}
555-
fileName = fileName + '.zip'
556-
link.setAttribute('download', `${fileName}`)
557-
document.body.appendChild(link)
558-
link.click()
559-
link.parentNode.removeChild(link)
555+
isValidDownloadFile(blob).then((isValidFile) => {
556+
if (isValidFile.success) {
557+
// eslint-disable-next-line no-undef
558+
const url = window.URL.createObjectURL(new Blob([blob]))
559+
const link = document.createElement('a')
560+
link.href = url
561+
let fileName = s.legacySubmissionId
562+
if (!fileName) {
563+
fileName = s.id
564+
}
565+
fileName = fileName + '.zip'
566+
link.setAttribute('download', `${fileName}`)
567+
document.body.appendChild(link)
568+
link.click()
569+
link.parentNode.removeChild(link)
570+
} else {
571+
this.setState({
572+
alertMessage: isValidFile.message || 'Can not download this submission.'
573+
})
574+
}
575+
})
560576
})
561577
}}
562578
>
@@ -611,10 +627,14 @@ class SubmissionsComponent extends React.Component {
611627
fileName = fileName + '.zip'
612628
submissionsService.downloadSubmission(submission.id)
613629
.then((blob) => {
614-
const file = new window.File([blob], `${fileName}`)
615-
allFiles.push(file)
616-
downloadedFile += 1
617-
checkToCompressFiles()
630+
isValidDownloadFile(blob).then((isValidFile) => {
631+
if (isValidFile.success) {
632+
const file = new window.File([blob], `${fileName}`)
633+
allFiles.push(file)
634+
}
635+
downloadedFile += 1
636+
checkToCompressFiles()
637+
})
618638
}).catch(() => {
619639
downloadedFile += 1
620640
checkToCompressFiles()
@@ -625,6 +645,20 @@ class SubmissionsComponent extends React.Component {
625645
</div>
626646
</div>) : null}
627647
</div>
648+
649+
{alertMessage ? (
650+
<AlertModal
651+
title=''
652+
message={alertMessage}
653+
theme={theme}
654+
closeText='OK'
655+
onClose={() => {
656+
this.setState({
657+
alertMessage: ''
658+
})
659+
}}
660+
/>
661+
) : null}
628662
</>
629663
)
630664
}

src/components/ChallengeEditor/index.js

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ import {
3030
MULTI_ROUND_CHALLENGE_TEMPLATE_ID, DS_TRACK_ID,
3131
CHALLENGE_STATUS
3232
} from '../../config/constants'
33-
import { getDomainTypes, getResourceRoleByName, is2RoundsChallenge } from '../../util/tc'
33+
import {
34+
getDomainTypes,
35+
getResourceRoleByName,
36+
is2RoundsChallenge,
37+
checkCopilot
38+
} from '../../util/tc'
3439
import { getPhaseEndDate } from '../../util/date'
3540
import { PrimaryButton, OutlineButton } from '../Buttons'
3641
import TrackField from './Track-Field'
@@ -1527,6 +1532,14 @@ class ChallengeEditor extends Component {
15271532
const statusMessage = challenge.status && challenge.status.split(' ')[0].toUpperCase()
15281533
const errorContainer = <div className={styles.errorContainer}><div className={styles.errorMessage}>{error}</div></div>
15291534

1535+
// Make sure that the Launch and Mark as completed buttons are hidden
1536+
// for tasks that are assigned to the current logged in user, if that user has the copilot role.
1537+
const preventCopilotFromActivatingTask = isTask &&
1538+
checkCopilot(token) &&
1539+
assignedMemberDetails &&
1540+
loggedInUser &&
1541+
`${loggedInUser.userId}` === `${assignedMemberDetails.userId}`
1542+
15301543
const actionButtons = <React.Fragment>
15311544
{!isLoading && this.state.hasValidationErrors && <div className={styles.error}>Please fix the errors before saving</div>}
15321545
{
@@ -1553,18 +1566,23 @@ class ChallengeEditor extends Component {
15531566
<PrimaryButton text={'Save Draft'} type={'disabled'} />
15541567
)}
15551568
</div>
1556-
{isDraft && (
1557-
<div className={styles.button}>
1558-
{(challenge.legacyId || isTask) && !this.state.hasValidationErrors ? (
1559-
<PrimaryButton text={'Launch as Active'} type={'info'} onClick={this.toggleLaunch} />
1560-
) : (
1561-
<Tooltip content={MESSAGE.NO_LEGACY_CHALLENGE}>
1562-
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
1563-
<PrimaryButton text={'Launch as Active'} type={'disabled'} />
1564-
</Tooltip>
1565-
)}
1566-
</div>
1567-
)}
1569+
{
1570+
(
1571+
isDraft &&
1572+
!preventCopilotFromActivatingTask
1573+
) && (
1574+
<div className={styles.button}>
1575+
{(challenge.legacyId || isTask) && !this.state.hasValidationErrors ? (
1576+
<PrimaryButton text={'Launch as Active'} type={'info'} onClick={this.toggleLaunch} />
1577+
) : (
1578+
<Tooltip content={MESSAGE.NO_LEGACY_CHALLENGE}>
1579+
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
1580+
<PrimaryButton text={'Launch as Active'} type={'disabled'} />
1581+
</Tooltip>
1582+
)}
1583+
</div>
1584+
)
1585+
}
15681586
{statusMessage !== CHALLENGE_STATUS.CANCELLED &&
15691587
<div className={styles.button}>
15701588
<CancelDropDown challenge={challenge} onSelectMenu={cancelChallenge} />
@@ -1575,7 +1593,10 @@ class ChallengeEditor extends Component {
15751593
<div className={styles.button}>
15761594
<OutlineButton text={isSaving ? 'Saving...' : 'Save'} type={'success'} onClick={this.onSaveChallenge} />
15771595
</div>
1578-
{isTask && (
1596+
{(
1597+
isTask &&
1598+
!preventCopilotFromActivatingTask
1599+
) && (
15791600
<div className={styles.button}>
15801601
<Tooltip content={MESSAGE.MARK_COMPLETE}>
15811602
<PrimaryButton text={'Mark Complete'} type={'success'} onClick={this.openCloseTaskConfirmation} />

src/config/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ export const ADMIN_ROLES = [
263263
'connect admin'
264264
]
265265

266+
export const COPILOT_ROLES = [
267+
'copilot'
268+
]
269+
266270
export const downloadAttachmentURL = (challengeId, attachmentId, token) =>
267271
`${CHALLENGE_API_URL}/${challengeId}/attachments/${attachmentId}/download?token=${token}`
268272

src/util/tc.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
CHALLENGE_TRACKS,
77
ALLOWED_USER_ROLES,
88
ADMIN_ROLES,
9+
COPILOT_ROLES,
910
SUBMITTER_ROLE_UUID,
1011
READ_ONLY_ROLES,
1112
ALLOWED_DOWNLOAD_SUBMISSIONS_ROLES,
@@ -198,6 +199,15 @@ export const checkAdmin = token => {
198199
return roles.some(val => ADMIN_ROLES.indexOf(val.toLowerCase()) > -1)
199200
}
200201

202+
/**
203+
* Checks if token has any of the copilot roles
204+
* @param token
205+
*/
206+
export const checkCopilot = token => {
207+
const roles = _.get(decodeToken(token), 'roles')
208+
return roles.some(val => COPILOT_ROLES.indexOf(val.toLowerCase()) > -1)
209+
}
210+
201211
/**
202212
* Get resource role by name
203213
*

src/util/topcoder-react-lib.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,21 @@ export const getTopcoderReactLib = () => {
1414
const reactLib = require('topcoder-react-lib')
1515
return reactLib
1616
}
17+
18+
export const isValidDownloadFile = async (blobFile) => {
19+
if (!blobFile) {
20+
return {
21+
success: false
22+
}
23+
}
24+
if (blobFile.type.indexOf('json') >= 0) {
25+
const backendResonse = JSON.parse(await blobFile.text())
26+
return {
27+
success: false,
28+
message: backendResonse.message || ''
29+
}
30+
}
31+
return {
32+
success: true
33+
}
34+
}

0 commit comments

Comments
 (0)