diff --git a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.spec.tsx b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.spec.tsx index 5cad3a2b227b1..84927e6427a5a 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.spec.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.spec.tsx @@ -21,6 +21,7 @@ const appRoot = mockAppRoot() Save: 'Save', Required_field: '{{field}} is required', }) + .withEndpoint('GET', '/v1/abac/attributes/:key/is-in-use', async () => ({ inUse: false })) .build(); const FormProviderWrapper = ({ children, defaultValues }: { children: ReactNode; defaultValues?: Partial }) => { @@ -255,4 +256,33 @@ describe('AttributesForm', () => { const trashButtons = screen.getAllByRole('button', { name: 'ABAC_Remove_attribute' }); expect(trashButtons).toHaveLength(1); }); + + it('should show disclaimer when trying to delete a locked attribute value that is in use', async () => { + const defaultValues = { + name: 'Test Attribute', + lockedAttributes: [{ value: 'Value 1' }, { value: 'Value 2' }], + }; + render( + + + , + { + wrapper: mockAppRoot() + .withEndpoint('GET', '/v1/abac/attributes/:key/is-in-use', async () => ({ inUse: true })) + .withTranslations('en', 'core', { + ABAC_Cannot_delete_attribute_value_in_use: 'Cannot delete attribute value assigned to rooms. <1>View rooms', + }) + .build(), + }, + ); + + const trashButtons = screen.getAllByRole('button', { name: 'ABAC_Remove_attribute' }); + await userEvent.click(trashButtons[0]); + + await waitFor(() => { + expect(screen.getByText('Cannot delete attribute value assigned to rooms.')).toBeInTheDocument(); + }); + + expect(screen.getByText('Cannot delete attribute value assigned to rooms.')).toBeInTheDocument(); + }); }); diff --git a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx index 7e2cb90fba850..9e899452914fd 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx @@ -10,10 +10,14 @@ import { IconButton, TextInput, } from '@rocket.chat/fuselage'; +import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { ContextualbarScrollableContent } from '@rocket.chat/ui-client'; -import { useCallback, useId, useMemo, Fragment } from 'react'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useCallback, useId, useMemo, Fragment, useState } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; + +import { useViewRoomsAction } from '../hooks/useViewRoomsAction'; export type AttributesFormFormData = { name: string; @@ -33,6 +37,7 @@ const AttributesForm = ({ onSave, onCancel, description }: AttributesFormProps) register, formState: { errors, isDirty }, watch, + getValues, } = useFormContext(); const { t } = useTranslation(); @@ -40,7 +45,9 @@ const AttributesForm = ({ onSave, onCancel, description }: AttributesFormProps) const attributeValues = watch('attributeValues'); const lockedAttributes = watch('lockedAttributes'); - const { fields: lockedAttributesFields, remove: removeLockedAttribute } = useFieldArray({ + const isAttributeUsed = useEndpoint('GET', '/v1/abac/attributes/:key/is-in-use', { key: getValues('name') }); + + const { fields: lockedAttributesFields, remove: removeLockedAttributeField } = useFieldArray({ name: 'lockedAttributes', }); @@ -64,6 +71,20 @@ const AttributesForm = ({ onSave, onCancel, description }: AttributesFormProps) const nameField = useId(); const valuesField = useId(); + const [showDisclaimer, setShowDisclaimer] = useState([]); + const viewRoomsAction = useViewRoomsAction(); + + const removeLockedAttribute = useEffectEvent(async (index: number) => { + const isInUse = await isAttributeUsed(); + if (showDisclaimer.includes(index)) { + return; + } + if (isInUse?.inUse) { + return setShowDisclaimer((prev) => [...prev, index]); + } + return removeLockedAttributeField(index); + }); + const getAttributeValuesError = useCallback(() => { if (errors.attributeValues?.length && errors.attributeValues?.length > 0) { return errors.attributeValues[0]?.value?.message; @@ -118,6 +139,26 @@ const AttributesForm = ({ onSave, onCancel, description }: AttributesFormProps) )} {errors.lockedAttributes?.[index]?.value && {errors.lockedAttributes?.[index]?.value?.message}} + {showDisclaimer.includes(index) && ( + + { + e.preventDefault(); + viewRoomsAction(getValues('name')); + }} + > + {t('ABAC_View_rooms')} + + ), + }} + /> + + )} ))} {fields.map((field, index) => ( diff --git a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx index a52d6d4a743c4..d77f9b083cf0c 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx @@ -9,7 +9,7 @@ import { GenericTableRow, usePagination, } from '@rocket.chat/ui-client'; -import { useEndpoint, useRouter } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useRouter, useSearchParameter } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -22,7 +22,9 @@ import { useIsABACAvailable } from '../hooks/useIsABACAvailable'; const AttributesPage = () => { const { t } = useTranslation(); - const [text, setText] = useState(''); + const searchTerm = useSearchParameter('searchTerm'); + const [text, setText] = useState(searchTerm ?? ''); + const debouncedText = useDebouncedValue(text, 200); const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); const getAttributes = useEndpoint('GET', '/v1/abac/attributes'); diff --git a/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx b/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx index 7c6222531d8a8..99ca28c4c27a8 100644 --- a/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx +++ b/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx @@ -9,7 +9,7 @@ import { GenericTableRow, usePagination, } from '@rocket.chat/ui-client'; -import { useEndpoint, useRouter } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useRouter, useSearchParameter } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import { useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; @@ -21,15 +21,18 @@ import { useIsABACAvailable } from '../hooks/useIsABACAvailable'; const RoomsPage = () => { const { t } = useTranslation(); + const router = useRouter(); + + const searchTerm = useSearchParameter('searchTerm'); + const searchType = useSearchParameter('type') as 'roomName' | 'attribute' | 'value'; - const [text, setText] = useState(''); - const [filterType, setFilterType] = useState<'all' | 'roomName' | 'attribute' | 'value'>('all'); + const [text, setText] = useState(searchTerm ?? ''); + const [filterType, setFilterType] = useState<'all' | 'roomName' | 'attribute' | 'value'>(searchType ?? 'all'); const debouncedText = useDebouncedValue(text, 200); const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); const getRooms = useEndpoint('GET', '/v1/abac/rooms'); const isABACAvailable = useIsABACAvailable(); - const router = useRouter(); const handleNewAttribute = useEffectEvent(() => { router.navigate({ name: 'admin-ABAC', diff --git a/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx b/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx index f9623076ab056..dc1dcf3101883 100644 --- a/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx +++ b/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx @@ -7,6 +7,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Trans, useTranslation } from 'react-i18next'; import { useIsABACAvailable } from './useIsABACAvailable'; +import { useViewRoomsAction } from './useViewRoomsAction'; import { ABACQueryKeys } from '../../../../lib/queryKeys'; export const useAttributeOptions = (attribute: { _id: string; key: string }): GenericMenuItemProps[] => { @@ -18,6 +19,7 @@ export const useAttributeOptions = (attribute: { _id: string; key: string }): Ge const isAttributeUsed = useEndpoint('GET', '/v1/abac/attributes/:key/is-in-use', { key: attribute.key }); const dispatchToastMessage = useToastMessageDispatch(); const isABACAvailable = useIsABACAvailable(); + const viewRoomsAction = useViewRoomsAction(); const editAction = useEffectEvent(() => { return router.navigate( @@ -56,8 +58,10 @@ export const useAttributeOptions = (attribute: { _id: string; key: string }): Ge icon={null} title={t('ABAC_Cannot_delete_attribute')} confirmText={t('View_rooms')} - // TODO Route to rooms tab once implemented - onConfirm={() => setModal(null)} + onConfirm={() => { + viewRoomsAction(attribute.key); + setModal(null); + }} onCancel={() => setModal(null)} > { + const router = useRouter(); + return useEffectEvent((key: string) => { + return router.navigate( + { + name: 'admin-ABAC', + params: { + tab: 'rooms', + context: '', + id: '', + }, + search: { + searchTerm: key, + type: 'attribute', + }, + }, + { replace: true }, + ); + }); +}; diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 21e1547c838e2..fa4cd5d56281b 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -31,6 +31,7 @@ "ABAC_Element_Name": "Element Name", "ABAC_Cannot_delete_attribute": "Cannot delete attribute assigned to rooms", "ABAC_Cannot_delete_attribute_content": "Unassign {{attributeName}} from all rooms before attempting to delete it.", + "ABAC_Cannot_delete_attribute_value_in_use": "Cannot delete attribute value assigned to rooms. <1>View rooms", "ABAC_Delete_room_attribute": "Delete attribute", "ABAC_Delete_room_attribute_content": "Are you sure you want to delete {{attributeName}} ?
Existing rooms will not be affected as it is not currently assigned to any.", "ABAC_Attribute_created": "{{attributeName}} attribute created",