Use post retry actions to avoid missing messages (#796)

* Use post retry actions to avoid missing messages

* Review feedback

* Fix race condition and update redux-offline

* Review feedback
This commit is contained in:
Chris Duarte
2017-08-01 16:09:57 -07:00
committed by enahum
parent e30ed26977
commit fd0ca606bb
9 changed files with 103 additions and 61 deletions

View File

@@ -12,7 +12,7 @@ import {
selectChannel,
leaveChannel as serviceLeaveChannel
} from 'mattermost-redux/actions/channels';
import {getPosts, getPostsBefore, getPostsSince, getPostThread} from 'mattermost-redux/actions/posts';
import {getPosts, getPostsWithRetry, getPostsBefore, getPostsSinceWithRetry, getPostThread} from 'mattermost-redux/actions/posts';
import {getFilesForPost} from 'mattermost-redux/actions/files';
import {savePreferences, deletePreferences} from 'mattermost-redux/actions/preferences';
import {getTeamMembersByIds} from 'mattermost-redux/actions/teams';
@@ -136,8 +136,8 @@ export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
};
}
export function loadPostsIfNecessary(channelId) {
return async (dispatch, getState) => {
export function loadPostsIfNecessaryWithRetry(channelId) {
return (dispatch, getState) => {
const state = getState();
const {posts, postsInChannel} = state.entities.posts;
@@ -145,13 +145,14 @@ export function loadPostsIfNecessary(channelId) {
// Get the first page of posts if it appears we haven't gotten it yet, like the webapp
if (!postsIds || postsIds.length < ViewTypes.POST_VISIBILITY_CHUNK_SIZE) {
return getPosts(channelId)(dispatch, getState);
getPostsWithRetry(channelId)(dispatch, getState);
return;
}
const postsForChannel = postsIds.map((id) => posts[id]);
const latestPostTime = getLastCreateAt(postsForChannel);
return getPostsSince(channelId, latestPostTime)(dispatch, getState);
getPostsSinceWithRetry(channelId, latestPostTime)(dispatch, getState);
};
}
@@ -325,11 +326,9 @@ export function unmarkFavorite(channelId) {
};
}
export function refreshChannel(channelId) {
return async (dispatch, getState) => {
dispatch(setChannelRefreshing());
await getPosts(channelId)(dispatch, getState);
dispatch(setChannelRefreshing(false));
export function refreshChannelWithRetry(channelId) {
return (dispatch, getState) => {
getPostsWithRetry(channelId)(dispatch, getState);
};
}
@@ -350,13 +349,6 @@ export function setChannelLoading(loading = true) {
};
}
export function setChannelRefreshing(refreshing = true) {
return {
type: ViewTypes.SET_CHANNEL_REFRESHING,
refreshing
};
}
export function setPostTooltipVisible(visible = true) {
return {
type: ViewTypes.POST_TOOLTIP_VISIBLE,

View File

@@ -4,18 +4,14 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {refreshChannel} from 'app/actions/views/channel';
import {refreshChannelWithRetry} from 'app/actions/views/channel';
import {getTheme} from 'app/selectors/preferences';
import PostList from './post_list';
function mapStateToProps(state, ownProps) {
const {loading, refreshing} = state.views.channel;
return {
...ownProps,
channelIsLoading: loading,
refreshing,
theme: getTheme(state)
};
}
@@ -23,7 +19,7 @@ function mapStateToProps(state, ownProps) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
refreshChannel
refreshChannelWithRetry
}, dispatch)
};
}

View File

@@ -22,7 +22,7 @@ const LOAD_MORE_POSTS = 'load-more-posts';
export default class PostList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
refreshChannel: PropTypes.func.isRequired
refreshChannelWithRetry: PropTypes.func.isRequired
}).isRequired,
channel: PropTypes.object,
channelIsLoading: PropTypes.bool.isRequired,
@@ -79,7 +79,7 @@ export default class PostList extends PureComponent {
const {actions, channel} = this.props;
if (Object.keys(channel).length) {
actions.refreshChannel(channel.id);
actions.refreshChannelWithRetry(channel.id);
}
};

View File

