diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js
index 18ac0bc812..bd90194ca1 100644
--- a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js
+++ b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js
@@ -128,11 +128,15 @@ function ListView({
enabled: !!options?.reviewWorkflows,
}
);
- const ReviewWorkflowsStage = useEnterprise(
+ const ReviewWorkflowsColumns = useEnterprise(
REVIEW_WORKFLOW_COLUMNS_CELL_CE,
- async () =>
- (await import('../../../../../ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn'))
- .ReviewWorkflowsStageEE,
+ async () => {
+ const { ReviewWorkflowsStageEE, ReviewWorkflowsAssigneeEE } = await import(
+ '../../../../../ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn'
+ );
+
+ return { ReviewWorkflowsStageEE, ReviewWorkflowsAssigneeEE };
+ },
{
enabled: hasReviewWorkflows,
}
@@ -457,7 +461,7 @@ function ListView({
};
// Block rendering until the review stage component is fully loaded in EE
- if (!ReviewWorkflowsStage) {
+ if (!ReviewWorkflowsColumns) {
return null;
}
@@ -607,21 +611,38 @@ function ListView({
);
}
- if (hasReviewWorkflows && name === 'strapi_stage') {
- return (
-
- {rowData.strapi_stage ? (
-
- ) : (
- -
- )}
- |
- );
+ if (hasReviewWorkflows) {
+ if (name === 'strapi_stage') {
+ return (
+
+ {rowData.strapi_stage ? (
+
+ ) : (
+ -
+ )}
+ |
+ );
+ }
+ if (name === 'strapi_assignee') {
+ return (
+
+ {rowData.strapi_assignee ? (
+
+ ) : (
+ -
+ )}
+ |
+ );
+ }
}
return (
diff --git a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js
index ab2ec539e1..7cc035b48e 100644
--- a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js
+++ b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js
@@ -18,6 +18,8 @@ import {
import { useIntl } from 'react-intl';
import { useMutation } from 'react-query';
+import { useLicenseLimits } from '../../../../../../hooks/useLicenseLimits';
+import * as LimitsModal from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/components/LimitsModal';
import { useReviewWorkflows } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
import { getStageColorByHex } from '../../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
import { STAGE_ATTRIBUTE_NAME } from '../../constants';
@@ -25,7 +27,6 @@ import { STAGE_ATTRIBUTE_NAME } from '../../constants';
export function StageSelect() {
const {
initialData,
- isCreatingEntry,
layout: { uid },
isSingleType,
onChange,
@@ -34,17 +35,19 @@ export function StageSelect() {
const { formatMessage } = useIntl();
const { formatAPIError } = useAPIErrorHandler();
const toggleNotification = useNotification();
- const { workflows, isLoading } = useReviewWorkflows();
-
- const activeWorkflowStage = initialData?.[STAGE_ATTRIBUTE_NAME] ?? null;
-
- // TODO: this works only as long as we support one workflow
- const workflow = workflows?.[0] ?? null;
+ const {
+ meta,
+ workflows: [workflow],
+ isLoading,
+ } = useReviewWorkflows({ filters: { contentTypes: uid } });
+ const { getFeature } = useLicenseLimits();
+ const [showLimitModal, setShowLimitModal] = React.useState(false);
+ const limits = getFeature('review-workflows');
// it is possible to rely on initialData here, because it always will
// be updated at the same time when modifiedData is updated, otherwise
// the entity is flagged as modified
- const currentWorkflowStage = initialData?.[STAGE_ATTRIBUTE_NAME] ?? null;
+ const activeWorkflowStage = initialData?.[STAGE_ATTRIBUTE_NAME] ?? null;
const mutation = useMutation(
async ({ entityId, stageId, uid }) => {
@@ -71,36 +74,53 @@ export function StageSelect() {
type: 'success',
message: {
id: 'content-manager.reviewWorkflows.stage.notification.saved',
- defaultMessage: 'Success: Review stage updated',
+ defaultMessage: 'Review stage updated',
},
});
},
}
);
- // if entities are created e.g. through lifecycle methods
- // they may not have a stage assigned. Updating the entity won't
- // set the default stage either which may lead to entities that
- // do not have a stage assigned for a while. By displaying an
- // error by default we are trying to nudge users into assigning a stage.
- const initialStageNullError =
- currentWorkflowStage === null &&
- !isLoading &&
- !isCreatingEntry &&
- formatMessage({
- id: 'content-manager.reviewWorkflows.stage.select.placeholder',
- defaultMessage: 'Select a stage',
- });
-
- const formattedError =
- (mutation.error && formatAPIError(mutation.error)) || initialStageNullError || null;
-
const handleChange = async ({ value: stageId }) => {
- mutation.mutate({
- entityId: initialData.id,
- stageId,
- uid,
- });
+ try {
+ /**
+ * If the current license has a limit:
+ * check if the total count of workflows exceeds that limit and display
+ * the limits modal.
+ *
+ * If the current license does not have a limit (e.g. offline license):
+ * do nothing (for now).
+ *
+ */
+
+ if (limits?.workflows && parseInt(limits.workflows, 10) > meta.workflowCount) {
+ setShowLimitModal('workflow');
+
+ /**
+ * If the current license has a limit:
+ * check if the total count of stages exceeds that limit and display
+ * the limits modal.
+ *
+ * If the current license does not have a limit (e.g. offline license):
+ * do nothing (for now).
+ *
+ */
+ } else if (
+ limits?.stagesPerWorkflow &&
+ parseInt(limits.stagesPerWorkflow, 10) > workflow.stages.length
+ ) {
+ setShowLimitModal('stage');
+ } else {
+ mutation.mutateAsync({
+ entityId: initialData.id,
+ stageId,
+ uid,
+ });
+ }
+ } catch (error) {
+ // react-query@v3: the error doesn't have to be handled here
+ // see: https://github.com/TanStack/query/issues/121
+ }
};
const { themeColorName } = activeWorkflowStage?.color
@@ -108,67 +128,107 @@ export function StageSelect() {
: {};
return (
-
-
- handleChange({ value })}
- label={formatMessage({
- id: 'content-manager.reviewWorkflows.stage.label',
- defaultMessage: 'Review stage',
- })}
- startIcon={
-
- }
- // eslint-disable-next-line react/no-unstable-nested-components
- customizeContent={() => (
-
-
- {activeWorkflowStage?.name}
-
- {isLoading ? : null}
-
- )}
- >
- {workflow
- ? workflow.stages.map(({ id, color, name }) => {
- const { themeColorName } = getStageColorByHex(color);
+ <>
+
+
+ handleChange({ value })}
+ label={formatMessage({
+ id: 'content-manager.reviewWorkflows.stage.label',
+ defaultMessage: 'Review stage',
+ })}
+ startIcon={
+
+ }
+ // eslint-disable-next-line react/no-unstable-nested-components
+ customizeContent={() => (
+
+
+ {activeWorkflowStage?.name}
+
+ {isLoading ? : null}
+
+ )}
+ >
+ {workflow
+ ? workflow.stages.map(({ id, color, name }) => {
+ const { themeColorName } = getStageColorByHex(color);
- return (
-
- }
- value={id}
- textValue={name}
- >
- {name}
-
- );
- })
- : []}
-
-
-
-
+ return (
+
+ }
+ value={id}
+ textValue={name}
+ >
+ {name}
+
+ );
+ })
+ : []}
+
+
+
+
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.workflows.limit.title',
+ defaultMessage: 'You’ve reached the limit of workflows in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.workflows.limit.body',
+ defaultMessage: 'Delete a workflow or contact Sales to enable more workflows.',
+ })}
+
+
+
+ setShowLimitModal(false)}
+ >
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.stages.limit.title',
+ defaultMessage: 'You have reached the limit of stages for this workflow in your plan',
+ })}
+
+
+
+ {formatMessage({
+ id: 'content-manager.reviewWorkflows.stages.limit.body',
+ defaultMessage: 'Try deleting some stages or contact Sales to enable more stages.',
+ })}
+
+
+ >
);
}
diff --git a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/tests/StageSelect.test.js b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/tests/StageSelect.test.js
index f6939eb8cf..9bdf650646 100644
--- a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/tests/StageSelect.test.js
+++ b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/tests/StageSelect.test.js
@@ -92,21 +92,6 @@ describe('EE | Content Manager | EditView | InformationBox | StageSelect', () =>
server.close();
});
- it('renders an error, if no workflow stage is assigned to the entity', async () => {
- useCMEditViewDataManager.mockReturnValue({
- initialData: {
- [STAGE_ATTRIBUTE_NAME]: null,
- },
- layout: { uid: 'api::articles:articles' },
- });
-
- const { getByText, queryByRole } = setup();
-
- expect(queryByRole('combobox')).toBeInTheDocument();
-
- await waitFor(() => expect(getByText(/select a stage/i)).toBeInTheDocument());
- });
-
it('renders an enabled select input, if the entity is edited', () => {
useCMEditViewDataManager.mockReturnValue({
initialData: {
diff --git a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/constants.js b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/constants.js
index a1a9c9f8b3..c0f04ed517 100644
--- a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/constants.js
+++ b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/constants.js
@@ -1,2 +1,2 @@
-export const STAGE_ATTRIBUTE_NAME = 'strapi_reviewWorkflows_stage';
+export const STAGE_ATTRIBUTE_NAME = 'strapi_stage';
export const ASSIGNEE_ATTRIBUTE_NAME = 'strapi_assignee';
diff --git a/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/tests/InformationBoxEE.test.js b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/tests/InformationBoxEE.test.js
new file mode 100644
index 0000000000..fe38fdda6f
--- /dev/null
+++ b/packages/core/admin/ee/admin/content-manager/pages/EditView/InformationBox/tests/InformationBoxEE.test.js
@@ -0,0 +1,137 @@
+import React from 'react';
+
+import { lightTheme, ThemeProvider } from '@strapi/design-system';
+import { useCMEditViewDataManager } from '@strapi/helper-plugin';
+import { render } from '@testing-library/react';
+import { rest } from 'msw';
+import { setupServer } from 'msw/node';
+import { IntlProvider } from 'react-intl';
+import { QueryClient, QueryClientProvider } from 'react-query';
+import { Provider } from 'react-redux';
+import { createStore } from 'redux';
+
+import { STAGE_ATTRIBUTE_NAME, ASSIGNEE_ATTRIBUTE_NAME } from '../constants';
+import { InformationBoxEE } from '../InformationBoxEE';
+
+jest.mock('@strapi/helper-plugin', () => ({
+ ...jest.requireActual('@strapi/helper-plugin'),
+ useCMEditViewDataManager: jest.fn(),
+ useNotification: jest.fn(() => ({
+ toggleNotification: jest.fn(),
+ })),
+}));
+
+const server = setupServer(
+ rest.get('*/users', (req, res, ctx) =>
+ res(
+ ctx.json({
+ data: {
+ results: [
+ {
+ id: 1,
+ },
+ ],
+
+ pagination: {
+ page: 1,
+ },
+ },
+ })
+ )
+ ),
+
+ rest.get('*/review-workflows/workflows', (req, res, ctx) =>
+ res(
+ ctx.json({
+ data: [
+ {
+ id: 1,
+ },
+ ],
+ })
+ )
+ )
+);
+
+const setup = (props) => {
+ return render(, {
+ wrapper({ children }) {
+ const store = createStore((state = {}) => state, {});
+ const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+ });
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+ },
+ });
+};
+
+describe('EE | Content Manager | EditView | InformationBox', () => {
+ beforeAll(() => {
+ server.listen();
+ });
+
+ afterAll(() => {
+ server.close();
+ });
+
+ it('renders the title and body of the Information component', () => {
+ useCMEditViewDataManager.mockReturnValue({
+ initialData: {},
+ isCreatingEntry: true,
+ layout: { uid: 'api::articles:articles' },
+ });
+
+ const { getByText } = setup();
+
+ expect(getByText('Information')).toBeInTheDocument();
+ expect(getByText('Last update')).toBeInTheDocument();
+ });
+
+ it('renders neither stage nor assignee select inputs, if no nothing is returned for an entity', () => {
+ useCMEditViewDataManager.mockReturnValue({
+ initialData: {},
+ layout: { uid: 'api::articles:articles' },
+ });
+
+ const { queryByRole } = setup();
+
+ expect(queryByRole('combobox')).not.toBeInTheDocument();
+ });
+
+ it('renders stage and assignee select inputs, if it is returned for an entity', () => {
+ useCMEditViewDataManager.mockReturnValue({
+ initialData: {
+ [STAGE_ATTRIBUTE_NAME]: {
+ id: 1,
+ color: '#4945FF',
+ name: 'Stage 1',
+ worklow: 1,
+ },
+
+ [ASSIGNEE_ATTRIBUTE_NAME]: {
+ id: 1,
+ firstname: 'Firstname',
+ lastname: 'Lastname',
+ },
+ },
+ layout: { uid: 'api::articles:articles' },
+ });
+
+ const { queryAllByRole } = setup();
+
+ expect(queryAllByRole('combobox').length).toBe(2);
+ });
+});