foreman_rh_cloud 14.7.1 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/foreman_rh_cloud/tags_auth.rb +28 -13
  3. data/config/routes.rb +10 -0
  4. data/lib/foreman_rh_cloud/plugin.rb +14 -0
  5. data/lib/foreman_rh_cloud/version.rb +1 -1
  6. data/package.json +1 -1
  7. data/test/unit/services/foreman_rh_cloud/tags_auth_test.rb +26 -0
  8. data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.js +36 -0
  9. data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.scss +3 -0
  10. data/webpack/ComplianceHostDetailsTab/__tests__/ComplianceHostDetailsTab.test.js +159 -0
  11. data/webpack/ComplianceHostDetailsTab/index.js +3 -0
  12. data/webpack/ForemanRhCloudFills.js +11 -0
  13. data/webpack/ForemanRhCloudPages.js +7 -0
  14. data/webpack/InsightsCompliance/ComplianceIframe.js +34 -0
  15. data/webpack/InsightsCompliance/ComplianceIframe.scss +12 -0
  16. data/webpack/InsightsCompliance/InsightsCompliancePage.js +11 -0
  17. data/webpack/InsightsCompliance/InsightsCompliancePage.test.js +35 -0
  18. data/webpack/InsightsCompliance/complianceIframeConstants.js +7 -0
  19. data/webpack/InsightsCompliance/complianceIframeHelpers.js +23 -0
  20. data/webpack/InsightsCompliance/complianceIframeHelpers.test.js +50 -0
  21. data/webpack/common/Hooks/PermissionsHooks.js +10 -0
  22. data/webpack/common/IopIframe/IopAppIframe.js +74 -0
  23. data/webpack/common/IopIframe/IopHostDetailsIframe.js +66 -0
  24. data/webpack/common/IopIframe/IopHostDetailsIframe.scss +6 -0
  25. data/webpack/common/IopIframe/IopHostDetailsIframeTab.js +81 -0
  26. data/webpack/common/IopIframe/__tests__/IopAppIframe.test.js +92 -0
  27. data/webpack/common/IopIframe/__tests__/IopHostDetailsIframeTab.test.js +93 -0
  28. data/webpack/common/IopIframe/__tests__/iopIframeHelpers.test.js +63 -0
  29. data/webpack/common/IopIframe/__tests__/postIopChromeToIframe.test.js +60 -0
  30. data/webpack/common/IopIframe/__tests__/useIopIframeHeight.test.js +37 -0
  31. data/webpack/common/IopIframe/iopIframeConstants.js +5 -0
  32. data/webpack/common/IopIframe/iopIframeHelpers.js +49 -0
  33. data/webpack/common/IopIframe/postIopChromeToIframe.js +35 -0
  34. data/webpack/common/IopIframe/useIopIframeChrome.js +54 -0
  35. data/webpack/common/IopIframe/useIopIframeHeight.js +35 -0
  36. data/webpack/common/IopIframe/useIopIframeNavigate.js +40 -0
  37. metadata +27 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df573a3f57c51c056bd94b2f485ace3c6d74a461556fda782d8df21658b28467
4
- data.tar.gz: a043bffea8a13e761f1c4b411059be37fea5cf95556bb8410bd4b38e75265c6d
3
+ metadata.gz: 637a2b5c3eb9d907b2c20a3921b728efe617dd2dccb6cd1f99135784f2721bed
4
+ data.tar.gz: 2b254832b522c4c60c6f11ff8e727a890c3b0632d1df3e0abc024657ad8a0343
5
5
  SHA512:
6
- metadata.gz: d21ee9703cf4464e2ad9396c3049e42b19a38abe233a1f5fb82acea8a0ae08d254b8c99a036390bc3528c405fd82b7a00d226234d48f5df95a484458e0109dc7
7
- data.tar.gz: 52915f7c6bdd4c91c1117b7d90b27434f824129e916ba52329aedaa3ba74053346c769ca6013a967501fdf6b42c101ba40c08e8e52534dd3cb96b477acee4dd0
6
+ metadata.gz: 9e3c2a143ad9a3e7ff76ddf83440655c0fd0c558f423acc0dac4ddbb88e9245850d39f270e7f455849d58fd2e37904804a0e1611641f7f8438ca2a79e15e1e36
7
+ data.tar.gz: 22886acec69b9b059802e3ba750bb43bda9132990c4c104e07a49ed6852f37b54db2302ead5acfeb973e136ba435256437ef786e396253420e89322946d929e7
@@ -5,6 +5,7 @@ module ForemanRhCloud
5
5
  TAG_NAMESPACE = 'sat_iam'.freeze
