Skip to content

Commit e47ae76

Browse files
AllanPazRibeirodougfabrisgabriellsh
authored
fix: cleaning the room filter between navigation on the admin section (#32152)
Co-authored-by: dougfabris <devfabris@gmail.com> Co-authored-by: gabriellsh <gabriel.henriques@rocket.chat>
1 parent 9c925a3 commit e47ae76

File tree

7 files changed

+157
-39
lines changed

7 files changed

+157
-39
lines changed

.changeset/brown-lobsters-join.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': patch
3+
---
4+
5+
Resolved an issue with the room type filter not being reset after navigating between admin sections.

apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { Box, Icon, TextInput } from '@rocket.chat/fuselage';
22
import type { OptionProp } from '@rocket.chat/ui-client';
33
import { MultiSelectCustom } from '@rocket.chat/ui-client';
44
import { useTranslation } from '@rocket.chat/ui-contexts';
5-
import React, { useCallback, useState } from 'react';
5+
import React, { useCallback, useMemo, useState } from 'react';
66
import type { Dispatch, ReactElement, SetStateAction } from 'react';
77

8-
const roomTypeFilterStructure = [
8+
const initialRoomTypeFilterStructure = [
99
{
1010
id: 'filter_by_room',
1111
text: 'Filter_by_room',
@@ -49,6 +49,13 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch<SetStateAction
4949

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

52+
const roomTypeFilterStructure = useMemo(() => {
53+
return initialRoomTypeFilterStructure.map((option) => ({
54+
...option,
55+
checked: roomTypeSelectedOptions.some((selectedOption) => selectedOption.id === option.id),
56+
}));
57+
}, [roomTypeSelectedOptions]);
58+
5259
const handleSearchTextChange = useCallback(
5360
(event) => {
5461
const text = event.currentTarget.value;
@@ -88,7 +95,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch<SetStateAction
8895
</Box>
8996
<Box minWidth='x224' m='x4'>
9097
<MultiSelectCustom
91-
dropdownOptions={roomTypeFilterStructure}
98+
dropdownOptions={roomTypeFilterStructure as OptionProp[]}
9299
defaultTitle={'All_rooms' as any}
93100
selectedOptionsTitle='Rooms'
94101
setSelectedOptions={handleRoomTypeChange}
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { faker } from '@faker-js/faker';
2+
3+
import { Users } from './fixtures/userStates';
4+
import { Admin, AdminSectionsHref } from './page-objects';
5+
import { createTargetChannel, createTargetPrivateChannel } from './utils';
6+
import { expect, test } from './utils/test';
7+
8+
test.use({ storageState: Users.admin.state });
9+
10+
test.describe.serial('admin-rooms', () => {
11+
let channel: string;
12+
let privateRoom: string;
13+
let admin: Admin;
14+
15+
test.beforeEach(async ({ page }) => {
16+
admin = new Admin(page);
17+
await page.goto('/admin/rooms');
18+
});
19+
20+
test.beforeAll(async ({ browser, api }) => {
21+
[channel, privateRoom] = await Promise.all([
22+
createTargetChannel(api),
23+
createTargetPrivateChannel(api),
24+
browser.newPage({ storageState: Users.admin.state }).then(async (page) => {
25+
await page.goto('/home');
26+
await page.waitForSelector('[data-qa-id="home-header"]');
27+
return page;
28+
}),
29+
]);
30+
});
31+
32+
test('should display the Rooms Table', async ({ page }) => {
33+
await expect(page.locator('[data-qa-type="PageHeader-title"]')).toContainText('Rooms');
34+
});
35+
36+
test('should filter room by name', async ({ page }) => {
37+
await admin.inputSearchRooms.fill(channel);
38+
39+
await expect(page.locator(`[qa-room-name="${channel}"]`)).toBeVisible();
40+
});
41+
42+
test('should filter rooms by type', async ({ page }) => {
43+
const dropdown = await admin.dropdownFilterRoomType();
44+
await dropdown.click();
45+
46+
const privateOption = page.locator('text=Private channels');
47+
48+
await privateOption.waitFor();
49+
await privateOption.click();
50+
51+
const selectedDropdown = await admin.dropdownFilterRoomType('Rooms (1)');
52+
await expect(selectedDropdown).toBeVisible();
53+
54+
await expect(page.locator('text=Private Channel').first()).toBeVisible();
55+
});
56+
57+
test('should filter rooms by type and name', async ({ page }) => {
58+
await admin.inputSearchRooms.fill(privateRoom);
59+
60+
const dropdown = await admin.dropdownFilterRoomType();
61+
await dropdown.click();
62+
63+
await page.locator('text=Private channels').click();
64+
65+
await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible();
66+
});
67+
68+
test('should be empty in case of the search does not find any room', async ({ page }) => {
69+
const nonExistingChannel = faker.string.alpha(10);
70+
71+
await admin.inputSearchRooms.fill(nonExistingChannel);
72+
73+
const dropdown = await admin.dropdownFilterRoomType();
74+
await dropdown.click();
75+
76+
await page.locator('text=Private channels').click();
77+
78+
await expect(page.locator('text=No results found')).toBeVisible();
79+
});
80+
81+
test('should filter rooms by type and name and clean the filter after changing section', async ({ page }) => {
82+
await admin.inputSearchRooms.fill(privateRoom);
83+
const dropdown = await admin.dropdownFilterRoomType();
84+
await dropdown.click();
85+
86+
await page.locator('text=Private channels').click();
87+
88+
const workspaceButton = await admin.adminSectionButton(AdminSectionsHref.Workspace);
89+
await workspaceButton.click();
90+
91+
const roomsButton = await admin.adminSectionButton(AdminSectionsHref.Rooms);
92+
await roomsButton.click();
93+
94+
const selectDropdown = await admin.dropdownFilterRoomType('All rooms');
95+
await expect(selectDropdown).toBeVisible();
96+
});
97+
});

apps/meteor/tests/e2e/page-objects/admin.ts

+30
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@ import type { Locator, Page } from '@playwright/test';
22

33
import { AdminFlextab } from './fragments/admin-flextab';
44

5+
export enum AdminSectionsHref {
6+
Workspace = '/admin/info',
7+
Subscription = '/admin/subscription',
8+
Engagement = '/admin/engagement/users',
9+
Moderation = '/admin/moderation',
10+
Federation = '/admin/federation',
11+
Rooms = '/admin/rooms',
12+
Users = '/admin/users',
13+
Invites = '/admin/invites',
14+
User_Status = '/admin/user-status',
15+
Permissions = '/admin/permissions',
16+
Device_Management = '/admin/device-management',
17+
Email_Inboxes = '/admin/email-inboxes',
18+
Mailer = '/admin/mailer',
19+
Third_party_login = '/admin/third-party-login',
20+
Integrations = '/admin/integrations',
21+
Import = '/admin/import',
22+
Reports = '/admin/reports',
23+
Sounds = '/admin/sounds',
24+
Emoji = '/admin/emoji',
25+
Settings = '/admin/settings',
26+
}
527
export class Admin {
628
public readonly page: Page;
729

@@ -268,4 +290,12 @@ export class Admin {
268290
get btnFullScreen(): Locator {
269291
return this.page.getByRole('button', { name: 'Full Screen' });
270292
}
293+
294+
async dropdownFilterRoomType(text = 'All rooms'): Promise<Locator> {
295+
return this.page.locator(`div[role="button"]:has-text("${text}")`);
296+
}
297+
298+
async adminSectionButton(href: AdminSectionsHref): Promise<Locator> {
299+
return this.page.locator(`a[href="${href}"]`);
300+
}
271301
}

packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustom.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@ export const MultiSelectCustom = ({
105105
<Box display='flex' flexGrow={1} position='relative'>
106106
<MultiSelectCustomAnchor
107107
ref={reference}
108-
onClick={toggleCollapsed as any}
109108
collapsed={collapsed}
109+
onClick={() => toggleCollapsed(!collapsed)}
110+
onKeyDown={(e) => (e.code === 'Enter' || e.code === 'Space') && toggleCollapsed(!collapsed)}
110111
defaultTitle={defaultTitle}
111112
selectedOptionsTitle={selectedOptionsTitle}
112113
selectedOptionsCount={count}

packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomAnchor.tsx

+12-34
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,44 @@
11
import { css } from '@rocket.chat/css-in-js';
2-
import { Box, Button, Icon, Palette } from '@rocket.chat/fuselage';
2+
import { Box, Icon } from '@rocket.chat/fuselage';
33
import type { TranslationKey } from '@rocket.chat/ui-contexts';
44
import { useTranslation } from '@rocket.chat/ui-contexts';
55
import type { ComponentProps } from 'react';
66
import { forwardRef } from 'react';
77

88
type MultiSelectCustomAnchorProps = {
9-
onClick?: (value: boolean) => void;
109
collapsed: boolean;
1110
defaultTitle: TranslationKey;
1211
selectedOptionsTitle: TranslationKey;
1312
selectedOptionsCount: number;
1413
maxCount: number;
15-
} & ComponentProps<typeof Button>;
14+
} & ComponentProps<typeof Box>;
1615

1716
const MultiSelectCustomAnchor = forwardRef<HTMLElement, MultiSelectCustomAnchorProps>(function MultiSelectCustomAnchor(
18-
{ onClick, collapsed, selectedOptionsCount, selectedOptionsTitle, defaultTitle, maxCount, ...props },
17+
{ collapsed, selectedOptionsCount, selectedOptionsTitle, defaultTitle, maxCount, ...props },
1918
ref,
2019
) {
2120
const t = useTranslation();
22-
23-
const inputStyle = collapsed
24-
? css`
25-
&,
26-
&:hover,
27-
&:active,
28-
&:focus {
29-
cursor: pointer;
30-
border-color: ${Palette.stroke['stroke-highlight'].toString()}!important;
31-
box-shadow: 0 0 0 2px ${Palette.shadow['shadow-highlight'].toString()};
32-
}
33-
`
34-
: css`
35-
& {
36-
cursor: pointer;
37-
}
38-
`;
39-
21+
const customStyle = css`
22+
&:hover {
23+
cursor: pointer;
24+
}
25+
`;
4026
const isDirty = selectedOptionsCount > 0 && selectedOptionsCount !== maxCount - 1;
4127

4228
return (
4329
<Box
4430
ref={ref}
45-
onClick={onClick}
31+
role='button'
32+
tabIndex={0}
4633
display='flex'
4734
justifyContent='space-between'
4835
alignItems='center'
49-
flexDirection='row'
50-
borderColor={Palette.stroke['stroke-light'].toString()}
51-
borderWidth='x1'
52-
borderRadius={4}
53-
bg={Palette.surface['surface-light'].toString()}
5436
h='x40'
55-
w='full'
56-
pb={10}
57-
pi={16}
58-
color={isDirty ? Palette.text['font-default'].toString() : Palette.text['font-annotation'].toString()}
59-
className={inputStyle}
37+
className={['rcx-input-box__wrapper', customStyle].filter(Boolean)}
6038
{...props}
6139
>
6240
{isDirty ? `${t(selectedOptionsTitle)} (${selectedOptionsCount})` : t(defaultTitle)}
63-
<Icon name='chevron-down' fontSize='x20' color='hint' />
41+
<Icon name={collapsed ? 'chevron-up' : 'chevron-down'} fontSize='x20' color='hint' />
6442
</Box>
6543
);
6644
});

packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomListWrapper.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const MultiSelectCustomListWrapper = forwardRef<Element, ComponentProps<typeof B
66
ref,
77
) {
88
return (
9-
<Box ref={ref} zIndex='2' w='full' position='absolute' mbs={40} pbs={4}>
9+
<Box ref={ref} zIndex={99} w='full' position='absolute' mbs={40} pbs={4}>
1010
{children}
1111
</Box>
1212
);

0 commit comments

Comments
 (0)