[FIX] Threads List being requested more than expected (#22879)

pull/21779/head^2
Guilherme Gazzo 5 years ago committed by GitHub
parent 3e68e780bf
commit cbae247fee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/models/server/models/Messages.js
  2. 2
      app/threads/client/components/ThreadComponent.tsx
  3. 2
      app/threads/client/components/ThreadView.tsx
  4. 7
      app/threads/client/flextab/threadlist.tsx
  5. 2
      client/hooks/lists/useScrollableRecordList.ts
  6. 2
      client/lib/lists/ThreadsList.ts
  7. 33
      client/views/room/contextualBar/Threads/Row.tsx
  8. 82
      client/views/room/contextualBar/Threads/ThreadList.tsx
  9. 60
      client/views/room/contextualBar/Threads/withData.tsx
  10. 3
      client/views/room/providers/ToolboxProvider.tsx
  11. 6
      definition/ISubscription.ts

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

@ -59,7 +59,7 @@ const ThreadComponent: FC<{
mid: string;
jump: unknown;
room: IRoom;
onClickBack: () => void;
onClickBack: (e: unknown) => void;
}> = ({
mid,
jump,

@ -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<Element, ThreadViewProps>(({

@ -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 <Header.ToolBoxAction {...props} >
{ unread > 0 && <Header.Badge variant={variant}>{unread}</Header.Badge> }
</Header.ToolBoxAction>;

@ -25,7 +25,7 @@ export const useScrollableRecordList = <T extends IRocketChatRecord>(
useEffect(() => {
loadMoreItems(0);
}, [recordList, loadMoreItems, initialItemCount]);
}, [loadMoreItems]);
return { loadMoreItems, initialItemCount };
};

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

@ -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<HTMLElement>, 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<ThreadRowProps> = 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<HTMLElement>): unknown =>
handleFollowButton(e, thread._id)
}
onClick={onClick}
/>
);

@ -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<ThreadListProps> = 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({
<TextInput
placeholder={t('Search_Messages')}
value={text}
onChange={setText}
onChange={setText as any}
addon={<Icon name='magnifier' size='x20' />}
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) => (
<Row
thread={data}
showRealNames={showRealNames}
unread={unread}
unreadUser={unreadUser}
unreadGroup={unreadGroup}
userId={userId}
onClick={onClick}
/>
)}
components={{ Scroller: ScrollableContentWrapper as any }}
itemContent={(_index, data: IMessage): FC<IMessage> =>
(
<Row
thread={data}
showRealNames={showRealNames}
unread={unread}
unreadUser={unreadUser}
unreadGroup={unreadGroup}
userId={userId || ''}
onClick={onClick}
/>
) as unknown as FC<IMessage>
}
/>
)}
</Box>
</VerticalBar.Content>
{mid && (
{typeof mid === 'string' && (
<VerticalBar.InnerContent>
<ThreadComponent onClickBack={onClick} mid={mid} jump={jump} room={room} />
</VerticalBar.InnerContent>
)}
</>
);
}
};
export default withData(ThreadList);

@ -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<ThreadListProps>): 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}
/>
);
};

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

@ -26,9 +26,9 @@ export interface ISubscription extends IRocketChatRecord {
userMentions: number;
groupMentions: number;
tunread: Array<string>;
tunreadGroup: Array<string>;
tunreadUser: Array<string>;
tunread?: Array<string>;
tunreadGroup?: Array<string>;
tunreadUser?: Array<string>;
prid?: RoomID;

Loading…
Cancel
Save