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';
type Permission = Pick<StrapiPermission, 'action' | 'subject'>;
interface PermissionMap {
main: Permission[];
create: Permission[];
update: Permission[];
delete: Permission[];
createAction: Permission[];
deleteAction: Permission[];
publish: Permission[];
}
export const PERMISSIONS: PermissionMap = {
export const PERMISSIONS = {
main: [
{
action: 'plugin::content-releases.read',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
},
],
create: [
{
action: 'plugin::content-releases.create',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
},
],
update: [
{
action: 'plugin::content-releases.update',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
},
],
delete: [
{
action: 'plugin::content-releases.delete',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
},
],
createAction: [
{
action: 'plugin::content-releases.create-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
},
],
deleteAction: [
{
action: 'plugin::content-releases.delete-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
},
],
publish: [
{
action: 'plugin::content-releases.publish',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: [],
},
],
};
} satisfies Record<string, StrapiPermission[]>;

View File

@ -26,6 +26,7 @@ import {
useNotification,
useQueryParams,
ConfirmDialog,
useRBAC,
} from '@strapi/helper-plugin';
import { ArrowLeft, EmptyDocuments, More, Pencil, Trash } from '@strapi/icons';
import { useIntl } from 'react-intl';
@ -58,8 +59,16 @@ const ReleaseInfoWrapper = styled(Flex)`
border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
`;
const StyledFlex = styled(Flex)`
const StyledFlex = styled(Flex)<{ disabled?: boolean }>`
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)`
@ -80,10 +89,11 @@ const TrashIcon = styled(Trash)`
interface PopoverButtonProps {
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
disabled?: boolean;
children: React.ReactNode;
}
const PopoverButton = ({ onClick, children }: PopoverButtonProps) => {
const PopoverButton = ({ onClick, disabled, children }: PopoverButtonProps) => {
return (
<StyledFlex
paddingTop={2}
@ -95,6 +105,7 @@ const PopoverButton = ({ onClick, children }: PopoverButtonProps) => {
as="button"
hasRadius
onClick={onClick}
disabled={disabled}
>
{children}
</StyledFlex>
@ -120,6 +131,9 @@ export const ReleaseDetailsLayout = ({
const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
const toggleNotification = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const {
allowedActions: { canUpdate, canDelete },
} = useRBAC(PERMISSIONS);
const release = data?.data;
@ -224,28 +238,24 @@ export const ReleaseDetailsLayout = ({
minWidth="242px"
>
<Flex alignItems="center" justifyContent="center" direction="column" padding={1}>
<CheckPermissions permissions={PERMISSIONS.update}>
<PopoverButton onClick={openReleaseModal}>
<PencilIcon />
<Typography ellipsis>
{formatMessage({
id: 'content-releases.header.actions.edit',
defaultMessage: 'Edit',
})}
</Typography>
</PopoverButton>
</CheckPermissions>
<CheckPermissions permissions={PERMISSIONS.delete}>
<PopoverButton onClick={openWarningConfirmDialog}>
<TrashIcon />
<Typography ellipsis textColor="danger600">
{formatMessage({
id: 'content-releases.header.actions.delete',
defaultMessage: 'Delete',
})}
</Typography>
</PopoverButton>
</CheckPermissions>
<PopoverButton disabled={!canUpdate} onClick={openReleaseModal}>
<PencilIcon />
<Typography ellipsis>
{formatMessage({
id: 'content-releases.header.actions.edit',
defaultMessage: 'Edit',
})}
</Typography>
</PopoverButton>
<PopoverButton disabled={!canDelete} onClick={openWarningConfirmDialog}>
<TrashIcon />
<Typography ellipsis textColor="danger600">
{formatMessage({
id: 'content-releases.header.actions.delete',
defaultMessage: 'Delete',
})}
</Typography>
</PopoverButton>
</Flex>
<ReleaseInfoWrapper
direction="column"

View File

@ -1,3 +1,4 @@
import { useRBAC } from '@strapi/helper-plugin';
import { render, server, screen } from '@tests/utils';
import { rest } from 'msw';
@ -9,6 +10,10 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
// eslint-disable-next-line
CheckPermissions: ({ children }: { children: JSX.Element }) => <div>{children}</div>,
useRBAC: jest.fn(() => ({
isLoading: false,
allowedActions: { canUpdate: true, canDelete: true },
})),
}));
describe('Releases details page', () => {
@ -141,4 +146,42 @@ describe('Releases details page', () => {
const container = screen.getByText(/This entry was/);
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();
});
});