6
6
  TAG_SHORT_NAME = 'scope'.freeze
7
7
  TAG_NAME = "#{TAG_NAMESPACE}/#{TAG_SHORT_NAME}".freeze
8
+ SYNC_CACHE_TTL = 10.seconds
8
9
 
9
10
  def self.auth_tag_for(user, org, loc)
10
11
  new(user, org, loc, nil).auth_tag
@@ -20,20 +21,28 @@ module ForemanRhCloud
20
21
  end
21
22
 
22
23
  def update_tag
23
- loc_name = location_name_for_tag
24
- logger.debug("Updating tags for user: #{@user}, org: #{@org.name}, loc: #{loc_name}")
24
+ cached = Rails.cache.read(sync_identity)
25
+ return cached if cached
25
26
 
26
- payload = tags_query_payload
27
- params = {
28
- organization: @org,
29
- method: :post,
30
- url: "#{InsightsCloud.gateway_url}/tags",
31
- headers: {
32
- content_type: :json,
33
- },
34
- payload: payload.to_json,
35
- }
36
- execute_cloud_request(params) unless payload[:host_id_list].empty?
27
+ Foreman::AdvisoryLockManager.with_session_lock(sync_identity) do
28
+ Rails.cache.fetch(sync_identity, expires_in: SYNC_CACHE_TTL) do
29
+ loc_name = location_name_for_tag
30
+ logger.debug("Updating tags for user: #{@user}, org: #{@org.name}, loc: #{loc_name}")
31
+
32
+ payload = tags_query_payload
33
+ params = {
34
+ organization: @org,
35
+ method: :post,
36
+ url: "#{InsightsCloud.gateway_url}/tags",
37
+ headers: {
38
+ content_type: :json,
39
+ },
40
+ payload: payload.to_json,
41
+ }
42
+ execute_cloud_request(params) unless payload[:host_id_list].empty?
43
+ true
44
+ end
45
+ end
37
46
  end
38
47
 
39
48
  def allowed_hosts
@@ -63,5 +72,11 @@ module ForemanRhCloud
63
72
  def location_name_for_tag
64
73
  @loc ? @loc.name : '*'
65
74
  end
75
+
76
+ # Single identity string reused as BOTH the advisory-lock name and the
77
+ # Rails.cache key, so the lock and the cached entry always correspond 1:1.
78
+ def sync_identity
79
+ "rh_cloud_tags_auth_sync_#{tag_value}"
80
+ end
66
81
  end
67
82
  end
data/config/routes.rb CHANGED
@@ -36,6 +36,16 @@ Rails.application.routes.draw do
36
36
  get 'insights_cloud', to: '/react#index' # Uses foreman's react controller
37
37
  get 'insights_vulnerability', to: '/react#index'
38
38
  get 'insights_vulnerability/:cve_id', to: '/react#index'
39
+ scope 'insights_compliance' do
40
+ get 'reports', to: '/react#index'
41
+ get 'reports/:report_id', to: '/react#index'
42
+ get 'reports/:report_id/delete', to: '/react#index'
43
+ get 'scappolicies', to: '/react#index'
44
+ get 'scappolicies/new', to: '/react#index'
45
+ get 'scappolicies/:policy_id', to: '/react#index'
46
+ get 'scappolicies/:policy_id/edit', to: '/react#index'
47
+ get 'scappolicies/:policy_id/delete', to: '/react#index'
48
+ end
39
49
  end
40
50
 
41
51
  scope :module => :'insights_cloud/api', :path => :redhat_access do
@@ -145,6 +145,20 @@ module ForemanRhCloud
145
145
  url_hash: { controller: :react, action: :index },
146
146
  parent: :insights_menu,
147
147
  if: -> { ForemanRhCloud.with_iop_smart_proxy? }
