fix(content-releases):disable edit and delete buttons in the Details page when you don't have permissions (#19099)

* add the disable permissions and style to the edit and delete buttons

* fix review comments and add unit test

* fix type errors

* remove useless Icon

* add satisfies to define Permissions type
This commit is contained in:
Simone 2023-12-22 11:38:07 +01:00 committed by GitHub
parent 1832540151
commit 28dd017262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 37 deletions

View File

@ -1,57 +1,74 @@
import { Permission as StrapiPermission } from '@strapi/helper-plugin'; import { Permission as StrapiPermission } from '@strapi/helper-plugin';
type Permission = Pick<StrapiPermission, 'action' | 'subject'>; export const PERMISSIONS = {
interface PermissionMap {
main: Permission[];
create: Permission[];
update: Permission[];
delete: Permission[];
createAction: Permission[];
deleteAction: Permission[];
publish: Permission[];
}
export const PERMISSIONS: PermissionMap = {
main: [ main: [
{ {
action: 'plugin::content-releases.read', action: 'plugin::content-releases.read',
subject: null, subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
}, },
], ],
create: [ create: [
{ {
action: 'plugin::content-releases.create', action: 'plugin::content-releases.create',
subject: null, subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
}, },
], ],
update: [ update: [
{ {
action: 'plugin::content-releases.update', action: 'plugin::content-releases.update',
subject: null, subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
}, },
], ],
delete: [ delete: [
{ {
action: 'plugin::content-releases.delete', action: 'plugin::content-releases.delete',
subject: null, subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
}, },
], ],
createAction: [ createAction: [
{ {
action: 'plugin::content-releases.create-action', action: 'plugin::content-releases.create-action',
subject: null, subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
}, },
], ],
deleteAction: [ deleteAction: [
{ {
action: 'plugin::content-releases.delete-action', action: 'plugin::content-releases.delete-action',
subject: null, subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
}, },
], ],
publish: [ publish: [
{ {
action: 'plugin::content-releases.publish', action: 'plugin::content-releases.publish',
subject: null, subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
}, },
], ],
}; } satisfies Record<string, StrapiPermission[]>;

View File

