diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index 044bcdfad..940d781aa 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -6,7 +6,27 @@ import flatten from 'lodash/flatten'; import isEmpty from 'lodash/isEmpty'; import {getMutationOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; import {store} from 'coral-framework/services/store'; -import {getDefinitionName} from '../utils'; +import {getDefinitionName, getResponseErrors} from '../utils'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-framework/translations.json'; +const lang = new I18n(translations); + +class ResponseErrors extends Error { + constructor(errors) { + super(`Response Errors ${JSON.stringify(errors)}`); + this.errors = errors.map((e) => new ResponseError(e)); + } +} + +class ResponseError { + constructor(error) { + Object.assign(this, error); + } + + translate(...args) { + return lang.t(`error.${this.translation_key}`, ...args); + } +} /** * Exports a HOC with the same signature as `graphql`, that will @@ -41,6 +61,11 @@ export default (document, config) => (WrappedComponent) => { .filter((i) => i); const update = (proxy, result) => { + if (getResponseErrors(result)) { + + // Do not run updates when we have mutation errors. + return; + } updateCallbacks.forEach((cb) => cb(proxy, result)); }; @@ -53,7 +78,14 @@ export default (document, config) => (WrappedComponent) => { .reduce((res, map) => { Object.keys(map).forEach((key) => { if (!(key in res)) { - res[key] = map[key]; + res[key] = (prev, result) => { + if (getResponseErrors(result.mutationResult)) { + + // Do not run updates when we have mutation errors. + return prev; + } + return map[key](prev, result); + }; } else { const existing = res[key]; res[key] = (prev, result) => { @@ -75,7 +107,14 @@ export default (document, config) => (WrappedComponent) => { if (isEmpty(wrappedConfig.optimisticResponse)) { delete wrappedConfig.optimisticResponse; } - return data.mutate(wrappedConfig); + return data.mutate(wrappedConfig) + .then((res) => { + const errors = getResponseErrors(res); + if (errors) { + throw new ResponseErrors(errors); + } + return Promise.resolve(res); + }); }; return config.props({...data, mutate}); }; diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index 036f9c149..84182f7dd 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -1,7 +1,14 @@ import * as React from 'react'; import {graphql} from 'react-apollo'; import {getQueryOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; -import {getDefinitionName, separateDataAndRoot} from '../utils'; +import {getDefinitionName, separateDataAndRoot, getResponseErrors} from '../utils'; + +const withSkipOnErrors = (reducer) => (prev, action, ...rest) => { + if (action.type === 'APOLLO_MUTATION_RESULT' && getResponseErrors(action.result)) { + return prev; + } + return reducer(prev, action, ...rest); +}; /** * Exports a HOC with the same signature as `graphql`, that will @@ -23,10 +30,11 @@ export default (document, config) => (WrappedComponent) => { .concat(...configs.map((cfg) => cfg.reducer)) .filter((i) => i); - const reducer = reducerCallbacks.reduce( - (a, b) => (prev, ...rest) => - b(a(prev, ...rest), ...rest), - ); + const reducer = withSkipOnErrors( + reducerCallbacks.reduce( + (a, b) => (prev, ...rest) => + b(a(prev, ...rest), ...rest), + )); return { ...base, diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index 8fb9cbf81..c8e68517a 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -100,3 +100,14 @@ export function mergeDocuments(documents) { const literals = [main, ...substitutions.map(() => '\n')]; return gql.apply(null, [literals, ...substitutions]); } + +export function getResponseErrors(mutationResult) { + const result = []; + Object.keys(mutationResult.data).forEach((response) => { + const errors = mutationResult.data[response].errors; + if (errors && errors.length) { + result.push(...errors); + } + }); + return result.length ? result : false; +}