From 93969787db8fa7051a428eb00aae7c695ed46100 Mon Sep 17 00:00:00 2001 From: markkaylor Date: Thu, 14 Dec 2023 12:38:05 +0100 Subject: [PATCH] feat(content-releases): add delete release action button (#19047) --- .../src/components/CMReleasesContainer.tsx | 24 +++- .../src/components/ReleaseActionMenu.tsx | 125 ++++++++++++++++++ .../tests/CMReleasesContainer.test.tsx | 6 +- .../tests/ReleaseActionMenu.test.tsx | 21 +++ .../content-releases/admin/src/constants.ts | 30 ++--- .../admin/src/services/release.ts | 22 ++- .../admin/src/translations/en.json | 3 + .../server/src/services/release.ts | 4 +- .../shared/contracts/releases.ts | 2 +- 9 files changed, 202 insertions(+), 35 deletions(-) create mode 100644 packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx create mode 100644 packages/core/content-releases/admin/src/components/tests/ReleaseActionMenu.test.tsx diff --git a/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx b/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx index f66a5889d4..131d64ec9e 100644 --- a/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx +++ b/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx @@ -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 = () => { )} - + {release.name} - + + ); })} @@ -322,7 +332,7 @@ export const CMReleasesContainer = () => { color="neutral700" variant="tertiary" startIcon={} - onClick={toggleAddActionToReleaseModal} + onClick={toggleModal} > {formatMessage({ id: 'content-releases.content-manager-edit-view.add-to-release', @@ -331,9 +341,9 @@ export const CMReleasesContainer = () => { - {showModal && ( + {isModalOpen && ( diff --git a/packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx b/packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx new file mode 100644 index 0000000000..46ac52979a --- /dev/null +++ b/packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx @@ -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 + + + {/* + TODO Fix in the DS + - as={IconButton} has TS error: Property 'icon' does not exist on type 'IntrinsicAttributes & TriggerProps & RefAttributes' + - The Icon doesn't actually show unless you hack it with some padding...and it's still a little strange + */} + } + /> + {/* + TODO: Using Menu instead of SimpleMenu mainly because there is no positioning provided from the DS, + Refactor this once fixed in the DS + */} + + + + + + + {formatMessage({ + id: 'content-releases.content-manager-edit-view.remove-from-release', + defaultMessage: 'Remove from release', + })} + + + + + + + + ); +}; diff --git a/packages/core/content-releases/admin/src/components/tests/CMReleasesContainer.test.tsx b/packages/core/content-releases/admin/src/components/tests/CMReleasesContainer.test.tsx index 9feffc181e..295bfb7c4a 100644 --- a/packages/core/content-releases/admin/src/components/tests/CMReleasesContainer.test.tsx +++ b/packages/core/content-releases/admin/src/components/tests/CMReleasesContainer.test.tsx @@ -132,7 +132,9 @@ describe('CMReleasesContainer', () => { render(); 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(); }); }); diff --git a/packages/core/content-releases/admin/src/components/tests/ReleaseActionMenu.test.tsx b/packages/core/content-releases/admin/src/components/tests/ReleaseActionMenu.test.tsx new file mode 100644 index 0000000000..947da4cf35 --- /dev/null +++ b/packages/core/content-releases/admin/src/components/tests/ReleaseActionMenu.test.tsx @@ -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 }) =>
{children}
, +})); + +describe('ReleaseActionMenu', () => { + it('should render the menu with its options', async () => { + const { user } = render(); + + 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(); + }); +}); diff --git a/packages/core/content-releases/admin/src/constants.ts b/packages/core/content-releases/admin/src/constants.ts index 894e8cec45..5eaafe772c 100644 --- a/packages/core/content-releases/admin/src/constants.ts +++ b/packages/core/content-releases/admin/src/constants.ts @@ -1,62 +1,50 @@ -import { Permission } from '@strapi/helper-plugin'; +import { Permission as StrapiPermission } from '@strapi/helper-plugin'; +type Permission = Pick; 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, }, ], }; diff --git a/packages/core/content-releases/admin/src/services/release.ts b/packages/core/content-releases/admin/src/services/release.ts index a3b0d3d03c..4008213c72 100644 --- a/packages/core/content-releases/admin/src/services/release.ts +++ b/packages/core/content-releases/admin/src/services/release.ts @@ -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, }; diff --git a/packages/core/content-releases/admin/src/translations/en.json b/packages/core/content-releases/admin/src/translations/en.json index b96c9aae80..043c4c70eb 100644 --- a/packages/core/content-releases/admin/src/translations/en.json +++ b/packages/core/content-releases/admin/src/translations/en.json @@ -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", diff --git a/packages/core/content-releases/server/src/services/release.ts b/packages/core/content-releases/server/src/services/release.ts index 3f653e55a2..9a2f88ba85 100644 --- a/packages/core/content-releases/server/src/services/release.ts +++ b/packages/core/content-releases/server/src/services/release.ts @@ -116,9 +116,7 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => ({ return { ...release, - action: { - type: actionForEntry.type, - }, + action: actionForEntry }; } diff --git a/packages/core/content-releases/shared/contracts/releases.ts b/packages/core/content-releases/shared/contracts/releases.ts index a6d10aa275..6ea37f6bb8 100644 --- a/packages/core/content-releases/shared/contracts/releases.ts +++ b/packages/core/content-releases/shared/contracts/releases.ts @@ -23,7 +23,7 @@ export interface ReleaseDataResponse extends Omit { } export interface ReleaseForContentTypeEntryDataResponse extends Omit { - action: { type: ReleaseAction['type'] }; + action: ReleaseAction; } /**