148
+ menu :top_menu,
149
+ :insights_compliance_reports,
150
+ caption: N_('Compliance Reports'),
151
+ url: '/foreman_rh_cloud/insights_compliance/reports',
152
+ url_hash: { controller: :react, action: :index },
153
+ parent: :insights_menu,
154
+ if: -> { ForemanRhCloud.with_iop_smart_proxy? }
155
+ menu :top_menu,
156
+ :insights_compliance_scappolicies,
157
+ caption: N_('SCAP Policies'),
158
+ url: '/foreman_rh_cloud/insights_compliance/scappolicies',
159
+ url_hash: { controller: :react, action: :index },
160
+ parent: :insights_menu,
161
+ if: -> { ForemanRhCloud.with_iop_smart_proxy? }
148
162
  end
149
163
 
150
164
  # In IoP case we want the page to be in the admin menu
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '14.7.1'.freeze
2
+ VERSION = '15.0.0'.freeze
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "14.7.1",
3
+ "version": "15.0.0",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -3,6 +3,7 @@ require 'json'
3
3
 
4
4
  class TagsAuthTest < ActiveSupport::TestCase
5
5
  setup do
6
+ Rails.cache.clear
6
7
  @user = FactoryBot.build(:user)
7
8
  @logger = Logger.new(IO::NULL)
8
9
  @org = FactoryBot.build(:organization)
@@ -41,6 +42,31 @@ class TagsAuthTest < ActiveSupport::TestCase
41
42
  @auth.update_tag
42
43
  end
43
44
 
45
+ test 'Does not repeat cloud request for the same user/org/location within the sync TTL' do
46
+ @auth.stubs(:allowed_hosts).returns(['test_uuid1'])
47
+ @auth.expects(:execute_cloud_request).once
48
+
49
+ @auth.update_tag
50
+ @auth.update_tag
51
+ end
52
+
53
+ test 'Serializes sync through an advisory lock on a cold cache miss' do
54
+ @auth.stubs(:allowed_hosts).returns(['test_uuid1'])
55
+ @auth.stubs(:execute_cloud_request)
56
+ Foreman::AdvisoryLockManager.expects(:with_session_lock).yields
57
+
58
+ @auth.update_tag
59
+ end
60
+
61
+ test 'Skips the advisory lock entirely on a warm cache hit' do
62
+ @auth.stubs(:allowed_hosts).returns(['test_uuid1'])
63
+ @auth.stubs(:execute_cloud_request)
64
+ @auth.update_tag
65
+
66
+ Foreman::AdvisoryLockManager.expects(:with_session_lock).never
67
+ @auth.update_tag
68
+ end
69
+
44
70
  test 'Generates tags with wildcard location when location is nil' do
45
71
  auth_with_nil_loc = ::ForemanRhCloud::TagsAuth.new(@user, @org, nil, @logger)
