From cbae247fee5711842a1f41ad6fe91ee3641b2fed Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 23 Aug 2021 22:53:25 -0300 Subject: [PATCH] [FIX] Threads List being requested more than expected (#22879) --- app/models/server/models/Messages.js | 2 + .../client/components/ThreadComponent.tsx | 2 +- app/threads/client/components/ThreadView.tsx | 2 +- app/threads/client/flextab/threadlist.tsx | 7 +- client/hooks/lists/useScrollableRecordList.ts | 2 +- client/lib/lists/ThreadsList.ts | 2 +- .../contextualBar/Threads/{Row.js => Row.tsx} | 33 +++++--- .../Threads/{ThreadList.js => ThreadList.tsx} | 82 +++++++++++++------ .../Threads/{withData.js => withData.tsx} | 60 +++++++++----- .../views/room/providers/ToolboxProvider.tsx | 3 +- definition/ISubscription.ts | 6 +- 11 files changed, 138 insertions(+), 63 deletions(-) rename client/views/room/contextualBar/Threads/{Row.js => Row.tsx} (64%) rename client/views/room/contextualBar/Threads/{ThreadList.js => ThreadList.tsx} (69%) rename client/views/room/contextualBar/Threads/{withData.js => withData.tsx} (54%) diff --git a/app/models/server/models/Messages.js b/app/models/server/models/Messages.js index a840ef76d72..bc5a4b22cdb 100644 --- a/app/models/server/models/Messages.js +++ b/app/models/server/models/Messages.js @@ -30,6 +30,8 @@ export class Messages extends Base { // threads this.tryEnsureIndex({ tmid: 1 }, { sparse: true }); this.tryEnsureIndex({ tcount: 1, tlm: 1 }, { sparse: true }); + this.tryEnsureIndex({ rid: 1, tlm: -1 }, { partialFilterExpression: { tcount: { $exists: true } } }); // used for the List Threads + this.tryEnsureIndex({ rid: 1, tcount: 1 }); // used for the List Threads Count // livechat this.tryEnsureIndex({ 'navigation.token': 1 }, { sparse: true }); } diff --git a/app/threads/client/components/ThreadComponent.tsx b/app/threads/client/components/ThreadComponent.tsx index 36fe0a4655e..5af90e6c875 100644 --- a/app/threads/client/components/ThreadComponent.tsx +++ b/app/threads/client/components/ThreadComponent.tsx @@ -59,7 +59,7 @@ const ThreadComponent: FC<{ mid: string; jump: unknown; room: IRoom; - onClickBack: () => void; + onClickBack: (e: unknown) => void; }> = ({ mid, jump, diff --git a/app/threads/client/components/ThreadView.tsx b/app/threads/client/components/ThreadView.tsx index 6838abfdf6a..98752fbc3b2 100644 --- a/app/threads/client/components/ThreadView.tsx +++ b/app/threads/client/components/ThreadView.tsx @@ -12,7 +12,7 @@ type ThreadViewProps = { onToggleExpand: (expanded: boolean) => void; onToggleFollow: (following: boolean) => void; onClose: () => void; - onClickBack: () => void; + onClickBack: (e: unknown) => void; }; const ThreadView = forwardRef(({ diff --git a/app/threads/client/flextab/threadlist.tsx b/app/threads/client/flextab/threadlist.tsx index 9d76aad3c7a..3a95124fc61 100644 --- a/app/threads/client/flextab/threadlist.tsx +++ b/app/threads/client/flextab/threadlist.tsx @@ -29,8 +29,11 @@ addAction('thread', (options) => { icon: 'thread', template, renderAction: (props): ReactNode => { - const unread = room.tunread?.length > 99 ? '99+' : room.tunread?.length; - const variant = getVariant(room.tunreadUser?.length, room.tunreadGroup?.length); + const tunread = room.tunread?.length || 0; + const tunreadUser = room.tunreadUser?.length || 0; + const tunreadGroup = room.tunreadGroup?.length || 0; + const unread = tunread > 99 ? '99+' : tunread; + const variant = getVariant(tunreadUser, tunreadGroup); return { unread > 0 && {unread} } ; diff --git a/client/hooks/lists/useScrollableRecordList.ts b/client/hooks/lists/useScrollableRecordList.ts index 3332cd45b86..3737898f580 100644 --- a/client/hooks/lists/useScrollableRecordList.ts +++ b/client/hooks/lists/useScrollableRecordList.ts @@ -25,7 +25,7 @@ export const useScrollableRecordList = ( useEffect(() => { loadMoreItems(0); - }, [recordList, loadMoreItems, initialItemCount]); + }, [loadMoreItems]); return { loadMoreItems, initialItemCount }; }; diff --git a/client/lib/lists/ThreadsList.ts b/client/lib/lists/ThreadsList.ts index 8b26ee0cc20..e412f8fec43 100644 --- a/client/lib/lists/ThreadsList.ts +++ b/client/lib/lists/ThreadsList.ts @@ -31,7 +31,7 @@ const isThreadFollowedByUser = (threadMessage: ThreadMessage, uid: IUser['_id']) threadMessage.replies?.includes(uid) ?? false; const isThreadUnread = (threadMessage: ThreadMessage, tunread: ISubscription['tunread']): boolean => - tunread.includes(threadMessage._id); + Boolean(tunread?.includes(threadMessage._id)); const isThreadTextMatching = (threadMessage: ThreadMessage, regex: RegExp): boolean => regex.test(threadMessage.msg); diff --git a/client/views/room/contextualBar/Threads/Row.js b/client/views/room/contextualBar/Threads/Row.tsx similarity index 64% rename from client/views/room/contextualBar/Threads/Row.js rename to client/views/room/contextualBar/Threads/Row.tsx index 0e9fcde3929..3b23cb85a38 100644 --- a/client/views/room/contextualBar/Threads/Row.js +++ b/client/views/room/contextualBar/Threads/Row.tsx @@ -1,6 +1,7 @@ -import React, { memo } from 'react'; +import React, { FC, memo, MouseEvent } from 'react'; import { call } from '../../../../../app/ui-utils/client'; +import { IMessage } from '../../../../../definition/IMessage'; import { useTranslation } from '../../../../contexts/TranslationContext'; import { useTimeAgo } from '../../../../hooks/useTimeAgo'; import { clickableItem } from '../../../../lib/clickableItem'; @@ -10,18 +11,28 @@ import { normalizeThreadMessage } from './normalizeThreadMessage'; const Thread = memo(mapProps(clickableItem(ThreadListMessage))); -const handleFollowButton = (e, threadId) => { +const handleFollowButton = (e: MouseEvent, threadId: string): void => { e.preventDefault(); e.stopPropagation(); - call( - ![true, 'true'].includes(e.currentTarget.dataset.following) - ? 'followMessage' - : 'unfollowMessage', - { mid: threadId }, - ); + const { following } = e.currentTarget.dataset; + + following && + call(![true, 'true'].includes(following) ? 'followMessage' : 'unfollowMessage', { + mid: threadId, + }); +}; + +type ThreadRowProps = { + thread: IMessage; + showRealNames: boolean; + unread: string[]; + unreadUser: string[]; + unreadGroup: string[]; + userId: string; + onClick: (threadId: string) => void; }; -const Row = memo(function Row({ +const Row: FC = memo(function Row({ thread, showRealNames, unread, @@ -54,7 +65,9 @@ const Row = memo(function Row({ msg={msg} t={t} formatDate={formatDate} - handleFollowButton={(e) => handleFollowButton(e, thread._id)} + handleFollowButton={(e: MouseEvent): unknown => + handleFollowButton(e, thread._id) + } onClick={onClick} /> ); diff --git a/client/views/room/contextualBar/Threads/ThreadList.js b/client/views/room/contextualBar/Threads/ThreadList.tsx similarity index 69% rename from client/views/room/contextualBar/Threads/ThreadList.js rename to client/views/room/contextualBar/Threads/ThreadList.tsx index 46ac4e391e5..506bf77b092 100644 --- a/client/views/room/contextualBar/Threads/ThreadList.js +++ b/client/views/room/contextualBar/Threads/ThreadList.tsx @@ -1,9 +1,12 @@ import { Box, Icon, TextInput, Select, Margins, Callout, Throbber } from '@rocket.chat/fuselage'; import { useResizeObserver, useMutableCallback, useAutoFocus } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo } from 'react'; +import React, { FC, useMemo } from 'react'; import { Virtuoso } from 'react-virtuoso'; import ThreadComponent from '../../../../../app/threads/client/components/ThreadComponent'; +import { IMessage } from '../../../../../definition/IMessage'; +import { IRoom } from '../../../../../definition/IRoom'; +import { IUser } from '../../../../../definition/IUser'; import ScrollableContentWrapper from '../../../../components/ScrollableContentWrapper'; import VerticalBar from '../../../../components/VerticalBar'; import { @@ -17,28 +20,57 @@ import { useTabContext } from '../../providers/ToolboxProvider'; import Row from './Row'; import { withData } from './withData'; -function ThreadList({ +export type ThreadListProps = { + total: number; + threads: IMessage[]; + room: IRoom; + unread?: string[]; + unreadUser?: string[]; + unreadGroup?: string[]; + userId?: IUser['_id'] | null; + + type: 'all' | 'following' | 'unread'; + setType: (type: string) => void; + + loading: boolean; + + error?: Error; + + text: string; + setText: (text: string) => void; + + onClose: () => void; + + loadMoreItems: (min: number, max: number) => void; +}; + +export const ThreadList: FC = function ThreadList({ total = 10, threads = [], room, unread = [], unreadUser = [], unreadGroup = [], + text, type, setType, loadMoreItems, loading, onClose, error, - userId, - text, + userId = '', setText, }) { - const showRealNames = useSetting('UI_Use_Real_Name'); + const showRealNames = Boolean(useSetting('UI_Use_Real_Name')); const t = useTranslation(); const inputRef = useAutoFocus(true); const [name] = useCurrentRoute(); + + if (!name) { + throw new Error('No route name'); + } + const channelRoute = useRoute(name); const onClick = useMutableCallback((e) => { const { id: context } = e.currentTarget.dataset; @@ -46,11 +78,11 @@ function ThreadList({ tab: 'thread', context, rid: room._id, - name: room.name, + ...(room.name && { name: room.name }), }); }); - const options = useMemo( + const options: [string, string][] = useMemo( () => [ ['all', t('All')], ['following', t('Following')], @@ -89,7 +121,7 @@ function ThreadList({ } ref={inputRef} /> @@ -131,34 +163,38 @@ function ThreadList({ }} totalCount={total} endReached={ - loading ? () => {} : (start) => loadMoreItems(start, Math.min(50, total - start)) + loading + ? (): void => undefined + : (start): unknown => loadMoreItems(start, Math.min(50, total - start)) } overscan={25} data={threads} - components={{ Scroller: ScrollableContentWrapper }} - itemContent={(index, data) => ( - - )} + components={{ Scroller: ScrollableContentWrapper as any }} + itemContent={(_index, data: IMessage): FC => + ( + + ) as unknown as FC + } /> )} - {mid && ( + {typeof mid === 'string' && ( )} ); -} +}; export default withData(ThreadList); diff --git a/client/views/room/contextualBar/Threads/withData.js b/client/views/room/contextualBar/Threads/withData.tsx similarity index 54% rename from client/views/room/contextualBar/Threads/withData.js rename to client/views/room/contextualBar/Threads/withData.tsx index c5cc0bfeaab..6a86e650464 100644 --- a/client/views/room/contextualBar/Threads/withData.js +++ b/client/views/room/contextualBar/Threads/withData.tsx @@ -1,40 +1,63 @@ import { useDebouncedValue, useLocalStorage } from '@rocket.chat/fuselage-hooks'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { FC, useCallback, useMemo, useState } from 'react'; import { useUserId, useUserSubscription } from '../../../../contexts/UserContext'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; +import { ThreadsListOptions } from '../../../../lib/lists/ThreadsList'; import { useUserRoom } from '../../hooks/useUserRoom'; import { useTabBarClose } from '../../providers/ToolboxProvider'; +import { ThreadListProps } from './ThreadList'; import { useThreadsList } from './useThreadsList'; -const subscriptionFields = { tunread: 1, tunreadUser: 1, tunreadGroup: 1 }; +const subscriptionFields = { tunread: true, tunreadUser: true, tunreadGroup: true }; const roomFields = { t: 1, name: 1 }; -export function withData(Component) { - const WrappedComponent = ({ rid, ...props }) => { +export function withData(Component: FC): FC<{ rid: string }> { + const WrappedComponent: FC<{ rid: string }> = ({ rid, ...props }) => { const userId = useUserId(); const onClose = useTabBarClose(); const room = useUserRoom(rid, roomFields); const subscription = useUserSubscription(rid, subscriptionFields); - const [type, setType] = useLocalStorage('thread-list-type', 'all'); + const [type, setType] = useLocalStorage<'all' | 'following' | 'unread'>( + 'thread-list-type', + 'all', + ); const [text, setText] = useState(''); const debouncedText = useDebouncedValue(text, 400); - - const options = useMemo( - () => ({ - rid, - text: debouncedText, - type, - tunread: subscription?.tunread, - uid: userId, - }), - [rid, debouncedText, type, subscription, userId], + const options: ThreadsListOptions = useDebouncedValue( + useMemo(() => { + if (type === 'all' || !subscription || !userId) { + return { + rid, + text: debouncedText, + type: 'all', + }; + } + switch (type) { + case 'following': + return { + rid, + text: debouncedText, + type, + uid: userId, + }; + case 'unread': + return { + rid, + text: debouncedText, + type, + tunread: subscription?.tunread, + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [rid, debouncedText, type, subscription?.tunread?.sort().join(), userId]), + 300, ); - const { threadsList, initialItemCount, loadMoreItems } = useThreadsList(options, userId); + const { threadsList, loadMoreItems } = useThreadsList(options, userId as string); const { phase, error, items: threads, itemCount: totalItemCount } = useRecordList(threadsList); const handleTextChange = useCallback((event) => { @@ -51,15 +74,14 @@ export function withData(Component) { error={error} threads={threads} total={totalItemCount} - initial={initialItemCount} loading={phase === AsyncStatePhase.LOADING} loadMoreItems={loadMoreItems} room={room} text={text} setText={handleTextChange} type={type} - setType={setType} - onClose={onClose} + setType={setType as any} + onClose={onClose as any} /> ); }; diff --git a/client/views/room/providers/ToolboxProvider.tsx b/client/views/room/providers/ToolboxProvider.tsx index 36975bc0822..6fa117332de 100644 --- a/client/views/room/providers/ToolboxProvider.tsx +++ b/client/views/room/providers/ToolboxProvider.tsx @@ -139,8 +139,7 @@ const ToolboxProvider = ({ children, room }: { children: ReactNode; room: IRoom ); }; -export const useTabContext = (): ToolboxActionConfig | undefined => - useContext(ToolboxContext).context; +export const useTabContext = (): unknown | undefined => useContext(ToolboxContext).context; export const useTab = (): ToolboxActionConfig | undefined => useContext(ToolboxContext).activeTabBar; export const useTabBarOpen = (): Function => useContext(ToolboxContext).open; diff --git a/definition/ISubscription.ts b/definition/ISubscription.ts index 917edfa2c88..55546ca59b3 100644 --- a/definition/ISubscription.ts +++ b/definition/ISubscription.ts @@ -26,9 +26,9 @@ export interface ISubscription extends IRocketChatRecord { userMentions: number; groupMentions: number; - tunread: Array; - tunreadGroup: Array; - tunreadUser: Array; + tunread?: Array; + tunreadGroup?: Array; + tunreadUser?: Array; prid?: RoomID;