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
import Form from "../shared/Form";
import { withSnackbar } from "notistack";
import CustomError from "../../../utils/CustomError";
const styles = theme => ({
......@@ -44,7 +45,7 @@ class UniversitySemestersDatesForm extends Form {
messages.push("Le début du semestre de printemps doit être antérieur à sa fin...");
}
return this.buildError(messages);
return new CustomError(messages);
}
render() {
......
......@@ -57,8 +57,8 @@ class Editor extends Component {
/////////
// Shortcut functions to those of the form instance
formHasError() {
return this.getForm().hasError();
getFormError() {
return this.getForm().getError();
}
getFormData() {
......@@ -79,8 +79,8 @@ class Editor extends Component {
* @memberof Editor
*/
handleSaveEditorRequest() {
const formHasError = this.formHasError();
if (!formHasError.status) { // no error, we can save if necessary
const getFormError = this.getFormError();
if (!getFormError.status) { // no error, we can save if necessary
if (this.props.forceSave || this.formHasChanges()) {
// Copy the model data and copy above the data from the form
// So that we don't forget anything.
......@@ -93,7 +93,7 @@ class Editor extends Component {
}
} else {
this.notifyFormHasErrors(formHasError.messages);
this.notifyFormHasErrors(getFormError.messages);
}
}
......
......@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import areSameObjects from "../../../utils/areSameObjects";
import renderFieldsMixIn from "./renderFieldsMixIn";
import CustomError from "../../../utils/CustomError";
/**
* React component that should contain `Field` instances.
......@@ -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.
*
* TODO change setup with new custom error
*
* @returns
* @memberof Form
*/
fieldsHaveError() {
const messages = this.getFields()
.map(({ field }) => field.hasError().messages)
.map(({ field }) => field.getError().messages)
.filter(messages => messages.length > 0);
return this.buildError(messages);
return new CustomError(messages);
}
/**
......@@ -122,11 +102,9 @@ class Form extends Component {
* @returns
* @memberof Form
*/
hasError() {
return this.combineErrors([
this.fieldsHaveError(),
this.hasFormLevelErrors()
]);
getError() {
return this.fieldsHaveError()
.combine(this.hasFormLevelErrors());
}
/**
......@@ -134,11 +112,11 @@ class Form extends Component {
*
* Can be override in sub classes
*
* @returns object built with this.buildError
* @returns {CustomError}
* @memberof Form
*/
hasFormLevelErrors() {
return this.fieldsHaveError();
return CustomError([]); // default: no errors
}
......
......@@ -2,6 +2,7 @@ import React from "react";
import PropTypes from "prop-types";
import Form from "./Form";
import CustomError from "../../../utils/CustomError";
const frequencyOptions = [
{ value: "w", label: "Il s'agit du montant hebdomadaire" },
......@@ -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.");
}
return this.buildError(messages);
return CustomError(messages);
}
render() {
......
......@@ -12,7 +12,7 @@ import KeyboardArrowRightIcon from "@material-ui/icons/KeyboardArrowRight";
import dateToDateStr from "../../../../utils/dateToDateStr";
import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
/**
* Class to customize the header of the date selection box
......@@ -32,13 +32,14 @@ class DateField extends Field {
return dateToDateStr(this.state.value);
}
hasError() {
getError() {
const date = this.state.value;
let messages = Array();
if (this.props.required && !date) {
messages.push("Date requise !");
}
return this.buildError(messages);
return new CustomError(messages);
}
handleDateChange = (date) => {
......
......@@ -3,6 +3,8 @@ import { PureComponent } from "react";
import PropTypes from "prop-types";
import Form from "../Form";
import FieldWrapper from "./FieldWrapper";
// eslint-disable-next-line no-unused-vars
import CustomError from "../../../../utils/CustomError";
/**
* Class that handle fields logic
......@@ -43,13 +45,14 @@ class Field extends PureComponent {
super.setState(newState);
}
hasError() {
/**
* @returns {CustomError}
* @memberof Field
*/
getError() {
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
......@@ -58,7 +61,7 @@ class Field extends PureComponent {
* @returns
* @memberof Field
*/
serializeFromField(){
serializeFromField() {
return this.state.value;
}
......@@ -87,8 +90,7 @@ class Field extends PureComponent {
return (
<FieldWrapper
required={this.props.required}
hasError={this.hasError().status}
errors={this.hasError().messages}
errorObj={this.getError()}
label={this.props.label}
>
{this.renderField()}
......
......@@ -7,21 +7,28 @@ import compose from "recompose/compose";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
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 {
render() {
const { props } = this;
const { classes, required, errorObj, label, children } = this.props;
return (
<FormControl className={props.classes.formElement} required={props.required} error={props.hasError} fullWidth={true}>
<FormLabel>{props.label}</FormLabel>
<FormControl className={classes.formElement} required={required} error={errorObj.status} fullWidth={true}>
<FormLabel>{label}</FormLabel>
{
props.errors.map((error, idx) => (
errorObj.messages.map((error, idx) => (
<FormHelperText key={idx}>{error}</FormHelperText>
))
}
{props.children}
{children}
</FormControl>
);
}
......@@ -30,15 +37,15 @@ class FieldWrapper extends PureComponent {
FieldWrapper.defaultProps = {
label: "LABEL",
required: false,
hasError: false,
errors: []
errorObj: new CustomError(),
};
FieldWrapper.propTypes = {
label: PropTypes.string.isRequired,
errorObj: PropTypes.instanceOf(CustomError).isRequired,
classes: PropTypes.object.isRequired,
required: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired,
errors: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
children: PropTypes.node.isRequired,
};
......
......@@ -10,11 +10,13 @@ import Markdown from "../../../shared/Markdown";
import LinkText from "../../../other/TextLink";
import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
class MarkdownField extends Field {
defaultNullValue = "";
hasError() {
getError() {
const { value } = this.state;
let messages = Array();
if (this.props.required && value == "") {
......@@ -23,7 +25,8 @@ class MarkdownField extends Field {
if (this.props.maxLength && value.length > this.props.maxLength) {
messages.push("Le texte est trop long.");
}
return this.buildError(messages);
return new CustomError(messages);
}
handleChangeValue = (val) => {
......
......@@ -8,6 +8,7 @@ import Select from "@material-ui/core/Select";
import Checkbox from "@material-ui/core/Checkbox";
import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
class MultiSelectField extends Field {
......@@ -18,13 +19,13 @@ class MultiSelectField extends Field {
props.options.map((opt) => this.optionsByValue[opt.value] = opt.label);
}
hasError() {
getError() {
const { value } = this.state;
let messages = Array();
if (this.props.required && value.length === 0) {
messages.push("Ce champ est requis.");
}
return this.buildError(messages);
return new CustomError(messages);
}
handleChangeValue = (value) => {
......
......@@ -5,12 +5,13 @@ import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
class NumberField extends Field {
defaultNullValue = "";
hasError() {
getError() {
const { value } = this.state;
let messages = Array();
if (this.props.required && value == "") {
......@@ -22,7 +23,7 @@ class NumberField extends Field {
if (this.props.minValue && value < this.props.minValue) {
messages.push("Le nombre inscrit est trop petit.");
}
return this.buildError(messages);
return new CustomError(messages);
}
handleChangeValue = (val) => {
......
......@@ -6,6 +6,7 @@ import MenuItem from "@material-ui/core/MenuItem";
import Select from "@material-ui/core/Select";
import Field from "./Field";
import CustomError from "../../../../utils/CustomError";
import withStyles from "@material-ui/core/styles/withStyles";
......@@ -40,14 +41,14 @@ class SelectField extends Field {
super(props, customStateAttrs);
}
hasError() {
getError() {
const { value } = this.state;
let messages = Array();
if (this.props.required && (value === null)) {
messages.push("Ce champ est requis.");
}
return this.buildError(messages);
return new CustomError(messages);
}
handleChangeValue = (value) => {
......
......@@ -7,12 +7,13 @@ import Typography from "@material-ui/core/Typography";
import Field from "./Field";
import isUrl from "../../../../utils/isUrl";
import stringHasExtension from "../../../../utils/stringHasExtension";
import CustomError from "../../../../utils/CustomError";
class TextField extends Field {
defaultNullValue = "";
hasError() {
getError() {
const { value } = this.state;
let messages = Array();
if (this.props.required && value == "") {
......@@ -29,7 +30,7 @@ class TextField extends Field {
messages.push("Extension de l'URL non conforme");
}
}
return this.buildError(messages);
return new CustomError(messages);
}
handleChangeValue = (val) => {
......
......@@ -16,12 +16,13 @@ import Field from "./Field";
import Typography from "@material-ui/core/Typography";
import isUrl from "../../../../utils/isUrl";
import CustomError from "../../../../utils/CustomError";
class UsefulLinksField extends Field {
hasError() {
getError() {
const usefulLinks = this.state.value;
let messages = Array();
......@@ -58,7 +59,7 @@ class UsefulLinksField extends Field {
});
if (!lengthOk) { messages.push("Un url ou une description est trop longue."); }
return this.buildError(messages);
return new CustomError(messages);
}
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