foreman_rh_cloud 6.0.43 → 7.0.45

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +11 -5
  3. data/app/helpers/foreman_inventory_upload_host_helper.rb +1 -3
  4. data/app/models/concerns/rh_cloud_host.rb +10 -0
  5. data/config/package-lock.json.plugin +9880 -6929
  6. data/db/migrate/20221102110254_fix_rh_cloud_settings_category_to_dsl.rb +7 -0
  7. data/lib/foreman_inventory_upload/async/delayed_start.rb +51 -0
  8. data/lib/foreman_inventory_upload/async/generate_all_reports_job.rb +12 -9
  9. data/lib/foreman_inventory_upload/async/shell_process.rb +9 -1
  10. data/lib/foreman_inventory_upload/async/upload_report_job.rb +3 -1
  11. data/lib/foreman_rh_cloud/async/exponential_backoff.rb +55 -0
  12. data/lib/foreman_rh_cloud/engine.rb +8 -1
  13. data/lib/foreman_rh_cloud/version.rb +1 -1
  14. data/lib/foreman_rh_cloud.rb +4 -0
  15. data/lib/insights_cloud/async/insights_full_sync.rb +3 -1
  16. data/lib/insights_cloud/async/insights_resolutions_sync.rb +3 -1
  17. data/lib/insights_cloud/async/insights_rules_sync.rb +3 -1
  18. data/lib/insights_cloud/async/insights_scheduled_sync.rb +4 -1
  19. data/lib/inventory_sync/async/host_result.rb +1 -1
  20. data/lib/inventory_sync/async/inventory_scheduled_sync.rb +5 -2
  21. data/lib/inventory_sync/async/query_inventory_job.rb +4 -1
  22. data/package.json +6 -6
  23. data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +11 -0
  24. data/test/jobs/exponential_backoff_test.rb +45 -0
  25. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButton.js +1 -2
  26. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/SyncButton.test.js.snap +0 -5
  27. data/webpack/ForemanRhCloudFills.js +1 -1
  28. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +4 -1
  29. data/webpack/InsightsHostDetailsTab/InsightsTotalRiskChart.js +4 -1
  30. data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +4 -3
  31. metadata +8 -4
  32. data/app/overrides/hosts_list.rb +0 -13
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FixRhCloudSettingsCategoryToDsl < ActiveRecord::Migration[6.0]
4
+ def up
5
+ Setting.where(category: 'Setting::RhCloud').update_all(category: 'Setting')
6
+ end
7
+ end
@@ -0,0 +1,51 @@
1
+ module ForemanInventoryUpload
2
+ module Async
3
+ module DelayedStart
4
+ extend ActiveSupport::Concern
5
+
6
+ START_WINDOW = 3.hours.seconds
7
+
8
+ def after_delay(delay = nil, logger: nil, &block)
9
+ logger ||= self.logger if respond_to? :logger
10
+ delay ||= ForemanRhCloud.requests_delay || Random.new.rand(START_WINDOW)
11
+ delay = delay.to_i
12
+
13
+ logger&.debug("planning a delay for #{delay} seconds before the rest of the execution")
14
+
15
+ sequence do
16
+ plan_action(ForemanInventoryUpload::Async::DelayAction, delay)
17
+ concurrence(&block)
18
+ end
19
+ end
20
+
21
+ def humanized_name
22
+ _('Wait and %s' % super)
23
+ end
24
+ end
25
+
26
+ class DelayAction < ::Actions::EntryAction
27
+ Wake = Algebrick.atom
28
+
29
+ def plan(delay)
30
+ plan_self(delay: delay)
31
+ end
32
+
33
+ def run(event = nil)
34
+ case event
35
+ when nil
36
+ action_logger.debug("Going to sleep for #{sleep_seconds} seconds")
37
+ plan_event(Wake, sleep_seconds)
38
+ suspend
39
+ when Wake
40
+ action_logger.debug('Waking up')
41
+ else
42
+ action_logger.debug("DelayAction received unknown event #{event}")
43
+ end
44
+ end
45
+
46
+ def sleep_seconds
47
+ input[:delay].to_i
48
+ end
49
+ end
50
+ end
51
+ end
@@ -2,6 +2,7 @@ module ForemanInventoryUpload
2
2
  module Async
