-
Notifications
You must be signed in to change notification settings - Fork 51
Projects api v6 #1735
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
Projects api v6 #1735
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ const LOCAL_MEMBER_API = 'http://localhost:3003/v6' | |
| const LOCAL_RESOURCE_API = 'http://localhost:3004/v6' | ||
| const LOCAL_REVIEW_API = 'http://localhost:3005/v6' | ||
| const LOCAL_SKILLS_API_V5 = 'http://localhost:3006/v5/standardized-skills' | ||
| const LOCAL_PROJECTS_API = 'http://localhost:3008/v6/projects' | ||
| // Lookups API available on 3007 if needed in future | ||
| // const LOCAL_LOOKUPS_API = 'http://localhost:3007/v6' | ||
|
|
||
|
|
@@ -46,8 +47,8 @@ module.exports = { | |
| // Copilots and other apps remain on dev | ||
| COPILOTS_URL: 'https://copilots.topcoder-dev.com', | ||
|
|
||
| // Projects API: keep dev unless you run projects locally | ||
| PROJECT_API_URL: `${DEV_API_HOSTNAME}/v5/projects`, | ||
| // Projects API v6: keep dev default (switch to LOCAL_PROJECTS_API when needed) | ||
| PROJECT_API_URL: `${DEV_API_HOSTNAME}/v6/projects`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
|
|
||
| // Local groups/resources/review services | ||
| GROUPS_API_URL: `${LOCAL_GROUPS_API}/groups`, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,7 +29,7 @@ module.exports = { | |
| CHALLENGE_PHASES_URL: `${PROD_API_HOSTNAME}/v6/challenge-phases`, | ||
| CHALLENGE_TIMELINES_URL: `${PROD_API_HOSTNAME}/v6/challenge-timelines`, | ||
| COPILOTS_URL: `https://copilots.${DOMAIN}`, | ||
| PROJECT_API_URL: `${PROD_API_HOSTNAME}/v5/projects`, | ||
| PROJECT_API_URL: `${PROD_API_HOSTNAME}/v6/projects`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| GROUPS_API_URL: `${PROD_API_HOSTNAME}/v6/groups`, | ||
| TERMS_API_URL: `${PROD_API_HOSTNAME}/v5/terms`, | ||
| MEMBERS_API_URL: `${PROD_API_HOSTNAME}/v5/members`, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,45 @@ const normalizeTrackForScorecards = (challenge, metadata) => { | |
| return null | ||
| } | ||
|
|
||
| const normalizePhaseToken = (value) => (value || '') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| .toString() | ||
| .toLowerCase() | ||
| .trim() | ||
| .replace(/\bphase\b$/, '') | ||
| .replace(/[-_\s]/g, '') | ||
|
|
||
| const normalizeIdValue = (value) => ( | ||
| value === undefined || value === null | ||
| ? '' | ||
| : value.toString() | ||
| ) | ||
|
|
||
| const getScorecardsForPhase = (scorecards = [], phases = [], phaseId) => { | ||
| const normalizedPhaseId = normalizeIdValue(phaseId) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| if (!normalizedPhaseId) { | ||
| return [] | ||
| } | ||
|
|
||
| const selectedPhase = phases.find(phase => ( | ||
| normalizeIdValue(phase.phaseId) === normalizedPhaseId || | ||
| normalizeIdValue(phase.id) === normalizedPhaseId | ||
| )) | ||
|
|
||
| if (!selectedPhase || !selectedPhase.name) { | ||
| return [] | ||
| } | ||
|
|
||
| const normalizedPhaseName = normalizePhaseToken(selectedPhase.name) | ||
| if (!normalizedPhaseName) { | ||
| return [] | ||
| } | ||
|
|
||
| return scorecards.filter(scorecard => ( | ||
| scorecard && | ||
| normalizePhaseToken(scorecard.type) === normalizedPhaseName | ||
| )) | ||
| } | ||
|
|
||
| class ChallengeReviewerField extends Component { | ||
| constructor (props) { | ||
| super(props) | ||
|
|
@@ -602,6 +641,31 @@ class ChallengeReviewerField extends Component { | |
| baseCoefficient: defaultReviewer.baseCoefficient, | ||
| incrementalCoefficient: defaultReviewer.incrementalCoefficient | ||
| }) | ||
|
|
||
| if (updatedReviewers[index] && (updatedReviewers[index].isMemberReview !== false)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| const { metadata = {} } = this.props | ||
| const scorecardsForPhase = getScorecardsForPhase( | ||
| metadata.scorecards || [], | ||
| challenge.phases || [], | ||
| value | ||
| ) | ||
| const currentScorecardId = normalizeIdValue(updatedReviewers[index].scorecardId) | ||
| const hasCurrentScorecard = scorecardsForPhase.some(scorecard => ( | ||
| normalizeIdValue(scorecard.id) === currentScorecardId | ||
| )) | ||
|
|
||
| if (!hasCurrentScorecard) { | ||
| const defaultScorecardId = normalizeIdValue(defaultReviewer && defaultReviewer.scorecardId) | ||
| const hasDefaultScorecard = defaultScorecardId && scorecardsForPhase.some(scorecard => ( | ||
| normalizeIdValue(scorecard.id) === defaultScorecardId | ||
| )) | ||
| const fallbackScorecardId = hasDefaultScorecard | ||
| ? defaultScorecardId | ||
| : normalizeIdValue(scorecardsForPhase[0] && scorecardsForPhase[0].id) | ||
|
|
||
| fieldUpdate.scorecardId = fallbackScorecardId || '' | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (field === 'memberReviewerCount') { | ||
|
|
@@ -661,29 +725,12 @@ class ChallengeReviewerField extends Component { | |
| const { challenge, metadata = {}, readOnly = false } = this.props | ||
| const { scorecards = [], workflows = [] } = metadata | ||
| const validationErrors = challenge.submitTriggered ? this.validateReviewer(reviewer) : {} | ||
| const selectedPhase = challenge.phases.find(p => p.phaseId === reviewer.phaseId) | ||
| const filteredScorecards = getScorecardsForPhase( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| scorecards, | ||
| challenge.phases || [], | ||
| reviewer.phaseId | ||
| ) | ||
| const isDesignChallenge = challenge && challenge.trackId === DES_TRACK_ID | ||
| const normalize = (value) => (value || '') | ||
| .toString() | ||
| .toLowerCase() | ||
| .trim() | ||
| .replace(/\bphase\b$/, '') | ||
| .replace(/[-_\s]/g, '') | ||
|
|
||
| const filteredScorecards = scorecards.filter(item => { | ||
| if (!selectedPhase || !selectedPhase.name || !item || !item.type) { | ||
| return false | ||
| } | ||
|
|
||
| const normalizedType = normalize(item.type) | ||
| const normalizedPhaseName = normalize(selectedPhase.name) | ||
|
|
||
| if (!normalizedType || !normalizedPhaseName) { | ||
| return false | ||
| } | ||
|
|
||
| return normalizedType === normalizedPhaseName | ||
| }) | ||
|
|
||
| return ( | ||
| <div key={`reviewer-${index}`} className={styles.reviewerForm}> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ import { | |
| import PhaseInput from '../../PhaseInput' | ||
| import CheckpointPrizesField from '../CheckpointPrizes-Field' | ||
| import { isBetaMode } from '../../../util/localstorage' | ||
| import WiproAllowedField from '../WiproAllowedField' | ||
|
|
||
| const ChallengeView = ({ | ||
| projectDetail, | ||
|
|
@@ -95,6 +96,7 @@ const ChallengeView = ({ | |
| if (isLoading || _.isEmpty(metadata.challengePhases) || challenge.id !== challengeId) return <Loader /> | ||
| const showTimeline = false // disables the timeline for time being https://github.com/topcoder-platform/challenge-engine-ui/issues/706 | ||
| const isTask = _.get(challenge, 'task.isTask', false) | ||
| const isFunChallenge = challenge.funChallenge === true | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| const phases = _.get(challenge, 'phases', []) | ||
| const showCheckpointPrizes = _.get(challenge, 'timelineTemplateId') === MULTI_ROUND_CHALLENGE_TEMPLATE_ID | ||
| const useDashboardData = _.find(challenge.metadata, { name: 'show_data_dashboard' }) | ||
|
|
@@ -195,6 +197,7 @@ const ChallengeView = ({ | |
| <> | ||
| {dashboardToggle} | ||
| <NDAField beta challenge={challenge} readOnly /> | ||
| <WiproAllowedField challenge={challenge} onUpdateOthers={() => {}} readOnly /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| <div className={cn(styles.row, styles.topRow)}> | ||
| <div className={styles.col}> | ||
| <span><span className={styles.fieldTitle}>Groups:</span> {groups}</span> | ||
|
|
@@ -262,14 +265,24 @@ const ChallengeView = ({ | |
| token={token} | ||
| readOnly | ||
| />} | ||
| <ChallengePrizesField challenge={challenge} readOnly /> | ||
| { | ||
| showCheckpointPrizes && ( | ||
| <CheckpointPrizesField challenge={challenge} readOnly /> | ||
| ) | ||
| } | ||
| <CopilotFeeField challenge={challenge} readOnly /> | ||
| <ChallengeTotalField challenge={challenge} /> | ||
| {isFunChallenge ? ( | ||
| <div className={cn(styles.row, styles.topRow)}> | ||
| <div className={styles.col}> | ||
| <span><span className={styles.fieldTitle}>Fun Challenge:</span> True</span> | ||
| </div> | ||
| </div> | ||
| ) : ( | ||
| <> | ||
| <ChallengePrizesField challenge={challenge} readOnly /> | ||
| { | ||
| showCheckpointPrizes && ( | ||
| <CheckpointPrizesField challenge={challenge} readOnly /> | ||
| ) | ||
| } | ||
| <CopilotFeeField challenge={challenge} readOnly /> | ||
| <ChallengeTotalField challenge={challenge} /> | ||
| </> | ||
| )} | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| @use '../../../styles/includes' as *; | ||
|
|
||
| .row { | ||
| box-sizing: border-box; | ||
| display: flex; | ||
| flex-direction: row; | ||
| margin: 30px 30px 0 30px; | ||
| align-content: space-between; | ||
| justify-content: flex-start; | ||
|
|
||
| .tcCheckbox { | ||
| @include tc-checkbox; | ||
|
|
||
| height: 18px; | ||
| width: 210px; | ||
| margin: 0; | ||
| padding: 0; | ||
| vertical-align: bottom; | ||
| position: relative; | ||
| display: inline-block; | ||
|
|
||
| input[type='checkbox'] { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| display: none; | ||
| } | ||
|
|
||
| label { | ||
| @include roboto-light(); | ||
|
|
||
| line-height: 17px; | ||
| font-weight: 300; | ||
| cursor: pointer; | ||
| position: absolute; | ||
| display: inline-block; | ||
| width: 14px; | ||
| height: 14px; | ||
| top: 0; | ||
| left: 0; | ||
| border: none; | ||
| box-shadow: none; | ||
| background: $tc-gray-30; | ||
| transition: all 0.15s ease-in-out; | ||
|
|
||
| &::after { | ||
| opacity: 0; | ||
| content: ''; | ||
| position: absolute; | ||
| width: 9px; | ||
| height: 5px; | ||
| background: transparent; | ||
| top: 2px; | ||
| left: 2px; | ||
| border-top: none; | ||
| border-right: none; | ||
| transform: rotate(-45deg); | ||
| transition: all 0.15s ease-in-out; | ||
| } | ||
|
|
||
| &:hover::after { | ||
| opacity: 0.3; | ||
| } | ||
|
|
||
| div { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| margin-left: 24px; | ||
| width: 300px; | ||
| } | ||
| } | ||
|
|
||
| input[type='checkbox']:checked ~ label { | ||
| background: $tc-blue-20; | ||
| } | ||
|
|
||
| input[type='checkbox']:checked + label::after { | ||
| border-color: $white; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import React from 'react' | ||
| import PropTypes from 'prop-types' | ||
| import styles from './FunChallengeField.module.scss' | ||
|
|
||
| /** | ||
| * Renders a checkbox to toggle the `funChallenge` flag for Marathon Match challenges. | ||
| * | ||
| * @param {Object} props component props | ||
| * @param {Object} props.challenge challenge data object that may include `funChallenge` | ||
| * @param {Function} props.onUpdateOthers callback used to update top-level challenge fields | ||
| * @param {boolean} props.readOnly when true, renders the control as read-only | ||
| * @returns {import('react').ReactNode} rendered fun challenge checkbox field | ||
| */ | ||
| const FunChallengeField = ({ challenge, onUpdateOthers, readOnly }) => { | ||
| const isFunChallenge = challenge.funChallenge === true | ||
|
|
||
| return ( | ||
| <div className={styles.row}> | ||
| <div className={styles.tcCheckbox}> | ||
| <input | ||
| name='funChallenge' | ||
| type='checkbox' | ||
| id='funChallenge' | ||
| checked={isFunChallenge} | ||
| readOnly={readOnly} | ||
| onChange={() => onUpdateOthers({ field: 'funChallenge', value: !isFunChallenge })} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| /> | ||
| <label htmlFor='funChallenge'> | ||
| <div>Fun Challenge</div> | ||
| <input type='hidden' /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| </label> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| FunChallengeField.defaultProps = { | ||
| readOnly: false | ||
| } | ||
|
|
||
| FunChallengeField.propTypes = { | ||
| challenge: PropTypes.shape().isRequired, | ||
| onUpdateOthers: PropTypes.func.isRequired, | ||
| readOnly: PropTypes.bool | ||
| } | ||
|
|
||
| export default FunChallengeField | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗
correctness]Ensure that all dependent services and clients are updated to use the new v6 endpoint for projects. This change could break functionality if any part of the system still relies on the v5 endpoint.