foreman_rh_cloud 3.0.19 → 3.0.20

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -2
  3. data/app/controllers/insights_cloud/hits_controller.rb +37 -0
  4. data/app/controllers/insights_cloud/settings_controller.rb +1 -1
  5. data/app/controllers/insights_cloud/tasks_controller.rb +1 -2
  6. data/app/models/inventory_sync/inventory_status.rb +6 -0
  7. data/app/models/setting/rh_cloud.rb +5 -5
  8. data/app/services/foreman_rh_cloud/remediations_retriever.rb +78 -0
  9. data/app/services/foreman_rh_cloud/template_renderer_helper.rb +22 -0
  10. data/app/views/job_templates/rh_cloud_remediations.erb +14 -0
  11. data/config/routes.rb +1 -0
  12. data/db/migrate/20210404000001_change_resolutions.foreman_rh_cloud.rb +10 -0
  13. data/db/seeds.d/50_job_templates.rb +14 -0
  14. data/lib/foreman_rh_cloud/engine.rb +13 -1
  15. data/lib/foreman_rh_cloud/version.rb +1 -1
  16. data/lib/insights_cloud.rb +12 -0
  17. data/lib/insights_cloud/async/insights_full_sync.rb +16 -6
  18. data/lib/insights_cloud/async/insights_resolutions_sync.rb +69 -0
  19. data/lib/insights_cloud/async/insights_rules_sync.rb +13 -17
  20. data/lib/insights_cloud/async/insights_scheduled_sync.rb +1 -1
  21. data/lib/tasks/rh_cloud_inventory.rake +1 -1
  22. data/package.json +1 -1
  23. data/test/factories/insights_factories.rb +22 -0
  24. data/test/jobs/insights_full_sync_test.rb +12 -8
  25. data/test/jobs/insights_resolutions_sync_test.rb +74 -0
  26. data/test/jobs/insights_rules_sync_test.rb +5 -3
  27. data/test/jobs/inventory_full_sync_test.rb +1 -1
  28. data/test/unit/services/foreman_rh_cloud/remediations_retriever_test.rb +49 -0
  29. data/test/unit/services/foreman_rh_cloud/template_renderer_helper_test.rb +28 -0
  30. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/AdvancedSettingsConstants.js +5 -3
  31. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/PageDescription/PageDescription.js +15 -2
  32. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/PageDescription/__tests__/__snapshots__/PageDescription.test.js.snap +13 -2
  33. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +1 -1
  34. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableSelectors.js +3 -0
  35. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediateButton.js +59 -0
  36. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationActions.js +12 -0
  37. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +43 -0
  38. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +101 -0
  39. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.scss +9 -0
  40. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModalFooter.js +43 -0
  41. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationTableConstants.js +38 -0
  42. data/webpack/InsightsCloudSync/Components/RemediationModal/Resolutions.js +55 -0
  43. data/webpack/InsightsCloudSync/Components/RemediationModal/index.js +34 -0
  44. data/webpack/InsightsCloudSync/InsightsCloudSync.js +8 -3
  45. data/webpack/InsightsCloudSync/InsightsCloudSync.scss +5 -0
  46. data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +9 -6
  47. data/webpack/{InsightsCloudSync/Components/InsightsTable/components → common/table}/EmptyState.js +0 -0
  48. data/webpack/common/table/helpers.js +7 -0
  49. metadata +33 -9
@@ -2,13 +2,21 @@ require 'rest-client'
2
2
 
3
3
  module InsightsCloud
4
4
  module Async
5
- class InsightsRulesSync < ::ApplicationJob
5
+ class InsightsRulesSync < ::Actions::EntryAction
6
6
  include ::ForemanRhCloud::CloudAuth
7
7
 
8
- def perform
8
+ def plan
9
+ plan_self
10
+ plan_resolutions
11
+ end
12
+
13
+ def plan_resolutions
14
+ plan_action InsightsResolutionsSync
15
+ end
16
+
17
+ def run
9
18
  offset = 0
10
19
  InsightsRule.transaction do
