diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/Users/ListPage/Filters/index.js b/packages/core/admin/admin/src/pages/SettingsPage/components/Filters/index.js similarity index 100% rename from packages/core/admin/admin/src/pages/SettingsPage/pages/Users/ListPage/Filters/index.js rename to packages/core/admin/admin/src/pages/SettingsPage/components/Filters/index.js diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Filters/ComboboxFilter.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Filters/ComboboxFilter.js new file mode 100644 index 0000000000..64337693b7 --- /dev/null +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Filters/ComboboxFilter.js @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Combobox, ComboboxOption } from '@strapi/design-system/Combobox'; + +const ComboboxFilter = ({ value, options, setModifiedData }) => { + return ( + setModifiedData((prev) => ({ ...prev, value }))} + > + {options.map(({ label, customValue }) => { + return ( + + {label} + + ); + })} + + ); +}; + +ComboboxFilter.defaultProps = { + value: null, +}; + +ComboboxFilter.propTypes = { + value: PropTypes.string, + options: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string.isRequired, + customValue: PropTypes.string.isRequired, + }) + ).isRequired, + setModifiedData: PropTypes.func.isRequired, +}; + +export default ComboboxFilter; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js index 627aa26776..7c72d2ada1 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js @@ -18,7 +18,7 @@ import { useIntl } from 'react-intl'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import adminPermissions from '../../../../../permissions'; import TableRows from './DynamicTable/TableRows'; -import Filters from './Filters'; +import Filters from '../../../components/Filters'; import ModalForm from './ModalForm'; import PaginationFooter from './PaginationFooter'; import { deleteData, fetchData } from './utils/api'; diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json index cfb387df99..6716e13f2e 100644 --- a/packages/core/admin/admin/src/translations/en.json +++ b/packages/core/admin/admin/src/translations/en.json @@ -185,11 +185,11 @@ "Settings.permissions.auditLogs.details": "Log Details", "Settings.permissions.auditLogs.payload": "Payload", "Settings.permissions.auditLogs.listview.header.subtitle": "Logs of all the activities that happened in your environment", - "Settings.permissions.auditLogs.entry.create": "Create entry ({model})", - "Settings.permissions.auditLogs.entry.update": "Update entry ({model})", - "Settings.permissions.auditLogs.entry.delete": "Delete entry ({model})", - "Settings.permissions.auditLogs.entry.publish": "Publish entry ({model})", - "Settings.permissions.auditLogs.entry.unpublish": "Unpublish entry ({model})", + "Settings.permissions.auditLogs.entry.create": "Create entry {model}", + "Settings.permissions.auditLogs.entry.update": "Update entry {model}", + "Settings.permissions.auditLogs.entry.delete": "Delete entry {model}", + "Settings.permissions.auditLogs.entry.publish": "Publish entry {model}", + "Settings.permissions.auditLogs.entry.unpublish": "Unpublish entry {model}", "Settings.permissions.auditLogs.media.create": "Create media", "Settings.permissions.auditLogs.media.update": "Update media", "Settings.permissions.auditLogs.media.delete": "Delete media", diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js index 225aef39db..c23bd89827 100644 --- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js +++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js @@ -25,7 +25,7 @@ const TableRows = ({ headers, rows, onOpenModal }) => { id: `Settings.permissions.auditLogs.${value}`, defaultMessage: getDefaultMessage(value), }, - { model } + { model: `(${model})` } ); } diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js new file mode 100644 index 0000000000..c93f137117 --- /dev/null +++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js @@ -0,0 +1,49 @@ +import { useQueries } from 'react-query'; +import { useNotification, useFetchClient } from '@strapi/helper-plugin'; +import { useLocation } from 'react-router-dom'; + +const useAuditLogsData = ({ canRead }) => { + const { get } = useFetchClient(); + const { search } = useLocation(); + const toggleNotification = useNotification(); + + const fetchAuditLogsPage = async ({ queryKey }) => { + const search = queryKey[1]; + const { data } = await get(`/admin/audit-logs${search}`); + + return data; + }; + + const fetchAllUsers = async () => { + const { data } = await get(`/admin/users`); + + return data; + }; + + const queryOptions = { + enabled: canRead, + keepPreviousData: true, + retry: false, + staleTime: 1000 * 20, + onError() { + toggleNotification({ + type: 'warning', + message: { id: 'notification.error', defaultMessage: 'An error occured' }, + }); + }, + }; + + const [auditLogsData, userData] = useQueries([ + { queryKey: ['auditLogs', search], queryFn: fetchAuditLogsPage, ...queryOptions }, + { queryKey: ['auditLogsUsers'], queryFn: fetchAllUsers, ...queryOptions }, + ]); + + const { data: users, isLoading: isLoadingUsers } = userData; + const { data: auditLogs, isLoadingAuditLogs } = auditLogsData; + + const isLoading = isLoadingAuditLogs || isLoadingUsers; + + return { auditLogs, users: users?.data, isLoading }; +}; + +export default useAuditLogsData; diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js index dba8e591a1..9d364d0f4d 100644 --- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js +++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js @@ -4,53 +4,54 @@ import { SettingsPageTitle, DynamicTable, useRBAC, - useNotification, useFocusWhenNavigate, - useFetchClient, useQueryParams, } from '@strapi/helper-plugin'; -import { HeaderLayout, ContentLayout } from '@strapi/design-system/Layout'; +import { HeaderLayout, ContentLayout, ActionLayout } from '@strapi/design-system/Layout'; import { Main } from '@strapi/design-system/Main'; -import { useLocation } from 'react-router-dom'; -import { useQuery } from 'react-query'; import adminPermissions from '../../../../../../../admin/src/permissions'; import TableRows from './TableRows'; import tableHeaders from './utils/tableHeaders'; import PaginationFooter from './PaginationFooter'; import Modal from './Modal'; +import Filters from '../../../../../../../admin/src/pages/SettingsPage/components/Filters'; +import getDisplayedFilters from './utils/getDisplayedFilters'; +import getDefaultMessage, { actionTypes } from './utils/getActionTypesDefaultMessages'; +import useAuditLogsData from './hooks/useAuditLogsData'; const ListView = () => { const { formatMessage } = useIntl(); - const toggleNotification = useNotification(); const { allowedActions: { canRead }, } = useRBAC(adminPermissions.settings.auditLogs); - const { get } = useFetchClient(); - const { search } = useLocation(); const [{ query }, setQuery] = useQueryParams(); + const { auditLogs, users, isLoading } = useAuditLogsData({ canRead }); useFocusWhenNavigate(); - const fetchAuditLogsPage = async ({ queryKey }) => { - const search = queryKey[1]; - const { data } = await get(`/admin/audit-logs${search}`); - - return data; - }; - - const { data, isLoading } = useQuery(['auditLogs', search], fetchAuditLogsPage, { - enabled: canRead, - keepPreviousData: true, - retry: false, - staleTime: 1000 * 10, - onError() { - toggleNotification({ - type: 'warning', - message: { id: 'notification.error', defaultMessage: 'An error occured' }, - }); - }, + const actionOptions = Object.keys(actionTypes).map((action) => { + return { + label: formatMessage( + { + id: `Settings.permissions.auditLogs.${action}`, + defaultMessage: getDefaultMessage(action), + }, + { model: '' } + ), + customValue: action, + }; }); + const userOptions = users?.results.map((user) => { + return { + label: `${user.firstname} ${user.lastname}`, + // Combobox expects a string value + customValue: user.id.toString(), + }; + }); + + const displayedFilters = getDisplayedFilters({ actionOptions, userOptions }); + const title = formatMessage({ id: 'global.auditLogs', defaultMessage: 'Audit Logs', @@ -74,21 +75,22 @@ const ListView = () => { defaultMessage: 'Logs of all the activities that happened in your environment', })} /> + } /> setQuery({ id })} /> - + {query?.id && setQuery({ id: null }, 'remove')} logId={query.id} />} diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getActionTypesDefaultMessages.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getActionTypesDefaultMessages.js index 7539bcec57..22c1808711 100644 --- a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getActionTypesDefaultMessages.js +++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getActionTypesDefaultMessages.js @@ -1,9 +1,9 @@ -const actionTypes = { - 'entry.create': 'Create entry ({model})', - 'entry.update': 'Update entry ({model})', - 'entry.delete': 'Delete entry ({model})', - 'entry.publish': 'Publish entry ({model})', - 'entry.unpublish': 'Unpublish entry ({model})', +export const actionTypes = { + 'entry.create': 'Create entry {model}', + 'entry.update': 'Update entry {model}', + 'entry.delete': 'Delete entry {model}', + 'entry.publish': 'Publish entry {model}', + 'entry.unpublish': 'Unpublish entry {model}', 'media.create': 'Create media', 'media.update': 'Update media', 'media.delete': 'Delete media', diff --git a/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js new file mode 100644 index 0000000000..5fadfe392c --- /dev/null +++ b/packages/core/admin/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js @@ -0,0 +1,60 @@ +import ComboboxFilter from '../Filters/ComboboxFilter'; + +const customOperators = [ + { + intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$eq', defaultMessage: 'is' }, + value: '$eq', + }, + { + intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$ne', defaultMessage: 'is not' }, + value: '$ne', + }, +]; + +const getDisplayedFilters = ({ actionOptions, userOptions }) => { + return [ + { + name: 'action', + metadatas: { + label: 'Action', + options: actionOptions, + customOperators, + customInput: ComboboxFilter, + }, + fieldSchema: { type: 'enumeration' }, + }, + { + name: 'user', + metadatas: { + label: 'User', + options: userOptions, + customOperators: [ + ...customOperators, + { + intlLabel: { + id: 'components.FilterOptions.FILTER_TYPES.$null', + defaultMessage: 'is null', + }, + value: '$null', + }, + { + intlLabel: { + id: 'components.FilterOptions.FILTER_TYPES.$notNull', + defaultMessage: 'is not null', + }, + value: '$notNull', + }, + ], + customInput: ComboboxFilter, + }, + fieldSchema: { type: 'relation', mainField: { name: 'id', schema: { type: 'integer' } } }, + }, + { + name: 'date', + metadatas: { label: 'Date' }, + fieldSchema: { type: 'datetime' }, + }, + ]; +}; + +export default getDisplayedFilters; diff --git a/packages/core/helper-plugin/lib/src/components/FilterListURLQuery/AttributeTag.js b/packages/core/helper-plugin/lib/src/components/FilterListURLQuery/AttributeTag.js index 53cf633680..3a57c44243 100644 --- a/packages/core/helper-plugin/lib/src/components/FilterListURLQuery/AttributeTag.js +++ b/packages/core/helper-plugin/lib/src/components/FilterListURLQuery/AttributeTag.js @@ -42,6 +42,18 @@ const AttributeTag = ({ attribute, filter, onClick, operator, value }) => { formattedValue = formatNumber(value); } + // Handle custom input + if (attribute.metadatas.customInput) { + // If options, get the option label + if (attribute.metadatas.options) { + const selectedOption = attribute.metadatas.options.find((option) => { + return option.customValue === value; + }); + // Set the provided option label or fallback to the value from query + formattedValue = selectedOption?.label || value; + } + } + const content = `${attribute.metadatas.label || attribute.name} ${formatMessage({ id: `components.FilterOptions.FILTER_TYPES.${operator}`, defaultMessage: operator, @@ -60,7 +72,11 @@ AttributeTag.propTypes = { attribute: PropTypes.shape({ name: PropTypes.string.isRequired, fieldSchema: PropTypes.object.isRequired, - metadatas: PropTypes.shape({ label: PropTypes.string.isRequired }).isRequired, + metadatas: PropTypes.shape({ + label: PropTypes.string.isRequired, + options: PropTypes.array, + customInput: PropTypes.func, + }).isRequired, }).isRequired, filter: PropTypes.object.isRequired, onClick: PropTypes.func.isRequired, diff --git a/packages/core/helper-plugin/lib/src/components/FilterPopoverURLQuery/index.js b/packages/core/helper-plugin/lib/src/components/FilterPopoverURLQuery/index.js index e90f3fa85f..93484cecf7 100644 --- a/packages/core/helper-plugin/lib/src/components/FilterPopoverURLQuery/index.js +++ b/packages/core/helper-plugin/lib/src/components/FilterPopoverURLQuery/index.js @@ -50,7 +50,7 @@ const FilterPopoverURLQuery = ({ displayedFilters, isVisible, onBlur, onToggle, } if (type === 'enumeration') { - filterValue = options[0]; + filterValue = options && options[0]; } const filter = getFilterList(nextField)[0].value; @@ -113,6 +113,8 @@ const FilterPopoverURLQuery = ({ displayedFilters, isVisible, onBlur, onToggle, const appliedFilter = displayedFilters.find((filter) => filter.name === modifiedData.name); const operator = modifiedData.filter; + const filterList = appliedFilter.metadatas.customOperators || getFilterList(appliedFilter); + const CustomInput = appliedFilter.metadatas.customInput; return ( @@ -150,7 +152,7 @@ const FilterPopoverURLQuery = ({ displayedFilters, isVisible, onBlur, onToggle, value={modifiedData.filter} onChange={handleChangeOperator} > - {getFilterList(appliedFilter).map((option) => { + {filterList.map((option) => { return (