From 4f3db554ce49f460c76becdd47acf0bf38b23f40 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Wed, 10 Dec 2025 16:49:28 -0300 Subject: [PATCH] regression(ABAC): Missing settings from abac -> Settings tab --- ...ettingToggle.tsx => AbacEnabledToggle.tsx} | 8 +- .../ABAC/ABACSettingTab/SettingField.tsx | 148 ++++++++++++++++++ .../ABACSettingTab/SettingToggle.spec.tsx | 24 +-- .../ABACSettingTab/SettingToggle.stories.tsx | 9 +- .../ABAC/ABACSettingTab/SettingsPage.tsx | 7 +- .../__snapshots__/SettingToggle.spec.tsx.snap | 19 +-- 6 files changed, 176 insertions(+), 39 deletions(-) rename apps/meteor/client/views/admin/ABAC/ABACSettingTab/{SettingToggle.tsx => AbacEnabledToggle.tsx} (91%) create mode 100644 apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingField.tsx diff --git a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.tsx b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/AbacEnabledToggle.tsx similarity index 91% rename from apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.tsx rename to apps/meteor/client/views/admin/ABAC/ABACSettingTab/AbacEnabledToggle.tsx index b7020e9023994..264f0fc07d47b 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/AbacEnabledToggle.tsx @@ -9,11 +9,11 @@ import { useEditableSetting } from '../../EditableSettingsContext'; import MemoizedSetting from '../../settings/Setting/MemoizedSetting'; import SettingSkeleton from '../../settings/Setting/SettingSkeleton'; -type SettingToggleProps = { +type ABACEnabledToggleProps = { hasABAC: 'loading' | boolean; }; -const SettingToggle = ({ hasABAC }: SettingToggleProps) => { +const ABACEnabledToggle = ({ hasABAC }: ABACEnabledToggleProps) => { const setting = useEditableSetting('ABAC_Enabled'); const setModal = useSetModal(); const dispatch = useSettingsDispatch(); @@ -86,10 +86,10 @@ const SettingToggle = ({ hasABAC }: SettingToggleProps) => { packageValue={setting.packageValue === true} hint={t(setting.i18nDescription || '')} disabled={!hasABAC || setting.blocked} - hasResetButton={setting.packageValue !== setting.value} + hasResetButton={hasABAC && setting.packageValue !== setting.value} onChangeValue={(value: SettingValue) => onChange(value === true)} onResetButtonClick={() => onReset()} /> ); }; -export default SettingToggle; +export default ABACEnabledToggle; diff --git a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingField.tsx b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingField.tsx new file mode 100644 index 0000000000000..8255b7c24445f --- /dev/null +++ b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingField.tsx @@ -0,0 +1,148 @@ +import type { ISettingColor, SettingEditor, SettingValue } from '@rocket.chat/core-typings'; +import { isSettingColor, isSetting } from '@rocket.chat/core-typings'; +import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks'; +import { useSettingStructure } from '@rocket.chat/ui-contexts'; +import DOMPurify from 'dompurify'; +import type { ReactElement } from 'react'; +import { useEffect, useMemo, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MarkdownText from '../../../../components/MarkdownText'; +import { useEditableSetting, useEditableSettingsDispatch, useEditableSettingVisibilityQuery } from '../../EditableSettingsContext'; +import MemoizedSetting from '../../settings/Setting/MemoizedSetting'; +import { useHasSettingModule } from '../../settings/hooks/useHasSettingModule'; + +type SettingFieldProps = { + className?: string; + settingId: string; + sectionChanged?: boolean; +}; + +function SettingField({ className = undefined, settingId, sectionChanged }: SettingFieldProps): ReactElement { + const setting = useEditableSetting(settingId); + const persistedSetting = useSettingStructure(settingId); + const hasSettingModule = useHasSettingModule(setting); + + if (!setting || !persistedSetting) { + throw new Error(`Setting ${settingId} not found`); + } + + // Checks if setting has at least required fields before doing anything + if (!isSetting(setting)) { + throw new Error(`Setting ${settingId} is not valid`); + } + + const dispatch = useEditableSettingsDispatch(); + + const update = useDebouncedCallback( + ({ value, editor }: { value?: SettingValue; editor?: SettingEditor }) => { + if (!persistedSetting) { + return; + } + + dispatch([ + { + _id: persistedSetting._id, + ...(value !== undefined && { value }), + ...(editor !== undefined && { editor }), + changed: + JSON.stringify(persistedSetting.value) !== JSON.stringify(value) || + (isSettingColor(persistedSetting) && JSON.stringify(persistedSetting.editor) !== JSON.stringify(editor)), + }, + ]); + }, + 230, + [persistedSetting, dispatch], + ); + + const { t, i18n } = useTranslation(); + + const [value, setValue] = useState(setting.value); + const [editor, setEditor] = useState(isSettingColor(setting) ? setting.editor : undefined); + + useEffect(() => { + setValue(setting.value); + }, [setting.value]); + + useEffect(() => { + setEditor(isSettingColor(setting) ? setting.editor : undefined); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [(setting as ISettingColor).editor]); + + const onChangeValue = useCallback( + (value: SettingValue) => { + setValue(value); + update({ value }); + }, + [update], + ); + + const onChangeEditor = useCallback( + (editor: SettingEditor) => { + setEditor(editor); + update({ editor }); + }, + [update], + ); + + const onResetButtonClick = useCallback(() => { + setValue(setting.value); + setEditor(isSettingColor(setting) ? setting.editor : undefined); + update({ + value: persistedSetting.packageValue, + ...(isSettingColor(persistedSetting) && { editor: persistedSetting.packageEditor }), + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [setting.value, (setting as ISettingColor).editor, update, persistedSetting]); + + const { _id, readonly, type, packageValue, i18nLabel, i18nDescription, alert } = setting; + + const disabled = !useEditableSettingVisibilityQuery(persistedSetting.enableQuery); + const invisible = !useEditableSettingVisibilityQuery(persistedSetting.displayQuery); + + const labelText = (i18n.exists(i18nLabel) && t(i18nLabel)) || (i18n.exists(_id) && t(_id)) || i18nLabel || _id; + + const hint = useMemo( + () => (i18nDescription && i18n.exists(i18nDescription) ? : undefined), + [i18n, i18nDescription, t], + ); + + const callout = useMemo( + () => + alert && , + [alert, i18n, t], + ); + + const shouldDisableEnterprise = setting.enterprise && !hasSettingModule; + + const hasResetButton = + !shouldDisableEnterprise && + !readonly && + type !== 'asset' && + ((isSettingColor(setting) && JSON.stringify(setting.packageEditor) !== JSON.stringify(editor)) || + JSON.stringify(value) !== JSON.stringify(packageValue)) && + !disabled; + + // @todo: type check props based on setting type + + return ( + + ); +} + +export default SettingField; diff --git a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.spec.tsx b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.spec.tsx index 849eb8da969ce..127d0192a21ef 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.spec.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.spec.tsx @@ -4,7 +4,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { axe } from 'jest-axe'; -import SettingToggle from './SettingToggle'; +import AbacEnabledToggle from './AbacEnabledToggle'; import EditableSettingsProvider from '../../settings/EditableSettingsProvider'; const settingStructure = { @@ -26,9 +26,9 @@ const baseAppRoot = mockAppRoot() Cancel: 'Cancel', }); -describe('SettingToggle', () => { +describe('AbacEnabledToggle', () => { it('should render the setting toggle when setting exists', () => { - const { baseElement } = render(, { + const { baseElement } = render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', true, settingStructure).build(), }); expect(baseElement).toMatchSnapshot(); @@ -36,7 +36,7 @@ describe('SettingToggle', () => { it('should show warning modal when disabling ABAC', async () => { const user = userEvent.setup(); - render(, { + render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', true, settingStructure).build(), }); @@ -57,7 +57,7 @@ describe('SettingToggle', () => { it('should not show warning modal when enabling ABAC', async () => { const user = userEvent.setup(); - render(, { + render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', false, settingStructure).build(), }); @@ -70,7 +70,7 @@ describe('SettingToggle', () => { it('should show warning modal when resetting setting', async () => { const user = userEvent.setup(); - render(, { + render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', true, settingStructure).build(), }); @@ -87,7 +87,7 @@ describe('SettingToggle', () => { }); it('should have no accessibility violations', async () => { - const { container } = render(, { + const { container } = render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', true, settingStructure).build(), }); const results = await axe(container); @@ -96,7 +96,7 @@ describe('SettingToggle', () => { it('should handle setting change correctly', async () => { const user = userEvent.setup(); - render(, { + render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', false, settingStructure).build(), }); @@ -108,7 +108,7 @@ describe('SettingToggle', () => { }); it('should be disabled when abac license is not installed', () => { - const { baseElement } = render(, { + const { baseElement } = render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', true, settingStructure).build(), }); @@ -118,14 +118,14 @@ describe('SettingToggle', () => { }); it('should show skeleton when loading', () => { - const { baseElement } = render(, { + const { baseElement } = render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', true, settingStructure).build(), }); expect(baseElement).toMatchSnapshot(); }); it('should show reset button when value differs from package value', () => { - render(, { + render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', true, settingStructure).build(), }); @@ -133,7 +133,7 @@ describe('SettingToggle', () => { }); it('should not show reset button when value matches package value', () => { - render(, { + render(, { wrapper: baseAppRoot.withSetting('ABAC_Enabled', false, settingStructure).build(), }); diff --git a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.stories.tsx b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.stories.tsx index 3fcc703b5085a..7fb4027aafddf 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.stories.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingToggle.stories.tsx @@ -1,12 +1,11 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import type { Meta, StoryObj } from '@storybook/react'; -import SettingToggle from './SettingToggle'; +import AbacEnabledToggle from './AbacEnabledToggle'; import EditableSettingsProvider from '../../settings/EditableSettingsProvider'; -const meta: Meta = { - title: 'Admin/ABAC/SettingToggle', - component: SettingToggle, +const meta: Meta = { + component: AbacEnabledToggle, parameters: { layout: 'padded', }, @@ -44,7 +43,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { args: { diff --git a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingsPage.tsx b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingsPage.tsx index 15e90c4def3a8..c6fb3108eb686 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingsPage.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/SettingsPage.tsx @@ -1,7 +1,8 @@ import { Box, Callout, Margins } from '@rocket.chat/fuselage'; import { Trans } from 'react-i18next'; -import SettingToggle from './SettingToggle'; +import AbacEnabledToggle from './AbacEnabledToggle'; +import SettingField from './SettingField'; import { useHasLicenseModule } from '../../../../hooks/useHasLicenseModule'; import { links } from '../../../../lib/links'; @@ -11,7 +12,9 @@ const SettingsPage = () => { - + + + diff --git a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/__snapshots__/SettingToggle.spec.tsx.snap b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/__snapshots__/SettingToggle.spec.tsx.snap index ef05ad349663b..7c48a94fb6800 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACSettingTab/__snapshots__/SettingToggle.spec.tsx.snap +++ b/apps/meteor/client/views/admin/ABAC/ABACSettingTab/__snapshots__/SettingToggle.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`SettingToggle should be disabled when abac license is not installed 1`] = ` +exports[`AbacEnabledToggle should be disabled when abac license is not installed 1`] = `
-