Skip to content

Sept 2023 release #1570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 18, 2023
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
89 changes: 70 additions & 19 deletions src/components/ChallengeEditor/ChallengeViewTabs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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 (
<div className={styles.list}>
Expand Down Expand Up @@ -184,20 +229,26 @@ const ChallengeViewTabs = ({
/>
</div>
)}
{isTask && challenge.status === 'Active' && (
<div className={styles.button}>
{assignedMemberDetails ? (
<Tooltip content={MESSAGE.MARK_COMPLETE}>
<PrimaryButton text={'Mark Complete'} type={'success'} onClick={onCloseTask} />
</Tooltip>
) : (
<Tooltip content={MESSAGE.NO_TASK_ASSIGNEE}>
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
<PrimaryButton text={'Mark Complete'} type={'disabled'} />
</Tooltip>
)}
</div>
)}
{
(
isTask &&
challenge.status === 'Active' &&
!preventCopilotFromActivatingTask
) && (
<div className={styles.button}>
{assignedMemberDetails ? (
<Tooltip content={MESSAGE.MARK_COMPLETE}>
<PrimaryButton text={'Mark Complete'} type={'success'} onClick={onCloseTask} />
</Tooltip>
) : (
<Tooltip content={MESSAGE.NO_TASK_ASSIGNEE}>
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
<PrimaryButton text={'Mark Complete'} type={'disabled'} />
</Tooltip>
)}
</div>
)
}
{enableEdit && !canEditResource && (
<PrimaryButton text={'Edit'} type={'info'} submit link={`./edit`} />
)}
Expand Down
74 changes: 54 additions & 20 deletions src/components/ChallengeEditor/Submissions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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 => (
<div className={styles.submission} key={s.id}>
Expand Down Expand Up @@ -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.'
})
}
})
})
}}
>
Expand Down Expand Up @@ -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()
Expand All @@ -625,6 +645,20 @@ class SubmissionsComponent extends React.Component {
</div>
</div>) : null}
</div>

{alertMessage ? (
<AlertModal
title=''
message={alertMessage}
theme={theme}
closeText='OK'
onClose={() => {
this.setState({
alertMessage: ''
})
}}
/>
) : null}
</>
)
}
Expand Down
49 changes: 35 additions & 14 deletions src/components/ChallengeEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -1527,6 +1532,14 @@ class ChallengeEditor extends Component {
const statusMessage = challenge.status && challenge.status.split(' ')[0].toUpperCase()
const errorContainer = <div className={styles.errorContainer}><div className={styles.errorMessage}>{error}</div></div>

// 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 = <React.Fragment>
{!isLoading && this.state.hasValidationErrors && <div className={styles.error}>Please fix the errors before saving</div>}
{
Expand All @@ -1553,18 +1566,23 @@ class ChallengeEditor extends Component {
<PrimaryButton text={'Save Draft'} type={'disabled'} />
)}
</div>
{isDraft && (
<div className={styles.button}>
{(challenge.legacyId || isTask) && !this.state.hasValidationErrors ? (
<PrimaryButton text={'Launch as Active'} type={'info'} onClick={this.toggleLaunch} />
) : (
<Tooltip content={MESSAGE.NO_LEGACY_CHALLENGE}>
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
<PrimaryButton text={'Launch as Active'} type={'disabled'} />
</Tooltip>
)}
</div>
)}
{
(
isDraft &&
!preventCopilotFromActivatingTask
) && (
<div className={styles.button}>
{(challenge.legacyId || isTask) && !this.state.hasValidationErrors ? (
<PrimaryButton text={'Launch as Active'} type={'info'} onClick={this.toggleLaunch} />
) : (
<Tooltip content={MESSAGE.NO_LEGACY_CHALLENGE}>
{/* Don't disable button for real inside tooltip, otherwise mouseEnter/Leave events work not good */}
<PrimaryButton text={'Launch as Active'} type={'disabled'} />
</Tooltip>
)}
</div>
)
}
{statusMessage !== CHALLENGE_STATUS.CANCELLED &&
<div className={styles.button}>
<CancelDropDown challenge={challenge} onSelectMenu={cancelChallenge} />
Expand All @@ -1575,7 +1593,10 @@ class ChallengeEditor extends Component {
<div className={styles.button}>
<OutlineButton text={isSaving ? 'Saving...' : 'Save'} type={'success'} onClick={this.onSaveChallenge} />
</div>
{isTask && (
{(
isTask &&
!preventCopilotFromActivatingTask
) && (
<div className={styles.button}>
<Tooltip content={MESSAGE.MARK_COMPLETE}>
<PrimaryButton text={'Mark Complete'} type={'success'} onClick={this.openCloseTaskConfirmation} />
Expand Down
4 changes: 4 additions & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`

Expand Down
10 changes: 10 additions & 0 deletions src/util/tc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CHALLENGE_TRACKS,
ALLOWED_USER_ROLES,
ADMIN_ROLES,
COPILOT_ROLES,
SUBMITTER_ROLE_UUID,
READ_ONLY_ROLES,
ALLOWED_DOWNLOAD_SUBMISSIONS_ROLES,
Expand Down Expand Up @@ -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
*
Expand Down
18 changes: 18 additions & 0 deletions src/util/topcoder-react-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}