From 2deb2e01e03ed9dcb2ac3db7e957d28001428d94 Mon Sep 17 00:00:00 2001 From: Anurag Shivarathri Date: Thu, 5 May 2022 18:36:18 +0530 Subject: [PATCH] [Gekidou][MM-43870, MM-43896, MM-43898, MM-43902, MM-43903] CRT related bug fixes (#6205) * Fixes * Updated snapshot * Excluding current user from getting updated * Doesn't handle users if it's empty Co-authored-by: Elias Nahum --- app/actions/local/thread.ts | 24 ++++++++++---- app/actions/remote/thread.ts | 32 +++++++++++++++++++ app/client/rest/posts.ts | 5 ++- app/client/rest/threads.ts | 9 ++++++ app/components/channel_item/channel_item.tsx | 2 +- .../transformers/channel.ts | 5 ++- .../threads_button.test.tsx.snap | 3 ++ .../threads_button/threads_button.tsx | 4 +++ .../options/mark_unread_option/index.ts | 19 +++++++++++ .../mark_unread_option.tsx | 18 ++++++++--- app/screens/post_options/post_options.tsx | 5 ++- .../options/mark_as_unread_option.tsx | 15 +++++---- app/screens/thread_options/thread_options.tsx | 1 - 13 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 app/screens/post_options/options/mark_unread_option/index.ts rename app/screens/post_options/options/{ => mark_unread_option}/mark_unread_option.tsx (61%) diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index 7e6caf225..e9b9b15e1 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -8,7 +8,7 @@ import DatabaseManager from '@database/manager'; import {getTranslations, t} from '@i18n'; import {getChannelById} from '@queries/servers/channel'; import {getPostById} from '@queries/servers/post'; -import {getCurrentTeamId, setCurrentChannelId} from '@queries/servers/system'; +import {getCurrentTeamId, getCurrentUserId, setCurrentChannelId} from '@queries/servers/system'; import {addChannelToTeamHistory} from '@queries/servers/team'; import {getIsCRTEnabled, getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; @@ -176,6 +176,9 @@ export async function processReceivedThreads(serverUrl: string, threads: Thread[ return {error: `${serverUrl} database not found`}; } + const {database} = operator; + const currentUserId = await getCurrentUserId(database); + const posts: Post[] = []; const users: UserProfile[] = []; @@ -183,7 +186,11 @@ export async function processReceivedThreads(serverUrl: string, threads: Thread[ for (let i = 0; i < threads.length; i++) { const {participants, post} = threads[i]; posts.push(post); - participants.forEach((participant) => users.push(participant)); + participants.forEach((participant) => { + if (currentUserId !== participant.id) { + users.push(participant); + } + }); } const postModels = await operator.handlePosts({ @@ -200,12 +207,15 @@ export async function processReceivedThreads(serverUrl: string, threads: Thread[ loadedInGlobalThreads, }); - const userModels = await operator.handleUsers({ - users, - prepareRecordsOnly: true, - }); + const models = [...postModels, ...threadModels]; - const models = [...postModels, ...threadModels, ...userModels]; + if (users.length) { + const userModels = await operator.handleUsers({ + users, + prepareRecordsOnly: true, + }); + models.push(...userModels); + } if (!prepareRecordsOnly) { await operator.batchRecords(models); diff --git a/app/actions/remote/thread.ts b/app/actions/remote/thread.ts index c1a82fcfb..279c5deb6 100644 --- a/app/actions/remote/thread.ts +++ b/app/actions/remote/thread.ts @@ -179,6 +179,38 @@ export const updateThreadRead = async (serverUrl: string, teamId: string, thread } }; +export const markThreadAsUnread = async (serverUrl: string, teamId: string, threadId: string, postId: string) => { + const database = DatabaseManager.serverDatabases[serverUrl]?.database; + + if (!database) { + return {error: `${serverUrl} database not found`}; + } + + let client; + try { + client = NetworkManager.getClient(serverUrl); + } catch (error) { + return {error}; + } + + try { + const data = await client.markThreadAsUnread('me', teamId, threadId, postId); + + // Update locally + const post = await getPostById(database, threadId); + if (post) { + await updateThread(serverUrl, threadId, { + last_viewed_at: post.createAt - 1, + }); + } + + return {data}; + } catch (error) { + forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); + return {error}; + } +}; + export const updateThreadFollowing = async (serverUrl: string, teamId: string, threadId: string, state: boolean) => { const database = DatabaseManager.serverDatabases[serverUrl]?.database; diff --git a/app/client/rest/posts.ts b/app/client/rest/posts.ts index 693e1640c..92e4ecc81 100644 --- a/app/client/rest/posts.ts +++ b/app/client/rest/posts.ts @@ -145,9 +145,12 @@ const ClientPosts = (superclass: any) => class extends superclass { markPostAsUnread = async (userId: string, postId: string) => { this.analytics.trackAPI('api_post_set_unread_post'); + // collapsed_threads_supported is not based on user preferences but to know if "CLIENT" supports CRT + const body = JSON.stringify({collapsed_threads_supported: true}); + return this.doFetch( `${this.getUserRoute(userId)}/posts/${postId}/set_unread`, - {method: 'post'}, + {method: 'post', body}, ); }; diff --git a/app/client/rest/threads.ts b/app/client/rest/threads.ts index a8ad6a7e3..6859912a9 100644 --- a/app/client/rest/threads.ts +++ b/app/client/rest/threads.ts @@ -8,6 +8,7 @@ import {PER_PAGE_DEFAULT} from './constants'; export interface ClientThreadsMix { getThreads: (userId: string, teamId: string, before?: string, after?: string, pageSize?: number, deleted?: boolean, unread?: boolean, since?: number, totalsOnly?: boolean, serverVersion?: string) => Promise; getThread: (userId: string, teamId: string, threadId: string, extended?: boolean) => Promise; + markThreadAsUnread: (userId: string, teamId: string, threadId: string, postId: string) => Promise; updateTeamThreadsAsRead: (userId: string, teamId: string) => Promise; updateThreadRead: (userId: string, teamId: string, threadId: string, timestamp: number) => Promise; updateThreadFollow: (userId: string, teamId: string, threadId: string, state: boolean) => Promise; @@ -42,6 +43,14 @@ const ClientThreads = (superclass: any) => class extends superclass { ); }; + markThreadAsUnread = (userId: string, teamId: string, threadId: string, postId: string) => { + const url = `${this.getThreadRoute(userId, teamId, threadId)}/set_unread/${postId}`; + return this.doFetch( + url, + {method: 'post'}, + ); + }; + updateTeamThreadsAsRead = (userId: string, teamId: string) => { const url = `${this.getThreadsRoute(userId, teamId)}/read`; return this.doFetch( diff --git a/app/components/channel_item/channel_item.tsx b/app/components/channel_item/channel_item.tsx index 50b7c0434..fd8fa9033 100644 --- a/app/components/channel_item/channel_item.tsx +++ b/app/components/channel_item/channel_item.tsx @@ -68,11 +68,11 @@ export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ color: changeOpacity(theme.sidebarText, 0.4), }, badge: { + borderColor: theme.sidebarBg, position: 'relative', left: 0, top: -2, alignSelf: undefined, - borderColor: theme.sidebarBg, }, infoBadge: { color: theme.buttonColor, diff --git a/app/database/operator/server_data_operator/transformers/channel.ts b/app/database/operator/server_data_operator/transformers/channel.ts index 4d54ca05c..3e4df3e21 100644 --- a/app/database/operator/server_data_operator/transformers/channel.ts +++ b/app/database/operator/server_data_operator/transformers/channel.ts @@ -131,7 +131,10 @@ export const transformMyChannelRecord = async ({action, database, value}: Transf const fieldsMapper = (myChannel: MyChannelModel) => { myChannel._raw.id = isCreateAction ? (raw.channel_id || myChannel.id) : record.id; myChannel.roles = raw.roles; - myChannel.messageCount = isCRTEnabled ? raw.msg_count_root! : raw.msg_count; + + // ignoring msg_count_root because msg_count is already calculated in "handleMyChannel" based on CRT is enabled or not + myChannel.messageCount = raw.msg_count; + myChannel.isUnread = Boolean(raw.is_unread); myChannel.mentionsCount = isCRTEnabled ? raw.mention_count_root! : raw.mention_count; myChannel.lastPostAt = (isCRTEnabled ? (raw.last_root_post_at || raw.last_post_at) : raw.last_post_at) || 0; diff --git a/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap b/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap index 4fe72059a..90d71d780 100644 --- a/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap +++ b/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap @@ -54,6 +54,9 @@ exports[`Threads Component should match snapshot 1`] = ` ({ iconActive: { color: theme.sidebarText, }, + text: { + flex: 1, + }, })); type Props = { @@ -67,6 +70,7 @@ const ThreadsButton = ({currentChannelId, unreadsAndMentions}: Props) => { ]; const text = [ + customStyles.text, unreads ? channelItemTextStyle.bright : channelItemTextStyle.regular, styles.text, unreads ? styles.highlight : undefined, diff --git a/app/screens/post_options/options/mark_unread_option/index.ts b/app/screens/post_options/options/mark_unread_option/index.ts new file mode 100644 index 000000000..64275cab8 --- /dev/null +++ b/app/screens/post_options/options/mark_unread_option/index.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; + +import {observeCurrentTeamId} from '@queries/servers/system'; + +import MarkAsUnreadOption from './mark_unread_option'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { + return { + teamId: observeCurrentTeamId(database), + }; +}); + +export default withDatabase(enhanced(MarkAsUnreadOption)); diff --git a/app/screens/post_options/options/mark_unread_option.tsx b/app/screens/post_options/options/mark_unread_option/mark_unread_option.tsx similarity index 61% rename from app/screens/post_options/options/mark_unread_option.tsx rename to app/screens/post_options/options/mark_unread_option/mark_unread_option.tsx index 24ad73415..e525b5dbe 100644 --- a/app/screens/post_options/options/mark_unread_option.tsx +++ b/app/screens/post_options/options/mark_unread_option/mark_unread_option.tsx @@ -4,23 +4,33 @@ import React, {useCallback} from 'react'; import {markPostAsUnread} from '@actions/remote/post'; +import {markThreadAsUnread} from '@actions/remote/thread'; import {BaseOption} from '@components/common_post_options'; import Screens from '@constants/screens'; import {useServerUrl} from '@context/server'; import {t} from '@i18n'; import {dismissBottomSheet} from '@screens/navigation'; +import type PostModel from '@typings/database/models/servers/post'; + type Props = { - postId: string; + location: typeof Screens[keyof typeof Screens]; + post: PostModel; + teamId: string; } -const MarkAsUnreadOption = ({postId}: Props) => { +const MarkAsUnreadOption = ({location, post, teamId}: Props) => { const serverUrl = useServerUrl(); const onPress = useCallback(async () => { await dismissBottomSheet(Screens.POST_OPTIONS); - markPostAsUnread(serverUrl, postId); - }, [serverUrl, postId]); + if (location === Screens.THREAD) { + const threadId = post.rootId || post.id; + markThreadAsUnread(serverUrl, teamId, threadId, post.id); + } else { + markPostAsUnread(serverUrl, post.id); + } + }, [location, post, serverUrl, teamId]); return ( } {canMarkAsUnread && !isSystemPost && - + } {canCopyPermalink && } {!isSystemPost && diff --git a/app/screens/thread_options/options/mark_as_unread_option.tsx b/app/screens/thread_options/options/mark_as_unread_option.tsx index 43dff7574..13afa39d3 100644 --- a/app/screens/thread_options/options/mark_as_unread_option.tsx +++ b/app/screens/thread_options/options/mark_as_unread_option.tsx @@ -3,29 +3,30 @@ import React, {useCallback} from 'react'; -import {updateThreadRead} from '@actions/remote/thread'; +import {markThreadAsUnread, updateThreadRead} from '@actions/remote/thread'; import {BaseOption} from '@components/common_post_options'; import {Screens} from '@constants'; import {useServerUrl} from '@context/server'; import {t} from '@i18n'; import {dismissBottomSheet} from '@screens/navigation'; -import type PostModel from '@typings/database/models/servers/post'; import type ThreadModel from '@typings/database/models/servers/thread'; type Props = { - post: PostModel; teamId: string; thread: ThreadModel; } -const MarkAsUnreadOption = ({teamId, thread, post}: Props) => { +const MarkAsUnreadOption = ({teamId, thread}: Props) => { const serverUrl = useServerUrl(); const onHandlePress = useCallback(async () => { await dismissBottomSheet(Screens.THREAD_OPTIONS); - const timestamp = thread.unreadReplies ? Date.now() : post.createAt; - updateThreadRead(serverUrl, teamId, thread.id, timestamp); - }, [serverUrl, thread]); + if (thread.unreadReplies) { + updateThreadRead(serverUrl, teamId, thread.id, Date.now()); + } else { + markThreadAsUnread(serverUrl, teamId, thread.id, thread.id); + } + }, [serverUrl, teamId, thread]); const id = thread.unreadReplies ? t('global_threads.options.mark_as_read') : t('mobile.post_info.mark_unread'); const defaultMessage = thread.unreadReplies ? 'Mark as Read' : 'Mark as Unread'; diff --git a/app/screens/thread_options/thread_options.tsx b/app/screens/thread_options/thread_options.tsx index 8725cf053..d3232bf94 100644 --- a/app/screens/thread_options/thread_options.tsx +++ b/app/screens/thread_options/thread_options.tsx @@ -92,7 +92,6 @@ const ThreadOptions = ({ key='mark-as-unread' teamId={team.id} thread={thread} - post={post} />,