diff --git a/src/components/ChallengeEditor/ChallengeViewTabs/index.js b/src/components/ChallengeEditor/ChallengeViewTabs/index.js
index c8cb0007..84ee5676 100644
--- a/src/components/ChallengeEditor/ChallengeViewTabs/index.js
+++ b/src/components/ChallengeEditor/ChallengeViewTabs/index.js
@@ -13,7 +13,12 @@ import LegacyLinks from '../../LegacyLinks'
import ForumLink from '../../ForumLink'
import ResourcesTab from '../Resources'
import Submissions from '../Submissions'
-import { checkAdmin, checkEditResourceRoles, checkReadOnlyRoles } from '../../../util/tc'
+import {
+ checkAdmin,
+ checkEditResourceRoles,
+ checkReadOnlyRoles,
+ checkCopilot
+} from '../../../util/tc'
import { CHALLENGE_STATUS, MESSAGE } from '../../../config/constants'
import Tooltip from '../../Tooltip'
import CancelDropDown from '../Cancel-Dropdown'
@@ -117,6 +122,21 @@ const ChallengeViewTabs = ({
const isDraft = challenge.status.toUpperCase() === CHALLENGE_STATUS.DRAFT
const isSelfServiceCopilot = challenge.legacy.selfServiceCopilot === loggedInUser.handle
const isAdmin = checkAdmin(token)
+
+ // Make sure that the Launch and Mark as completed buttons are hidden
+ // for tasks that are assigned to the current logged in user, if that user has the copilot role.
+ const preventCopilotFromActivatingTask = useMemo(() => {
+ return isTask &&
+ checkCopilot(token) &&
+ assignedMemberDetails &&
+ loggedInUser &&
+ `${loggedInUser.userId}` === `${assignedMemberDetails.userId}`
+ }, [
+ token,
+ assignedMemberDetails,
+ loggedInUser
+ ])
+
const isReadOnly = checkReadOnlyRoles(token)
const canApprove = (isSelfServiceCopilot || enableEdit) && isDraft && isSelfService
const hasBillingAccount = _.get(projectDetail, 'billingAccountId') !== null
@@ -125,10 +145,35 @@ const ChallengeViewTabs = ({
// OR if this isn't a non-self-service draft, permit launching if:
// a) the current user is either the self-service copilot or is an admin AND
// b) the challenge is approved
- const canLaunch = enableEdit && hasBillingAccount && !isReadOnly &&
- ((!isSelfService && isDraft) ||
- ((isSelfServiceCopilot || isAdmin) &&
- challenge.status.toUpperCase() === CHALLENGE_STATUS.APPROVED))
+ const canLaunch = useMemo(() => {
+ return enableEdit &&
+ hasBillingAccount &&
+ (!isReadOnly) &&
+ (!preventCopilotFromActivatingTask) &&
+ (
+ (
+ !isSelfService &&
+ isDraft
+ ) ||
+ (
+ (
+ isSelfServiceCopilot ||
+ isAdmin
+ ) &&
+ challenge.status.toUpperCase() === CHALLENGE_STATUS.APPROVED
+ )
+ )
+ }, [
+ enableEdit,
+ hasBillingAccount,
+ isReadOnly,
+ isSelfService,
+ isDraft,
+ isSelfServiceCopilot,
+ isAdmin,
+ challenge.status,
+ preventCopilotFromActivatingTask
+ ])
return (
@@ -184,20 +229,26 @@ const ChallengeViewTabs = ({
/>
)}
- {isTask && challenge.status === 'Active' && (
-
- {assignedMemberDetails ? (
-
-
-
- ) : (
-
- {/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
-
-
- )}
-
- )}
+ {
+ (
+ isTask &&
+ challenge.status === 'Active' &&
+ !preventCopilotFromActivatingTask
+ ) && (
+
+ {assignedMemberDetails ? (
+
+
+
+ ) : (
+
+ {/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
+
+
+ )}
+
+ )
+ }
{enableEdit && !canEditResource && (
)}
diff --git a/src/components/ChallengeEditor/Submissions/index.js b/src/components/ChallengeEditor/Submissions/index.js
index 7aba7d92..13428040 100644
--- a/src/components/ChallengeEditor/Submissions/index.js
+++ b/src/components/ChallengeEditor/Submissions/index.js
@@ -9,6 +9,7 @@ import moment from 'moment'
import _ from 'lodash'
import { STUDIO_URL, SUBMISSION_REVIEW_APP_URL, getTCMemberURL } from '../../../config/constants'
import { PrimaryButton } from '../../Buttons'
+import AlertModal from '../../Modal/AlertModal'
import cn from 'classnames'
import ReactSVG from 'react-svg'
import {
@@ -20,17 +21,23 @@ import {
checkAdmin
} from '../../../util/tc'
import {
- getTopcoderReactLib
+ getTopcoderReactLib,
+ isValidDownloadFile
} from '../../../util/topcoder-react-lib'
import {
compressFiles
} from '../../../util/files'
import styles from './Submissions.module.scss'
+import modalStyles from '../../../styles/modal.module.scss'
const assets = require.context('../../../assets/images', false, /svg/)
const ArrowDown = './arrow-down.svg'
const Lock = './lock.svg'
const Download = './IconSquareDownload.svg'
+const theme = {
+ container: modalStyles.modalContainer
+}
+
class SubmissionsComponent extends React.Component {
constructor (props) {
super(props)
@@ -42,7 +49,8 @@ class SubmissionsComponent extends React.Component {
isShowInformation: false,
memberOfModal: '',
sortedSubmissions: [],
- downloadingAll: false
+ downloadingAll: false,
+ alertMessage: ''
}
this.getSubmissionsSortParam = this.getSubmissionsSortParam.bind(this)
this.updateSortedSubmissions = this.updateSortedSubmissions.bind(this)
@@ -222,7 +230,7 @@ class SubmissionsComponent extends React.Component {
const { field, sort } = this.getSubmissionsSortParam()
const revertSort = sort === 'desc' ? 'asc' : 'desc'
- const { sortedSubmissions, downloadingAll } = this.state
+ const { sortedSubmissions, downloadingAll, alertMessage } = this.state
const renderSubmission = s => (
@@ -544,19 +552,27 @@ class SubmissionsComponent extends React.Component {
const submissionsService = getService(token)
submissionsService.downloadSubmission(s.id)
.then((blob) => {
- // eslint-disable-next-line no-undef
- const url = window.URL.createObjectURL(new Blob([blob]))
- const link = document.createElement('a')
- link.href = url
- let fileName = s.legacySubmissionId
- if (!fileName) {
- fileName = s.id
- }
- fileName = fileName + '.zip'
- link.setAttribute('download', `${fileName}`)
- document.body.appendChild(link)
- link.click()
- link.parentNode.removeChild(link)
+ isValidDownloadFile(blob).then((isValidFile) => {
+ if (isValidFile.success) {
+ // eslint-disable-next-line no-undef
+ const url = window.URL.createObjectURL(new Blob([blob]))
+ const link = document.createElement('a')
+ link.href = url
+ let fileName = s.legacySubmissionId
+ if (!fileName) {
+ fileName = s.id
+ }
+ fileName = fileName + '.zip'
+ link.setAttribute('download', `${fileName}`)
+ document.body.appendChild(link)
+ link.click()
+ link.parentNode.removeChild(link)
+ } else {
+ this.setState({
+ alertMessage: isValidFile.message || 'Can not download this submission.'
+ })
+ }
+ })
})
}}
>
@@ -611,10 +627,14 @@ class SubmissionsComponent extends React.Component {
fileName = fileName + '.zip'
submissionsService.downloadSubmission(submission.id)
.then((blob) => {
- const file = new window.File([blob], `${fileName}`)
- allFiles.push(file)
- downloadedFile += 1
- checkToCompressFiles()
+ isValidDownloadFile(blob).then((isValidFile) => {
+ if (isValidFile.success) {
+ const file = new window.File([blob], `${fileName}`)
+ allFiles.push(file)
+ }
+ downloadedFile += 1
+ checkToCompressFiles()
+ })
}).catch(() => {
downloadedFile += 1
checkToCompressFiles()
@@ -625,6 +645,20 @@ class SubmissionsComponent extends React.Component {
) : null}
+
+ {alertMessage ? (
+ {
+ this.setState({
+ alertMessage: ''
+ })
+ }}
+ />
+ ) : null}
>
)
}
diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js
index f181e87b..0bf42dfa 100644
--- a/src/components/ChallengeEditor/index.js
+++ b/src/components/ChallengeEditor/index.js
@@ -30,7 +30,12 @@ import {
MULTI_ROUND_CHALLENGE_TEMPLATE_ID, DS_TRACK_ID,
CHALLENGE_STATUS
} from '../../config/constants'
-import { getDomainTypes, getResourceRoleByName, is2RoundsChallenge } from '../../util/tc'
+import {
+ getDomainTypes,
+ getResourceRoleByName,
+ is2RoundsChallenge,
+ checkCopilot
+} from '../../util/tc'
import { getPhaseEndDate } from '../../util/date'
import { PrimaryButton, OutlineButton } from '../Buttons'
import TrackField from './Track-Field'
@@ -1527,6 +1532,14 @@ class ChallengeEditor extends Component {
const statusMessage = challenge.status && challenge.status.split(' ')[0].toUpperCase()
const errorContainer =
+ // Make sure that the Launch and Mark as completed buttons are hidden
+ // for tasks that are assigned to the current logged in user, if that user has the copilot role.
+ const preventCopilotFromActivatingTask = isTask &&
+ checkCopilot(token) &&
+ assignedMemberDetails &&
+ loggedInUser &&
+ `${loggedInUser.userId}` === `${assignedMemberDetails.userId}`
+
const actionButtons =
{!isLoading && this.state.hasValidationErrors && Please fix the errors before saving
}
{
@@ -1553,18 +1566,23 @@ class ChallengeEditor extends Component {
)}
- {isDraft && (
-
- {(challenge.legacyId || isTask) && !this.state.hasValidationErrors ? (
-
- ) : (
-
- {/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
-
-
- )}
-
- )}
+ {
+ (
+ isDraft &&
+ !preventCopilotFromActivatingTask
+ ) && (
+
+ {(challenge.legacyId || isTask) && !this.state.hasValidationErrors ? (
+
+ ) : (
+
+ {/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
+
+
+ )}
+
+ )
+ }
{statusMessage !== CHALLENGE_STATUS.CANCELLED &&
@@ -1575,7 +1593,10 @@ class ChallengeEditor extends Component {
- {isTask && (
+ {(
+ isTask &&
+ !preventCopilotFromActivatingTask
+ ) && (
diff --git a/src/config/constants.js b/src/config/constants.js
index 0bb1db0f..a959f65b 100644
--- a/src/config/constants.js
+++ b/src/config/constants.js
@@ -263,6 +263,10 @@ export const ADMIN_ROLES = [
'connect admin'
]
+export const COPILOT_ROLES = [
+ 'copilot'
+]
+
export const downloadAttachmentURL = (challengeId, attachmentId, token) =>
`${CHALLENGE_API_URL}/${challengeId}/attachments/${attachmentId}/download?token=${token}`
diff --git a/src/util/tc.js b/src/util/tc.js
index 2f85ed5d..00b8c81e 100644
--- a/src/util/tc.js
+++ b/src/util/tc.js
@@ -6,6 +6,7 @@ import {
CHALLENGE_TRACKS,
ALLOWED_USER_ROLES,
ADMIN_ROLES,
+ COPILOT_ROLES,
SUBMITTER_ROLE_UUID,
READ_ONLY_ROLES,
ALLOWED_DOWNLOAD_SUBMISSIONS_ROLES,
@@ -198,6 +199,15 @@ export const checkAdmin = token => {
return roles.some(val => ADMIN_ROLES.indexOf(val.toLowerCase()) > -1)
}
+/**
+ * Checks if token has any of the copilot roles
+ * @param token
+ */
+export const checkCopilot = token => {
+ const roles = _.get(decodeToken(token), 'roles')
+ return roles.some(val => COPILOT_ROLES.indexOf(val.toLowerCase()) > -1)
+}
+
/**
* Get resource role by name
*
diff --git a/src/util/topcoder-react-lib.js b/src/util/topcoder-react-lib.js
index 5eb90f84..e139379b 100644
--- a/src/util/topcoder-react-lib.js
+++ b/src/util/topcoder-react-lib.js
@@ -14,3 +14,21 @@ export const getTopcoderReactLib = () => {
const reactLib = require('topcoder-react-lib')
return reactLib
}
+
+export const isValidDownloadFile = async (blobFile) => {
+ if (!blobFile) {
+ return {
+ success: false
+ }
+ }
+ if (blobFile.type.indexOf('json') >= 0) {
+ const backendResonse = JSON.parse(await blobFile.text())
+ return {
+ success: false,
+ message: backendResonse.message || ''
+ }
+ }
+ return {
+ success: true
+ }
+}