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;
}
/**