From a4d6360273fc252af74137f1bdb7d0d5bc922c1c Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Fri, 19 Feb 2021 01:46:36 +0200 Subject: [PATCH 1/8] Add an option to select a timeline template --- .../TimelineTemplate-Field.module.scss | 55 ++++++++ .../TimelineTemplate-Field/index.js | 120 ++++++++++++++++++ src/components/ChallengeEditor/index.js | 2 + 3 files changed, 177 insertions(+) create mode 100644 src/components/ChallengeEditor/TimelineTemplate-Field/TimelineTemplate-Field.module.scss create mode 100644 src/components/ChallengeEditor/TimelineTemplate-Field/index.js diff --git a/src/components/ChallengeEditor/TimelineTemplate-Field/TimelineTemplate-Field.module.scss b/src/components/ChallengeEditor/TimelineTemplate-Field/TimelineTemplate-Field.module.scss new file mode 100644 index 00000000..f6707a0c --- /dev/null +++ b/src/components/ChallengeEditor/TimelineTemplate-Field/TimelineTemplate-Field.module.scss @@ -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; + } + } +} diff --git a/src/components/ChallengeEditor/TimelineTemplate-Field/index.js b/src/components/ChallengeEditor/TimelineTemplate-Field/index.js new file mode 100644 index 00000000..7dc13ce9 --- /dev/null +++ b/src/components/ChallengeEditor/TimelineTemplate-Field/index.js @@ -0,0 +1,120 @@ +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 () { + this.checkData() + } + + shouldComponentUpdate (nextProps) { + const { onUpdateSelect, challengeTimelines, timelineTemplates, challenge } = nextProps + 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(onUpdateSelect, challengeTimelines, timelineTemplates, challenge) + } + return true + } + + loadSelectedOption (validOptions, value) { + if (!value) return + const { timelineTemplates, challenge } = this.props + const selectedOption = {} + const selectedTemplate = _.find(timelineTemplates, t => t.id === (value)) + if (!selectedTemplate) return + selectedOption.label = selectedTemplate.name + selectedOption.value = selectedTemplate.id + this.setState({ + validOptions, + matchString: `${challenge.typeId}-${challenge.trackId}-${value}`, + selectedOption + }) + } + + checkData () { + const { onUpdateSelect, challengeTimelines, timelineTemplates, challenge } = this.props + 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 (challenge.timelineTemplateId) { + if (!_.includes(validOptions.map(o => o.id), challenge.timelineTemplateId)) { + onUpdateSelect(defaultValue || '', false, 'timelineTemplateId') + return this.loadSelectedOption(validOptions, defaultValue) + } + } else if (defaultValue) { + onUpdateSelect(defaultValue, false, 'timelineTemplateId') + 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.challenge.timelineTemplateId) { + 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 error = this.getErrorMessage() + return ( + <> +
+
+ +
+
+ this.props.onUpdateSelect(e.value, false, 'timelineTemplateId')} - isDisabled={this.state.validOptions.length === 0} + isDisabled={this.state.validOptions.length === 0 || this.props.readOnly} />
@@ -107,14 +107,16 @@ class TimelineTemplateField extends Component { TimelineTemplateField.defaultProps = { challengeTimelines: [], - timelineTemplates: [] + timelineTemplates: [], + readOnly: false } TimelineTemplateField.propTypes = { challengeTimelines: PropTypes.arrayOf(PropTypes.shape()).isRequired, timelineTemplates: PropTypes.arrayOf(PropTypes.shape()).isRequired, challenge: PropTypes.shape().isRequired, - onUpdateSelect: PropTypes.func.isRequired + onUpdateSelect: PropTypes.func.isRequired, + readOnly: PropTypes.bool } export default TimelineTemplateField From 14a44696281f45d10c24c7f44c996d809297d7d5 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 4 Mar 2021 00:26:38 +0200 Subject: [PATCH 4/8] chore: deploy branch to DEV --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a204949..7b066742 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,6 +81,7 @@ workflows: branches: only: - develop + - feature/timeline-template # Production builds are exectuted only on tagged commits to the # master branch. From 9ee3ecac55b397e614137a4b32b1c91aa3d6a6b3 Mon Sep 17 00:00:00 2001 From: James Cori Date: Wed, 3 Mar 2021 17:46:44 -0500 Subject: [PATCH 5/8] Removing Default Filter --- src/services/challenges.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/challenges.js b/src/services/challenges.js index 321b6ee1..3f77e8de 100644 --- a/src/services/challenges.js +++ b/src/services/challenges.js @@ -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', []) } From ef7bf8d57e6c370365dbdde50a06c71285cb2e16 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 4 Mar 2021 01:58:42 +0200 Subject: [PATCH 6/8] fixes --- .../TimelineTemplate-Field/index.js | 37 ++++++++++--------- src/components/ChallengeEditor/index.js | 1 + 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/components/ChallengeEditor/TimelineTemplate-Field/index.js b/src/components/ChallengeEditor/TimelineTemplate-Field/index.js index 9b52867a..7be5e3a4 100644 --- a/src/components/ChallengeEditor/TimelineTemplate-Field/index.js +++ b/src/components/ChallengeEditor/TimelineTemplate-Field/index.js @@ -19,24 +19,20 @@ class TimelineTemplateField extends Component { } componentDidMount () { - this.checkData() + const { challengeTimelines, timelineTemplates, challenge } = this.props + this.checkData(challengeTimelines, timelineTemplates, challenge) } - shouldComponentUpdate (nextProps) { - const { onUpdateSelect, challengeTimelines, timelineTemplates, challenge } = nextProps - 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(onUpdateSelect, challengeTimelines, timelineTemplates, challenge) - } - return true + componentWillUnmount () { + this.props.onUpdateSelect(this.state.selectedOption.value, false, 'timelineTemplateId') } loadSelectedOption (validOptions, value) { - if (!value) return + // if (!value) return const { timelineTemplates, challenge } = this.props const selectedOption = {} const selectedTemplate = _.find(timelineTemplates, t => t.id === (value)) - if (!selectedTemplate) return + // if (!selectedTemplate) return selectedOption.label = selectedTemplate.name selectedOption.value = selectedTemplate.id this.setState({ @@ -46,19 +42,18 @@ class TimelineTemplateField extends Component { }) } - checkData () { - const { onUpdateSelect, challengeTimelines, timelineTemplates, challenge } = this.props + checkData (challengeTimelines, timelineTemplates, challenge) { 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 (challenge.timelineTemplateId) { - if (!_.includes(validOptions.map(o => o.id), challenge.timelineTemplateId)) { - onUpdateSelect(defaultValue || '', false, 'timelineTemplateId') - return this.loadSelectedOption(validOptions, defaultValue) + if (!_.includes(_.map(validOptions, o => o.id), challenge.timelineTemplateId)) { + this.loadSelectedOption(validOptions, defaultValue) + } else { + this.loadSelectedOption(validOptions, challenge.timelineTemplateId) } } else if (defaultValue) { - onUpdateSelect(defaultValue, false, 'timelineTemplateId') return this.loadSelectedOption(validOptions, defaultValue) } } @@ -75,6 +70,11 @@ class TimelineTemplateField extends Component { } render () { + const { challengeTimelines, timelineTemplates, challenge } = 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) + } const error = this.getErrorMessage() return ( <> @@ -89,7 +89,10 @@ class TimelineTemplateField extends Component { options={this.state.validOptions.map(type => ({ label: type.name, value: type.id }))} placeholder='Timeline Template' isClearable={false} - onChange={(e) => this.props.onUpdateSelect(e.value, false, 'timelineTemplateId')} + onChange={(e) => { + this.props.onUpdateSelect(e.value, false, 'timelineTemplateId') + this.loadSelectedOption(this.state.validOptions, e.value) + }} isDisabled={this.state.validOptions.length === 0 || this.props.readOnly} /> diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index f337fa41..58437080 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -1434,6 +1434,7 @@ class ChallengeEditor extends Component { {isBetaMode() && ( )} + )} {!isTask && ( From f8f223ef30f569e95b346f76e47df6679ca1b915 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Fri, 5 Mar 2021 00:58:54 +0200 Subject: [PATCH 7/8] fix issues --- .../TimelineTemplate-Field/index.js | 31 ++++++++++--------- src/components/ChallengeEditor/index.js | 23 +++++++++++--- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/components/ChallengeEditor/TimelineTemplate-Field/index.js b/src/components/ChallengeEditor/TimelineTemplate-Field/index.js index 7be5e3a4..242f4e07 100644 --- a/src/components/ChallengeEditor/TimelineTemplate-Field/index.js +++ b/src/components/ChallengeEditor/TimelineTemplate-Field/index.js @@ -19,20 +19,20 @@ class TimelineTemplateField extends Component { } componentDidMount () { - const { challengeTimelines, timelineTemplates, challenge } = this.props - this.checkData(challengeTimelines, timelineTemplates, challenge) + const { challengeTimelines, timelineTemplates, challenge, currentTemplate } = this.props + this.checkData(challengeTimelines, timelineTemplates, challenge, currentTemplate) } componentWillUnmount () { - this.props.onUpdateSelect(this.state.selectedOption.value, false, 'timelineTemplateId') + this.props.onUpdateSelect(this.state.selectedOption.value) } loadSelectedOption (validOptions, value) { - // if (!value) return const { timelineTemplates, challenge } = this.props const selectedOption = {} const selectedTemplate = _.find(timelineTemplates, t => t.id === (value)) - // if (!selectedTemplate) return + this.props.onUpdateSelect(selectedTemplate) + selectedOption.label = selectedTemplate.name selectedOption.value = selectedTemplate.id this.setState({ @@ -42,16 +42,16 @@ class TimelineTemplateField extends Component { }) } - checkData (challengeTimelines, timelineTemplates, challenge) { + 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 (challenge.timelineTemplateId) { - if (!_.includes(_.map(validOptions, o => o.id), challenge.timelineTemplateId)) { + if (currentTemplate && currentTemplate.id) { + if (!_.includes(_.map(validOptions, o => o.id), currentTemplate.id)) { this.loadSelectedOption(validOptions, defaultValue) } else { - this.loadSelectedOption(validOptions, challenge.timelineTemplateId) + this.loadSelectedOption(validOptions, currentTemplate.id) } } else if (defaultValue) { return this.loadSelectedOption(validOptions, defaultValue) @@ -61,7 +61,7 @@ class TimelineTemplateField extends Component { 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.challenge.timelineTemplateId) { + } 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' @@ -70,10 +70,10 @@ class TimelineTemplateField extends Component { } render () { - const { challengeTimelines, timelineTemplates, challenge } = this.props + 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) + this.checkData(challengeTimelines, timelineTemplates, challenge, currentTemplate) } const error = this.getErrorMessage() return ( @@ -90,7 +90,6 @@ class TimelineTemplateField extends Component { placeholder='Timeline Template' isClearable={false} onChange={(e) => { - this.props.onUpdateSelect(e.value, false, 'timelineTemplateId') this.loadSelectedOption(this.state.validOptions, e.value) }} isDisabled={this.state.validOptions.length === 0 || this.props.readOnly} @@ -111,7 +110,8 @@ class TimelineTemplateField extends Component { TimelineTemplateField.defaultProps = { challengeTimelines: [], timelineTemplates: [], - readOnly: false + readOnly: false, + currentTemplate: null } TimelineTemplateField.propTypes = { @@ -119,7 +119,8 @@ TimelineTemplateField.propTypes = { timelineTemplates: PropTypes.arrayOf(PropTypes.shape()).isRequired, challenge: PropTypes.shape().isRequired, onUpdateSelect: PropTypes.func.isRequired, - readOnly: PropTypes.bool + readOnly: PropTypes.bool, + currentTemplate: PropTypes.shape() } export default TimelineTemplateField diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 58437080..99f9d31f 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -834,7 +834,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', @@ -847,7 +847,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() @@ -1136,8 +1136,9 @@ 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.isDefault) // filter and return timeline templates that are available for this challenge type - return _.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1) + return _.map(_.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1), tt => tt.id === defaultChallengeTimeline.timelineTemplateId ? { ...tt, isDefault: true } : tt) } render () { @@ -1354,7 +1355,13 @@ class ChallengeEditor extends Component {
- +
{ errorContainer } @@ -1434,7 +1441,13 @@ class ChallengeEditor extends Component { {isBetaMode() && ( )} - + )} {!isTask && ( From a382c05265ff4acb368e9e5d3fa45e4ffdb40141 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 10 Mar 2021 17:42:28 +0530 Subject: [PATCH 8/8] fix: git#1097-The Continue Setup Button disable: Unable to create the challenge, task, and first2finish due to this --- src/components/ChallengeEditor/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index e7abb91c..95d5421f 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -1144,9 +1144,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.isDefault) + 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 _.map(_.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1), tt => tt.id === defaultChallengeTimeline.timelineTemplateId ? { ...tt, isDefault: true } : tt) + const avlTemplates = _.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1) + return _.map(avlTemplates, tt => tt.id === defaultChallengeTimeline.timelineTemplateId ? { ...tt, isDefault: true } : tt) } render () {