11
- InsightsResolution.delete_all
12
20
  InsightsRule.delete_all
13
21
  loop do
14
22
  api_response = query_insights_rules(offset)
@@ -16,13 +24,14 @@ module InsightsCloud
16
24
  logger.debug("Downloaded #{offset + results.count} of #{results.total}")
17
25
  write_rules_page(results.rules)
18
26
  offset += results.count
27
+ output[:rules_count] = results.total
19
28
  break if offset >= results.total
20
29
  end
21
30
  end
22
31
  end
23
32
 
24
33
  def logger
25
- Foreman::Logging.logger('background')
34
+ action_logger
26
35
  end
27
36
 
28
37
  private
@@ -43,12 +52,8 @@ module InsightsCloud
43
52
 
44
53
  def write_rules_page(rules)
45
54
  rules_attributes = rules.map { |rule| to_rule_hash(rule) }
46
- resolutions_attributes = rules.map do |rule|
47
- rule['resolution_set'].map { |resolution| to_resolution_hash(rule['rule_id'], resolution) }
48
- end.flatten
49
55
 
50
56
  InsightsRule.create(rules_attributes)
51
- InsightsResolution.create(resolutions_attributes)
52
57
  end
53
58
 
54
59
  def to_rule_hash(rule_hash)
@@ -66,15 +71,6 @@ module InsightsCloud
66
71
  rating: rule_hash['rating'],
67
72
  }
68
73
  end
69
-
70
- def to_resolution_hash(rule_id, resolution_hash)
71
- {
72
- rule_id: rule_id,
73
- system_type: resolution_hash['system_type'],
74
- resolution: resolution_hash['resolution'],
75
- has_playbook: resolution_hash['has_playbook'],
76
- }
77
- end
78
74
  end
79
75
  end
80
76
  end
@@ -10,7 +10,7 @@ module InsightsCloud
10
10
  return
11
11
  end
12
12
 
13
- InsightsFullSync.perform_later()
13
+ ForemanTasks.async_task InsightsFullSync
14
14
  ensure
15
15
  self.class.set(:wait => 24.hours).perform_later
16
16
  end
@@ -54,7 +54,7 @@ namespace :rh_cloud_inventory do
54
54
 
55
55
  desc "Synchronize Insights hosts hits"
56
56
  task sync: :environment do
57
- InsightsCloud::Async::InsightsFullSync.perform_now()
57
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsFullSync)
58
58
  puts "Synchronized Insights hosts hits data"
59
59
  end
60
60
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "3.0.19",
3
+ "version": "3.0.20",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -19,6 +19,28 @@ FactoryBot.define do
19
19
  sequence(:results_url) { |n| "https://cloud.redhat.com/insights/overview/stability/test_rule%7CTEST_RULE/accdf444-5628-451d-bf3e-cf909ad7275#{n}/" }
20
20
  rule_id { "test_rule|TEST_RULE" }
21
21
  end
22
+
23
+ factory :insights_rule do
24
+ sequence(:rule_id) { |n| "test_rule#{n}|TEST_RULE#{n}" }
25
+ description { 'test rule description' }
26
+ category_name { 'Testing' }
27
+ impact_name { 'Testing error' }
28
+ summary { 'Testing summary' }
29
+ generic { 'Testing generic' }
30
+ reason { 'No apparent reason' }
31
+ total_risk { -1 }
32
+ reboot_required { true }
33
+ more_info { 'more test info' }
34
+ rating { 0 }
35
+ end
36
+
37
+ factory :insights_resolution do
38
+ sequence(:rule_id) { |n| "test_rule#{n}|TEST_RULE#{n}" }
39
+ description { 'fix the issue on the fly' }
40
+ needs_reboot { false }
41
+ resolution_risk { 1 }
42
+ resolution_type { 'fix' }
43
+ end
22
44
  end
23
45
 
24
46
  FactoryBot.modify do
@@ -1,7 +1,9 @@
1
1
  require 'test_helper'
2
2
 