3
3
  class GenerateAllReportsJob < ::Actions::EntryAction
4
4
  include ::Actions::RecurringAction
5
+ include ForemanInventoryUpload::Async::DelayedStart
5
6
 
6
7
  def plan
7
8
  unless Setting[:allow_auto_inventory_upload]
@@ -12,17 +13,19 @@ module ForemanInventoryUpload
12
13
  return
13
14
  end
14
15
 
15
- organizations = Organization.unscoped.all
16
+ after_delay do
17
+ organizations = Organization.unscoped.all
16
18
 
17
- organizations.map do |organization|
18
- total_hosts = ForemanInventoryUpload::Generators::Queries.for_org(organization.id, use_batches: false).count
19
+ organizations.map do |organization|
20
+ total_hosts = ForemanInventoryUpload::Generators::Queries.for_org(organization.id, use_batches: false).count
19
21
 
20
- if total_hosts <= ForemanInventoryUpload.max_org_size
21
- plan_generate_report(ForemanInventoryUpload.generated_reports_folder, organization)
22
- else
23
- logger.info("Skipping automatic uploads for organization #{organization.name}, too many hosts (#{total_hosts}/#{ForemanInventoryUpload.max_org_size})")
24
- end
25
- end.compact
22
+ if total_hosts <= ForemanInventoryUpload.max_org_size
23
+ plan_generate_report(ForemanInventoryUpload.generated_reports_folder, organization)
24
+ else
25
+ logger.info("Skipping automatic uploads for organization #{organization.name}, too many hosts (#{total_hosts}/#{ForemanInventoryUpload.max_org_size})")
26
+ end
27
+ end.compact
28
+ end
26
29
  end
27
30
 
28
31
  def rescue_strategy_for_self
@@ -4,13 +4,14 @@ module ForemanInventoryUpload
4
4
  module Async
5
5
  class ShellProcess < ::Actions::EntryAction
6
6
  include AsyncHelpers
7
+ include ::ForemanRhCloud::Async::ExponentialBackoff
7
8
 
8
9
  def plan(instance_label, more_inputs = {})
9
10
  inputs = more_inputs.merge(instance_label: instance_label)
10
11
  plan_self(inputs)
11
12
  end
12
13
 
13
- def run
14
+ def try_execute
14
15
  klass_name = self.class.name
15
16
  logger.debug("Starting #{klass_name} with label #{instance_label}")
16
17
  progress_output do |progress_output|
@@ -25,6 +26,9 @@ module ForemanInventoryUpload
25
26
  end
26
27
  end
27
28
  logger.debug("Finished job #{klass_name} with label #{instance_label}")
29
+
30
+ assert_task_status(ProgressOutput.get(instance_label).status)
31
+ done!
28
32
  end
29
33
 
30
34
  def command
@@ -58,6 +62,10 @@ module ForemanInventoryUpload
58
62
  def instance_label
59
63
  input[:instance_label]
60
64
  end
65
+
66
+ def assert_task_status(status)
67
+ raise Foreman::Exception.new('Process exited with an unknown status: %{status}', status: status) unless status.match?(/pid \d+ exit 0/)
68
+ end
61
69
  end
62
70
  end
63
71
  end
@@ -12,11 +12,12 @@ module ForemanInventoryUpload
12
12
  super(label, filename: filename, organization_id: organization_id)
13
13
  end
14
14
 
15
- def run
15
+ def try_execute
16
16
  if content_disconnected?
17
17
  progress_output do |progress_output|
