From 6da847fdf231f61a74e42b55695e7950dfa0cd82 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Thu, 29 Oct 2020 16:53:29 -0300 Subject: [PATCH 1/5] Ignore `_hidden` messages in `chat.getThreadsList` --- app/api/server/v1/chat.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js index b10a66c4e9332..447c10bd5d57a 100644 --- a/app/api/server/v1/chat.js +++ b/app/api/server/v1/chat.js @@ -445,6 +445,7 @@ API.v1.addRoute('chat.getThreadsList', { authRequired: true }, { } const typeThread = { + _hidden: { $ne: true }, ...type === 'following' && { replies: { $in: [this.userId] } }, ...type === 'unread' && { _id: { $in: Subscriptions.findOneByRoomIdAndUserId(room._id, user._id).tunread } }, ...text && { From 6e7a71f11f2e6bf003a62aeb5fc022443cb137fc Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Thu, 29 Oct 2020 17:05:54 -0300 Subject: [PATCH 2/5] Ignore frontend query text in Threads search bar --- client/channel/Threads/ContextualBar/List.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/client/channel/Threads/ContextualBar/List.js b/client/channel/Threads/ContextualBar/List.js index 8d43cb32fd886..7955fb0117dbb 100644 --- a/client/channel/Threads/ContextualBar/List.js +++ b/client/channel/Threads/ContextualBar/List.js @@ -92,14 +92,8 @@ export function withData(WrappedComponent) { useEffect(() => { const cursor = Tracker.autorun(() => { const query = { - ...type === 'subscribed' && { replies: { $in: [userId] } }, - ...type === 'unread' && { _id: { $in: subscription?.tunread } }, - ...text && { - $text: { - $search: text, - }, - }, }; + setThreads(Threads.current.find(query, { sort: { tlm: -1 } }).fetch().map(filterProps)); }); From 505292deff8bd956f9465b297be2a2dcd9d613a7 Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Thu, 29 Oct 2020 18:08:49 -0300 Subject: [PATCH 3/5] Clean current threads list before insert new filtered list --- client/channel/Threads/ContextualBar/List.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/channel/Threads/ContextualBar/List.js b/client/channel/Threads/ContextualBar/List.js index 7955fb0117dbb..f509f155cc2b4 100644 --- a/client/channel/Threads/ContextualBar/List.js +++ b/client/channel/Threads/ContextualBar/List.js @@ -61,6 +61,8 @@ export function withData(WrappedComponent) { return new Promise((resolve) => { ref.current = resolve; }); }, []); + useEffect(() => () => Threads.current.remove({}, () => {}), [text]); + useEffect(() => { if (state !== ENDPOINT_STATES.DONE || !data || !data.threads) { return; @@ -93,7 +95,6 @@ export function withData(WrappedComponent) { const cursor = Tracker.autorun(() => { const query = { }; - setThreads(Threads.current.find(query, { sort: { tlm: -1 } }).fetch().map(filterProps)); }); From 8e7092eb9a71850d5378668bdd9c478c337fed09 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Fri, 30 Oct 2020 01:41:42 -0300 Subject: [PATCH 4/5] Change how thread messages are selected --- client/channel/Threads/ContextualBar/List.js | 166 +++++++++++++------ 1 file changed, 112 insertions(+), 54 deletions(-) diff --git a/client/channel/Threads/ContextualBar/List.js b/client/channel/Threads/ContextualBar/List.js index f509f155cc2b4..d69afe1694d2a 100644 --- a/client/channel/Threads/ContextualBar/List.js +++ b/client/channel/Threads/ContextualBar/List.js @@ -1,11 +1,9 @@ -import { Mongo } from 'meteor/mongo'; -import { Tracker } from 'meteor/tracker'; import s from 'underscore.string'; import React, { useCallback, useMemo, useState, useEffect, useRef, memo } from 'react'; import { Box, Icon, TextInput, Select, Margins, Callout } from '@rocket.chat/fuselage'; import { FixedSizeList as List } from 'react-window'; import InfiniteLoader from 'react-window-infinite-loader'; -import { useDebouncedValue, useDebouncedState, useResizeObserver, useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import { useDebouncedValue, useResizeObserver, useLocalStorage } from '@rocket.chat/fuselage-hooks'; import VerticalBar from '../../../components/basic/VerticalBar'; import { useTranslation } from '../../../contexts/TranslationContext'; @@ -13,7 +11,7 @@ import { useRoute, useCurrentRoute } from '../../../contexts/RouterContext'; import { call, renderMessageBody } from '../../../../app/ui-utils/client'; import { useUserId, useUserSubscription } from '../../../contexts/UserContext'; import { Messages } from '../../../../app/models/client'; -import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; +import { ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; import { useUserRoom } from '../../hooks/useUserRoom'; import { useSetting } from '../../../contexts/SettingsContext'; import { useTimeAgo } from '../../../hooks/useTimeAgo'; @@ -21,6 +19,7 @@ import { clickableItem } from '../../helpers/clickableItem'; import { MessageSkeleton } from '../../components/Message'; import ThreadListMessage from './components/Message'; import { getConfig } from '../../../../app/ui-utils/client/config'; +import { useEndpoint } from '../../../contexts/ServerContext'; function mapProps(WrappedComponent) { return ({ msg, username, replies, tcount, ts, ...props }) => ; @@ -41,69 +40,128 @@ export function withData(WrappedComponent) { return ({ rid, ...props }) => { const room = useUserRoom(rid, roomFields); const subscription = useUserSubscription(rid, subscriptionFields); - const userId = useUserId(); + + const [{ + state, + error, + threads, + count, + }, setState] = useState(() => ({ + state: ENDPOINT_STATES.LOADING, + error: null, + data: {}, + threads: [], + count: 0, + })); const [type, setType] = useLocalStorage('thread-list-type', 'all'); const [text, setText] = useState(''); - const [total, setTotal] = useState(LIST_SIZE); - const [threads, setThreads] = useDebouncedState([], 100); - const Threads = useRef(new Mongo.Collection(null)); - const ref = useRef(); - const [pagination, setPagination] = useState({ skip: 0, count: LIST_SIZE }); - - const params = useMemo(() => ({ rid: room._id, count: pagination.count, offset: pagination.skip, type, text }), [room._id, pagination.skip, pagination.count, type, text]); - - const { data, state, error } = useEndpointDataExperimental('chat.getThreadsList', useDebouncedValue(params, 400)); - - const loadMoreItems = useCallback((skip, count) => { - setPagination({ skip, count: count - skip }); - return new Promise((resolve) => { ref.current = resolve; }); - }, []); - - useEffect(() => () => Threads.current.remove({}, () => {}), [text]); - - useEffect(() => { - if (state !== ENDPOINT_STATES.DONE || !data || !data.threads) { - return; + const mergeThreads = useCallback( + (threads, newThreads) => + Array.from( + new Map([ + ...threads.map((msg) => [msg._id, msg]), + ...newThreads.map((msg) => [msg._id, msg]), + ]).values(), + ) + .sort((a, b) => a.tlm.getTime() - b.tlm.getTime()), + [], + ); + + const getThreadsList = useEndpoint('GET', 'chat.getThreadsList'); + const fetchThreads = useCallback(async ({ rid, offset, limit, type, text }) => { + try { + const data = await getThreadsList({ + rid, + offset, + count: limit, + type, + text, + }); + + setState(({ threads }) => ({ + state: ENDPOINT_STATES.DONE, + error: null, + data, + threads: mergeThreads(threads, data.threads.map(filterProps)), + count: data.total, + })); + } catch (error) { + setState(({ data, threads, count }) => ({ + state: ENDPOINT_STATES.ERROR, + error, + data, + threads, + count, + })); } + }, [getThreadsList, mergeThreads]); - data.threads.forEach(({ _id, ...message }) => { - Threads.current.upsert({ _id }, filterProps(message)); + const debouncedText = useDebouncedValue(text, 400); + useEffect(() => { + fetchThreads({ + rid: room._id, + offset: 0, + limit: LIST_SIZE, + type, + text: debouncedText, }); - setTotal(data.total); - ref.current && ref.current(); - }, [data, state]); + }, [debouncedText, fetchThreads, room._id, type]); useEffect(() => { - const cursor = Messages.find({ rid: room._id, tcount: { $exists: true }, _hidden: { $ne: true } }).observe({ - added: ({ _id, ...message }) => { - Threads.current.upsert({ _id }, message); - }, // Update message to re-render DOM - changed: ({ _id, ...message }) => { - Threads.current.update({ _id }, message); - }, // Update message to re-render DOM + const cursor = Messages.find({ + rid: room._id, + tcount: { $exists: true }, + _hidden: { $ne: true }, + }).observe({ + added: (message) => { + setState((state) => ({ + ...state, + threads: mergeThreads(state.threads, [message]), + })); + }, + changed: (message) => { + setState((state) => ({ + ...state, + threads: mergeThreads(state.threads, [message]), + })); + }, removed: ({ _id }) => { - Threads.current.remove(_id); + setState((state) => ({ + ...state, + threads: state.threads.filter((thread) => thread._id !== _id), + })); }, }); - return () => cursor.stop(); - }, [room._id]); + return () => { + cursor.stop(); + }; + }, [mergeThreads, room._id]); - useEffect(() => { - const cursor = Tracker.autorun(() => { - const query = { - }; - setThreads(Threads.current.find(query, { sort: { tlm: -1 } }).fetch().map(filterProps)); - }); + const filteredThreads = threads.filter((thread) => { + if (type === 'following') { + return thread.replies?.includes(userId) ?? false; + } - return () => cursor.stop(); - }, [room._id, type, setThreads, userId, subscription.tunread, text]); + if (type === 'unread') { + return subscription?.tunread?.includes(thread._id) ?? false; + } + + return true; + }); - const handleTextChange = useCallback((e) => { - setPagination({ skip: 0, count: LIST_SIZE }); - setText(e.currentTarget.value); + const loadMoreItems = useCallback((start, end) => fetchThreads({ + rid: room._id, + offset: start, + limit: end - start, + type, + text, + }), [fetchThreads, room._id, type, text]); + + const handleTextChange = useCallback((event) => { + setText(event.currentTarget.value); }, []); return , [showRealNames, unread, unreadUser, unreadGroup, userId, onClick]); const isItemLoaded = useCallback((index) => index < threadsRef.current.length, []); - const { ref, contentBoxSize: { inlineSize = 378, blockSize = 750 } = {} } = useResizeObserver({ debounceDelay: 100 }); + const { ref, contentBoxSize: { inlineSize = 378, blockSize = 1 } = {} } = useResizeObserver({ debounceDelay: 100 }); return From a934aab1773a6861984fab99e5636f0c188f849a Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Fri, 30 Oct 2020 11:17:49 -0300 Subject: [PATCH 5/5] Use only the endpoint as data source --- client/channel/Threads/ContextualBar/List.js | 53 ++------------------ 1 file changed, 3 insertions(+), 50 deletions(-) diff --git a/client/channel/Threads/ContextualBar/List.js b/client/channel/Threads/ContextualBar/List.js index d69afe1694d2a..e15138413038f 100644 --- a/client/channel/Threads/ContextualBar/List.js +++ b/client/channel/Threads/ContextualBar/List.js @@ -10,7 +10,6 @@ import { useTranslation } from '../../../contexts/TranslationContext'; import { useRoute, useCurrentRoute } from '../../../contexts/RouterContext'; import { call, renderMessageBody } from '../../../../app/ui-utils/client'; import { useUserId, useUserSubscription } from '../../../contexts/UserContext'; -import { Messages } from '../../../../app/models/client'; import { ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; import { useUserRoom } from '../../hooks/useUserRoom'; import { useSetting } from '../../../contexts/SettingsContext'; @@ -50,7 +49,6 @@ export function withData(WrappedComponent) { }, setState] = useState(() => ({ state: ENDPOINT_STATES.LOADING, error: null, - data: {}, threads: [], count: 0, })); @@ -83,15 +81,13 @@ export function withData(WrappedComponent) { setState(({ threads }) => ({ state: ENDPOINT_STATES.DONE, error: null, - data, - threads: mergeThreads(threads, data.threads.map(filterProps)), + threads: mergeThreads(offset === 0 ? [] : threads, data.threads.map(filterProps)), count: data.total, })); } catch (error) { - setState(({ data, threads, count }) => ({ + setState(({ threads, count }) => ({ state: ENDPOINT_STATES.ERROR, error, - data, threads, count, })); @@ -109,49 +105,6 @@ export function withData(WrappedComponent) { }); }, [debouncedText, fetchThreads, room._id, type]); - useEffect(() => { - const cursor = Messages.find({ - rid: room._id, - tcount: { $exists: true }, - _hidden: { $ne: true }, - }).observe({ - added: (message) => { - setState((state) => ({ - ...state, - threads: mergeThreads(state.threads, [message]), - })); - }, - changed: (message) => { - setState((state) => ({ - ...state, - threads: mergeThreads(state.threads, [message]), - })); - }, - removed: ({ _id }) => { - setState((state) => ({ - ...state, - threads: state.threads.filter((thread) => thread._id !== _id), - })); - }, - }); - - return () => { - cursor.stop(); - }; - }, [mergeThreads, room._id]); - - const filteredThreads = threads.filter((thread) => { - if (type === 'following') { - return thread.replies?.includes(userId) ?? false; - } - - if (type === 'unread') { - return subscription?.tunread?.includes(thread._id) ?? false; - } - - return true; - }); - const loadMoreItems = useCallback((start, end) => fetchThreads({ rid: room._id, offset: start, @@ -171,7 +124,7 @@ export function withData(WrappedComponent) { unreadGroup={subscription?.tunreadGroup} userId={userId} error={error} - threads={filteredThreads} + threads={threads} total={count} loading={state === ENDPOINT_STATES.LOADING} loadMoreItems={loadMoreItems}