From 0578ab5021cbf977664ba4fe3b289e31055b67f6 Mon Sep 17 00:00:00 2001 From: MariaAga Date: Tue, 1 Jul 2025 05:35:43 -0400 Subject: [PATCH 1/4] add Scalprum context wrapper --- .eslintrc | 8 +- package.json | 4 + .../common/ScalprumModule/ScalprumContext.js | 115 ++++++++++++++++++ 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 webpack/common/ScalprumModule/ScalprumContext.js diff --git a/.eslintrc b/.eslintrc index a632094ee..10e128828 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,6 +14,7 @@ "advisor", "ansible", "camelcase", + "cdn", "checkbox", "csrf", "dropdown", @@ -22,6 +23,7 @@ "href", "ips", "ipv4", + "dropdown", "jed", "katello", "knowledgebase", @@ -30,15 +32,15 @@ "nowrap", "pid", "redhat", + "redux", "remediate", "remediations", "repo", "rhc", + "scalprum", "theforeman", "tooltip", - "unmount", - "redux", - "dropdown" + "unmount" ], "minLength": 3 } diff --git a/package.json b/package.json index 43479e9e8..f2be22cd4 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,10 @@ "peerDependencies": { "@theforeman/vendor": ">= 15.0.1" }, + "dependencies": { + "@scalprum/react-core": "^0.9.3", + "@scalprum/core": "^0.8.1" + }, "devDependencies": { "@babel/core": "^7.7.0", "@theforeman/builder": ">= 15.0.1", diff --git a/webpack/common/ScalprumModule/ScalprumContext.js b/webpack/common/ScalprumModule/ScalprumContext.js new file mode 100644 index 000000000..80fcec3a1 --- /dev/null +++ b/webpack/common/ScalprumModule/ScalprumContext.js @@ -0,0 +1,115 @@ +import React, { useState, createContext, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { ScalprumProvider } from '@scalprum/react-core'; + +export const ScalprumContext = createContext(null); + +/* +Wrap your component with ScalprumContextWrapper: + + + +inside the WrappedComponent (not the component that loads the wrapper), use useEffect and setConfig: + const { config, setConfig } = React.useContext(ScalprumContext); + const path = 'apps/landing' + const scope = 'landing' + const module = './AnsibleWidget' + const newConfig = { + [scope]: { + name: scope, + manifestLocation: `${window.location.origin}/scalprum/${cdnPath}/fed-mods.json`, + cdnPath: `${window.location.origin}/scalprum/${cdnPath}/`, + }, + }; + useEffect(() => { + setConfig(newConfig) + },[]) + + Load ScalprumComponent conditionally to the config being set + {config[scope] && ( + + )} +*/ + +export const ScalprumContextWrapper = ({ children }) => { + const [config, setConfig] = useState({}); + const contextData = { + config, + setConfig: useCallback( + newConfig => setConfig(prev => ({ ...prev, ...newConfig })), + [] + ), + }; + + const mockUser = { + entitlements: {}, + identity: { + account_number: 'string', + org_id: 'string', + internal: { + org_id: 'string', + account_id: 'string', + }, + type: 'string', + user: { + username: 'string', + email: 'string', + first_name: 'string', + last_name: 'string', + is_active: 'boolean', + is_internal: 'boolean', + is_org_admin: 'boolean', + locale: 'string', + }, + }, + }; + if (Object.keys(config).length) + return ( + + { + if ( + manifest.baseURL === 'auto' && + config[manifest.name]?.cdnPath + ) { + const _cdnPath = config[manifest.name]?.cdnPath; + return { + ...manifest, + baseURL: _cdnPath, + loadScripts: manifest.loadScripts.map( + script => `${_cdnPath}${script}` + ), + }; + } + return manifest; + }, + }, + }} + api={{ + chrome: { + isBeta: () => false, + on: () => {}, + auth: { + getUser: () => Promise.resolve(mockUser), + }, + }, + }} + config={config} + > + {children} + + + ); + + return ( + + {children} + + ); +}; + +ScalprumContextWrapper.propTypes = { + children: PropTypes.node.isRequired, +}; From daddd32e7f6d3fccbcd81225382aea9896658194 Mon Sep 17 00:00:00 2001 From: nofaralfasi Date: Tue, 15 Jul 2025 09:29:57 -0400 Subject: [PATCH 2/4] Integrate SystemDetailTable component into Host Details CVEs tab --- .../CVEsHostDetailsTab/CVEsHostDetailsTab.js | 36 +++++++++++++------ webpack/CVEsHostDetailsTab/index.js | 4 +-- webpack/ForemanRhCloudFills.js | 5 +-- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js b/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js index 3241c5c9f..811acb9fc 100644 --- a/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +++ b/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js @@ -1,17 +1,31 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { translate as __ } from 'foremanReact/common/I18n'; +import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout'; +import { ScalprumComponent } from '@scalprum/react-core'; +import { ScalprumContextWrapper } from '../common/ScalprumModule/ScalprumContext'; -const CVEsHostDetailsTab = ({ hostName }) => ( -
-

- {__('CVEs tab for host:')} {hostName} -

-
-); +const CVEsHostDetailsTab = () => { + const scope = 'vulnerability'; + const module = './SystemDetailTable'; + return ( +
+ -CVEsHostDetailsTab.propTypes = { - hostName: PropTypes.string.isRequired, + +
+

+ This page is under development. Please check back soon for updates. +

+
+
+
+ ); }; -export default CVEsHostDetailsTab; +const CVEsHostDetailsTabWrapper = () => ( + + + +); + +export default CVEsHostDetailsTabWrapper; diff --git a/webpack/CVEsHostDetailsTab/index.js b/webpack/CVEsHostDetailsTab/index.js index e67c6a77e..401a6f39b 100644 --- a/webpack/CVEsHostDetailsTab/index.js +++ b/webpack/CVEsHostDetailsTab/index.js @@ -1,3 +1,3 @@ -import CVEsHostDetailsTab from './CVEsHostDetailsTab'; +import CVEsHostDetailsTabWrapper from './CVEsHostDetailsTab'; -export default CVEsHostDetailsTab; +export default CVEsHostDetailsTabWrapper; diff --git a/webpack/ForemanRhCloudFills.js b/webpack/ForemanRhCloudFills.js index a64ff9217..3596c4841 100644 --- a/webpack/ForemanRhCloudFills.js +++ b/webpack/ForemanRhCloudFills.js @@ -5,7 +5,8 @@ import InventoryAutoUploadSwitcher from './ForemanInventoryUpload/SubscriptionsP import NewHostDetailsTab from './InsightsHostDetailsTab/NewHostDetailsTab'; import { InsightsTotalRiskChartWrapper } from './InsightsHostDetailsTab/InsightsTotalRiskChartWrapper'; import { isNotRhelHost, vulnerabilityDisabled } from './ForemanRhCloudHelpers'; -import CVEsHostDetailsTab from './CVEsHostDetailsTab/CVEsHostDetailsTab'; +import CVEsHostDetailsTabWrapper from './CVEsHostDetailsTab/CVEsHostDetailsTab'; + const fills = [ { @@ -33,7 +34,7 @@ const fills = [ { slot: 'host-details-page-tabs', name: 'CVEs', - component: props => , + component: props => , weight: 300, metadata: { hideTab: vulnerabilityDisabled, From 9d92d4de45efbadb4168aec0a3816f645edaf37a Mon Sep 17 00:00:00 2001 From: Shimon Shtein Date: Wed, 16 Jul 2025 15:55:31 -0400 Subject: [PATCH 3/4] Update to the latest requirements --- .eslintrc | 1 + .../CVEsHostDetailsTab/CVEsHostDetailsTab.js | 44 +++-- .../__tests__/CVEsHostDetailsTab.test.js | 29 ++-- webpack/ForemanRhCloudFills.js | 2 +- webpack/ForemanRhCloudPages.js | 9 +- .../InsightsVulnerability.js | 13 -- .../InsightsVulnerability.test.js | 18 -- .../InsightsVulnerabilityListPage.js | 21 +++ .../InsightsVulnerabilityListPage.test.js | 20 +++ .../common/ScalprumModule/ScalprumContext.js | 164 ++++++------------ 10 files changed, 148 insertions(+), 173 deletions(-) delete mode 100644 webpack/InsightsVulnerability/InsightsVulnerability.js delete mode 100644 webpack/InsightsVulnerability/InsightsVulnerability.test.js create mode 100644 webpack/InsightsVulnerability/InsightsVulnerabilityListPage.js create mode 100644 webpack/InsightsVulnerability/InsightsVulnerabilityListPage.test.js diff --git a/.eslintrc b/.eslintrc index 10e128828..99cdfd21c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,7 @@ "cdn", "checkbox", "csrf", + "cve", "dropdown", "donut", "hostnames", diff --git a/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js b/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js index 811acb9fc..e719d5f7d 100644 --- a/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +++ b/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js @@ -1,31 +1,37 @@ import React from 'react'; -import { translate as __ } from 'foremanReact/common/I18n'; -import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout'; -import { ScalprumComponent } from '@scalprum/react-core'; -import { ScalprumContextWrapper } from '../common/ScalprumModule/ScalprumContext'; +import PropTypes from 'prop-types'; +import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core'; +import { providerOptions } from '../common/ScalprumModule/ScalprumContext'; -const CVEsHostDetailsTab = () => { +const CVEsHostDetailsTab = ({ systemId }) => { const scope = 'vulnerability'; const module = './SystemDetailTable'; return ( -
- - - -
-

- This page is under development. Please check back soon for updates. -

-
-
+
+
); }; -const CVEsHostDetailsTabWrapper = () => ( - - - +CVEsHostDetailsTab.propTypes = { + systemId: PropTypes.string.isRequired, +}; + +const CVEsHostDetailsTabWrapper = ({ response }) => ( + + + ); +CVEsHostDetailsTabWrapper.propTypes = { + response: PropTypes.shape({ + subscription_facet_attributes: PropTypes.shape({ + uuid: PropTypes.string.isRequired, + }), + }).isRequired, +}; + export default CVEsHostDetailsTabWrapper; diff --git a/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js b/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js index 42ebf8ee4..be22e2b08 100644 --- a/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js +++ b/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js @@ -1,18 +1,25 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; -import CVEsHostDetailsTab from '../CVEsHostDetailsTab'; +import { render } from '@testing-library/react'; +import CVEsHostDetailsTabWrapper from '../CVEsHostDetailsTab'; -describe('CVEsHostDetailsTab', () => { +jest.mock('@scalprum/react-core', () => ({ + ScalprumComponent: jest.fn(props => ( +
{JSON.stringify(props)}
+ )), + ScalprumProvider: jest.fn(({ children }) =>
{children}
), +})); + +describe('CVEsHostDetailsTabWrapper', () => { it('renders without crashing', () => { - render(); + const { container } = render( + + ); expect( - screen.getByText('CVEs tab for host: test-host.example.com') + container.querySelector( + '.rh-cloud-insights-vulnerability-host-details-component' + ) ).toBeTruthy(); }); - - it('renders the host name', () => { - const hostName = 'test-host.example.com'; - render(); - expect(screen.getByText(`CVEs tab for host: ${hostName}`)).toBeTruthy(); - }); }); diff --git a/webpack/ForemanRhCloudFills.js b/webpack/ForemanRhCloudFills.js index 3596c4841..cefc6178d 100644 --- a/webpack/ForemanRhCloudFills.js +++ b/webpack/ForemanRhCloudFills.js @@ -7,7 +7,6 @@ import { InsightsTotalRiskChartWrapper } from './InsightsHostDetailsTab/Insights import { isNotRhelHost, vulnerabilityDisabled } from './ForemanRhCloudHelpers'; import CVEsHostDetailsTabWrapper from './CVEsHostDetailsTab/CVEsHostDetailsTab'; - const fills = [ { slot: 'katello-manage-manifest-form', @@ -38,6 +37,7 @@ const fills = [ weight: 300, metadata: { hideTab: vulnerabilityDisabled, + title: __('Vulnerability'), }, }, ]; diff --git a/webpack/ForemanRhCloudPages.js b/webpack/ForemanRhCloudPages.js index 988442afb..2cb3512cc 100644 --- a/webpack/ForemanRhCloudPages.js +++ b/webpack/ForemanRhCloudPages.js @@ -2,7 +2,7 @@ import React from 'react'; import componentRegistry from 'foremanReact/components/componentRegistry'; import { registerRoutes as foremanRegisterRoutes } from 'foremanReact/routes/RoutingService'; import ForemanInventoryUpload from './ForemanInventoryUpload'; -import InsightsVulnerability from './InsightsVulnerability/InsightsVulnerability'; +import InsightsVulnerabilityListPage from './InsightsVulnerability/InsightsVulnerabilityListPage'; import InsightsCloudSync from './InsightsCloudSync'; import InsightsHostDetailsTab from './InsightsHostDetailsTab'; @@ -10,7 +10,10 @@ const pages = [ { name: 'ForemanInventoryUpload', type: ForemanInventoryUpload }, { name: 'InsightsCloudSync', type: InsightsCloudSync }, { name: 'InsightsHostDetailsTab', type: InsightsHostDetailsTab }, - { name: 'InsightsVulnerability', type: InsightsVulnerability }, + { + name: 'InsightsVulnerabilityListPage', + type: InsightsVulnerabilityListPage, + }, ]; export const registerPages = () => { @@ -31,7 +34,7 @@ export const routes = [ { path: '/foreman_rh_cloud/insights_vulnerability', exact: true, - render: props => , + render: props => , }, ]; diff --git a/webpack/InsightsVulnerability/InsightsVulnerability.js b/webpack/InsightsVulnerability/InsightsVulnerability.js deleted file mode 100644 index a77325dd4..000000000 --- a/webpack/InsightsVulnerability/InsightsVulnerability.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout'; -import { translate as __ } from 'foremanReact/common/I18n'; - -const InsightsVulnerability = () => ( - -
-

This page is under development. Please check back soon for updates.

-
-
-); - -export default InsightsVulnerability; diff --git a/webpack/InsightsVulnerability/InsightsVulnerability.test.js b/webpack/InsightsVulnerability/InsightsVulnerability.test.js deleted file mode 100644 index 479448c45..000000000 --- a/webpack/InsightsVulnerability/InsightsVulnerability.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import InsightsVulnerability from './InsightsVulnerability'; - -describe('InsightsVulnerability component', () => { - it('renders the "under development" message', () => { - render(); - expect( - screen.getByText(/this page is under development/i) - ).toBeInTheDocument(); - }); - - it('renders the container with correct class', () => { - const { container } = render(); - expect(container.querySelector('.insights-vulnerability')).toBeTruthy(); - }); -}); diff --git a/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.js b/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.js new file mode 100644 index 000000000..fc17c3979 --- /dev/null +++ b/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core'; +import { providerOptions } from '../common/ScalprumModule/ScalprumContext'; + +const InsightsVulnerabilityListPage = () => { + const scope = 'vulnerability'; + const module = './CveListPage'; + return ( +
+ +
+ ); +}; + +const InsightsVulnerabilityListPageWrap = () => ( + + + +); + +export default InsightsVulnerabilityListPageWrap; diff --git a/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.test.js b/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.test.js new file mode 100644 index 000000000..39aecb720 --- /dev/null +++ b/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.test.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import InsightsVulnerabilityListPage from './InsightsVulnerabilityListPage'; + +jest.mock('@scalprum/react-core', () => ({ + ScalprumComponent: jest.fn(props => ( +
{JSON.stringify(props)}
+ )), + ScalprumProvider: jest.fn(({ children }) =>
{children}
), +})); + +describe('InsightsVulnerabilityListPage component', () => { + it('renders the container with correct class', () => { + const { container } = render(); + expect( + container.querySelector('.rh-cloud-insights-vulnerability-page') + ).toBeTruthy(); + }); +}); diff --git a/webpack/common/ScalprumModule/ScalprumContext.js b/webpack/common/ScalprumModule/ScalprumContext.js index 80fcec3a1..77829c2d6 100644 --- a/webpack/common/ScalprumModule/ScalprumContext.js +++ b/webpack/common/ScalprumModule/ScalprumContext.js @@ -1,115 +1,63 @@ -import React, { useState, createContext, useCallback } from 'react'; -import PropTypes from 'prop-types'; -import { ScalprumProvider } from '@scalprum/react-core'; - -export const ScalprumContext = createContext(null); +export const modulesConfig = { + vulnerability: { + name: 'vulnerability', + manifestLocation: `${window.location.origin}/assets/apps/vulnerability/fed-mods.json`, + cdnPath: `${window.location.origin}/assets/apps/vulnerability/`, + }, +}; -/* -Wrap your component with ScalprumContextWrapper: - - - -inside the WrappedComponent (not the component that loads the wrapper), use useEffect and setConfig: - const { config, setConfig } = React.useContext(ScalprumContext); - const path = 'apps/landing' - const scope = 'landing' - const module = './AnsibleWidget' - const newConfig = { - [scope]: { - name: scope, - manifestLocation: `${window.location.origin}/scalprum/${cdnPath}/fed-mods.json`, - cdnPath: `${window.location.origin}/scalprum/${cdnPath}/`, +export const mockUser = { + entitlements: {}, + identity: { + account_number: 'string', + org_id: 'FOREMAN', + internal: { + org_id: 'string', + account_id: 'string', }, - }; - useEffect(() => { - setConfig(newConfig) - },[]) - - Load ScalprumComponent conditionally to the config being set - {config[scope] && ( - - )} -*/ - -export const ScalprumContextWrapper = ({ children }) => { - const [config, setConfig] = useState({}); - const contextData = { - config, - setConfig: useCallback( - newConfig => setConfig(prev => ({ ...prev, ...newConfig })), - [] - ), - }; + type: 'string', + user: { + username: 'string', + email: 'string', + first_name: 'string', + last_name: 'string', + is_active: 'boolean', + is_internal: 'boolean', + is_org_admin: 'boolean', + locale: 'string', + }, + }, +}; - const mockUser = { - entitlements: {}, - identity: { - account_number: 'string', - org_id: 'string', - internal: { - org_id: 'string', - account_id: 'string', +export const providerOptions = { + pluginSDKOptions: { + pluginLoaderOptions: { + transformPluginManifest: manifest => { + if ( + manifest.baseURL === 'auto' && + modulesConfig[manifest.name]?.cdnPath + ) { + const _cdnPath = modulesConfig[manifest.name]?.cdnPath; + return { + ...manifest, + baseURL: _cdnPath, + loadScripts: manifest.loadScripts.map( + script => `${_cdnPath}${script}` + ), + }; + } + return manifest; }, - type: 'string', - user: { - username: 'string', - email: 'string', - first_name: 'string', - last_name: 'string', - is_active: 'boolean', - is_internal: 'boolean', - is_org_admin: 'boolean', - locale: 'string', + }, + }, + api: { + chrome: { + isBeta: () => false, + on: () => {}, + auth: { + getUser: () => Promise.resolve(mockUser), }, }, - }; - if (Object.keys(config).length) - return ( - - { - if ( - manifest.baseURL === 'auto' && - config[manifest.name]?.cdnPath - ) { - const _cdnPath = config[manifest.name]?.cdnPath; - return { - ...manifest, - baseURL: _cdnPath, - loadScripts: manifest.loadScripts.map( - script => `${_cdnPath}${script}` - ), - }; - } - return manifest; - }, - }, - }} - api={{ - chrome: { - isBeta: () => false, - on: () => {}, - auth: { - getUser: () => Promise.resolve(mockUser), - }, - }, - }} - config={config} - > - {children} - - - ); - - return ( - - {children} - - ); -}; - -ScalprumContextWrapper.propTypes = { - children: PropTypes.node.isRequired, + }, + config: modulesConfig, }; From 44864b7cc505b94a7542a0d9bc9cc271d553acd3 Mon Sep 17 00:00:00 2001 From: Jeremy Lenz Date: Fri, 18 Jul 2025 10:10:36 -0400 Subject: [PATCH 4/4] Fix tab name and slot name --- webpack/ForemanRhCloudFills.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack/ForemanRhCloudFills.js b/webpack/ForemanRhCloudFills.js index cefc6178d..088908504 100644 --- a/webpack/ForemanRhCloudFills.js +++ b/webpack/ForemanRhCloudFills.js @@ -32,12 +32,12 @@ const fills = [ }, { slot: 'host-details-page-tabs', - name: 'CVEs', + name: 'Vulnerabilities', component: props => , weight: 300, metadata: { hideTab: vulnerabilityDisabled, - title: __('Vulnerability'), + title: __('Vulnerabilities'), }, }, ];