3
- class InsightsFullSyncTest < ActiveJob::TestCase
3
+ class InsightsFullSyncTest < ActiveSupport::TestCase
4
4
  setup do
5
+ InsightsCloud::Async::InsightsFullSync.any_instance.stubs(:plan_rules_sync)
6
+
5
7
  uuid1 = 'accdf444-5628-451d-bf3e-cf909ad72756'
6
8
  @host1 = FactoryBot.create(:host, :managed, name: 'host1')
7
9
  FactoryBot.create(:insights_facet, host_id: @host1.id, uuid: uuid1)
@@ -56,8 +58,9 @@ class InsightsFullSyncTest < ActiveJob::TestCase
56
58
  test 'Hits data is replaced with data from cloud' do
57
59
  InsightsCloud::Async::InsightsFullSync.any_instance.expects(:query_insights_hits).returns(@hits)
58
60
 
59
- ForemanTasks.expects(:sync_task).with(InventorySync::Async::InventoryHostsSync)
60
- InsightsCloud::Async::InsightsFullSync.perform_now()
61
+ InsightsCloud::Async::InsightsFullSync.any_instance.expects(:plan_hosts_sync)
62
+ InsightsCloud::Async::InsightsFullSync.any_instance.expects(:plan_rules_sync)
63
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsFullSync)
61
64
 
62
65
  @host1.reload
63
66
  @host2.reload
@@ -68,11 +71,12 @@ class InsightsFullSyncTest < ActiveJob::TestCase
68
71
 
69
72
  test 'Hits counters are reset correctly' do
70
73
  InsightsCloud::Async::InsightsFullSync.any_instance.expects(:query_insights_hits).returns(@hits).twice
71
- ForemanTasks.stubs(:sync_task)
72
74
 
73
- InsightsCloud::Async::InsightsFullSync.perform_now()
75
+ InsightsCloud::Async::InsightsFullSync.any_instance.stubs(:plan_hosts_sync)
76
+
77
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsFullSync)
74
78
  # Invoke again
75
- InsightsCloud::Async::InsightsFullSync.perform_now()
79
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsFullSync)
76
80
 
77
81
  @host1.reload
78
82
  @host2.reload
@@ -101,10 +105,10 @@ class InsightsFullSyncTest < ActiveJob::TestCase
101
105
  HITS_JSON
102
106
  hits = JSON.parse(hits_json)
103
107
 
104
- ForemanTasks.stubs(:sync_task)
108
+ InsightsCloud::Async::InsightsFullSync.any_instance.stubs(:plan_hosts_sync)
105
109
  InsightsCloud::Async::InsightsFullSync.any_instance.expects(:query_insights_hits).returns(hits)
106
110
 
107
- InsightsCloud::Async::InsightsFullSync.perform_now()
111
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsFullSync)
108
112
 
109
113
  @host1.reload
110
114
  @host2.reload
