[Gekidou MM-42242 MM-46043] Paginating Threads (#6535)

* Pagination and bug

* Feedback and version update

* Making the args optional

* Code refactor
This commit is contained in:
Anurag Shivarathri
2022-08-04 17:19:55 +05:30
committed by GitHub
parent e443a69265
commit 690dd1e66e
10 changed files with 81 additions and 16 deletions

View File

@@ -324,7 +324,7 @@ export async function updateThread(serverUrl: string, threadId: string, updatedT
}
return {model};
} catch (error) {
logError('Failed markTeamThreadsAsRead', error);
logError('Failed updateThread', error);
return {error};
}
}

View File

@@ -570,7 +570,7 @@ export const fetchPostAuthors = async (serverUrl: string, posts: Post[], fetchOn
}
};
export async function fetchPostThread(serverUrl: string, postId: string, fetchOnly = false) {
export async function fetchPostThread(serverUrl: string, postId: string, options?: FetchPaginatedThreadOptions, fetchOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
@@ -585,7 +585,13 @@ export async function fetchPostThread(serverUrl: string, postId: string, fetchOn
try {
const isCRTEnabled = await getIsCRTEnabled(operator.database);
const data = await client.getPostThread(postId, isCRTEnabled, isCRTEnabled);
// Not doing any version check as server versions below 6.7 will ignore the additional params from the client.
const data = await client.getPostThread(postId, {
collapsedThreads: isCRTEnabled,
collapsedThreadsExtended: isCRTEnabled,
...options,
});
const result = processPostsFetched(data);
let posts: Model[] = [];
if (!fetchOnly) {
@@ -637,7 +643,11 @@ export async function fetchPostsAround(serverUrl: string, channelId: string, pos
try {
const [after, post, before] = await Promise.all<PostsObjectsRequest>([
client.getPostsAfter(channelId, postId, 0, perPage, isCRTEnabled, isCRTEnabled),
client.getPostThread(postId, isCRTEnabled, isCRTEnabled),
client.getPostThread(postId, {
collapsedThreads: isCRTEnabled,
collapsedThreadsExtended: isCRTEnabled,
fetchAll: true,
}),
client.getPostsBefore(channelId, postId, 0, perPage, isCRTEnabled, isCRTEnabled),
]);

View File

@@ -40,7 +40,6 @@ export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string,
}
// Load thread before we open to the thread modal
// @Todo: https://mattermost.atlassian.net/browse/MM-42232
fetchPostThread(serverUrl, rootId);
// Mark thread as read

View File

@@ -11,7 +11,7 @@ export interface ClientPostsMix {
getPost: (postId: string) => Promise<Post>;
patchPost: (postPatch: Partial<Post> & {id: string}) => Promise<Post>;
deletePost: (postId: string) => Promise<any>;
getPostThread: (postId: string, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<any>;
getPostThread: (postId: string, options: FetchPaginatedThreadOptions) => Promise<PostResponse>;
getPosts: (channelId: string, page?: number, perPage?: number, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<PostResponse>;
getPostsSince: (channelId: string, since: number, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<PostResponse>;
getPostsBefore: (channelId: string, postId: string, page?: number, perPage?: number, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<PostResponse>;
@@ -79,9 +79,18 @@ const ClientPosts = (superclass: any) => class extends superclass {
);
};
getPostThread = async (postId: string, collapsedThreads = false, collapsedThreadsExtended = false) => {
getPostThread = (postId: string, options: FetchPaginatedThreadOptions): Promise<PostResponse> => {
const {
fetchThreads = true,
collapsedThreads = false,
collapsedThreadsExtended = false,
direction = 'up',
fetchAll = false,
perPage = fetchAll ? undefined : PER_PAGE_DEFAULT,
...rest
} = options;
return this.doFetch(
`${this.getPostRoute(postId)}/thread${buildQueryString({collapsedThreads, collapsedThreadsExtended})}`,
`${this.getPostRoute(postId)}/thread${buildQueryString({skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended, direction, perPage, ...rest})}`,
{method: 'get'},
);
};

View File

@@ -149,10 +149,17 @@ const PostList = ({
if (location === Screens.CHANNEL && channelId) {
await fetchPosts(serverUrl, channelId);
} else if (location === Screens.THREAD && rootId) {
await fetchPostThread(serverUrl, rootId);
const options: FetchPaginatedThreadOptions = {};
const lastPost = posts[0];
if (lastPost) {
options.fromCreateAt = lastPost.createAt;
options.fromPost = lastPost.id;
options.direction = 'down';
}
await fetchPostThread(serverUrl, rootId, options);
}
setRefreshing(false);
}, [channelId, location, rootId]);
}, [channelId, location, posts, rootId]);
const onScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (Platform.OS === 'android') {

View File

@@ -25,7 +25,7 @@ import {DEFAULT_LOCALE, getLocalizedMessage, t} from '@i18n';
import NativeNotifications from '@notifications';
import {queryServerName} from '@queries/app/servers';
import {getCurrentChannelId} from '@queries/servers/system';
import {getIsCRTEnabled} from '@queries/servers/thread';
import {getIsCRTEnabled, getThreadById} from '@queries/servers/thread';
import {showOverlay} from '@screens/navigation';
import EphemeralStore from '@store/ephemeral_store';
import NavigationStore from '@store/navigation_store';
@@ -139,7 +139,10 @@ class PushNotifications {
if (database) {
const isCRTEnabled = await getIsCRTEnabled(database);
if (isCRTEnabled && payload.root_id) {
markThreadAsRead(serverUrl, payload.team_id, payload.post_id);
const thread = await getThreadById(database, payload.root_id);
if (thread?.isFollowing) {
markThreadAsRead(serverUrl, payload.team_id, payload.post_id);
}
} else {
markChannelAsViewed(serverUrl, payload.channel_id, false);
}

View File

@@ -155,7 +155,9 @@ function Permalink({
let data;
const loadThreadPosts = isCRTEnabled && rootId;
if (loadThreadPosts) {
data = await fetchPostThread(serverUrl, rootId);
data = await fetchPostThread(serverUrl, rootId, {
fetchAll: true,
});
} else {
data = await fetchPostsAround(serverUrl, channelId, postId, POSTS_LIMIT, isCRTEnabled);
}

View File

@@ -9,6 +9,7 @@ import {switchMap} from 'rxjs/operators';
import {observeMyChannel, observeChannel} from '@queries/servers/channel';
import {queryPostsChunk, queryPostsInThread} from '@queries/servers/post';
import {observeConfigValue} from '@queries/servers/system';
import {observeIsCRTEnabled, observeThreadById} from '@queries/servers/thread';
import ThreadPostList from './thread_post_list';
@@ -41,6 +42,7 @@ const enhanced = withObservables(['forceQueryAfterAppState', 'rootPost'], ({data
switchMap((channel) => of$(channel?.teamId)),
),
thread: observeThreadById(database, rootPost.id),
version: observeConfigValue(database, 'Version'),
};
});

View File

@@ -1,15 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useMemo, useRef} from 'react';
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import {StyleSheet, View} from 'react-native';
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
import {fetchPostThread} from '@actions/remote/post';
import {markThreadAsRead} from '@actions/remote/thread';
import PostList from '@components/post_list';
import {Screens} from '@constants';
import {useServerUrl} from '@context/server';
import {debounce} from '@helpers/api/general';
import {useIsTablet} from '@hooks/device';
import {isMinimumServerVersion} from '@utils/helpers';
import type PostModel from '@typings/database/models/servers/post';
import type ThreadModel from '@typings/database/models/servers/thread';
@@ -22,6 +25,7 @@ type Props = {
rootPost: PostModel;
teamId: string;
thread?: ThreadModel;
version?: string;
}
const edges: Edge[] = ['bottom'];
@@ -34,18 +38,35 @@ const styles = StyleSheet.create({
const ThreadPostList = ({
channelLastViewedAt, isCRTEnabled,
nativeID, posts, rootPost, teamId, thread,
nativeID, posts, rootPost, teamId, thread, version,
}: Props) => {
const isTablet = useIsTablet();
const serverUrl = useServerUrl();
const canLoadPosts = useRef(true);
const fetchingPosts = useRef(false);
const onEndReached = useCallback(debounce(async () => {
if (isMinimumServerVersion(version || '', 6, 7) && !fetchingPosts.current && canLoadPosts.current && posts.length) {
fetchingPosts.current = true;
const options: FetchPaginatedThreadOptions = {};
const lastPost = posts[posts.length - 1];
if (lastPost) {
options.fromPost = lastPost.id;
options.fromCreateAt = lastPost.createAt;
}
const result = await fetchPostThread(serverUrl, rootPost.id, options);
fetchingPosts.current = false;
canLoadPosts.current = Boolean(result?.posts?.length);
}
}, 500), [rootPost, posts, version]);
const threadPosts = useMemo(() => {
return [...posts, rootPost];
}, [posts, rootPost]);
// If CRT is enabled, mark the thread as read on mount.
useEffect(() => {
if (isCRTEnabled) {
if (isCRTEnabled && thread?.isFollowing) {
markThreadAsRead(serverUrl, teamId, rootPost.id);
}
}, []);
@@ -69,6 +90,7 @@ const ThreadPostList = ({
lastViewedAt={lastViewedAt}
location={Screens.THREAD}
nativeID={nativeID}
onEndReached={onEndReached}
posts={threadPosts}
rootId={rootPost.id}
shouldShowJoinLeaveMessages={false}

11
types/api/posts.d.ts vendored
View File

@@ -116,3 +116,14 @@ type PostSearchParams = {
terms: string;
is_or_search: boolean;
};
type FetchPaginatedThreadOptions = {
fetchThreads?: boolean;
collapsedThreads?: boolean;
collapsedThreadsExtended?: boolean;
direction?: 'up'|'down';
fetchAll?: boolean;
perPage?: number;
fromCreateAt?: number;
fromPost?: string;
}