From efeab53d0b7c00bd7a085efa03484c90d61359dd Mon Sep 17 00:00:00 2001 From: Krm <35422525+keremkurtulus@users.noreply.github.com> Date: Thu, 26 May 2022 21:23:40 +0300 Subject: [PATCH] [Gekiodu - MM-44156] Channel ordering is different between web and mobile (#6304) * new approach added queries for sorting channels * crt reply checking added for post remote actions * Fix category_body test * crt replay check fixed for fetchPostsForChannel * sortedchannels re-rendering fix * Only fetch Direct channels if there are more/less Co-authored-by: Elias Nahum --- app/actions/remote/post.ts | 30 ++++-- app/actions/remote/search.ts | 2 +- app/actions/websocket/posts.ts | 11 ++- app/queries/servers/categories.ts | 26 ++++- app/queries/servers/channel.ts | 40 +++++++- .../categories/body/category_body.test.tsx | 20 ++++ .../categories/body/category_body.tsx | 12 +-- .../categories_list/categories/body/index.ts | 97 +++++++------------ .../categories/unreads/index.ts | 78 +++++++-------- types/api/channels.d.ts | 1 + 10 files changed, 186 insertions(+), 131 deletions(-) diff --git a/app/actions/remote/post.ts b/app/actions/remote/post.ts index 8e01a5021e..4b1097512c 100644 --- a/app/actions/remote/post.ts +++ b/app/actions/remote/post.ts @@ -137,9 +137,12 @@ export async function createPost(serverUrl: string, post: Partial, files: posts: [created], prepareRecordsOnly: true, }); - const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true); - if (member) { - models.push(member); + const isCrtReply = isCRTEnabled && created.root_id !== ''; + if (!isCrtReply) { + const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true); + if (member) { + models.push(member); + } } if (isCRTEnabled) { const {models: threadModels} = await createThreadFromNewPost(serverUrl, created, true); @@ -201,6 +204,9 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => { return {error}; } + const {database} = operator; + const isCRTEnabled = await getIsCRTEnabled(database); + try { const timestamp = Date.now(); const apiPost = await post.toApi(); @@ -232,9 +238,12 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => { posts: [created], prepareRecordsOnly: true, }); - const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true); - if (member) { - models.push(member); + const isCrtReply = isCRTEnabled && created.root_id !== ''; + if (!isCrtReply) { + const {member} = await updateLastPostAt(serverUrl, created.channel_id, created.create_at, true); + if (member) { + models.push(member); + } } await operator.batchRecords(models); } catch (error: any) { @@ -322,7 +331,8 @@ export async function fetchPostsForChannel(serverUrl: string, channelId: string, let lastPostAt = 0; for (const post of data.posts) { - if (!isCRTEnabled || post.root_id) { + const isCrtReply = isCRTEnabled && post.root_id !== ''; + if (!isCrtReply) { lastPostAt = post.create_at > lastPostAt ? post.create_at : lastPostAt; } } @@ -727,7 +737,8 @@ export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Po const channelMemberships = await Promise.all(userPromises); if (!fetchOnly && channels.length && channelMemberships.length) { - const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships); + const isCRTEnabled = await getIsCRTEnabled(operator.database); + const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled); if (modelPromises.length) { const channelModelsArray = await Promise.all(modelPromises); if (channelModelsArray.length) { @@ -993,7 +1004,8 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe } if (channels?.length && channelMemberships?.length) { - const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships); + const isCRTEnabled = await getIsCRTEnabled(operator.database); + const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled); if (channelPromises.length) { promises.push(...channelPromises); } diff --git a/app/actions/remote/search.ts b/app/actions/remote/search.ts index 34715c21a2..b5c00ef988 100644 --- a/app/actions/remote/search.ts +++ b/app/actions/remote/search.ts @@ -85,7 +85,7 @@ export async function fetchRecentMentions(serverUrl: string): Promise { try { @@ -104,3 +107,24 @@ export const observeIsChannelFavorited = (database: Database, teamId: string, ch distinctUntilChanged(), ); }; + +export const observeChannelsByCategoryChannelSortOrder = (database: Database, category: CategoryModel, excludeIds?: string[]) => { + return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe( + switchMap((categoryChannels) => { + const ids = categoryChannels.map((cc) => cc.channelId); + const idsStr = `'${ids.join("','")}'`; + const exclude = excludeIds?.length ? `AND c.id NOT IN ('${excludeIds.join("','")}')` : ''; + return database.get(CHANNEL).query( + Q.unsafeSqlQuery(`SELECT DISTINCT c.* FROM ${CHANNEL} c INNER JOIN + ${CATEGORY_CHANNEL} cc ON cc.channel_id=c.id AND c.id IN (${idsStr}) ${exclude} + ORDER BY cc.sort_order`), + ).observe(); + }), + ); +}; + +export const observeChannelsByLastPostAtInCategory = (database: Database, category: CategoryModel, excludeIds?: string[]) => { + return category.myChannels.observeWithColumns(['last_post_at']).pipe( + switchMap((myChannels) => observeChannelsByLastPostAt(database, myChannels, excludeIds)), + ); +}; diff --git a/app/queries/servers/channel.ts b/app/queries/servers/channel.ts index 4d8503311e..e081383d31 100644 --- a/app/queries/servers/channel.ts +++ b/app/queries/servers/channel.ts @@ -24,11 +24,11 @@ import type UserModel from '@typings/database/models/servers/user'; const {SERVER: {CHANNEL, MY_CHANNEL, CHANNEL_MEMBERSHIP, MY_CHANNEL_SETTINGS, CHANNEL_INFO, USER}} = MM_TABLES; -export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, channels: Channel[], channelMembers: ChannelMembership[]): Array> { +export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, channels: Channel[], channelMembers: ChannelMembership[], isCRTEnabled?: boolean): Array> { const channelInfos: ChannelInfo[] = []; - const memberships = channelMembers.map((cm) => ({...cm, id: cm.channel_id})); - + const channelMap: Record = {}; for (const c of channels) { + channelMap[c.id] = c; channelInfos.push({ id: c.id, header: c.header, @@ -39,11 +39,21 @@ export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, }); } + const memberships = channelMembers.map((cm) => { + const channel = channelMap[cm.channel_id]; + return { + ...cm, + id: cm.channel_id, + last_post_at: channel.last_post_at, + last_root_post_at: channel.last_root_post_at, + }; + }); + try { const channelRecords = operator.handleChannel({channels, prepareRecordsOnly: true}); const channelInfoRecords = operator.handleChannelInfo({channelInfos, prepareRecordsOnly: true}); const membershipRecords = operator.handleChannelMembership({channelMemberships: memberships, prepareRecordsOnly: true}); - const myChannelRecords = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true}); + const myChannelRecords = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true, isCRTEnabled}); const myChannelSettingsRecords = operator.handleMyChannelSettings({settings: memberships, prepareRecordsOnly: true}); return [channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords]; @@ -99,6 +109,7 @@ export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, tea const member = membershipsMap[c.id]; if (member) { member.last_post_at = c.last_post_at; + member.last_root_post_at = c.last_root_post_at; } channelInfos.push({ @@ -399,6 +410,16 @@ export const observeAllMyChannelNotifyProps = (database: Database) => { ); }; +export const observeNotifyPropsByChannels = (database: Database, channels: ChannelModel[]|MyChannelModel[]) => { + const ids = channels.map((c) => c.id); + return queryMyChannelSettingsByIds(database, ids).observeWithColumns(['notify_props']).pipe( + map$((settings) => settings.reduce>>((obj, setting) => { + obj[setting.id] = setting.notifyProps; + return obj; + }, {})), + ); +}; + export const queryChannelsByNames = (database: Database, names: string[]) => { return database.get(CHANNEL).query(Q.where('name', Q.oneOf(names))); }; @@ -563,3 +584,14 @@ export const observeChannelSettings = (database: Database, channelId: string) => switchMap((result) => (result.length ? result[0].observe() : of$(undefined))), ); }; + +export const observeChannelsByLastPostAt = (database: Database, myChannels: MyChannelModel[], excludeIds?: string[]) => { + const ids = myChannels.map((c) => c.id); + const idsStr = `'${ids.join("','")}'`; + const exclude = excludeIds?.length ? `AND c.id NOT IN ('${excludeIds.join("','")}')` : ''; + return database.get(CHANNEL).query( + Q.unsafeSqlQuery(`SELECT DISTINCT c.* FROM ${CHANNEL} c INNER JOIN + ${MY_CHANNEL} mc ON mc.id=c.id AND c.id IN (${idsStr}) ${exclude} + ORDER BY CASE mc.last_post_at WHEN 0 THEN c.create_at ELSE mc.last_post_at END DESC`), + ).observe(); +}; diff --git a/app/screens/home/channel_list/categories_list/categories/body/category_body.test.tsx b/app/screens/home/channel_list/categories_list/categories/body/category_body.test.tsx index 6030c78aad..5aec2a54ab 100644 --- a/app/screens/home/channel_list/categories_list/categories/body/category_body.test.tsx +++ b/app/screens/home/channel_list/categories_list/categories/body/category_body.test.tsx @@ -12,9 +12,29 @@ import TestHelper from '@test/test_helper'; import CategoryBody from '.'; import type CategoryModel from '@typings/database/models/servers/category'; +import type CategoryChannelModel from '@typings/database/models/servers/category_channel'; +import type ChannelModel from '@typings/database/models/servers/channel'; const {SERVER: {CATEGORY}} = MM_TABLES; +jest.mock('@queries/servers/categories', () => { + const Queries = jest.requireActual('@queries/servers/categories'); + const switchMap = jest.requireActual('rxjs/operators').switchMap; + const mQ = jest.requireActual('@nozbe/watermelondb').Q; + + return { + ...Queries, + observeChannelsByCategoryChannelSortOrder: (database: Database, category: CategoryModel, excludeIds?: string[]) => { + return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe( + switchMap((categoryChannels: CategoryChannelModel[]) => { + const ids = categoryChannels.filter((cc) => excludeIds?.includes(cc.channelId)).map((cc) => cc.channelId); + return database.get('Channel').query(mQ.where('id', mQ.oneOf(ids))).observe(); + }), + ); + }, + }; +}); + describe('components/channel_list/categories/body', () => { let database: Database; let category: CategoryModel; diff --git a/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx b/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx index e5077a2e6b..c3a2fbcb1e 100644 --- a/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx +++ b/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx @@ -16,7 +16,6 @@ import type ChannelModel from '@typings/database/models/servers/channel'; type Props = { sortedChannels: ChannelModel[]; - hiddenChannelIds: Set; category: CategoryModel; limit: number; onChannelSwitch: (channelId: string) => void; @@ -25,21 +24,16 @@ type Props = { const extractKey = (item: ChannelModel) => item.id; -const CategoryBody = ({sortedChannels, category, hiddenChannelIds, limit, onChannelSwitch, unreadChannels}: Props) => { +const CategoryBody = ({sortedChannels, category, limit, onChannelSwitch, unreadChannels}: Props) => { const serverUrl = useServerUrl(); const ids = useMemo(() => { - let filteredChannels = sortedChannels; - - // Remove all closed gm/dms - if (hiddenChannelIds.size) { - filteredChannels = sortedChannels.filter((item) => item && !hiddenChannelIds.has(item.id)); - } + const filteredChannels = sortedChannels; if (category.type === DMS_CATEGORY && limit > 0) { return filteredChannels.slice(0, limit); } return filteredChannels; - }, [category.type, limit, hiddenChannelIds, sortedChannels.length]); + }, [category.type, limit, sortedChannels]); const directChannels = useMemo(() => { return ids.concat(unreadChannels).filter(isDMorGM); diff --git a/app/screens/home/channel_list/categories_list/categories/body/index.ts b/app/screens/home/channel_list/categories_list/categories/body/index.ts index b1f751075e..af5b951578 100644 --- a/app/screens/home/channel_list/categories_list/categories/body/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/body/index.ts @@ -1,16 +1,17 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Database} from '@nozbe/watermelondb'; +import {Database, Q} from '@nozbe/watermelondb'; import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import {combineLatest, of as of$} from 'rxjs'; -import {map, switchMap, concatAll, combineLatestWith} from 'rxjs/operators'; +import {map, switchMap, combineLatestWith} from 'rxjs/operators'; import {General, Preferences} from '@constants'; import {DMS_CATEGORY} from '@constants/categories'; import {getPreferenceAsBool} from '@helpers/api/preference'; -import {observeAllMyChannelNotifyProps, queryChannelsByNames, queryMyChannelSettingsByIds} from '@queries/servers/channel'; +import {observeChannelsByCategoryChannelSortOrder, observeChannelsByLastPostAtInCategory} from '@queries/servers/categories'; +import {observeNotifyPropsByChannels, queryChannelsByNames} from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {observeCurrentChannelId, observeCurrentUserId, observeLastUnreadChannelId} from '@queries/servers/system'; import {WithDatabaseArgs} from '@typings/database/database'; @@ -19,16 +20,20 @@ import {getDirectChannelName} from '@utils/channel'; import CategoryBody from './category_body'; import type CategoryModel from '@typings/database/models/servers/category'; -import type CategoryChannelModel from '@typings/database/models/servers/category_channel'; import type ChannelModel from '@typings/database/models/servers/channel'; -import type MyChannelModel from '@typings/database/models/servers/my_channel'; -import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings'; import type PreferenceModel from '@typings/database/models/servers/preference'; type ChannelData = Pick & { isMuted: boolean; }; +type EnhanceProps = { + category: CategoryModel; + locale: string; + currentUserId: string; + isTablet: boolean; +} & WithDatabaseArgs + const sortAlpha = (locale: string, a: ChannelData, b: ChannelData) => { if (a.isMuted && !b.isMuted) { return 1; @@ -43,23 +48,18 @@ const filterArchived = (channels: Array, currentChannelId: return channels.filter((c): c is ChannelModel => c != null && ((c.deleteAt > 0 && c.id === currentChannelId) || !c.deleteAt)); }; -const buildAlphaData = (channels: ChannelModel[], settings: MyChannelSettingsModel[], locale: string) => { - const settingsById = settings.reduce((result: Record, s) => { - result[s.id] = s; - return result; - }, {}); - +const buildAlphaData = (channels: ChannelModel[], notifyProps: Record>, locale: string) => { const chanelsById = channels.reduce((result: Record, c) => { result[c.id] = c; return result; }, {}); const combined = channels.map((c) => { - const s = settingsById[c.id]; + const s = notifyProps[c.id]; return { id: c.id, displayName: c.displayName, - isMuted: s?.notifyProps?.mark_unread === General.MENTION, + isMuted: s?.mark_unread === General.MENTION, }; }); @@ -67,37 +67,20 @@ const buildAlphaData = (channels: ChannelModel[], settings: MyChannelSettingsMod return of$(combined.map((cdata) => chanelsById[cdata.id])); }; -const observeSettings = (database: Database, channels: ChannelModel[]) => { - const ids = channels.map((c) => c.id); - return queryMyChannelSettingsByIds(database, ids).observeWithColumns(['notify_props']); -}; - -export const getChannelsFromRelation = async (relations: CategoryChannelModel[] | MyChannelModel[]) => { - return Promise.all(relations.map((r) => r.channel?.fetch())); -}; - -const getSortedChannels = (database: Database, category: CategoryModel, locale: string) => { +const observeSortedChannels = (database: Database, category: CategoryModel, excludeIds: string[], locale: string) => { switch (category.sorting) { case 'alpha': { - const channels = category.channels.observeWithColumns(['display_name']); - const settings = channels.pipe( - switchMap((cs) => observeSettings(database, cs)), - ); - return combineLatest([channels, settings]).pipe( - switchMap(([cs, st]) => buildAlphaData(cs, st, locale)), + const channels = category.channels.extend(Q.where('id', Q.notIn(excludeIds))).observeWithColumns(['display_name']); + const notifyProps = channels.pipe(switchMap((cs) => observeNotifyPropsByChannels(database, cs))); + return combineLatest([channels, notifyProps]).pipe( + switchMap(([cs, np]) => buildAlphaData(cs, np, locale)), ); } case 'manual': { - return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe( - map(getChannelsFromRelation), - concatAll(), - ); + return observeChannelsByCategoryChannelSortOrder(database, category, excludeIds); } default: - return category.myChannels.observeWithColumns(['last_post_at']).pipe( - map(getChannelsFromRelation), - concatAll(), - ); + return observeChannelsByLastPostAtInCategory(database, category, excludeIds); } }; @@ -105,25 +88,13 @@ const mapPrefName = (prefs: PreferenceModel[]) => of$(prefs.map((p) => p.name)); const mapChannelIds = (channels: ChannelModel[]) => of$(channels.map((c) => c.id)); -type EnhanceProps = { - category: CategoryModel; - locale: string; - currentUserId: string; - isTablet: boolean; -} & WithDatabaseArgs - const withUserId = withObservables([], ({database}: WithDatabaseArgs) => ({currentUserId: observeCurrentUserId(database)})); const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, locale, isTablet, database, currentUserId}: EnhanceProps) => { + const dmMap = (p: PreferenceModel) => getDirectChannelName(p.name, currentUserId); + const observedCategory = category.observe(); const currentChannelId = observeCurrentChannelId(database); - const sortedChannels = observedCategory.pipe( - switchMap((c) => getSortedChannels(database, c, locale)), - combineLatestWith(currentChannelId), - map(([cs, ccId]) => filterArchived(cs, ccId)), - ); - - const dmMap = (p: PreferenceModel) => getDirectChannelName(p.name, currentUserId); const hiddenDmIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, undefined, 'false'). observeWithColumns(['value']).pipe( @@ -137,8 +108,19 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, }), ); - const hiddenGmIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_GROUP_CHANNEL_SHOW, undefined, 'false'). - observeWithColumns(['value']).pipe(switchMap(mapPrefName)); + const hiddenChannelIds = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_GROUP_CHANNEL_SHOW, undefined, 'false'). + observeWithColumns(['value']).pipe( + switchMap(mapPrefName), + combineLatestWith(hiddenDmIds), + switchMap(([a, b]) => of$(new Set(a.concat(b)))), + ); + + const sortedChannels = observedCategory.pipe( + combineLatestWith(hiddenChannelIds), + switchMap(([c, excludeIds]) => observeSortedChannels(database, c, Array.from(excludeIds), locale)), + combineLatestWith(currentChannelId), + map(([channels, ccId]) => filterArchived(channels, ccId)), + ); let limit = of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT); if (category.type === DMS_CATEGORY) { @@ -150,19 +132,15 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, ); } - const hiddenChannelIds = combineLatest([hiddenDmIds, hiddenGmIds]).pipe(switchMap( - ([a, b]) => of$(new Set(a.concat(b))), - )); - const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS). observeWithColumns(['value']). pipe( switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))), ); - const notifyProps = observeAllMyChannelNotifyProps(database); const lastUnreadId = isTablet ? observeLastUnreadChannelId(database) : of$(undefined); const unreadChannels = category.myChannels.observeWithColumns(['mentions_count', 'is_unread']); + const notifyProps = unreadChannels.pipe(switchMap((myChannels) => observeNotifyPropsByChannels(database, myChannels))); const filterUnreads = unreadChannels.pipe( combineLatestWith(notifyProps, lastUnreadId), map(([my, settings, lastUnread]) => { @@ -185,7 +163,6 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, return { limit, - hiddenChannelIds, sortedChannels: filtered, category: observedCategory, unreadChannels: sortedChannels.pipe( diff --git a/app/screens/home/channel_list/categories_list/categories/unreads/index.ts b/app/screens/home/channel_list/categories_list/categories/unreads/index.ts index a42b1f2255..4c2e51c202 100644 --- a/app/screens/home/channel_list/categories_list/categories/unreads/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/unreads/index.ts @@ -3,17 +3,15 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; -import {of as of$, combineLatest} from 'rxjs'; -import {combineLatestWith, concatAll, map, switchMap} from 'rxjs/operators'; +import {of as of$} from 'rxjs'; +import {combineLatestWith, map, switchMap} from 'rxjs/operators'; import {Preferences} from '@constants'; import {getPreferenceAsBool} from '@helpers/api/preference'; -import {getChannelById, observeAllMyChannelNotifyProps, queryMyChannelUnreads} from '@queries/servers/channel'; +import {getChannelById, observeChannelsByLastPostAt, observeNotifyPropsByChannels, queryMyChannelUnreads} from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {observeLastUnreadChannelId} from '@queries/servers/system'; -import {getChannelsFromRelation} from '../body'; - import UnreadCategories from './unreads'; import type {WithDatabaseArgs} from '@typings/database/database'; @@ -32,36 +30,31 @@ type CA = [ b: ChannelModel | undefined, ] -const concatenateChannelsArray = ([a, b]: CA) => { - return of$(b ? a.filter((c) => c && c.id !== b.id).concat(b) : a); -}; - type NotifyProps = { [key: string]: Partial; } -const mostRecentFirst = (a: MyChannelModel, b: MyChannelModel) => { - return b.lastPostAt - a.lastPostAt; -}; - /** * Filtering / Sorting: * * Unreads, Mentions, and Muted Mentions Only - * * Mentions on top, then unreads, then muted channels with mentions. - * Secondary sorting within each of those is by recent posting or by recent root post if CRT is enabled. */ -type FilterAndSortMyChannelsArgs = [ + type FilterAndSortMyChannelsArgs = [ MyChannelModel[], + Record, NotifyProps, ] -const filterAndSortMyChannels = ([myChannels, notifyProps]: FilterAndSortMyChannelsArgs): MyChannelModel[] => { - const mentions: MyChannelModel[] = []; - const unreads: MyChannelModel[] = []; - const mutedMentions: MyChannelModel[] = []; +const concatenateChannelsArray = ([a, b]: CA) => { + return of$(b ? a.filter((c) => c && c.id !== b.id).concat(b) : a); +}; + +const filterAndSortMyChannels = ([myChannels, channels, notifyProps]: FilterAndSortMyChannelsArgs): ChannelModel[] => { + const mentions: ChannelModel[] = []; + const unreads: ChannelModel[] = []; + const mutedMentions: ChannelModel[] = []; const isMuted = (id: string) => { return notifyProps[id]?.mark_unread === 'mention'; @@ -71,32 +64,34 @@ const filterAndSortMyChannels = ([myChannels, notifyProps]: FilterAndSortMyChann const id = myChannel.id; // is it a mention? - if (!isMuted(id) && myChannel.mentionsCount > 0) { - mentions.push(myChannel); + if (!isMuted(id) && myChannel.mentionsCount > 0 && channels[id]) { + mentions.push(channels[id]); continue; } // is it unread? - if (!isMuted(myChannel.id) && myChannel.isUnread) { - unreads.push(myChannel); + if (!isMuted(myChannel.id) && myChannel.isUnread && channels[id]) { + unreads.push(channels[id]); continue; } // is it a muted mention? - if (isMuted(myChannel.id) && myChannel.mentionsCount > 0) { - mutedMentions.push(myChannel); + if (isMuted(myChannel.id) && myChannel.mentionsCount > 0 && channels[id]) { + mutedMentions.push(channels[id]); continue; } } - // Sort - mentions.sort(mostRecentFirst); - unreads.sort(mostRecentFirst); - mutedMentions.sort(mostRecentFirst); - return [...mentions, ...unreads, ...mutedMentions]; }; +const makeChannelsMap = (channels: ChannelModel[]) => { + return channels.reduce>((result, c) => { + result[c.id] = c; + return result; + }, {}); +}; + const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], ({currentTeamId, isTablet, database, onlyUnreads}: WithDatabaseProps) => { const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS). observeWithColumns(['value']). @@ -108,23 +103,18 @@ const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], ( const unreadChannels = unreadsOnTop.pipe(switchMap((gU) => { if (gU || onlyUnreads) { - const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe( - switchMap(getC), - ) : of$(''); - const notifyProps = observeAllMyChannelNotifyProps(database); + const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe(switchMap(getC)) : of$(undefined); + const myUnreadChannels = queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']); + const notifyProps = myUnreadChannels.pipe(switchMap((cs) => observeNotifyPropsByChannels(database, cs))); + const channels = myUnreadChannels.pipe(switchMap((myChannels) => observeChannelsByLastPostAt(database, myChannels))); + const channelsMap = channels.pipe(switchMap((cs) => of$(makeChannelsMap(cs)))); - const unreads = queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']).pipe( - combineLatestWith(notifyProps), + return queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']).pipe( + combineLatestWith(channelsMap, notifyProps), map(filterAndSortMyChannels), - map(getChannelsFromRelation), - concatAll(), - ); - - const combined = combineLatest([unreads, lastUnread]).pipe( + combineLatestWith(lastUnread), switchMap(concatenateChannelsArray), ); - - return combined; } return of$([]); })); diff --git a/types/api/channels.d.ts b/types/api/channels.d.ts index e935ea3563..1f14f292a4 100644 --- a/types/api/channels.d.ts +++ b/types/api/channels.d.ts @@ -27,6 +27,7 @@ type Channel = { header: string; purpose: string; last_post_at: number; + last_root_post_at?: number; total_msg_count: number; total_msg_count_root?: number; extra_update_at: number;