18
18
  progress_output.write_line('Upload was stopped since disconnected mode setting is enabled for content on this instance.')
19
19
  progress_output.status = "Task aborted, exit 1"
20
+ done!
20
21
  end
21
22
  return
22
23
  end
@@ -26,6 +27,7 @@ module ForemanInventoryUpload
26
27
  progress_output do |progress_output|
27
28
  progress_output.write_line("Skipping organization #{organization}, no candlepin certificate defined.")
28
29
  progress_output.status = "Task aborted, exit 1"
30
+ done!
29
31
  end
30
32
  return
31
33
  end
@@ -0,0 +1,55 @@
1
+ module ForemanRhCloud
2
+ module Async
3
+ module ExponentialBackoff
4
+ extend ActiveSupport::Concern
5
+ include Dynflow::Action::Polling
6
+
7
+ # Use each interval once
8
+ def attempts_before_next_interval
9
+ 1
10
+ end
11
+
12
+ # define poll intervals in the following way: [1/10..1, 1..10, 10..100] e.t.c.
13
+ # total count of intervals would be the amount of poll retries.
14
+ def poll_intervals
15
+ (1..poll_max_retries).map do |i|
16
+ base = 10**i
17
+ random_interval(base)
18
+ end
19
+ end
20
+
21
+ def done!
22
+ @done = true
23
+ end
24
+
25
+ def done?
26
+ @done
27
+ end
28
+
29
+ def invoke_external_task
30
+ # Call the polling method from task's framework
31
+ poll_external_task_with_rescue
32
+ # supress unexpected task output serialization
33
+ {}
34
+ end
35
+
36
+ def poll_external_task
37
+ try_execute
38
+ # supress unexpected task output serialization
39
+ {}
40
+ end
41
+
42
+ # override this method in the consumng class
43
+ # This is the action that we expect to retry in case of an exception.
44
+ def try_execute
45
+ raise NotImplementedError
46
+ end
47
+
48
+ private
49
+
50
+ def random_interval(base)
51
+ Random.new.rand(base..10 * base)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -42,7 +42,7 @@ module ForemanRhCloud
42
42
 
43
43
  initializer 'foreman_rh_cloud.register_plugin', :before => :finisher_hook do |_app|
44
44
  Foreman::Plugin.register :foreman_rh_cloud do
45
- requires_foreman '>= 3.3'
45
+ requires_foreman '>= 3.4'
46
46
 
47
47
  apipie_documented_controllers ["#{ForemanRhCloud::Engine.root}/app/controllers/api/v2/**/*.rb"]
48
48
 
@@ -125,6 +125,13 @@ module ForemanRhCloud
125
125
  onlyif: proc { |host| host.insights }
126
126
  end
127
127
 
128
+ extend_page 'hosts/_list' do |context|
129
+ context.with_profile :cloud, _('RH Cloud'), default: true do
130
+ add_pagelet :hosts_table_column_header, key: :insights_recommendations_count, label: _('Recommendations'), sortable: true, width: '12%', class: 'hidden-xs ellipsis', priority: 100
131
+ add_pagelet :hosts_table_column_content, key: :insights_recommendations_count, callback: ->(host) { hits_counts_cell(host) }, class: 'hidden-xs ellipsis text-center', priority: 100
132
+ end
133
+ end
134
+
128
135
  extend_template_helpers ForemanRhCloud::TemplateRendererHelper
129
136
  allowed_template_helpers :remediations_playbook, :download_rh_playbook
130
137
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '6.0.43'.freeze
2
+ VERSION = '7.0.45'.freeze
3
3
  end
@@ -120,4 +120,8 @@ module ForemanRhCloud
120
120
  def self.cloud_url_validator
121
121
  @cloud_url_validator ||= Regexp.new(ENV['SATELLITE_RH_CLOUD_VALIDATOR'] || 'redhat.com$')
122
122
  end
