Skip to content

Commit 82358f6

Browse files
authored
Merge pull request #1735 from topcoder-platform/projects-api-v6
Projects api v6
2 parents 285be20 + 6f51c86 commit 82358f6

14 files changed

Lines changed: 422 additions & 58 deletions

File tree

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ workflows:
160160
context: org-global
161161
filters: &filters-dev
162162
branches:
163-
only: ["develop", "pm-2917", "points", "pm-3270", "engagements"]
163+
only: ["develop", "pm-2917", "points", "pm-3270", "projects-api-v6"]
164164

165165
# Production builds are exectuted only on tagged commits to the
166166
# master branch.

config/constants/development.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module.exports = {
3030
CHALLENGE_PHASES_URL: `${DEV_API_HOSTNAME}/v6/challenge-phases`,
3131
CHALLENGE_TIMELINES_URL: `${DEV_API_HOSTNAME}/v6/challenge-timelines`,
3232
COPILOTS_URL: 'https://copilots.topcoder-dev.com',
33-
PROJECT_API_URL: `${DEV_API_HOSTNAME}/v5/projects`,
33+
PROJECT_API_URL: `${DEV_API_HOSTNAME}/v6/projects`,
3434
GROUPS_API_URL: `${DEV_API_HOSTNAME}/v6/groups`,
3535
TERMS_API_URL: `${DEV_API_HOSTNAME}/v5/terms`,
3636
RESOURCES_API_URL: `${DEV_API_HOSTNAME}/v6/resources`,

config/constants/local.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const LOCAL_MEMBER_API = 'http://localhost:3003/v6'
1212
const LOCAL_RESOURCE_API = 'http://localhost:3004/v6'
1313
const LOCAL_REVIEW_API = 'http://localhost:3005/v6'
1414
const LOCAL_SKILLS_API_V5 = 'http://localhost:3006/v5/standardized-skills'
15+
const LOCAL_PROJECTS_API = 'http://localhost:3008/v6/projects'
1516
// Lookups API available on 3007 if needed in future
1617
// const LOCAL_LOOKUPS_API = 'http://localhost:3007/v6'
1718

@@ -46,8 +47,8 @@ module.exports = {
4647
// Copilots and other apps remain on dev
4748
COPILOTS_URL: 'https://copilots.topcoder-dev.com',
4849

49-
// Projects API: keep dev unless you run projects locally
50-
PROJECT_API_URL: `${DEV_API_HOSTNAME}/v5/projects`,
50+
// Projects API v6: keep dev default (switch to LOCAL_PROJECTS_API when needed)
51+
PROJECT_API_URL: `${DEV_API_HOSTNAME}/v6/projects`,
5152

5253
// Local groups/resources/review services
5354
GROUPS_API_URL: `${LOCAL_GROUPS_API}/groups`,

config/constants/production.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = {
2929
CHALLENGE_PHASES_URL: `${PROD_API_HOSTNAME}/v6/challenge-phases`,
3030
CHALLENGE_TIMELINES_URL: `${PROD_API_HOSTNAME}/v6/challenge-timelines`,
3131
COPILOTS_URL: `https://copilots.${DOMAIN}`,
32-
PROJECT_API_URL: `${PROD_API_HOSTNAME}/v5/projects`,
32+
PROJECT_API_URL: `${PROD_API_HOSTNAME}/v6/projects`,
3333
GROUPS_API_URL: `${PROD_API_HOSTNAME}/v6/groups`,
3434
TERMS_API_URL: `${PROD_API_HOSTNAME}/v5/terms`,
3535
MEMBERS_API_URL: `${PROD_API_HOSTNAME}/v5/members`,

src/components/ChallengeEditor/ChallengeReviewer-Field/index.js

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,45 @@ const normalizeTrackForScorecards = (challenge, metadata) => {
6161
return null
6262
}
6363

64+
const normalizePhaseToken = (value) => (value || '')
65+
.toString()
66+
.toLowerCase()
67+
.trim()
68+
.replace(/\bphase\b$/, '')
69+
.replace(/[-_\s]/g, '')
70+
71+
const normalizeIdValue = (value) => (
72+
value === undefined || value === null
73+
? ''
74+
: value.toString()
75+
)
76+
77+
const getScorecardsForPhase = (scorecards = [], phases = [], phaseId) => {
78+
const normalizedPhaseId = normalizeIdValue(phaseId)
79+
if (!normalizedPhaseId) {
80+
return []
81+
}
82+
83+
const selectedPhase = phases.find(phase => (
84+
normalizeIdValue(phase.phaseId) === normalizedPhaseId ||
85+
normalizeIdValue(phase.id) === normalizedPhaseId
86+
))
87+
88+
if (!selectedPhase || !selectedPhase.name) {
89+
return []
90+
}
91+
92+
const normalizedPhaseName = normalizePhaseToken(selectedPhase.name)
93+
if (!normalizedPhaseName) {
94+
return []
95+
}
96+
97+
return scorecards.filter(scorecard => (
98+
scorecard &&
99+
normalizePhaseToken(scorecard.type) === normalizedPhaseName
100+
))
101+
}
102+
64103
class ChallengeReviewerField extends Component {
65104
constructor (props) {
66105
super(props)
@@ -602,6 +641,31 @@ class ChallengeReviewerField extends Component {
602641
baseCoefficient: defaultReviewer.baseCoefficient,
603642
incrementalCoefficient: defaultReviewer.incrementalCoefficient
604643
})
644+
645+
if (updatedReviewers[index] && (updatedReviewers[index].isMemberReview !== false)) {
646+
const { metadata = {} } = this.props
647+
const scorecardsForPhase = getScorecardsForPhase(
648+
metadata.scorecards || [],
649+
challenge.phases || [],
650+
value
651+
)
652+
const currentScorecardId = normalizeIdValue(updatedReviewers[index].scorecardId)
653+
const hasCurrentScorecard = scorecardsForPhase.some(scorecard => (
654+
normalizeIdValue(scorecard.id) === currentScorecardId
655+
))
656+
657+
if (!hasCurrentScorecard) {
658+
const defaultScorecardId = normalizeIdValue(defaultReviewer && defaultReviewer.scorecardId)
659+
const hasDefaultScorecard = defaultScorecardId && scorecardsForPhase.some(scorecard => (
660+
normalizeIdValue(scorecard.id) === defaultScorecardId
661+
))
662+
const fallbackScorecardId = hasDefaultScorecard
663+
? defaultScorecardId
664+
: normalizeIdValue(scorecardsForPhase[0] && scorecardsForPhase[0].id)
665+
666+
fieldUpdate.scorecardId = fallbackScorecardId || ''
667+
}
668+
}
605669
}
606670

607671
if (field === 'memberReviewerCount') {
@@ -661,29 +725,12 @@ class ChallengeReviewerField extends Component {
661725
const { challenge, metadata = {}, readOnly = false } = this.props
662726
const { scorecards = [], workflows = [] } = metadata
663727
const validationErrors = challenge.submitTriggered ? this.validateReviewer(reviewer) : {}
664-
const selectedPhase = challenge.phases.find(p => p.phaseId === reviewer.phaseId)
728+
const filteredScorecards = getScorecardsForPhase(
729+
scorecards,
730+
challenge.phases || [],
731+
reviewer.phaseId
732+
)
665733
const isDesignChallenge = challenge && challenge.trackId === DES_TRACK_ID
666-
const normalize = (value) => (value || '')
667-
.toString()
668-
.toLowerCase()
669-
.trim()
670-
.replace(/\bphase\b$/, '')
671-
.replace(/[-_\s]/g, '')
672-
673-
const filteredScorecards = scorecards.filter(item => {
674-
if (!selectedPhase || !selectedPhase.name || !item || !item.type) {
675-
return false
676-
}
677-
678-
const normalizedType = normalize(item.type)
679-
const normalizedPhaseName = normalize(selectedPhase.name)
680-
681-
if (!normalizedType || !normalizedPhaseName) {
682-
return false
683-
}
684-
685-
return normalizedType === normalizedPhaseName
686-
})
687734

688735
return (
689736
<div key={`reviewer-${index}`} className={styles.reviewerForm}>

src/components/ChallengeEditor/ChallengeView/index.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
import PhaseInput from '../../PhaseInput'
3232
import CheckpointPrizesField from '../CheckpointPrizes-Field'
3333
import { isBetaMode } from '../../../util/localstorage'
34+
import WiproAllowedField from '../WiproAllowedField'
3435

3536
const ChallengeView = ({
3637
projectDetail,
@@ -95,6 +96,7 @@ const ChallengeView = ({
9596
if (isLoading || _.isEmpty(metadata.challengePhases) || challenge.id !== challengeId) return <Loader />
9697
const showTimeline = false // disables the timeline for time being https://github.com/topcoder-platform/challenge-engine-ui/issues/706
9798
const isTask = _.get(challenge, 'task.isTask', false)
99+
const isFunChallenge = challenge.funChallenge === true
98100
const phases = _.get(challenge, 'phases', [])
99101
const showCheckpointPrizes = _.get(challenge, 'timelineTemplateId') === MULTI_ROUND_CHALLENGE_TEMPLATE_ID
100102
const useDashboardData = _.find(challenge.metadata, { name: 'show_data_dashboard' })
@@ -195,6 +197,7 @@ const ChallengeView = ({
195197
<>
196198
{dashboardToggle}
197199
<NDAField beta challenge={challenge} readOnly />
200+
<WiproAllowedField challenge={challenge} onUpdateOthers={() => {}} readOnly />
198201
<div className={cn(styles.row, styles.topRow)}>
199202
<div className={styles.col}>
200203
<span><span className={styles.fieldTitle}>Groups:</span> {groups}</span>
@@ -262,14 +265,24 @@ const ChallengeView = ({
262265
token={token}
263266
readOnly
264267
/>}
265-
<ChallengePrizesField challenge={challenge} readOnly />
266-
{
267-
showCheckpointPrizes && (
268-
<CheckpointPrizesField challenge={challenge} readOnly />
269-
)
270-
}
271-
<CopilotFeeField challenge={challenge} readOnly />
272-
<ChallengeTotalField challenge={challenge} />
268+
{isFunChallenge ? (
269+
<div className={cn(styles.row, styles.topRow)}>
270+
<div className={styles.col}>
271+
<span><span className={styles.fieldTitle}>Fun Challenge:</span> True</span>
272+
</div>
273+
</div>
274+
) : (
275+
<>
276+
<ChallengePrizesField challenge={challenge} readOnly />
277+
{
278+
showCheckpointPrizes && (
279+
<CheckpointPrizesField challenge={challenge} readOnly />
280+
)
281+
}
282+
<CopilotFeeField challenge={challenge} readOnly />
283+
<ChallengeTotalField challenge={challenge} />
284+
</>
285+
)}
273286
</div>
274287
</div>
275288
</div>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
@use '../../../styles/includes' as *;
2+
3+
.row {
4+
box-sizing: border-box;
5+
display: flex;
6+
flex-direction: row;
7+
margin: 30px 30px 0 30px;
8+
align-content: space-between;
9+
justify-content: flex-start;
10+
11+
.tcCheckbox {
12+
@include tc-checkbox;
13+
14+
height: 18px;
15+
width: 210px;
16+
margin: 0;
17+
padding: 0;
18+
vertical-align: bottom;
19+
position: relative;
20+
display: inline-block;
21+
22+
input[type='checkbox'] {
23+
display: none;
24+
}
25+
26+
label {
27+
@include roboto-light();
28+
29+
line-height: 17px;
30+
font-weight: 300;
31+
cursor: pointer;
32+
position: absolute;
33+
display: inline-block;
34+
width: 14px;
35+
height: 14px;
36+
top: 0;
37+
left: 0;
38+
border: none;
39+
box-shadow: none;
40+
background: $tc-gray-30;
41+
transition: all 0.15s ease-in-out;
42+
43+
&::after {
44+
opacity: 0;
45+
content: '';
46+
position: absolute;
47+
width: 9px;
48+
height: 5px;
49+
background: transparent;
50+
top: 2px;
51+
left: 2px;
52+
border-top: none;
53+
border-right: none;
54+
transform: rotate(-45deg);
55+
transition: all 0.15s ease-in-out;
56+
}
57+
58+
&:hover::after {
59+
opacity: 0.3;
60+
}
61+
62+
div {
63+
margin-left: 24px;
64+
width: 300px;
65+
}
66+
}
67+
68+
input[type='checkbox']:checked ~ label {
69+
background: $tc-blue-20;
70+
}
71+
72+
input[type='checkbox']:checked + label::after {
73+
border-color: $white;
74+
}
75+
}
76+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import styles from './FunChallengeField.module.scss'
4+
5+
/**
6+
* Renders a checkbox to toggle the `funChallenge` flag for Marathon Match challenges.
7+
*
8+
* @param {Object} props component props
9+
* @param {Object} props.challenge challenge data object that may include `funChallenge`
10+
* @param {Function} props.onUpdateOthers callback used to update top-level challenge fields
11+
* @param {boolean} props.readOnly when true, renders the control as read-only
12+
* @returns {import('react').ReactNode} rendered fun challenge checkbox field
13+
*/
14+
const FunChallengeField = ({ challenge, onUpdateOthers, readOnly }) => {
15+
const isFunChallenge = challenge.funChallenge === true
16+
17+
return (
18+
<div className={styles.row}>
19+
<div className={styles.tcCheckbox}>
20+
<input
21+
name='funChallenge'
22+
type='checkbox'
23+
id='funChallenge'
24+
checked={isFunChallenge}
25+
readOnly={readOnly}
26+
onChange={() => onUpdateOthers({ field: 'funChallenge', value: !isFunChallenge })}
27+
/>
28+
<label htmlFor='funChallenge'>
29+
<div>Fun Challenge</div>
30+
<input type='hidden' />
31+
</label>
32+
</div>
33+
</div>
34+
)
35+
}
36+
37+
FunChallengeField.defaultProps = {
38+
readOnly: false
39+
}
40+
41+
FunChallengeField.propTypes = {
42+
challenge: PropTypes.shape().isRequired,
43+
onUpdateOthers: PropTypes.func.isRequired,
44+
readOnly: PropTypes.bool
45+
}
46+
47+
export default FunChallengeField

0 commit comments

Comments
 (0)