@ -26,6 +26,7 @@ import {
useNotification, useNotification,
useQueryParams, useQueryParams,
ConfirmDialog, ConfirmDialog,
useRBAC,
} from '@strapi/helper-plugin'; } from '@strapi/helper-plugin';
import { ArrowLeft, EmptyDocuments, More, Pencil, Trash } from '@strapi/icons'; import { ArrowLeft, EmptyDocuments, More, Pencil, Trash } from '@strapi/icons';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -58,8 +59,16 @@ const ReleaseInfoWrapper = styled(Flex)`
border-top: 1px solid ${({ theme }) => theme.colors.neutral150}; border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
`; `;
const StyledFlex = styled(Flex)` const StyledFlex = styled(Flex)<{ disabled?: boolean }>`
align-self: stretch; align-self: stretch;
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
svg path {
fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
}
span {
color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
}
`; `;
const PencilIcon = styled(Pencil)` const PencilIcon = styled(Pencil)`
@ -80,10 +89,11 @@ const TrashIcon = styled(Trash)`
interface PopoverButtonProps { interface PopoverButtonProps {
onClick?: (event: React.MouseEvent<HTMLElement>) => void; onClick?: (event: React.MouseEvent<HTMLElement>) => void;
disabled?: boolean;
children: React.ReactNode; children: React.ReactNode;
} }
const PopoverButton = ({ onClick, children }: PopoverButtonProps) => { const PopoverButton = ({ onClick, disabled, children }: PopoverButtonProps) => {
return ( return (
<StyledFlex <StyledFlex
paddingTop={2} paddingTop={2}
@ -95,6 +105,7 @@ const PopoverButton = ({ onClick, children }: PopoverButtonProps) => {
as="button" as="button"
hasRadius hasRadius
onClick={onClick} onClick={onClick}
disabled={disabled}
> >
{children} {children}
</StyledFlex> </StyledFlex>
@ -120,6 +131,9 @@ export const ReleaseDetailsLayout = ({
const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation(); const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
const toggleNotification = useNotification(); const toggleNotification = useNotification();
const { formatAPIError } = useAPIErrorHandler(); const { formatAPIError } = useAPIErrorHandler();
const {
allowedActions: { canUpdate, canDelete },
} = useRBAC(PERMISSIONS);
const release = data?.data; const release = data?.data;
@ -224,8 +238,7 @@ export const ReleaseDetailsLayout = ({
minWidth="242px" minWidth="242px"
> >
<Flex alignItems="center" justifyContent="center" direction="column" padding={1}> <Flex alignItems="center" justifyContent="center" direction="column" padding={1}>
<CheckPermissions permissions={PERMISSIONS.update}> <PopoverButton disabled={!canUpdate} onClick={openReleaseModal}>
<PopoverButton onClick={openReleaseModal}>
<PencilIcon /> <PencilIcon />
<Typography ellipsis> <Typography ellipsis>
{formatMessage({ {formatMessage({
@ -234,9 +247,7 @@ export const ReleaseDetailsLayout = ({
})} })}
</Typography> </Typography>
</PopoverButton> </PopoverButton>
</CheckPermissions> <PopoverButton disabled={!canDelete} onClick={openWarningConfirmDialog}>
<CheckPermissions permissions={PERMISSIONS.delete}>
<PopoverButton onClick={openWarningConfirmDialog}>
<TrashIcon /> <TrashIcon />
<Typography ellipsis textColor="danger600"> <Typography ellipsis textColor="danger600">
{formatMessage({ {formatMessage({
@ -245,7 +256,6 @@ export const ReleaseDetailsLayout = ({
})} })}
</Typography> </Typography>
</PopoverButton> </PopoverButton>
</CheckPermissions>
</Flex> </Flex>
<ReleaseInfoWrapper <ReleaseInfoWrapper
direction="column" direction="column"

View File

@ -1,3 +1,4 @@
import { useRBAC } from '@strapi/helper-plugin';
import { render, server, screen } from '@tests/utils'; import { render, server, screen } from '@tests/utils';
import { rest } from 'msw'; import { rest } from 'msw';
@ -9,6 +10,10 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'), ...jest.requireActual('@strapi/helper-plugin'),
// eslint-disable-next-line // eslint-disable-next-line
CheckPermissions: ({ children }: { children: JSX.Element }) => <div>{children}</div>, CheckPermissions: ({ children }: { children: JSX.Element }) => <div>{children}</div>,
useRBAC: jest.fn(() => ({
isLoading: false,
allowedActions: { canUpdate: true, canDelete: true },
})),
})); }));
describe('Releases details page', () => { describe('Releases details page', () => {
@ -141,4 +146,42 @@ describe('Releases details page', () => {
const container = screen.getByText(/This entry was/); const container = screen.getByText(/This entry was/);
expect(container.querySelector('span')).toHaveTextContent('published'); expect(container.querySelector('span')).toHaveTextContent('published');
}); });
it('renders the details page with the delete and edit buttons disabled', async () => {
// @ts-expect-error mocking
useRBAC.mockImplementation(() => ({
isLoading: false,
allowedActions: { canUpdate: false, canDelete: false },
}));
server.use(
rest.get('/content-releases/:releaseId', (req, res, ctx) =>
res(ctx.json(mockReleaseDetailsPageData.noActionsHeaderData))
)
);
server.use(
rest.get('/content-releases/:releaseId/actions', (req, res, ctx) =>
res(ctx.json(mockReleaseDetailsPageData.noActionsBodyData))
)
);
const { user } = render(<ReleaseDetailsPage />, {
initialEntries: [{ pathname: `/content-releases/1` }],
});
await screen.findByText(mockReleaseDetailsPageData.noActionsHeaderData.data.name);
const moreButton = screen.getByRole('button', { name: 'Release actions' });
expect(moreButton).toBeInTheDocument();
await user.click(moreButton);
// shows the popover actions
const editButton = screen.getByRole('button', { name: 'Edit' });
expect(editButton).toBeDisabled();
const deleteButton = screen.getByRole('button', { name: 'Delete' });
expect(deleteButton).toBeDisabled();
});
}); });