Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cleaning the room filter between navigation on the admin section #32152

Merged
merged 30 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
16a73ee
feat: persisting the room filter between navigation on the admin section
AllanPazRibeiro Apr 8, 2024
45cffd9
feat: changing the logic from context/provider to local storage
AllanPazRibeiro Apr 12, 2024
5e44741
fix: fixing channel name on search
AllanPazRibeiro Apr 15, 2024
fecfb19
fix: removing not necessary test
AllanPazRibeiro Apr 15, 2024
4e7731c
fix: fix changest text
AllanPazRibeiro Apr 15, 2024
c4749b7
feat: adding unit test and fixing some test locators
AllanPazRibeiro Apr 16, 2024
72f0ca5
feat: improving page selectors to the tests
AllanPazRibeiro Apr 17, 2024
e068dfc
feat: set proper roles to the select component
AllanPazRibeiro Apr 18, 2024
24862f0
chore: sanitize `MultiSelectCustom`
dougfabris Apr 18, 2024
18be844
feat: improving the test selectors
AllanPazRibeiro Apr 18, 2024
e835cf4
feat: improving the test assertions
AllanPazRibeiro Apr 19, 2024
bd2e23c
fix: setting mocking file to ts and fix unit test warnings
AllanPazRibeiro Apr 19, 2024
fefad0b
fix: adding one sigle before each to setup the the test
AllanPazRibeiro Apr 19, 2024
903b5b4
Merge branch 'develop' into fix/CORE-217/fixing-room-search-filter-pe…
AllanPazRibeiro Apr 22, 2024
2e44a61
fix: cleaning the filters after changing sections
dougfabris Apr 18, 2024
497a7ec
chore: update changeset
AllanPazRibeiro Apr 24, 2024
f69d330
fix: fix lint issues
AllanPazRibeiro Apr 24, 2024
c75ea2f
fix: fix lint issues
AllanPazRibeiro Apr 24, 2024
43a65d3
chore: changing unit test verification logic
AllanPazRibeiro Apr 26, 2024
7b76abf
feat: adding more assertions for the unit tests
AllanPazRibeiro Apr 30, 2024
2a831dc
feat: change the application to use useMemo instead useEffect to avoi…
AllanPazRibeiro May 3, 2024
db152e0
fix: lint issues
AllanPazRibeiro May 3, 2024
f9df2e3
chore: adding the mocking client wrapper into the mock package
AllanPazRibeiro May 7, 2024
0f30b24
feat: improving test
AllanPazRibeiro May 7, 2024
35aacbd
fix: lint issues
AllanPazRibeiro May 8, 2024
b216f83
Merge branch 'develop' into fix/CORE-217/fixing-room-search-filter-pe…
AllanPazRibeiro May 14, 2024
f9c3ea3
Merge branch 'develop' into fix/CORE-217/fixing-room-search-filter-pe…
AllanPazRibeiro May 29, 2024
f94fbd1
fix: some fixes in the test selectors
AllanPazRibeiro Jun 6, 2024
05765b1
chore: removing unit test
AllanPazRibeiro Jun 7, 2024
bac931e
missing review
gabriellsh Jun 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/brown-lobsters-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/meteor': patch
---

Update: Fixed Room Filter Persistence in Admin Navigation

Resolved an issue causing room filter settings to reset on admin page navigation. Now, filter selections remain intact across different admin sections, streamlining task management and reducing redundant inputs.
18 changes: 18 additions & 0 deletions apps/meteor/client/contexts/SearchFilterContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { OptionProp } from '@rocket.chat/ui-client';
import type { Dispatch, SetStateAction } from 'react';
import { createContext } from 'react';

export type SearchFilters = {
searchText: string;
types: OptionProp[];
};

type SearchFilterContextValue = {
searchFilters: SearchFilters;
setSearchFilters: Dispatch<SetStateAction<SearchFilters>>;
};