@@ -0,0 +1,74 @@
1
+ require 'test_helper'
2
+
3
+ class InsightsResolutionsSyncTest < ActiveSupport::TestCase
4
+ setup do
5
+ @resolutions = {
6
+ "advisor:ansible_deprecated_repo|ANSIBLE_DEPRECATED_REPO" => {
7
+ "id" => "advisor:ansible_deprecated_repo|ANSIBLE_DEPRECATED_REPO",
8
+ "resolution_risk" => 1,
9
+ "resolutions" => [
10
+ {
11
+ "description" => "Enable ansible repo and update ansible package",
12
+ "id" => "fix",
13
+ "needs_reboot" => false,
14
+ "resolution_risk" => 1,
15
+ },
16
+ ],
17
+ },
18
+ "advisor:hardening_logging_auditd|HARDENING_LOGGING_5_AUDITD" => {
19
+ "id" => "advisor:hardening_logging_auditd|HARDENING_LOGGING_5_AUDITD",
20
+ "resolution_risk" => 1,
21
+ "resolutions" => [
22
+ {
23
+ "description" => "Install and enable auditd",
24
+ "id" => "fix",
25
+ "needs_reboot" => false,
26
+ "resolution_risk" => 1,
27
+ },
28
+ ],
29
+ },
30
+ "advisor:network_manager_dhcp_client_network_issue|NETWORK_MANAGER_DHCP_CLIENT_NETWORK_ISSUE" => {
31
+ "id" => "advisor:network_manager_dhcp_client_network_issue|NETWORK_MANAGER_DHCP_CLIENT_NETWORK_ISSUE",
32
+ "resolution_risk" => 1,
33
+ "resolutions" => [
34
+ {
35
+ "description" => "Update the NetworkManager package to fix this issue",
36
+ "id" => "fix",
37
+ "needs_reboot" => false,
38
+ "resolution_risk" => 1,
39
+ },
40
+ ],
41
+ },
42
+ "advisor:network_tcp_connection_hang|NETWORK_TCP_CONNECTION_HANG_WARN" => {
43
+ "id" => "advisor:network_tcp_connection_hang|NETWORK_TCP_CONNECTION_HANG_WARN",
44
+ "resolution_risk" => 3,
45
+ "resolutions" => [
46
+ {
47
+ "description" => "Update system to the latest kernel and reboot",
48
+ "id" => "kernel_update",
49
+ "needs_reboot" => true,
50
+ "resolution_risk" => 3,
51
+ },
52
+ {
53
+ "description" => "Run in panic and scream",
54
+ "id" => "panic",
55
+ "needs_reboot" => true,
56
+ "resolution_risk" => 10,
57
+ },
58
+ ],
59
+ },
60
+ }
61
+
62
+ @rule = FactoryBot.create(:insights_rule, rule_id: 'network_tcp_connection_hang|NETWORK_TCP_CONNECTION_HANG_WARN')
63
+ end
64
+
65
+ test 'Resolutions data is replaced with data from cloud' do
66
+ InsightsCloud::Async::InsightsResolutionsSync.any_instance.stubs(:query_insights_resolutions).returns(@resolutions)
67
+
68
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsResolutionsSync)
69
+ @rule.reload
70
+
71
+ assert_equal 5, InsightsResolution.all.count
72
+ assert_equal 2, @rule.resolutions.count
73
+ end
74
+ end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class InsightsRulesSyncTest < ActiveJob::TestCase
3
+ class InsightsRulesSyncTest < ActiveSupport::TestCase
4
4
  setup do
5
5
  rules_json = <<-'RULES_JSON'
6
6
  {
@@ -107,12 +107,14 @@ class InsightsRulesSyncTest < ActiveJob::TestCase
107
107
  @rules = JSON.parse(rules_json)
108
108
  @host = FactoryBot.create(:host, :managed, name: 'host1')
109
109
  @hit = FactoryBot.create(:insights_hit, host_id: @host.id)
110
+
111
+ InsightsCloud::Async::InsightsRulesSync.any_instance.stubs(:plan_resolutions)
110
112
  end
111
113
 
112
114
  test 'Hits data is replaced with data from cloud' do
113
115
  InsightsCloud::Async::InsightsRulesSync.any_instance.expects(:query_insights_rules).returns(@rules)
114
116
 
115
- InsightsCloud::Async::InsightsRulesSync.perform_now()
117
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsRulesSync)
116
118
  @hit.reload
117
119
 
118
120
  assert_equal 2, InsightsRule.all.count
@@ -191,7 +193,7 @@ class InsightsRulesSyncTest < ActiveJob::TestCase
191
193
  InsightsCloud::Async::InsightsRulesSync.any_instance.
192
194
  stubs(:query_insights_rules).returns(@rules).then.returns(@last_rule)
193
195
 
194
- InsightsCloud::Async::InsightsRulesSync.perform_now()
196
+ ForemanTasks.sync_task(InsightsCloud::Async::InsightsRulesSync)
195
197
 
196
198
  assert_equal 3, InsightsRule.all.count
197
199
  end
@@ -1,6 +1,6 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
- class InventoryFullSyncTest < ActiveJob::TestCase
3
+ class InventoryFullSyncTest < ActiveSupport::TestCase
4
4
  setup do
