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
This commit is contained in:
Kyriakos Z
2022-07-07 13:19:02 +03:00
committed by GitHub
parent e481c07630
commit b2d838d3da
6 changed files with 107 additions and 8 deletions

View File

@@ -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<AppEntryData | AppEntryError> => {
export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, initialTeamId = ''): Promise<AppEntryData | AppEntryError> => {
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<MyTeamsRequest>, Promise<MyChannelsRequest | undefined>, Promise<MyUserRequest>] = [

View File

@@ -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<void> {
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
}
}
}

View File

@@ -27,4 +27,5 @@ export default keyMirror({
SWIPEABLE: null,
ITEM_IN_VIEWPORT: null,
SEND_TO_POST_DRAFT: null,
CRT_TOGGLED: null,
});

View File

@@ -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<Array<Promise<Model[]>>> {
const modelPromises: Array<Promise<Model[]>> = [];
@@ -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};
}

View File

@@ -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<boolean> {
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;
}

View File

@@ -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]);