Skip to content

Commit 6f0bc4c

Browse files
committed
Prevent copilots from paying themselves
#1568
1 parent 8edff13 commit 6f0bc4c

File tree

4 files changed

+119
-33
lines changed

4 files changed

+119
-33
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/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
*

0 commit comments

Comments
 (0)