123
+
124
+ def self.requests_delay
125
+ @requests_delay ||= ENV['SATELLITE_RH_CLOUD_REQUESTS_DELAY']
126
+ end
123
127
  end
@@ -5,6 +5,7 @@ module InsightsCloud
5
5
  module Async
6
6
  class InsightsFullSync < ::Actions::EntryAction
7
7
  include ::ForemanRhCloud::CertAuth
8
+ include ::ForemanRhCloud::Async::ExponentialBackoff
8
9
 
9
10
  def plan(organizations)
10
11
  organizations = organizations.select do |organization|
@@ -28,7 +29,7 @@ module InsightsCloud
28
29
  end
29
30
  end
30
31
 
31
- def run
32
+ def try_execute
32
33
  organizations.each do |organization|
33
34
  unless cert_auth_available?(organization)
34
35
  logger.debug("Certificate is not available for org: #{organization.name}, skipping insights sync")
@@ -37,6 +38,7 @@ module InsightsCloud
37
38
 
38
39
  perform_hits_sync(organization)
39
40
  end
41
+ done!
40
42
  end
41
43
 
42
44
  def perform_hits_sync(organization)
@@ -4,10 +4,11 @@ module InsightsCloud
4
4
  module Async
5
5
  class InsightsResolutionsSync < ::Actions::EntryAction
6
6
  include ::ForemanRhCloud::CertAuth
7
+ include ::ForemanRhCloud::Async::ExponentialBackoff
7
8
 
8
9
  RULE_ID_REGEX = /[^:]*:(?<id>.*)/
9
10
 
10
- def run
11
+ def try_execute
11
12
  InsightsResolution.transaction do
12
13
  InsightsResolution.delete_all
13
14
  rule_ids = relevant_rules
@@ -18,6 +19,7 @@ module InsightsCloud
18
19
  rule_ids -= Array(written_rules)
19
20
  end
20
21
  end
22
+ done!
21
23
  end
22
24
 
23
25
  def logger
@@ -4,6 +4,7 @@ module InsightsCloud
4
4
  module Async
5
5
  class InsightsRulesSync < ::Actions::EntryAction
6
6
  include ::ForemanRhCloud::CertAuth
7
+ include ::ForemanRhCloud::Async::ExponentialBackoff
7
8
 
8
9
  def plan(organizations)
9
10
  # since the tasks are not connected, we need to force sequence execution here
@@ -18,7 +19,7 @@ module InsightsCloud
18
19
  plan_action InsightsResolutionsSync
19
20
  end
20
21
 
21
- def run
22
+ def try_execute
22
23
  offset = 0
23
24
  InsightsRule.transaction do
24
25
  organizations.each do |organization|
@@ -36,6 +37,7 @@ module InsightsCloud
36
37
  # Remove all rules that do not have hits associated with them
37
38
  cleanup_rules
38
39
  end
40
+ done!
39
41
  end
40
42
 
41
43
  def logger
@@ -2,6 +2,7 @@ module InsightsCloud
2
2
  module Async
3
3
  class InsightsScheduledSync < ::Actions::EntryAction
4
4
  include ::Actions::RecurringAction
5
+ include ForemanInventoryUpload::Async::DelayedStart
5
6
 
6
7
  def plan
7
8
  unless Setting[:allow_auto_insights_sync]
@@ -12,7 +13,9 @@ module InsightsCloud
12
13
  return
13
14
  end
14
15
 
15
- plan_full_sync
16
+ after_delay do
17
+ plan_full_sync
18
+ end
16
19
  end
17
20
 
18
21
  def plan_full_sync
@@ -11,7 +11,7 @@ module InventorySync
11
11
  @per_page = result['per_page']
12
12
  @sub_ids = result["results"].map { |host| host['subscription_manager_id'] }
13
13
  @uuid_by_sub_id = Hash[result["results"].map { |host| [host['subscription_manager_id'], host['id']] }]
