mirror of
https://github.com/strapi/strapi.git
synced 2025-08-07 00:09:23 +00:00
Merge pull request #17047 from strapi/chore/rw-ee_else_ce
Chore: Refactor CM list-view review-workflow columns to use useEnterprise
This commit is contained in:
commit
e47c94e31b
@ -54,3 +54,8 @@ if (!Component) {
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### `enabled`
|
||||
|
||||
Similar to react-query this boolean flag allows disabling the EE import, e.g. when more than one condition needs to be applied. If `enabled`
|
||||
is set to false, the first argument (CE_DATA) will be returned.
|
||||
|
@ -1,2 +0,0 @@
|
||||
// Overwritten in EE
|
||||
export default () => null;
|
@ -1,6 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import { BaseCheckbox, IconButton, Tbody, Td, Tr, Flex } from '@strapi/design-system';
|
||||
import {
|
||||
BaseCheckbox,
|
||||
IconButton,
|
||||
Tbody,
|
||||
Td,
|
||||
Tr,
|
||||
Flex,
|
||||
lightTheme,
|
||||
Status,
|
||||
Typography,
|
||||
} from '@strapi/design-system';
|
||||
import { useTracking, useFetchClient, useAPIErrorHandler } from '@strapi/helper-plugin';
|
||||
import { Trash, Duplicate, Pencil } from '@strapi/icons';
|
||||
import { AxiosError } from 'axios';
|
||||
@ -8,15 +18,19 @@ import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { useEnterprise } from '../../../../../hooks/useEnterprise';
|
||||
import { getFullName } from '../../../../../utils';
|
||||
import { usePluginsQueryParams } from '../../../../hooks';
|
||||
import { getTrad } from '../../../../utils';
|
||||
import CellContent from '../CellContent';
|
||||
|
||||
const REVIEW_WORKFLOW_COLUMNS_CE = () => null;
|
||||
|
||||
export const TableRows = ({
|
||||
canCreate,
|
||||
canDelete,
|
||||
contentType,
|
||||
features: { hasDraftAndPublish, hasReviewWorkflows },
|
||||
headers,
|
||||
entriesToDelete,
|
||||
onClickDelete,
|
||||
@ -33,6 +47,18 @@ export const TableRows = ({
|
||||
const { trackUsage } = useTracking();
|
||||
const pluginsQueryParams = usePluginsQueryParams();
|
||||
const { formatAPIError } = useAPIErrorHandler(getTrad);
|
||||
const ReviewWorkflowsStage = useEnterprise(
|
||||
REVIEW_WORKFLOW_COLUMNS_CE,
|
||||
async () =>
|
||||
(
|
||||
await import(
|
||||
'../../../../../../../ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn'
|
||||
)
|
||||
).ReviewWorkflowsStageEE,
|
||||
{
|
||||
enabled: hasReviewWorkflows,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
@ -74,6 +100,11 @@ export const TableRows = ({
|
||||
}
|
||||
};
|
||||
|
||||
// block rendering until the review stage component is fully loaded in EE
|
||||
if (!ReviewWorkflowsStage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table Cells with actions e.g edit, delete, duplicate have `stopPropagation`
|
||||
* to prevent the row from being selected.
|
||||
@ -113,20 +144,59 @@ export const TableRows = ({
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
{headers.map(({ key, cellFormatter, name, ...rest }) => {
|
||||
if (hasDraftAndPublish && name === 'publishedAt') {
|
||||
return (
|
||||
<Td key={key}>
|
||||
<Status
|
||||
width="min-content"
|
||||
showBullet={false}
|
||||
variant={data.publishedAt ? 'success' : 'secondary'}
|
||||
size="S"
|
||||
>
|
||||
<Typography
|
||||
fontWeight="bold"
|
||||
textColor={`${data.publishedAt ? 'success' : 'secondary'}700`}
|
||||
>
|
||||
{formatMessage({
|
||||
id: getTrad(
|
||||
`containers.List.${data.publishedAt ? 'published' : 'draft'}`
|
||||
),
|
||||
defaultMessage: data.publishedAt ? 'Published' : 'Draft',
|
||||
})}
|
||||
</Typography>
|
||||
</Status>
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasReviewWorkflows && name === 'strapi_reviewWorkflows_stage') {
|
||||
return (
|
||||
<Td key={key}>
|
||||
{data.strapi_reviewWorkflows_stage ? (
|
||||
<ReviewWorkflowsStage
|
||||
color={
|
||||
data.strapi_reviewWorkflows_stage.color ?? lightTheme.colors.primary600
|
||||
}
|
||||
name={data.strapi_reviewWorkflows_stage.name}
|
||||
/>
|
||||
) : (
|
||||
<Typography textColor="neutral800">-</Typography>
|
||||
)}
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Td key={key}>
|
||||
{typeof cellFormatter === 'function' ? (
|
||||
cellFormatter(data, { key, name, ...rest })
|
||||
) : (
|
||||
<CellContent
|
||||
content={data[name.split('.')[0]]}
|
||||
name={name}
|
||||
contentType={contentType}
|
||||
{...rest}
|
||||
rowId={data.id}
|
||||
/>
|
||||
)}
|
||||
<CellContent
|
||||
content={data[name.split('.')[0]]}
|
||||
name={name}
|
||||
contentType={contentType}
|
||||
{...rest}
|
||||
rowId={data.id}
|
||||
/>
|
||||
</Td>
|
||||
);
|
||||
})}
|
||||
@ -212,6 +282,10 @@ TableRows.propTypes = {
|
||||
uid: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
entriesToDelete: PropTypes.array,
|
||||
features: PropTypes.shape({
|
||||
hasDraftAndPublish: PropTypes.bool.isRequired,
|
||||
hasReviewWorkflows: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
headers: PropTypes.array.isRequired,
|
||||
onClickDelete: PropTypes.func,
|
||||
onSelectRow: PropTypes.func,
|
||||
|
@ -10,8 +10,6 @@ import {
|
||||
HeaderLayout,
|
||||
useNotifyAT,
|
||||
Flex,
|
||||
Typography,
|
||||
Status,
|
||||
} from '@strapi/design-system';
|
||||
import {
|
||||
NoPermissions,
|
||||
@ -33,7 +31,6 @@ import {
|
||||
} from '@strapi/helper-plugin';
|
||||
import { ArrowLeft, Cog, Plus } from '@strapi/icons';
|
||||
import axios from 'axios';
|
||||
import getReviewWorkflowsColumn from 'ee_else_ce/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import PropTypes from 'prop-types';
|
||||
import { stringify } from 'qs';
|
||||
@ -45,6 +42,7 @@ import { bindActionCreators, compose } from 'redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
|
||||
import { useEnterprise } from '../../../hooks/useEnterprise';
|
||||
import { selectAdminPermissions } from '../../../pages/App/selectors';
|
||||
import { InjectionZone } from '../../../shared/components';
|
||||
import AttributeFilter from '../../components/AttributeFilter';
|
||||
@ -67,6 +65,8 @@ const ConfigureLayoutBox = styled(Box)`
|
||||
}
|
||||
`;
|
||||
|
||||
const REVIEW_WORKFLOW_COLUMNS_CE = null;
|
||||
|
||||
function ListView({
|
||||
canCreate,
|
||||
canDelete,
|
||||
@ -107,8 +107,24 @@ function ListView({
|
||||
const { pathname } = useLocation();
|
||||
const { push } = useHistory();
|
||||
const { formatMessage } = useIntl();
|
||||
const hasDraftAndPublish = options?.draftAndPublish || false;
|
||||
const fetchClient = useFetchClient();
|
||||
|
||||
const hasDraftAndPublish = options?.draftAndPublish ?? false;
|
||||
const hasReviewWorkflows = options?.reviewWorkflows ?? false;
|
||||
|
||||
const reviewWorkflowColumns = useEnterprise(
|
||||
REVIEW_WORKFLOW_COLUMNS_CE,
|
||||
async () =>
|
||||
(
|
||||
await import(
|
||||
'../../../../../ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants'
|
||||
)
|
||||
).REVIEW_WORKFLOW_COLUMNS_EE,
|
||||
{
|
||||
enabled: !!options?.reviewWorkflows,
|
||||
}
|
||||
);
|
||||
|
||||
const { post, del } = fetchClient;
|
||||
|
||||
const bulkPublishMutation = useMutation(
|
||||
@ -388,24 +404,8 @@ function ListView({
|
||||
};
|
||||
});
|
||||
|
||||
if (!hasDraftAndPublish) {
|
||||
return formattedHeaders;
|
||||
}
|
||||
|
||||
// this should not exist. Ideally we would use registerHook() similar to what has been done
|
||||
// in the i18n plugin. In order to do that review-workflows should have been a plugin. In
|
||||
// a future iteration we need to find a better pattern.
|
||||
|
||||
// In CE this will return null - in EE a column definition including the custom formatting component.
|
||||
const reviewWorkflowColumn = getReviewWorkflowsColumn(layout);
|
||||
|
||||
if (reviewWorkflowColumn) {
|
||||
formattedHeaders.push(reviewWorkflowColumn);
|
||||
}
|
||||
|
||||
return [
|
||||
...formattedHeaders,
|
||||
{
|
||||
if (hasDraftAndPublish) {
|
||||
formattedHeaders.push({
|
||||
key: '__published_at_temp_key__',
|
||||
name: 'publishedAt',
|
||||
fieldSchema: {
|
||||
@ -419,25 +419,29 @@ function ListView({
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
},
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
cellFormatter(cellData) {
|
||||
const isPublished = cellData.publishedAt;
|
||||
const variant = isPublished ? 'success' : 'secondary';
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Status width="min-content" showBullet={false} variant={variant} size="S">
|
||||
<Typography fontWeight="bold" textColor={`${variant}700`}>
|
||||
{formatMessage({
|
||||
id: getTrad(`containers.List.${isPublished ? 'published' : 'draft'}`),
|
||||
defaultMessage: isPublished ? 'Published' : 'Draft',
|
||||
})}
|
||||
</Typography>
|
||||
</Status>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [runHookWaterfall, displayedHeaders, layout, hasDraftAndPublish, formatMessage]);
|
||||
if (reviewWorkflowColumns) {
|
||||
// Make sure the column header label is translated
|
||||
if (typeof reviewWorkflowColumns.metadatas.label !== 'string') {
|
||||
reviewWorkflowColumns.metadatas.label = formatMessage(
|
||||
reviewWorkflowColumns.metadatas.label
|
||||
);
|
||||
}
|
||||
|
||||
formattedHeaders.push(reviewWorkflowColumns);
|
||||
}
|
||||
|
||||
return formattedHeaders;
|
||||
}, [
|
||||
runHookWaterfall,
|
||||
displayedHeaders,
|
||||
layout,
|
||||
reviewWorkflowColumns,
|
||||
hasDraftAndPublish,
|
||||
formatMessage,
|
||||
]);
|
||||
|
||||
const subtitle = canRead
|
||||
? formatMessage(
|
||||
@ -580,6 +584,10 @@ function ListView({
|
||||
canCreate={canCreate}
|
||||
canDelete={canDelete}
|
||||
contentType={contentType}
|
||||
features={{
|
||||
hasDraftAndPublish,
|
||||
hasReviewWorkflows,
|
||||
}}
|
||||
headers={tableHeaders}
|
||||
rows={data}
|
||||
withBulkActions
|
||||
|
@ -54,6 +54,14 @@ describe('useEnterprise (EE)', () => {
|
||||
await waitFor(() => expect(result.current).toStrictEqual(EE_DATA_FIXTURE));
|
||||
});
|
||||
|
||||
test('Returns CE data, when enabled is set to false', async () => {
|
||||
const { result } = setup(CE_DATA_FIXTURE, async () => EE_DATA_FIXTURE, {
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current).toStrictEqual(CE_DATA_FIXTURE));
|
||||
});
|
||||
|
||||
test('Returns a custom defaultValue on first render followed by the EE data', async () => {
|
||||
const { result } = setup(CE_DATA_FIXTURE, async () => EE_DATA_FIXTURE, {
|
||||
defaultValue: false,
|
||||
|
@ -9,7 +9,7 @@ function isEnterprise() {
|
||||
export function useEnterprise(
|
||||
ceData,
|
||||
eeCallback,
|
||||
{ defaultValue = null, combine = (ceData, eeData) => eeData } = {}
|
||||
{ defaultValue = null, combine = (ceData, eeData) => eeData, enabled = true } = {}
|
||||
) {
|
||||
const eeCallbackRef = useCallbackRef(eeCallback);
|
||||
const combineCallbackRef = useCallbackRef(combine);
|
||||
@ -17,7 +17,7 @@ export function useEnterprise(
|
||||
// We have to use a nested object here, because functions (e.g. Components)
|
||||
// can not be stored as value directly
|
||||
const [{ data }, setData] = React.useState({
|
||||
data: isEnterprise() ? defaultValue : ceData,
|
||||
data: isEnterprise() && enabled ? defaultValue : ceData,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -27,10 +27,10 @@ export function useEnterprise(
|
||||
setData({ data: combineCallbackRef(ceData, eeData) });
|
||||
}
|
||||
|
||||
if (isEnterprise()) {
|
||||
if (isEnterprise() && enabled) {
|
||||
importEE();
|
||||
}
|
||||
}, [ceData, eeCallbackRef, combineCallbackRef]);
|
||||
}, [ceData, eeCallbackRef, combineCallbackRef, enabled]);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Typography } from '@strapi/design-system';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import getTrad from '../../../../../../../admin/src/content-manager/utils/getTrad';
|
||||
import { STAGE_COLOR_DEFAULT } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/constants';
|
||||
|
||||
import ReviewWorkflowsStage from '.';
|
||||
|
||||
export default (layout) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// TODO: As soon as the feature was enabled in EE mode, the BE currently does not have a way to send
|
||||
// `false` once a user is in CE mode again. We shouldn't have to perform the window.strapi.isEE check here
|
||||
// and it is meant to be in interim solution until we find a better one.
|
||||
const hasReviewWorkflows =
|
||||
(window.strapi.features.isEnabled(window.strapi.features.REVIEW_WORKFLOWS) &&
|
||||
layout.contentType.options?.reviewWorkflows) ??
|
||||
false;
|
||||
|
||||
if (!hasReviewWorkflows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
key: '__strapi_reviewWorkflows_stage_temp_key__',
|
||||
name: 'strapi_reviewWorkflows_stage',
|
||||
fieldSchema: {
|
||||
type: 'relation',
|
||||
},
|
||||
metadatas: {
|
||||
label: formatMessage({
|
||||
id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.stage`),
|
||||
defaultMessage: 'Review stage',
|
||||
}),
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
mainField: {
|
||||
name: 'name',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
cellFormatter({ strapi_reviewWorkflows_stage }) {
|
||||
// if entities are created e.g. through lifecycle methods
|
||||
// they may not have a stage assigned
|
||||
if (!strapi_reviewWorkflows_stage) {
|
||||
return <Typography textColor="neutral800">-</Typography>;
|
||||
}
|
||||
|
||||
const { color, name } = strapi_reviewWorkflows_stage;
|
||||
|
||||
return <ReviewWorkflowsStage color={color ?? STAGE_COLOR_DEFAULT} name={name} />;
|
||||
},
|
||||
};
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
import { ReviewWorkflowsStageEE } from './ReviewWorkflowsStageEE';
|
||||
|
||||
export default ReviewWorkflowsStageEE;
|
@ -4,7 +4,7 @@ import { Box, Flex, Typography } from '@strapi/design-system';
|
||||
import { pxToRem } from '@strapi/helper-plugin';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getStageColorByHex } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
|
||||
import { getStageColorByHex } from '../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
|
||||
|
||||
export function ReviewWorkflowsStageEE({ color, name }) {
|
||||
const { themeColorName } = getStageColorByHex(color);
|
@ -0,0 +1,24 @@
|
||||
import getTrad from '../../../../../../admin/src/content-manager/utils/getTrad';
|
||||
|
||||
export const REVIEW_WORKFLOW_COLUMNS_EE = {
|
||||
key: '__strapi_reviewWorkflows_stage_temp_key__',
|
||||
name: 'strapi_reviewWorkflows_stage',
|
||||
fieldSchema: {
|
||||
type: 'relation',
|
||||
},
|
||||
metadatas: {
|
||||
// formatMessage() will be applied when the column is rendered
|
||||
label: {
|
||||
id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.stage`),
|
||||
defaultMessage: 'Review stage',
|
||||
},
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
mainField: {
|
||||
name: 'name',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './ReviewWorkflowsStageEE';
|
@ -4,12 +4,12 @@ import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { render } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import ReviewWorkflowsStage from '..';
|
||||
import { ReviewWorkflowsStageEE } from '..';
|
||||
|
||||
const ComponentFixture = (props) => (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ReviewWorkflowsStage {...props} />
|
||||
<ReviewWorkflowsStageEE {...props} />
|
||||
</IntlProvider>
|
||||
</ThemeProvider>
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user