From 38a723aad00250f1ed1e2787a6a1918d60f638c6 Mon Sep 17 00:00:00 2001 From: Renat Kalimulin Date: Thu, 10 Jul 2025 20:43:13 +0300 Subject: [PATCH] Delete Button/Symbol for ACL's visible in view mode Resolves: #865 --- .../components/ACLPage/List/List.styled.ts | 4 +- frontend/src/components/ACLPage/List/List.tsx | 25 +++++-- .../ActionComponent/ActionComponent.styled.ts | 1 + .../ActionPermissionWrapper.tsx | 57 ++++++++++++++++ .../ActionPermissionWrapper.spec.tsx | 67 +++++++++++++++++++ .../common/ActionComponent/index.ts | 2 + 6 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/common/ActionComponent/ActionPermissionWrapper/ActionPermissionWrapper.tsx create mode 100644 frontend/src/components/common/ActionComponent/ActionPermissionWrapper/__tests__/ActionPermissionWrapper.spec.tsx diff --git a/frontend/src/components/ACLPage/List/List.styled.ts b/frontend/src/components/ACLPage/List/List.styled.ts index b1e8afead..01ca04407 100644 --- a/frontend/src/components/ACLPage/List/List.styled.ts +++ b/frontend/src/components/ACLPage/List/List.styled.ts @@ -7,9 +7,7 @@ export const EnumCell = styled.div` `; export const DeleteCell = styled.div` - svg { - cursor: pointer; - } + display: flex; `; export const Chip = styled.div<{ diff --git a/frontend/src/components/ACLPage/List/List.tsx b/frontend/src/components/ACLPage/List/List.tsx index c3ad4e3ad..f1a8298eb 100644 --- a/frontend/src/components/ACLPage/List/List.tsx +++ b/frontend/src/components/ACLPage/List/List.tsx @@ -24,6 +24,7 @@ import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel import Search from 'components/common/Search/Search'; import ResourcePageHeading from 'components/common/ResourcePageHeading/ResourcePageHeading'; import BreakableTextCell from 'components/common/NewTable/BreakableTextCell'; +import { ActionPermissionWrapper } from 'components/common/ActionComponent'; import * as S from './List.styled'; @@ -150,13 +151,23 @@ const ACList: React.FC = () => { // eslint-disable-next-line react/no-unstable-nested-components cell: ({ row }) => { return ( - handleDeleteClick(row.original)}> - - + handleDeleteClick(row.original)} + permission={{ + resource: ResourceType.ACL, + action: Action.EDIT, + }} + > + + + + ); }, size: 76, diff --git a/frontend/src/components/common/ActionComponent/ActionComponent.styled.ts b/frontend/src/components/common/ActionComponent/ActionComponent.styled.ts index ace26a048..85979db00 100644 --- a/frontend/src/components/common/ActionComponent/ActionComponent.styled.ts +++ b/frontend/src/components/common/ActionComponent/ActionComponent.styled.ts @@ -5,6 +5,7 @@ export const Wrapper = styled.div` flex-direction: row; align-items: center; justify-content: center; + width: min-content; `; export const MessageTooltip = styled.div` diff --git a/frontend/src/components/common/ActionComponent/ActionPermissionWrapper/ActionPermissionWrapper.tsx b/frontend/src/components/common/ActionComponent/ActionPermissionWrapper/ActionPermissionWrapper.tsx new file mode 100644 index 000000000..7f0d16ae3 --- /dev/null +++ b/frontend/src/components/common/ActionComponent/ActionPermissionWrapper/ActionPermissionWrapper.tsx @@ -0,0 +1,57 @@ +import React, { ReactElement } from 'react'; +import { + ActionComponentProps, + getDefaultActionMessage, +} from 'components/common/ActionComponent/ActionComponent'; +import { usePermission } from 'lib/hooks/usePermission'; +import { useActionTooltip } from 'lib/hooks/useActionTooltip'; +import * as S from 'components/common/ActionComponent/ActionComponent.styled'; + +interface Props extends ActionComponentProps { + children: ReactElement; + onAction: () => void; +} + +const ActionPermissionWrapper: React.FC = ({ + permission, + onAction, + children, + placement, + message = getDefaultActionMessage(), +}) => { + const canDoAction = usePermission( + permission.resource, + permission.action, + permission.value + ); + + const { x, y, refs, strategy, open } = useActionTooltip( + !canDoAction, + placement + ); + + return ( + canDoAction && onAction()} + style={{ cursor: canDoAction ? 'pointer' : 'not-allowed' }} + > + {children} + {open && ( + + {message} + + )} + + ); +}; + +export default ActionPermissionWrapper; diff --git a/frontend/src/components/common/ActionComponent/ActionPermissionWrapper/__tests__/ActionPermissionWrapper.spec.tsx b/frontend/src/components/common/ActionComponent/ActionPermissionWrapper/__tests__/ActionPermissionWrapper.spec.tsx new file mode 100644 index 000000000..44d8395ce --- /dev/null +++ b/frontend/src/components/common/ActionComponent/ActionPermissionWrapper/__tests__/ActionPermissionWrapper.spec.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import ActionPermissionWrapper from 'components/common/ActionComponent/ActionPermissionWrapper/ActionPermissionWrapper'; +import { render } from 'lib/testHelpers'; +import { Action, ResourceType } from 'generated-sources'; +import { usePermission } from 'lib/hooks/usePermission'; +import userEvent from '@testing-library/user-event'; + +jest.mock('lib/hooks/usePermission', () => ({ + usePermission: jest.fn(), +})); + +const onActionMock = jest.fn(); + +const testText = 'test'; +const TestComponent = () =>
{testText}
; + +describe('ActionPermissionWrapper', () => { + it('children renders', () => { + render( + + + + ); + expect(screen.getByText(testText)).toBeInTheDocument(); + }); + + it('action calls when allowed', async () => { + (usePermission as jest.Mock).mockImplementation(() => true); + render( + + + + ); + await userEvent.click(screen.getByText(testText)); + expect(onActionMock).toHaveBeenCalledTimes(1); + }); + + it('action not calls when not allowed', async () => { + (usePermission as jest.Mock).mockImplementation(() => false); + render( + + + + ); + await userEvent.click(screen.getByText(testText)); + expect(onActionMock).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/components/common/ActionComponent/index.ts b/frontend/src/components/common/ActionComponent/index.ts index d959680b9..707083b6e 100644 --- a/frontend/src/components/common/ActionComponent/index.ts +++ b/frontend/src/components/common/ActionComponent/index.ts @@ -3,6 +3,7 @@ import ActionButton from './ActionButton/ActionButton'; import ActionCanButton from './ActionButton/ActionCanButton/ActionCanButton'; import ActionNavLink from './ActionNavLink/ActionNavLink'; import ActionDropdownItem from './ActionDropDownItem/ActionDropdownItem'; +import ActionPermissionWrapper from './ActionPermissionWrapper/ActionPermissionWrapper'; export { ActionSelect, @@ -10,4 +11,5 @@ export { ActionCanButton, ActionButton, ActionDropdownItem, + ActionPermissionWrapper, };