14
- @uuid_by_fqdn = Hash[result["results"].map { |host| [host['fqdn'].downcase, host['id']] }]
14
+ @uuid_by_fqdn = Hash[result["results"].map { |host| [host['fqdn']&.downcase, host['id']] }]
15
15
  end
16
16
 
17
17
  def status_hashes
@@ -2,6 +2,7 @@ module InventorySync
2
2
  module Async
3
3
  class InventoryScheduledSync < ::Actions::EntryAction
4
4
  include ::Actions::RecurringAction
5
+ include ForemanInventoryUpload::Async::DelayedStart
5
6
 
6
7
  def plan
7
8
  unless Setting[:allow_auto_inventory_upload]
@@ -12,8 +13,10 @@ module InventorySync
12
13
  return
13
14
  end
14
15
 
15
- Organization.unscoped.each do |org|
16
- plan_org_sync(org)
16
+ after_delay do
17
+ Organization.unscoped.each do |org|
18
+ plan_org_sync(org)
19
+ end
17
20
  end
18
21
  end
19
22
 
@@ -5,6 +5,7 @@ module InventorySync
5
5
  class QueryInventoryJob < ::Actions::EntryAction
6
6
  include ActiveSupport::Callbacks
7
7
  include ::ForemanRhCloud::CertAuth
8
+ include ::ForemanRhCloud::Async::ExponentialBackoff
8
9
 
9
10
  define_callbacks :iteration, :step
10
11
 
@@ -18,7 +19,7 @@ module InventorySync
18
19
  plan_self(actual_params)
19
20
  end
20
21
 
21
- def run
22
+ def try_execute
22
23
  run_callbacks :iteration do
23
24
  organizations.each do |organization|
24
25
  if !cert_auth_available?(organization) || organization.manifest_expired?
@@ -43,6 +44,8 @@ module InventorySync
43
44
  end
44
45
  end
45
46
  end
47
+ # declare the action as finished to stop iterations
48
+ done!
46
49
  end
47
50
 
48
51
  private
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "6.0.43",
3
+ "version": "7.0.45",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,14 +22,14 @@
22
22
  "url": "http://projects.theforeman.org/projects/foreman_rh_cloud/issues"
23
23
  },