46
72
  uuid1 = 'test_uuid1'
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import IopHostDetailsIframeTab from '../common/IopIframe/IopHostDetailsIframeTab';
5
+ import { getComplianceIframeSrc } from '../InsightsCompliance/complianceIframeHelpers';
6
+ import { IOP_COMPLIANCE_READY } from '../InsightsCompliance/complianceIframeConstants';
7
+ import './ComplianceHostDetailsTab.scss';
8
+
9
+ const ComplianceHostDetailsTabWrapper = ({ response = {} }) => (
10
+ <IopHostDetailsIframeTab
11
+ response={response}
12
+ iframeSrc={getComplianceIframeSrc()}
13
+ buildAppRoute={inventoryId => `systems/${inventoryId}`}
14
+ title={__('Compliance')}
15
+ readyMessageType={IOP_COMPLIANCE_READY}
16
+ className="rh-cloud-insights-compliance-host-details-component compliance"
17
+ testId="compliance-host-details-iframe"
18
+ />
19
+ );
20
+
21
+ ComplianceHostDetailsTabWrapper.propTypes = {
22
+ response: PropTypes.shape({
23
+ id: PropTypes.number,
24
+ operatingsystem_name: PropTypes.string,
25
+ insights_attributes: PropTypes.object,
26
+ subscription_facet_attributes: PropTypes.shape({
27
+ uuid: PropTypes.string,
28
+ }),
29
+ }),
30
+ };
31
+
32
+ ComplianceHostDetailsTabWrapper.defaultProps = {
33
+ response: {},
34
+ };
35
+
36
+ export default ComplianceHostDetailsTabWrapper;
@@ -0,0 +1,3 @@
1
+ div.rh-cloud-insights-compliance-host-details-component {
2
+ padding: 0;
3
+ }
@@ -0,0 +1,159 @@
1
+ import React from 'react';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import ComplianceHostDetailsTabWrapper from '../ComplianceHostDetailsTab';
5
+ import { OVERVIEW_TAB_PATH } from '../../ForemanRhCloudHelpers';
6
+
7
+ const mockHistoryReplace = jest.fn();
8
+
9
+ jest.mock('react-router-dom', () => ({
10
+ ...jest.requireActual('react-router-dom'),
11
+ useHistory: () => ({
12
+ replace: mockHistoryReplace,
13
+ }),
14
+ }));
15
+
16
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
17
+ useForemanContext: () => ({
18
+ metadata: {
19
+ permissions: new Set(['view_hosts']),
20
+ },
21
+ }),
22
+ useForemanPermissions: () => new Set(['view_hosts']),
23
+ }));
24
+
25
+ jest.mock('foremanReact/common/I18n', () => ({
26
+ translate: str => str,
27
+ sprintf: str => str,
28
+ }));
29
+
30
+ jest.mock('../../InsightsCompliance/complianceIframeHelpers', () => ({
31
+ getComplianceIframeSrc: () =>
32
+ 'https://example.test/assets/apps/compliance/index.html',
33
+ }));
34
+
35
+ const mockPostChrome = jest.fn();
36
+ jest.mock('../../common/IopIframe/postIopChromeToIframe', () => ({
37
+ postIopChromeToIframe: (...args) => mockPostChrome(...args),
38
+ }));
39
+
40
+ const defaultResponse = {
41
+ id: 1,
42
+ operatingsystem_name: 'Red Hat Enterprise Linux 8',
43
+ insights_attributes: { uuid: 'insights-1' },
44
+ subscription_facet_attributes: { uuid: '1-2-3' },
45
+ };
46
+
47
+ describe('ComplianceHostDetailsTabWrapper', () => {
48
+ beforeEach(() => {
49
+ jest.clearAllMocks();
50
+ });
51
+
52
+ it('renders compliance iframe for a valid host without redirecting', () => {
53
+ const { getByTestId, container } = render(
54
+ <MemoryRouter>
55
+ <ComplianceHostDetailsTabWrapper response={defaultResponse} />
56
+ </MemoryRouter>
57
+ );
58
+
59
+ expect(
60
+ container.querySelector(
61
+ '.rh-cloud-insights-compliance-host-details-component'
62
+ )
63
+ ).toBeTruthy();
64
+
65
+ const iframe = getByTestId('compliance-host-details-iframe');
66
+ expect(iframe.getAttribute('src')).toBe(
67
+ 'https://example.test/assets/apps/compliance/index.html'
68
+ );
69
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
70
+ });
71
+
72
+ it('posts systems/<uuid> appRoute for the host-tab iframe', () => {
73
+ const { getByTestId } = render(
74
+ <MemoryRouter>
75
+ <ComplianceHostDetailsTabWrapper response={defaultResponse} />
76
+ </MemoryRouter>
77
+ );
78
+
79
+ fireEvent.load(getByTestId('compliance-host-details-iframe'));
80
+
81
+ expect(mockPostChrome).toHaveBeenCalledWith(
82
+ expect.anything(),
83
+ expect.objectContaining({
84
+ appRoute: 'systems/1-2-3',
85
+ embedded: 'host-tab',
86
+ })
87
+ );
88
+ });
89
+
90
+ it('posts updated appRoute when inventoryId changes', () => {
91
+ const responseHostA = {
92
+ ...defaultResponse,
93
+ subscription_facet_attributes: { uuid: 'uuid-host-A' },
94
+ };
95
+
96
+ const { rerender, getByTestId } = render(
97
+ <MemoryRouter>
98
+ <ComplianceHostDetailsTabWrapper response={responseHostA} />
99
+ </MemoryRouter>
100
+ );
101
+
102
+ expect(getByTestId('compliance-host-details-iframe')).toBeTruthy();
103
+
104
+ const responseHostB = {
105
+ ...defaultResponse,
106
+ subscription_facet_attributes: { uuid: 'uuid-host-B' },
107
+ };
108
+
109
+ rerender(
110
+ <MemoryRouter>
111
+ <ComplianceHostDetailsTabWrapper response={responseHostB} />
112
+ </MemoryRouter>
113
+ );
114
+
115
+ fireEvent.load(getByTestId('compliance-host-details-iframe'));
116
+
117
+ expect(mockPostChrome).toHaveBeenCalledWith(
118
+ expect.anything(),
119
+ expect.objectContaining({
120
+ appRoute: 'systems/uuid-host-B',
121
+ embedded: 'host-tab',
122
+ })
123
+ );
124
+ });
125
+
126
+ it('redirects to Overview when tab should be hidden', () => {
127
+ const nonRhelResponse = {
128
+ id: 2,
129
+ operatingsystem_name: 'Ubuntu 20.04',
130
+ insights_attributes: { uuid: 'insights-1' },
131
+ subscription_facet_attributes: { uuid: '1-2-3' },
132
+ };
133
+
134
+ const { container } = render(
135
+ <MemoryRouter>
136
+ <ComplianceHostDetailsTabWrapper response={nonRhelResponse} />
137
+ </MemoryRouter>
138
+ );
139
+
140
+ expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
141
+ expect(
142
+ container.querySelector(
143
+ '.rh-cloud-insights-compliance-host-details-component'
144
+ )
145
+ ).toBeNull();
146
+ });
147
+
148
+ it('does not redirect when host data is not yet loaded', () => {
149
+ const emptyResponse = { subscription_facet_attributes: { uuid: '1-2-3' } };
150
+
151
+ render(
152
+ <MemoryRouter>
153
+ <ComplianceHostDetailsTabWrapper response={emptyResponse} />
154
+ </MemoryRouter>
155
+ );
156
+
157
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
158
+ });
159
+ });
@@ -0,0 +1,3 @@
1
+ import ComplianceHostDetailsTabWrapper from './ComplianceHostDetailsTab';
2
+
3
+ export default ComplianceHostDetailsTabWrapper;
@@ -10,6 +10,7 @@ import {
10
10
  hasNoInsightsFacet,
11
11
  } from './ForemanRhCloudHelpers';
12
12
  import CVEsHostDetailsTabWrapper from './CVEsHostDetailsTab/CVEsHostDetailsTab';
13
+ import ComplianceHostDetailsTabWrapper from './ComplianceHostDetailsTab/ComplianceHostDetailsTab';
13
14
  import InsightsVulnerabilityActionsBar from './InsightsVulnerabilityActionsBar';
14
15
 
15
16
  const fills = [
@@ -51,6 +52,16 @@ const fills = [
51
52
  title: __('Vulnerabilities'),
52
53
  },
53
54
  },
55
+ {
56
+ slot: 'host-details-page-tabs',
57
+ name: 'Compliance',
58
+ component: props => <ComplianceHostDetailsTabWrapper {...props} />,
59
+ weight: 350,
60
+ metadata: {
61
+ hideTab: props => isNotRhelHost(props) || hasNoInsightsFacet(props),
62
+ title: __('Compliance'),
63
+ },
64
+ },
54
65
  ];
55
66
 
56
67
  export const registerFills = () => {
@@ -8,6 +8,7 @@ import IopRecommendationDetails from './IopRecommendationDetails/IopRecommendati
8
8
  import IopPathwayDetails from './IopPathwayDetails/IopPathwayDetails';
9
9
  import InsightsHostDetailsTab from './InsightsHostDetailsTab';
10
10
  import CveDetailsPage from './CveDetailsPage';
11
+ import InsightsCompliancePage from './InsightsCompliance/InsightsCompliancePage';
11
12
  import './common/styles.scss';
12
13
 
13
14
  const pages = [
@@ -20,6 +21,7 @@ const pages = [
20
21
  type: InsightsVulnerabilityListPage,
21
22
  },
22
23
  { name: 'CveDetailsPage', type: CveDetailsPage },
24
+ { name: 'InsightsCompliancePage', type: InsightsCompliancePage },
23
25
  ];
24
26
 
25
27
  export const registerPages = () => {
@@ -57,6 +59,11 @@ export const routes = [
57
59
  exact: true,
58
60
  render: props => <CveDetailsPage {...props} />,
59
61
  },
62
+ {
63
+ path: '/foreman_rh_cloud/insights_compliance',
64
+ exact: false,
65
+ render: props => <InsightsCompliancePage {...props} />,
66
+ },
60
67
  ];
61
68
 
62
69
  export const registerRoutes = () => {
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import IopAppIframe from '../common/IopIframe/IopAppIframe';
4
+ import {
5
+ getComplianceAppRoute,
6
+ getComplianceForemanPath,
7
+ getComplianceIframeSrc,
8
+ } from './complianceIframeHelpers';
9
+ import {
10
+ IOP_COMPLIANCE_NAVIGATE,
11
+ IOP_COMPLIANCE_READY,
12
+ } from './complianceIframeConstants';
13
+
14
+ import './ComplianceIframe.scss';
15
+
16
+ const ComplianceIframe = ({ appName }) => (
17
+ <IopAppIframe
18
+ appName={appName}
19
+ iframeSrc={getComplianceIframeSrc()}
20
+ getAppRoute={getComplianceAppRoute}
21
+ getForemanPath={getComplianceForemanPath}
22
+ defaultAppRoute="reports"
23
+ readyMessageType={IOP_COMPLIANCE_READY}
24
+ navigateMessageType={IOP_COMPLIANCE_NAVIGATE}
25
+ className="rh-cloud-insights-compliance-iframe"
26
+ testId="compliance-iframe"
27
+ />
28
+ );
29
+
30
+ ComplianceIframe.propTypes = {
31
+ appName: PropTypes.string.isRequired,
32
+ };
33
+
34
+ export default ComplianceIframe;
@@ -0,0 +1,12 @@
1
+ .rh-cloud-insights-compliance-page {
2
+ display: flex;
3
+ flex-direction: column;
4
+ min-height: calc(100vh - var(--pf-v5-global--spacer--2xl, 3rem));
5
+ }
6
+
7
+ .rh-cloud-insights-compliance-iframe {
8
+ border: 0;
9
+ flex: 1 1 auto;
10
+ min-height: 70vh;
11
+ width: 100%;
12
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+
3
+ import ComplianceIframe from './ComplianceIframe';
4
+
5
+ const InsightsCompliancePage = () => (
6
+ <div className="rh-cloud-insights-compliance-page compliance">
7
+ <ComplianceIframe appName="Compliance" />
8
+ </div>
9
+ );
10
+
11
+ export default InsightsCompliancePage;
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { MemoryRouter } from 'react-router-dom';
5
+
6
+ import InsightsCompliancePage from './InsightsCompliancePage';
7
+
8
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
9
+ useForemanContext: () => ({
10
+ metadata: {
11
+ permissions: new Set(['view_compliance']),
12
+ },
13
+ }),
14
+ useForemanPermissions: () => new Set(['view_compliance']),
15
+ }));
16
+
17
+ describe('InsightsCompliancePage', () => {
18
+ it('renders the compliance iframe container', () => {
19
+ render(
20
+ <MemoryRouter
21
+ initialEntries={['/foreman_rh_cloud/insights_compliance/reports']}
22
+ >
23
+ <InsightsCompliancePage />
24
+ </MemoryRouter>
25
+ );
26
+
27
+ expect(
28
+ document.querySelector('.rh-cloud-insights-compliance-page')
29
+ ).toBeTruthy();
30
+ expect(screen.getByTestId('compliance-iframe')).toHaveAttribute(
31
+ 'src',
32
+ `${window.location.origin}/assets/apps/compliance/index.html`
33
+ );
34
+ });
35
+ });
@@ -0,0 +1,7 @@
1
+ export const COMPLIANCE_STATIC_APP_PATH = '/assets/apps/compliance/index.html';
2
+
3
+ export const IOP_COMPLIANCE_READY = 'IOP_COMPLIANCE_READY';
4
+
5
+ export const IOP_COMPLIANCE_NAVIGATE = 'IOP_COMPLIANCE_NAVIGATE';
6
+
7
+ export const COMPLIANCE_ROUTE_PREFIX = '/foreman_rh_cloud/insights_compliance';
@@ -0,0 +1,23 @@
1
+ import {
2
+ COMPLIANCE_ROUTE_PREFIX,
3
+ COMPLIANCE_STATIC_APP_PATH,
4
+ } from './complianceIframeConstants';
5
+ import {
6
+ getIopAppRoute,
7
+ getIopForemanPath,
8
+ getIopIframeSrc,
9
+ } from '../common/IopIframe/iopIframeHelpers';
10
+
11
+ const COMPLIANCE_IOP_PATH = {
12
+ routePrefix: COMPLIANCE_ROUTE_PREFIX,
13
+ fallbackRoute: 'reports',
14
+ };
15
+
16
+ export const getComplianceForemanPath = (appRoute = 'reports') =>
17
+ getIopForemanPath(appRoute, COMPLIANCE_IOP_PATH);
18
+
19
+ export const getComplianceAppRoute = (pathname = '') =>
20
+ getIopAppRoute(pathname, COMPLIANCE_IOP_PATH);
21
+
22
+ export const getComplianceIframeSrc = () =>
23
+ getIopIframeSrc(COMPLIANCE_STATIC_APP_PATH);
@@ -0,0 +1,50 @@
1
+ import {
2
+ getComplianceAppRoute,
3
+ getComplianceForemanPath,
4
+ getComplianceIframeSrc,
5
+ } from './complianceIframeHelpers';
6
+
7
+ describe('complianceIframeHelpers', () => {
8
+ describe('getComplianceAppRoute', () => {
9
+ it('maps foreman compliance paths to app routes', () => {
10
+ expect(
11
+ getComplianceAppRoute('/foreman_rh_cloud/insights_compliance/reports')
12
+ ).toBe('reports');
13
+ expect(
14
+ getComplianceAppRoute(
15
+ '/foreman_rh_cloud/insights_compliance/reports/report-1'
16
+ )
17
+ ).toBe('reports/report-1');
18
+ expect(
19
+ getComplianceAppRoute(
20
+ '/foreman_rh_cloud/insights_compliance/scappolicies/new'
21
+ )
22
+ ).toBe('scappolicies/new');
23
+ });
24
+
25
+ it('defaults to reports for unknown paths', () => {
26
+ expect(getComplianceAppRoute('/foreman_rh_cloud/insights_cloud')).toBe(
27
+ 'reports'
28
+ );
29
+ });
30
+ });
31
+
32
+ describe('getComplianceForemanPath', () => {
33
+ it('maps app routes to foreman compliance paths', () => {
34
+ expect(getComplianceForemanPath('reports')).toBe(
35
+ '/foreman_rh_cloud/insights_compliance/reports'
36
+ );
37
+ expect(getComplianceForemanPath('scappolicies/new')).toBe(
38
+ '/foreman_rh_cloud/insights_compliance/scappolicies/new'
39
+ );
40
+ });
41
+ });
42
+
43
+ describe('getComplianceIframeSrc', () => {
44
+ it('returns the static compliance app entry', () => {
45
+ expect(getComplianceIframeSrc()).toBe(
46
+ `${window.location.origin}/assets/apps/compliance/index.html`
47
+ );
48
+ });
49
+ });
50
+ });
@@ -25,6 +25,16 @@ const PERMISSION_MAPPING = {
25
25
  ],
26
26
  view_advisor: ['advisor:recommendation-results:read', 'advisor:exports:read'],
27
27
  edit_advisor: ['advisor:disable-recommendations:write'],
28
+ view_compliance: [
29
+ 'inventory:hosts:read',
30
+ 'compliance:report:read',
31
+ 'compliance:policy:read',
32
+ ],
33
+ edit_compliance: [
34
+ 'compliance:policy:write',
35
+ 'compliance:policy:create',
36
+ 'compliance:policy:delete',
37
+ ],
28
38
  };
29
39
 
30
40
  /**