diff --git a/.circleci/config.yml b/.circleci/config.yml index 0d96a6c9..ab65da7d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -150,7 +150,7 @@ workflows: context : org-global filters: &filters-dev branches: - only: ['develop'] + only: ['develop', 'Oct_2022_Release'] # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/config/constants/development.js b/config/constants/development.js index 2efc33f9..0d3f0d15 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -5,7 +5,7 @@ module.exports = { ACCOUNTS_APP_CONNECTOR_URL: `https://accounts-auth0.${DOMAIN}`, ACCOUNTS_APP_LOGIN_URL: `https://accounts-auth0.${DOMAIN}`, COMMUNITY_APP_URL: `https://www.${DOMAIN}`, - MEMBER_API_URL: `${DEV_API_HOSTNAME}/v4/members`, + MEMBER_API_URL: `${DEV_API_HOSTNAME}/v5/members`, MEMBER_API_V3_URL: `${DEV_API_HOSTNAME}/v3/members`, CHALLENGE_API_URL: `${DEV_API_HOSTNAME}/v5/challenges`, CHALLENGE_TIMELINE_TEMPLATES_URL: `${DEV_API_HOSTNAME}/v5/timeline-templates`, @@ -34,6 +34,7 @@ module.exports = { DS_TRACK_ID: 'c0f5d461-8219-4c14-878a-c3a3f356466d', QA_TRACK_ID: '36e6a8d0-7e1e-4608-a673-64279d99c115', CHALLENGE_TYPE_ID: '927abff4-7af9-4145-8ba1-577c16e64e2e', + MARATHON_TYPE_ID: '929bc408-9cf2-4b3e-ba71-adfbf693046c', SEGMENT_API_KEY: 'QBtLgV8vCiuRX1lDikbMjcoe9aCHkF6n', CREATE_FORUM_TYPE_IDS: ['927abff4-7af9-4145-8ba1-577c16e64e2e', 'dc876fa4-ef2d-4eee-b701-b555fcc6544c', 'ecd58c69-238f-43a4-a4bb-d172719b9f31'], FILE_PICKER_API_KEY: process.env.FILE_PICKER_API_KEY, diff --git a/config/constants/production.js b/config/constants/production.js index 4040c262..d90f7944 100644 --- a/config/constants/production.js +++ b/config/constants/production.js @@ -5,7 +5,7 @@ module.exports = { ACCOUNTS_APP_CONNECTOR_URL: process.env.ACCOUNTS_APP_CONNECTOR_URL || `https://accounts-auth0.${DOMAIN}`, ACCOUNTS_APP_LOGIN_URL: `https://accounts-auth0.${DOMAIN}`, COMMUNITY_APP_URL: `https://www.${DOMAIN}`, - MEMBER_API_URL: `${PROD_API_HOSTNAME}/v4/members`, + MEMBER_API_URL: `${PROD_API_HOSTNAME}/v5/members`, MEMBER_API_V3_URL: `${PROD_API_HOSTNAME}/v3/members`, CHALLENGE_API_URL: `${PROD_API_HOSTNAME}/v5/challenges`, CHALLENGE_TIMELINE_TEMPLATES_URL: `${PROD_API_HOSTNAME}/v5/timeline-templates`, @@ -34,6 +34,7 @@ module.exports = { DS_TRACK_ID: 'c0f5d461-8219-4c14-878a-c3a3f356466d', QA_TRACK_ID: '36e6a8d0-7e1e-4608-a673-64279d99c115', CHALLENGE_TYPE_ID: '927abff4-7af9-4145-8ba1-577c16e64e2e', + MARATHON_TYPE_ID: '929bc408-9cf2-4b3e-ba71-adfbf693046c', SEGMENT_API_KEY: 'QSQAW5BWmZfLoKFNRgNKaqHvLDLJoGqF', CREATE_FORUM_TYPE_IDS: ['927abff4-7af9-4145-8ba1-577c16e64e2e', 'dc876fa4-ef2d-4eee-b701-b555fcc6544c', 'ecd58c69-238f-43a4-a4bb-d172719b9f31'], FILE_PICKER_API_KEY: process.env.FILE_PICKER_API_KEY, diff --git a/src/components/ChallengeEditor/ChallengeView/index.js b/src/components/ChallengeEditor/ChallengeView/index.js index f4bed266..31578261 100644 --- a/src/components/ChallengeEditor/ChallengeView/index.js +++ b/src/components/ChallengeEditor/ChallengeView/index.js @@ -20,7 +20,12 @@ import AssignedMemberField from '../AssignedMember-Field' import { getResourceRoleByName } from '../../../util/tc' import { isBetaMode } from '../../../util/cookie' import { loadGroupDetails } from '../../../actions/challenges' -import { REVIEW_TYPES, CONNECT_APP_URL, PHASE_PRODUCT_CHALLENGE_ID_FIELD } from '../../../config/constants' +import { + REVIEW_TYPES, + CONNECT_APP_URL, + PHASE_PRODUCT_CHALLENGE_ID_FIELD, + DS_TRACK_ID +} from '../../../config/constants' import PhaseInput from '../../PhaseInput' const ChallengeView = ({ @@ -91,6 +96,9 @@ const ChallengeView = ({ 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 phases = _.get(challenge, 'phases', []) + const isDataScience = challenge.trackId === DS_TRACK_ID + const useDashboardData = _.find(challenge.metadata, { name: 'show_data_dashboard' }) + const useDashboard = useDashboardData ? useDashboardData.value : true return (
@@ -133,6 +141,13 @@ const ChallengeView = ({ Challenge Name: {challenge.name}
+ {isDataScience && ( +
+
+ Show data dashboard: {useDashboard ? 'Yes' : 'No'} +
+
+ )} {isTask && } { + types = _.sortBy(types, ['name']) return ( <>
diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index a64eb95b..84302707 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -20,11 +20,14 @@ import { MESSAGE, COMMUNITY_APP_URL, DES_TRACK_ID, + DEV_TRACK_ID, CHALLENGE_TYPE_ID, + MARATHON_TYPE_ID, REVIEW_TYPES, MILESTONE_STATUS, PHASE_PRODUCT_CHALLENGE_ID_FIELD, - QA_TRACK_ID + QA_TRACK_ID, + DS_TRACK_ID } from '../../config/constants' import { PrimaryButton, OutlineButton } from '../Buttons' import TrackField from './Track-Field' @@ -594,6 +597,8 @@ class ChallengeEditor extends Component { submissionLimit.count = '' } existingMetadata.value = JSON.stringify(submissionLimit) + } else if (existingMetadata.name === 'show_data_dashboard') { + existingMetadata.value = Boolean(value) } else { existingMetadata.value = `${value}` } @@ -945,17 +950,14 @@ class ChallengeEditor extends Component { async createNewChallenge () { if (!this.props.isNew) return const { metadata, createChallenge, projectDetail } = this.props - const { showDesignChallengeWarningModel, challenge: { name, trackId, typeId, milestoneId } } = this.state + const { challenge: { name, trackId, typeId, milestoneId, challengeType, metadata: challengeMetadata } } = this.state const { timelineTemplates } = metadata const isDesignChallenge = trackId === DES_TRACK_ID + const isDataScience = trackId === DS_TRACK_ID const isChallengeType = typeId === CHALLENGE_TYPE_ID - - if (!showDesignChallengeWarningModel && isDesignChallenge && isChallengeType) { - this.setState({ - showDesignChallengeWarningModel: true - }) - return - } + const isDevChallenge = trackId === DEV_TRACK_ID + const isMM = typeId === MARATHON_TYPE_ID + const showDashBoard = (isDataScience && isChallengeType) || (isDevChallenge && isMM) // indicate that creating process has started this.setState({ isSaving: true }) @@ -967,6 +969,10 @@ class ChallengeEditor extends Component { const defaultTemplate = avlTemplates && avlTemplates.length > 0 ? avlTemplates[0] : STD_DEV_TIMELINE_TEMPLATE const isTask = _.find(metadata.challengeTypes, { id: typeId, isTask: true }) const tags = trackId === QA_TRACK_ID ? ['QA'] : [] + if (challengeType) { + tags.push(challengeType) + } + let timelineTemplateId = defaultTemplate.id const newChallenge = { status: 'New', @@ -979,7 +985,7 @@ class ChallengeEditor extends Component { reviewType: isTask || isDesignChallenge ? REVIEW_TYPES.INTERNAL : REVIEW_TYPES.COMMUNITY }, descriptionFormat: 'markdown', - timelineTemplateId: defaultTemplate.id, + timelineTemplateId, terms: [{ id: DEFAULT_TERM_UUID, roleId: SUBMITTER_ROLE_UUID }], groups: [], milestoneId, @@ -1006,6 +1012,16 @@ class ChallengeEditor extends Component { newChallenge.discussions = discussions } } + if (showDashBoard) { + if (!newChallenge.metadata) { + newChallenge.metadata = [] + } + let useDashboard = _.find(challengeMetadata, { name: 'show_data_dashboard' }) + if (useDashboard === undefined) { + useDashboard = { name: 'show_data_dashboard', value: true } + } + newChallenge.metadata.push(useDashboard) + } try { const action = await createChallenge(newChallenge, projectDetail.id) if (isTask) { @@ -1544,6 +1560,13 @@ class ChallengeEditor extends Component { const currentChallengeId = this.getCurrentChallengeId() const showTimeline = false // disables the timeline for time being https://github.com/topcoder-platform/challenge-engine-ui/issues/706 const copilotResources = metadata.members || challengeResources + const isDevChallenge = challenge.trackId === DEV_TRACK_ID + const isMM = challenge.typeId === MARATHON_TYPE_ID + const isChallengeType = challenge.typeId === CHALLENGE_TYPE_ID + const showDashBoard = (challenge.trackId === DS_TRACK_ID && isChallengeType) || (isDevChallenge && isMM) + const useDashboardData = _.find(challenge.metadata, { name: 'show_data_dashboard' }) + const useDashboard = useDashboardData ? useDashboardData.value : true + const challengeForm = isNew ? (
@@ -1551,6 +1574,24 @@ class ChallengeEditor extends Component { + { + showDashBoard && ( +
+
+ +
+
+ this.onUpdateMetadata('show_data_dashboard', e.target.checked)} + /> +
+
+ ) + } {projectDetail.version === 'v4' && } {useTask && ()}
@@ -1584,6 +1625,24 @@ class ChallengeEditor extends Component { + { + showDashBoard && ( +
+
+ +
+
+ this.onUpdateMetadata('show_data_dashboard', e.target.checked)} + /> +
+
+ ) + } {isTask && ( {!isBillingAccountLoading && !isBillingAccountLoadingFailed && !isBillingAccountExpired && (
- Billing Account: {activeProject.status}   Start Date: {billingStartDate}   End Date: {billingEndDate} + Billing Account: ACTIVE   Start Date: {billingStartDate}   End Date: {billingEndDate}
)} {!isBillingAccountLoading && !isBillingAccountLoadingFailed && isBillingAccountExpired && ( diff --git a/src/components/SelectUserAutocomplete/index.js b/src/components/SelectUserAutocomplete/index.js index 3217718f..7c87f729 100644 --- a/src/components/SelectUserAutocomplete/index.js +++ b/src/components/SelectUserAutocomplete/index.js @@ -7,7 +7,7 @@ import React, { useState, useCallback } from 'react' import PropTypes from 'prop-types' import Select from '../Select' -import { suggestProfiles } from '../../services/user' +import { suggestProfilesV5, fetchProfileV5 } from '../../services/user' import _ from 'lodash' import { AUTOCOMPLETE_MIN_LENGTH, AUTOCOMPLETE_DEBOUNCE_TIME_MS } from '../../config/constants' @@ -27,13 +27,17 @@ export default function SelectUserAutocomplete (props) { return } - suggestProfiles(inputValue).then((suggestions) => { - const suggestedOptions = suggestions.map((user) => ({ - label: user.handle, - value: user.userId.toString() - })) - setOptions(suggestedOptions) - }) + Promise.all([suggestProfilesV5(inputValue), fetchProfileV5(inputValue)]).then( + ([suggestions, user]) => { + const suggestedOptions = suggestions.map((u) => ({ + label: u.handle, + value: u.userId.toString() + })) + if (user && !_.find(suggestions, u => u.userId === user.userId)) { + suggestedOptions.push({ label: user.handle, value: user.userId.toString() }) + } + setOptions(suggestedOptions) + }) }, AUTOCOMPLETE_DEBOUNCE_TIME_MS), []) // debounce, to reduce API calling rate return ( diff --git a/src/config/constants.js b/src/config/constants.js index b05d9e10..73da22e9 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -17,6 +17,7 @@ export const { DS_TRACK_ID, QA_TRACK_ID, CHALLENGE_TYPE_ID, + MARATHON_TYPE_ID, SEGMENT_API_KEY } = process.env export const CREATE_FORUM_TYPE_IDS = typeof process.env.CREATE_FORUM_TYPE_IDS === 'string' ? process.env.CREATE_FORUM_TYPE_IDS.split(',') : process.env.CREATE_FORUM_TYPE_IDS diff --git a/src/services/user.js b/src/services/user.js index 8a42a9da..fc7bd888 100644 --- a/src/services/user.js +++ b/src/services/user.js @@ -1,6 +1,6 @@ import _ from 'lodash' import { axiosInstance } from './axiosWithAuth' -const { MEMBER_API_V3_URL } = process.env +const { MEMBER_API_URL, MEMBER_API_V3_URL } = process.env /** * Api request for fetching user profile @@ -11,6 +11,16 @@ export async function fetchProfile (handle) { return _.get(response, 'data.result.content') } +/** + * Api request for fetching user profile v5 + * @returns {Promise<*>} + */ +export async function fetchProfileV5 (handle) { + const response = await axiosInstance.get(`${MEMBER_API_URL}?handle=${handle}`) + const data = _.get(response, 'data') + return data.length ? data[0] : undefined +} + /** * Api request for fetching user profile * @returns {Promise<*>} @@ -49,3 +59,12 @@ export async function suggestProfiles (partialHandle) { const response = await axiosInstance.get(`${MEMBER_API_V3_URL}/_suggest/${encodeURIComponent(partialHandle)}`) return _.get(response, 'data.result.content') } + +/** + * Api request for finding (suggesting) users by the part of the handle + * @returns {Promise<*>} + */ +export async function suggestProfilesV5 (partialHandle) { + const response = await axiosInstance.get(`${MEMBER_API_URL}/autocomplete?term=${encodeURIComponent(partialHandle)}`) + return _.get(response, 'data') +}