diff --git a/assets/.eslintrc.js b/assets/.eslintrc.js
index 95080d8bd8..d98c1fc48b 100644
--- a/assets/.eslintrc.js
+++ b/assets/.eslintrc.js
@@ -6,6 +6,9 @@ module.exports = {
jquery: true,
'jest/globals': true,
},
+ globals: {
+ process: true,
+ },
extends: ['eslint:recommended', 'plugin:react/recommended'],
parserOptions: {
ecmaFeatures: {
diff --git a/assets/js/components/ChecksCatalog/CatalogContainer.jsx b/assets/js/components/ChecksCatalog/CatalogContainer.jsx
new file mode 100644
index 0000000000..9c7f72fc5b
--- /dev/null
+++ b/assets/js/components/ChecksCatalog/CatalogContainer.jsx
@@ -0,0 +1,75 @@
+import React from 'react';
+
+import NotificationBox from '@components/NotificationBox';
+import LoadingBox from '@components/LoadingBox';
+
+import { groupBy } from '@lib/lists';
+
+import { EOS_ERROR } from 'eos-icons-react';
+
+import CheckItem from './CheckItem';
+
+const CatalogContainer = ({
+ onRefresh = () => {},
+ catalogData = [],
+ catalogError = null,
+ loading = false,
+}) => {
+ if (loading) {
+ return ;
+ }
+
+ if (catalogError) {
+ return (
+ }
+ text={catalogError}
+ buttonText="Try again"
+ buttonOnClick={onRefresh}
+ />
+ );
+ }
+
+ if (catalogData.length === 0) {
+ return (
+ }
+ text="Checks catalog is empty."
+ buttonText="Try again"
+ buttonOnClick={onRefresh}
+ />
+ );
+ }
+
+ return (
+
+ {Object.entries(groupBy(catalogData, 'group')).map(
+ ([group, checks], idx) => (
+
+
+
+ {group}
+
+
+
+ {checks.map((check) => (
+
+ ))}
+
+
+ )
+ )}
+
+ );
+};
+
+export default CatalogContainer;
diff --git a/assets/js/components/ChecksCatalog/CatalogContainer.test.jsx b/assets/js/components/ChecksCatalog/CatalogContainer.test.jsx
new file mode 100644
index 0000000000..39428f280b
--- /dev/null
+++ b/assets/js/components/ChecksCatalog/CatalogContainer.test.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+
+import { screen, within } from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+import { faker } from '@faker-js/faker';
+import { renderWithRouter } from '@lib/test-utils';
+import { catalogCheckFactory } from '@lib/test-utils/factories';
+
+import CatalogContainer from './CatalogContainer';
+
+describe('ChecksCatalog CatalogContainer component', () => {
+ it('should render the notification box', () => {
+ renderWithRouter();
+
+ expect(screen.getByText('some error')).toBeVisible();
+ expect(screen.getByRole('button')).toHaveTextContent('Try again');
+ });
+
+ it('should render the loading box', () => {
+ renderWithRouter();
+
+ expect(screen.getByText('Loading checks catalog...')).toBeVisible();
+ });
+
+ it('should render an error message if the checks catalog is empty', () => {
+ renderWithRouter();
+
+ expect(screen.getByText('Checks catalog is empty.')).toBeVisible();
+ expect(screen.getByRole('button')).toHaveTextContent('Try again');
+ });
+
+ it('should render the checks catalog', () => {
+ const groupName1 = faker.animal.cat();
+ const groupName2 = faker.animal.cat();
+ const group1 = catalogCheckFactory.buildList(5, { group: groupName1 });
+ const group2 = catalogCheckFactory.buildList(5, { group: groupName2 });
+ const catalog = group1.concat(group2);
+
+ renderWithRouter(
+
+ );
+
+ const groups = screen.getAllByRole('list');
+ expect(groups.length).toBe(2);
+
+ for (let group of groups) {
+ let { getAllByRole } = within(group);
+ let checks = getAllByRole('listitem');
+ expect(checks.length).toBe(5);
+ }
+ });
+});
diff --git a/assets/js/components/ChecksCatalog/CheckItem.jsx b/assets/js/components/ChecksCatalog/CheckItem.jsx
new file mode 100644
index 0000000000..0f274815c6
--- /dev/null
+++ b/assets/js/components/ChecksCatalog/CheckItem.jsx
@@ -0,0 +1,62 @@
+import React from 'react';
+
+import { Disclosure, Transition } from '@headlessui/react';
+
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+
+const CheckItem = ({ checkID, premium = false, description, remediation }) => {
+ return (
+
+
+
+
+
+
+ {checkID}
+
+ {premium > 0 && (
+
+ Premium
+
+ )}
+
+
+
+
+ {description}
+
+
+
+
+
+
+
+
+
+
+ {remediation}
+
+
+
+
+
+
+
+ );
+};
+
+export default CheckItem;
diff --git a/assets/js/components/ChecksCatalog/CheckItem.test.jsx b/assets/js/components/ChecksCatalog/CheckItem.test.jsx
new file mode 100644
index 0000000000..f13440178b
--- /dev/null
+++ b/assets/js/components/ChecksCatalog/CheckItem.test.jsx
@@ -0,0 +1,66 @@
+import React from 'react';
+
+import { screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import userEvent from '@testing-library/user-event';
+
+import { renderWithRouter } from '@lib/test-utils';
+import { catalogCheckFactory } from '@lib/test-utils/factories';
+
+import CheckItem from './CheckItem';
+
+describe('ChecksCatalog CheckItem component', () => {
+ it('should show check information', () => {
+ const check = catalogCheckFactory.build();
+
+ renderWithRouter(
+
+ );
+
+ expect(screen.getByText(check.id)).toBeVisible();
+ expect(screen.getByText(check.description)).toBeVisible();
+ });
+
+ it('should show premium badge if the check is premium', () => {
+ const check = catalogCheckFactory.build();
+
+ renderWithRouter(
+
+ );
+
+ expect(screen.getByText('Premium')).toBeVisible();
+ });
+
+ it('should show check remediation when the row is clicked', () => {
+ const check = catalogCheckFactory.build();
+
+ renderWithRouter(
+
+ );
+
+ const checks = screen.getAllByRole('listitem');
+ const checkDiv = checks[0].querySelector('div');
+
+ expect(screen.queryByText(check.remediation)).not.toBeInTheDocument();
+ userEvent.click(checkDiv);
+ expect(screen.getByText(check.remediation)).toBeVisible();
+ userEvent.click(checkDiv);
+ expect(screen.queryByText(check.remediation)).not.toBeInTheDocument();
+ });
+});
diff --git a/assets/js/components/ChecksCatalog/ChecksCatalogNew.jsx b/assets/js/components/ChecksCatalog/ChecksCatalogNew.jsx
new file mode 100644
index 0000000000..bd2ec58885
--- /dev/null
+++ b/assets/js/components/ChecksCatalog/ChecksCatalogNew.jsx
@@ -0,0 +1,41 @@
+import React, { useState, useEffect } from 'react';
+
+import axios from 'axios';
+
+import CatalogContainer from './CatalogContainer';
+
+const wandaURL = process.env.WANDA_URL;
+
+export const ChecksCatalogNew = () => {
+ const [catalogError, setError] = useState(null);
+ const [loading, setLoaded] = useState(true);
+ const [catalogData, setCatalog] = useState([]);
+
+ useEffect(() => {
+ getCatalog();
+ }, []);
+
+ const getCatalog = () => {
+ setLoaded(true);
+ axios
+ .get(`${wandaURL}/api/checks/catalog`)
+ .then((catalog) => {
+ setCatalog(catalog.data.items);
+ })
+ .catch((error) => {
+ setError(error.message);
+ })
+ .finally(() => {
+ setLoaded(false);
+ });
+ };
+
+ return (
+ getCatalog()}
+ catalogData={catalogData}
+ catalogError={catalogError}
+ loading={loading}
+ />
+ );
+};
diff --git a/assets/js/components/ChecksCatalog/index.js b/assets/js/components/ChecksCatalog/index.js
index e2133b19cf..28776e7e6b 100644
--- a/assets/js/components/ChecksCatalog/index.js
+++ b/assets/js/components/ChecksCatalog/index.js
@@ -1,3 +1,5 @@
import ChecksCatalog from './ChecksCatalog';
+export { ChecksCatalogNew } from './ChecksCatalogNew';
+
export default ChecksCatalog;
diff --git a/assets/js/lib/test-utils/factories.js b/assets/js/lib/test-utils/factories.js
index b9841c65a9..2bf400327c 100644
--- a/assets/js/lib/test-utils/factories.js
+++ b/assets/js/lib/test-utils/factories.js
@@ -27,3 +27,11 @@ export const healthSummaryFactory = Factory.define(() => ({
casing: 'upper',
}),
}));
+
+export const catalogCheckFactory = Factory.define(() => ({
+ id: faker.datatype.uuid(),
+ name: faker.animal.cat(),
+ group: faker.animal.cat(),
+ description: faker.lorem.paragraph(),
+ remediation: faker.lorem.paragraph(),
+}));
diff --git a/assets/js/trento.jsx b/assets/js/trento.jsx
index 85904fc9d6..96a1588fff 100644
--- a/assets/js/trento.jsx
+++ b/assets/js/trento.jsx
@@ -23,7 +23,7 @@ import ClusterDetails from '@components/ClusterDetails';
import DatabasesOverview from '@components/DatabasesOverview';
import SapSystemDetails from './components/SapSystemDetails/SapSystemDetails';
import DatabaseDetails from './components/DatabaseDetails';
-import ChecksCatalog from '@components/ChecksCatalog';
+import ChecksCatalog, { ChecksCatalogNew } from '@components/ChecksCatalog';
import NotFound from '@components/NotFound';
import SomethingWentWrong from '@components/SomethingWentWrong';
import Settings from '@components/Settings';
@@ -49,6 +49,7 @@ const App = () => {
} />
} />
} />
+ } />
} />
} />