24
24
  "peerDependencies": {
25
- "@theforeman/vendor": ">= 8.16.0"
25
+ "@theforeman/vendor": ">= 10.1.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@babel/core": "~7.7.0",
29
- "@theforeman/builder": ">= 8.16.0",
30
- "@theforeman/stories": ">= 8.16.0",
31
- "@theforeman/test": ">= 8.16.0",
32
- "@theforeman/eslint-plugin-foreman": ">= 8.16.0",
29
+ "@theforeman/builder": ">= 10.1.1",
30
+ "@theforeman/stories": ">= 10.1.1",
31
+ "@theforeman/test": ">= 10.1.1",
32
+ "@theforeman/eslint-plugin-foreman": ">= 10.1.1",
33
33
  "babel-eslint": "~10.0.0",
34
34
  "eslint": "~6.7.2",
35
35
  "eslint-plugin-spellcheck": "~0.0.17",
@@ -72,6 +72,17 @@ module InsightsCloud::Api
72
72
  assert_equal etag, @response.headers[Rack::ETAG]
73
73
  end
74
74
 
75
+ test "should set content type header to response from cloud" do
76
+ req = RestClient::Request.new(:method => 'GET', :url => 'http://test.theforeman.org')
77
+ net_http_resp = Net::HTTPResponse.new(1.0, 200, "OK")
78
+ net_http_resp[:content_type] = 'application/zip'
79
+ res = RestClient::Response.create(@body, net_http_resp, req)
80
+ ::ForemanRhCloud::CloudRequestForwarder.any_instance.stubs(:forward_request).returns(res)
81
+
82
+ get :forward_request, params: { "path" => "static/v1/release/insights-core.egg" }
83
+ assert_equal net_http_resp[:content_type], @response.headers['Content-Type']
84
+ end
85
+
75
86
  test "should handle failed authentication to cloud" do
76
87
  net_http_resp = Net::HTTPResponse.new(1.0, 401, "Unauthorized")
77
88
  res = RestClient::Response.create(@body, net_http_resp, @http_req)
@@ -0,0 +1,45 @@
1
+ require 'test_plugin_helper'
2
+ require 'foreman_tasks/test_helpers'
3
+
4
+ class ExponentialBackoffTest < ActiveSupport::TestCase
5
+ include ForemanTasks::TestHelpers::WithInThreadExecutor
6
+
7
+ class TestAction < ::Actions::EntryAction
8
+ include ::ForemanRhCloud::Async::ExponentialBackoff
9
+
10
+ def try_execute
11
+ action_callback&.call(self)
12
+ end
13
+
14
+ # define a method to execute code inside the class context.
15
+ def action_callback(instance)
16
+ end
17
+ end
18
+
19
+ test 'executes an action once' do
20
+ TestAction.any_instance.expects(:action_callback).returns(->(instance) { instance.done! })
21
+
22
+ ForemanTasks.sync_task(TestAction)
23
+ end
24
+
25
+ test 'fails after a single excution if done was called' do
26
+ TestAction.any_instance.expects(:action_callback).returns(
27
+ lambda do |instance|
28
+ instance.done!
29
+ raise ::Foreman::Exception('Foo')
30
+ end)
31
+
32
+ ForemanTasks.sync_task(TestAction)
33
+ end
34
+
35
+ test 'executes the task three times before failing it' do
36
+ # speed up the execution
37
+ TestAction.any_instance.stubs(:poll_intervals).returns([0, 0, 0])
38
+
39
+ TestAction.any_instance.expects(:action_callback).raises(::Foreman::Exception.new('Foo')).times(3)
40
+
41
+ ForemanTasks.sync_task(TestAction)
42
+ rescue ForemanTasks::TaskError => ex
43
+ assert ex.aggregated_message =~ /Foo/
44
+ end
45
+ end
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Spinner, Button } from '@patternfly/react-core';
4
- import { RedoIcon } from '@patternfly/react-icons';
5
4
  import { STATUS } from 'foremanReact/constants';
6
5
  import { SYNC_BUTTON_TEXT } from '../../../../ForemanInventoryConstants';
7
6
 