5
5
  User.current = User.find_by(login: 'secret_admin')
6
6
 
@@ -0,0 +1,49 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class RemediationsRetrieverTest < ActiveSupport::TestCase
4
+ setup do
5
+ @host1 = FactoryBot.create(:host)
6
+ @host2 = FactoryBot.create(:host)
7
+ end
8
+ test 'groups hosts together' do
9
+ rule1 = FactoryBot.create(:insights_rule)
10
+ rule1_remediation1 = FactoryBot.create(:insights_resolution, rule_id: rule1.rule_id, resolution_type: 'fix')
11
+
12
+ FactoryBot.create(:insights_facet, uuid: 'HOST1', host_id: @host1.id)
13
+ host1_hit1 = FactoryBot.create(:insights_hit, rule_id: rule1.rule_id, host_id: @host1.id)
14
+
15
+ FactoryBot.create(:insights_facet, uuid: 'HOST2', host_id: @host2.id)
16
+ host2_hit1 = FactoryBot.create(:insights_hit, rule_id: rule1.rule_id, host_id: @host2.id)
17
+
18
+ pairs = [{'hit_id' => host1_hit1.id, 'resolution_id' => rule1_remediation1.id}, {'hit_id' => host2_hit1.id, 'resolution_id' => rule1_remediation1.id}]
19
+ retriever = ForemanRhCloud::RemediationsRetriever.new(pairs)
20
+
21
+ actual_request = retriever.send(:playbook_request)
22
+
23
+ assert_not_nil (issues = actual_request[:issues])
24
+ assert_not_nil (resolution = issues.first)
25
+ assert_equal 'fix', resolution[:resolution]
26
+ assert_equal "advisor:#{rule1.rule_id}", resolution[:id]
27
+ assert_same_elements ['HOST1', 'HOST2'], resolution[:systems]
28
+ end
29
+
30
+ test 'does not group different resolutions' do
31
+ rule1 = FactoryBot.create(:insights_rule)
32
+ rule1_remediation1 = FactoryBot.create(:insights_resolution, rule_id: rule1.rule_id, resolution_type: 'fix')
33
+ rule1_remediation2 = FactoryBot.create(:insights_resolution, rule_id: rule1.rule_id, resolution_type: 'panic')
34
+
35
+ FactoryBot.create(:insights_facet, uuid: 'HOST1', host_id: @host1.id)
36
+ host1_hit1 = FactoryBot.create(:insights_hit, rule_id: rule1.rule_id, host_id: @host1.id)
37
+
38
+ FactoryBot.create(:insights_facet, uuid: 'HOST2', host_id: @host2.id)
39
+ host2_hit1 = FactoryBot.create(:insights_hit, rule_id: rule1.rule_id, host_id: @host2.id)
40
+
41
+ pairs = [{'hit_id' => host1_hit1.id, 'resolution_id' => rule1_remediation1.id}, {'hit_id' => host2_hit1.id, 'resolution_id' => rule1_remediation2.id}]
42
+ retriever = ForemanRhCloud::RemediationsRetriever.new(pairs)
43
+
44
+ actual_request = retriever.send(:playbook_request)
45
+
46
+ assert_not_nil (issues = actual_request[:issues])
47
+ assert_equal 2, issues.count
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class TemplateRendererHelperTest < ActiveSupport::TestCase
4
+ include ForemanRhCloud::TemplateRendererHelper
5
+
6
+ setup do
7
+ response = mock('respone')
8
+ response.stubs(:body).returns('TEST PLAYBOOK')
9
+ ForemanRhCloud::RemediationsRetriever.any_instance.stubs(:query_playbook).returns(response)
10
+ @host1 = FactoryBot.create(:host)
11
+ end
12
+
13
+ test 'Generates a playbook for hit and remediation' do
14
+ rule = FactoryBot.create(:insights_rule)
15
+ hit = FactoryBot.create(:insights_hit, rule: rule, host_id: @host1.id)
16
+ remediation = FactoryBot.create(:insights_resolution, rule: rule)
17
+
18
+ pairs = [{hit_id: hit.id, remediation_id: remediation.id}].to_json
19
+
20
+ actual_playbook = remediations_playbook(pairs)
21
+
22
+ assert_equal 'TEST PLAYBOOK', actual_playbook
23
+ end
24
+
25
+ def template_logger
26
+ Logger.new(IO::NULL)
27
+ end
28
+ end
@@ -3,7 +3,7 @@ import { translate as __ } from 'foremanReact/common/I18n';
3
3
  export const settingsDict = {
4
4
  autoUploadEnabled: {
5
5
  name: 'allow_auto_inventory_upload',
6
- label: __('Auto upload'),
6
+ label: __('Automatic inventory upload'),
7
7
  tooltip: __(
8
8
  'Enable automatic upload of your hosts inventory to the Red Hat cloud'
9
9
  ),
@@ -20,7 +20,9 @@ export const settingsDict = {
20
20
  },
21
21
  excludePackagesEnabled: {
22
22
  name: 'exclude_installed_packages',
23
- label: __('Exclude Packages'),
24
- tooltip: __('Exclude packages from being uploaded to the Red Hat cloud'),
23
+ label: __('Exclude installed Packages'),
24
+ tooltip: __(
25
+ 'Exclude installed packages from being uploaded to the Red Hat cloud'
26
+ ),
25
27
  },
26
28
  };
@@ -10,14 +10,27 @@ export const PageDescription = () => (
10
10
  </p>
11
11
  <p>
12
12
  {__(
13
- 'In order to utilize these services, you can set the auto upload in the settings to "ON".'
13
+ 'You can toggle the Auto upload switch to the ON position to enable Satellite to automatically upload your host inventory once a day.'
14
14
  )}
15
15
  </p>
16
16
  <p>
17
17
  {__(
18
- 'You can also trigger the upload manually by opening the relevant organization card, and clicking on the "Restart" button'
18
+ 'Click Restart to upload your host inventory to Red Hat Insights. Perform this step for each organization from which you want to manually upload a host inventory.'
19
19
  )}
20
20
  </p>
21
+ <p>
22
+ {__(
23
+ 'Enabling inventory uploads is required by subscription watch. For more information about subscription watch see link:'
24
+ )}
25
+ &nbsp;
26
+ <a
27
+ href="https://access.redhat.com/documentation/en-us/subscription_central/2020-04/html/getting_started_with_subscription_watch/assembly-about-subscriptionwatch"
28
+ target="_blank"
29
+ rel="noopener noreferrer"
30
+ >
31
+ About subscription watch
32
+ </a>
33
+ </p>
21
34
  </div>
22
35
  );
23
36
 
@@ -8,10 +8,21 @@ exports[`PageDescription rendering render without Props 1`] = `
8
8
  Red Hat Insights is a set of cloud services which provide unified subscription reporting, predictive analysis and remediation of issues through this Satellite instance.
9
9
  </p>
10
10
  <p>
11
- In order to utilize these services, you can set the auto upload in the settings to "ON".
11
+ You can toggle the Auto upload switch to the ON position to enable Satellite to automatically upload your host inventory once a day.
12
12
  </p>
13
13
  <p>
14
- You can also trigger the upload manually by opening the relevant organization card, and clicking on the "Restart" button
14
+ Click Restart to upload your host inventory to Red Hat Insights. Perform this step for each organization from which you want to manually upload a host inventory.
15
+ </p>
16
+ <p>
17
+ Enabling inventory uploads is required by subscription watch. For more information about subscription watch see link:
18
+  
19
+ <a
20
+ href="https://access.redhat.com/documentation/en-us/subscription_central/2020-04/html/getting_started_with_subscription_watch/assembly-about-subscriptionwatch"
21
+ rel="noopener noreferrer"
22
+ target="_blank"
23
+ >
24
+ About subscription watch
25
+ </a>
15
26
  </p>
16
27
  </div>
17
28
  `;