From ecb5805dccc1e0ea3bab0ceb654eb4ddca62bc3d Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 25 Mar 2026 16:30:24 -0300 Subject: [PATCH 1/4] fix: update app action button content to use translation utility --- apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts b/apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts index eb1f5a3823fff..a7277b1bc601d 100644 --- a/apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts +++ b/apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import { useAppActionButtons } from './useAppActionButtons'; import { useApplyButtonAuthFilter } from './useApplyButtonFilters'; import { UiKitTriggerTimeoutError } from '../../app/ui-message/client/UiKitTriggerTimeoutError'; +import { Utilities } from '../../ee/lib/misc/Utilities'; import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager'; export const useUserDropdownAppsActionButtons = () => { @@ -25,7 +26,7 @@ export const useUserDropdownAppsActionButtons = () => { return { id: `${action.appId}_${action.actionId}`, // icon: action.icon as GenericMenuItemProps['icon'], - content: action.labelI18n, + content: t(Utilities.getI18nKeyForApp(action.labelI18n, action.appId)), onClick: () => { void actionManager .emitInteraction(action.appId, { From e82e6dbc8273bae09b60152ea134290034d19e5d Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 25 Mar 2026 16:31:27 -0300 Subject: [PATCH 2/4] fix: adjust role permission check in `useApplyButtonAuthFilter` --- apps/meteor/client/hooks/useApplyButtonFilters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/hooks/useApplyButtonFilters.ts b/apps/meteor/client/hooks/useApplyButtonFilters.ts index 63542c5189c68..66f0422c12e04 100644 --- a/apps/meteor/client/hooks/useApplyButtonFilters.ts +++ b/apps/meteor/client/hooks/useApplyButtonFilters.ts @@ -66,7 +66,7 @@ export const useApplyButtonAuthFilter = (): ((button: IUIActionButton) => boolea const hasAllPermissionsResult = hasAllPermissions ? queryAllPermissions(hasAllPermissions)[1]() : true; const hasOnePermissionResult = hasOnePermission ? queryAtLeastOnePermission(hasOnePermission)[1]() : true; - const hasAllRolesResult = hasAllRoles ? !!uid && hasAllRoles.every((role) => queryRole(role, room?._id)) : true; + const hasAllRolesResult = hasAllRoles ? !!uid && hasAllRoles.every((role) => queryRole(role, room?._id)[1]()) : true; const hasOneRoleResult = hasOneRole ? !!uid && hasOneRole.some((role) => queryRole(role, room?._id)[1]()) : true; return hasAllPermissionsResult && hasOnePermissionResult && hasAllRolesResult && hasOneRoleResult; From 76df8359140a899c3d4424db07e5ba5fec66c106 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 25 Mar 2026 16:34:04 -0300 Subject: [PATCH 3/4] chore: changeset --- .changeset/neat-bananas-behave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/neat-bananas-behave.md diff --git a/.changeset/neat-bananas-behave.md b/.changeset/neat-bananas-behave.md new file mode 100644 index 0000000000000..9d0f5be6c795a --- /dev/null +++ b/.changeset/neat-bananas-behave.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes app actions ignoring role filters and i18n translation From a1d4ed850a1fc30a1cd02a456aa12a4b9679702b Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 26 Mar 2026 10:17:13 -0300 Subject: [PATCH 4/4] test: create `useApplyButtonFilter` tests --- .../hooks/useApplyButtonFilters.spec.ts | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 apps/meteor/client/hooks/useApplyButtonFilters.spec.ts diff --git a/apps/meteor/client/hooks/useApplyButtonFilters.spec.ts b/apps/meteor/client/hooks/useApplyButtonFilters.spec.ts new file mode 100644 index 0000000000000..cb386383cf5b4 --- /dev/null +++ b/apps/meteor/client/hooks/useApplyButtonFilters.spec.ts @@ -0,0 +1,212 @@ +import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; +import { UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui'; +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { renderHook } from '@testing-library/react'; + +import { useApplyButtonAuthFilter } from './useApplyButtonFilters'; + +describe('useApplyButtonAuthFilter', () => { + describe('Role-based filtering', () => { + it('should filter button when user does not have required role (hasAllRoles)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasAllRoles: ['admin'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot() + .withJohnDoe({ roles: ['user'] }) + .build(), + }); + + expect(result.current(button)).toBe(false); + }); + + it('should show button when user has required role (hasAllRoles)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasAllRoles: ['admin'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot().withJohnDoe().withRole('admin').build(), + }); + + expect(result.current(button)).toBe(true); + }); + + it('should filter button when user does not have any required role (hasOneRole)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasOneRole: ['admin', 'moderator'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot() + .withJohnDoe({ roles: ['user'] }) + .build(), + }); + + expect(result.current(button)).toBe(false); + }); + + it('should show button when user has at least one of the required roles (hasOneRole)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasOneRole: ['admin', 'moderator'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot().withJohnDoe().withRole('moderator').build(), + }); + + expect(result.current(button)).toBe(true); + }); + + it('should show button when no role filter is specified', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot() + .withJohnDoe({ roles: ['user'] }) + .build(), + }); + + expect(result.current(button)).toBe(true); + }); + + it('should filter button when user is not logged in and role is required (hasAllRoles)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasAllRoles: ['admin'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot().withAnonymous().build(), + }); + + expect(result.current(button)).toBe(false); + }); + }); + + describe('Permission-based filtering', () => { + it('should filter button when user does not have required permission (hasAllPermissions)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasAllPermissions: ['manage-apps'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot().withJohnDoe().build(), + }); + + expect(result.current(button)).toBe(false); + }); + + it('should show button when user has required permission (hasAllPermissions)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasAllPermissions: ['manage-apps'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot().withJohnDoe().withPermission('manage-apps').build(), + }); + + expect(result.current(button)).toBe(true); + }); + + it('should show button when user has at least one of the required permissions (hasOnePermission)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasOnePermission: ['manage-apps', 'manage-users'], + }, + }; + + const { result } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot().withJohnDoe().withPermission('manage-apps').build(), + }); + + expect(result.current(button)).toBe(true); + }); + }); + + describe('Combined filters', () => { + it('should apply both role and permission filters (AND logic)', () => { + const button: IUIActionButton = { + appId: 'test-app', + actionId: 'test-action', + labelI18n: 'test_label', + context: UIActionButtonContext.USER_DROPDOWN_ACTION, + when: { + hasAllRoles: ['admin'], + hasAllPermissions: ['manage-apps'], + }, + }; + + const { result: result1 } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot() + .withJohnDoe({ roles: ['user'] }) + .build(), + }); + expect(result1.current(button)).toBe(false); + + const { result: result2 } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot() + .withJohnDoe({ roles: ['user'] }) + .withPermission('manage-apps') + .build(), + }); + expect(result2.current(button)).toBe(false); + + const { result: result3 } = renderHook(() => useApplyButtonAuthFilter(), { + wrapper: mockAppRoot().withJohnDoe().withRole('admin').withPermission('manage-apps').build(), + }); + expect(result3.current(button)).toBe(true); + }); + }); +});