Add some improvements to thread handling (#8407)
* Add some improvements to thread handling * Add missing changes * Android changes * iOS changes * Avoid copys * Refactor getReplyAt
This commit is contained in:
parent
0ad2679595
commit
4b068889e7
10 changed files with 104 additions and 33 deletions
|
|
@ -55,26 +55,33 @@ class PushNotificationDataRunnable {
|
|||
val receivingThreads = isCRTEnabled && !rootId.isNullOrEmpty()
|
||||
val notificationData = Arguments.createMap()
|
||||
|
||||
var channel: ReadableMap? = null
|
||||
var myTeam: ReadableMap? = null
|
||||
|
||||
if (!teamId.isNullOrEmpty()) {
|
||||
val res = fetchTeamIfNeeded(db, serverUrl, teamId)
|
||||
res.first?.let { notificationData.putMap("team", it) }
|
||||
res.second?.let { notificationData.putMap("myTeam", it) }
|
||||
|
||||
myTeam = res.second
|
||||
myTeam?.let { notificationData.putMap("myTeam", it) }
|
||||
}
|
||||
|
||||
if (channelId != null && postId != null) {
|
||||
val channelRes = fetchMyChannel(db, serverUrl, channelId, isCRTEnabled)
|
||||
channelRes.first?.let { notificationData.putMap("channel", it) }
|
||||
|
||||
channel = channelRes.first
|
||||
channel?.let { notificationData.putMap("channel", it) }
|
||||
channelRes.second?.let { notificationData.putMap("myChannel", it) }
|
||||
val loadedProfiles = channelRes.third
|
||||
|
||||
// Fetch categories if needed
|
||||
if (!teamId.isNullOrEmpty() && notificationData.getMap("myTeam") != null) {
|
||||
if (!teamId.isNullOrEmpty() && myTeam != null) {
|
||||
// should load all categories
|
||||
val res = fetchMyTeamCategories(db, serverUrl, teamId)
|
||||
res?.let { notificationData.putMap("categories", it) }
|
||||
} else if (notificationData.getMap("channel") != null) {
|
||||
} else if (channel != null) {
|
||||
// check if the channel is in the category for the team
|
||||
val res = addToDefaultCategoryIfNeeded(db, notificationData.getMap("channel")!!)
|
||||
val res = addToDefaultCategoryIfNeeded(db, channel)
|
||||
res?.let { notificationData.putArray("categoryChannels", it) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.mattermost.helpers.database_extension
|
||||
|
||||
import android.util.Log
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.NoSuchKeyException
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
|
|
@ -9,6 +10,21 @@ import com.mattermost.helpers.mapCursor
|
|||
import com.nozbe.watermelondb.WMDatabase
|
||||
import org.json.JSONObject
|
||||
|
||||
private fun getLastReplyAt(thread: ReadableMap): Double {
|
||||
try {
|
||||
var v = thread.getDouble("last_reply_at")
|
||||
if (v == 0.0) {
|
||||
val post = thread.getMap("post")
|
||||
if (post != null) {
|
||||
v = post.getDouble("create_at")
|
||||
}
|
||||
}
|
||||
return v
|
||||
} catch (e: NoSuchKeyException) {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
internal fun insertThread(db: WMDatabase, thread: ReadableMap) {
|
||||
// These fields are not present when we extract threads from posts
|
||||
try {
|
||||
|
|
@ -17,7 +33,7 @@ internal fun insertThread(db: WMDatabase, thread: ReadableMap) {
|
|||
val lastViewedAt = try { thread.getDouble("last_viewed_at") } catch (e: NoSuchKeyException) { 0 }
|
||||
val unreadReplies = try { thread.getInt("unread_replies") } catch (e: NoSuchKeyException) { 0 }
|
||||
val unreadMentions = try { thread.getInt("unread_mentions") } catch (e: NoSuchKeyException) { 0 }
|
||||
val lastReplyAt = try { thread.getDouble("last_reply_at") } catch (e: NoSuchKeyException) { 0 }
|
||||
val lastReplyAt = getLastReplyAt(thread)
|
||||
val replyCount = try { thread.getInt("reply_count") } catch (e: NoSuchKeyException) { 0 }
|
||||
|
||||
db.execute(
|
||||
|
|
@ -44,7 +60,7 @@ internal fun updateThread(db: WMDatabase, thread: ReadableMap, existingRecord: R
|
|||
val lastViewedAt = try { thread.getDouble("last_viewed_at") } catch (e: NoSuchKeyException) { existingRecord.getDouble("last_viewed_at") }
|
||||
val unreadReplies = try { thread.getInt("unread_replies") } catch (e: NoSuchKeyException) { existingRecord.getInt("unread_replies") }
|
||||
val unreadMentions = try { thread.getInt("unread_mentions") } catch (e: NoSuchKeyException) { existingRecord.getInt("unread_mentions") }
|
||||
val lastReplyAt = try { thread.getDouble("last_reply_at") } catch (e: NoSuchKeyException) { 0 }
|
||||
val lastReplyAt = getLastReplyAt(thread)
|
||||
val replyCount = try { thread.getInt("reply_count") } catch (e: NoSuchKeyException) { 0 }
|
||||
|
||||
db.execute(
|
||||
|
|
@ -231,8 +247,19 @@ fun handleThreadInTeam(db: WMDatabase, thread: ReadableMap, teamId: String) {
|
|||
|
||||
fun handleTeamThreadsSync(db: WMDatabase, threadList: ArrayList<ReadableMap>, teamIds: ArrayList<String>) {
|
||||
val sortedList = threadList.filter{ it.getBoolean("is_following") }
|
||||
.sortedBy { it.getDouble("last_reply_at") }
|
||||
.map { it.getDouble("last_reply_at") }
|
||||
.sortedBy {
|
||||
var v = getLastReplyAt(it)
|
||||
if (v == 0.0) {
|
||||
Log.d("Database", "Trying to add a thread with no replies and no post")
|
||||
}
|
||||
v
|
||||
}
|
||||
.map {
|
||||
getLastReplyAt(it)
|
||||
}
|
||||
if (sortedList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
val earliest = sortedList.first()
|
||||
val latest = sortedList.last()
|
||||
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ async function restDeferredAppEntryActions(
|
|||
setTimeout(async () => {
|
||||
if (chData?.channels?.length && chData.memberships?.length && initialTeamId) {
|
||||
if (isCRTEnabled && initialTeamId) {
|
||||
await syncTeamThreads(serverUrl, initialTeamId, false, false, groupLabel);
|
||||
await syncTeamThreads(serverUrl, initialTeamId, {groupLabel});
|
||||
}
|
||||
fetchPostsForUnreadChannels(serverUrl, chData.channels, chData.memberships, initialChannelId, false, groupLabel);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ export const syncThreadsIfNeeded = async (
|
|||
|
||||
if (teams?.length) {
|
||||
for (const team of teams) {
|
||||
promises.push(syncTeamThreads(serverUrl, team.id, true, true, groupLabel));
|
||||
promises.push(syncTeamThreads(serverUrl, team.id, {excludeDirect: true, fetchOnly: true, groupLabel}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -325,9 +325,22 @@ export const syncThreadsIfNeeded = async (
|
|||
}
|
||||
};
|
||||
|
||||
type SyncThreadOptions = {
|
||||
excludeDirect?: boolean;
|
||||
fetchOnly?: boolean;
|
||||
refresh?: boolean;
|
||||
groupLabel?: string;
|
||||
}
|
||||
|
||||
export const syncTeamThreads = async (
|
||||
serverUrl: string, teamId: string,
|
||||
excludeDirect = false, fetchOnly = false, groupLabel?: string,
|
||||
serverUrl: string,
|
||||
teamId: string,
|
||||
{
|
||||
excludeDirect = false,
|
||||
fetchOnly = false,
|
||||
refresh = false,
|
||||
groupLabel,
|
||||
}: SyncThreadOptions = {},
|
||||
) => {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
|
@ -368,39 +381,56 @@ export const syncTeamThreads = async (
|
|||
return {error: allUnreadThreads.error || latestThreads.error};
|
||||
}
|
||||
|
||||
const dedupe = new Set(latestThreads.threads?.map((t) => t.id));
|
||||
|
||||
if (latestThreads.threads?.length) {
|
||||
// We are fetching the threads for the first time. We get "latest" and "earliest" values.
|
||||
// At this point we may receive threads without replies, so we also check the post.create_at timestamp.
|
||||
const {earliestThread, latestThread} = getThreadsListEdges(latestThreads.threads);
|
||||
syncDataUpdate.latest = latestThread.last_reply_at;
|
||||
syncDataUpdate.earliest = earliestThread.last_reply_at;
|
||||
syncDataUpdate.latest = latestThread.last_reply_at || latestThread.post.create_at;
|
||||
syncDataUpdate.earliest = earliestThread.last_reply_at || earliestThread.post.create_at;
|
||||
|
||||
threads.push(...latestThreads.threads);
|
||||
}
|
||||
if (allUnreadThreads.threads?.length) {
|
||||
const dedupe = new Set(latestThreads.threads?.map((t) => t.id));
|
||||
const unread = allUnreadThreads.threads.filter((u) => !dedupe.has(u.id));
|
||||
threads.push(...unread);
|
||||
}
|
||||
} else {
|
||||
const allNewThreads = await fetchThreads(
|
||||
serverUrl,
|
||||
teamId,
|
||||
{deleted: true, since: syncData.latest + 1, excludeDirect},
|
||||
undefined,
|
||||
undefined,
|
||||
groupLabel,
|
||||
);
|
||||
const [allUnreadThreads, allNewThreads] = await Promise.all([
|
||||
fetchThreads(
|
||||
serverUrl,
|
||||
teamId,
|
||||
{unread: true, excludeDirect},
|
||||
Direction.Down,
|
||||
undefined,
|
||||
groupLabel,
|
||||
),
|
||||
fetchThreads(
|
||||
serverUrl,
|
||||
teamId,
|
||||
{deleted: true, since: refresh ? undefined : syncData.latest + 1, excludeDirect},
|
||||
undefined,
|
||||
1,
|
||||
groupLabel,
|
||||
),
|
||||
]);
|
||||
|
||||
if (allNewThreads.error) {
|
||||
return {error: allNewThreads.error};
|
||||
}
|
||||
if (allNewThreads.threads?.length) {
|
||||
// As we are syncing, we get all new threads and we will update the "latest" value.
|
||||
const {latestThread} = getThreadsListEdges(allNewThreads.threads);
|
||||
syncDataUpdate.latest = latestThread.last_reply_at;
|
||||
const latestDate = latestThread.last_reply_at || latestThread.post.create_at;
|
||||
syncDataUpdate.latest = Math.max(syncData.latest, latestDate);
|
||||
|
||||
threads.push(...allNewThreads.threads);
|
||||
}
|
||||
if (allUnreadThreads.threads?.length) {
|
||||
const dedupe = new Set(allNewThreads.threads?.map((t) => t.id));
|
||||
const unread = allUnreadThreads.threads.filter((u) => !dedupe.has(u.id));
|
||||
threads.push(...unread);
|
||||
}
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ export const transformThreadRecord = ({action, database, value}: TransformerArgs
|
|||
const fieldsMapper = (thread: ThreadModel) => {
|
||||
thread._raw.id = isCreateAction ? (raw?.id ?? thread.id) : record.id;
|
||||
|
||||
// When post is individually fetched, we get last_reply_at as 0, so we use the record's value
|
||||
thread.lastReplyAt = raw.last_reply_at || record?.lastReplyAt;
|
||||
// When post is individually fetched, we get last_reply_at as 0, so we use the record's value.
|
||||
// If there is no reply at, we default to the post's create_at
|
||||
thread.lastReplyAt = raw.last_reply_at || record?.lastReplyAt || raw.post.create_at;
|
||||
|
||||
thread.lastViewedAt = raw.last_viewed_at ?? record?.lastViewedAt ?? 0;
|
||||
thread.replyCount = raw.reply_count;
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export const prepareThreadsFromReceivedPosts = async (operator: ServerDataOperat
|
|||
id: post.id,
|
||||
participants: post.participants,
|
||||
reply_count: post.reply_count,
|
||||
last_reply_at: post.last_reply_at,
|
||||
last_reply_at: post.last_reply_at || post.create_at,
|
||||
is_following: post.is_following,
|
||||
lastFetchedAt: post.create_at,
|
||||
} as ThreadWithLastFetchedAt);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const enhanced = withObservables(['tab', 'teamId'], ({database, tab, teamId}: Pr
|
|||
threads: teamThreadsSyncObserver.pipe(
|
||||
switchMap((teamThreadsSync) => {
|
||||
const earliest = tab === 'all' ? teamThreadsSync?.[0]?.earliest : 0;
|
||||
return queryThreadsInTeam(database, teamId, getOnlyUnreads, false, true, true, earliest).observe();
|
||||
return queryThreadsInTeam(database, teamId, getOnlyUnreads, true, true, true, earliest).observe();
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ const ThreadsList = ({
|
|||
const handleRefresh = useCallback(() => {
|
||||
setRefreshing(true);
|
||||
|
||||
syncTeamThreads(serverUrl, teamId).finally(() => {
|
||||
syncTeamThreads(serverUrl, teamId, {refresh: true}).finally(() => {
|
||||
setRefreshing(false);
|
||||
});
|
||||
}, [serverUrl, teamId]);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ export function processIsCRTEnabled(preferences: PreferenceModel[]|PreferenceTyp
|
|||
export const getThreadsListEdges = (threads: Thread[]) => {
|
||||
// Sort a clone of 'threads' array by last_reply_at
|
||||
const sortedThreads = [...threads].sort((a, b) => {
|
||||
return a.last_reply_at - b.last_reply_at;
|
||||
const aDate = a.last_reply_at || a.post.create_at;
|
||||
const bDate = b.last_reply_at || b.post.create_at;
|
||||
return aDate - bDate;
|
||||
});
|
||||
|
||||
const earliestThread = sortedThreads[0];
|
||||
|
|
|
|||
|
|
@ -114,7 +114,11 @@ extension PushNotification {
|
|||
var copy = threads[index]
|
||||
copy.unreadMentions = thread.unreadMentions
|
||||
copy.unreadReplies = thread.unreadReplies
|
||||
copy.lastReplyAt = thread.lastReplyAt
|
||||
if (thread.lastReplyAt == 0) {
|
||||
copy.lastReplyAt = thread.post?.createAt ?? 0
|
||||
} else {
|
||||
copy.lastReplyAt = thread.lastReplyAt
|
||||
}
|
||||
copy.lastViewedAt = thread.lastViewedAt
|
||||
notificationData.threads?[index] = copy
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue