diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/ActionBody.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/ActionBody.js new file mode 100644 index 0000000000..15cdc27f51 --- /dev/null +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/ActionBody.js @@ -0,0 +1,94 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; +import { Loader } from '@strapi/design-system/Loader'; +import { Grid } from '@strapi/design-system/Grid'; +import { Box } from '@strapi/design-system/Box'; +import { Flex } from '@strapi/design-system/Flex'; +import { Typography } from '@strapi/design-system/Typography'; +import { pxToRem } from '@strapi/helper-plugin'; +import getDefaultMessage from '../utils/getActionTypesDefaultMessages'; +import ActionItem from './ActionItem'; + +const ActionBody = ({ status, data, formattedDate }) => { + const { formatMessage } = useIntl(); + + if (status === 'loading') { + return ( + + + + ); + } + + const { action, user, payload } = data; + + return ( + <> + + + {formatMessage({ + id: 'Settings.permissions.auditLogs.details', + defaultMessage: 'Log Details', + })} + + + + + + + + {/* TODO remove when adding JSON component */} + + {JSON.stringify(payload, null, 2)} + + + ); +}; + +ActionBody.defaultProps = { + data: {}, +}; + +ActionBody.propTypes = { + status: PropTypes.oneOf(['idle', 'loading', 'error', 'success']).isRequired, + data: PropTypes.shape({ + action: PropTypes.string, + date: PropTypes.string, + payload: PropTypes.object, + user: PropTypes.object, + }), + formattedDate: PropTypes.string.isRequired, +}; + +export default ActionBody; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/ActionItem.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/ActionItem.js similarity index 100% rename from packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/ActionItem.js rename to packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/ActionItem.js diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/index.js new file mode 100644 index 0000000000..51cd6b2c37 --- /dev/null +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/Modal/index.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useQuery } from 'react-query'; +import { ModalLayout, ModalHeader, ModalBody } from '@strapi/design-system/ModalLayout'; +import { Breadcrumbs, Crumb } from '@strapi/design-system/Breadcrumbs'; +import { useNotification } from '@strapi/helper-plugin'; +import useFormatTimeStamp from '../hooks/useFormatTimeStamp'; +import { useFetchClient } from '../../../../../../hooks'; +import ActionBody from './ActionBody'; + +const Modal = ({ handleClose, logId }) => { + const { get } = useFetchClient(); + const toggleNotification = useNotification(); + + const fetchAuditLog = async (id) => { + const { data } = await get(`/admin/audit-logs/${id}`); + + return data; + }; + + const { data, status } = useQuery(['audit-log', logId], () => fetchAuditLog(logId), { + onError() { + toggleNotification({ + type: 'warning', + message: { id: 'notification.error', defaultMessage: 'An error occured' }, + }); + handleClose(); + }, + }); + + const formatTimeStamp = useFormatTimeStamp(); + const formattedDate = data ? formatTimeStamp(data.date) : ''; + + return ( + + + + {formattedDate} + + + + + + + ); +}; + +Modal.propTypes = { + handleClose: PropTypes.func.isRequired, + logId: PropTypes.number.isRequired, +}; + +export default Modal; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/index.js deleted file mode 100644 index 90933ae19c..0000000000 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/index.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useIntl } from 'react-intl'; -import { ModalLayout, ModalHeader, ModalBody } from '@strapi/design-system/ModalLayout'; -import { Breadcrumbs, Crumb } from '@strapi/design-system/Breadcrumbs'; -import { Grid } from '@strapi/design-system/Grid'; -import { Box } from '@strapi/design-system/Box'; -import { Typography } from '@strapi/design-system/Typography'; -import { pxToRem } from '@strapi/helper-plugin'; -import getDefaultMessage from '../utils/getActionTypesDefaultMessages'; -import useFormatTimeStamp from '../hooks/useFormatTimeStamp'; -import ActionItem from './ActionItem'; - -const ModalDialog = ({ onToggle, data: { date, user, action } }) => { - const { formatMessage } = useIntl(); - const formatTimeStamp = useFormatTimeStamp(); - const formattedDate = formatTimeStamp(date); - - return ( - - - - {formattedDate} - - - - - - {formatMessage({ - id: 'Settings.permissions.auditLogs.details', - defaultMessage: 'Log Details', - })} - - - - - - - - - - ); -}; - -ModalDialog.propTypes = { - onToggle: PropTypes.func.isRequired, - data: PropTypes.object.isRequired, -}; - -export default ModalDialog; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js index 4264faa79f..fc49d05dc5 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/index.js @@ -10,7 +10,7 @@ import { onRowClick, stopPropagation } from '@strapi/helper-plugin'; import useFormatTimeStamp from '../hooks/useFormatTimeStamp'; import getDefaultMessage from '../utils/getActionTypesDefaultMessages'; -const TableRows = ({ headers, rows, onModalToggle }) => { +const TableRows = ({ headers, rows, onOpenModal }) => { const { formatMessage } = useIntl(); const formatTimeStamp = useFormatTimeStamp(); @@ -36,7 +36,7 @@ const TableRows = ({ headers, rows, onModalToggle }) => { onModalToggle(data.id), + fn: () => onOpenModal(data.id), })} > {headers.map(({ key, name, cellFormatter }) => { @@ -55,7 +55,7 @@ const TableRows = ({ headers, rows, onModalToggle }) => { onModalToggle(data.id)} + onClick={() => onOpenModal(data.id)} aria-label={formatMessage( { id: 'app.component.table.view', defaultMessage: '{target} details' }, { target: `${data.action} action` } @@ -79,7 +79,7 @@ TableRows.defaultProps = { TableRows.propTypes = { headers: PropTypes.array.isRequired, rows: PropTypes.array, - onModalToggle: PropTypes.func.isRequired, + onOpenModal: PropTypes.func.isRequired, }; export default TableRows; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/tests/index.test.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/tests/index.test.js index 34035848da..530737f8cd 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/tests/index.test.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/TableRows/tests/index.test.js @@ -60,14 +60,14 @@ const rows = [ }, ]; -const onModalToggle = jest.fn(); +const onModalOpen = jest.fn(); // eslint-disable-next-line react/prop-types const App = ( - + @@ -94,13 +94,13 @@ describe('ADMIN | Pages | AUDIT LOGS | ListView | Dynamic Table | Table Rows', ( const label = screen.getByText(/update action details/i); const viewDetailsButton = label.closest('button'); fireEvent.click(viewDetailsButton); - expect(onModalToggle).toHaveBeenCalled(); + expect(onModalOpen).toHaveBeenCalled(); }); it('should open a modal when clicked on a row', () => { render(App); const rows = document.querySelectorAll('tr'); fireEvent.click(rows[0]); - expect(onModalToggle).toHaveBeenCalled(); + expect(onModalOpen).toHaveBeenCalled(); }); }); diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/index.js index 2562a9257a..d986258bb1 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/index.js @@ -15,13 +15,11 @@ import adminPermissions from '../../../../../permissions'; import { useFetchClient } from '../../../../../hooks'; import TableRows from './TableRows'; import tableHeaders from './utils/tableHeaders'; -import ModalDialog from './ModalDialog'; import PaginationFooter from './PaginationFooter'; +import Modal from './Modal'; const ListView = () => { const { formatMessage } = useIntl(); - const [isModalOpen, setIsModalOpen] = useState(false); - const [detailsActionData, setDetailsActionData] = useState(null); const toggleNotification = useNotification(); const { allowedActions: { canRead }, @@ -31,14 +29,14 @@ const ListView = () => { useFocusWhenNavigate(); - const fetchData = async ({ queryKey }) => { + const fetchAuditLogsPage = async ({ queryKey }) => { const search = queryKey[1]; const { data } = await get(`/admin/audit-logs${search}`); return data; }; - const { data, isLoading } = useQuery(['auditLogs', search], fetchData, { + const { data, isLoading } = useQuery(['auditLogs', search], fetchAuditLogsPage, { enabled: canRead, keepPreviousData: true, retry: false, @@ -64,14 +62,7 @@ const ListView = () => { }, })); - const handleToggle = (id) => { - setIsModalOpen((prev) => !prev); - - if (data.results && id) { - const actionData = data.results.find((action) => action.id === id); - setDetailsActionData(actionData); - } - }; + const [modalLogId, setModalLogId] = useState(null); return (
@@ -91,11 +82,15 @@ const ListView = () => { withBulkActions isLoading={isLoading} > - + setModalLogId(id)} + /> - {isModalOpen && } + {modalLogId && setModalLogId(null)} logId={modalLogId} />}
); }; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/index.test.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/index.test.js index d0d79d2843..320dd07b5c 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/index.test.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/index.test.js @@ -2,13 +2,13 @@ import React from 'react'; import { Router } from 'react-router-dom'; import { IntlProvider } from 'react-intl'; import { createMemoryHistory } from 'history'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; import userEvent from '@testing-library/user-event'; import { ThemeProvider, lightTheme } from '@strapi/design-system'; import { TrackingProvider } from '@strapi/helper-plugin'; import ListView from '../index'; -import { TEST_DATA, getBigTestData } from './utils/data'; +import { TEST_PAGE_DATA, TEST_SINGLE_DATA, getBigTestPageData } from './utils/data'; const history = createMemoryHistory(); const user = userEvent.setup(); @@ -87,7 +87,7 @@ describe('ADMIN | Pages | AUDIT LOGS | ListView', () => { it('should show a list of audit logs with all actions', async () => { mockUseQuery.mockReturnValue({ data: { - results: TEST_DATA, + results: TEST_PAGE_DATA, }, isLoading: false, }); @@ -104,20 +104,31 @@ describe('ADMIN | Pages | AUDIT LOGS | ListView', () => { it('should open a modal when clicked on a table row and close modal when clicked', async () => { mockUseQuery.mockReturnValue({ data: { - results: TEST_DATA, + results: TEST_PAGE_DATA, }, isLoading: false, }); + render(App); - const { container } = render(App); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - const rows = container.querySelector('tbody').querySelectorAll('tr'); - await user.click(rows[0]); - expect(screen.getByRole('dialog')).toBeInTheDocument(); + mockUseQuery.mockReturnValue({ + data: TEST_SINGLE_DATA, + status: 'success', + }); - const label = screen.getByText(/close the modal/i); - const closeButton = label.closest('button'); + const auditLogRow = screen.getByText('Create role').closest('tr'); + await user.click(auditLogRow); + + const modal = screen.getByRole('dialog'); + expect(modal).toBeInTheDocument(); + + const modalContainer = within(modal); + expect(modalContainer.getByText('Create role')).toBeInTheDocument(); + expect(modalContainer.getByText('test user')).toBeInTheDocument(); + expect(modalContainer.getAllByText('December 22, 2022, 16:11:03')).toHaveLength(3); + + const closeButton = modalContainer.getByText(/close the modal/i).closest('button'); await user.click(closeButton); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); @@ -125,7 +136,7 @@ describe('ADMIN | Pages | AUDIT LOGS | ListView', () => { it('should show pagination and be on page 1 on first render', async () => { mockUseQuery.mockReturnValue({ data: { - results: getBigTestData(15), + results: getBigTestPageData(15), pagination: { page: 1, pageSize: 10, @@ -147,7 +158,7 @@ describe('ADMIN | Pages | AUDIT LOGS | ListView', () => { it('paginates the results', async () => { mockUseQuery.mockReturnValue({ data: { - results: getBigTestData(35), + results: getBigTestPageData(35), pagination: { page: 1, pageSize: 10, @@ -191,7 +202,7 @@ describe('ADMIN | Pages | AUDIT LOGS | ListView', () => { mockUseQuery.mockReturnValue({ data: { - results: getBigTestData(20), + results: getBigTestPageData(20), pagination: { page: 1, pageSize: 20, diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/utils/data.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/utils/data.js index 12fcc9559e..96964ef4d1 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/utils/data.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/tests/utils/data.js @@ -1,4 +1,4 @@ -export const TEST_DATA = [ +const TEST_PAGE_DATA = [ { id: 1, action: 'role.create', @@ -37,15 +37,31 @@ export const TEST_DATA = [ }, ]; -export const getBigTestData = (quantity) => { +const TEST_SINGLE_DATA = { + id: 1, + action: 'role.create', + date: '2022-12-22T16:11:03.126Z', + payload: { + meta: 'data', + }, + user: { + id: 1, + fullname: 'test user', + email: 'test@test.com', + }, +}; + +const getBigTestPageData = (quantity) => { const data = []; for (let i = 0; i < quantity; i++) { data.push({ - ...TEST_DATA[i % TEST_DATA.length], + ...TEST_PAGE_DATA[i % TEST_PAGE_DATA.length], id: i + 1, }); } return data; }; + +export { TEST_PAGE_DATA, TEST_SINGLE_DATA, getBigTestPageData };