From c9259c66808eb34940d463415b51e46df597a969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Ch=C3=A1vez?= Date: Tue, 16 Jan 2024 15:17:59 +0100 Subject: [PATCH] fix(content-releases): add edit entry to details view (#19161) Co-authored-by: Mark Kaylor --- .../src/components/CMReleasesContainer.tsx | 11 +- .../src/components/ReleaseActionMenu.tsx | 175 ++++++++++++++---- .../tests/ReleaseActionMenu.test.tsx | 12 +- .../admin/src/pages/ReleaseDetailsPage.tsx | 14 +- .../admin/src/translations/en.json | 1 + .../content-releases/admin/tests/store.ts | 13 ++ .../server/src/services/release.ts | 20 +- 7 files changed, 194 insertions(+), 52 deletions(-) diff --git a/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx b/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx index 1b089fb075..969451033c 100644 --- a/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx +++ b/packages/core/content-releases/admin/src/components/CMReleasesContainer.tsx @@ -356,11 +356,12 @@ export const CMReleasesContainer = () => { {release.name} - + + + ); diff --git a/packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx b/packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx index b2413f1471..3243bb1804 100644 --- a/packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx +++ b/packages/core/content-releases/admin/src/components/ReleaseActionMenu.tsx @@ -1,55 +1,69 @@ -import { Flex, IconButton, Typography } from '@strapi/design-system'; -import { Menu } from '@strapi/design-system/v2'; +import * as React from 'react'; + +import { Flex, IconButton, Typography, Icon } from '@strapi/design-system'; +import { Menu, Link } from '@strapi/design-system/v2'; import { CheckPermissions, useAPIErrorHandler, useNotification } from '@strapi/helper-plugin'; -import { Cross, More } from '@strapi/icons'; +import { Cross, More, Pencil } from '@strapi/icons'; import { isAxiosError } from 'axios'; import { useIntl } from 'react-intl'; +import { useSelector, TypedUseSelectorHook } from 'react-redux'; +import { NavLink } from 'react-router-dom'; import styled from 'styled-components'; -import { DeleteReleaseAction } from '../../../shared/contracts/release-actions'; +import { DeleteReleaseAction, ReleaseAction } from '../../../shared/contracts/release-actions'; import { PERMISSIONS } from '../constants'; import { useDeleteReleaseActionMutation } from '../services/release'; -const StyledMenuItem = styled(Menu.Item)` +import type { Store } from '@strapi/admin/strapi-admin'; +import type { Permission } from '@strapi/helper-plugin'; + +type RootState = ReturnType; +const useTypedSelector: TypedUseSelectorHook = useSelector; + +const StyledMenuItem = styled(Menu.Item)<{ variant?: 'neutral' | 'danger' }>` &:hover { - background: ${({ theme }) => theme.colors.danger100}; + background: ${({ theme, variant = 'neutral' }) => theme.colors[`${variant}100`]}; + + svg { + path { + fill: ${({ theme, variant = 'neutral' }) => theme.colors[`${variant}600`]}; + } + } + + a { + color: ${({ theme }) => theme.colors.neutral800}; + } } svg { path { - fill: ${({ theme }) => theme.colors.danger600}; + fill: ${({ theme, variant = 'neutral' }) => theme.colors[`${variant}600`]}; } } - &:hover { - svg { - path { - fill: ${({ theme }) => theme.colors.danger600}; - } - } + a { + color: ${({ theme }) => theme.colors.neutral800}; + } + + span, + a { + width: 100%; } `; -const StyledCross = styled(Cross)` - padding: ${({ theme }) => theme.spaces[1]}; -`; - +/* ------------------------------------------------------------------------------------------------- + * DeleteReleaseActionItemProps + * -----------------------------------------------------------------------------------------------*/ const StyledIconButton = styled(IconButton)` /* Setting this style inline with borderColor will not apply the style */ border: ${({ theme }) => `1px solid ${theme.colors.neutral200}`}; `; - -interface ReleaseActionMenuProps { +interface DeleteReleaseActionItemProps { releaseId: DeleteReleaseAction.Request['params']['releaseId']; actionId: DeleteReleaseAction.Request['params']['actionId']; - hasTriggerBorder?: boolean; } -export const ReleaseActionMenu = ({ - releaseId, - actionId, - hasTriggerBorder = false, -}: ReleaseActionMenuProps) => { +const DeleteReleaseActionItem = ({ releaseId, actionId }: DeleteReleaseActionItemProps) => { const { formatMessage } = useIntl(); const toggleNotification = useNotification(); const { formatAPIError } = useAPIErrorHandler(); @@ -90,6 +104,97 @@ export const ReleaseActionMenu = ({ } }; + return ( + + + + + + {formatMessage({ + id: 'content-releases.content-manager-edit-view.remove-from-release', + defaultMessage: 'Remove from release', + })} + + + + + ); +}; + +/* ------------------------------------------------------------------------------------------------- + * ReleaseActionEntryLinkItem + * -----------------------------------------------------------------------------------------------*/ +interface ReleaseActionEntryLinkItemProps { + contentTypeUid: ReleaseAction['contentType']; + entryId: ReleaseAction['entry']['id']; + locale: ReleaseAction['locale']; +} + +const ReleaseActionEntryLinkItem = ({ + contentTypeUid, + entryId, + locale, +}: ReleaseActionEntryLinkItemProps) => { + const { formatMessage } = useIntl(); + // Confirm user has permissions to access the entry for the given locale + const collectionTypePermissions = useTypedSelector( + (state) => state.rbacProvider.collectionTypesRelatedPermissions + ); + const updatePermissions = contentTypeUid + ? collectionTypePermissions[contentTypeUid]?.['plugin::content-manager.explorer.update'] + : []; + const canUpdateEntryForLocale = Boolean( + !locale || + updatePermissions?.find((permission: Permission) => + permission.properties?.locales?.includes(locale) + ) + ); + + return ( + + {canUpdateEntryForLocale && ( + + } + > + + {formatMessage({ + id: 'content-releases.content-manager-edit-view.edit-entry', + defaultMessage: 'Edit entry', + })} + + + + )} + + ); +}; + +/* ------------------------------------------------------------------------------------------------- + * Root + * -----------------------------------------------------------------------------------------------*/ + +interface RootProps { + children: React.ReactNode; + hasTriggerBorder?: boolean; +} + +const Root = ({ children, hasTriggerBorder = false }: RootProps) => { + const { formatMessage } = useIntl(); + return ( // A user can access the dropdown if they have permissions to delete a release-action OR update a release @@ -115,21 +220,15 @@ export const ReleaseActionMenu = ({ Refactor this once fixed in the DS */} - - - - - - {formatMessage({ - id: 'content-releases.content-manager-edit-view.remove-from-release', - defaultMessage: 'Remove from release', - })} - - - - + {children} ); }; + +export const ReleaseActionMenu = { + Root, + DeleteReleaseActionItem, + ReleaseActionEntryLinkItem, +}; 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 index 947da4cf35..021d710c56 100644 --- a/packages/core/content-releases/admin/src/components/tests/ReleaseActionMenu.test.tsx +++ b/packages/core/content-releases/admin/src/components/tests/ReleaseActionMenu.test.tsx @@ -10,12 +10,22 @@ jest.mock('@strapi/helper-plugin', () => ({ describe('ReleaseActionMenu', () => { it('should render the menu with its options', async () => { - const { user } = render(); + 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(); + expect(screen.getByRole('menuitem', { name: 'Edit entry' })).toBeInTheDocument(); }); }); diff --git a/packages/core/content-releases/admin/src/pages/ReleaseDetailsPage.tsx b/packages/core/content-releases/admin/src/pages/ReleaseDetailsPage.tsx index fe9ee4aafc..5a82909de0 100644 --- a/packages/core/content-releases/admin/src/pages/ReleaseDetailsPage.tsx +++ b/packages/core/content-releases/admin/src/pages/ReleaseDetailsPage.tsx @@ -633,7 +633,7 @@ const ReleaseDetailsBody = () => { - {releaseActions[key].map(({ id, type, entry }) => ( + {releaseActions[key].map(({ id, type, entry, contentType, locale }) => ( {`${ @@ -680,7 +680,17 @@ const ReleaseDetailsBody = () => { - + + + + diff --git a/packages/core/content-releases/admin/src/translations/en.json b/packages/core/content-releases/admin/src/translations/en.json index c34d1174bf..b309e13271 100644 --- a/packages/core/content-releases/admin/src/translations/en.json +++ b/packages/core/content-releases/admin/src/translations/en.json @@ -10,6 +10,7 @@ "content-manager-edit-view.add-to-release.redirect-button": "Open the list of releases", "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-releases.content-manager-edit-view.edit-entry": "Edit entry", "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", diff --git a/packages/core/content-releases/admin/tests/store.ts b/packages/core/content-releases/admin/tests/store.ts index 61aebe2d31..df9e5b932b 100644 --- a/packages/core/content-releases/admin/tests/store.ts +++ b/packages/core/content-releases/admin/tests/store.ts @@ -17,6 +17,19 @@ const initialState = { conditions: [], }, ], + collectionTypesRelatedPermissions: { + 'api::category.category': { + 'plugin::content-manager.explorer.update': [ + { + action: 'plugin::content-manager.explorer.update', + subject: 'api::category.category', + properties: { + locales: ['en'], + }, + }, + ], + }, + }, }, }; diff --git a/packages/core/content-releases/server/src/services/release.ts b/packages/core/content-releases/server/src/services/release.ts index 3ca43d303d..0cdfc76e70 100644 --- a/packages/core/content-releases/server/src/services/release.ts +++ b/packages/core/content-releases/server/src/services/release.ts @@ -261,12 +261,7 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => ({ const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions( contentTypeUids ); - const allLocales: Locale[] = await strapi.plugin('i18n').service('locales').find(); - const allLocalesDictionary = allLocales.reduce((acc, locale) => { - acc[locale.code] = { name: locale.name, code: locale.code }; - - return acc; - }, {}); + const allLocalesDictionary = await this.getLocalesDataForActions(); const formattedData = actions.map((action: ReleaseAction) => { const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType]; @@ -289,6 +284,19 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => ({ return _.groupBy(groupName)(formattedData); }, + async getLocalesDataForActions() { + if (!strapi.plugin('i18n')) { + return {}; + } + + const allLocales: Locale[] = (await strapi.plugin('i18n').service('locales').find()) || []; + return allLocales.reduce((acc, locale) => { + acc[locale.code] = { name: locale.name, code: locale.code }; + + return acc; + }, {}); + }, + async getContentTypesDataForActions(contentTypesUids: ReleaseAction['contentType'][]) { const contentManagerContentTypeService = strapi .plugin('content-manager')