@@ -36,7 +36,6 @@ const ViewTypes = keyMirror({
ADD_FILE_TO_FETCH_CACHE: null,
SET_CHANNEL_LOADER: null,
SET_CHANNEL_REFRESHING: null,
SET_CHANNEL_DISPLAY_NAME: null,
POST_TOOLTIP_VISIBLE: null,

View File

@@ -251,7 +251,6 @@ const state = {
channel: {
drafts: {},
loading: false,
refreshing: false,
tooltipVisible: false
},
connection: true,

View File

@@ -181,15 +181,6 @@ function loading(state = false, action) {
}
}
function refreshing(state = false, action) {
switch (action.type) {
case ViewTypes.SET_CHANNEL_REFRESHING:
return action.refreshing;
default:
return state;
}
}
function tooltipVisible(state = false, action) {
switch (action.type) {
case ViewTypes.POST_TOOLTIP_VISIBLE:
@@ -245,7 +236,6 @@ export default combineReducers({
displayName,
drafts,
loading,
refreshing,
tooltipVisible,
postVisibility,
loadingPosts

View File

@@ -8,12 +8,14 @@ import {
Animated,
Dimensions,
Platform,
StyleSheet,
View
} from 'react-native';
import {General} from 'mattermost-redux/constants';
import ChannelLoader from 'app/components/channel_loader';
import FormattedText from 'app/components/formatted_text';
import PostList from 'app/components/post_list';
import PostListRetry from 'app/components/post_list_retry';
@@ -23,14 +25,16 @@ const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window');
class ChannelPostList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
loadPostsIfNecessary: PropTypes.func.isRequired,
loadPostsIfNecessaryWithRetry: PropTypes.func.isRequired,
loadThreadIfNecessary: PropTypes.func.isRequired,
increasePostVisibility: PropTypes.func.isRequired,
selectPost: PropTypes.func.isRequired
selectPost: PropTypes.func.isRequired,
refreshChannelWithRetry: PropTypes.func.isRequired
}).isRequired,
channel: PropTypes.object.isRequired,
channelIsLoading: PropTypes.bool,
channelIsRefreshing: PropTypes.bool,
channelRefreshingFailed: PropTypes.bool,
currentChannelId: PropTypes.string,
intl: intlShape.isRequired,
loadingPosts: PropTypes.bool,
@@ -51,26 +55,44 @@ class ChannelPostList extends PureComponent {
super(props);
this.state = {
loaderOpacity: new Animated.Value(1)
loaderOpacity: new Animated.Value(1),
retryMessageHeight: new Animated.Value(0)
};
}
componentDidMount() {
const {channel, posts, channelRefreshingFailed} = this.props;
this.mounted = true;
this.loadPosts(this.props.channel.id);
if (posts.length || channel.total_msg_count === 0 || channelRefreshingFailed) {
this.channelLoaded();
}
}
componentWillReceiveProps(nextProps) {
const {currentChannelId, channel: currentChannel} = this.props;
const {currentChannelId: nextChannelId, channel: nextChannel, channelRefreshingFailed: nextChannelRefreshingFailed, posts: nextPosts} = nextProps;
// Show the loader if the channel id change
if (this.props.currentChannelId !== nextProps.currentChannelId) {
if (currentChannelId !== nextChannelId) {
this.setState({
loaderOpacity: new Animated.Value(1)
}, () => {
if (nextPosts.length || nextChannel.total_msg_count === 0 || nextChannelRefreshingFailed) {
this.channelLoaded();
}
});
}
if (this.props.channel.id !== nextProps.channel.id) {
if (currentChannel.id !== nextChannel.id) {
// Load the posts when the channel actually changes
this.loadPosts(nextProps.channel.id);
this.loadPosts(nextChannel.id);
}
if (nextChannelRefreshingFailed && this.state.channelLoaded && nextPosts.length) {
this.toggleRetryMessage();
} else if (!nextChannelRefreshingFailed || !nextPosts.length) {
this.toggleRetryMessage(false);
}
const showLoadMore = nextProps.posts.length >= nextProps.postVisibility;
@@ -94,6 +116,14 @@ class ChannelPostList extends PureComponent {
});
};
toggleRetryMessage = (show = true) => {
const value = show ? 38 : 0;
Animated.timing(this.state.retryMessageHeight, {
toValue: value,
duration: 350
}).start();
};
goToThread = (post) => {
const {actions, channel, intl, navigator, theme} = this.props;
const channelId = post.channel_id;
@@ -141,10 +171,9 @@ class ChannelPostList extends PureComponent {
}
};
loadPosts = async (channelId) => {
loadPosts = (channelId) => {
this.setState({channelLoaded: false});
await this.props.actions.loadPostsIfNecessary(channelId);
this.channelLoaded();
this.props.actions.loadPostsIfNecessaryWithRetry(channelId);
};
render() {
@@ -152,27 +181,26 @@ class ChannelPostList extends PureComponent {
channel,
channelIsLoading,
channelIsRefreshing,
channelRefreshingFailed,
loadingPosts,
myMember,
navigator,
networkOnline,
posts,
postVisibility,
theme
} = this.props;
const {channelLoaded, loaderOpacity} = this.state;
const {loaderOpacity, retryMessageHeight} = this.state;
let component;
if (!posts.length && channel.total_msg_count > 0 && (!channelIsLoading || !networkOnline)) {
// If no posts has been loaded and we are offline
if (!posts.length && channelRefreshingFailed) {
component = (
<PostListRetry
retry={() => this.loadPosts(channel.id)}
theme={theme}
/>
);
} else if ((channelIsLoading || !channelLoaded) && !channelIsRefreshing && !loadingPosts) {
} else if (channelIsLoading) {
component = <ChannelLoader theme={theme}/>;
} else {
component = (
@@ -188,6 +216,8 @@ class ChannelPostList extends PureComponent {
lastViewedAt={myMember.last_viewed_at}
channel={channel}
navigator={navigator}
refreshing={channelIsRefreshing}
channelIsLoading={channelIsLoading}
/>
);
}
@@ -208,9 +238,28 @@ class ChannelPostList extends PureComponent {
>
<ChannelLoader theme={theme}/>
</AnimatedView>
<AnimatedView style={[style.refreshIndicator, {height: retryMessageHeight}]}>
<FormattedText
id='mobile.retry_message'
defaultMessage='Refreshing messages failed. Pull up to try again.'
style={{color: 'white', flex: 1, fontSize: 12}}
/>
</AnimatedView>
</View>
);
}
}
const style = StyleSheet.create({
refreshIndicator: {
alignItems: 'center',
backgroundColor: '#fb8000',
flexDirection: 'row',
paddingHorizontal: 10,
position: 'absolute',
top: 0,
width: deviceWidth
}
});
export default injectIntl(ChannelPostList);

View File

@@ -8,8 +8,7 @@ import {selectPost} from 'mattermost-redux/actions/posts';
import {RequestStatus} from 'mattermost-redux/constants';
import {makeGetPostsInChannel} from 'mattermost-redux/selectors/entities/posts';
import {getCurrentChannelId, getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels';
import {loadPostsIfNecessary, loadThreadIfNecessary, increasePostVisibility} from 'app/actions/views/channel';
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
import {getTheme} from 'app/selectors/preferences';
import ChannelPostList from './channel_post_list';
@@ -19,19 +18,36 @@ function makeMapStateToProps() {
return function mapStateToProps(state, ownProps) {
const channelId = ownProps.channel.id;
const {refreshing} = state.views.channel;
const {getPosts} = state.requests.posts;
const {getPosts, getPostsRetryAttempts, getPostsSince, getPostsSinceRetryAttempts} = state.requests.posts;
const posts = getPostsInChannel(state, channelId) || [];
const {websocket: websocketRequest} = state.requests.general;
const {connection} = state.views;
const networkOnline = connection && websocketRequest.status === RequestStatus.SUCCESS;
let getPostsStatus;
if (getPostsRetryAttempts > 0) {
getPostsStatus = getPosts.status;
} else if (getPostsSinceRetryAttempts > 1) {
getPostsStatus = getPostsSince.status;
}
let channelIsRefreshing = getPostsStatus === RequestStatus.STARTED;
let channelRefreshingFailed = getPostsStatus === RequestStatus.FAILURE;
if (!networkOnline) {
channelIsRefreshing = false;
channelRefreshingFailed = true;
}
return {
channelIsLoading: getPosts.status === RequestStatus.STARTED || state.views.channel.loading,
channelIsRefreshing: refreshing,
channelIsLoading: state.views.channel.loading,
channelIsRefreshing,
channelRefreshingFailed,
currentChannelId: getCurrentChannelId(state),
posts,
postVisibility: state.views.channel.postVisibility[channelId],
loadingPosts: state.views.channel.loadingPosts[channelId],
myMember: getMyCurrentChannelMembership(state),
networkOnline: state.offline.online,
networkOnline,
theme: getTheme(state),
...ownProps
};
@@ -41,10 +57,11 @@ function makeMapStateToProps() {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
loadPostsIfNecessary,
loadPostsIfNecessaryWithRetry,
loadThreadIfNecessary,
increasePostVisibility,
selectPost
selectPost,
refreshChannelWithRetry
}, dispatch)
};
}

View File

@@ -4791,7 +4791,7 @@ redux-devtools-instrument@^1.3.3:
"redux-offline@git+https://github.com/enahum/redux-offline.git":
version "1.1.4"
resolved "git+https://github.com/enahum/redux-offline.git#454d32a14ae50b9727bccbcdd295cad3cd037335"
resolved "git+https://github.com/enahum/redux-offline.git#3907341aa13fe913ec02a939af76e381d369a91d"
dependencies:
redux-persist "^4.5.0"