export const SearchFilterContext = createContext<SearchFilterContextValue>({
searchFilters: { searchText: '', types: [] },
setSearchFilters: () => undefined,
});
5 changes: 4 additions & 1 deletion apps/meteor/client/providers/MeteorProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import LayoutProvider from './LayoutProvider';
import ModalProvider from './ModalProvider/ModalProvider';
import OmnichannelProvider from './OmnichannelProvider';
import RouterProvider from './RouterProvider';
import { SearchFilterProvider } from './SearchFiltersProvider/SearchFilterProvider';
import ServerProvider from './ServerProvider';
import SessionProvider from './SessionProvider';
import SettingsProvider from './SettingsProvider';
Expand Down Expand Up @@ -48,7 +49,9 @@ const MeteorProvider: FC = ({ children }) => (
<ActionManagerProvider>
<VideoConfProvider>
<CallProvider>
<OmnichannelProvider>{children}</OmnichannelProvider>
<OmnichannelProvider>
<SearchFilterProvider>{children}</SearchFilterProvider>
</OmnichannelProvider>
</CallProvider>
</VideoConfProvider>
</ActionManagerProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* eslint-disable no-await-in-loop */
import '@testing-library/jest-dom';
import type { OptionProp } from '@rocket.chat/ui-client';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React, { useContext } from 'react';

import type { SearchFilters } from '../../contexts/SearchFilterContext';
import { SearchFilterContext } from '../../contexts/SearchFilterContext';
import { SearchFilterProvider } from './SearchFilterProvider';

const DropdownTestComponent = () => {
const { searchFilters, setSearchFilters } = useContext(SearchFilterContext);

const handleCheckboxChange = (id: string) => {
setSearchFilters({
...searchFilters,
types: searchFilters.types.map((type) => (type.id === id ? { ...type, checked: !type.checked } : type)),
} as SearchFilters);
};

const handleSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchFilters({ ...searchFilters, searchText: e.target.value });
};

return (
<div>
<input type='text' value={searchFilters.searchText} onChange={handleSearchTextChange} placeholder='Search...' aria-label='Search' />
{searchFilters.types.map((option) =>
option.isGroupTitle ? (
<div key={option.id}>{option.text}</div>
) : (
<label key={option.id}>
<input type='checkbox' checked={option.checked} onChange={() => handleCheckboxChange(option.id)} />
{option.text}
</label>
),
)}
</div>
);
};

