From b2d838d3da212fac90b5289ac43f38a397e4c0ef Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Thu, 7 Jul 2022 13:19:02 +0300 Subject: [PATCH] MM-37110: handle toggling of CRT feature (#6382) * MM-37110: handle toggling of CRT feature When a user toggles CRT on/off the app should truncate affected tables, and re-fetch data. Truncated tables: - POST - POSTS_IN_CHANNEL - POSTS_IN_THREAD - THREAD - THREADS_IN_TEAM - THREAD_PARTICIPANT - MY_CHANNEL After truncation `entry` is called again. We must make sure though that we save the CRT change before calling `entry` again, or we end up with infinite recursion. PS the UI seems to handle the change rather good * Fixes appEntry and popToRoot * Small refactor * Fixes since param on appEntry * Further refactoring * Delete unneeded return type * Removes shouldPopToRoot from appEntry * Addresses review comments --- app/actions/remote/entry/common.ts | 21 ++++++++++++-- app/actions/websocket/preferences.ts | 27 ++++++++++++++--- app/constants/events.ts | 1 + app/queries/servers/entry.ts | 43 ++++++++++++++++++++++++++++ app/queries/servers/preference.ts | 14 +++++++++ app/screens/home/index.tsx | 9 +++++- 6 files changed, 107 insertions(+), 8 deletions(-) diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index b31f3a5e3b..4f1dab6de1 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -22,7 +22,8 @@ import NetworkManager from '@managers/network_manager'; import {getDeviceToken} from '@queries/app/global'; import {queryAllServers} from '@queries/app/servers'; import {queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel'; -import {prepareModels} from '@queries/servers/entry'; +import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry'; +import {getHasCRTChanged} from '@queries/servers/preference'; import {getConfig, getPushVerificationStatus, getWebSocketLastDisconnected} from '@queries/servers/system'; import {deleteMyTeams, getAvailableTeamIds, getNthLastChannelFromTeam, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team'; import {isDMorGM} from '@utils/channel'; @@ -128,18 +129,32 @@ export const entry = async (serverUrl: string, teamId?: string, channelId?: stri return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData, meData}; }; -export const fetchAppEntryData = async (serverUrl: string, since: number, initialTeamId = ''): Promise => { +export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTeamId = ''): Promise => { const database = DatabaseManager.serverDatabases[serverUrl]?.database; if (!database) { return {error: `${serverUrl} database not found`}; } - + let since = sinceArg; const includeDeletedChannels = true; const fetchOnly = true; const confReq = await fetchConfigAndLicense(serverUrl); const prefData = await fetchMyPreferences(serverUrl, fetchOnly); const isCRTEnabled = Boolean(prefData.preferences && processIsCRTEnabled(prefData.preferences, confReq.config)); + if (prefData.preferences) { + const crtToggled = await getHasCRTChanged(database, prefData.preferences); + if (crtToggled) { + const currentServerUrl = await DatabaseManager.getActiveServerUrl(); + const isSameServer = currentServerUrl === serverUrl; + if (isSameServer) { + since = 0; + } + const {error} = await truncateCrtRelatedTables(serverUrl); + if (error) { + return {error: `Resetting CRT on ${serverUrl} failed`}; + } + } + } // Fetch in parallel teams / team membership / channels for current team / user preferences / user const promises: [Promise, Promise, Promise] = [ diff --git a/app/actions/websocket/preferences.ts b/app/actions/websocket/preferences.ts index b14edb2f43..a3b1e7b301 100644 --- a/app/actions/websocket/preferences.ts +++ b/app/actions/websocket/preferences.ts @@ -1,12 +1,23 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {DeviceEventEmitter} from 'react-native'; + import {updateDmGmDisplayName} from '@actions/local/channel'; +import {appEntry} from '@actions/remote/entry'; import {fetchPostById} from '@actions/remote/post'; -import {Preferences} from '@constants'; +import {Events, Preferences} from '@constants'; import DatabaseManager from '@database/manager'; +import {truncateCrtRelatedTables} from '@queries/servers/entry'; import {getPostById} from '@queries/servers/post'; -import {deletePreferences, differsFromLocalNameFormat} from '@queries/servers/preference'; +import {deletePreferences, differsFromLocalNameFormat, getHasCRTChanged} from '@queries/servers/preference'; + +async function handleCRTToggled(serverUrl: string) { + const currentServerUrl = await DatabaseManager.getActiveServerUrl(); + await truncateCrtRelatedTables(serverUrl); + appEntry(serverUrl); + DeviceEventEmitter.emit(Events.CRT_TOGGLED, serverUrl === currentServerUrl); +} export async function handlePreferenceChangedEvent(serverUrl: string, msg: WebSocketMessage): Promise { let database; @@ -24,6 +35,7 @@ export async function handlePreferenceChangedEvent(serverUrl: string, msg: WebSo handleSavePostAdded(serverUrl, [preference]); const hasDiffNameFormatPref = await differsFromLocalNameFormat(database, [preference]); + const crtToggled = await getHasCRTChanged(database, [preference]); if (operator) { await operator.handlePreferences({ @@ -35,6 +47,10 @@ export async function handlePreferenceChangedEvent(serverUrl: string, msg: WebSo if (hasDiffNameFormatPref) { updateDmGmDisplayName(serverUrl); } + + if (crtToggled) { + handleCRTToggled(serverUrl); + } } catch (error) { // Do nothing } @@ -50,7 +66,7 @@ export async function handlePreferencesChangedEvent(serverUrl: string, msg: WebS handleSavePostAdded(serverUrl, preferences); const hasDiffNameFormatPref = await differsFromLocalNameFormat(operator.database, preferences); - + const crtToggled = await getHasCRTChanged(operator.database, preferences); if (operator) { await operator.handlePreferences({ prepareRecordsOnly: false, @@ -61,6 +77,10 @@ export async function handlePreferencesChangedEvent(serverUrl: string, msg: WebS if (hasDiffNameFormatPref) { updateDmGmDisplayName(serverUrl); } + + if (crtToggled) { + handleCRTToggled(serverUrl); + } } catch (error) { // Do nothing } @@ -95,4 +115,3 @@ async function handleSavePostAdded(serverUrl: string, preferences: PreferenceTyp } } } - diff --git a/app/constants/events.ts b/app/constants/events.ts index 385cb86203..fd023e0870 100644 --- a/app/constants/events.ts +++ b/app/constants/events.ts @@ -27,4 +27,5 @@ export default keyMirror({ SWIPEABLE: null, ITEM_IN_VIEWPORT: null, SEND_TO_POST_DRAFT: null, + CRT_TOGGLED: null, }); diff --git a/app/queries/servers/entry.ts b/app/queries/servers/entry.ts index f8e809df76..f482cce8ff 100644 --- a/app/queries/servers/entry.ts +++ b/app/queries/servers/entry.ts @@ -1,11 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {MM_TABLES} from '@constants/database'; +import DatabaseManager from '@database/manager'; import ServerDataOperator from '@database/operator/server_data_operator'; import {prepareCategories, prepareCategoryChannels} from './categories'; import {prepareDeleteChannel, prepareMyChannelsForTeam} from './channel'; import {prepareMyPreferences} from './preference'; +import {resetWebSocketLastDisconnected} from './system'; import {prepareDeleteTeam, prepareMyTeams} from './team'; import {prepareUsers} from './user'; @@ -29,6 +32,16 @@ type PrepareModelsArgs = { isCRTEnabled?: boolean; } +const { + POST, + POSTS_IN_CHANNEL, + POSTS_IN_THREAD, + THREAD, + THREADS_IN_TEAM, + THREAD_PARTICIPANT, + MY_CHANNEL, +} = MM_TABLES.SERVER; + export async function prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData, isCRTEnabled}: PrepareModelsArgs): Promise>> { const modelPromises: Array> = []; @@ -67,3 +80,33 @@ export async function prepareModels({operator, initialTeamId, removeTeams, remov return modelPromises; } + +export async function truncateCrtRelatedTables(serverUrl: string): Promise<{error: any}> { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + + try { + await database.write(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return database.adapter.unsafeExecute({ + sqls: [ + [`DELETE FROM ${POST}`, []], + [`DELETE FROM ${POSTS_IN_CHANNEL}`, []], + [`DELETE FROM ${POSTS_IN_THREAD}`, []], + [`DELETE FROM ${THREAD}`, []], + [`DELETE FROM ${THREADS_IN_TEAM}`, []], + [`DELETE FROM ${THREAD_PARTICIPANT}`, []], + [`DELETE FROM ${MY_CHANNEL}`, []], + ], + }); + }); + await resetWebSocketLastDisconnected(operator); + } catch (error) { + if (__DEV__) { + throw error; + } + return {error}; + } + + return {error: false}; +} diff --git a/app/queries/servers/preference.ts b/app/queries/servers/preference.ts index 5d58ea3e15..f5ba81c0d6 100644 --- a/app/queries/servers/preference.ts +++ b/app/queries/servers/preference.ts @@ -8,6 +8,7 @@ import {MM_TABLES} from '@constants/database'; import {getPreferenceValue} from '@helpers/api/preference'; import {getCurrentTeamId} from './system'; +import {getIsCRTEnabled} from './thread'; import type ServerDataOperator from '@database/operator/server_data_operator'; import type {ServerDatabase} from '@typings/database/database'; @@ -79,3 +80,16 @@ export const differsFromLocalNameFormat = async (database: Database, preferences return true; }; + +export async function getHasCRTChanged(database: Database, preferences: PreferenceType[]): Promise { + const oldCRT = await getIsCRTEnabled(database); + const newCRTPref = preferences.filter((p) => p.name === Preferences.COLLAPSED_REPLY_THREADS)?.[0]; + + if (!newCRTPref) { + return false; + } + + const newCRT = newCRTPref.value === 'on'; + + return oldCRT !== newCRT; +} diff --git a/app/screens/home/index.tsx b/app/screens/home/index.tsx index fece5f503e..832def0792 100644 --- a/app/screens/home/index.tsx +++ b/app/screens/home/index.tsx @@ -11,7 +11,7 @@ import {enableFreeze, enableScreens} from 'react-native-screens'; import {Events, Screens} from '@constants'; import {useTheme} from '@context/theme'; -import {findChannels} from '@screens/navigation'; +import {findChannels, popToRoot} from '@screens/navigation'; import NavigationStore from '@store/navigation_store'; import {alertChannelArchived, alertChannelRemove, alertTeamRemove} from '@utils/navigation'; import {notificationError} from '@utils/notification'; @@ -67,10 +67,17 @@ export default function HomeScreen(props: HomeProps) { alertChannelArchived(displayName, intl); }); + const crtToggledListener = DeviceEventEmitter.addListener(Events.CRT_TOGGLED, (isSameServer: boolean) => { + if (isSameServer) { + popToRoot(); + } + }); + return () => { leaveTeamListener.remove(); leaveChannelListener.remove(); archivedChannelListener.remove(); + crtToggledListener.remove(); }; }, [intl.locale]);