mirror of
https://github.com/strapi/strapi.git
synced 2025-10-29 17:04:13 +00:00
feat(content-releases): add delete release action button (#19047)
This commit is contained in:
parent
849c8126fd
commit
93969787db
@ -32,8 +32,13 @@ import { GetContentTypeEntryReleases } from '../../../shared/contracts/releases'
|
||||
import { PERMISSIONS } from '../constants';
|
||||
import { useCreateReleaseActionMutation, useGetReleasesForEntryQuery } from '../services/release';
|
||||
|
||||
import { ReleaseActionMenu } from './ReleaseActionMenu';
|
||||
import { ReleaseActionOptions } from './ReleaseActionOptions';
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* AddActionToReleaseModal
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const RELEASE_ACTION_FORM_SCHEMA = yup.object().shape({
|
||||
type: yup.string().oneOf(['publish', 'unpublish']).required(),
|
||||
releaseId: yup.string().required(),
|
||||
@ -199,8 +204,12 @@ const AddActionToReleaseModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* CMReleasesContainer
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const CMReleasesContainer = () => {
|
||||
const [showModal, setShowModal] = React.useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
isCreatingEntry,
|
||||
@ -237,7 +246,7 @@ export const CMReleasesContainer = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toggleAddActionToReleaseModal = () => setShowModal((prev) => !prev);
|
||||
const toggleModal = () => setIsModalOpen((prev) => !prev);
|
||||
|
||||
const getReleaseColorVariant = (
|
||||
actionType: 'publish' | 'unpublish',
|
||||
@ -306,11 +315,12 @@ export const CMReleasesContainer = () => {
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box padding={4}>
|
||||
<Flex padding={4} direction="column" gap={3} width="100%" alignItems="flex-start">
|
||||
<Typography fontSize={2} fontWeight="bold" variant="omega" textColor="neutral700">
|
||||
{release.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<ReleaseActionMenu releaseId={release.id} actionId={release.action.id} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
@ -322,7 +332,7 @@ export const CMReleasesContainer = () => {
|
||||
color="neutral700"
|
||||
variant="tertiary"
|
||||
startIcon={<Plus />}
|
||||
onClick={toggleAddActionToReleaseModal}
|
||||
onClick={toggleModal}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'content-releases.content-manager-edit-view.add-to-release',
|
||||
@ -331,9 +341,9 @@ export const CMReleasesContainer = () => {
|
||||
</Button>
|
||||
</CheckPermissions>
|
||||
</Flex>
|
||||
{showModal && (
|
||||
{isModalOpen && (
|
||||
<AddActionToReleaseModal
|
||||
handleClose={toggleAddActionToReleaseModal}
|
||||
handleClose={toggleModal}
|
||||
contentTypeUid={contentType.uid}
|
||||
entryId={params.id}
|
||||
/>
|
||||
|
||||
@ -0,0 +1,125 @@
|
||||
import { Flex, IconButton, Typography } from '@strapi/design-system';
|
||||
import { Menu } from '@strapi/design-system/v2';
|
||||
import { CheckPermissions, useAPIErrorHandler, useNotification } from '@strapi/helper-plugin';
|
||||
import { Cross, More } from '@strapi/icons';
|
||||
import { isAxiosError } from 'axios';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DeleteReleaseAction } from '../../../shared/contracts/release-actions';
|
||||
import { PERMISSIONS } from '../constants';
|
||||
import { useDeleteReleaseActionMutation } from '../services/release';
|
||||
|
||||
const StyledMenuItem = styled(Menu.Item)`
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: ${({ theme }) => theme.colors.danger600};
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
path {
|
||||
fill: ${({ theme }) => theme.colors.danger600};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCross = styled(Cross)`
|
||||
padding: ${({ theme }) => theme.spaces[1]};
|
||||
`;
|
||||
|
||||
interface ReleaseActionMenuProps {
|
||||
releaseId: DeleteReleaseAction.Request['params']['releaseId'];
|
||||
actionId: DeleteReleaseAction.Request['params']['actionId'];
|
||||
}
|
||||
|
||||
export const ReleaseActionMenu = ({ releaseId, actionId }: ReleaseActionMenuProps) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const toggleNotification = useNotification();
|
||||
const { formatAPIError } = useAPIErrorHandler();
|
||||
const [deleteReleaseAction] = useDeleteReleaseActionMutation();
|
||||
|
||||
const handleDeleteAction = async () => {
|
||||
const response = await deleteReleaseAction({
|
||||
params: { releaseId, actionId },
|
||||
});
|
||||
|
||||
if ('data' in response) {
|
||||
// Handle success
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: formatMessage({
|
||||
id: 'content-releases.content-manager-edit-view.remove-from-release.notification.success',
|
||||
defaultMessage: 'Entry removed from release',
|
||||
}),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ('error' in response) {
|
||||
if (isAxiosError(response.error)) {
|
||||
// Handle axios error
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: formatAPIError(response.error),
|
||||
});
|
||||
} else {
|
||||
// Handle generic error
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
// A user can access the dropdown if they have permissions to delete a release-action OR update a release
|
||||
<CheckPermissions permissions={[...PERMISSIONS.deleteAction, ...PERMISSIONS.update]}>
|
||||
<Menu.Root>
|
||||
{/*
|
||||
TODO Fix in the DS
|
||||
- as={IconButton} has TS error: Property 'icon' does not exist on type 'IntrinsicAttributes & TriggerProps & RefAttributes<HTMLButtonElement>'
|
||||
- The Icon doesn't actually show unless you hack it with some padding...and it's still a little strange
|
||||
*/}
|
||||
<Menu.Trigger
|
||||
as={IconButton}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
aria-label={formatMessage({
|
||||
id: 'content-releases.content-manager-edit-view.release-action-menu',
|
||||
defaultMessage: 'Release action options',
|
||||
})}
|
||||
// @ts-expect-error See above
|
||||
icon={<More />}
|
||||
/>
|
||||
{/*
|
||||
TODO: Using Menu instead of SimpleMenu mainly because there is no positioning provided from the DS,
|
||||
Refactor this once fixed in the DS
|
||||
*/}
|
||||
<Menu.Content top={1}>
|
||||
<CheckPermissions permissions={PERMISSIONS.deleteAction}>
|
||||
<StyledMenuItem color="danger600" onSelect={handleDeleteAction}>
|
||||
<Flex gap={2}>
|
||||
<StyledCross />
|
||||
<Typography variant="omega">
|
||||
{formatMessage({
|
||||
id: 'content-releases.content-manager-edit-view.remove-from-release',
|
||||
defaultMessage: 'Remove from release',
|
||||
})}
|
||||
</Typography>
|
||||
</Flex>
|
||||
</StyledMenuItem>
|
||||
</CheckPermissions>
|
||||
</Menu.Content>
|
||||
</Menu.Root>
|
||||
</CheckPermissions>
|
||||
);
|
||||
};
|
||||
@ -132,7 +132,9 @@ describe('CMReleasesContainer', () => {
|
||||
render(<CMReleasesContainer />);
|
||||
|
||||
const informationBox = await screen.findByRole('complementary', { name: 'Releases' });
|
||||
expect(within(informationBox).getByText('release1')).toBeInTheDocument();
|
||||
expect(within(informationBox).getByText('release2')).toBeInTheDocument();
|
||||
const release1 = await within(informationBox).findByText('release1');
|
||||
const release2 = await within(informationBox).findByText('release2');
|
||||
expect(release1).toBeInTheDocument();
|
||||
expect(release2).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import { render, screen } from '@tests/utils';
|
||||
|
||||
import { ReleaseActionMenu } from '../ReleaseActionMenu';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
// eslint-disable-next-line
|
||||
CheckPermissions: ({ children }: { children: JSX.Element }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
describe('ReleaseActionMenu', () => {
|
||||
it('should render the menu with its options', async () => {
|
||||
const { user } = render(<ReleaseActionMenu releaseId="1" actionId="1" />);
|
||||
|
||||
const menuTrigger = screen.getByRole('button', { name: 'Release action options' });
|
||||
expect(menuTrigger).toBeInTheDocument();
|
||||
|
||||
await user.click(menuTrigger);
|
||||
expect(screen.getByRole('menuitem', { name: 'Remove from release' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -1,62 +1,50 @@
|
||||
import { Permission } from '@strapi/helper-plugin';
|
||||
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[];
|
||||
}
|
||||
|
||||
export const PERMISSIONS: PermissionMap = {
|
||||
main: [
|
||||
{
|
||||
id: 293,
|
||||
action: 'plugin::content-releases.read',
|
||||
subject: null,
|
||||
conditions: [],
|
||||
actionParameters: [],
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
create: [
|
||||
{
|
||||
id: 294,
|
||||
action: 'plugin::content-releases.create',
|
||||
subject: null,
|
||||
conditions: [],
|
||||
actionParameters: [],
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
update: [
|
||||
{
|
||||
id: 295,
|
||||
action: 'plugin::content-releases.update',
|
||||
subject: null,
|
||||
conditions: [],
|
||||
actionParameters: [],
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
delete: [
|
||||
{
|
||||
id: 296,
|
||||
action: 'plugin::content-releases.delete',
|
||||
subject: null,
|
||||
conditions: [],
|
||||
actionParameters: [],
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
createAction: [
|
||||
{
|
||||
id: 297,
|
||||
action: 'plugin::content-releases.create-action',
|
||||
subject: null,
|
||||
conditions: [],
|
||||
actionParameters: [],
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
deleteAction: [
|
||||
{
|
||||
action: 'plugin::content-releases.delete-action',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import { CreateReleaseAction } from '../../../shared/contracts/release-actions';
|
||||
import {
|
||||
CreateReleaseAction,
|
||||
DeleteReleaseAction,
|
||||
} from '../../../shared/contracts/release-actions';
|
||||
import { pluginId } from '../pluginId';
|
||||
|
||||
import { axiosBaseQuery } from './axios';
|
||||
@ -202,6 +205,21 @@ const releaseApi = createApi({
|
||||
{ type: 'ReleaseAction', id: arg.params.actionId },
|
||||
],
|
||||
}),
|
||||
deleteReleaseAction: build.mutation<
|
||||
DeleteReleaseAction.Response,
|
||||
DeleteReleaseAction.Request
|
||||
>({
|
||||
query({ params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
|
||||
method: 'DELETE',
|
||||
};
|
||||
},
|
||||
invalidatesTags: [
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
{ type: 'ReleaseAction', id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
@ -215,6 +233,7 @@ const {
|
||||
useCreateReleaseActionMutation,
|
||||
useUpdateReleaseMutation,
|
||||
useUpdateReleaseActionMutation,
|
||||
useDeleteReleaseActionMutation,
|
||||
} = releaseApi;
|
||||
|
||||
export {
|
||||
@ -226,5 +245,6 @@ export {
|
||||
useCreateReleaseActionMutation,
|
||||
useUpdateReleaseMutation,
|
||||
useUpdateReleaseActionMutation,
|
||||
useDeleteReleaseActionMutation,
|
||||
releaseApi,
|
||||
};
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
"content-manager-edit-view.add-to-release": "Add to release",
|
||||
"content-manager-edit-view.add-to-release.notification.success": "Entry added to release",
|
||||
"content-manager-edit-view.list-releases.title": "{isPublish, select, true {Will be published in} other {Will be unpublished in}}",
|
||||
"content-manager-edit-view.remove-from-release": "Remove from release",
|
||||
"content-manager-edit-view.remove-from-release.notification.success": "Entry removed from release",
|
||||
"content-manager-edit-view.release-action-menu": "Release action options",
|
||||
"content-manager.notification.entry-error": "Failed to get entry data",
|
||||
"plugin.name": "Releases",
|
||||
"pages.Releases.title": "Releases",
|
||||
|
||||
@ -116,9 +116,7 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => ({
|
||||
|
||||
return {
|
||||
...release,
|
||||
action: {
|
||||
type: actionForEntry.type,
|
||||
},
|
||||
action: actionForEntry
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ export interface ReleaseDataResponse extends Omit<Release, 'actions'> {
|
||||
}
|
||||
|
||||
export interface ReleaseForContentTypeEntryDataResponse extends Omit<Release, 'actions'> {
|
||||
action: { type: ReleaseAction['type'] };
|
||||
action: ReleaseAction;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user