MM-11603 Add a retry screen when fetching teams fails (#2085)
This commit is contained in:
parent
b1dff8aa04
commit
c6ea5e6024
8 changed files with 401 additions and 7 deletions
|
|
@ -7,6 +7,7 @@ import {markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/c
|
|||
import {ChannelTypes, TeamTypes} from 'mattermost-redux/action_types';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
|
||||
|
|
@ -56,6 +57,8 @@ export function selectDefaultTeam() {
|
|||
|
||||
if (defaultTeam) {
|
||||
handleTeamChange(defaultTeam.id)(dispatch, getState);
|
||||
} else if (state.requests.teams.getTeams.status === RequestStatus.FAILURE || state.requests.teams.getMyTeams.status === RequestStatus.FAILURE) {
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_ERROR_TEAMS);
|
||||
} else {
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_NO_TEAMS);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export default class Root extends PureComponent {
|
|||
EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
|
||||
EventEmitter.on(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped);
|
||||
EventEmitter.on(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams);
|
||||
EventEmitter.on(NavigationTypes.NAVIGATION_ERROR_TEAMS, this.errorTeamsList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ export default class Root extends PureComponent {
|
|||
EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
|
||||
EventEmitter.off(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped);
|
||||
EventEmitter.off(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams);
|
||||
EventEmitter.off(NavigationTypes.NAVIGATION_ERROR_TEAMS, this.errorTeamsList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,11 +71,23 @@ export default class Root extends PureComponent {
|
|||
setTimeout(this.handleNoTeams, 200);
|
||||
return;
|
||||
}
|
||||
this.navigateToTeamsPage('SelectTeam');
|
||||
};
|
||||
|
||||
errorTeamsList = () => {
|
||||
if (!this.refs.provider) {
|
||||
setTimeout(this.errorTeamsList, 200);
|
||||
return;
|
||||
}
|
||||
this.navigateToTeamsPage('ErrorTeamsList');
|
||||
}
|
||||
|
||||
navigateToTeamsPage = (screen) => {
|
||||
const {currentUrl, navigator, theme} = this.props;
|
||||
const {intl} = this.refs.provider.getChildContext();
|
||||
|
||||
let navigatorButtons;
|
||||
let passProps = {theme};
|
||||
if (Platform.OS === 'android') {
|
||||
navigatorButtons = {
|
||||
rightButtons: [{
|
||||
|
|
@ -93,8 +107,16 @@ export default class Root extends PureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
if (screen === 'SelectTeam') {
|
||||
passProps = {
|
||||
...passProps,
|
||||
currentUrl,
|
||||
userWithoutTeams: true,
|
||||
};
|
||||
}
|
||||
|
||||
navigator.resetTo({
|
||||
screen: 'SelectTeam',
|
||||
screen,
|
||||
title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}),
|
||||
animated: false,
|
||||
backButtonTitle: '',
|
||||
|
|
@ -105,13 +127,9 @@ export default class Root extends PureComponent {
|
|||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
navigatorButtons,
|
||||
passProps: {
|
||||
currentUrl,
|
||||
userWithoutTeams: true,
|
||||
theme,
|
||||
},
|
||||
passProps,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
handleNotificationTapped = async () => {
|
||||
const {navigator} = this.props;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const NavigationTypes = keyMirror({
|
|||
NAVIGATION_CLOSE_MODAL: null,
|
||||
NAVIGATION_NO_TEAMS: null,
|
||||
RESTART_APP: null,
|
||||
NAVIGATION_ERROR_TEAMS: null,
|
||||
});
|
||||
|
||||
export default NavigationTypes;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ErrorTeamsList should match snapshot 1`] = `
|
||||
ShallowWrapper {
|
||||
"length": 1,
|
||||
Symbol(enzyme.__root__): [Circular],
|
||||
Symbol(enzyme.__unrendered__): <ErrorTeamsList
|
||||
actions={
|
||||
Object {
|
||||
"connection": [Function],
|
||||
"loadMe": [Function],
|
||||
"logout": [Function],
|
||||
"selectDefaultTeam": [Function],
|
||||
}
|
||||
}
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [Function],
|
||||
}
|
||||
}
|
||||
theme={Object {}}
|
||||
/>,
|
||||
Symbol(enzyme.__renderer__): Object {
|
||||
"batchedUpdates": [Function],
|
||||
"getNode": [Function],
|
||||
"render": [Function],
|
||||
"simulateEvent": [Function],
|
||||
"unmount": [Function],
|
||||
},
|
||||
Symbol(enzyme.__node__): Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {
|
||||
"children": Array [
|
||||
<Connect(StatusBar) />,
|
||||
<FailedNetworkAction
|
||||
errorDescription={
|
||||
Object {
|
||||
"defaultMessage": "Make sure you have an active connection and try again.",
|
||||
"id": "mobile.failed_network_action.shortDescription",
|
||||
}
|
||||
}
|
||||
errorTitle={
|
||||
Object {
|
||||
"defaultMessage": "Team Not Found",
|
||||
"id": "error.team_not_found.title",
|
||||
}
|
||||
}
|
||||
onRetry={[Function]}
|
||||
theme={Object {}}
|
||||
/>,
|
||||
],
|
||||
"style": Object {
|
||||
"backgroundColor": undefined,
|
||||
"flex": 1,
|
||||
},
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": Array [
|
||||
Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {},
|
||||
"ref": null,
|
||||
"rendered": null,
|
||||
"type": [Function],
|
||||
},
|
||||
Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {
|
||||
"errorDescription": Object {
|
||||
"defaultMessage": "Make sure you have an active connection and try again.",
|
||||
"id": "mobile.failed_network_action.shortDescription",
|
||||
},
|
||||
"errorTitle": Object {
|
||||
"defaultMessage": "Team Not Found",
|
||||
"id": "error.team_not_found.title",
|
||||
},
|
||||
"onRetry": [Function],
|
||||
"theme": Object {},
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": null,
|
||||
"type": [Function],
|
||||
},
|
||||
],
|
||||
"type": [Function],
|
||||
},
|
||||
Symbol(enzyme.__nodes__): Array [
|
||||
Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {
|
||||
"children": Array [
|
||||
<Connect(StatusBar) />,
|
||||
<FailedNetworkAction
|
||||
errorDescription={
|
||||
Object {
|
||||
"defaultMessage": "Make sure you have an active connection and try again.",
|
||||
"id": "mobile.failed_network_action.shortDescription",
|
||||
}
|
||||
}
|
||||
errorTitle={
|
||||
Object {
|
||||
"defaultMessage": "Team Not Found",
|
||||
"id": "error.team_not_found.title",
|
||||
}
|
||||
}
|
||||
onRetry={[Function]}
|
||||
theme={Object {}}
|
||||
/>,
|
||||
],
|
||||
"style": Object {
|
||||
"backgroundColor": undefined,
|
||||
"flex": 1,
|
||||
},
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": Array [
|
||||
Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {},
|
||||
"ref": null,
|
||||
"rendered": null,
|
||||
"type": [Function],
|
||||
},
|
||||
Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {
|
||||
"errorDescription": Object {
|
||||
"defaultMessage": "Make sure you have an active connection and try again.",
|
||||
"id": "mobile.failed_network_action.shortDescription",
|
||||
},
|
||||
"errorTitle": Object {
|
||||
"defaultMessage": "Team Not Found",
|
||||
"id": "error.team_not_found.title",
|
||||
},
|
||||
"onRetry": [Function],
|
||||
"theme": Object {},
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": null,
|
||||
"type": [Function],
|
||||
},
|
||||
],
|
||||
"type": [Function],
|
||||
},
|
||||
],
|
||||
Symbol(enzyme.__options__): Object {
|
||||
"adapter": ReactSixteenAdapter {
|
||||
"options": Object {
|
||||
"enableComponentDidUpdateOnSetState": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
116
app/screens/error_teams_list/error_teams_list.js
Normal file
116
app/screens/error_teams_list/error_teams_list.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
InteractionManager,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import FailedNetworkAction from 'app/components/failed_network_action';
|
||||
import Loading from 'app/components/loading';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
|
||||
const errorTitle = {
|
||||
id: 'error.team_not_found.title',
|
||||
defaultMessage: 'Team Not Found',
|
||||
};
|
||||
|
||||
const errorDescription = {
|
||||
id: 'mobile.failed_network_action.shortDescription',
|
||||
defaultMessage: 'Make sure you have an active connection and try again.',
|
||||
};
|
||||
|
||||
export default class ErrorTeamsList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadMe: PropTypes.func.isRequired,
|
||||
connection: PropTypes.func.isRequired,
|
||||
logout: PropTypes.func.isRequired,
|
||||
selectDefaultTeam: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
|
||||
this.state = {
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.theme !== nextProps.theme) {
|
||||
setNavigatorStyles(this.props.navigator, nextProps.theme);
|
||||
}
|
||||
}
|
||||
|
||||
goToChannelView = () => {
|
||||
const {navigator, theme} = this.props;
|
||||
|
||||
navigator.resetTo({
|
||||
screen: 'Channel',
|
||||
animated: false,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
getUserInfo = async () => {
|
||||
this.setState({loading: true});
|
||||
this.props.actions.connection(true);
|
||||
await this.props.actions.loadMe();
|
||||
this.props.actions.connection(false);
|
||||
this.setState({loading: false});
|
||||
this.props.actions.selectDefaultTeam();
|
||||
this.goToChannelView();
|
||||
}
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
const {logout} = this.props.actions;
|
||||
if (event.id === 'logout') {
|
||||
InteractionManager.runAfterInteractions(logout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
if (this.state.loading) {
|
||||
return <Loading/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<StatusBar/>
|
||||
<FailedNetworkAction
|
||||
onRetry={this.getUserInfo}
|
||||
theme={theme}
|
||||
errorTitle={errorTitle}
|
||||
errorDescription={errorDescription}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
65
app/screens/error_teams_list/error_teams_list.test.js
Normal file
65
app/screens/error_teams_list/error_teams_list.test.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {configure, shallow} from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import FailedNetworkAction from 'app/components/failed_network_action';
|
||||
import ErrorTeamsList from './error_teams_list';
|
||||
|
||||
configure({adapter: new Adapter()});
|
||||
|
||||
describe('ErrorTeamsList', () => {
|
||||
const navigator = {
|
||||
setOnNavigatorEvent: () => {}, // eslint-disable-line no-empty-function
|
||||
};
|
||||
|
||||
const loadMe = async () => {
|
||||
return {
|
||||
data: {},
|
||||
};
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
actions: {
|
||||
loadMe: () => {}, // eslint-disable-line no-empty-function
|
||||
connection: () => {}, // eslint-disable-line no-empty-function
|
||||
logout: () => {}, // eslint-disable-line no-empty-function
|
||||
selectDefaultTeam: () => {}, // eslint-disable-line no-empty-function
|
||||
},
|
||||
theme: {},
|
||||
navigator,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<ErrorTeamsList {...baseProps}/>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call for userInfo on retry', async () => {
|
||||
const connection = jest.fn();
|
||||
const selectDefaultTeam = jest.fn();
|
||||
const logout = jest.fn();
|
||||
const actions = {
|
||||
loadMe,
|
||||
logout,
|
||||
selectDefaultTeam,
|
||||
connection,
|
||||
};
|
||||
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
actions,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<ErrorTeamsList {...newProps}/>
|
||||
);
|
||||
|
||||
wrapper.find(FailedNetworkAction).props().onRetry();
|
||||
await loadMe();
|
||||
expect(selectDefaultTeam).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
24
app/screens/error_teams_list/index.js
Normal file
24
app/screens/error_teams_list/index.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {logout, loadMe} from 'mattermost-redux/actions/users';
|
||||
import {connection} from 'app/actions/device';
|
||||
import {selectDefaultTeam} from 'app/actions/views/select_team';
|
||||
|
||||
import ErrorTeamsList from './error_teams_list.js';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
logout,
|
||||
selectDefaultTeam,
|
||||
connection,
|
||||
loadMe,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ErrorTeamsList);
|
||||
|
|
@ -58,5 +58,6 @@ export function registerScreens(store, Provider) {
|
|||
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(require('app/screens/text_preview').default), store, Provider);
|
||||
Navigation.registerComponent('Thread', () => wrapWithContextProvider(require('app/screens/thread').default), store, Provider);
|
||||
Navigation.registerComponent('TimezoneSettings', () => wrapWithContextProvider(require('app/screens/timezone').default), store, Provider);
|
||||
Navigation.registerComponent('ErrorTeamsList', () => wrapWithContextProvider(require('app/screens/error_teams_list').default), store, Provider);
|
||||
Navigation.registerComponent('UserProfile', () => wrapWithContextProvider(require('app/screens/user_profile').default), store, Provider);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue