Commit 16553345 authored by Florent Chehab's avatar Florent Chehab

Updated error handling in the app

Fixed #69
parent 3b11b824
Pipeline #35739 failed with stages
in 0 seconds
...@@ -15,6 +15,7 @@ import getMapDispatchToPropsForEditor from "../shared/editorFunctions/getMapDisp ...@@ -15,6 +15,7 @@ import getMapDispatchToPropsForEditor from "../shared/editorFunctions/getMapDisp
import Form from "../shared/Form"; import Form from "../shared/Form";
import { withSnackbar } from "notistack"; import { withSnackbar } from "notistack";
import CustomError from "../../../utils/CustomError";
const styles = theme => ({ const styles = theme => ({
...@@ -44,7 +45,7 @@ class UniversitySemestersDatesForm extends Form { ...@@ -44,7 +45,7 @@ class UniversitySemestersDatesForm extends Form {
messages.push("Le début du semestre de printemps doit être antérieur à sa fin..."); messages.push("Le début du semestre de printemps doit être antérieur à sa fin...");
} }
return this.buildError(messages); return new CustomError(messages);
} }
render() { render() {
......
...@@ -57,8 +57,8 @@ class Editor extends Component { ...@@ -57,8 +57,8 @@ class Editor extends Component {
///////// /////////
// Shortcut functions to those of the form instance // Shortcut functions to those of the form instance
formHasError() { getFormError() {
return this.getForm().hasError(); return this.getForm().getError();
} }
getFormData() { getFormData() {
...@@ -79,8 +79,8 @@ class Editor extends Component { ...@@ -79,8 +79,8 @@ class Editor extends Component {
* @memberof Editor * @memberof Editor
*/ */
handleSaveEditorRequest() { handleSaveEditorRequest() {
const formHasError = this.formHasError(); const getFormError = this.getFormError();
if (!formHasError.status) { // no error, we can save if necessary if (!getFormError.status) { // no error, we can save if necessary
if (this.props.forceSave || this.formHasChanges()) { if (this.props.forceSave || this.formHasChanges()) {
// Copy the model data and copy above the data from the form // Copy the model data and copy above the data from the form
// So that we don't forget anything. // So that we don't forget anything.
...@@ -93,7 +93,7 @@ class Editor extends Component { ...@@ -93,7 +93,7 @@ class Editor extends Component {
} }
} else { } else {
this.notifyFormHasErrors(formHasError.messages); this.notifyFormHasErrors(getFormError.messages);
} }
} }
......
...@@ -3,6 +3,7 @@ import PropTypes from "prop-types"; ...@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import areSameObjects from "../../../utils/areSameObjects"; import areSameObjects from "../../../utils/areSameObjects";
import renderFieldsMixIn from "./renderFieldsMixIn"; import renderFieldsMixIn from "./renderFieldsMixIn";
import CustomError from "../../../utils/CustomError";
/** /**
* React component that should contain `Field` instances. * React component that should contain `Field` instances.
...@@ -77,40 +78,19 @@ class Form extends Component { ...@@ -77,40 +78,19 @@ class Form extends Component {
} }
/**
* Function to build a generic way of handling field errors
*
* @param {Array} messages
* @returns
* @memberof Form
*/
buildError(messages) {
return { status: messages.length > 0, messages };
}
/**
* Combine errors constructed with buildError
*
* @param {array} arrayOfErrors
* @returns
* @memberof Form
*/
combineErrors(arrayOfErrors) {
return this.buildError(arrayOfErrors.flatMap(error => error.messages));
}
/** /**
* Function to build all the errors from the fields of the form. * Function to build all the errors from the fields of the form.
* *
* TODO change setup with new custom error
*
* @returns * @returns
* @memberof Form * @memberof Form
*/ */
fieldsHaveError() { fieldsHaveError() {
const messages = this.getFields() const messages = this.getFields()
.map(({ field }) => field.hasError().messages) .map(({ field }) => field.getError().messages)
.filter(messages => messages.length > 0); .filter(messages => messages.length > 0);
return this.buildError(messages); return new CustomError(messages);
} }
/** /**
...@@ -122,11 +102,9 @@ class Form extends Component { ...@@ -122,11 +102,9 @@ class Form extends Component {
* @returns * @returns
* @memberof Form * @memberof Form
*/ */
hasError() { getError() {
return this.combineErrors([ return this.fieldsHaveError()
this.fieldsHaveError(), .combine(this.hasFormLevelErrors());
this.hasFormLevelErrors()
]);
} }
/** /**
...@@ -134,11 +112,11 @@ class Form extends Component { ...@@ -134,11 +112,11 @@ class Form extends Component {
* *
* Can be override in sub classes * Can be override in sub classes
* *
* @returns object built with this.buildError * @returns {CustomError}
* @memberof Form * @memberof Form
*/ */
hasFormLevelErrors() { hasFormLevelErrors() {
return this.fieldsHaveError(); return CustomError([]); // default: no errors
} }
......
...@@ -2,6 +2,7 @@ import React from "react"; ...@@ -2,6 +2,7 @@ import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Form from "./Form"; import Form from "./Form";
import CustomError from "../../../utils/CustomError";
const frequencyOptions = [ const frequencyOptions = [
{ value: "w", label: "Il s'agit du montant hebdomadaire" }, { value: "w", label: "Il s'agit du montant hebdomadaire" },
...@@ -21,7 +22,7 @@ class ScholarshipForm extends Form { ...@@ -21,7 +22,7 @@ class ScholarshipForm extends Form {
messages.push("La logique voudrait que la borne supérieure de la bourse soit... supérieure à la borne inférieure."); messages.push("La logique voudrait que la borne supérieure de la bourse soit... supérieure à la borne inférieure.");
} }
return this.buildError(messages); return CustomError(messages);
} }
render() { render() {
......
...@@ -12,7 +12,7 @@ import KeyboardArrowRightIcon from "@material-ui/icons/KeyboardArrowRight"; ...@@ -12,7 +12,7 @@ import KeyboardArrowRightIcon from "@material-ui/icons/KeyboardArrowRight";
import dateToDateStr from "../../../../utils/dateToDateStr"; import dateToDateStr from "../../../../utils/dateToDateStr";
import Field from "./Field"; import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
/** /**
* Class to customize the header of the date selection box * Class to customize the header of the date selection box
...@@ -32,13 +32,14 @@ class DateField extends Field { ...@@ -32,13 +32,14 @@ class DateField extends Field {
return dateToDateStr(this.state.value); return dateToDateStr(this.state.value);
} }
hasError() { getError() {
const date = this.state.value; const date = this.state.value;
let messages = Array(); let messages = Array();
if (this.props.required && !date) { if (this.props.required && !date) {
messages.push("Date requise !"); messages.push("Date requise !");
} }
return this.buildError(messages);
return new CustomError(messages);
} }
handleDateChange = (date) => { handleDateChange = (date) => {
......
...@@ -3,6 +3,8 @@ import { PureComponent } from "react"; ...@@ -3,6 +3,8 @@ import { PureComponent } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Form from "../Form"; import Form from "../Form";
import FieldWrapper from "./FieldWrapper"; import FieldWrapper from "./FieldWrapper";
// eslint-disable-next-line no-unused-vars
import CustomError from "../../../../utils/CustomError";
/** /**
* Class that handle fields logic * Class that handle fields logic
...@@ -43,13 +45,14 @@ class Field extends PureComponent { ...@@ -43,13 +45,14 @@ class Field extends PureComponent {
super.setState(newState); super.setState(newState);
} }
hasError() { /**
* @returns {CustomError}
* @memberof Field
*/
getError() {
throw Error("This methods has to be override in sub classes"); throw Error("This methods has to be override in sub classes");
} }
buildError(messages) {
return { status: messages.length > 0, messages };
}
/** /**
* function to get serialize the value from the field, to get it ready to send to server * function to get serialize the value from the field, to get it ready to send to server
...@@ -58,7 +61,7 @@ class Field extends PureComponent { ...@@ -58,7 +61,7 @@ class Field extends PureComponent {
* @returns * @returns
* @memberof Field * @memberof Field
*/ */
serializeFromField(){ serializeFromField() {
return this.state.value; return this.state.value;
} }
...@@ -87,8 +90,7 @@ class Field extends PureComponent { ...@@ -87,8 +90,7 @@ class Field extends PureComponent {
return ( return (
<FieldWrapper <FieldWrapper
required={this.props.required} required={this.props.required}
hasError={this.hasError().status} errorObj={this.getError()}
errors={this.hasError().messages}
label={this.props.label} label={this.props.label}
> >
{this.renderField()} {this.renderField()}
......
...@@ -7,21 +7,28 @@ import compose from "recompose/compose"; ...@@ -7,21 +7,28 @@ import compose from "recompose/compose";
import FormControl from "@material-ui/core/FormControl"; import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel"; import FormLabel from "@material-ui/core/FormLabel";
import FormHelperText from "@material-ui/core/FormHelperText"; import FormHelperText from "@material-ui/core/FormHelperText";
import CustomError from "../../../../utils/CustomError";
/**
* Wrapper for fields in form to handle labels and visual feedback
*
* @class FieldWrapper
* @extends {PureComponent}
*/
class FieldWrapper extends PureComponent { class FieldWrapper extends PureComponent {
render() { render() {
const { props } = this; const { classes, required, errorObj, label, children } = this.props;
return ( return (
<FormControl className={props.classes.formElement} required={props.required} error={props.hasError} fullWidth={true}> <FormControl className={classes.formElement} required={required} error={errorObj.status} fullWidth={true}>
<FormLabel>{props.label}</FormLabel> <FormLabel>{label}</FormLabel>
{ {
props.errors.map((error, idx) => ( errorObj.messages.map((error, idx) => (
<FormHelperText key={idx}>{error}</FormHelperText> <FormHelperText key={idx}>{error}</FormHelperText>
)) ))
} }
{props.children} {children}
</FormControl> </FormControl>
); );
} }
...@@ -30,15 +37,15 @@ class FieldWrapper extends PureComponent { ...@@ -30,15 +37,15 @@ class FieldWrapper extends PureComponent {
FieldWrapper.defaultProps = { FieldWrapper.defaultProps = {
label: "LABEL", label: "LABEL",
required: false, required: false,
hasError: false, errorObj: new CustomError(),
errors: []
}; };
FieldWrapper.propTypes = { FieldWrapper.propTypes = {
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
errorObj: PropTypes.instanceOf(CustomError).isRequired,
classes: PropTypes.object.isRequired,
required: PropTypes.bool.isRequired, required: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired, children: PropTypes.node.isRequired,
errors: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
}; };
......
...@@ -10,11 +10,13 @@ import Markdown from "../../../shared/Markdown"; ...@@ -10,11 +10,13 @@ import Markdown from "../../../shared/Markdown";
import LinkText from "../../../other/TextLink"; import LinkText from "../../../other/TextLink";
import Field from "./Field"; import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
class MarkdownField extends Field { class MarkdownField extends Field {
defaultNullValue = ""; defaultNullValue = "";
hasError() { getError() {
const { value } = this.state; const { value } = this.state;
let messages = Array(); let messages = Array();
if (this.props.required && value == "") { if (this.props.required && value == "") {
...@@ -23,7 +25,8 @@ class MarkdownField extends Field { ...@@ -23,7 +25,8 @@ class MarkdownField extends Field {
if (this.props.maxLength && value.length > this.props.maxLength) { if (this.props.maxLength && value.length > this.props.maxLength) {
messages.push("Le texte est trop long."); messages.push("Le texte est trop long.");
} }
return this.buildError(messages);
return new CustomError(messages);
} }
handleChangeValue = (val) => { handleChangeValue = (val) => {
......
...@@ -8,6 +8,7 @@ import Select from "@material-ui/core/Select"; ...@@ -8,6 +8,7 @@ import Select from "@material-ui/core/Select";
import Checkbox from "@material-ui/core/Checkbox"; import Checkbox from "@material-ui/core/Checkbox";
import Field from "./Field"; import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
class MultiSelectField extends Field { class MultiSelectField extends Field {
...@@ -18,13 +19,13 @@ class MultiSelectField extends Field { ...@@ -18,13 +19,13 @@ class MultiSelectField extends Field {
props.options.map((opt) => this.optionsByValue[opt.value] = opt.label); props.options.map((opt) => this.optionsByValue[opt.value] = opt.label);
} }
hasError() { getError() {
const { value } = this.state; const { value } = this.state;
let messages = Array(); let messages = Array();
if (this.props.required && value.length === 0) { if (this.props.required && value.length === 0) {
messages.push("Ce champ est requis."); messages.push("Ce champ est requis.");
} }
return this.buildError(messages); return new CustomError(messages);
} }
handleChangeValue = (value) => { handleChangeValue = (value) => {
......
...@@ -5,12 +5,13 @@ import TextField from "@material-ui/core/TextField"; ...@@ -5,12 +5,13 @@ import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import Field from "./Field"; import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
class NumberField extends Field { class NumberField extends Field {
defaultNullValue = ""; defaultNullValue = "";
hasError() { getError() {
const { value } = this.state; const { value } = this.state;
let messages = Array(); let messages = Array();
if (this.props.required && value == "") { if (this.props.required && value == "") {
...@@ -22,7 +23,7 @@ class NumberField extends Field { ...@@ -22,7 +23,7 @@ class NumberField extends Field {
if (this.props.minValue && value < this.props.minValue) { if (this.props.minValue && value < this.props.minValue) {
messages.push("Le nombre inscrit est trop petit."); messages.push("Le nombre inscrit est trop petit.");
} }
return this.buildError(messages); return new CustomError(messages);
} }
handleChangeValue = (val) => { handleChangeValue = (val) => {
......
...@@ -6,6 +6,7 @@ import MenuItem from "@material-ui/core/MenuItem"; ...@@ -6,6 +6,7 @@ import MenuItem from "@material-ui/core/MenuItem";
import Select from "@material-ui/core/Select"; import Select from "@material-ui/core/Select";
import Field from "./Field"; import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
import withStyles from "@material-ui/core/styles/withStyles"; import withStyles from "@material-ui/core/styles/withStyles";
...@@ -40,14 +41,14 @@ class SelectField extends Field { ...@@ -40,14 +41,14 @@ class SelectField extends Field {
super(props, customStateAttrs); super(props, customStateAttrs);
} }
hasError() { getError() {
const { value } = this.state; const { value } = this.state;
let messages = Array(); let messages = Array();
if (this.props.required && (value === null)) { if (this.props.required && (value === null)) {
messages.push("Ce champ est requis."); messages.push("Ce champ est requis.");
} }
return this.buildError(messages); return new CustomError(messages);
} }
handleChangeValue = (value) => { handleChangeValue = (value) => {
......
...@@ -7,12 +7,13 @@ import Typography from "@material-ui/core/Typography"; ...@@ -7,12 +7,13 @@ import Typography from "@material-ui/core/Typography";
import Field from "./Field"; import Field from "./Field";
import isUrl from "../../../../utils/isUrl"; import isUrl from "../../../../utils/isUrl";
import stringHasExtension from "../../../../utils/stringHasExtension"; import stringHasExtension from "../../../../utils/stringHasExtension";
import CustomError from "../../../../utils/CustomError";
class TextField extends Field { class TextField extends Field {
defaultNullValue = ""; defaultNullValue = "";
hasError() { getError() {
const { value } = this.state; const { value } = this.state;
let messages = Array(); let messages = Array();
if (this.props.required && value == "") { if (this.props.required && value == "") {
...@@ -29,7 +30,7 @@ class TextField extends Field { ...@@ -29,7 +30,7 @@ class TextField extends Field {
messages.push("Extension de l'URL non conforme"); messages.push("Extension de l'URL non conforme");
} }
} }
return this.buildError(messages); return new CustomError(messages);
} }
handleChangeValue = (val) => { handleChangeValue = (val) => {
......
...@@ -16,12 +16,13 @@ import Field from "./Field"; ...@@ -16,12 +16,13 @@ import Field from "./Field";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import isUrl from "../../../../utils/isUrl"; import isUrl from "../../../../utils/isUrl";
import CustomError from "../../../../utils/CustomError";
class UsefulLinksField extends Field { class UsefulLinksField extends Field {
hasError() { getError() {
const usefulLinks = this.state.value; const usefulLinks = this.state.value;
let messages = Array(); let messages = Array();
...@@ -58,7 +59,7 @@ class UsefulLinksField extends Field { ...@@ -58,7 +59,7 @@ class UsefulLinksField extends Field {
}); });
if (!lengthOk) { messages.push("Un url ou une description est trop longue."); } if (!lengthOk) { messages.push("Un url ou une description est trop longue."); }
return this.buildError(messages); return new CustomError(messages);
} }
serializeFromField() { serializeFromField() {
......
/**
* Class to handle app errors (such as form errors) in a nice wrapped way
* It's for non fatal errors.
*
* @export
* @class CustomError
*/
export default class CustomError {
/**
*Creates an instance of CustomError.
*
* @param {Array[string] = []} messages
* @memberof CustomError
*/
constructor(messages = []) {
this.messages = messages;
this.status = messages.length > 0;
}
/**
* Method to combine to error class
*
* @param {CustomError} other
* @returns {CustomError}
* @memberof CustomError
*/
combine(other) {
return new CustomError(Array.prototype.concat(this.messages, other.messages));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment