foreman_rh_cloud 12.1.4 → 12.2.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +1 -2
  3. data/app/controllers/insights_cloud/ui_requests_controller.rb +99 -0
  4. data/app/services/foreman_rh_cloud/cloud_request_forwarder.rb +7 -13
  5. data/app/services/foreman_rh_cloud/gateway_request.rb +26 -0
  6. data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +116 -0
  7. data/app/services/foreman_rh_cloud/tags_auth.rb +55 -0
  8. data/config/routes.rb +7 -0
  9. data/lib/foreman_rh_cloud/engine.rb +7 -1
  10. data/lib/foreman_rh_cloud/version.rb +1 -1
  11. data/lib/insights_cloud.rb +8 -0
  12. data/package.json +5 -1
  13. data/test/controllers/insights_cloud/ui_requests_controller_test.rb +169 -0
  14. data/test/unit/services/foreman_rh_cloud/cloud_request_forwarder_test.rb +3 -3
  15. data/test/unit/services/foreman_rh_cloud/insights_api_forwarder_test.rb +176 -0
  16. data/test/unit/services/foreman_rh_cloud/tags_auth_test.rb +29 -0
  17. data/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +30 -10
  18. data/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js +18 -11
  19. data/webpack/CVEsHostDetailsTab/index.js +2 -2
  20. data/webpack/CveDetailsPage/CveDetailsPage.js +20 -0
  21. data/webpack/CveDetailsPage/CveDetailsPage.test.js +31 -0
  22. data/webpack/CveDetailsPage/index.js +1 -0
  23. data/webpack/ForemanColumnExtensions/index.js +49 -14
  24. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/InventoryFilter.js +1 -0
  25. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/InventoryFilter.test.js.snap +1 -0
  26. data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +2 -0
  27. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +8 -1
  28. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +4 -0
  29. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorButton.js +3 -3
  30. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/__snapshots__/CloudConnectorButton.test.js.snap +3 -0
  31. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudPingModal/index.js +10 -4
  32. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/PageDescription/PageDescription.js +6 -6
  33. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.js +2 -0
  34. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/__snapshots__/SettingsWarning.test.js.snap +2 -0
  35. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButton.js +1 -0
  36. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButton.test.js.snap +1 -0
  37. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/InventoryAutoUpload.js +3 -1
  38. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/__snapshots__/InventoryAutoUpload.test.js.snap +3 -0
  39. data/webpack/ForemanRhCloudFills.js +6 -3
  40. data/webpack/ForemanRhCloudPages.js +21 -3
  41. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +1 -0
  42. data/webpack/InsightsCloudSync/Components/InsightsTable/SelectAllAlert.js +2 -0
  43. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap +1 -0
  44. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +3 -0
  45. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModalFooter.js +12 -2
  46. data/webpack/InsightsCloudSync/Components/RemediationModal/Resolutions.js +1 -0
  47. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +6 -1
  48. data/webpack/InsightsCloudSync/InsightsCloudSync.js +39 -1
  49. data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +6 -50
  50. data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +51 -1
  51. data/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.js +21 -0
  52. data/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.test.js +20 -0
  53. data/webpack/InsightsVulnerabilityHostIndexExtensions/CVECountCell.js +45 -0
  54. data/webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js +28 -0
  55. data/webpack/IopRecommendationDetails/IopRecommendationDetails.js +44 -0
  56. data/webpack/common/DropdownToggle.js +1 -0
  57. data/webpack/common/ScalprumModule/ScalprumContext.js +73 -0
  58. data/webpack/common/Switcher/SwitcherPF4.js +1 -0
  59. data/webpack/common/Switcher/__tests__/__snapshots__/SwitcherPF4.test.js.snap +1 -0
  60. data/webpack/common/Switcher/index.js +1 -0
  61. metadata +21 -4
  62. data/webpack/InsightsVulnerability/InsightsVulnerability.js +0 -13
  63. data/webpack/InsightsVulnerability/InsightsVulnerability.test.js +0 -18
@@ -42,7 +42,7 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
42
42
  }
43
43
 
44
44
  paths.each do |key, value|
45
- actual_params = @forwarder.path_params(key, generate_certs_hash)
45
+ actual_params = @forwarder.path_params(key)
46
46
  assert_equal value, actual_params[:url]
47
47
  end
48
48
  end
@@ -167,7 +167,7 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
167
167
  'action_dispatch.request.query_parameters' => params
168
168
  )
169
169
 
170
- actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, generate_certs_hash, @host)
170
+ actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, @host)
171
171
 
172
172
  assert_match /foo/, actual[:headers][:user_agent]
173
173
  assert_match /bar/, actual[:headers][:user_agent]
@@ -192,7 +192,7 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
192
192
  'action_dispatch.request.query_parameters' => params
193
193
  )
194
194
 
195
- actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, generate_certs_hash, @host)
195
+ actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, @host)
196
196
 
197
197
  assert_match /text\/html/, actual[:headers][:content_type]
198
198
  end
@@ -0,0 +1,176 @@
1
+ require 'test_plugin_helper'
2
+ require 'puma/null_io'
3
+
4
+ class UIRequestForwarderTest < ActiveSupport::TestCase
5
+ include MockCerts
6
+
7
+ setup do
8
+ @forwarder = ::ForemanRhCloud::InsightsApiForwarder.new
9
+ @user = FactoryBot.build(:user)
10
+ @organization = FactoryBot.build(:organization)
11
+ @location = FactoryBot.build(:location)
12
+
13
+ setup_certs_expectation do
14
+ @forwarder.stubs(:foreman_certificates)
15
+ end
16
+
17
+ ForemanRhCloud.stubs(:cert_base_url).returns('https://cert.cloud.example.com')
18
+ end
19
+
20
+ test 'should scope GET requests with proper tags' do
21
+ user_agent = { :foo => :bar }
22
+ params = {}
23
+
24
+ req = ActionDispatch::Request.new(
25
+ 'REQUEST_URI' => '/api/vulnerability/v1/cves/abc-123/affected_systems',
26
+ 'REQUEST_METHOD' => 'GET',
27
+ 'HTTP_USER_AGENT' => user_agent,
28
+ 'rack.input' => ::Puma::NullIO.new,
29
+ 'action_dispatch.request.query_parameters' => params
30
+ )
31
+
32
+ ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag)
33
+ @forwarder.expects(:execute_cloud_request).with do |actual_params|
34
+ actual = actual_params[:headers][:params]
35
+ assert_equal "U:\"#{@user.login}\"O:\"#{@organization.name}\"L:\"#{@location.name}\"", tag_value(actual.find { |param| param[0] == :tag && tag_name(param[1]) =~ /#{ForemanRhCloud::TagsAuth::TAG_NAME}/ }[1])
36
+ true
37
+ end
38
+
39
+ @forwarder.forward_request(req, '/api/vulnerability/v1/cves/abc-123/affected_systems', 'test_controller', @user, @organization, @location)
40
+
41
+ # This test asserts the parameters that are sent to the execute_cloud_request method.
42
+ # This is done by setting the expectation before the actual call.
43
+ end
44
+
45
+ test 'should not scope GET requests for unknown uris' do
46
+ user_agent = { :foo => :bar }
47
+ params = {}
48
+
49
+ req = ActionDispatch::Request.new(
50
+ 'REQUEST_URI' => '/api/vulnerability/foo/bar',
51
+ 'REQUEST_METHOD' => 'GET',
52
+ 'HTTP_USER_AGENT' => user_agent,
53
+ 'rack.input' => ::Puma::NullIO.new,
54
+ 'action_dispatch.request.query_parameters' => params
55
+ )
56
+
57
+ ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag).never
58
+ @forwarder.expects(:execute_cloud_request).with do |actual_params|
59
+ actual = actual_params[:headers][:params]
60
+ assert_equal 0, actual.count
61
+ true
62
+ end
63
+
64
+ @forwarder.forward_request(req, '/api/vulnerability/foo/bar', 'test_controller', @user, @organization, @location)
65
+
66
+ # This test asserts the parameters that are sent to the execute_cloud_request method.
67
+ # This is done by setting the expectation before the actual call.
68
+ end
69
+
70
+ test 'should merge URI params in GET requests' do
71
+ user_agent = { :foo => :bar }
72
+ params = { :page => 5, :per_page => 42 }
73
+
74
+ req = ActionDispatch::Request.new(
75
+ 'REQUEST_URI' => '/api/vulnerability/v1/cves/abc-123/affected_systems',
76
+ 'REQUEST_METHOD' => 'GET',
77
+ 'HTTP_USER_AGENT' => user_agent,
78
+ 'rack.input' => ::Puma::NullIO.new,
79
+ 'action_dispatch.request.query_parameters' => params
80
+ )
81
+
82
+ ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag)
83
+ @forwarder.expects(:execute_cloud_request).with do |actual_params|
84
+ actual = actual_params[:headers][:params]
85
+ assert_equal "U:\"#{@user.login}\"O:\"#{@organization.name}\"L:\"#{@location.name}\"", tag_value(actual.find { |param| param[0] == :tag && tag_name(param[1]) =~ /#{ForemanRhCloud::TagsAuth::TAG_NAME}/ }[1])
86
+ assert_equal 5, actual.find { |param| param[0] == :page }[1]
87
+ assert_equal 42, actual.find { |param| param[0] == :per_page }[1]
88
+ true
89
+ end
90
+
91
+ @forwarder.forward_request(req, '/api/vulnerability/v1/cves/abc-123/affected_systems', 'test_controller', @user, @organization, @location)
92
+ # This test asserts the parameters that are sent to the execute_cloud_request method.
93
+ # This is done by setting the expectation before the actual call.
94
+ end
95
+
96
+ test 'should not scope POST requests' do
97
+ post_data = 'Random POST data'
98
+ req = ActionDispatch::Request.new(
99
+ 'REQUEST_URI' => '/foo/bar',
100
+ 'REQUEST_METHOD' => 'POST',
101
+ 'rack.input' => ::Puma::NullIO.new,
102
+ 'RAW_POST_DATA' => post_data
103
+ )
104
+
105
+ ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag).never
106
+ @forwarder.expects(:execute_cloud_request).with do |actual_params|
107
+ actual = actual_params[:headers][:params]
108
+ assert_equal 0, actual.count
109
+ true
110
+ end
111
+
112
+ @forwarder.forward_request(req, '/api/vulnerability/v1/cves', 'test_controller', @user, @organization, @location)
113
+
114
+ # This test asserts the parameters that are sent to the execute_cloud_request method.
115
+ # This is done by setting the expectation before the actual call.
116
+ end
117
+
118
+ test 'should not scope PUT requests' do
119
+ put_data = 'Random PUT data'
120
+ req = ActionDispatch::Request.new(
121
+ 'REQUEST_URI' => '/foo/bar',
122
+ 'REQUEST_METHOD' => 'PUT',
123
+ 'rack.input' => ::Puma::NullIO.new,
124
+ 'RAW_POST_DATA' => put_data
125
+ )
126
+
127
+ ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag).never
128
+ @forwarder.expects(:execute_cloud_request).with do |actual_params|
129
+ actual = actual_params[:headers][:params]
130
+ assert_equal 0, actual.count
131
+ true
132
+ end
133
+
134
+ @forwarder.forward_request(req, '/api/vulnerability/v1/cves', 'test_controller', @user, @organization, @location)
135
+
136
+ # This test asserts the parameters that are sent to the execute_cloud_request method.
137
+ # This is done by setting the expectation before the actual call.
138
+ end
139
+
140
+ test 'should not scope PATCH requests' do
141
+ post_data = 'Random PATCH data'
142
+ req = ActionDispatch::Request.new(
143
+ 'REQUEST_URI' => '/foo/bar',
144
+ 'REQUEST_METHOD' => 'PATCH',
145
+ 'rack.input' => ::Puma::NullIO.new,
146
+ 'RAW_POST_DATA' => post_data,
147
+ "action_dispatch.request.path_parameters" => { :format => "json" }
148
+ )
149
+
150
+ ::ForemanRhCloud::TagsAuth.any_instance.expects(:update_tag).never
151
+ @forwarder.expects(:execute_cloud_request).with do |actual_params|
152
+ actual = actual_params[:headers][:params]
153
+ assert_equal 0, actual.count
154
+ true
155
+ end
156
+
157
+ @forwarder.forward_request(req, '/api/vulnerability/v1/cves', 'test_controller', @user, @organization, @location)
158
+
159
+ # This test asserts the parameters that are sent to the execute_cloud_request method.
160
+ # This is done by setting the expectation before the actual call.
161
+ end
162
+
163
+ def tag_value(param_value)
164
+ return param_value unless param_value.is_a?(String)
165
+
166
+ tag_string = CGI.unescape(param_value)
167
+ tag_string.split('=')[1]
168
+ end
169
+
170
+ def tag_name(param_value)
171
+ return param_value unless param_value.is_a?(String)
172
+
173
+ tag_string = CGI.unescape(param_value)
174
+ tag_string.split('=')[0]
175
+ end
176
+ end
@@ -0,0 +1,29 @@
1
+ require 'test_plugin_helper'
2
+ require 'json'
3
+
4
+ class TagsAuthTest < ActiveSupport::TestCase
5
+ setup do
6
+ @user = FactoryBot.build(:user)
7
+ @logger = Logger.new(IO::NULL)
8
+ @org = FactoryBot.build(:organization)
9
+ @loc = FactoryBot.build(:location)
10
+ @auth = ::ForemanRhCloud::TagsAuth.new(@user, @org, @loc, @logger)
11
+ end
12
+
13
+ test 'Generates tags update request' do
14
+ uuid1 = 'test_uuid1'
15
+ uuid2 = 'test_uuid2'
16
+
17
+ @auth.expects(:allowed_hosts).returns([uuid1, uuid2])
18
+ @auth.expects(:execute_cloud_request).with do |actual_params|
19
+ actual = JSON.parse(actual_params[:payload])
20
+ assert_includes actual['host_id_list'], uuid1
21
+ assert_includes actual['host_id_list'], uuid2
22
+ assert_equal ForemanRhCloud::TagsAuth::TAG_SHORT_NAME, actual['tags'].first['key']
23
+ assert_equal ForemanRhCloud::TagsAuth::TAG_NAMESPACE, actual['tags'].first['namespace']
24
+ assert_equal "U:\"#{@user.login}\"O:\"#{@org.name}\"L:\"#{@loc.name}\"", actual['tags'].first['value']
25
+ end
26
+
27
+ @auth.update_tag
28
+ end
29
+ end
@@ -1,17 +1,37 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { translate as __ } from 'foremanReact/common/I18n';
3
+ import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core';
4
+ import { providerOptions } from '../common/ScalprumModule/ScalprumContext';
4
5
 
5
- const CVEsHostDetailsTab = ({ hostName }) => (
6
- <div>
7
- <h1>
8
- {__('CVEs tab for host:')} {hostName}
9
- </h1>
10
- </div>
11
- );
6
+ const CVEsHostDetailsTab = ({ systemId }) => {
7
+ const scope = 'vulnerability';
8
+ const module = './SystemDetailTable';
9
+ return (
10
+ <div className="rh-cloud-insights-vulnerability-host-details-component">
11
+ <ScalprumComponent scope={scope} module={module} systemId={systemId} />
12
+ </div>
13
+ );
14
+ };
12
15
 
13
16
  CVEsHostDetailsTab.propTypes = {
14
- hostName: PropTypes.string.isRequired,
17
+ systemId: PropTypes.string.isRequired,
18
+ };
19
+
20
+ const CVEsHostDetailsTabWrapper = ({ response }) => (
21
+ <ScalprumProvider {...providerOptions}>
22
+ <CVEsHostDetailsTab
23
+ // eslint-disable-next-line camelcase
24
+ systemId={response?.subscription_facet_attributes?.uuid}
25
+ />
26
+ </ScalprumProvider>
27
+ );
28
+
29
+ CVEsHostDetailsTabWrapper.propTypes = {
30
+ response: PropTypes.shape({
31
+ subscription_facet_attributes: PropTypes.shape({
32
+ uuid: PropTypes.string.isRequired,
33
+ }),
34
+ }).isRequired,
15
35
  };
16
36
 
17
- export default CVEsHostDetailsTab;
37
+ export default CVEsHostDetailsTabWrapper;
@@ -1,18 +1,25 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
- import CVEsHostDetailsTab from '../CVEsHostDetailsTab';
2
+ import { render } from '@testing-library/react';
3
+ import CVEsHostDetailsTabWrapper from '../CVEsHostDetailsTab';
4
4
 
5
- describe('CVEsHostDetailsTab', () => {
5
+ jest.mock('@scalprum/react-core', () => ({
6
+ ScalprumComponent: jest.fn(props => (
7
+ <div data-testid="mock-scalprum-component">{JSON.stringify(props)}</div>
8
+ )),
9
+ ScalprumProvider: jest.fn(({ children }) => <div>{children}</div>),
10
+ }));
11
+
12
+ describe('CVEsHostDetailsTabWrapper', () => {
6
13
  it('renders without crashing', () => {
7
- render(<CVEsHostDetailsTab hostName="test-host.example.com" />);
14
+ const { container } = render(
15
+ <CVEsHostDetailsTabWrapper
16
+ response={{ subscription_facet_attributes: { uuid: '1-2-3' } }}
17
+ />
18
+ );
8
19
  expect(
9
- screen.getByText('CVEs tab for host: test-host.example.com')
20
+ container.querySelector(
21
+ '.rh-cloud-insights-vulnerability-host-details-component'
22
+ )
10
23
  ).toBeTruthy();
11
24
  });
12
-
13
- it('renders the host name', () => {
14
- const hostName = 'test-host.example.com';
15
- render(<CVEsHostDetailsTab hostName={hostName} />);
16
- expect(screen.getByText(`CVEs tab for host: ${hostName}`)).toBeTruthy();
17
- });
18
25
  });
@@ -1,3 +1,3 @@
1
- import CVEsHostDetailsTab from './CVEsHostDetailsTab';
1
+ import CVEsHostDetailsTabWrapper from './CVEsHostDetailsTab';
2
2
 
3
- export default CVEsHostDetailsTab;
3
+ export default CVEsHostDetailsTabWrapper;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { useParams } from 'react-router-dom';
3
+ import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core';
4
+ import { providerOptions } from '../common/ScalprumModule/ScalprumContext';
5
+
6
+ const CveDetailsPage = () => {
7
+ const { cveId } = useParams();
8
+ const scope = 'vulnerability';
9
+ const module = './CveDetailPage';
10
+
11
+ return (
12
+ <ScalprumProvider {...providerOptions}>
13
+ <div className="rh-cloud-cve-details-page">
14
+ <ScalprumComponent scope={scope} module={module} cveId={cveId} />
15
+ </div>
16
+ </ScalprumProvider>
17
+ );
18
+ };
19
+
20
+ export default CveDetailsPage;
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import CveDetailsPage from './CveDetailsPage';
5
+
6
+ // Mock react-router-dom
7
+ jest.mock('react-router-dom', () => ({
8
+ useParams: jest.fn(() => ({ cveId: 'CVE-2021-1234' })),
9
+ }));
10
+
11
+ jest.mock('@scalprum/react-core', () => ({
12
+ ScalprumComponent: jest.fn(props => (
13
+ <div data-testid="mock-scalprum-component">{JSON.stringify(props)}</div>
14
+ )),
15
+ ScalprumProvider: jest.fn(({ children }) => <div>{children}</div>),
16
+ }));
17
+
18
+ describe('CveDetailsPage component', () => {
19
+ it('renders the container with correct class', () => {
20
+ const { container } = render(<CveDetailsPage />);
21
+ expect(
22
+ container.querySelector('.rh-cloud-cve-details-page')
23
+ ).toBeTruthy();
24
+ });
25
+
26
+ it('passes cveId from URL params to ScalprumComponent', () => {
27
+ const { getByTestId } = render(<CveDetailsPage />);
28
+ const mockComponent = getByTestId('mock-scalprum-component');
29
+ expect(mockComponent.textContent).toContain('CVE-2021-1234');
30
+ });
31
+ });
@@ -0,0 +1 @@
1
+ export { default } from './CveDetailsPage';
@@ -1,19 +1,15 @@
1
1
  import React from 'react';
2
+ import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core';
2
3
  import { translate as __ } from 'foremanReact/common/I18n';
3
4
  import { propsToCamelCase } from 'foremanReact/common/helpers';
5
+ import { CVECountCell } from '../InsightsVulnerabilityHostIndexExtensions/CVECountCell';
6
+ import { providerOptions } from '../common/ScalprumModule/ScalprumContext';
4
7
 
5
- const RecommendationsCell = hostDetails => {
8
+ const HostedRecommendationsCell = hostDetails => {
6
9
  const insightsAttributes = propsToCamelCase(
7
10
  // eslint-disable-next-line camelcase
8
11
  hostDetails?.insights_attributes ?? {}
9
12
  );
10
- // Local insights advisor
11
- if (insightsAttributes.useLocalAdvisorEngine) {
12
- // TODO: Replace this placeholder with the actual local advisor integration
13
- return <span>Local advisor placeholder</span>;
14
- }
15
-
16
- // Hosted insights advisor
17
13
  const { insightsHitsCount: hitsCount } = insightsAttributes;
18
14
  if (hitsCount === undefined || hitsCount === null) return '—';
19
15
  const hostname = hostDetails?.name;
@@ -22,6 +18,38 @@ const RecommendationsCell = hostDetails => {
22
18
  return <a href={hitsUrl}>{hitsCount}</a>;
23
19
  };
24
20
 
21
+ const IopRecommendationsCell = hostDetails => {
22
+ const scope = 'advisor';
23
+ const module = './RecommendationsCellWrapped';
24
+
25
+ return (
26
+ <span className="rh-cloud-insights-recommendations-cell">
27
+ <ScalprumComponent scope={scope} module={module} />
28
+ </span>
29
+ );
30
+ };
31
+
32
+ const IopRecommendationsCellWrapped = hostDetails => (
33
+ <ScalprumProvider {...providerOptions}>
34
+ <IopRecommendationsCell hostDetails={hostDetails} />
35
+ </ScalprumProvider>
36
+ );
37
+
38
+ const RecommendationsCell = hostDetails => {
39
+ const insightsAttributes = propsToCamelCase(
40
+ // eslint-disable-next-line camelcase
41
+ hostDetails?.insights_attributes ?? {}
42
+ );
43
+
44
+ return insightsAttributes.useLocalAdvisorEngine ? (
45
+ <IopRecommendationsCellWrapped hostDetails={hostDetails} />
46
+ ) : (
47
+ <HostedRecommendationsCell hostDetails={hostDetails} />
48
+ );
49
+ };
50
+
51
+ const insightsCategoryName = __('Insights');
52
+
25
53
  const hostsIndexColumnExtensions = [
26
54
  {
27
55
  columnName: 'insights_recommendations_count',
@@ -29,13 +57,20 @@ const hostsIndexColumnExtensions = [
29
57
  wrapper: RecommendationsCell,
30
58
  weight: 1500,
31
59
  isSorted: true,
60
+ tableName: 'hosts',
61
+ categoryName: insightsCategoryName,
62
+ categoryKey: 'insights',
63
+ },
64
+ {
65
+ columnName: 'cves_count',
66
+ title: __('Total CVEs'),
67
+ wrapper: hostDetails => <CVECountCell hostDetails={hostDetails} />,
68
+ weight: 2600,
69
+ tableName: 'hosts',
70
+ categoryName: insightsCategoryName,
71
+ categoryKey: 'insights',
72
+ isSorted: false,
32
73
  },
33
74
  ];
34
75
 
35
- hostsIndexColumnExtensions.forEach(column => {
36
- column.tableName = 'hosts';
37
- column.categoryName = 'Insights';
38
- column.categoryKey = 'insights';
39
- });
40
-
41
76
  export default hostsIndexColumnExtensions;
@@ -26,6 +26,7 @@ const InventoryFilter = ({
26
26
  <FormGroup>
27
27
  <TextInput
28
28
  id="inventory_filter_input"
29
+ ouiaId="inventory_filter_input"
29
30
  value={filterTerm}
30
31
  type="text"
31
32
  placeholder={__('Filter..')}
@@ -8,6 +8,7 @@ exports[`InventoryFilter rendering render with props 1`] = `
8
8
  <TextInput
9
9
  id="inventory_filter_input"
10
10
  onChange={[Function]}
11
+ ouiaId="inventory_filter_input"
11
12
  placeholder="Filter.."
12
13
  type="text"
13
14
  value="test_filter_term"
@@ -68,6 +68,7 @@ const MinimalInventoryDropdown = ({ setChosenValue }) => {
68
68
  };
69
69
  return (
70
70
  <Dropdown
71
+ ouiaId="inventory-dropdown"
71
72
  isOpen={isOpen}
72
73
  onSelect={onSelect}
73
74
  onOpenChange={val => setIsOpen(val)}
@@ -90,6 +91,7 @@ const MinimalInventoryDropdown = ({ setChosenValue }) => {
90
91
  value={value}
91
92
  key={value}
92
93
  description={item.description}
94
+ ouiaId={`inventory-dropdownItem-${value}`}
93
95
  >
94
96
  {item.title}
95
97
  </DropdownItem>
@@ -26,6 +26,7 @@ const PageTitle = () => {
26
26
  const dropdownItems = [
27
27
  <DropdownItem
28
28
  key="tasks-history-button"
29
+ ouiaId="tasks-history-button"
29
30
  href={getActionsHistoryUrl()}
30
31
  target="_blank"
31
32
  rel="noopener noreferrer"
@@ -34,13 +35,18 @@ const PageTitle = () => {
34
35
  </DropdownItem>,
35
36
  <DropdownItem
36
37
  key="inventory-documentation-button"
38
+ ouiaId="inventory-documentation-button"
37
39
  href={getInventoryDocsUrl()}
38
40
  target="_blank"
39
41
  rel="noopener noreferrer"
40
42
  >
41
43
  {DOCS_BUTTON_TEXT}
42
44
  </DropdownItem>,
43
- <DropdownItem key="cloud-ping" onClick={togglePingModal}>
45
+ <DropdownItem
46
+ key="cloud-ping"
47
+ ouiaId="dropdownItem-cloud-ping"
48
+ onClick={togglePingModal}
49
+ >
44
50
  {CLOUD_PING_TITLE}
45
51
  </DropdownItem>,
46
52
  ];
@@ -55,6 +61,7 @@ const PageTitle = () => {
55
61
  <GridItem span={6}>
56
62
  <Dropdown
57
63
  className="title-dropdown"
64
+ ouiaId="title-dropdown"
58
65
  onSelect={() => setIsDropdownOpen(false)}
59
66
  toggle={
60
67
  <KebabToggle
@@ -25,6 +25,7 @@ exports[`PageTitle rendering render without Props 1`] = `
25
25
  Array [
26
26
  <DropdownItem
27
27
  href="/foreman_tasks/tasks?search=label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AGenerateReportJob+or+label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AGenerateAllReportsJob&page=1"
28
+ ouiaId="tasks-history-button"
28
29
  rel="noopener noreferrer"
29
30
  target="_blank"
30
31
  >
@@ -32,6 +33,7 @@ exports[`PageTitle rendering render without Props 1`] = `
32
33
  </DropdownItem>,
33
34
  <DropdownItem
34
35
  href="/links/manual/?root_url=https%3A%2F%2Faccess.redhat.com%2Fdocumentation%2Fen-us%2Fred_hat_insights%2F2023%2Fhtml%2Fred_hat_insights_remediations_guide%2Fhost-communication-with-insights_red-hat-insights-remediation-guide%23uploading-satellite-host-inventory-to-insights_configuring-satellite-cloud-connector"
36
+ ouiaId="inventory-documentation-button"
35
37
  rel="noopener noreferrer"
36
38
  target="_blank"
37
39
  >
@@ -39,6 +41,7 @@ exports[`PageTitle rendering render without Props 1`] = `
39
41
  </DropdownItem>,
40
42
  <DropdownItem
41
43
  onClick={[Function]}
44
+ ouiaId="dropdownItem-cloud-ping"
42
45
  >
43
46
  Connectivity test
44
47
  </DropdownItem>,
@@ -47,6 +50,7 @@ exports[`PageTitle rendering render without Props 1`] = `
47
50
  isOpen={false}
48
51
  isPlain={true}
49
52
  onSelect={[Function]}
53
+ ouiaId="title-dropdown"
50
54
  position="right"
51
55
  toggle={
52
56
  <KebabToggle
@@ -26,7 +26,7 @@ export const CloudConnectorButton = ({ status, onClick, jobLink }) => {
26
26
  className="cloud-connector-pending-button"
27
27
  onMouseEnter={() => setIsPopoverVisible(true)}
28
28
  >
29
- <Button variant="secondary" isDisabled>
29
+ <Button variant="secondary" ouiaId="button-in-progress" isDisabled>
30
30
  <Spinner size="sm" /> {__('Cloud Connector is in progress')}
31
31
  </Button>
32
32
  </div>
@@ -36,14 +36,14 @@ export const CloudConnectorButton = ({ status, onClick, jobLink }) => {
36
36
 
37
37
  if (status === CONNECTOR_STATUS.RESOLVED) {
38
38
  return (
39
- <Button variant="secondary" onClick={onClick}>
39
+ <Button variant="secondary" ouiaId="button-reconfigure" onClick={onClick}>
40
40
  {__('Reconfigure cloud connector')}
41
41
  </Button>
42
42
  );
43
43
  }
44
44
 
45
45
  return (
46
- <Button variant="secondary" onClick={onClick}>
46
+ <Button variant="secondary" ouiaId="button-configure" onClick={onClick}>
47
47
  {__('Configure cloud connector')}
48
48
  </Button>
49
49
  );
@@ -3,6 +3,7 @@
3
3
  exports[`CloudConnectorButton render no cloud connector 1`] = `
4
4
  <Button
5
5
  onClick={[MockFunction]}
6
+ ouiaId="button-configure"
6
7
  variant="secondary"
7
8
  >
8
9
  Configure cloud connector
@@ -34,6 +35,7 @@ exports[`CloudConnectorButton render pending connector 1`] = `
34
35
  >
35
36
  <Button
36
37
  isDisabled={true}
38
+ ouiaId="button-in-progress"
37
39
  variant="secondary"
38
40
  >
39
41
  <Spinner
@@ -49,6 +51,7 @@ exports[`CloudConnectorButton render pending connector 1`] = `
49
51
  exports[`CloudConnectorButton render resolved cloud connector 1`] = `
50
52
  <Button
51
53
  onClick={[MockFunction]}
54
+ ouiaId="button-reconfigure"
52
55
  variant="secondary"
53
56
  >
54
57
  Reconfigure cloud connector
@@ -79,26 +79,32 @@ const CloudPingModal = ({ title, isOpen, toggle }) => {
79
79
  <>
80
80
  <Modal
81
81
  id="cloud-ping-modal"
82
+ ouiaId="cloud-ping-modal"
82
83
  appendTo={document.getElementsByClassName('react-container')[0]}
83
84
  variant={ModalVariant.large}
84
85
  title={title}
85
86
  isOpen={isOpen}
86
87
  onClose={toggle}
87
88
  >
88
- <Card className="certs-status">
89
+ <Card className="certs-status" ouiaId="card-org-status">
89
90
  <CardTitle>{__('Organization status')}</CardTitle>
90
91
  <CardBody>
91
- <Text>
92
+ <Text ouiaId="text-description">
92
93
  {__('Displays manifest statuses per accessible organizations.')}
93
94
  </Text>
94
95
  {isPending ? (
95
96
  <Spinner size="xl" />
96
97
  ) : (
97
98
  <>
98
- <Text className="pull-right">
99
+ <Text className="pull-right" ouiaId="text-org-count">
99
100
  {sprintf(__('%s organizations'), rows.length)}
100
101
  </Text>
101
- <Table aria-label="Simple Table" cells={['']} rows={rows}>
102
+ <Table
103
+ aria-label="Simple Table"
104
+ ouiaId="simple-table"
105
+ cells={['']}
106
+ rows={rows}
107
+ >
102
108
  <TableHeader />
103
109
  <TableBody />
104
110
  </Table>{' '}