foreman_rh_cloud 12.2.9 → 12.2.11
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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/foreman_rh_cloud/locale/fr/foreman_rh_cloud.js +47 -26
- data/app/assets/javascripts/foreman_rh_cloud/locale/ja/foreman_rh_cloud.js +46 -25
- data/app/assets/javascripts/foreman_rh_cloud/locale/ka/foreman_rh_cloud.js +32 -11
- data/app/assets/javascripts/foreman_rh_cloud/locale/ko/foreman_rh_cloud.js +46 -25
- data/app/assets/javascripts/foreman_rh_cloud/locale/zh_CN/foreman_rh_cloud.js +46 -25
- data/app/controllers/concerns/insights_cloud/package_profile_upload_extensions.rb +2 -3
- data/app/services/foreman_rh_cloud/cert_auth.rb +13 -3
- data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +3 -1
- data/app/services/foreman_rh_cloud/tags_auth.rb +2 -1
- data/lib/foreman_inventory_upload/async/create_missing_insights_facets.rb +29 -0
- data/lib/foreman_inventory_upload/async/generate_host_report.rb +20 -0
- data/lib/foreman_inventory_upload/async/generate_report_job.rb +1 -1
- data/lib/foreman_inventory_upload/async/host_inventory_report_job.rb +39 -0
- data/lib/foreman_inventory_upload/async/single_host_report_job.rb +20 -0
- data/lib/foreman_inventory_upload/async/upload_report_job.rb +2 -1
- data/lib/foreman_inventory_upload/generators/fact_helpers.rb +2 -2
- data/lib/foreman_inventory_upload/generators/slice.rb +3 -3
- data/lib/foreman_inventory_upload/scripts/uploader.sh.erb +7 -1
- data/lib/foreman_rh_cloud/plugin.rb +9 -9
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/tasks/rh_cloud_inventory.rake +14 -32
- data/locale/foreman_rh_cloud.pot +55 -18
- data/locale/fr/foreman_rh_cloud.po +48 -27
- data/locale/ja/foreman_rh_cloud.po +47 -26
- data/locale/ka/foreman_rh_cloud.po +32 -11
- data/locale/ko/foreman_rh_cloud.po +47 -26
- data/locale/zh_CN/foreman_rh_cloud.po +47 -26
- data/package.json +1 -1
- data/test/unit/fact_helpers_test.rb +47 -0
- data/test/unit/slice_generator_test.rb +57 -0
- data/webpack/InsightsHostDetailsTab/InsightsTotalRiskChart.js +57 -21
- data/webpack/InsightsHostDetailsTab/__tests__/InsightsTotalRiskChart.test.js +194 -0
- data/webpack/InsightsVulnerabilityHostIndexExtensions/CVECountCell.js +8 -2
- data/webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js +48 -2
- metadata +6 -2
- data/app/services/foreman_rh_cloud/gateway_request.rb +0 -26
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
# 0868a4d1af5275b3f70b0a6dac4c99a4, 2023
|
|
10
10
|
# Amit Upadhye <aupadhye@redhat.com>, 2023
|
|
11
11
|
# Ewoud Kohl van Wijngaarden <ewoud+transifex@kohlvanwijngaarden.nl>, 2025
|
|
12
|
+
# Ondřej Gajdušek, 2025
|
|
12
13
|
#
|
|
13
14
|
msgid ""
|
|
14
15
|
msgstr ""
|
|
15
|
-
"Project-Id-Version: foreman_rh_cloud
|
|
16
|
+
"Project-Id-Version: foreman_rh_cloud 13.0.5\n"
|
|
16
17
|
"Report-Msgid-Bugs-To: \n"
|
|
17
18
|
"PO-Revision-Date: 2023-01-20 13:26+0000\n"
|
|
18
|
-
"Last-Translator:
|
|
19
|
-
"n.nl>, 2025\n"
|
|
19
|
+
"Last-Translator: Ondřej Gajdušek, 2025\n"
|
|
20
20
|
"Language-Team: Chinese (China) (https://app.transifex.com/foreman/teams/114/zh"
|
|
21
21
|
"_CN/)\n"
|
|
22
22
|
"MIME-Version: 1.0\n"
|
|
@@ -44,7 +44,7 @@ msgid "All recommendations are now selected."
|
|
|
44
44
|
msgstr "现在选择了所有建议。"
|
|
45
45
|
|
|
46
46
|
msgid "Analytics data collection"
|
|
47
|
-
msgstr ""
|
|
47
|
+
msgstr "数据收集分析"
|
|
48
48
|
|
|
49
49
|
msgid "Any Organization"
|
|
50
50
|
msgstr "任意机构"
|
|
@@ -127,11 +127,17 @@ msgstr "下载最新的报告"
|
|
|
127
127
|
msgid "Enable automatic deletion of mismatched host records from the Red Hat cloud"
|
|
128
128
|
msgstr "启用自动删除来自红帽云的主机记录"
|
|
129
129
|
|
|
130
|
+
msgid "Enable automatic deletion of mismatched host records from the Red Hat cloud. Ignored when using local Insights."
|
|
131
|
+
msgstr ""
|
|
132
|
+
|
|
130
133
|
msgid "Enable automatic synchronization of Insights recommendations from the Red Hat cloud"
|
|
131
134
|
msgstr "启用来自红帽云的 Insights 建议自动同步"
|
|
132
135
|
|
|
133
|
-
msgid "Enable automatic
|
|
134
|
-
msgstr "
|
|
136
|
+
msgid "Enable automatic synchronization of Insights recommendations from the Red Hat cloud. Ignored when using local Insights."
|
|
137
|
+
msgstr ""
|
|
138
|
+
|
|
139
|
+
msgid "Enable automatic upload of your host inventory to the Red Hat cloud. Ignored when using local Insights."
|
|
140
|
+
msgstr ""
|
|
135
141
|
|
|
136
142
|
msgid "Enable automatic upload of your hosts inventory to the Red Hat cloud"
|
|
137
143
|
msgstr "启用自动将主机清单上传到红帽云"
|
|
@@ -146,12 +152,12 @@ msgid "Encountered an error while trying to access the server:"
|
|
|
146
152
|
msgstr "在尝试访问服务器时遇到错误:"
|
|
147
153
|
|
|
148
154
|
msgid "Exclude installed packages"
|
|
149
|
-
msgstr ""
|
|
155
|
+
msgstr "排除安装的软件包"
|
|
150
156
|
|
|
151
157
|
msgid "Exclude installed packages from being uploaded to the Red Hat cloud"
|
|
152
158
|
msgstr "将安装的软件包上传到红帽云"
|
|
153
159
|
|
|
154
|
-
msgid "Exclude installed packages from being uploaded to the Red Hat cloud. (If insights_minimal_data_collection is set to true, this setting is ignored and installed packages are always excluded.)"
|
|
160
|
+
msgid "Exclude installed packages from being uploaded to the Red Hat cloud. (If insights_minimal_data_collection is set to true, this setting is ignored and installed packages are always excluded.) Ignored when using local Insights."
|
|
155
161
|
msgstr ""
|
|
156
162
|
|
|
157
163
|
msgid "Exit Code: %s"
|
|
@@ -188,7 +194,7 @@ msgid "Generate and upload report"
|
|
|
188
194
|
msgstr "生成并上传报告"
|
|
189
195
|
|
|
190
196
|
msgid "Generate report"
|
|
191
|
-
msgstr ""
|
|
197
|
+
msgstr "生成报告"
|
|
192
198
|
|
|
193
199
|
msgid "Generate the report, but do not upload"
|
|
194
200
|
msgstr "生成报告,但不上传"
|
|
@@ -217,6 +223,9 @@ msgstr "获取 RH Cloud 中缺少的主机"
|
|
|
217
223
|
msgid "Host Insights recommendations"
|
|
218
224
|
msgstr "主机 Insights 建议"
|
|
219
225
|
|
|
226
|
+
msgid "Host inventory report job"
|
|
227
|
+
msgstr ""
|
|
228
|
+
|
|
220
229
|
msgid "Host was not uploaded to your RH cloud inventory"
|
|
221
230
|
msgstr "主机没有上传到您的 RH 云清单"
|
|
222
231
|
|
|
@@ -260,7 +269,7 @@ msgid "Knowledgebase article"
|
|
|
260
269
|
msgstr "知识库文章"
|
|
261
270
|
|
|
262
271
|
msgid "Learn more about {minimalDataCollectionSetting}."
|
|
263
|
-
msgstr ""
|
|
272
|
+
msgstr "了解有关 {minimalDataCollectionSetting} 的更多信息。"
|
|
264
273
|
|
|
265
274
|
msgid "List of host UUIDs"
|
|
266
275
|
msgstr "主机 UUID 列表"
|
|
@@ -278,6 +287,9 @@ msgid "Manual"
|
|
|
278
287
|
msgstr "手册"
|
|
279
288
|
|
|
280
289
|
msgid "Minimal data collection"
|
|
290
|
+
msgstr "最小数据收集"
|
|
291
|
+
|
|
292
|
+
msgid "Missing Insights facets created: %s"
|
|
281
293
|
msgstr ""
|
|
282
294
|
|
|
283
295
|
msgid "Moderate"
|
|
@@ -314,7 +326,7 @@ msgid "Obfuscate host ipv4 addresses"
|
|
|
314
326
|
msgstr "模糊的主机 ipv4 地址"
|
|
315
327
|
|
|
316
328
|
msgid "Obfuscate host ipv4 addresses."
|
|
317
|
-
msgstr ""
|
|
329
|
+
msgstr "对主机 ipv4 地址进行模糊化处理"
|
|
318
330
|
|
|
319
331
|
msgid "Obfuscate host names"
|
|
320
332
|
msgstr "模糊主机名"
|
|
@@ -322,20 +334,20 @@ msgstr "模糊主机名"
|
|
|
322
334
|
msgid "Obfuscate host names sent to the Red Hat cloud"
|
|
323
335
|
msgstr "发送到红帽云的模糊主机名"
|
|
324
336
|
|
|
325
|
-
msgid "Obfuscate host names sent to the Red Hat cloud. (If insights_minimal_data_collection is set to true, this setting is ignored because host names are not included in the report.)"
|
|
337
|
+
msgid "Obfuscate host names sent to the Red Hat cloud. (If insights_minimal_data_collection is set to true, this setting is ignored because host names are not included in the report.) Ignored when using local Insights."
|
|
326
338
|
msgstr ""
|
|
327
339
|
|
|
328
340
|
msgid "Obfuscate ipv4 addresses sent to the Red Hat cloud"
|
|
329
341
|
msgstr "发送到红帽云的模糊 ipv4 地址"
|
|
330
342
|
|
|
331
|
-
msgid "Obfuscate ipv4 addresses sent to the Red Hat cloud. (If insights_minimal_data_collection is set to true, this setting is ignored because host IPv4 addresses are not included in the report.)"
|
|
343
|
+
msgid "Obfuscate ipv4 addresses sent to the Red Hat cloud. (If insights_minimal_data_collection is set to true, this setting is ignored because host IPv4 addresses are not included in the report.) Ignored when using local Insights."
|
|
332
344
|
msgstr ""
|
|
333
345
|
|
|
334
|
-
msgid "Only include the minimum required data in inventory reports for uploading to Red Hat cloud. When this is true, installed packages are excluded from the report regardless of the exclude_installed_packages setting, and host names and IPv4 addresses are excluded from the report regardless of obfuscation settings."
|
|
346
|
+
msgid "Only include the minimum required data in inventory reports for uploading to Red Hat cloud. When this is true, installed packages are excluded from the report regardless of the exclude_installed_packages setting, and host names and IPv4 addresses are excluded from the report regardless of obfuscation settings. Ignored when using local Insights."
|
|
335
347
|
msgstr ""
|
|
336
348
|
|
|
337
349
|
msgid "Only send the minimum required data to Red Hat cloud, obfuscation settings are disabled"
|
|
338
|
-
msgstr ""
|
|
350
|
+
msgstr "仅将最低所需的数据发送到红帽云,禁用模糊设置"
|
|
339
351
|
|
|
340
352
|
msgid "Oops! Couldn't find organization that matches your query"
|
|
341
353
|
msgstr "未找到与您的查询匹配的机构"
|
|
@@ -352,8 +364,8 @@ msgstr "运行 playbook"
|
|
|
352
364
|
msgid "RH Cloud"
|
|
353
365
|
msgstr "RH Cloud"
|
|
354
366
|
|
|
355
|
-
msgid "RHC daemon id"
|
|
356
|
-
msgstr "
|
|
367
|
+
msgid "RHC daemon id. Ignored when using local Insights."
|
|
368
|
+
msgstr ""
|
|
357
369
|
|
|
358
370
|
msgid "Read more about it in RH cloud insights"
|
|
359
371
|
msgstr "在 RH Cloud insights 中了解更多有关它的信息"
|
|
@@ -413,7 +425,7 @@ msgid "Run RH Cloud playbook"
|
|
|
413
425
|
msgstr "运行 RH Cloud playbook"
|
|
414
426
|
|
|
415
427
|
msgid "Run playbook generated by Red Hat remediations app"
|
|
416
|
-
msgstr ""
|
|
428
|
+
msgstr "运行由红帽补救应用生成的 playbook"
|
|
417
429
|
|
|
418
430
|
msgid "Run remediation playbook generated by Insights"
|
|
419
431
|
msgstr "运行 Insights 生成的补救 playbook"
|
|
@@ -428,7 +440,7 @@ msgid "Select recommendations from all pages"
|
|
|
428
440
|
msgstr "在所有页面中选择建议"
|
|
429
441
|
|
|
430
442
|
msgid "Send additional data to enhance Insights services, as per the settings"
|
|
431
|
-
msgstr ""
|
|
443
|
+
msgstr "根据设置,发送额外数据以增强 Insights 服务"
|
|
432
444
|
|
|
433
445
|
msgid "Set the current organization context for the request"
|
|
434
446
|
msgstr "设置请求的当前机构上下文"
|
|
@@ -436,8 +448,8 @@ msgstr "设置请求的当前机构上下文"
|
|
|
436
448
|
msgid "Settings"
|
|
437
449
|
msgstr "设置"
|
|
438
450
|
|
|
439
|
-
msgid "Should import include parameter tags from Foreman?"
|
|
440
|
-
msgstr "
|
|
451
|
+
msgid "Should import include parameter tags from Foreman? Ignored when using local Insights."
|
|
452
|
+
msgstr ""
|
|
441
453
|
|
|
442
454
|
msgid "Show Advanced Settings"
|
|
443
455
|
msgstr "显示高级设置"
|
|
@@ -445,6 +457,12 @@ msgstr "显示高级设置"
|
|
|
445
457
|
msgid "Show if system is configured to use local iop-advisor-engine."
|
|
446
458
|
msgstr "显示系统是否被配置为使用本地 iop-advisor-engine。"
|
|
447
459
|
|
|
460
|
+
msgid "Single-host report job"
|
|
461
|
+
msgstr ""
|
|
462
|
+
|
|
463
|
+
msgid "Single-host report job for host %s"
|
|
464
|
+
msgstr ""
|
|
465
|
+
|
|
448
466
|
msgid "Start inventory synchronization"
|
|
449
467
|
msgstr "启动清单同步"
|
|
450
468
|
|
|
@@ -476,7 +494,7 @@ msgid "The report file %{filename} doesn't exist"
|
|
|
476
494
|
msgstr "报告文件 %{filename} 不存在"
|
|
477
495
|
|
|
478
496
|
msgid "The scheduled process is disabled because this Foreman is configured with a local IoP Smart Proxy."
|
|
479
|
-
msgstr ""
|
|
497
|
+
msgstr "调度的进程被禁用,因为此 Foreman 配置了一个本地 IoP Smart Proxy。"
|
|
480
498
|
|
|
481
499
|
msgid "The server returned the following error: %s"
|
|
482
500
|
msgstr "服务器返回以下错误:%s"
|
|
@@ -487,6 +505,9 @@ msgstr "任务失败,错误为:"
|
|
|
487
505
|
msgid "There are no recommendations for your hosts"
|
|
488
506
|
msgstr "没有适用于您的主机的建议"
|
|
489
507
|
|
|
508
|
+
msgid "There were no missing Insights facets"
|
|
509
|
+
msgstr ""
|
|
510
|
+
|
|
490
511
|
msgid "This action will also enable automatic reports upload"
|
|
491
512
|
msgstr "此操作还会启用自动报告上传"
|
|
492
513
|
|
|
@@ -497,7 +518,7 @@ msgid "To manually upload the data for a specific organization, select an organi
|
|
|
497
518
|
msgstr "要手动上传特定机构的数据,请选择机构并点 {restartButtonName}。"
|
|
498
519
|
|
|
499
520
|
msgid "Total CVEs"
|
|
500
|
-
msgstr ""
|
|
521
|
+
msgstr "CVE 总数"
|
|
501
522
|
|
|
502
523
|
msgid "Total risk"
|
|
503
524
|
msgstr "总风险"
|
|
@@ -527,10 +548,10 @@ msgid "View in Red Hat Insights"
|
|
|
527
548
|
msgstr "禁用 Red Hat Insights"
|
|
528
549
|
|
|
529
550
|
msgid "Vulnerabilities"
|
|
530
|
-
msgstr ""
|
|
551
|
+
msgstr "安全漏洞"
|
|
531
552
|
|
|
532
553
|
msgid "Vulnerability"
|
|
533
|
-
msgstr ""
|
|
554
|
+
msgstr "安全漏洞"
|
|
534
555
|
|
|
535
556
|
msgid "Wait and %s"
|
|
536
557
|
msgstr "等待和 %s"
|
|
@@ -599,7 +620,7 @@ msgid "rule title"
|
|
|
599
620
|
msgstr "rule 标题"
|
|
600
621
|
|
|
601
622
|
msgid "setting minimal data collection"
|
|
602
|
-
msgstr ""
|
|
623
|
+
msgstr "设置最小数据收集"
|
|
603
624
|
|
|
604
625
|
msgid "solution url"
|
|
605
626
|
msgstr "solution url"
|
data/package.json
CHANGED
|
@@ -313,4 +313,51 @@ class FactHelpersTest < ActiveSupport::TestCase
|
|
|
313
313
|
assert_equal '10.230.230.3', ip3
|
|
314
314
|
end
|
|
315
315
|
end
|
|
316
|
+
|
|
317
|
+
describe 'IoP smart proxy checks' do
|
|
318
|
+
test 'obfuscate_hostname? returns false when global setting is enabled but IoP is present' do
|
|
319
|
+
Setting.expects(:[]).with(:obfuscate_inventory_hostnames).returns(true)
|
|
320
|
+
ForemanRhCloud.expects(:with_iop_smart_proxy?).returns(true)
|
|
321
|
+
host = mock('host')
|
|
322
|
+
# When IoP is present, it falls back to checking host-specific facts
|
|
323
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_hostname_enabled').returns(nil)
|
|
324
|
+
|
|
325
|
+
result = @instance.obfuscate_hostname?(host)
|
|
326
|
+
|
|
327
|
+
refute result
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
test 'obfuscate_hostname? returns true when global setting is enabled and IoP is not present' do
|
|
331
|
+
Setting.expects(:[]).with(:obfuscate_inventory_hostnames).returns(true)
|
|
332
|
+
ForemanRhCloud.expects(:with_iop_smart_proxy?).returns(false)
|
|
333
|
+
host = mock('host')
|
|
334
|
+
|
|
335
|
+
result = @instance.obfuscate_hostname?(host)
|
|
336
|
+
|
|
337
|
+
assert result
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
test 'obfuscate_ips? returns false when global setting is enabled but IoP is present' do
|
|
341
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(true)
|
|
342
|
+
ForemanRhCloud.expects(:with_iop_smart_proxy?).returns(true)
|
|
343
|
+
host = mock('host')
|
|
344
|
+
# When IoP is present, it falls back to checking host-specific facts
|
|
345
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv4_enabled').returns(nil)
|
|
346
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv6_enabled').returns(nil)
|
|
347
|
+
|
|
348
|
+
result = @instance.obfuscate_ips?(host)
|
|
349
|
+
|
|
350
|
+
refute result
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
test 'obfuscate_ips? returns true when global setting is enabled and IoP is not present' do
|
|
354
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(true)
|
|
355
|
+
ForemanRhCloud.expects(:with_iop_smart_proxy?).returns(false)
|
|
356
|
+
host = mock('host')
|
|
357
|
+
|
|
358
|
+
result = @instance.obfuscate_ips?(host)
|
|
359
|
+
|
|
360
|
+
assert result
|
|
361
|
+
end
|
|
362
|
+
end
|
|
316
363
|
end
|
|
@@ -107,6 +107,36 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
|
107
107
|
assert_equal 'test_nic1', actual_nic['name']
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
test 'does not generate a report with minimal data collection when iop is present' do
|
|
111
|
+
Setting[:insights_minimal_data_collection] = true
|
|
112
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
113
|
+
|
|
114
|
+
batch = Host.where(id: @host.id).in_batches.first
|
|
115
|
+
generator = create_generator(batch)
|
|
116
|
+
|
|
117
|
+
json_str = generator.render
|
|
118
|
+
actual = JSON.parse(json_str.join("\n"))
|
|
119
|
+
|
|
120
|
+
assert_equal '00000000-0000-0000-0000-000000000000', actual['report_slice_id']
|
|
121
|
+
assert_not_nil(actual_host = actual['hosts'].first)
|
|
122
|
+
assert_nil actual_host['ip_addresses']
|
|
123
|
+
assert_nil actual_host['mac_addresses']
|
|
124
|
+
assert_equal @host.fqdn, actual_host['fqdn']
|
|
125
|
+
assert_equal '1234', actual_host['account']
|
|
126
|
+
assert_equal 1, generator.hosts_count
|
|
127
|
+
assert_not_nil(actual_system_profile = actual_host['system_profile'])
|
|
128
|
+
assert_nil actual_system_profile['number_of_cpus']
|
|
129
|
+
assert_nil actual_system_profile['number_of_sockets']
|
|
130
|
+
assert_nil actual_system_profile['cores_per_socket']
|
|
131
|
+
assert_nil actual_system_profile['system_memory_bytes']
|
|
132
|
+
assert_nil actual_system_profile['os_release']
|
|
133
|
+
assert_not_nil(actual_network_interfaces = actual_system_profile['network_interfaces'])
|
|
134
|
+
assert_not_nil(actual_nic = actual_network_interfaces.first)
|
|
135
|
+
refute actual_nic.key?('mtu')
|
|
136
|
+
refute actual_nic.key?('mac_address')
|
|
137
|
+
assert_equal 'test_nic1', actual_nic['name']
|
|
138
|
+
end
|
|
139
|
+
|
|
110
140
|
test 'generates a report with minimal data collection' do
|
|
111
141
|
Setting[:insights_minimal_data_collection] = true
|
|
112
142
|
create_fact_values(@host,
|
|
@@ -930,6 +960,33 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
|
930
960
|
assert_equal 'alibaba', actual_profile['cloud_provider']
|
|
931
961
|
end
|
|
932
962
|
|
|
963
|
+
test 'do not exclude packages when iop is present' do
|
|
964
|
+
Setting[:exclude_installed_packages] = true
|
|
965
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
|
966
|
+
installed_package = ::Katello::InstalledPackage.create(name: 'test-package', nvrea: 'test-package-1.0.x86_64', nvra: 'test-package-1.0.x86_64')
|
|
967
|
+
|
|
968
|
+
another_host = FactoryBot.create(
|
|
969
|
+
:host,
|
|
970
|
+
:with_subscription,
|
|
971
|
+
:with_content,
|
|
972
|
+
content_view: @host.content_views.first,
|
|
973
|
+
lifecycle_environment: @host.lifecycle_environments.first,
|
|
974
|
+
organization: @host.organization,
|
|
975
|
+
installed_packages: [installed_package]
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
batch = Host.where(id: another_host.id).in_batches.first
|
|
979
|
+
generator = create_generator(batch)
|
|
980
|
+
|
|
981
|
+
json_str = generator.render
|
|
982
|
+
actual = JSON.parse(json_str.join("\n"))
|
|
983
|
+
|
|
984
|
+
assert_equal '00000000-0000-0000-0000-000000000000', actual['report_slice_id']
|
|
985
|
+
assert_not_nil(actual_host = actual['hosts'].first)
|
|
986
|
+
assert_not_nil(actual_profile = actual_host['system_profile'])
|
|
987
|
+
assert_not_nil(actual_profile['installed_packages'])
|
|
988
|
+
end
|
|
989
|
+
|
|
933
990
|
test 'include packages installed in the report' do
|
|
934
991
|
Setting[:exclude_installed_packages] = false
|
|
935
992
|
installed_package = ::Katello::InstalledPackage.create(name: 'test-package', nvrea: 'test-package-1.0.x86_64', nvra: 'test-package-1.0.x86_64')
|
|
@@ -18,36 +18,67 @@ import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader';
|
|
|
18
18
|
import { insightsCloudUrl } from '../InsightsCloudSync/InsightsCloudSyncHelpers';
|
|
19
19
|
import { getInitialRisks, theme } from './InsightsTabConstants';
|
|
20
20
|
|
|
21
|
-
const InsightsTotalRiskCard = ({ hostDetails
|
|
21
|
+
const InsightsTotalRiskCard = ({ hostDetails }) => {
|
|
22
|
+
const { id, insights_attributes: insightsFacet } = hostDetails;
|
|
23
|
+
const uuid = insightsFacet?.uuid;
|
|
24
|
+
// eslint-disable-next-line camelcase
|
|
25
|
+
const isIop = insightsFacet?.use_iop_mode;
|
|
22
26
|
const [totalRisks, setTotalRisks] = useState(getInitialRisks());
|
|
23
27
|
const hashHistory = useHistory();
|
|
24
28
|
const dispatch = useDispatch();
|
|
25
29
|
const API_KEY = `HOST_${id}_RECOMMENDATIONS`;
|
|
26
30
|
const API_OPTIONS = useMemo(() => ({ key: API_KEY }), [API_KEY]);
|
|
27
|
-
const url = id && insightsCloudUrl(`hits/${id}`); // This will keep the API call from being triggered if there's no host id.
|
|
28
|
-
const {
|
|
29
|
-
status = STATUS.PENDING,
|
|
30
|
-
response: { hits = [] },
|
|
31
|
-
} = useAPI('get', url, API_OPTIONS);
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// This will keep the API call from being triggered if there's no host id.
|
|
33
|
+
const url = isIop
|
|
34
|
+
? uuid && insightsCloudUrl(`api/insights/v1/system/${uuid}`)
|
|
35
|
+
: id && insightsCloudUrl(`hits/${id}`);
|
|
36
|
+
const { status = STATUS.PENDING, response } = useAPI('get', url, API_OPTIONS);
|
|
37
|
+
|
|
38
|
+
const checkRisks = useMemo(() => {
|
|
39
|
+
if (!response || status !== STATUS.RESOLVED) {
|
|
40
|
+
return getInitialRisks();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const risks = getInitialRisks();
|
|
44
|
+
if (isIop) {
|
|
45
|
+
const {
|
|
46
|
+
low_hits: lowHits = 0,
|
|
47
|
+
moderate_hits: moderateHits = 0,
|
|
48
|
+
important_hits: importantHits = 0,
|
|
49
|
+
critical_hits: criticalHits = 0,
|
|
50
|
+
hits = 0,
|
|
51
|
+
} = response;
|
|
52
|
+
|
|
53
|
+
risks[1].value += lowHits;
|
|
54
|
+
risks[2].value += moderateHits;
|
|
55
|
+
risks[3].value += importantHits;
|
|
56
|
+
risks[4].value += criticalHits;
|
|
57
|
+
risks.total = hits;
|
|
58
|
+
} else {
|
|
59
|
+
const { hits = [] } = response;
|
|
36
60
|
hits.forEach(({ total_risk: risk }) => {
|
|
37
61
|
risks[risk].value += 1;
|
|
38
62
|
});
|
|
39
63
|
risks.total = hits.length;
|
|
40
|
-
setTotalRisks(risks);
|
|
41
64
|
}
|
|
42
|
-
|
|
65
|
+
return risks;
|
|
66
|
+
}, [response, status, isIop]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setTotalRisks(checkRisks);
|
|
70
|
+
}, [checkRisks]);
|
|
71
|
+
|
|
72
|
+
if (!insightsFacet) return null;
|
|
43
73
|
|
|
44
74
|
const onChartClick = (evt, { index }) => {
|
|
45
75
|
hashHistory.push(`/Insights`);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
!isIop &&
|
|
77
|
+
dispatch(
|
|
78
|
+
push({
|
|
79
|
+
search: `search=total_risk+%3D+${index + 1}`,
|
|
80
|
+
})
|
|
81
|
+
);
|
|
51
82
|
};
|
|
52
83
|
|
|
53
84
|
const onChartHover = (evt, { index }) => [
|
|
@@ -61,11 +92,16 @@ const InsightsTotalRiskCard = ({ hostDetails: { id } }) => {
|
|
|
61
92
|
const { 1: low, 2: moderate, 3: important, 4: critical, total } = totalRisks;
|
|
62
93
|
|
|
63
94
|
// eslint-disable-next-line react/prop-types
|
|
64
|
-
const LegendLabel = ({ index, ...rest }) =>
|
|
65
|
-
|
|
66
|
-
<ChartLabel {...rest}
|
|
67
|
-
|
|
68
|
-
|
|
95
|
+
const LegendLabel = ({ index, ...rest }) => {
|
|
96
|
+
if (isIop) {
|
|
97
|
+
return <ChartLabel {...rest} />;
|
|
98
|
+
}
|
|
99
|
+
return (
|
|
100
|
+
<a key={index} onClick={() => onChartClick(null, { index })}>
|
|
101
|
+
<ChartLabel {...rest} />
|
|
102
|
+
</a>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
69
105
|
|
|
70
106
|
const legend = (
|
|
71
107
|
<ChartLegend
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { Provider } from 'react-redux';
|
|
5
|
+
import { ConnectedRouter } from 'connected-react-router';
|
|
6
|
+
import { createMemoryHistory } from 'history';
|
|
7
|
+
import configureMockStore from 'redux-mock-store';
|
|
8
|
+
import { STATUS } from 'foremanReact/constants';
|
|
9
|
+
import * as APIHooks from 'foremanReact/common/hooks/API/APIHooks';
|
|
10
|
+
import InsightsTotalRiskCard from '../InsightsTotalRiskChart';
|
|
11
|
+
|
|
12
|
+
jest.mock('foremanReact/common/hooks/API/APIHooks');
|
|
13
|
+
jest.mock('foremanReact/common/I18n', () => ({
|
|
14
|
+
translate: jest.fn(str => str),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const mockStore = configureMockStore();
|
|
18
|
+
const history = createMemoryHistory();
|
|
19
|
+
const store = mockStore({
|
|
20
|
+
router: {
|
|
21
|
+
location: {
|
|
22
|
+
pathname: '/',
|
|
23
|
+
search: '',
|
|
24
|
+
hash: '',
|
|
25
|
+
state: null,
|
|
26
|
+
},
|
|
27
|
+
action: 'POP',
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const defaultHostDetails = {
|
|
32
|
+
id: 1,
|
|
33
|
+
insights_attributes: {
|
|
34
|
+
uuid: 'test-uuid',
|
|
35
|
+
use_iop_mode: false,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const renderComponent = (props = {}) => {
|
|
40
|
+
const allProps = {
|
|
41
|
+
hostDetails: defaultHostDetails,
|
|
42
|
+
...props,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return render(
|
|
46
|
+
<Provider store={store}>
|
|
47
|
+
<ConnectedRouter history={history}>
|
|
48
|
+
<InsightsTotalRiskCard {...allProps} />
|
|
49
|
+
</ConnectedRouter>
|
|
50
|
+
</Provider>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
describe('InsightsTotalRiskChart', () => {
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
store.clearActions();
|
|
57
|
+
jest.clearAllMocks();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should show loading state initially', () => {
|
|
61
|
+
APIHooks.useAPI.mockReturnValue({
|
|
62
|
+
status: STATUS.PENDING,
|
|
63
|
+
response: null,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
renderComponent();
|
|
67
|
+
// SkeletonLoader shows loading state when status is PENDING
|
|
68
|
+
expect(screen.queryByText('No results found')).not.toBeInTheDocument();
|
|
69
|
+
expect(
|
|
70
|
+
screen.queryByTestId('rh-cloud-total-risk-card')
|
|
71
|
+
).not.toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should display error state when API fails', async () => {
|
|
75
|
+
APIHooks.useAPI.mockReturnValue({
|
|
76
|
+
status: STATUS.ERROR,
|
|
77
|
+
response: null,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
renderComponent();
|
|
81
|
+
expect(screen.getByText('No results found')).toBeInTheDocument();
|
|
82
|
+
expect(
|
|
83
|
+
screen.queryByTestId('rh-cloud-total-risk-card')
|
|
84
|
+
).not.toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle non-IoP mode API response correctly', async () => {
|
|
88
|
+
const mockResponse = {
|
|
89
|
+
hits: [
|
|
90
|
+
{ total_risk: 1 },
|
|
91
|
+
{ total_risk: 2 },
|
|
92
|
+
{ total_risk: 2 },
|
|
93
|
+
{ total_risk: 3 },
|
|
94
|
+
{ total_risk: 4 },
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
APIHooks.useAPI.mockReturnValue({
|
|
99
|
+
status: STATUS.RESOLVED,
|
|
100
|
+
response: mockResponse,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
renderComponent();
|
|
104
|
+
|
|
105
|
+
await waitFor(() => {
|
|
106
|
+
// Check if total number of recommendations is displayed
|
|
107
|
+
expect(screen.getByText('5')).toBeInTheDocument();
|
|
108
|
+
// Check if risk levels are displayed correctly
|
|
109
|
+
expect(screen.getByText(/Low: 1/)).toBeInTheDocument();
|
|
110
|
+
expect(screen.getByText(/Moderate: 2/)).toBeInTheDocument();
|
|
111
|
+
expect(screen.getByText(/Important: 1/)).toBeInTheDocument();
|
|
112
|
+
expect(screen.getByText(/Critical: 1/)).toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should handle IOP mode API response correctly', async () => {
|
|
117
|
+
const mockResponse = {
|
|
118
|
+
low_hits: 2,
|
|
119
|
+
moderate_hits: 3,
|
|
120
|
+
important_hits: 1,
|
|
121
|
+
critical_hits: 2,
|
|
122
|
+
hits: 8,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
APIHooks.useAPI.mockReturnValue({
|
|
126
|
+
status: STATUS.RESOLVED,
|
|
127
|
+
response: mockResponse,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
renderComponent({
|
|
131
|
+
hostDetails: {
|
|
132
|
+
...defaultHostDetails,
|
|
133
|
+
insights_attributes: {
|
|
134
|
+
...defaultHostDetails.insights_attributes,
|
|
135
|
+
use_iop_mode: true,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
// Check if total number of recommendations is displayed
|
|
142
|
+
expect(screen.getByText('8')).toBeInTheDocument();
|
|
143
|
+
// Check if risk levels are displayed correctly
|
|
144
|
+
expect(screen.getByText(/Low: 2/)).toBeInTheDocument();
|
|
145
|
+
expect(screen.getByText(/Moderate: 3/)).toBeInTheDocument();
|
|
146
|
+
expect(screen.getByText(/Important: 1/)).toBeInTheDocument();
|
|
147
|
+
expect(screen.getByText(/Critical: 2/)).toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should show empty state when no recommendations exist', async () => {
|
|
152
|
+
APIHooks.useAPI.mockReturnValue({
|
|
153
|
+
status: STATUS.RESOLVED,
|
|
154
|
+
response: { hits: [] },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
renderComponent();
|
|
158
|
+
|
|
159
|
+
await waitFor(() => {
|
|
160
|
+
expect(screen.getByText(/Low: 0/)).toBeInTheDocument();
|
|
161
|
+
expect(screen.getByText(/Moderate: 0/)).toBeInTheDocument();
|
|
162
|
+
expect(screen.getByText(/Important: 0/)).toBeInTheDocument();
|
|
163
|
+
expect(screen.getByText(/Critical: 0/)).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should use correct API endpoint based on IOP mode', () => {
|
|
168
|
+
renderComponent({
|
|
169
|
+
hostDetails: {
|
|
170
|
+
...defaultHostDetails,
|
|
171
|
+
insights_attributes: {
|
|
172
|
+
...defaultHostDetails.insights_attributes,
|
|
173
|
+
use_iop_mode: true,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(APIHooks.useAPI).toHaveBeenCalledWith(
|
|
179
|
+
'get',
|
|
180
|
+
expect.stringContaining('/api/insights/v1/system/test-uuid'),
|
|
181
|
+
expect.any(Object)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
jest.clearAllMocks();
|
|
185
|
+
|
|
186
|
+
renderComponent();
|
|
187
|
+
|
|
188
|
+
expect(APIHooks.useAPI).toHaveBeenCalledWith(
|
|
189
|
+
'get',
|
|
190
|
+
expect.stringContaining('/hits/1'),
|
|
191
|
+
expect.any(Object)
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
});
|