diff --git a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/index.js b/packages/core/admin/admin/src/content-manager/components/AttributeFilter/index.js
deleted file mode 100644
index 54bd961ebe..0000000000
--- a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/index.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import React from 'react';
-
-import { useQueryParams } from '@strapi/helper-plugin';
-import PropTypes from 'prop-types';
-import { useIntl } from 'react-intl';
-
-import { useAdminUsers } from '../../../hooks/useAdminUsers';
-import { getDisplayName } from '../../utils';
-
-import { AdminUsersFilter } from './AdminUsersFilter';
-import Filters from './Filters';
-import useAllowedAttributes from './hooks/useAllowedAttributes';
-
-const CREATOR_ATTRIBUTES = ['createdBy', 'updatedBy'];
-
-const AttributeFilter = ({ contentType, slug, metadatas }) => {
- const { formatMessage } = useIntl();
-
- const [{ query }] = useQueryParams();
- // We get the users selected' ids
- const selectedUsers =
- query?.filters?.$and?.reduce((acc, filter) => {
- const [key, value] = Object.entries(filter)[0];
- const id = value.id?.$eq || value.id?.$ne;
-
- if (CREATOR_ATTRIBUTES.includes(key) && !acc.includes(id)) {
- acc.push(id);
- }
-
- return acc;
- }, []) ?? [];
- const { users, isLoading } = useAdminUsers(
- { filter: { id: { in: selectedUsers } } },
- {
- enabled: selectedUsers.length > 0,
- }
- );
-
- const allowedAttributes = useAllowedAttributes(contentType, slug);
- const displayedFilters = allowedAttributes.map((name) => {
- const attribute = contentType.attributes[name];
- const { type, enum: options } = attribute;
-
- const trackedEvent = {
- name: 'didFilterEntries',
- properties: { useRelation: type === 'relation' },
- };
-
- const { mainField, label } = metadatas[name].list;
-
- const filter = {
- name,
- metadatas: { label: formatMessage({ id: label, defaultMessage: label }) },
- fieldSchema: { type, options, mainField },
- trackedEvent,
- };
-
- if (attribute.type === 'relation' && attribute.target === 'admin::user') {
- filter.metadatas = {
- ...filter.metadatas,
- customOperators: [
- {
- intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$eq', defaultMessage: 'is' },
- value: '$eq',
- },
- {
- intlLabel: {
- id: 'components.FilterOptions.FILTER_TYPES.$ne',
- defaultMessage: 'is not',
- },
- value: '$ne',
- },
- ],
- customInput: AdminUsersFilter,
- options: users.map((user) => ({
- label: getDisplayName(user, formatMessage),
- customValue: user.id.toString(),
- })),
- };
- filter.fieldSchema.mainField = {
- name: 'id',
- };
- }
-
- return filter;
- });
-
- if (isLoading) {
- return null;
- }
-
- return ;
-};
-
-AttributeFilter.propTypes = {
- contentType: PropTypes.object.isRequired,
- metadatas: PropTypes.object.isRequired,
- slug: PropTypes.string.isRequired,
-};
-
-export default AttributeFilter;
diff --git a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/AdminUsersFilter.js b/packages/core/admin/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js
similarity index 89%
rename from packages/core/admin/admin/src/content-manager/components/AttributeFilter/AdminUsersFilter.js
rename to packages/core/admin/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js
index d2fefb42e5..ff08c4e280 100644
--- a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/AdminUsersFilter.js
+++ b/packages/core/admin/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js
@@ -4,8 +4,8 @@ import { Combobox, ComboboxOption } from '@strapi/design-system';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
-import { useAdminUsers } from '../../../hooks/useAdminUsers';
-import { getDisplayName } from '../../utils';
+import { useAdminUsers } from '../../../../hooks/useAdminUsers';
+import { getDisplayName } from '../../../utils';
const AdminUsersFilter = ({ value, onChange }) => {
const { formatMessage } = useIntl();
diff --git a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/tests/AdminUsersFilter.test.js b/packages/core/admin/admin/src/content-manager/components/Filter/CustomInputs/tests/AdminUsersFilter.test.js
similarity index 100%
rename from packages/core/admin/admin/src/content-manager/components/AttributeFilter/tests/AdminUsersFilter.test.js
rename to packages/core/admin/admin/src/content-manager/components/Filter/CustomInputs/tests/AdminUsersFilter.test.js
diff --git a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/Filters.js b/packages/core/admin/admin/src/content-manager/components/Filter/Filter.js
similarity index 89%
rename from packages/core/admin/admin/src/content-manager/components/AttributeFilter/Filters.js
rename to packages/core/admin/admin/src/content-manager/components/Filter/Filter.js
index ee2755fe0c..b3851404be 100644
--- a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/Filters.js
+++ b/packages/core/admin/admin/src/content-manager/components/Filter/Filter.js
@@ -2,11 +2,11 @@ import React, { useRef, useState } from 'react';
import { Box, Button } from '@strapi/design-system';
import { FilterListURLQuery, FilterPopoverURLQuery, useTracking } from '@strapi/helper-plugin';
-import { Filter } from '@strapi/icons';
+import { Filter as FilterIcon } from '@strapi/icons';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
-const Filters = ({ displayedFilters }) => {
+export const Filter = ({ displayedFilters }) => {
const [isVisible, setIsVisible] = useState(false);
const { formatMessage } = useIntl();
const buttonRef = useRef();
@@ -25,7 +25,7 @@ const Filters = ({ displayedFilters }) => {
}
+ startIcon={}
onClick={handleToggle}
size="S"
>
@@ -45,7 +45,7 @@ const Filters = ({ displayedFilters }) => {
);
};
-Filters.propTypes = {
+Filter.propTypes = {
displayedFilters: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
@@ -54,5 +54,3 @@ Filters.propTypes = {
})
).isRequired,
};
-
-export default Filters;
diff --git a/packages/core/admin/admin/src/content-manager/components/Filter/index.js b/packages/core/admin/admin/src/content-manager/components/Filter/index.js
new file mode 100644
index 0000000000..0eea77907f
--- /dev/null
+++ b/packages/core/admin/admin/src/content-manager/components/Filter/index.js
@@ -0,0 +1 @@
+export * from './Filter';
diff --git a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js b/packages/core/admin/admin/src/content-manager/hooks/useAllowedAttributes.js
similarity index 71%
rename from packages/core/admin/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js
rename to packages/core/admin/admin/src/content-manager/hooks/useAllowedAttributes.js
index bdd3d5e499..43dfc5a411 100644
--- a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js
+++ b/packages/core/admin/admin/src/content-manager/hooks/useAllowedAttributes.js
@@ -1,17 +1,11 @@
-import { findMatchingPermissions, useRBACProvider, useCollator } from '@strapi/helper-plugin';
-import { useIntl } from 'react-intl';
+import { useRBACProvider, findMatchingPermissions } from '@strapi/helper-plugin';
const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamiczone', 'password'];
const TIMESTAMPS = ['createdAt', 'updatedAt'];
const CREATOR_ATTRIBUTES = ['createdBy', 'updatedBy'];
-const useAllowedAttributes = (contentType, slug) => {
+export const useAllowedAttributes = (contentType, slug) => {
const { allPermissions } = useRBACProvider();
- const { locale } = useIntl();
-
- const formatter = useCollator(locale, {
- sensitivity: 'base',
- });
const readPermissionsForSlug = findMatchingPermissions(allPermissions, [
{
@@ -43,14 +37,11 @@ const useAllowedAttributes = (contentType, slug) => {
return true;
});
- const allowedAndDefaultAttributes = [
+
+ return [
'id',
...allowedAttributes,
...TIMESTAMPS,
...(canReadAdminUsers ? CREATOR_ATTRIBUTES : []),
];
-
- return allowedAndDefaultAttributes.sort((a, b) => formatter.compare(a, b));
};
-
-export default useAllowedAttributes;
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 36ceb1120d..a8c988def5 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
@@ -28,6 +28,7 @@ import {
useTracking,
Link,
useAPIErrorHandler,
+ useCollator,
useStrapiApp,
Table,
PaginationURLQuery,
@@ -46,10 +47,13 @@ import { bindActionCreators, compose } from 'redux';
import styled from 'styled-components';
import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
+import { useAdminUsers } from '../../../hooks/useAdminUsers';
import { useEnterprise } from '../../../hooks/useEnterprise';
import { selectAdminPermissions } from '../../../pages/App/selectors';
import { InjectionZone } from '../../../shared/components';
-import AttributeFilter from '../../components/AttributeFilter';
+import { Filter } from '../../components/Filter';
+import { AdminUsersFilter } from '../../components/Filter/CustomInputs/AdminUsersFilter';
+import { useAllowedAttributes } from '../../hooks/useAllowedAttributes';
import { getTrad, getDisplayName } from '../../utils';
import { getData, getDataSucceeded, onChangeListHeaders, onResetListHeaders } from './actions';
@@ -70,6 +74,8 @@ const ConfigureLayoutBox = styled(Box)`
const REVIEW_WORKFLOW_COLUMNS_CE = null;
const REVIEW_WORKFLOW_COLUMNS_CELL_CE = () => null;
+const REVIEW_WORKFLOW_FILTER_CE = [];
+const CREATOR_ATTRIBUTES = ['createdBy', 'updatedBy'];
function ListView({
canCreate,
@@ -101,17 +107,90 @@ function ListView({
const { notifyStatus } = useNotifyAT();
const { formatAPIError } = useAPIErrorHandler(getTrad);
const permissions = useSelector(selectAdminPermissions);
+ const allowedAttributes = useAllowedAttributes(contentType, slug);
+ const [{ query }] = useQueryParams();
+ const { pathname } = useLocation();
+ const { push } = useHistory();
+ const { formatMessage, locale } = useIntl();
+ const fetchClient = useFetchClient();
+ const formatter = useCollator(locale, {
+ sensitivity: 'base',
+ });
+
+ const selectedUserIds =
+ query?.filters?.$and?.reduce((acc, filter) => {
+ const [key, value] = Object.entries(filter)[0];
+ const id = value.id?.$eq || value.id?.$ne;
+
+ if (CREATOR_ATTRIBUTES.includes(key) && !acc.includes(id)) {
+ acc.push(id);
+ }
+
+ return acc;
+ }, []) ?? [];
+
+ const { users, isLoading: isLoadingAdminUsers } = useAdminUsers(
+ { filter: { id: { in: selectedUserIds } } },
+ {
+ enabled: selectedUserIds.length > 0,
+ }
+ );
useFocusWhenNavigate();
- const [{ query }] = useQueryParams();
const params = React.useMemo(() => buildValidGetParams(query), [query]);
const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
- const { pathname } = useLocation();
- const { push } = useHistory();
- const { formatMessage } = useIntl();
- const fetchClient = useFetchClient();
+ const displayedAttributeFilters = allowedAttributes.map((name) => {
+ const attribute = contentType.attributes[name];
+ const { type, enum: options } = attribute;
+
+ const trackedEvent = {
+ name: 'didFilterEntries',
+ properties: { useRelation: type === 'relation' },
+ };
+
+ const { mainField, label } = metadatas[name].list;
+
+ const filter = {
+ name,
+ metadatas: { label: formatMessage({ id: label, defaultMessage: label }) },
+ fieldSchema: { type, options, mainField },
+ trackedEvent,
+ };
+
+ if (attribute.type === 'relation' && attribute.target === 'admin::user') {
+ filter.metadatas = {
+ ...filter.metadatas,
+ customOperators: [
+ {
+ intlLabel: {
+ id: 'components.FilterOptions.FILTER_TYPES.$eq',
+ defaultMessage: 'is',
+ },
+ value: '$eq',
+ },
+ {
+ intlLabel: {
+ id: 'components.FilterOptions.FILTER_TYPES.$ne',
+ defaultMessage: 'is not',
+ },
+ value: '$ne',
+ },
+ ],
+ customInput: AdminUsersFilter,
+ options: users.map((user) => ({
+ label: getDisplayName(user, formatMessage),
+ customValue: user.id.toString(),
+ })),
+ };
+ filter.fieldSchema.mainField = {
+ name: 'id',
+ };
+ }
+
+ return filter;
+ });
const hasDraftAndPublish = options?.draftAndPublish ?? false;
const hasReviewWorkflows = options?.reviewWorkflows ?? false;
@@ -142,6 +221,34 @@ function ListView({
}
);
+ const reviewWorkflowFilter = useEnterprise(
+ REVIEW_WORKFLOW_FILTER_CE,
+ async () =>
+ (
+ await import(
+ '../../../../../ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants'
+ )
+ ).REVIEW_WORKFLOW_STAGE_FILTER,
+ {
+ combine(ceFilters, eeFilter) {
+ return [
+ ...ceFilters,
+ {
+ ...eeFilter,
+ metadatas: {
+ ...eeFilter.metadatas,
+ label: formatMessage(eeFilter.metadatas.label),
+ uid: contentType.uid,
+ },
+ },
+ ];
+ },
+
+ defaultValue: [],
+ enabled: hasReviewWorkflows,
+ }
+ );
+
const { post, del } = fetchClient;
const bulkUnpublishMutation = useMutation(
@@ -550,8 +657,12 @@ function ListView({
trackedEvent="didSearch"
/>
)}
- {isFilterable && (
-
+ {isFilterable && !isLoadingAdminUsers && (
+ formatter.compare(a.metadatas.label, b.metadatas.label)
+ )}
+ />
)}
>
}
diff --git a/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/ReviewWorkflowsFilter.js b/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/ReviewWorkflowsFilter.js
new file mode 100644
index 0000000000..539ab7d4d1
--- /dev/null
+++ b/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/ReviewWorkflowsFilter.js
@@ -0,0 +1,70 @@
+import * as React from 'react';
+
+import { Flex, Loader, SingleSelect, SingleSelectOption, Typography } from '@strapi/design-system';
+import PropTypes from 'prop-types';
+import { useIntl } from 'react-intl';
+
+import { useReviewWorkflows } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows';
+import { getStageColorByHex } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
+
+export const ReviewWorkflowsFilter = ({ value, onChange, uid }) => {
+ const { formatMessage } = useIntl();
+ const {
+ workflows: [workflow],
+ isLoading,
+ } = useReviewWorkflows({ filters: { contentTypes: uid } });
+
+ return (
+ (
+
+
+ {value}
+
+ {isLoading ? : null}
+
+ )}
+ >
+ {(workflow?.stages ?? []).map(({ id, color, name }) => {
+ const { themeColorName } = getStageColorByHex(color);
+
+ return (
+
+ }
+ value={name}
+ >
+ {name}
+
+ );
+ })}
+
+ );
+};
+
+ReviewWorkflowsFilter.defaultProps = {
+ value: '',
+};
+
+ReviewWorkflowsFilter.propTypes = {
+ onChange: PropTypes.func.isRequired,
+ uid: PropTypes.string.isRequired,
+ value: PropTypes.string,
+};
diff --git a/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js b/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js
new file mode 100644
index 0000000000..1896ab52ef
--- /dev/null
+++ b/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js
@@ -0,0 +1,27 @@
+import { getTrad } from '../../../../../../../admin/src/content-manager/utils';
+
+import { ReviewWorkflowsFilter } from './ReviewWorkflowsFilter';
+
+export const REVIEW_WORKFLOW_STAGE_FILTER = {
+ fieldSchema: {
+ type: 'relation',
+ mainField: {
+ name: 'name',
+
+ schema: {
+ type: 'string',
+ },
+ },
+ },
+
+ metadatas: {
+ customInput: ReviewWorkflowsFilter,
+
+ label: {
+ id: getTrad(`containers.ListPage.table-headers.reviewWorkflows.stage`),
+ defaultMessage: 'Review stage',
+ },
+ },
+
+ name: 'strapi_stage',
+};
diff --git a/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/tests/ReviewWorkflowsFilter.test.js b/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/tests/ReviewWorkflowsFilter.test.js
new file mode 100644
index 0000000000..77aa28298b
--- /dev/null
+++ b/packages/core/admin/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/tests/ReviewWorkflowsFilter.test.js
@@ -0,0 +1,83 @@
+import React from 'react';
+
+import { ThemeProvider, lightTheme } from '@strapi/design-system';
+import { render, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { rest } from 'msw';
+import { setupServer } from 'msw/node';
+import { IntlProvider } from 'react-intl';
+import { QueryClientProvider, QueryClient } from 'react-query';
+
+import { ReviewWorkflowsFilter } from '../ReviewWorkflowsFilter';
+
+const server = setupServer(
+ rest.get('*/admin/review-workflows/workflows', (req, res, ctx) => {
+ return res(
+ ctx.json({
+ data: [
+ {
+ id: 1,
+ stages: [
+ {
+ id: 1,
+ name: 'To Review',
+ color: '#FFFFFF',
+ },
+ ],
+ },
+ ],
+ })
+ );
+ })
+);
+
+const queryClient = new QueryClient();
+
+const setup = (props) => {
+ return {
+ ...render( {}} {...props} />, {
+ wrapper: ({ children }) => (
+
+
+
+ {children}
+
+
+
+ ),
+ }),
+ user: userEvent.setup(),
+ };
+};
+
+describe('Content-Manger | List View | Filter | ReviewWorkflowsFilter', () => {
+ beforeAll(() => {
+ server.listen();
+ });
+
+ afterAll(() => {
+ server.close();
+ });
+
+ it('should display stages', async () => {
+ const { getByText, user, getByRole } = setup();
+
+ await user.click(getByRole('combobox'));
+
+ await waitFor(() => {
+ expect(getByText('To Review')).toBeInTheDocument();
+ });
+ });
+
+ it('should use the stage name as filter value', async () => {
+ const spy = jest.fn();
+ const { getByText, user, getByRole } = setup({ onChange: spy });
+
+ await user.click(getByRole('combobox'));
+ await user.click(getByText('To Review'));
+
+ await waitFor(() => {
+ expect(spy).toHaveBeenCalledWith('To Review');
+ });
+ });
+});