withQuery and withMutation support for notifyOnError (default: true)

This commit is contained in:
Chi Vinh Le
2018-01-23 19:05:08 +01:00
parent 303e2e84c5
commit 0523faee2b
16 changed files with 133 additions and 139 deletions
@@ -10,7 +10,6 @@ import {
} from 'coral-framework/graphql/mutations';
import { compose } from 'react-apollo';
import t from 'coral-framework/services/i18n';
import { getErrorMessages } from 'coral-framework/utils';
import { notify } from 'coral-framework/actions/notification';
class BanUserDialogContainer extends Component {
@@ -22,16 +21,11 @@ class BanUserDialogContainer extends Component {
banUser,
setCommentStatus,
hideBanUserDialog,
notify,
} = this.props;
try {
await banUser({ id: userId, message: '' });
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({ commentId, status: 'REJECTED' });
}
} catch (err) {
notify('error', getErrorMessages(err));
await banUser({ id: userId, message: '' });
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({ commentId, status: 'REJECTED' });
}
};
@@ -85,7 +79,7 @@ const mapDispatchToProps = dispatch => ({
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withBanUser,
withSetCommentStatus,
connect(mapStateToProps, mapDispatchToProps)
withSetCommentStatus
)(BanUserDialogContainer);
@@ -11,7 +11,6 @@ import {
import { compose, gql } from 'react-apollo';
import t, { timeago } from 'coral-framework/services/i18n';
import withQuery from 'coral-framework/hocs/withQuery';
import { getErrorMessages } from 'coral-framework/utils';
import get from 'lodash/get';
import { notify } from 'coral-framework/actions/notification';
@@ -28,17 +27,13 @@ class SuspendUserDialogContainer extends Component {
notify,
} = this.props;
hideSuspendUserDialog();
try {
await suspendUser({ id: userId, message, until });
notify(
'success',
t('suspenduser.notify_suspend_until', username, timeago(until))
);
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({ commentId, status: 'REJECTED' });
}
} catch (err) {
notify('error', getErrorMessages(err));
await suspendUser({ id: userId, message, until });
notify(
'success',
t('suspenduser.notify_suspend_until', username, timeago(until))
);
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({ commentId, status: 'REJECTED' });
}
};
@@ -26,7 +26,7 @@ import UserDetailComment from './UserDetailComment';
import update from 'immutability-helper';
import { showBanUserDialog } from 'actions/banUserDialog';
import { showSuspendUserDialog } from 'actions/suspendUserDialog';
import { notifyOnMutationError, notifyOnDataError } from 'coral-framework/hocs';
import { notify } from 'coral-framework/actions/notification';
const commentConnectionFragment = gql`
fragment CoralAdmin_UserDetail_CommentConnection on CommentConnection {
@@ -272,6 +272,7 @@ const mapDispatchToProps = dispatch => ({
viewUserDetail,
hideUserDetail,
toggleSelectAllCommentInUserDetail,
notify,
},
dispatch
),
@@ -282,7 +283,5 @@ export default compose(
withUserDetailQuery,
withSetCommentStatus,
withUnbanUser,
withUnsuspendUser,
notifyOnMutationError(['unbanUser', 'unsuspendUser', 'setCommentStatus']),
notifyOnDataError
withUnsuspendUser
)(UserDetailContainer);
@@ -15,7 +15,6 @@ import { handleFlaggedUsernameChange } from '../graphql';
import { notify } from 'coral-framework/actions/notification';
import { isFlaggedUserDangling } from '../utils';
import t from 'coral-framework/services/i18n';
import { notifyOnMutationError, notifyOnDataError } from 'coral-framework/hocs';
import FlaggedAccounts from '../components/FlaggedAccounts';
import FlaggedUser from '../containers/FlaggedUser';
@@ -295,7 +294,6 @@ const mapDispatchToProps = dispatch =>
export default compose(
connect(null, mapDispatchToProps),
withApproveUsername,
notifyOnMutationError(['approveUsername']),
withQuery(
gql`
query TalkAdmin_Community_FlaggedAccounts {
@@ -334,6 +332,5 @@ export default compose(
fetchPolicy: 'network-only',
},
}
),
notifyOnDataError
)
)(FlaggedAccountsContainer);
@@ -16,7 +16,7 @@ import { appendNewNodes } from 'plugin-api/beta/client/utils';
import update from 'immutability-helper';
import { Spinner } from 'coral-ui';
import withQuery from 'coral-framework/hocs/withQuery';
import { notifyOnMutationError, notifyOnDataError } from 'coral-framework/hocs';
import { notify } from 'coral-framework/actions/notification';
class PeopleContainer extends React.Component {
timer = null;
@@ -132,6 +132,7 @@ const mapDispatchToProps = dispatch =>
viewUserDetail,
showSuspendUserDialog,
showBanUserDialog,
notify,
},
dispatch
);
@@ -205,7 +206,6 @@ export default compose(
withSetUserRole,
withUnsuspendUser,
withUnbanUser,
notifyOnMutationError(['setUserRole', 'unsuspendUser', 'unbanUser']),
withQuery(
gql`
query TalkAdmin_Community_People {
@@ -241,6 +241,5 @@ export default compose(
fetchPolicy: 'network-only',
},
}
),
notifyOnDataError
)
)(PeopleContainer);
@@ -5,7 +5,6 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { compose } from 'react-apollo';
import { notify } from 'coral-framework/actions/notification';
import { notifyOnMutationError } from 'coral-framework/hocs';
const mapStateToProps = state => ({
user: state.community.user,
@@ -23,6 +22,5 @@ const mapDispatchToProps = dispatch =>
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withRejectUsername,
notifyOnMutationError(['rejectUsername'])
withRejectUsername
)(RejectUsernameDialog);
@@ -12,7 +12,7 @@ import TechSettings from './TechSettings';
import ModerationSettings from './ModerationSettings';
import { clearPending, setActiveSection } from '../../../actions/configure';
import Configure from '../components/Configure';
import { notifyOnMutationError, notifyOnDataError } from 'coral-framework/hocs';
import { notify } from 'coral-framework/actions/notification';
class ConfigureContainer extends Component {
savePending = async () => {
@@ -83,16 +83,15 @@ const mapDispatchToProps = dispatch =>
{
clearPending,
setActiveSection,
notify,
},
dispatch
);
export default compose(
withUpdateSettings,
notifyOnMutationError(['updateSettings']),
withConfigureQuery,
notifyOnDataError,
connect(mapStateToProps, mapDispatchToProps),
withUpdateSettings,
withConfigureQuery,
withMergedSettings('root.settings', 'pending', 'mergedSettings')
)(ConfigureContainer);
@@ -35,7 +35,6 @@ import { Spinner } from 'coral-ui';
import Moderation from '../components/Moderation';
import Comment from './Comment';
import baseQueueConfig from '../queueConfig';
import { notifyOnMutationError, notifyOnDataError } from 'coral-framework/hocs';
function prepareNotificationText(text) {
return truncate(text, { length: 50 }).replace('\n', ' ');
@@ -535,7 +534,5 @@ export default compose(
withQueueConfig(baseQueueConfig),
connect(mapStateToProps, mapDispatchToProps),
withSetCommentStatus,
notifyOnMutationError(['setCommentStatus']),
withModQueueQuery,
notifyOnDataError
withModQueueQuery
)(ModerationContainer);
@@ -7,7 +7,9 @@ import {
withUpdateAssetStatus,
withCloseAsset,
} from 'coral-framework/graphql/mutations';
import { notifyOnMutationError } from 'coral-framework/hocs';
import { notify } from 'coral-framework/actions/notification';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class AssetStatusInfoContainer extends React.Component {
openAsset = () =>
@@ -43,11 +45,19 @@ const withAssetStatusInfoFragments = withFragments({
`,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
notify,
},
dispatch
);
const enhance = compose(
connect(null, mapDispatchToProps),
withAssetStatusInfoFragments,
withUpdateAssetStatus,
withCloseAsset,
notifyOnMutationError(['updateAssetStatus', 'closeAsset'])
withCloseAsset
);
export default enhance(AssetStatusInfoContainer);
@@ -8,7 +8,7 @@ import { withUpdateAssetSettings } from 'coral-framework/graphql/mutations';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { clearPending, updatePending } from '../../../actions/configure';
import { notifyOnMutationError } from 'coral-framework/hocs';
import { notify } from 'coral-framework/actions/notification';
const slots = ['streamSettings'];
@@ -129,15 +129,15 @@ const mapDispatchToProps = dispatch =>
{
clearPending,
updatePending,
notify,
},
dispatch
);
const enhance = compose(
connect(mapStateToProps, mapDispatchToProps),
withSettingsFragments,
withUpdateAssetSettings,
notifyOnMutationError(['updateAssetSettings']),
connect(mapStateToProps, mapDispatchToProps),
withMergedSettings('asset.settings', 'pending', 'mergedSettings')
);
@@ -1,5 +1,18 @@
import { compose } from 'react-apollo';
import { withChangeUsername } from 'coral-framework/graphql/mutations';
import ChangeUsername from '../components/ChangeUsername';
import { notify } from 'coral-framework/actions/notification';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
export default compose(withChangeUsername)(ChangeUsername);
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
notify,
},
dispatch
);
export default compose(connect(null, mapDispatchToProps), withChangeUsername)(
ChangeUsername
);
-2
View File
@@ -6,5 +6,3 @@ export { default as withEmit } from './withEmit';
export { default as excludeIf } from './excludeIf';
export { default as connect } from './connect';
export { default as withMergedSettings } from './withMergedSettings';
export { default as notifyOnMutationError } from './notifyOnMutationError';
export { default as notifyOnDataError } from './notifyOnDataError';
@@ -1,32 +0,0 @@
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { notify } from 'coral-framework/actions/notification';
import { branch, lifecycle, compose } from 'recompose';
import { get } from 'lodash';
const notifyOnMutationError = compose(
branch(
({ notify }) => !notify,
connect(null, dispatch =>
bindActionCreators(
{
notify,
},
dispatch
)
)
),
lifecycle({
componentWillReceiveProps(next) {
if (
get(next, 'data.error.message') &&
get(this.props, 'data.error.message') !==
get(next, 'data.error.message')
) {
return this.props.notify('error', next.data.error.message);
}
},
})
);
export default notifyOnMutationError;
@@ -1,38 +0,0 @@
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { compose } from 'react-apollo';
import { notify } from 'coral-framework/actions/notification';
import { forEachError } from 'coral-framework/utils';
import { withProps, branch } from 'recompose';
const notifyOnMutationError = keys =>
compose(
branch(
({ notify }) => !notify,
connect(null, dispatch =>
bindActionCreators(
{
notify,
},
dispatch
)
)
),
withProps(ownProps =>
keys.reduce((props, key) => {
props[key] = async (...args) => {
try {
return await ownProps[key](...args);
} catch (e) {
forEachError(e, ({ msg }) => {
ownProps.notify('error', msg);
});
throw e;
}
};
return props;
}, {})
)
);
export default notifyOnMutationError;
+37 -6
View File
@@ -4,7 +4,11 @@ import merge from 'lodash/merge';
import uniq from 'lodash/uniq';
import flatten from 'lodash/flatten';
import isEmpty from 'lodash/isEmpty';
import { getDefinitionName, getResponseErrors } from '../utils';
import {
getDefinitionName,
getResponseErrors,
getErrorMessages,
} from '../utils';
import PropTypes from 'prop-types';
import t from 'coral-framework/services/i18n';
import hoistStatics from 'recompose/hoistStatics';
@@ -27,11 +31,7 @@ class ResponseError {
}
}
/**
* Exports a HOC with the same signature as `graphql`, that will
* apply mutation options registered in the graphRegistry.
*/
export default (document, config = {}) =>
const createHOC = (document, config, { notifyOnError = true }) =>
hoistStatics(WrappedComponent => {
config = {
...config,
@@ -46,10 +46,25 @@ export default (document, config = {}) =>
graphql: PropTypes.object,
};
static propTypes = {
notify: PropTypes.func,
};
get graphqlRegistry() {
return this.context.graphql.registry;
}
notifyErrors(messages) {
if (this.props.notify) {
this.props.notify('error', messages);
} else {
console.error(
'`notifyOnError` is set to `true` but missing `notify` property'
);
console.error(messages);
}
}
resolveDocument(documentOrCallback) {
return this.context.graphql.resolveDocument(
documentOrCallback,
@@ -165,6 +180,11 @@ export default (document, config = {}) =>
variables,
error,
});
// Show errors as notifications.
if (notifyOnError) {
this.notifyErrors(getErrorMessages(error));
}
throw error;
});
};
@@ -213,3 +233,14 @@ export default (document, config = {}) =>
}
};
});
/**
* Exports a HOC with the same signature as `graphql`, that will
* apply mutation options registered in the graphRegistry.
*/
export default (document, config = {}) => settingsOrComponent => {
if (typeof settingsOrComponent === 'function') {
return createHOC(document, config, {})(settingsOrComponent);
}
return createHOC(document, config, settingsOrComponent);
};
+40 -6
View File
@@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
import hoistStatics from 'recompose/hoistStatics';
import { getOperationName } from 'apollo-client/queries/getFromAST';
import throttle from 'lodash/throttle';
import get from 'lodash/get';
const withSkipOnErrors = reducer => (prev, action, ...rest) => {
if (
@@ -36,15 +37,12 @@ function networkStatusToString(networkStatus) {
return 'ready';
case 8:
return 'error';
default:
throw new Error(`Unknown network status ${networkStatus}`);
}
throw new Error(`Unknown network status ${networkStatus}`);
}
/**
* Exports a HOC with the same signature as `graphql`, that will
* apply query options registered in the graphRegistry.
*/
export default (document, config = {}) =>
const createHOC = (document, config, { notifyOnError = true }) =>
hoistStatics(WrappedComponent => {
return class WithQuery extends React.Component {
static contextTypes = {
@@ -53,6 +51,10 @@ export default (document, config = {}) =>
client: PropTypes.object,
};
static propTypes = {
notify: PropTypes.func,
};
// Lazily resolve fragments from graphRegistry to support circular dependencies.
memoized = null;
resolvedDocument = null;
@@ -166,10 +168,31 @@ export default (document, config = {}) =>
return () => this.client.networkInterface.unsubscribe(id);
};
notifyErrors(messages) {
if (this.props.notify) {
this.props.notify('error', messages);
} else {
console.error(
'`notifyOnError` is set to `true` but missing `notify` property'
);
console.error(messages);
}
}
nextData(data) {
this.apolloData = data;
this.emitWhenNeeded(data);
if (
get(data, 'error.message') &&
get(this, 'data.error.message') !== get(data, 'error.message')
) {
// Show errors as notifications.
if (notifyOnError) {
this.notifyErrors(data.error.message);
}
}
// If data was previously set, we update it in a immutable way.
if (this.data) {
if (this.data.loading && !data.loading) {
@@ -319,3 +342,14 @@ export default (document, config = {}) =>
}
};
});
/**
* Exports a HOC with the same signature as `graphql`, that will
* apply query options registered in the graphRegistry.
*/
export default (document, config = {}) => settingsOrComponent => {
if (typeof settingsOrComponent === 'function') {
return createHOC(document, config, {})(settingsOrComponent);
}
return createHOC(document, config, settingsOrComponent);
};