From 7e6735c4b6a806ccedc52f7e2ec00acdcea63c09 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Thu, 2 Oct 2025 14:06:55 -0300 Subject: [PATCH 1/6] feat: create header tag for ABAC rooms --- .../components/ABAC/ABACHeaderTag.spec.tsx | 77 +++++++++++++++++++ .../client/components/ABAC/ABACHeaderTag.tsx | 28 +++++++ .../__snapshots__/ABACHeaderTag.spec.tsx.snap | 26 +++++++ .../client/views/room/Header/RoomHeader.tsx | 2 + .../client/views/room/HeaderV2/RoomHeader.tsx | 2 + 5 files changed, 135 insertions(+) create mode 100644 apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx create mode 100644 apps/meteor/client/components/ABAC/ABACHeaderTag.tsx create mode 100644 apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap diff --git a/apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx b/apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx new file mode 100644 index 0000000000000..0cc8c4322f6f1 --- /dev/null +++ b/apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx @@ -0,0 +1,77 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import ABACHeaderTag from './ABACHeaderTag'; + +const appRoot = mockAppRoot() + .withTranslations('en', 'core', { + ABAC_header_tag_title: 'ABAC Protected', + ABAC_header_tag: 'ABAC', + }) + .build(); + +const createMockRoom = (overrides: any = {}) => ({ + _id: 'room-id', + t: 'c' as const, + name: 'test-room', + msgs: 0, + u: { _id: 'user-id', username: 'testuser' }, + usersCount: 1, + _updatedAt: new Date(), + ...overrides, +}); + +describe('ABACHeaderTag', () => { + it('should render the ABAC tag when room has ABAC attributes', () => { + const room = createMockRoom({ + abacAttributes: { someAttribute: 'value' }, + }); + + const { baseElement } = render(, { wrapper: appRoot }); + expect(baseElement).toMatchSnapshot(); + }); + + it('should not render when room has no ABAC attributes', () => { + const room = createMockRoom(); + + render(, { wrapper: appRoot }); + expect(screen.queryByText('ABAC')).not.toBeInTheDocument(); + }); + + it('should not render when room has undefined ABAC attributes', () => { + const room = createMockRoom({ + abacAttributes: undefined, + }); + + render(, { wrapper: appRoot }); + expect(screen.queryByText('ABAC')).not.toBeInTheDocument(); + }); + + it('should not render when room has null ABAC attributes', () => { + const room = createMockRoom({ + abacAttributes: null, + }); + + render(, { wrapper: appRoot }); + expect(screen.queryByText('ABAC')).not.toBeInTheDocument(); + }); + + it('should have no accessibility violations when rendered', async () => { + const room = createMockRoom({ + abacAttributes: { someAttribute: 'value' }, + }); + + const { container } = render(, { wrapper: appRoot }); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should have no accessibility violations when not rendered', async () => { + const room = createMockRoom(); + + const { container } = render(, { wrapper: appRoot }); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); diff --git a/apps/meteor/client/components/ABAC/ABACHeaderTag.tsx b/apps/meteor/client/components/ABAC/ABACHeaderTag.tsx new file mode 100644 index 0000000000000..b24538bf98736 --- /dev/null +++ b/apps/meteor/client/components/ABAC/ABACHeaderTag.tsx @@ -0,0 +1,28 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { Box, Palette } from '@rocket.chat/fuselage'; +import { useTranslation } from 'react-i18next'; + +import { HeaderTag } from '../Header'; + +type ABACHeaderTagProps = { + room: IRoom; +}; + +const ABACHeaderTag = ({ room }: ABACHeaderTagProps) => { + const { t } = useTranslation(); + + // @ts-expect-error to be implemented + if (!room.abacAttributes) { + return null; + } + + return ( + + + {t('ABAC_header_tag')} + + + ); +}; + +export default ABACHeaderTag; diff --git a/apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap b/apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap new file mode 100644 index 0000000000000..edfd0c3a8b890 --- /dev/null +++ b/apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ABACHeaderTag should render the ABAC tag when room has ABAC attributes 1`] = ` + +
+
+ + +
+ ABAC +
+
+
+
+
+ +`; diff --git a/apps/meteor/client/views/room/Header/RoomHeader.tsx b/apps/meteor/client/views/room/Header/RoomHeader.tsx index 931aeaac92a41..2ea3dcd30b09a 100644 --- a/apps/meteor/client/views/room/Header/RoomHeader.tsx +++ b/apps/meteor/client/views/room/Header/RoomHeader.tsx @@ -13,6 +13,7 @@ import RoomToolbox from './RoomToolbox'; import Encrypted from './icons/Encrypted'; import Favorite from './icons/Favorite'; import Translate from './icons/Translate'; +import ABACHeaderTag from '../../../components/ABAC/ABACHeaderTag'; import { Header, HeaderAvatar, HeaderContent, HeaderContentRow, HeaderSubtitle, HeaderToolbar } from '../../../components/Header'; import MarkdownText from '../../../components/MarkdownText'; @@ -50,6 +51,7 @@ const RoomHeader = ({ room, topic = '', slots = {}, roomToolbox }: RoomHeaderPro {room.prid && } {room.teamId && !room.teamMain && } + {isRoomFederated(room) && } diff --git a/apps/meteor/client/views/room/HeaderV2/RoomHeader.tsx b/apps/meteor/client/views/room/HeaderV2/RoomHeader.tsx index 3fd1677675216..3bd6b828eb6c4 100644 --- a/apps/meteor/client/views/room/HeaderV2/RoomHeader.tsx +++ b/apps/meteor/client/views/room/HeaderV2/RoomHeader.tsx @@ -12,6 +12,7 @@ import RoomTopic from './RoomTopic'; import Encrypted from './icons/Encrypted'; import Favorite from './icons/Favorite'; import Translate from './icons/Translate'; +import ABACHeaderTag from '../../../components/ABAC/ABACHeaderTag'; import { Header, HeaderContent, HeaderContentRow, HeaderToolbar } from '../../../components/Header'; export type RoomHeaderProps = { @@ -43,6 +44,7 @@ const RoomHeader = ({ room, slots = {}, roomToolbox }: RoomHeaderProps) => { + {isRoomFederated(room) && } From a63a4faad777e2571f2b0dab76c6cc17050eab0a Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Thu, 2 Oct 2025 14:07:14 -0300 Subject: [PATCH 2/6] chore: Adjust minimum header tag size --- .../ui-client/src/components/Header/HeaderTag/HeaderTag.tsx | 2 +- .../ui-client/src/components/HeaderV2/HeaderTag/HeaderTag.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-client/src/components/Header/HeaderTag/HeaderTag.tsx b/packages/ui-client/src/components/Header/HeaderTag/HeaderTag.tsx index 44661229707c4..afbad5d734340 100644 --- a/packages/ui-client/src/components/Header/HeaderTag/HeaderTag.tsx +++ b/packages/ui-client/src/components/Header/HeaderTag/HeaderTag.tsx @@ -2,7 +2,7 @@ import { Box, Tag } from '@rocket.chat/fuselage'; import type { ComponentProps, FC } from 'react'; const HeaderTag: FC> = ({ children, ...props }) => ( - + {children} diff --git a/packages/ui-client/src/components/HeaderV2/HeaderTag/HeaderTag.tsx b/packages/ui-client/src/components/HeaderV2/HeaderTag/HeaderTag.tsx index bef9407706e45..222a01dcf4daa 100644 --- a/packages/ui-client/src/components/HeaderV2/HeaderTag/HeaderTag.tsx +++ b/packages/ui-client/src/components/HeaderV2/HeaderTag/HeaderTag.tsx @@ -4,7 +4,7 @@ import type { ComponentProps } from 'react'; type HeaderTagProps = ComponentProps; const HeaderTag = ({ children, ...props }: HeaderTagProps) => ( - + {children} From b7c1f7de328d5d6c4f62617f726653fa11d67b40 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 2 Oct 2025 14:18:17 -0300 Subject: [PATCH 3/6] Create slow-trees-eat.md --- .changeset/slow-trees-eat.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/slow-trees-eat.md diff --git a/.changeset/slow-trees-eat.md b/.changeset/slow-trees-eat.md new file mode 100644 index 0000000000000..9ab1c8f826e9f --- /dev/null +++ b/.changeset/slow-trees-eat.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/ui-client": minor +--- + +Adds a new room header tag and tooltip to be shown when the room is under Attribute Based Access Control From f1da675fb3f661b248ce05f7483ac4672053be45 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Fri, 3 Oct 2025 10:52:32 -0300 Subject: [PATCH 4/6] chore: move to chore --- .changeset/slow-trees-eat.md | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .changeset/slow-trees-eat.md diff --git a/.changeset/slow-trees-eat.md b/.changeset/slow-trees-eat.md deleted file mode 100644 index 9ab1c8f826e9f..0000000000000 --- a/.changeset/slow-trees-eat.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/ui-client": minor ---- - -Adds a new room header tag and tooltip to be shown when the room is under Attribute Based Access Control From 4a3d5f560953c01022d562b8e6011c5fa6aa83dc Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Mon, 6 Oct 2025 10:24:53 -0300 Subject: [PATCH 5/6] fix: missing translations --- .../components/ABAC/ABACHeaderTag.spec.tsx | 25 ++++--------------- packages/i18n/src/locales/en.i18n.json | 2 ++ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx b/apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx index 0cc8c4322f6f1..212d8ce02341e 100644 --- a/apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx +++ b/apps/meteor/client/components/ABAC/ABACHeaderTag.spec.tsx @@ -1,3 +1,4 @@ +import type { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import { mockAppRoot } from '@rocket.chat/mock-providers'; import { render, screen } from '@testing-library/react'; import { axe } from 'jest-axe'; @@ -6,12 +7,12 @@ import ABACHeaderTag from './ABACHeaderTag'; const appRoot = mockAppRoot() .withTranslations('en', 'core', { - ABAC_header_tag_title: 'ABAC Protected', + ABAC_header_tag_title: 'Only compliant users have access to attribute-based access controlled rooms.', ABAC_header_tag: 'ABAC', }) .build(); -const createMockRoom = (overrides: any = {}) => ({ +const createMockRoom = (overrides: Partial = {}) => ({ _id: 'room-id', t: 'c' as const, name: 'test-room', @@ -25,6 +26,7 @@ const createMockRoom = (overrides: any = {}) => ({ describe('ABACHeaderTag', () => { it('should render the ABAC tag when room has ABAC attributes', () => { const room = createMockRoom({ + // @ts-expect-error to be implemented abacAttributes: { someAttribute: 'value' }, }); @@ -39,26 +41,9 @@ describe('ABACHeaderTag', () => { expect(screen.queryByText('ABAC')).not.toBeInTheDocument(); }); - it('should not render when room has undefined ABAC attributes', () => { - const room = createMockRoom({ - abacAttributes: undefined, - }); - - render(, { wrapper: appRoot }); - expect(screen.queryByText('ABAC')).not.toBeInTheDocument(); - }); - - it('should not render when room has null ABAC attributes', () => { - const room = createMockRoom({ - abacAttributes: null, - }); - - render(, { wrapper: appRoot }); - expect(screen.queryByText('ABAC')).not.toBeInTheDocument(); - }); - it('should have no accessibility violations when rendered', async () => { const room = createMockRoom({ + // @ts-expect-error to be implemented abacAttributes: { someAttribute: 'value' }, }); diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 8afb816dc0407..c9dcfa8d574b8 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -77,6 +77,8 @@ "A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "A new owner will be assigned automatically to those {{count}} rooms:
{{rooms}}.", "A_secure_and_highly_private_self-managed_solution_for_conference_calls": "A secure and highly private self-managed solution for conference calls.", "A_workspace_admin_needs_to_install_and_configure_a_conference_call_app": "A workspace admin needs to install and configure a conference call app.", + "ABAC_header_tag_title": "Only compliant users have access to attribute-based access controlled rooms.", + "ABAC_header_tag": "ABAC", "Accept": "Accept", "Accept_Call": "Accept Call", "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Accept incoming omnichannel requests even if there are no online agents", From 1de331c6cba98667650f53df03556b71872395a3 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Mon, 6 Oct 2025 10:46:59 -0300 Subject: [PATCH 6/6] test: add more tests & snapshots updates --- .../__snapshots__/ABACHeaderTag.spec.tsx.snap | 2 +- .../HeaderV2/HeaderTag/HeaderTag.spec.tsx | 8 +++++++ .../__snapshots__/HeaderTag.spec.tsx.snap | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 packages/ui-client/src/components/HeaderV2/HeaderTag/HeaderTag.spec.tsx create mode 100644 packages/ui-client/src/components/HeaderV2/HeaderTag/__snapshots__/HeaderTag.spec.tsx.snap diff --git a/apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap b/apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap index edfd0c3a8b890..20ce983ee9efd 100644 --- a/apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap +++ b/apps/meteor/client/components/ABAC/__snapshots__/ABACHeaderTag.spec.tsx.snap @@ -8,7 +8,7 @@ exports[`ABACHeaderTag should render the ABAC tag when room has ABAC attributes > { + const { baseElement } = render(Test Tag); + expect(baseElement).toMatchSnapshot(); +}); diff --git a/packages/ui-client/src/components/HeaderV2/HeaderTag/__snapshots__/HeaderTag.spec.tsx.snap b/packages/ui-client/src/components/HeaderV2/HeaderTag/__snapshots__/HeaderTag.spec.tsx.snap new file mode 100644 index 0000000000000..652dc9c51111a --- /dev/null +++ b/packages/ui-client/src/components/HeaderV2/HeaderTag/__snapshots__/HeaderTag.spec.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`should match snapshot 1`] = ` + +
+
+ + + Test Tag + + +
+
+ +`;