@@ -27,7 +26,7 @@ class SyncButton extends React.Component {
27
26
  isDisabled={status === STATUS.PENDING}
28
27
  variant="secondary"
29
28
  >
30
- {status === STATUS.PENDING ? <Spinner size="sm" /> : <RedoIcon />}
29
+ {status === STATUS.PENDING && <Spinner size="sm" />}
31
30
  {SYNC_BUTTON_TEXT}
32
31
  </Button>
33
32
  </React.Fragment>
@@ -9,11 +9,6 @@ exports[`SyncButton rendering render with Props 1`] = `
9
9
  size="lg"
10
10
  variant="secondary"
11
11
  >
12
- <RedoIcon
13
- color="currentColor"
14
- noVerticalAlign={false}
15
- size="sm"
16
- />
17
12
  Sync inventory status
18
13
  </Button>
19
14
  </Fragment>
@@ -21,7 +21,7 @@ const fills = [
21
21
  slot: 'host-overview-cards',
22
22
  name: 'insights-total-risk-chart',
23
23
  component: props => <InsightsTotalRiskCard {...props} />,
24
- weight: 1100,
24
+ weight: 2800,
25
25
  },
26
26
  ];
27
27
 
@@ -31,6 +31,7 @@ const InsightsTable = ({
31
31
  error,
32
32
  isAllSelected,
33
33
  hideHost,
34
+ hostname,
34
35
  }) => {
35
36
  const { perPage: appPerPage } = useForemanSettings();
36
37
  const perPage = urlPerPage || appPerPage;
@@ -40,7 +41,7 @@ const InsightsTable = ({
40
41
  // acts as componentDidMount
41
42
  useEffect(() => {
42
43
  fetchInsights({ page, perPage, query, sortBy, sortOrder });
43
- }, []);
44
+ }, [hostname]);
44
45
 
45
46
  useEffect(() => {
46
47
  setRows(
@@ -104,6 +105,7 @@ InsightsTable.propTypes = {
104
105
  error: PropTypes.string,
105
106
  isAllSelected: PropTypes.bool,
106
107
  hideHost: PropTypes.bool,
108
+ hostname: PropTypes.string,
107
109
  };
108
110
 
109
111
  InsightsTable.defaultProps = {
@@ -118,6 +120,7 @@ InsightsTable.defaultProps = {
118
120
  error: '',
119
121
  isAllSelected: false,
120
122
  hideHost: false,
123
+ hostname: '',
121
124
  };
122
125
 
123
126
  export default InsightsTable;
@@ -121,6 +121,7 @@ const InsightsTotalRiskCard = ({ hostDetails: { id } }) => {
121
121
  dropdownItems={[
122
122
  <DropdownItem
123
123
  key="insights-tab"
124
+ ouiaId="insights-tab-dropdown-item"
124
125
  onClick={() => hashHistory.push(`/Insights`)}
125
126
  >
126
127
  {__('View all recommendations')}
@@ -131,7 +132,9 @@ const InsightsTotalRiskCard = ({ hostDetails: { id } }) => {
131
132
  status={status}
132
133
  emptyState={
133
134
  <Bullseye>
134
- <Title headingLevel="h4"> {__('No results found')} </Title>
135
+ <Title ouiaId="no-results-title" headingLevel="h4">
136
+ {__('No results found')}
137
+ </Title>
135
138
  </Bullseye>
136
139
  }
137
140
  >
@@ -36,7 +36,7 @@ const NewHostDetailsTab = ({ hostName, router }) => {
36
36
  router.push({ pathname: '/foreman_rh_cloud/insights_cloud' });
37
37
 
38
38
  const dropdownItems = [
39
- <DropdownItem key="insights-link">
39
+ <DropdownItem key="insights-link" ouiaId="insights-link">
40
40
  <a onClick={onSatInsightsClick}>{__('Go to Satellite Insights page')}</a>
41
41
  </DropdownItem>,
42
42
  ];
@@ -44,7 +44,7 @@ const NewHostDetailsTab = ({ hostName, router }) => {
44
44
  if (hits.length) {
45
45
  const { host_uuid: uuid } = hits[0];
46
46
  dropdownItems.push(
47
- <DropdownItem key="insights-advisor-link">
47
+ <DropdownItem key="insights-advisor-link" ouiaId="insights-advisor-link">
48
48
  <a
49
49
  href={redHatAdvisorSystems(uuid)}
50
50
  target="_blank"
@@ -71,6 +71,7 @@ const NewHostDetailsTab = ({ hostName, router }) => {
71
71
  <RemediationModal />
72
72
  <Dropdown
73
73
  className="insights-dropdown"
74
+ ouiaId="insights-dropdown"
74
75
  onSelect={() => setIsDropdownOpen(false)}
75
76
  toggle={
76
77
  <KebabToggle onToggle={isOpen => setIsDropdownOpen(isOpen)} />
@@ -81,7 +82,7 @@ const NewHostDetailsTab = ({ hostName, router }) => {
81
82
  />
82
83
  </GridItem>
83
84
  <GridItem span={3}>
84
- <Pagination variant="top" isCompact />
85
+ <Pagination ouiaId="insights-pagination" variant="top" isCompact />
85
86
  </GridItem>
86
87
  <GridItem>
87
88
  <InsightsTable hideHost hostname={hostName} />