Skip to content

Commit

Permalink
Merge pull request #1117 from topcoder-platform/develop
Browse files Browse the repository at this point in the history
Prod release - Timeline Template
  • Loading branch information
vikasrohit authored Mar 11, 2021
2 parents 15ee107 + 942f682 commit bc9fadd
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 15 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ workflows:
branches:
only:
- develop
- feature/timeline-template

# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@
}
}

@-moz-document url-prefix() {
.challengeName {
&::-moz-placeholder { /* Mozilla Firefox 19+ */
line-height: 38px;
}
&::-webkit-input-placeholder { /* Webkit */
line-height: 38px;
}
&:-ms-input-placeholder { /* IE */
line-height: 38px;
}
.challengeName {
&::-moz-placeholder { /* Mozilla Firefox 19+ */
line-height: 38px;
color: $tc-gray-80;
}
&::-webkit-input-placeholder { /* Webkit */
line-height: 38px;
color: $tc-gray-80;
}
&:-ms-input-placeholder { /* IE */
line-height: 38px;
color: $tc-gray-80;
}
}

8 changes: 8 additions & 0 deletions src/components/ChallengeEditor/ChallengeView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { isBetaMode } from '../../../util/cookie'
import { loadGroupDetails } from '../../../actions/challenges'
import Tooltip from '../../Tooltip'
import { MESSAGE, REVIEW_TYPES } from '../../../config/constants'
import TimelineTemplateField from '../TimelineTemplate-Field'

const ChallengeView = ({
projectDetail,
Expand Down Expand Up @@ -200,6 +201,13 @@ const ChallengeView = ({
{isBetaMode() && (
<UseSchedulingAPIField challenge={challenge} readOnly />
)}
<TimelineTemplateField
challengeTimelines={metadata.challengeTimelines}
timelineTemplates={metadata.timelineTemplates}
challenge={challenge}
onUpdateSelect={() => {}}
readOnly
/>
</>
)}
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@import "../../../styles/includes";

.row {
box-sizing: border-box;
display: flex;
flex-direction: row;
margin: 30px 30px 0 30px;
align-content: space-between;
justify-content: flex-start;

.field {
@include upto-sm {
display: block;
padding-bottom: 10px;
}

label {
@include roboto-bold();

font-size: 16px;
line-height: 19px;
font-weight: 500;
color: $tc-gray-80;
}

&.col1 {
max-width: 185px;
min-width: 185px;
margin-right: 14px;
white-space: nowrap;
display: flex;
align-items: center;
flex-grow: 1;

span {
color: $tc-red;
}
}

&.col2.error {
color: $tc-red;
margin-top: -25px;
}
&.col2 {
align-self: flex-end;
width: 80%;
margin-bottom: auto;
margin-top: auto;
display: flex;
flex-direction: row;
max-width: 600px;
min-width: 600px;
}
}
}
126 changes: 126 additions & 0 deletions src/components/ChallengeEditor/TimelineTemplate-Field/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import _ from 'lodash'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Select from '../../Select'
import cn from 'classnames'
import styles from './TimelineTemplate-Field.module.scss'

class TimelineTemplateField extends Component {
constructor (props) {
super(props)
this.state = {
validOptions: [],
selectedOption: {}
}

this.checkData = this.checkData.bind(this)
this.loadSelectedOption = this.loadSelectedOption.bind(this)
this.getErrorMessage = this.getErrorMessage.bind(this)
}

componentDidMount () {
const { challengeTimelines, timelineTemplates, challenge, currentTemplate } = this.props
this.checkData(challengeTimelines, timelineTemplates, challenge, currentTemplate)
}

componentWillUnmount () {
this.props.onUpdateSelect(this.state.selectedOption.value)
}

loadSelectedOption (validOptions, value) {
const { timelineTemplates, challenge } = this.props
const selectedOption = {}
const selectedTemplate = _.find(timelineTemplates, t => t.id === (value))
this.props.onUpdateSelect(selectedTemplate)

selectedOption.label = selectedTemplate.name
selectedOption.value = selectedTemplate.id
this.setState({
validOptions,
matchString: `${challenge.typeId}-${challenge.trackId}-${value}`,
selectedOption
})
}

checkData (challengeTimelines, timelineTemplates, challenge, currentTemplate) {
const availableTemplates = _.filter(challengeTimelines, ct => ct.typeId === challenge.typeId && ct.trackId === challenge.trackId)
const availableTemplateIds = availableTemplates.map(tt => tt.timelineTemplateId)
const validOptions = _.filter(timelineTemplates, t => _.includes(availableTemplateIds, t.id))
const defaultValue = _.get(_.find(availableTemplates, t => t.isDefault), 'timelineTemplateId')
if (currentTemplate && currentTemplate.id) {
if (!_.includes(_.map(validOptions, o => o.id), currentTemplate.id)) {
this.loadSelectedOption(validOptions, defaultValue)
} else {
this.loadSelectedOption(validOptions, currentTemplate.id)
}
} else if (defaultValue) {
return this.loadSelectedOption(validOptions, defaultValue)
}
}

getErrorMessage () {
if (!this.props.challenge.typeId || !this.props.challenge.trackId) {
return 'Please select a work type and format to enable this field'
} else if (this.props.challenge.submitTriggered && !this.props.currentTemplate) {
return 'Timeline template is required field'
} else if (this.state.validOptions.length === 0) {
return 'Sorry, there are no available timeline templates for the options you have selected'
}
return null
}

render () {
const { challengeTimelines, timelineTemplates, challenge, currentTemplate } = this.props
const hasSelectedTypeAndTrack = !_.isEmpty(challenge.typeId) && !_.isEmpty(challenge.trackId)
if ((hasSelectedTypeAndTrack && this.state.validOptions.length === 0) || this.state.matchString !== `${challenge.typeId}-${challenge.trackId}-${this.state.selectedOption.value}`) {
this.checkData(challengeTimelines, timelineTemplates, challenge, currentTemplate)
}
const error = this.getErrorMessage()
return (
<>
<div className={styles.row}>
<div className={cn(styles.field, styles.col1)}>
<label htmlFor='type'>Timeline Template {!this.props.readOnly && <span>*</span>} :</label>
</div>
<div className={cn(styles.field, styles.col2, { [styles.disabled]: this.state.validOptions.length === 0 })}>
<Select
value={this.state.selectedOption}
name='timelineTemplateId'
options={this.state.validOptions.map(type => ({ label: type.name, value: type.id }))}
placeholder='Timeline Template'
isClearable={false}
onChange={(e) => {
this.loadSelectedOption(this.state.validOptions, e.value)
}}
isDisabled={this.state.validOptions.length === 0 || this.props.readOnly}
/>
</div>
</div>
{ error && <div className={styles.row}>
<div className={cn(styles.field, styles.col1)} />
<div className={cn(styles.field, styles.col2, styles.error)}>
{error}
</div>
</div> }
</>
)
}
}

TimelineTemplateField.defaultProps = {
challengeTimelines: [],
timelineTemplates: [],
readOnly: false,
currentTemplate: null
}

TimelineTemplateField.propTypes = {
challengeTimelines: PropTypes.arrayOf(PropTypes.shape()).isRequired,
timelineTemplates: PropTypes.arrayOf(PropTypes.shape()).isRequired,
challenge: PropTypes.shape().isRequired,
onUpdateSelect: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
currentTemplate: PropTypes.shape()
}

export default TimelineTemplateField
23 changes: 20 additions & 3 deletions src/components/ChallengeEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import Tooltip from '../Tooltip'
import UseSchedulingAPIField from './UseSchedulingAPIField'
import { getResourceRoleByName } from '../../util/tc'
import { isBetaMode } from '../../util/cookie'
import TimelineTemplateField from './TimelineTemplate-Field'

const theme = {
container: styles.modalContainer
Expand Down Expand Up @@ -838,7 +839,7 @@ class ChallengeEditor extends Component {
const STD_DEV_TIMELINE_TEMPLATE = _.find(timelineTemplates, { name: 'Standard Development' })
const avlTemplates = this.getAvailableTimelineTemplates()
// chooses first available timeline template or fallback template for the new challenge
const defaultTemplate = avlTemplates && avlTemplates.length > 0 ? avlTemplates[0] : STD_DEV_TIMELINE_TEMPLATE
const defaultTemplate = _.find(avlTemplates || [], t => t.isDefault) || STD_DEV_TIMELINE_TEMPLATE
const isTask = _.find(metadata.challengeTypes, { id: typeId, isTask: true })
const newChallenge = {
status: 'New',
Expand All @@ -851,7 +852,7 @@ class ChallengeEditor extends Component {
reviewType: isTask || isDesignChallenge ? REVIEW_TYPES.INTERNAL : REVIEW_TYPES.COMMUNITY
},
descriptionFormat: 'markdown',
timelineTemplateId: defaultTemplate.id,
timelineTemplateId: _.get(this.getCurrentTemplate(), 'id', defaultTemplate.id),
terms: [{ id: DEFAULT_TERM_UUID, roleId: SUBMITTER_ROLE_UUID }],
groups: []
// prizeSets: this.getDefaultPrizeSets()
Expand Down Expand Up @@ -1140,8 +1141,10 @@ class ChallengeEditor extends Component {

// all timeline template ids available for the challenge type
const availableTemplateIds = _.filter(challengeTimelines, ct => ct.typeId === challenge.typeId && ct.trackId === challenge.trackId).map(tt => tt.timelineTemplateId)
const defaultChallengeTimeline = _.find(challengeTimelines, ct => ct.typeId === challenge.typeId && ct.trackId === challenge.trackId && ct.isDefault)
// filter and return timeline templates that are available for this challenge type
return _.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1)
const avlTemplates = _.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1)
return _.map(avlTemplates, tt => tt.id === defaultChallengeTimeline.timelineTemplateId ? { ...tt, isDefault: true } : tt)
}

render () {
Expand Down Expand Up @@ -1360,6 +1363,13 @@ class ChallengeEditor extends Component {
<div className={styles.newFormContainer}>
<TrackField tracks={metadata.challengeTracks} challenge={challenge} onUpdateOthers={this.onUpdateOthers} />
<TypeField types={metadata.challengeTypes} onUpdateSelect={this.onUpdateSelect} challenge={challenge} />
<TimelineTemplateField
currentTemplate={this.state.currentTemplate}
challengeTimelines={metadata.challengeTimelines}
timelineTemplates={metadata.timelineTemplates}
challenge={challenge}
onUpdateSelect={this.resetPhase}
/>
<ChallengeNameField challenge={challenge} onUpdateInput={this.onUpdateInput} />
</div>
{ errorContainer }
Expand Down Expand Up @@ -1437,6 +1447,13 @@ class ChallengeEditor extends Component {
{isBetaMode() && (
<UseSchedulingAPIField challenge={challenge} toggleUseSchedulingAPI={this.toggleUseSchedulingAPI} />
)}
<TimelineTemplateField
challengeTimelines={metadata.challengeTimelines}
timelineTemplates={metadata.timelineTemplates}
challenge={challenge}
currentTemplate={this.state.currentTemplate}
onUpdateSelect={this.resetPhase}
/>
</React.Fragment>
)}
{!isTask && (
Expand Down
2 changes: 1 addition & 1 deletion src/services/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export async function fetchTimelineTemplates () {
* @returns {Promise<*>}
*/
export async function fetchChallengeTimelines () {
const response = await axiosInstance.get(`${CHALLENGE_TIMELINES_URL}?isDefault=true&page=1&perPage=100`)
const response = await axiosInstance.get(`${CHALLENGE_TIMELINES_URL}?page=1&perPage=100`)
return _.get(response, 'data', [])
}

Expand Down

0 comments on commit bc9fadd

Please sign in to comment.