PLT-5709 Global error handler (#187)
* WIP Global error handler Adding components Adding actions * Add general errors where errors are caught * Filter out non displayable errors * Initial setup to email errors * Add current user/team to error email * Fix a few lints * Ensure errors are cleared when reporting a problem * Add clear all and internationalization * Fix trailing comma issue * fix lints * Use Formatted Text component * Move error list out of channel * Add mobile prefix to i18n id for connection error * Set shouldRender to true in RightMenuDrawerItem.defaultProps * Update component name for RightMenuDrawerItem * Clean up propTypes in RightMenuDrawerItem * Clean up styles in RightMenuDrawerItem * Use selector to filter displayable errors * Clarify code for constructing wrapper style in ErrorList * Avoid indenting email body for error report * Remove extraneous params from errors actions * Rename action type for logging errors * Rename action for logging errors * Set shouldRender to true in RightMenuDrawerItem.defaultProps * Make logged errors displayable by default * Don't log error when websocket connection is closed * Hide button to dismiss all errors if ShowErrorsList isn't enabled * Display only last error in alert bar when ShowErrorsList is disabled * Add error logging where missing in actions * Always show button to report problem * Only include errors in report problem email if any have been logged * Clarify subject of report problem email * Set ShowErrorsList to false in default config.json * Remove unused component + translation for connection errors * Capture entire error object not just message * Display fallback if error.message is blank * Log errors in login actions * Fix construction of errors email body
This commit is contained in:
parent
f3dbb72f65
commit
e17eb6c237
25 changed files with 599 additions and 97 deletions
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
import {AsyncStorage} from 'react-native';
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {logError, getLogErrorAction} from 'service/actions/errors';
|
||||
import {ChannelTypes, GeneralTypes, TeamsTypes, UsersTypes} from 'service/constants';
|
||||
|
||||
export function loadStorage() {
|
||||
|
|
@ -49,7 +49,13 @@ export function loadStorage() {
|
|||
dispatch(batchActions(actions), getState);
|
||||
} catch (error) {
|
||||
// Error loading data
|
||||
dispatch({type: GeneralTypes.REMOVED_APP_CREDENTIALS, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: ChannelTypes.REMOVED_APP_CREDENTIALS,
|
||||
error
|
||||
},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -90,7 +96,7 @@ export async function updateStorage(key, data) {
|
|||
|
||||
return mergedStorageData;
|
||||
} catch (error) {
|
||||
// TODO: Need to handle this error
|
||||
logError(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -114,7 +120,7 @@ export function removeStorage() {
|
|||
await AsyncStorage.removeItem('storage');
|
||||
}
|
||||
} catch (error) {
|
||||
// TODO: Error removing data
|
||||
logError(error);
|
||||
}
|
||||
dispatch({type: UsersTypes.RESET_LOGOUT_STATE}, getState);
|
||||
};
|
||||
|
|
|
|||
127
app/components/error_list/error_list.js
Normal file
127
app/components/error_list/error_list.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
StyleSheet,
|
||||
View,
|
||||
TouchableOpacity
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import GeneralError from './general_error';
|
||||
import Config from 'assets/config.json';
|
||||
|
||||
const {width: deviceWidth} = Dimensions.get('window');
|
||||
|
||||
const style = StyleSheet.create({
|
||||
closeButton: {
|
||||
height: 20,
|
||||
width: 20,
|
||||
borderRadius: 10,
|
||||
marginBottom: 5,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
closeButtonContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 5
|
||||
},
|
||||
closeButtonText: {
|
||||
color: '#fff'
|
||||
},
|
||||
container: {
|
||||
paddingTop: 15,
|
||||
paddingBottom: 15,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: 75
|
||||
},
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: deviceWidth,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: 'rgba(255, 116, 92, 1)',
|
||||
zIndex: 99999
|
||||
}
|
||||
});
|
||||
|
||||
export default class ErrorList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
dismissError: PropTypes.func.isRequired,
|
||||
clearErrors: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
errors: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
renderErrorsList() {
|
||||
const {errors} = this.props;
|
||||
if (Config.ShowErrorsList) {
|
||||
return errors.map((error, index) => (
|
||||
<GeneralError
|
||||
key={index}
|
||||
dismiss={() => this.props.actions.dismissError(index)}
|
||||
error={error.error}
|
||||
/>
|
||||
));
|
||||
}
|
||||
const lastErrorIndex = errors.length - 1;
|
||||
const lastError = errors[lastErrorIndex];
|
||||
if (lastError) {
|
||||
return (
|
||||
<GeneralError
|
||||
dismiss={() => this.props.actions.dismissError(lastErrorIndex)}
|
||||
error={lastError.error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderDismissAll() {
|
||||
if (this.props.errors.length <= 1 || !Config.ShowErrorsList) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={style.closeButtonContainer}
|
||||
onPress={() => this.props.actions.clearErrors()}
|
||||
>
|
||||
<View style={style.closeButton}>
|
||||
<Icon
|
||||
name='close'
|
||||
size={10}
|
||||
color='#fff'
|
||||
/>
|
||||
</View>
|
||||
<FormattedText
|
||||
id='mobile.components.error_list.dismiss_all'
|
||||
defaultMessage='Dismiss All'
|
||||
style={style.closeButtonText}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const wrapperStyle = [style.wrapper];
|
||||
if (!this.props.errors.length) {
|
||||
wrapperStyle.push({height: 0});
|
||||
}
|
||||
return (
|
||||
<View style={wrapperStyle}>
|
||||
<View style={style.container}>
|
||||
{this.renderErrorsList()}
|
||||
{this.renderDismissAll()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
27
app/components/error_list/error_list_container.js
Normal file
27
app/components/error_list/error_list_container.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getDisplayableErrors} from 'service/selectors/errors';
|
||||
import {dismissError, clearErrors} from 'service/actions/errors';
|
||||
|
||||
import ErrorList from './error_list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
errors: getDisplayableErrors(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
dismissError,
|
||||
clearErrors
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ErrorList);
|
||||
71
app/components/error_list/general_error.js
Normal file
71
app/components/error_list/general_error.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
buttonContainer: {
|
||||
width: 25,
|
||||
height: 25,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
buttons: {
|
||||
marginHorizontal: 15
|
||||
},
|
||||
container: {
|
||||
alignSelf: 'stretch',
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
message: {
|
||||
flex: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
});
|
||||
|
||||
function GeneralError(props) {
|
||||
const {error, dismiss} = props;
|
||||
let message = error.message;
|
||||
if (!message) {
|
||||
if (error instanceof Error) {
|
||||
message = error.toString();
|
||||
} else {
|
||||
message = 'An error occurred.';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<Text style={style.message}>
|
||||
{error.message}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={style.buttonContainer}
|
||||
onPress={dismiss}
|
||||
>
|
||||
<Icon
|
||||
name='close'
|
||||
size={20}
|
||||
color='#fff'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
GeneralError.propTypes = {
|
||||
dismiss: PropTypes.func.isRequired,
|
||||
error: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default GeneralError;
|
||||
6
app/components/error_list/index.js
Normal file
6
app/components/error_list/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import ErrorListContainer from './error_list_container';
|
||||
|
||||
export default ErrorListContainer;
|
||||
|
|
@ -16,7 +16,7 @@ import Drawer from 'app/components/drawer';
|
|||
import FormattedText from 'app/components/formatted_text';
|
||||
import {RouteTransitions} from 'app/navigation/routes';
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
|
||||
import ErrorList from 'app/components/error_list';
|
||||
import NavigationModal from './navigation_modal';
|
||||
|
||||
const navigationPanResponder = NavigationExperimental.Card.CardStackPanResponder;
|
||||
|
|
@ -128,6 +128,7 @@ class Router extends React.Component {
|
|||
return (
|
||||
<View style={{flex: 1, flexDirection: 'column-reverse'}}>
|
||||
<View style={{flex: 1}}>
|
||||
<ErrorList/>
|
||||
{renderedScenes}
|
||||
</View>
|
||||
{navBar}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import {
|
|||
ScrollView,
|
||||
StyleSheet,
|
||||
Platform,
|
||||
View
|
||||
View,
|
||||
Linking
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
|
|
@ -17,14 +18,43 @@ import RightMenuDrawerItem from './right_menu_drawer_item';
|
|||
|
||||
export default class RightMenuDrawer extends React.Component {
|
||||
static propTypes = {
|
||||
errors: React.PropTypes.array.isRequired,
|
||||
currentUserId: React.PropTypes.string.isRequired,
|
||||
currentTeamId: React.PropTypes.string.isRequired,
|
||||
actions: React.PropTypes.shape({
|
||||
goToModalAccountSettings: React.PropTypes.func.isRequired,
|
||||
goToModalSelectTeam: React.PropTypes.func.isRequired,
|
||||
clearErrors: React.PropTypes.func.isRequired,
|
||||
logout: React.PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
theme: React.PropTypes.object
|
||||
};
|
||||
|
||||
openErrorEmail = () => {
|
||||
const recipient = 'feedback@mattermost.com';
|
||||
const subject = 'Problem with Mattermost React Native app';
|
||||
Linking.openURL(
|
||||
`mailto:${recipient}?subject=${subject}&body=${this.errorEmailBody()}`
|
||||
);
|
||||
this.props.actions.clearErrors();
|
||||
};
|
||||
|
||||
errorEmailBody = () => {
|
||||
const {currentUserId, currentTeamId, errors} = this.props;
|
||||
let contents = [
|
||||
`Current User Id: ${currentUserId}`,
|
||||
`Current Team Id: ${currentTeamId}`
|
||||
];
|
||||
if (errors.length) {
|
||||
contents = contents.concat([
|
||||
'',
|
||||
'Errors:',
|
||||
JSON.stringify(errors.map((e) => e.error))
|
||||
]);
|
||||
}
|
||||
return contents.join('\n');
|
||||
};
|
||||
|
||||
render() {
|
||||
const Styles = getStyleSheet(this.props.theme);
|
||||
|
||||
|
|
@ -66,7 +96,7 @@ export default class RightMenuDrawer extends React.Component {
|
|||
defaultMessage='Help'
|
||||
/>
|
||||
</RightMenuDrawerItem>
|
||||
<RightMenuDrawerItem>
|
||||
<RightMenuDrawerItem onPress={this.openErrorEmail}>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='phone'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux';
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToModalAccountSettings, goBack, goToModalSelectTeam} from 'app/actions/navigation';
|
||||
import {clearErrors} from 'service/actions/errors';
|
||||
|
||||
import {logout} from 'service/actions/users';
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
|
|
@ -14,7 +15,10 @@ import RightMenuDrawer from './right_menu_drawer';
|
|||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state)
|
||||
theme: getTheme(state),
|
||||
errors: state.errors,
|
||||
currentUserId: state.entities.users.currentId,
|
||||
currentTeamId: state.entities.teams.currentId
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +28,7 @@ function mapDispatchToProps(dispatch) {
|
|||
goToModalAccountSettings,
|
||||
goBack,
|
||||
goToModalSelectTeam,
|
||||
clearErrors,
|
||||
logout
|
||||
}, dispatch)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,40 +1,36 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {StyleSheet, TouchableHighlight, View} from 'react-native';
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
const styles = StyleSheet.create({
|
||||
item: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
paddingHorizontal: 10
|
||||
}
|
||||
});
|
||||
|
||||
export default class MainMenuItem extends React.Component {
|
||||
export default class RightMenuDrawerItem extends Component {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
onPress: React.PropTypes.func,
|
||||
style: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.array
|
||||
])
|
||||
children: PropTypes.node,
|
||||
onPress: PropTypes.func,
|
||||
style: View.propTypes.style
|
||||
}
|
||||
|
||||
render() {
|
||||
const {onPress, style, children} = this.props;
|
||||
return (
|
||||
<TouchableHighlight
|
||||
underlayColor='rgba(255, 255, 255, 0.3)'
|
||||
onPress={this.props.onPress}
|
||||
style={this.props.style}
|
||||
onPress={onPress}
|
||||
style={style}
|
||||
>
|
||||
<View style={Styles.item}>
|
||||
{this.props.children}
|
||||
<View style={styles.item}>
|
||||
{children}
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"DefaultServerUrl": "http://localhost:8065",
|
||||
"TestServerUrl": "http://localhost:8065",
|
||||
"DefaultLocale": "en",
|
||||
"DefaultTheme": "default"
|
||||
"DefaultTheme": "default",
|
||||
"ShowErrorsList": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1502,6 +1502,7 @@
|
|||
"mobile.channel_list.privateChannel": "Private Channel",
|
||||
"mobile.channel_list.publicChannel": "Public Channel",
|
||||
"mobile.components.channels_list_view.yourChannels": "Your channels:",
|
||||
"mobile.components.error_list.dismiss_all": "Dismiss All",
|
||||
"mobile.components.select_server_view.enterServerUrl": "Enter Server URL",
|
||||
"mobile.components.select_server_view.continue": "Continue",
|
||||
"mobile.components.select_server_view.siteUrlPlaceholder": "https://mattermost.example.com",
|
||||
|
|
|
|||
|
|
@ -8,16 +8,23 @@ import {
|
|||
PreferencesTypes,
|
||||
UsersTypes
|
||||
} from 'service/constants';
|
||||
import {forceLogoutIfNecessary} from './helpers';
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import Client from 'service/client';
|
||||
|
||||
import {logError, getLogErrorAction} from './errors';
|
||||
import {forceLogoutIfNecessary} from './helpers';
|
||||
|
||||
export function selectChannel(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: channelId
|
||||
}, getState);
|
||||
try {
|
||||
dispatch({
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: channelId
|
||||
}, getState);
|
||||
} catch (error) {
|
||||
logError(error)(dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +52,8 @@ export function createChannel(channel, userId) {
|
|||
{
|
||||
type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
|
||||
error
|
||||
}
|
||||
},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -96,14 +104,9 @@ export function createDirectChannel(teamId, userId, otherUserId) {
|
|||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: ChannelTypes.CREATE_CHANNEL_FAILURE,
|
||||
error
|
||||
},
|
||||
{
|
||||
type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
|
||||
error
|
||||
}
|
||||
{type: ChannelTypes.CREATE_CHANNEL_FAILURE, error},
|
||||
{type: ChannelTypes.CHANNEL_MEMBERS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -150,7 +153,11 @@ export function updateChannel(channel) {
|
|||
updated = await Client.updateChannel(channel);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.UPDATE_CHANNEL_FAILURE, error}, getState);
|
||||
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.UPDATE_CHANNEL_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +188,11 @@ export function updateChannelNotifyProps(userId, teamId, channelId, props) {
|
|||
notifyProps = await Client.updateChannelNotifyProps(teamId, data);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.NOTIFY_PROPS_FAILURE, error}, getState);
|
||||
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.NOTIFY_PROPS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -209,7 +220,10 @@ export function getChannel(teamId, channelId) {
|
|||
data = await Client.getChannel(teamId, channelId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.CHANNELS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.CHANNELS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -251,14 +265,9 @@ export function fetchMyChannelsAndMembers(teamId) {
|
|||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: ChannelTypes.CHANNELS_FAILURE,
|
||||
error
|
||||
},
|
||||
{
|
||||
type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
|
||||
error
|
||||
}
|
||||
{type: ChannelTypes.CHANNELS_FAILURE, error},
|
||||
{type: ChannelTypes.CHANNEL_MEMBERS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
|
@ -293,7 +302,10 @@ export function getMyChannelMembers(teamId) {
|
|||
channelMembers = await channelMembersRequest;
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.CHANNEL_MEMBERS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.CHANNEL_MEMBERS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -317,7 +329,10 @@ export function leaveChannel(teamId, channelId) {
|
|||
await Client.leaveChannel(teamId, channelId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.LEAVE_CHANNEL_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.LEAVE_CHANNEL_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +361,10 @@ export function joinChannel(userId, teamId, channelId, channelName) {
|
|||
}
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.JOIN_CHANNEL_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.JOIN_CHANNEL_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -385,7 +403,10 @@ export function deleteChannel(teamId, channelId) {
|
|||
await Client.deleteChannel(teamId, channelId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.DELETE_CHANNEL_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.DELETE_CHANNEL_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -430,7 +451,10 @@ export function viewChannel(teamId, channelId) {
|
|||
await Client.viewChannel(teamId, channelId, prevChannelId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.UPDATE_LAST_VIEWED_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.UPDATE_LAST_VIEWED_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -447,7 +471,10 @@ export function getMoreChannels(teamId, offset, limit = Constants.CHANNELS_CHUNK
|
|||
channels = await Client.getMoreChannels(teamId, offset, limit);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.MORE_CHANNELS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.MORE_CHANNELS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -474,7 +501,10 @@ export function searchMoreChannels(teamId, term) {
|
|||
channels = await Client.searchMoreChannels(teamId, term);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.MORE_CHANNELS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.MORE_CHANNELS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -499,7 +529,10 @@ export function getChannelStats(teamId, channelId) {
|
|||
stat = await Client.getChannelStats(teamId, channelId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.CHANNEL_STATS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.CHANNEL_STATS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -523,7 +556,10 @@ export function addChannelMember(teamId, channelId, userId) {
|
|||
await Client.addChannelMember(teamId, channelId, userId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.ADD_CHANNEL_MEMBER_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.ADD_CHANNEL_MEMBER_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -548,7 +584,10 @@ export function removeChannelMember(teamId, channelId, userId) {
|
|||
await Client.removeChannelMember(teamId, channelId, userId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.REMOVE_CHANNEL_MEMBER_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.REMOVE_CHANNEL_MEMBER_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -668,7 +707,10 @@ export function autocompleteChannels(teamId, term) {
|
|||
data = await Client.autocompleteChannels(teamId, term);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: ChannelTypes.AUTOCOMPLETE_CHANNELS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: ChannelTypes.AUTOCOMPLETE_CHANNELS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
40
service/actions/errors.js
Normal file
40
service/actions/errors.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ErrorTypes} from 'service/constants';
|
||||
|
||||
export function dismissErrorObject(index) {
|
||||
return {
|
||||
type: ErrorTypes.DISMISS_ERROR,
|
||||
index
|
||||
};
|
||||
}
|
||||
|
||||
export function dismissError(index) {
|
||||
return async (dispatch) => {
|
||||
dispatch(dismissErrorObject(index));
|
||||
};
|
||||
}
|
||||
|
||||
export function getLogErrorAction(error, displayable = true) {
|
||||
return {
|
||||
type: ErrorTypes.LOG_ERROR,
|
||||
displayable,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
export function logError(error, displayable = true) {
|
||||
return async (dispatch) => {
|
||||
// do something with the incoming error
|
||||
// like sending it to analytics
|
||||
|
||||
dispatch(getLogErrorAction(error, displayable));
|
||||
};
|
||||
}
|
||||
|
||||
export function clearErrors() {
|
||||
return async (dispatch) => {
|
||||
dispatch({type: ErrorTypes.CLEAR_ERRORS});
|
||||
};
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import {batchActions} from 'redux-batched-actions';
|
|||
|
||||
import Client from 'service/client';
|
||||
import {FilesTypes} from 'service/constants';
|
||||
import {getLogErrorAction} from 'service/actions/errors';
|
||||
import {forceLogoutIfNecessary} from './helpers';
|
||||
|
||||
export function getFilesForPost(teamId, channelId, postId) {
|
||||
|
|
@ -16,7 +17,10 @@ export function getFilesForPost(teamId, channelId, postId) {
|
|||
files = await Client.getFileInfosForPost(teamId, channelId, postId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: FilesTypes.FETCH_FILES_FOR_POST_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: FilesTypes.FETCH_FILES_FOR_POST_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import Client from 'service/client';
|
||||
import {bindClientFunc, FormattedError} from './helpers.js';
|
||||
import {GeneralTypes} from 'service/constants';
|
||||
import {getMyChannelMembers} from './channels';
|
||||
import {getLogErrorAction} from 'service/actions/errors';
|
||||
|
||||
export function getPing() {
|
||||
return async (dispatch, getState) => {
|
||||
|
|
@ -19,11 +22,17 @@ export function getPing() {
|
|||
data = await Client.getPing();
|
||||
if (!data.version) {
|
||||
// successful ping but not the right return data
|
||||
dispatch({type: GeneralTypes.PING_FAILURE, error: pingError}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: GeneralTypes.PING_FAILURE, error: pingError},
|
||||
getLogErrorAction(pingError)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch({type: GeneralTypes.PING_FAILURE, error: pingError}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: GeneralTypes.PING_FAILURE, error: pingError},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
import Client from 'service/client';
|
||||
import {UsersTypes} from 'service/constants';
|
||||
import {getLogErrorAction} from './errors';
|
||||
const HTTP_UNAUTHORIZED = 401;
|
||||
|
||||
export async function forceLogoutIfNecessary(err, dispatch) {
|
||||
|
|
@ -50,7 +52,10 @@ export function bindClientFunc(clientFunc, request, success, failure, ...args) {
|
|||
data = await clientFunc(...args);
|
||||
} catch (err) {
|
||||
forceLogoutIfNecessary(err, dispatch);
|
||||
dispatch(requestFailure(failure, err), getState);
|
||||
dispatch(batchActions([
|
||||
requestFailure(failure, err),
|
||||
getLogErrorAction(err)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Client from 'service/client';
|
|||
import {batchActions} from 'redux-batched-actions';
|
||||
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
|
||||
import {Constants, PostsTypes} from 'service/constants';
|
||||
import {getLogErrorAction} from './errors';
|
||||
import {getProfilesByIds, getStatusesByIds} from './users';
|
||||
|
||||
async function getProfilesAndStatusesForPosts(list, dispatch, getState) {
|
||||
|
|
@ -65,7 +66,10 @@ export function deletePost(teamId, post) {
|
|||
await Client.deletePost(teamId, post.channel_id, post.id);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PostsTypes.DELETE_POST_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PostsTypes.DELETE_POST_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +104,10 @@ export function getPost(teamId, channelId, postId) {
|
|||
getProfilesAndStatusesForPosts(post, dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PostsTypes.GET_POST_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PostsTypes.GET_POST_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +134,10 @@ export function getPosts(teamId, channelId, offset = 0, limit = Constants.POST_C
|
|||
getProfilesAndStatusesForPosts(posts, dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PostsTypes.GET_POSTS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PostsTypes.GET_POSTS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +166,10 @@ export function getPostsSince(teamId, channelId, since) {
|
|||
getProfilesAndStatusesForPosts(posts, dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PostsTypes.GET_POSTS_SINCE_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PostsTypes.GET_POSTS_SINCE_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +198,10 @@ export function getPostsBefore(teamId, channelId, postId, offset = 0, limit = Co
|
|||
getProfilesAndStatusesForPosts(posts, dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PostsTypes.GET_POSTS_BEFORE_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PostsTypes.GET_POSTS_BEFORE_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +230,10 @@ export function getPostsAfter(teamId, channelId, postId, offset = 0, limit = Con
|
|||
getProfilesAndStatusesForPosts(posts, dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PostsTypes.GET_POSTS_AFTER_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PostsTypes.GET_POSTS_AFTER_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {getPreferenceKey} from 'service/utils/preference_utils';
|
|||
|
||||
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
|
||||
|
||||
import {getLogErrorAction} from './errors';
|
||||
export function getMyPreferences() {
|
||||
return bindClientFunc(
|
||||
Client.getMyPreferences,
|
||||
|
|
@ -28,7 +29,10 @@ export function savePreferences(preferences) {
|
|||
await Client.savePreferences(preferences);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PreferencesTypes.SAVE_PREFERENCES_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PreferencesTypes.SAVE_PREFERENCES_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +56,10 @@ export function deletePreferences(preferences) {
|
|||
await Client.deletePreferences(preferences);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PreferencesTypes.DELETE_PREFERENCES_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PreferencesTypes.DELETE_PREFERENCES_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import Client from 'service/client';
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
import {Constants, TeamsTypes} from 'service/constants';
|
||||
import {getLogErrorAction} from './errors';
|
||||
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
|
||||
import {getProfilesByIds, getStatusesByIds} from './users';
|
||||
|
||||
|
|
@ -65,7 +66,10 @@ export function createTeam(userId, team) {
|
|||
created = await Client.createTeam(team);
|
||||
} catch (err) {
|
||||
forceLogoutIfNecessary(err, dispatch);
|
||||
dispatch({type: TeamsTypes.CREATE_TEAM_FAILURE, error: err}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: TeamsTypes.CREATE_TEAM_FAILURE, error: err},
|
||||
getLogErrorAction(err)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +131,10 @@ export function getTeamMember(teamId, userId) {
|
|||
getProfilesAndStatusesForMembers([userId], dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: TeamsTypes.TEAM_MEMBERS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: TeamsTypes.TEAM_MEMBERS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +160,10 @@ export function getTeamMembersByIds(teamId, userIds) {
|
|||
getProfilesAndStatusesForMembers(userIds, dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: TeamsTypes.TEAM_MEMBERS_FAILURE}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: TeamsTypes.TEAM_MEMBERS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
|
|
@ -184,9 +194,12 @@ export function addUserToTeam(teamId, userId) {
|
|||
|
||||
try {
|
||||
await Client.addUserToTeam(teamId, userId);
|
||||
} catch (err) {
|
||||
forceLogoutIfNecessary(err, dispatch);
|
||||
dispatch({type: TeamsTypes.ADD_TEAM_MEMBER_FAILURE, error: err}, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch(batchActions([
|
||||
{type: TeamsTypes.ADD_TEAM_MEMBER_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -213,9 +226,12 @@ export function removeUserFromTeam(teamId, userId) {
|
|||
|
||||
try {
|
||||
await Client.removeUserFromTeam(teamId, userId);
|
||||
} catch (err) {
|
||||
forceLogoutIfNecessary(err, dispatch);
|
||||
dispatch({type: TeamsTypes.REMOVE_TEAM_MEMBER_FAILURE, error: err}, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch(batchActions([
|
||||
{type: TeamsTypes.REMOVE_TEAM_MEMBER_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {batchActions} from 'redux-batched-actions';
|
|||
import Client from 'service/client';
|
||||
import {Constants, PreferencesTypes, UsersTypes, TeamsTypes} from 'service/constants';
|
||||
import {fetchTeams} from 'service/actions/teams';
|
||||
import {getLogErrorAction} from 'service/actions/errors';
|
||||
import {bindClientFunc, forceLogoutIfNecessary, debounce} from './helpers';
|
||||
|
||||
export function checkMfa(loginId) {
|
||||
|
|
@ -15,7 +16,10 @@ export function checkMfa(loginId) {
|
|||
dispatch({type: UsersTypes.CHECK_MFA_SUCCESS}, getState);
|
||||
return mfa;
|
||||
} catch (error) {
|
||||
dispatch({type: UsersTypes.CHECK_MFA_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.CHECK_MFA_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -36,7 +40,10 @@ export function login(loginId, password, mfaToken = '') {
|
|||
teamMembers = await teamMembersRequest;
|
||||
preferences = await preferencesRequest;
|
||||
} catch (error) {
|
||||
dispatch({type: UsersTypes.LOGIN_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.LOGIN_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +51,10 @@ export function login(loginId, password, mfaToken = '') {
|
|||
await fetchTeams()(dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.LOGIN_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.LOGIN_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +77,10 @@ export function login(loginId, password, mfaToken = '') {
|
|||
]), getState);
|
||||
}).
|
||||
catch((error) => {
|
||||
dispatch({type: UsersTypes.LOGIN_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.LOGIN_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
|
@ -81,7 +94,10 @@ export function loadMe() {
|
|||
user = await Client.getMe();
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.LOGIN_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.LOGIN_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +107,10 @@ export function loadMe() {
|
|||
preferences = await Client.getMyPreferences();
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PreferencesTypes.MY_PREFERENCES_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: PreferencesTypes.MY_PREFERENCES_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +118,10 @@ export function loadMe() {
|
|||
await fetchTeams()(dispatch, getState);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: TeamsTypes.FETCH_TEAMS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: TeamsTypes.FETCH_TEAMS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +131,10 @@ export function loadMe() {
|
|||
teamMembers = await Client.getMyTeamMembers();
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: TeamsTypes.MY_TEAM_MEMBERS_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: TeamsTypes.MY_TEAM_MEMBERS_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +182,10 @@ export function getProfiles(offset, limit = Constants.PROFILE_CHUNK_SIZE) {
|
|||
profiles = await Client.getProfiles(offset, limit);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.PROFILES_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.PROFILES_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +222,10 @@ export function getProfilesInTeam(teamId, offset, limit = Constants.PROFILE_CHUN
|
|||
profiles = await Client.getProfilesInTeam(teamId, offset, limit);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.PROFILES_IN_TEAM_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.PROFILES_IN_TEAM_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +255,10 @@ export function getProfilesInChannel(teamId, channelId, offset, limit = Constant
|
|||
profiles = await Client.getProfilesInChannel(teamId, channelId, offset, limit);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.PROFILES_IN_CHANNEL_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.PROFILES_IN_CHANNEL_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +288,10 @@ export function getProfilesNotInChannel(teamId, channelId, offset, limit = Const
|
|||
profiles = await Client.getProfilesNotInChannel(teamId, channelId, offset, limit);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.PROFILES_NOT_IN_CHANNEL_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.PROFILES_NOT_IN_CHANNEL_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +378,10 @@ export function autocompleteUsersInChannel(teamId, channelId, term) {
|
|||
data = await Client.autocompleteUsersInChannel(teamId, channelId, term);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.AUTOCOMPLETE_IN_CHANNEL_FAILURE, error}, getState);
|
||||
dispatch(batchActions([
|
||||
{type: UsersTypes.AUTOCOMPLETE_IN_CHANNEL_FAILURE, error},
|
||||
getLogErrorAction(error)
|
||||
]), getState);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
12
service/constants/errors.js
Normal file
12
service/constants/errors.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import keyMirror from 'service/utils/key_mirror';
|
||||
|
||||
const ErrorTypes = keyMirror({
|
||||
DISMISS_ERROR: null,
|
||||
LOG_ERROR: null,
|
||||
CLEAR_ERRORS: null
|
||||
});
|
||||
|
||||
export default ErrorTypes;
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import Constants from './constants';
|
||||
import ChannelTypes from './channels';
|
||||
import ErrorTypes from './errors';
|
||||
import GeneralTypes from './general';
|
||||
import UsersTypes from './users';
|
||||
import TeamsTypes from './teams';
|
||||
|
|
@ -19,6 +20,7 @@ const Preferences = {
|
|||
|
||||
export {
|
||||
Constants,
|
||||
ErrorTypes,
|
||||
GeneralTypes,
|
||||
UsersTypes,
|
||||
TeamsTypes,
|
||||
|
|
|
|||
27
service/reducers/errors/index.js
Normal file
27
service/reducers/errors/index.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ErrorTypes} from 'service/constants';
|
||||
|
||||
export default (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ErrorTypes.DISMISS_ERROR: {
|
||||
const nextState = [...state];
|
||||
nextState.splice(action.index, 1);
|
||||
|
||||
return nextState;
|
||||
}
|
||||
case ErrorTypes.LOG_ERROR: {
|
||||
const nextState = [...state];
|
||||
const {displayable, error} = action;
|
||||
nextState.push({displayable, error});
|
||||
|
||||
return nextState;
|
||||
}
|
||||
case ErrorTypes.CLEAR_ERRORS: {
|
||||
return [];
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
|
@ -2,9 +2,11 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import entities from './entities';
|
||||
import errors from './errors';
|
||||
import requests from './requests';
|
||||
|
||||
export default {
|
||||
entities,
|
||||
errors,
|
||||
requests
|
||||
};
|
||||
|
|
|
|||
6
service/selectors/errors.js
Normal file
6
service/selectors/errors.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
export function getDisplayableErrors(state) {
|
||||
return state.errors.filter((error) => error.displayable);
|
||||
}
|
||||
Loading…
Reference in a new issue