Skip to content

Commit

Permalink
regression: Rooms page filters faulty behavior (#30315)
Browse files Browse the repository at this point in the history
  • Loading branch information
rique223 authored Sep 12, 2023
1 parent f37e404 commit afd2b0d
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 254 deletions.
90 changes: 90 additions & 0 deletions apps/meteor/client/views/admin/rooms/RoomRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { isDiscussion } from '@rocket.chat/core-typings';
import type { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings';
import { Box, Icon } from '@rocket.chat/fuselage';
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import { useRouter, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useCallback } from 'react';

import { GenericTableCell, GenericTableRow } from '../../../components/GenericTable';
import RoomAvatar from '../../../components/avatar/RoomAvatar';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';

const roomTypeI18nMap = {
l: 'Omnichannel',
c: 'Channel',
d: 'Direct_Message',
p: 'Private_Channel',
} as const;

const getRoomDisplayName = (room: Pick<IRoom, RoomAdminFieldsType>): string | undefined =>
room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room);

const RoomRow = ({ room }: { room: Pick<IRoom, RoomAdminFieldsType> }) => {
const t = useTranslation();
const mediaQuery = useMediaQuery('(min-width: 1024px)');
const router = useRouter();

const { _id, t: type, usersCount, msgs, default: isDefault, featured, ...args } = room;
const icon = roomCoordinator.getRoomDirectives(room.t).getIcon?.(room);
const roomName = getRoomDisplayName(room);

const getRoomType = (
room: Pick<IRoom, RoomAdminFieldsType>,
): (typeof roomTypeI18nMap)[keyof typeof roomTypeI18nMap] | 'Teams_Public_Team' | 'Teams_Private_Team' | 'Discussion' => {
if (room.teamMain) {
return room.t === 'c' ? 'Teams_Public_Team' : 'Teams_Private_Team';
}
if (isDiscussion(room)) {
return 'Discussion';
}
return roomTypeI18nMap[(room as IRoom).t as keyof typeof roomTypeI18nMap];
};

const onClick = useCallback(
(rid) => (): void =>
router.navigate({
name: 'admin-rooms',
params: {
context: 'edit',
id: rid,
},
}),
[router],
);

return (
<GenericTableRow action key={_id} onKeyDown={onClick(_id)} onClick={onClick(_id)} tabIndex={0} role='link' qa-room-id={_id}>
<GenericTableCell withTruncatedText>
<Box display='flex' alignContent='center'>
<RoomAvatar size={mediaQuery ? 'x28' : 'x40'} room={{ type, name: roomName, _id, ...args }} />
<Box
display='flex'
flexGrow={1}
flexShrink={1}
flexBasis='0%'
flexDirection='row'
alignSelf='center'
alignItems='center'
withTruncatedText
>
{icon && <Icon mi={4} name={icon} fontScale='p2m' color='hint' />}
<Box fontScale='p2m' withTruncatedText color='default' qa-room-name={roomName}>
{roomName}
</Box>
</Box>
</Box>
</GenericTableCell>
<GenericTableCell>
<Box color='hint' fontScale='p2m' withTruncatedText>
{t(getRoomType(room))}
</Box>
</GenericTableCell>
<GenericTableCell withTruncatedText>{usersCount}</GenericTableCell>
{mediaQuery && <GenericTableCell withTruncatedText>{msgs}</GenericTableCell>}
{mediaQuery && <GenericTableCell withTruncatedText>{isDefault ? t('True') : t('False')}</GenericTableCell>}
{mediaQuery && <GenericTableCell withTruncatedText>{featured ? t('True') : t('False')}</GenericTableCell>}
</GenericTableRow>
);
};

export default RoomRow;
200 changes: 40 additions & 160 deletions apps/meteor/client/views/admin/rooms/RoomsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,33 @@
import { type IRoom, isDiscussion, isPublicRoom } from '@rocket.chat/core-typings';
import { Box, Icon, Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage';
import { Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage';
import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import type { OptionProp } from '@rocket.chat/ui-client';
import { useEndpoint, useRouter, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { CSSProperties, ReactElement, MutableRefObject } from 'react';
import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react';
import type { ReactElement, MutableRefObject } from 'react';
import React, { useRef, useState, useEffect, useMemo } from 'react';

import GenericNoResults from '../../../components/GenericNoResults';
import {
GenericTable,
GenericTableBody,
GenericTableCell,
GenericTableHeader,
GenericTableHeaderCell,
GenericTableLoadingTable,
GenericTableRow,
} from '../../../components/GenericTable';
import { usePagination } from '../../../components/GenericTable/hooks/usePagination';
import { useSort } from '../../../components/GenericTable/hooks/useSort';
import RoomAvatar from '../../../components/avatar/RoomAvatar';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
import RoomRow from './RoomRow';
import RoomsTableFilters from './RoomsTableFilters';
import { useFilteredTypeRooms } from './useFilteredTypeRooms';
import { useFilteredVisibilityRooms } from './useFilteredVisibilityRooms';

const style: CSSProperties = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' };

type RoomFilters = {
searchText: string;
types: OptionProp[];
visibility: OptionProp[];
};

const DEFAULT_TYPES = ['d', 'p', 'c', 'l', 'discussions', 'teams'];

const roomTypeI18nMap = {
l: 'Omnichannel',
c: 'Channel',
d: 'Direct_Message',
p: 'Private_Channel',
} as const;

const getRoomType = (
room: IRoom,
): (typeof roomTypeI18nMap)[keyof typeof roomTypeI18nMap] | 'Teams_Public_Team' | 'Teams_Private_Team' | 'Discussion' => {
if (room.teamMain) {
return room.t === 'c' ? 'Teams_Public_Team' : 'Teams_Private_Team';
}
if (isDiscussion(room)) {
return 'Discussion';
}
return roomTypeI18nMap[(room as IRoom).t as keyof typeof roomTypeI18nMap];
};

const getRoomDisplayName = (room: IRoom): string | undefined =>
room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room);

const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): ReactElement => {
const mediaQuery = useMediaQuery('(min-width: 1024px)');

const t = useTranslation();
const mediaQuery = useMediaQuery('(min-width: 1024px)');

const [roomFilters, setRoomFilters] = useState<RoomFilters>({ searchText: '', types: [], visibility: [] });

Expand All @@ -80,29 +47,15 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`,
count: itemsPerPage,
offset: searchText === prevRoomFilterText.current ? current : 0,
types: DEFAULT_TYPES,
types: [...roomFilters.types.map((roomType) => roomType.id)],
};
}, [searchText, sortBy, sortDirection, itemsPerPage, prevRoomFilterText, current, setCurrent]),
}, [searchText, sortBy, sortDirection, itemsPerPage, current, roomFilters.types, setCurrent]),
500,
);

const getAdminRooms = useEndpoint('GET', '/v1/rooms.adminRooms');

const dispatchToastMessage = useToastMessageDispatch();

const { data, refetch, isSuccess, isLoading, isError } = useQuery(
['rooms', query, 'admin'],
async () => {
const adminRooms = await getAdminRooms(query);

return { ...adminRooms, rooms: adminRooms.rooms as IRoom[] };
},
{
onError: (error) => {
dispatchToastMessage({ type: 'error', message: error });
},
},
);
const { data, refetch, isSuccess, isLoading, isError } = useQuery(['rooms', query, 'admin'], async () => getAdminRooms(query));

useEffect(() => {
reload.current = refetch;
Expand All @@ -112,48 +65,29 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
prevRoomFilterText.current = searchText;
}, [searchText]);

const router = useRouter();

const onClick = useCallback(
(rid) => (): void =>
router.navigate({
name: 'admin-rooms',
params: {
context: 'edit',
id: rid,
},
}),
[router],
);

const headers = useMemo(
() =>
[
<GenericTableHeaderCell key='name' direction={sortDirection} active={sortBy === 'name'} onClick={setSort} sort='name' w='x200'>
{t('Name')}
</GenericTableHeaderCell>,
<GenericTableHeaderCell key='type' direction={sortDirection} active={sortBy === 't'} onClick={setSort} sort='t' w='x100'>
{t('Type')}
</GenericTableHeaderCell>,
<GenericTableHeaderCell key='visibility' direction={sortDirection} active={sortBy === 't'} onClick={setSort} sort='t' w='x100'>
{t('Visibility')}
</GenericTableHeaderCell>,
<GenericTableHeaderCell
key='users'
direction={sortDirection}
active={sortBy === 'usersCount'}
onClick={setSort}
sort='usersCount'
w='x80'
>
{t('Users')}
</GenericTableHeaderCell>,
mediaQuery && (
const headers = (
<>
<GenericTableHeaderCell key='name' direction={sortDirection} active={sortBy === 'name'} onClick={setSort} sort='name' w='x200'>
{t('Name')}
</GenericTableHeaderCell>
<GenericTableHeaderCell key='type' direction={sortDirection} active={sortBy === 't'} onClick={setSort} sort='t' w='x100'>
{t('Type')}
</GenericTableHeaderCell>
<GenericTableHeaderCell
key='users'
direction={sortDirection}
active={sortBy === 'usersCount'}
onClick={setSort}
sort='usersCount'
w='x80'
>
{t('Users')}
</GenericTableHeaderCell>
{mediaQuery && (
<>
<GenericTableHeaderCell key='messages' direction={sortDirection} active={sortBy === 'msgs'} onClick={setSort} sort='msgs' w='x80'>
{t('Msgs')}
</GenericTableHeaderCell>
),
mediaQuery && (
<GenericTableHeaderCell
key='default'
direction={sortDirection}
Expand All @@ -164,8 +98,6 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
>
{t('Default')}
</GenericTableHeaderCell>
),
mediaQuery && (
<GenericTableHeaderCell
key='featured'
direction={sortDirection}
Expand All @@ -176,83 +108,32 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
>
{t('Featured')}
</GenericTableHeaderCell>
),
].filter(Boolean),
[sortDirection, sortBy, setSort, t, mediaQuery],
);

const renderRow = useCallback(
(room: IRoom) => {
const { _id, t: type, usersCount, msgs, default: isDefault, featured, ...args } = room;
const visibility = isPublicRoom(room) ? 'Public' : 'Private';
const icon = roomCoordinator.getRoomDirectives(room.t).getIcon?.(room);
const roomName = getRoomDisplayName(room);

return (
<GenericTableRow action key={_id} onKeyDown={onClick(_id)} onClick={onClick(_id)} tabIndex={0} role='link' qa-room-id={_id}>
<GenericTableCell style={style}>
<Box display='flex' alignContent='center'>
<RoomAvatar size={mediaQuery ? 'x28' : 'x40'} room={{ type, name: roomName, _id, ...args }} />
<Box display='flex' style={style} mi={8}>
<Box display='flex' flexDirection='row' alignSelf='center' alignItems='center' style={style}>
{icon && <Icon mi={2} name={icon} fontScale='p2m' color='hint' />}
<Box fontScale='p2m' style={style} color='default' qa-room-name={roomName}>
{roomName}
</Box>
</Box>
</Box>
</Box>
</GenericTableCell>
<GenericTableCell>
<Box color='hint' fontScale='p2m' style={style}>
{t(getRoomType(room))}
</Box>
<Box mi={4} />
</GenericTableCell>
<GenericTableCell>
<Box color='hint' fontScale='p2m' style={style}>
{t(visibility)}
</Box>
<Box mi='x4' />
</GenericTableCell>
<GenericTableCell style={style}>{usersCount}</GenericTableCell>
{mediaQuery && <GenericTableCell style={style}>{msgs}</GenericTableCell>}
{mediaQuery && <GenericTableCell style={style}>{isDefault ? t('True') : t('False')}</GenericTableCell>}
{mediaQuery && <GenericTableCell style={style}>{featured ? t('True') : t('False')}</GenericTableCell>}
</GenericTableRow>
);
},
[mediaQuery, onClick, t],
</>
)}
</>
);

function intersectArraysWithoutDuplicates(array1: IRoom[], array2: IRoom[]) {
const set2 = new Set(array2);

return [...new Set(array1)].filter((item) => set2.has(item));
}

const roomsTypeList = useFilteredTypeRooms(roomFilters.types, isLoading, data?.rooms);
const roomsVisibilityList = useFilteredVisibilityRooms(roomFilters.visibility, isLoading, data?.rooms);

const roomsList = intersectArraysWithoutDuplicates(roomsTypeList, roomsVisibilityList);

return (
<>
<RoomsTableFilters setFilters={setRoomFilters} />

{isLoading && (
<GenericTable>
<GenericTableHeader>{headers}</GenericTableHeader>
<GenericTableBody>
<GenericTableLoadingTable headerCells={6} />
<GenericTableLoadingTable headerCells={mediaQuery ? 6 : 3} />
</GenericTableBody>
</GenericTable>
)}
{isSuccess && data && data?.rooms.length > 0 && (
{isSuccess && data.rooms.length === 0 && <GenericNoResults />}
{isSuccess && data.rooms.length > 0 && (
<>
<GenericTable>
<GenericTableHeader>{headers}</GenericTableHeader>
<GenericTableBody>{isSuccess && roomsList?.map((room) => renderRow(room))}</GenericTableBody>
<GenericTableBody>
{data.rooms?.map((room) => (
<RoomRow key={room._id} room={room} />
))}
</GenericTableBody>
</GenericTable>
<Pagination
divider
Expand All @@ -265,7 +146,6 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
/>
</>
)}
{isSuccess && data && data.rooms.length === 0 && <GenericNoResults />}
{isError && (
<States>
<StatesIcon name='warning' variation='danger' />
Expand Down
Loading

0 comments on commit afd2b0d

Please sign in to comment.