describe('SearchFilterProvider', () => {
test('handles types in context correctly', async () => {
const initialTypes = [
{ id: '1', text: 'Type 1', isGroupTitle: false, checked: false },
{ id: '2', text: 'Type 2', isGroupTitle: false, checked: false },
] as OptionProp[];

render(
<SearchFilterProvider initialState={{ searchText: '', types: initialTypes }}>
<DropdownTestComponent />
</SearchFilterProvider>,
);

for (const type of initialTypes) {
const checkbox = await screen.findByLabelText(type.text);
expect(checkbox).not.toBeChecked();
}

const firstCheckbox = await screen.findByLabelText(initialTypes[0].text);
userEvent.click(firstCheckbox);

const updatedFirstCheckbox = await screen.findByLabelText(initialTypes[0].text);
expect(updatedFirstCheckbox).toBeChecked();
});

test('handles searchText and types in context correctly', async () => {
const initialTypes: OptionProp[] = [
{ id: '1', text: 'Type 1', isGroupTitle: false, checked: false },
{ id: '2', text: 'Type 2', isGroupTitle: false, checked: false },
] as OptionProp[];

render(
<SearchFilterProvider initialState={{ searchText: '', types: initialTypes }}>
<DropdownTestComponent />
</SearchFilterProvider>,
);

const searchInput = screen.getByLabelText('Search');
expect(searchInput).toHaveValue('');

userEvent.type(searchInput, 'New Search Text');

expect(searchInput).toHaveValue('New Search Text');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { FC, ReactNode } from 'react';
import React, { useState } from 'react';

import type { SearchFilters } from '../../contexts/SearchFilterContext';
import { SearchFilterContext } from '../../contexts/SearchFilterContext';

type SearchFilterProviderProps = {
children: ReactNode;
initialState?: SearchFilters;
};

export const SearchFilterProvider: FC<SearchFilterProviderProps> = ({ children, initialState }) => {
const [searchFilters, setSearchFilters] = useState<SearchFilters>(initialState || { searchText: '', types: [] });

return <SearchFilterContext.Provider value={{ searchFilters, setSearchFilters }}>{children}</SearchFilterContext.Provider>;
};
35 changes: 18 additions & 17 deletions apps/meteor/client/views/admin/rooms/RoomsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
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, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { ReactElement, MutableRefObject } from 'react';
import React, { useRef, useState, useEffect, useMemo } from 'react';
import React, { useRef, useEffect, useMemo, useContext } from 'react';

import GenericNoResults from '../../../components/GenericNoResults';
import {
Expand All @@ -16,41 +15,43 @@ import {
} from '../../../components/GenericTable';
import { usePagination } from '../../../components/GenericTable/hooks/usePagination';
import { useSort } from '../../../components/GenericTable/hooks/useSort';
import type { SearchFilters } from '../../../contexts/SearchFilterContext';
import { SearchFilterContext } from '../../../contexts/SearchFilterContext';
import RoomRow from './RoomRow';
import RoomsTableFilters from './RoomsTableFilters';

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

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

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

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

const prevRoomFilterText = useRef<string>(roomFilters.searchText);
const prevRoomFilters = useRef<SearchFilters>(searchFilters);

const { sortBy, sortDirection, setSort } = useSort<'name' | 't' | 'usersCount' | 'msgs' | 'default' | 'featured'>('name');
const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination();
const searchText = useDebouncedValue(roomFilters.searchText, 500);
const searchText = useDebouncedValue(searchFilters.searchText, 500);

const query = useDebouncedValue(
useMemo(() => {
if (searchText !== prevRoomFilterText.current) {
const filtersChanged =
searchText !== prevRoomFilters.current.searchText ||
JSON.stringify(searchFilters.types) !== JSON.stringify(prevRoomFilters.current.types);

if (filtersChanged) {
setCurrent(0);
}

return {
filter: searchText || '',
sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`,
count: itemsPerPage,
offset: searchText === prevRoomFilterText.current ? current : 0,
types: roomFilters.types.length ? [...roomFilters.types.map((roomType) => roomType.id)] : DEFAULT_TYPES,
offset: filtersChanged ? 0 : current,
types: searchFilters.types.length ? [...searchFilters.types.map((roomType) => roomType.id)] : DEFAULT_TYPES,
};
}, [searchText, sortBy, sortDirection, itemsPerPage, current, roomFilters.types, setCurrent]),
}, [searchText, sortBy, sortDirection, itemsPerPage, current, searchFilters.types, setCurrent]),
500,
);

Expand All @@ -63,8 +64,8 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
}, [reload, refetch]);

useEffect(() => {
prevRoomFilterText.current = searchText;
}, [searchText]);
prevRoomFilters.current = searchFilters;
}, [searchFilters]);

const headers = (
<>
Expand Down Expand Up @@ -116,7 +117,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React

return (
<>
<RoomsTableFilters setFilters={setRoomFilters} />
<RoomsTableFilters />
{isLoading && (
<GenericTable>
<GenericTableHeader>{headers}</GenericTableHeader>
Expand Down
28 changes: 13 additions & 15 deletions apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Box, Icon, TextInput } from '@rocket.chat/fuselage';
import type { OptionProp } from '@rocket.chat/ui-client';
import { MultiSelectCustom } from '@rocket.chat/ui-client';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useCallback, useState } from 'react';
import React, { useCallback, useContext } from 'react';
import type { Dispatch, ReactElement, SetStateAction } from 'react';

import { SearchFilterContext } from '../../../contexts/SearchFilterContext';

const roomTypeFilterStructure = [
{
id: 'filter_by_room',
Expand Down Expand Up @@ -43,27 +45,23 @@ const roomTypeFilterStructure = [
},
] as OptionProp[];

const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch<SetStateAction<any>> }): ReactElement => {
const RoomsTableFilters = (): ReactElement => {
const { searchFilters, setSearchFilters } = useContext(SearchFilterContext);
const t = useTranslation();
const [text, setText] = useState('');

const [roomTypeSelectedOptions, setRoomTypeSelectedOptions] = useState<OptionProp[]>([]);

const handleSearchTextChange = useCallback(
(event) => {
const text = event.currentTarget.value;
setFilters({ searchText: text, types: roomTypeSelectedOptions });
setText(text);
(event: React.ChangeEvent<HTMLInputElement>) => {
const newText = event.currentTarget.value;
setSearchFilters((filters) => ({ ...filters, searchText: newText, types: searchFilters.types }));
},
[roomTypeSelectedOptions, setFilters],
[searchFilters.types, setSearchFilters],
);

const handleRoomTypeChange = useCallback(
(options: OptionProp[]) => {
setFilters({ searchText: text, types: options });
setRoomTypeSelectedOptions(options);
setSearchFilters((filters) => ({ ...filters, types: options, searchText: searchFilters.searchText }));
},
[text, setFilters],
[searchFilters.searchText, setSearchFilters],
) as Dispatch<SetStateAction<OptionProp[]>>;

return (
Expand All @@ -83,7 +81,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch<SetStateAction
placeholder={t('Search_rooms')}
addon={<Icon name='magnifier' size='x20' />}
onChange={handleSearchTextChange}
value={text}
value={searchFilters.searchText}
/>
</Box>
<Box minWidth='x224' m='x4'>
Expand All @@ -92,7 +90,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch<SetStateAction
defaultTitle={'All_rooms' as any}
selectedOptionsTitle='Rooms'
setSelectedOptions={handleRoomTypeChange}
selectedOptions={roomTypeSelectedOptions}
selectedOptions={searchFilters.types}
/>
</Box>
</Box>
Expand Down
Loading
Loading