mirror of
https://github.com/strapi/strapi.git
synced 2025-11-16 10:07:55 +00:00
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:
parent
1832540151
commit
28dd017262
@ -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[]>;
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user