From 7cad0755d770384b9cf8851eacb6ab26a2721b79 Mon Sep 17 00:00:00 2001 From: Gloria Cheung Date: Thu, 27 Jun 2019 11:26:58 -0700 Subject: [PATCH 1/3] Issue #1781 - Update 'Save' button behavior on view-only pages (Case-Control, Proband Individual, and Experimental Data). --- .../components/experimental_curation.js | 9 +-- .../components/score/case_control_score.js | 30 ++++++++- .../components/score/experimental_score.js | 63 ++++++++++++++----- .../components/score/individual_score.js | 44 ++++++++++--- .../scss/clincoded/modules/_curator.scss | 4 +- 5 files changed, 116 insertions(+), 34 deletions(-) diff --git a/src/clincoded/static/components/experimental_curation.js b/src/clincoded/static/components/experimental_curation.js index 2d6657528..a8c0bae18 100644 --- a/src/clincoded/static/components/experimental_curation.js +++ b/src/clincoded/static/components/experimental_curation.js @@ -2551,7 +2551,8 @@ const ExperimentalViewer = createReactClass({ userScoreObj: {}, // Logged-in user's score object submitBusy: false, // True while form is submitting experimentalEvidenceType: null, - formError: false + scoreError: false, + scoreErrorMsg: '' }; }, @@ -2559,7 +2560,7 @@ const ExperimentalViewer = createReactClass({ handleUserScoreObj: function(newUserScoreObj) { this.setState({userScoreObj: newUserScoreObj}, () => { if (!newUserScoreObj.hasOwnProperty('score') || (newUserScoreObj.hasOwnProperty('score') && newUserScoreObj.score !== false && newUserScoreObj.scoreExplanation)) { - this.setState({formError: false}); + this.setState({scoreError: false, scoreErrorMsg: ''}); } }); }, @@ -2582,7 +2583,7 @@ const ExperimentalViewer = createReactClass({ if (Object.keys(newUserScoreObj).length) { if(newUserScoreObj.hasOwnProperty('score') && newUserScoreObj.score !== false && !newUserScoreObj.scoreExplanation) { - this.setState({formError: true}); + this.setState({scoreError: true, scoreErrorMsg: 'A reason is required for the changed score.'}); return false; } this.setState({submitBusy: true}); @@ -3268,7 +3269,7 @@ const ExperimentalViewer = createReactClass({ {isEvidenceScored || (!isEvidenceScored && affiliation && affiliatedExperimental) || (!isEvidenceScored && !affiliation && userExperimental) ? + scoreError={this.state.scoreError} scoreErrorMsg={this.state.scoreErrorMsg} affiliation={affiliation} /> : null} {!isEvidenceScored && ((affiliation && !affiliatedExperimental) || (!affiliation && !userExperimental)) ?
diff --git a/src/clincoded/static/components/score/case_control_score.js b/src/clincoded/static/components/score/case_control_score.js index 76c6d698a..67c4c99fb 100644 --- a/src/clincoded/static/components/score/case_control_score.js +++ b/src/clincoded/static/components/score/case_control_score.js @@ -18,6 +18,8 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ handleUserScoreObj: PropTypes.func, // Function to call create/update score object scoreSubmit: PropTypes.func, // Function to call when Save button is clicked; This prop's existence makes the Save button exist submitBusy: PropTypes.bool, // TRUE while the form submit is running + scoreError: PropTypes.bool, // TRUE if score selection is not changed + scoreErrorMsg: PropTypes.string, // Text string in response to the type of score error affiliation: PropTypes.object, // Affiliation object passed from parent isDisabled: PropTypes.bool // Boolean for the state (disabled or not) of the input field }, @@ -28,6 +30,9 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ modifiedScore: null, // Score that is selected by curator userScoreUuid: null, // Pre-existing logged-in user's score uuuid submitBusy: false, // TRUE while form is submitting + userOrigScore: null, // User originally selected score + scoreError: this.props.scoreError, // TRUE if score selection is not changed + scoreErrorMsg: this.props.scoreErrorMsg, // Text string in response to the type of score error scoreAffiliation: null // Affiliation associated with the score }; }, @@ -43,6 +48,7 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ this.updateUserScoreObj(); }); } + this.setState({scoreError: nextProps.scoreError, scoreErrorMsg: nextProps.scoreErrorMsg}); }, loadData() { @@ -69,6 +75,8 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ this.refs.scoreRange.setValue(modifiedScore ? modifiedScore : 'none'); this.updateUserScoreObj(); }); + // Save original score for checking if change has been made + this.setState({userOrigScore: !isNaN(parseFloat(modifiedScore)) ? modifiedScore : null}); } } }); @@ -165,9 +173,20 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ return affiliatedScore; }, + // Check if score has been changed before saving the score object + saveScore(e) { + if (this.state.modifiedScore === this.state.userOrigScore || + this.state.modifiedScore === 'none' && this.state.userOrigScore === null) { + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because score is not changed. Please select a new score then save.'}); + } + else { + this.props.scoreSubmit(e); + } + }, + render() { let modifiedScore = this.state.modifiedScore ? this.state.modifiedScore : 'none'; - + let scoreError = this.state.scoreError; return (
@@ -190,6 +209,11 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ + {scoreError ? +
+

{this.state.scoreErrorMsg}

+
+ : null} {this.props.isDisabled ?

A Study type must be selected @@ -199,8 +223,8 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({

{this.props.scoreSubmit ?
- +
: null}
diff --git a/src/clincoded/static/components/score/experimental_score.js b/src/clincoded/static/components/score/experimental_score.js index 94c6028b2..2768fea30 100644 --- a/src/clincoded/static/components/score/experimental_score.js +++ b/src/clincoded/static/components/score/experimental_score.js @@ -23,7 +23,8 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ handleUserScoreObj: PropTypes.func, // Function to call create/update score object scoreSubmit: PropTypes.func, // Function to call when Save button is clicked; This prop's existence makes the Save button exist submitBusy: PropTypes.bool, // TRUE while the form submit is running - formError: PropTypes.bool, // TRUE if no explanation is given for a different score + scoreError: PropTypes.bool, // TRUE if no explanation is given for a different score or no change is made + scoreErrorMsg: PropTypes.string, // Text string in response to the type of score error scoreDisabled: PropTypes.bool, // FALSE if the matched checkbox is selected affiliation: PropTypes.object // Affiliation object passed from parent }, @@ -45,8 +46,13 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ submitBusy: false, // TRUE while form is submitting disableScoreStatus: this.props.scoreDisabled, // FALSE if the matched checkbox is selected willNotCountScore: false, // TRUE if 'Review' is selected when Mode of Inheritance is not AD, AR, or X-Linked - formError: false, // TRUE if no explanation is given for a different score - scoreAffiliation: null // Affiliation associated with the score + scoreAffiliation: null, // Affiliation associated with the score + scoreError: this.props.scoreError, // TRUE if no explanation is given for a different score or no change is made + scoreErrorMsg: this.props.scoreErrorMsg, // Text string in response to the type of score error + scoreAffiliation: null, // Affiliation associated with the score + origStatus: null, // User originally selected status + origScore: null, // User originally selected score + origScoreExplanation: null // User originally entered explanation for selected score }; }, @@ -65,9 +71,7 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ this.refs.scoreStatus.resetValue(); }); } - if (nextProps.formError && nextProps.formError !== this.props.formError) { - this.setState({formError: true}); - } + this.setState({scoreError: nextProps.scoreError, scoreErrorMsg: nextProps.scoreErrorMsg}); this.setState({disableScoreStatus: nextProps.scoreDisabled}, () => { if (this.state.disableScoreStatus) { this.setState({showScoreInput: false}, () => { @@ -102,6 +106,14 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ modifiedScore = matchedScore.hasOwnProperty('score') ? matchedScore.score.toString() : null, scoreExplanation = matchedScore.scoreExplanation, calcScoreRange = this.getScoreRange(experimentalEvidenceType, parseFloat(defaultScore)); + + // Save original data for checking if changes has been made + this.setState({ + origStatus: scoreStatus, + origScore: modifiedScore, + origScoreExplanation: scoreExplanation === undefined ? null : scoreExplanation + }); + /**************************************************************************************/ /* Curators are allowed to access the score form fields when the 'Score' is selected, */ /* or when 'Review' is selected given the matched Mode of Inheritance types */ @@ -160,7 +172,8 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ modifiedScore: null, scoreExplanation: null, requiredScoreExplanation: false, - formError: false, + scoreError: false, + scoreErrorMsg: '', updateDefaultScore: true }, () => { let calcScoreRange = this.getScoreRange(experimentalEvidenceType, calcDefaultScore); @@ -183,7 +196,8 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ scoreRange: [], scoreExplanation: null, requiredScoreExplanation: false, - formError: false + scoreError: false, + scoreErrorMsg: '' }, () => { if (this.refs.scoreExplanation && this.refs.scoreExplanation.getValue()) { this.refs.scoreExplanation.resetValue(); @@ -209,7 +223,7 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ }); } else { // Reset explanation if default score is kept - this.setState({scoreExplanation: null, requiredScoreExplanation: false, formError: false}, () => { + this.setState({scoreExplanation: null, requiredScoreExplanation: false, scoreError: false, scoreErrorMsg: ''}, () => { this.refs.scoreExplanation.resetValue(); this.updateUserScoreObj(); }); @@ -221,12 +235,27 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ if (this.refs.scoreExplanation) { // Parse the score explanation entered by the curator let scoreExplanation = this.refs.scoreExplanation.getValue(); - this.setState({scoreExplanation: scoreExplanation, formError: false}, () => { + this.setState({scoreExplanation: scoreExplanation, scoreError: false, scoreErrorMsg: ''}, () => { this.updateUserScoreObj(); }); } }, + // Check if changes has been made before saving the score object. + saveScore(e) { + if ((this.state.scoreStatus === this.state.origStatus || + this.state.scoreStatus === 'none' && this.state.origStatus === null) && + (this.state.modifiedScore === this.state.origScore || + this.state.modifiedScore === 'none' && this.state.origScore === null) && + (this.state.scoreExplanation === this.state.origScoreExplanation || + this.state.scoreExplanation === '' && this.state.origScoreExplanation === null)) { + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because no field has been modified. Please make your changes then save.'}); + } + else { + this.props.scoreSubmit(e); + } + }, + // Put together the score object based on the form values for // the currently logged-in user updateUserScoreObj() { @@ -413,7 +442,7 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ let requiredScoreExplanation = this.state.requiredScoreExplanation; let disableScoreStatus = this.state.disableScoreStatus; let willNotCountScore = this.state.willNotCountScore; - let formError = this.state.formError; + let scoreError = this.state.scoreError; return (
@@ -457,17 +486,17 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ value={scoreExplanation} handleChange={this.handleScoreExplanation} placeholder="Note: If you selected a score different from the default score, you must provide a reason for the change here." rows="3" labelClassName="col-sm-5 control-label" wrapperClassName="col-sm-7" groupClassName="form-group" /> - {formError ? -
-

A reason is required for the changed score.

-
- : null}
: null} + {scoreError ? +
+

{this.state.scoreErrorMsg}

+
+ : null}
{this.props.scoreSubmit ?
-
: null} diff --git a/src/clincoded/static/components/score/individual_score.js b/src/clincoded/static/components/score/individual_score.js index 47b87cf78..5d6f89e92 100644 --- a/src/clincoded/static/components/score/individual_score.js +++ b/src/clincoded/static/components/score/individual_score.js @@ -60,7 +60,11 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ scoreError: this.props.scoreError, // TRUE if no explanation is given for modified score or no case info type scoreErrorMsg: this.props.scoreErrorMsg, // Text string in response to the type of score error scoreAffiliation: null, // Affiliation associated with the score - priorScoreStatus: undefined // Placeholder score status for clearing explanation text field given the comparison + priorScoreStatus: undefined, // Placeholder score status for clearing explanation text field given the comparison + origStatus: null, // User originally selected status + origCaseInfoType: null, // User originally selected case information type + origScore: null, // User originally selected score + origScoreExplanation: null // User originally entered explanation for selected score }; }, @@ -127,6 +131,14 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ modifiedScore = matchedScore.hasOwnProperty('score') ? matchedScore.score.toString() : null, scoreExplanation = matchedScore.scoreExplanation, calcScoreRange = this.getScoreRange(modeInheritanceType, caseInfoType, parseFloat(defaultScore)); + + // Save original data for checking if changes has been made + this.setState({ + origStatus: scoreStatus, + origCaseInfoType: caseInfoType === undefined ? null : caseInfoType, + origScore: modifiedScore, + origScoreExplanation: scoreExplanation === undefined ? null : scoreExplanation + }); /**************************************************************************************/ /* Curators are allowed to access the score form fields when the 'Score' is selected, */ /* or when 'Review' is selected given the matched Mode of Inheritance types */ @@ -321,6 +333,22 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ } }, + // Check if changes has been made before saving the score object. + saveScore(e) { + if ((this.state.scoreStatus === this.state.origStatus || + this.state.scoreStatus === 'none' && this.state.origStatus === null) && + (this.state.modifiedScore === this.state.origScore || + this.state.modifiedScore === 'none' && this.state.origScore === null) && + (this.state.scoreExplanation === this.state.origScoreExplanation || + this.state.scoreExplanation === '' && this.state.origScoreExplanation === null) && + this.state.caseInfoType === this.state.origCaseInfoType) { + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because no field has been modified. Please make your changes then save.'}); + } + else { + this.props.scoreSubmit(e); + } + }, + // Put together the score object based on the form values for // the currently logged-in user updateUserScoreObj() { @@ -637,18 +665,18 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ 'Note: If you selected a score different from the default score, you must provide a reason for the change here.' : null} rows="3" labelClassName="col-sm-5 control-label" wrapperClassName="col-sm-7" groupClassName="form-group" /> - {scoreError ? -
-

{this.state.scoreErrorMsg}

-
- : null} +
+ : null} + {scoreError ? +
+

{this.state.scoreErrorMsg}

: null} {this.props.scoreSubmit ?
- +
: null} diff --git a/src/clincoded/static/scss/clincoded/modules/_curator.scss b/src/clincoded/static/scss/clincoded/modules/_curator.scss index cb22eaf9c..75aadfe9e 100644 --- a/src/clincoded/static/scss/clincoded/modules/_curator.scss +++ b/src/clincoded/static/scss/clincoded/modules/_curator.scss @@ -3437,7 +3437,7 @@ table.login-users-interpretations { .required-field { position: absolute; top: -38px; - left: 198px; + left: 86px; } } @@ -3664,4 +3664,4 @@ table.login-users-interpretations { text-align: left; } } -} \ No newline at end of file +} From ee914f068602000ef8f0212688e26da403a76c6a Mon Sep 17 00:00:00 2001 From: Gloria Cheung Date: Tue, 23 Jul 2019 08:26:01 -0700 Subject: [PATCH 2/3] Removed duplicated state. --- src/clincoded/static/components/score/experimental_score.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/clincoded/static/components/score/experimental_score.js b/src/clincoded/static/components/score/experimental_score.js index 2768fea30..2f2608a87 100644 --- a/src/clincoded/static/components/score/experimental_score.js +++ b/src/clincoded/static/components/score/experimental_score.js @@ -49,7 +49,6 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ scoreAffiliation: null, // Affiliation associated with the score scoreError: this.props.scoreError, // TRUE if no explanation is given for a different score or no change is made scoreErrorMsg: this.props.scoreErrorMsg, // Text string in response to the type of score error - scoreAffiliation: null, // Affiliation associated with the score origStatus: null, // User originally selected status origScore: null, // User originally selected score origScoreExplanation: null // User originally entered explanation for selected score From 889e2f12c964a8283c9a82f7a0ed30663e9ee643 Mon Sep 17 00:00:00 2001 From: Gloria Cheung Date: Fri, 13 Dec 2019 13:34:46 -0800 Subject: [PATCH 3/3] Updated error messages according to review. --- src/clincoded/static/components/score/experimental_score.js | 2 +- src/clincoded/static/components/score/individual_score.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clincoded/static/components/score/experimental_score.js b/src/clincoded/static/components/score/experimental_score.js index 2f2608a87..a806005c4 100644 --- a/src/clincoded/static/components/score/experimental_score.js +++ b/src/clincoded/static/components/score/experimental_score.js @@ -248,7 +248,7 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ this.state.modifiedScore === 'none' && this.state.origScore === null) && (this.state.scoreExplanation === this.state.origScoreExplanation || this.state.scoreExplanation === '' && this.state.origScoreExplanation === null)) { - this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because no field has been modified. Please make your changes then save.'}); + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because score/explanation has not been modified. Please make your changes then save.'}); } else { this.props.scoreSubmit(e); diff --git a/src/clincoded/static/components/score/individual_score.js b/src/clincoded/static/components/score/individual_score.js index 081392e4d..22c776cfa 100644 --- a/src/clincoded/static/components/score/individual_score.js +++ b/src/clincoded/static/components/score/individual_score.js @@ -342,7 +342,7 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ (this.state.scoreExplanation === this.state.origScoreExplanation || this.state.scoreExplanation === '' && this.state.origScoreExplanation === null) && this.state.caseInfoType === this.state.origCaseInfoType) { - this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because no field has been modified. Please make your changes then save.'}); + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because score/explanation has not been modified. Please make your changes then save.'}); } else { this.props.scoreSubmit(e);