foreman_rh_cloud 14.2.0 → 14.3.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/concerns/rh_cloud_host.rb +36 -1
  3. data/config/routes.rb +1 -0
  4. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +2 -0
  5. data/lib/foreman_rh_cloud/version.rb +1 -1
  6. data/package.json +1 -1
  7. data/test/jobs/upload_report_direct_job_test.rb +29 -0
  8. data/test/unit/insights_facet_test.rb +5 -0
  9. data/test/unit/rh_cloud_host_test.rb +68 -0
  10. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +5 -1
  11. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListConstants.js +5 -0
  12. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/EmptyResults.js +15 -6
  13. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/emptyResults.scss +1 -5
  14. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/EmptyState.js +15 -7
  15. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/EmptyState.test.js +1 -3
  16. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/emptyState.scss +1 -5
  17. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/ErrorState.js +2 -2
  18. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/ListItem.test.js +0 -1
  19. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountList.test.js +2 -6
  20. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/Components/ClearButton/ClearButton.js +13 -10
  21. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/InventoryFilter.js +30 -11
  22. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/integration.test.js +3 -1
  23. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/inventoryFilter.scss +28 -22
  24. data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.js +4 -4
  25. data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.scss +16 -3
  26. data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +13 -14
  27. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +7 -7
  28. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.scss +3 -2
  29. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +63 -46
  30. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +4 -7
  31. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +83 -12
  32. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudPingModal/index.js +31 -38
  33. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +3 -1
  34. data/webpack/ForemanInventoryUpload/Components/TaskConstants.js +6 -0
  35. data/webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js +3 -2
  36. data/webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js +4 -3
  37. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +1 -1
  38. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/InventoryAutoUpload.js +29 -22
  39. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/InventoryAutoUpload.test.js +6 -2
  40. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryHelpers.test.js +9 -6
  41. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +90 -23
  42. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableActions.js +1 -2
  43. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +24 -30
  44. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableHelpers.js +5 -6
  45. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +193 -8
  46. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +4 -1
  47. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +8 -9
  48. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +26 -13
  49. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModalFooter.js +5 -2
  50. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationTableConstants.js +16 -10
  51. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +21 -9
  52. data/webpack/InsightsCloudSync/InsightsCloudSync.test.js +12 -19
  53. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +0 -1
  54. data/webpack/IopRecommendationDetails/IopRecommendationDetails.js +19 -0
  55. data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware.js +5 -1
  56. data/webpack/common/DropdownToggle.js +20 -6
  57. metadata +3 -45
  58. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.fixtures.js +0 -0
  59. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.js +0 -25
  60. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownloadHelper.js +0 -0
  61. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/FileDownload.test.js +0 -17
  62. data/webpack/ForemanInventoryUpload/Components/FileDownload/fileDownload.scss +0 -5
  63. data/webpack/ForemanInventoryUpload/Components/FileDownload/index.js +0 -1
  64. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.fixtures.js +0 -20
  65. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +0 -66
  66. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainerHelper.js +0 -0
  67. data/webpack/ForemanInventoryUpload/Components/NavContainer/__tests__/NavContainer.test.js +0 -69
  68. data/webpack/ForemanInventoryUpload/Components/NavContainer/index.js +0 -1
  69. data/webpack/ForemanInventoryUpload/Components/NavContainer/navContainer.scss +0 -9
  70. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.fixtures.js +0 -4
  71. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.js +0 -29
  72. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRunHelper.js +0 -0
  73. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/ScheduledRun.test.js +0 -33
  74. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/index.js +0 -12
  75. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/scheduledRun.scss +0 -13
  76. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.fixtures.js +0 -0
  77. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.js +0 -57
  78. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChartHelper.js +0 -0
  79. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/StatusChart.test.js +0 -30
  80. data/webpack/ForemanInventoryUpload/Components/StatusChart/index.js +0 -1
  81. data/webpack/ForemanInventoryUpload/Components/StatusChart/statusChart.scss +0 -10
  82. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.fixtures.js +0 -0
  83. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.js +0 -24
  84. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainerHelper.js +0 -0
  85. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/TabContainer.test.js +0 -15
  86. data/webpack/ForemanInventoryUpload/Components/TabContainer/index.js +0 -1
  87. data/webpack/ForemanInventoryUpload/Components/TabContainer/tabContainer.scss +0 -10
  88. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.fixtures.js +0 -0
  89. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.js +0 -19
  90. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooterHelper.js +0 -0
  91. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/TabFooter.test.js +0 -15
  92. data/webpack/ForemanInventoryUpload/Components/TabFooter/index.js +0 -1
  93. data/webpack/ForemanInventoryUpload/Components/TabFooter/tabFooter.scss +0 -0
  94. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.fixtures.js +0 -0
  95. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +0 -85
  96. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeaderHelper.js +0 -0
  97. data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +0 -114
  98. data/webpack/ForemanInventoryUpload/Components/TabHeader/index.js +0 -1
  99. data/webpack/ForemanInventoryUpload/Components/TabHeader/tabHeader.scss +0 -23
  100. data/webpack/common/Switcher/index.js +0 -80
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91157d5050744236651134413110454e4d595e1e25644eb1676d58e4990476d1
4
- data.tar.gz: acb8689d93c1d2aedf280049ff18075ad7989693ef75fea5a8b1babef63b447c
3
+ metadata.gz: 1c2ad7078753ff687e7dfa1cf410efbfbc3f1ad9b8dc74500f9e77bf0c9a7fc9
4
+ data.tar.gz: 9eb2679eb38c91fa3c1163915328d91959f7e6408e73bd4fc0c4d7fe98b9a0cf
5
5
  SHA512:
6
- metadata.gz: 5f2378f058fde9220f4fa8ca17a094dfe6e32f3f33456304c0178ca3b5023abf8b6bfd09fdcbff064e914e063c4fc6a9b66536d6703e259a4f28569202c37813
7
- data.tar.gz: 1d0adbd1839a3e73eada13ca910522407af5a66e838934ad327f8c36cb41058f5693c7d5d646caa3b7b1fba71670e184f24df1ee90b122d4fdd7f05338a845de
6
+ metadata.gz: 0e4cb92c3a41b257ebf018a988ced92b086aa5cec373214888bb22a3dc43f765a007f42182b1da6a058c97b8633d59e1239efef658c3e0d53744bb9b8fc41125
7
+ data.tar.gz: 780195de89dcac76c3774a8b48ae67c7c52ad8b2b4f1029020e1c1e42ec701092f2122ff4caec64de083cf01094ebac40b42cf1d0985d06fdbbc46838331d541
@@ -10,7 +10,8 @@ module RhCloudHost
10
10
  )
11
11
 
12
12
  has_many :insights_hits, through: :insights, source: :hits
13
- scoped_search :relation => :insights, :on => :hits_count, :only_explicit => true, :rename => :insights_recommendations_count
13
+ scoped_search :on => :id, :rename => :insights_recommendations_count, :only_explicit => true,
14
+ :ext_method => :search_by_insights_recommendations_count, :complete_value => false
14
15
 
15
16
  has_one :insights_client_report_status_object, :class_name => '::InsightsClientReportStatus', :foreign_key => 'host_id'
16
17
  scoped_search :relation => :insights_client_report_status_object, :on => :status, :rename => :insights_client_report_status,
@@ -46,6 +47,40 @@ module RhCloudHost
46
47
  end
47
48
 
48
49
  module ClassMethods
50
+ def search_by_insights_recommendations_count(_key, operator, value)
51
+ if ForemanRhCloud.with_iop_smart_proxy?
52
+ raise ScopedSearch::QueryNotSupported.new(
53
+ _('Searching by recommendations count is not available in IoP mode.')
54
+ )
55
+ end
56
+
57
+ unless ScopedSearch::QueryBuilder::SQL_OPERATORS.value?(operator)
58
+ raise ScopedSearch::QueryNotSupported.new(
59
+ _('Unsupported operator for recommendations count search: %s') % operator
60
+ )
61
+ end
62
+
63
+ facet_table = InsightsFacet.table_name
64
+ hits_count_col = "COALESCE(#{facet_table}.hits_count, 0)"
65
+
66
+ if ['IN', 'NOT IN'].include?(operator)
67
+ values = value.is_a?(Array) ? value : value.to_s.split(',').map(&:strip)
68
+ placeholders = (['?'] * values.size).join(',')
69
+ condition = sanitize_sql_for_conditions(
70
+ ["#{hits_count_col} #{operator} (#{placeholders})", *values]
71
+ )
72
+ else
73
+ condition = sanitize_sql_for_conditions(
74
+ ["#{hits_count_col} #{operator} ?", value_to_sql(operator, value)]
75
+ )
76
+ end
77
+
78
+ {
79
+ joins: "LEFT JOIN #{facet_table} ON #{facet_table}.host_id = #{Host::Managed.table_name}.id",
80
+ conditions: condition,
81
+ }
82
+ end
83
+
49
84
  def search_by_insights_uuid(_key, operator, value)
50
85
  # Determine which facet table to search based on IoP mode
51
86
  facet_table = ForemanRhCloud.with_iop_smart_proxy? ? Katello::Host::SubscriptionFacet.table_name : InsightsFacet.table_name
data/config/routes.rb CHANGED
@@ -33,6 +33,7 @@ Rails.application.routes.draw do
33
33
  namespace :foreman_rh_cloud do
34
34
  get 'inventory_upload', to: 'foreman_rh_cloud#inventory_upload'
35
35
  get 'recommendations', to: 'foreman_rh_cloud#recommendations'
36
+ get 'recommendations/pathways/:slug', to: 'foreman_rh_cloud#recommendations'
36
37
  get 'recommendations/:rule_id', to: 'foreman_rh_cloud#recommendations'
37
38
  get 'insights_cloud', to: '/react#index' # Uses foreman's react controller
38
39
  get 'insights_vulnerability', to: '/react#index'
@@ -59,11 +59,13 @@ module ForemanInventoryUpload
59
59
  def try_execute
60
60
  if content_disconnected?
61
61
  logger.info("Upload canceled: connection to Insights is not enabled. Report location: #{filename}")
62
+ done!
62
63
  return
63
64
  end
64
65
 
65
66
  unless organization.owner_details&.dig('upstreamConsumer', 'idCert')
66
67
  logger.info("Skipping organization '#{organization}', no candlepin certificate defined.")
68
+ done!
67
69
  return
68
70
  end
69
71
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '14.2.0'.freeze
2
+ VERSION = '14.3.0'.freeze
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "14.2.0",
3
+ "version": "14.3.0",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -401,4 +401,33 @@ class UploadReportDirectJobTest < ActiveSupport::TestCase
401
401
  file.close
402
402
  end
403
403
  end
404
+
405
+ test 'mark as done when upload aborted due to missing certificate' do
406
+ # Remove certificate from organization
407
+ Organization.any_instance.stubs(:owner_details).returns({})
408
+
409
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
410
+ action.expects(:action_subject).with(@organization)
411
+ plan_action(action, nil, @organization.id)
412
+
413
+ # Execute the action
414
+ action.send(:try_execute)
415
+
416
+ # Verify it is marked as done
417
+ assert action.done?
418
+ end
419
+
420
+ test 'mark as done when upload aborted due to disconnected mode' do
421
+ Setting.stubs(:[]).with(:subscription_connection_enabled).returns(false)
422
+
423
+ action = create_action(ForemanInventoryUpload::Async::UploadReportDirectJob)
424
+ action.expects(:action_subject).with(@organization)
425
+ plan_action(action, nil, @organization.id)
426
+
427
+ # Execute the action
428
+ action.send(:try_execute)
429
+
430
+ # Verify it is marked as done
431
+ assert action.done?
432
+ end
404
433
  end
@@ -6,6 +6,10 @@ class InsightsFacetTest < ActiveSupport::TestCase
6
6
  InsightsFacet.reset_counters(@host.insights.id, :hits_count)
7
7
  end
8
8
 
9
+ teardown do
10
+ ForemanRhCloud.unstub(:with_iop_smart_proxy?)
11
+ end
12
+
9
13
  test 'host with hits can be deleted' do
10
14
  assert_equal 1, @host.insights.hits.count
11
15
 
@@ -16,6 +20,7 @@ class InsightsFacetTest < ActiveSupport::TestCase
16
20
  end
17
21
 
18
22
  test 'search host by recommendations_count' do
23
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
19
24
  FactoryBot.create(:host) # create another host with no recommendations
20
25
 
21
26
  assert_equal 1, Host.search_for('insights_recommendations_count = 1').count
@@ -343,6 +343,74 @@ class RhCloudHostTest < ActiveSupport::TestCase
343
343
  end
344
344
  end
345
345
 
346
+ context 'scoped search on insights_recommendations_count' do
347
+ setup do
348
+ @org = FactoryBot.create(:organization)
349
+ end
350
+
351
+ teardown do
352
+ ForemanRhCloud.unstub(:with_iop_smart_proxy?)
353
+ end
354
+
355
+ test 'searches insights_facets.hits_count in non-IoP mode' do
356
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
357
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
358
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
359
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
360
+
361
+ results = Host::Managed.search_for('insights_recommendations_count = 1')
362
+
363
+ assert_includes results, host_with_hits
364
+ assert_not_includes results, host_without_hits
365
+ end
366
+
367
+ test 'supports greater-than operator in non-IoP mode' do
368
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
369
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
370
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
371
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
372
+
373
+ results = Host::Managed.search_for('insights_recommendations_count > 0')
374
+
375
+ assert_includes results, host_with_hits
376
+ assert_not_includes results, host_without_hits
377
+ end
378
+
379
+ test 'raises QueryNotSupported in IoP mode' do
380
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
381
+
382
+ error = assert_raises(ScopedSearch::QueryNotSupported) do
383
+ Host::Managed.search_for('insights_recommendations_count = 1')
384
+ end
385
+
386
+ assert_match(/not available in IoP mode/, error.message)
387
+ end
388
+
389
+ test 'supports IN operator in non-IoP mode' do
390
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
391
+ host_with_hits = FactoryBot.create(:host, :with_insights_hits, organization: @org)
392
+ InsightsFacet.reset_counters(host_with_hits.insights.id, :hits_count)
393
+ host_without_hits = FactoryBot.create(:host, :managed, organization: @org)
394
+
395
+ results = Host::Managed.search_for('insights_recommendations_count ^ (1,2,3)')
396
+
397
+ assert_includes results, host_with_hits
398
+ assert_not_includes results, host_without_hits
399
+ end
400
+
401
+ test 'handles hosts without insights facets in non-IoP mode' do
402
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
403
+ host_without_facet = FactoryBot.create(:host, :managed, organization: @org)
404
+ host_with_facet = FactoryBot.create(:host, :with_insights_hits, organization: @org)
405
+ InsightsFacet.reset_counters(host_with_facet.insights.id, :hits_count)
406
+
407
+ results = Host::Managed.search_for('insights_recommendations_count = 1')
408
+
409
+ assert_includes results, host_with_facet
410
+ assert_not_includes results, host_without_facet
411
+ end
412
+ end
413
+
346
414
  test 'scoped search for user_omitted inventory status works' do
347
415
  host1 = FactoryBot.create(:host, :managed)
348
416
  host2 = FactoryBot.create(:host, :managed)
@@ -8,6 +8,7 @@ import EmptyState from './Components/EmptyState';
8
8
  import ErrorState from './Components/ErrorState';
9
9
  import EmptyResults from './Components/EmptyResults';
10
10
  import { filterAccounts } from './AccountListHelper';
11
+ import { ACCOUNT_STATUS_POLLING_INTERVAL_MS } from './AccountListConstants';
11
12
  import './accountList.scss';
12
13
 
13
14
  const AccountList = ({
@@ -21,7 +22,10 @@ const AccountList = ({
21
22
  }) => {
22
23
  useEffect(() => {
23
24
  fetchAccountsStatus();
24
- const pollingID = setInterval(fetchAccountsStatus, 2000);
25
+ const pollingID = setInterval(
26
+ fetchAccountsStatus,
27
+ ACCOUNT_STATUS_POLLING_INTERVAL_MS
28
+ );
25
29
  startAccountStatusPolling(pollingID);
26
30
 
27
31
  return () => {
@@ -7,3 +7,8 @@ export const INVENTORY_ACCOUNT_STATUS_POLLING_START =
7
7
  export const INVENTORY_ACCOUNT_STATUS_POLLING_STOP =
8
8
  'INVENTORY_ACCOUNT_STATUS_POLLING_STOP';
9
9
  export const INVENTORY_PROCESS_RESTART = 'INVENTORY_PROCESS_RESTART';
10
+
11
+ // How often to re-fetch account statuses while generation/upload is in
12
+ // progress. Frequent enough to reflect status changes promptly, without
13
+ // hammering the status endpoint.
14
+ export const ACCOUNT_STATUS_POLLING_INTERVAL_MS = 2000;
@@ -1,14 +1,23 @@
1
1
  import React from 'react';
2
- import { EmptyState, Icon } from 'patternfly-react';
2
+ import {
3
+ EmptyState,
4
+ EmptyStateHeader,
5
+ EmptyStateIcon,
6
+ EmptyStateVariant,
7
+ } from '@patternfly/react-core';
8
+ import { OutlinedMehIcon } from '@patternfly/react-icons';
3
9
  import { translate as __ } from 'foremanReact/common/I18n';
4
10
  import './emptyResults.scss';
5
11
 
6
12
  const inventoryEmptyResults = () => (
7
- <EmptyState>
8
- <EmptyState.Title>
9
- <Icon className="no_results_icon" name="meh-o" />
10
- {__("Oops! Couldn't find organization that matches your query")}
11
- </EmptyState.Title>
13
+ <EmptyState variant={EmptyStateVariant.lg}>
14
+ <EmptyStateIcon icon={OutlinedMehIcon} />
15
+ <EmptyStateHeader
16
+ titleText={
17
+ <>{__("Oops! Couldn't find organization that matches your query")}</>
18
+ }
19
+ headingLevel="h2"
20
+ />
12
21
  </EmptyState>
13
22
  );
14
23
 
@@ -1,9 +1,5 @@
1
1
  .rh-cloud-inventory-page {
2
- .blank-slate-pf {
2
+ .pf-v5-c-empty-state {
3
3
  margin-top: 100px;
4
-
5
- .no_results_icon {
6
- margin-right: 5px;
7
- }
8
4
  }
9
5
  }
@@ -1,15 +1,23 @@
1
1
  import React from 'react';
2
- import { EmptyState, Spinner } from 'patternfly-react';
2
+ import {
3
+ EmptyState,
4
+ EmptyStateHeader,
5
+ EmptyStateIcon,
6
+ EmptyStateBody,
7
+ EmptyStateVariant,
8
+ Spinner,
9
+ } from '@patternfly/react-core';
3
10
  import { translate as __ } from 'foremanReact/common/I18n';
4
11
  import './emptyState.scss';
5
12
 
6
13
  const inventoryEmptyState = () => (
7
- <EmptyState>
8
- <Spinner loading inline size="lg" />
9
- <EmptyState.Title>
10
- {__('Fetching data about your accounts')}
11
- </EmptyState.Title>
12
- <EmptyState.Info>{__('Loading...')}</EmptyState.Info>
14
+ <EmptyState variant={EmptyStateVariant.lg}>
15
+ <EmptyStateIcon icon={Spinner} />
16
+ <EmptyStateHeader
17
+ titleText={<>{__('Fetching data about your accounts')}</>}
18
+ headingLevel="h2"
19
+ />
20
+ <EmptyStateBody>{__('Loading...')}</EmptyStateBody>
13
21
  </EmptyState>
14
22
  );
15
23
 
@@ -5,9 +5,7 @@ import EmptyState from '../EmptyState';
5
5
  describe('EmptyState', () => {
6
6
  it('renders fetching data message', () => {
7
7
  render(<EmptyState />);
8
- expect(
9
- screen.getByText('Fetching data about your accounts')
10
- ).toBeTruthy();
8
+ expect(screen.getByText('Fetching data about your accounts')).toBeTruthy();
11
9
  });
12
10
 
13
11
  it('renders loading indicator', () => {
@@ -1,9 +1,5 @@
1
1
  .rh-cloud-inventory-page {
2
- .blank-slate-pf {
2
+ .pf-v5-c-empty-state {
3
3
  margin-top: 100px;
4
-
5
- .spinner {
6
- float: none;
7
- }
8
4
  }
9
5
  }
@@ -1,12 +1,12 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Icon } from 'patternfly-react';
3
+ import { TimesIcon } from '@patternfly/react-icons';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
5
  import './errorState.scss';
6
6
 
7
7
  const ErrorState = ({ error }) => (
8
8
  <div className="error_state">
9
- <Icon className="error_icon" name="times" size="2x" />
9
+ <TimesIcon className="error_icon" size="xl" />
10
10
  <p>{__('Encountered an error while trying to access the server:')}</p>
11
11
  <p className="error_description">{error}</p>
12
12
  </div>
@@ -32,5 +32,4 @@ describe('ListItem', () => {
32
32
  expect(screen.getByText('Generated')).toBeTruthy();
33
33
  expect(screen.getByText('Uploaded')).toBeTruthy();
34
34
  });
35
-
36
35
  });
@@ -3,9 +3,7 @@ import { render, screen } from '@testing-library/react';
3
3
  import AccountList from '../AccountList';
4
4
  import { accounts } from '../AccountList.fixtures';
5
5
 
6
- jest.mock('../Components/ListItem', () => ({ label }) => (
7
- <div>{label}</div>
8
- ));
6
+ jest.mock('../Components/ListItem', () => ({ label }) => <div>{label}</div>);
9
7
 
10
8
  describe('AccountList', () => {
11
9
  it('renders account labels', () => {
@@ -26,9 +24,7 @@ describe('AccountList', () => {
26
24
 
27
25
  it('shows empty state when accounts are empty', () => {
28
26
  render(<AccountList accounts={{}} />);
29
- expect(
30
- screen.getByText('Fetching data about your accounts')
31
- ).toBeTruthy();
27
+ expect(screen.getByText('Fetching data about your accounts')).toBeTruthy();
32
28
  });
33
29
 
34
30
  it('shows error state when error is present', () => {
@@ -1,19 +1,22 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { noop } from 'foremanReact/common/helpers';
4
- import { Icon, OverlayTrigger, Tooltip } from 'patternfly-react';
4
+ import { Tooltip, Button } from '@patternfly/react-core';
5
+ import { TimesIcon } from '@patternfly/react-icons';
5
6
  import { translate as __ } from 'foremanReact/common/I18n';
6
7
 
7
8
  const ClearButton = ({ onClear }) => (
8
- <OverlayTrigger
9
- overlay={
10
- <Tooltip id="inventory_filter_clear_overlay">{__('Clear')}</Tooltip>
11
- }
12
- placement="top"
13
- trigger={['hover', 'focus']}
14
- >
15
- <Icon name="close" className="inventory-clear-button" onClick={onClear} />
16
- </OverlayTrigger>
9
+ <Tooltip content={__('Clear')} position="top">
10
+ <Button
11
+ variant="plain"
12
+ className="inventory-clear-button"
13
+ onClick={onClear}
14
+ aria-label={__('Clear')}
15
+ ouiaId="inventory-clear-button"
16
+ >
17
+ <TimesIcon />
18
+ </Button>
19
+ </Tooltip>
17
20
  );
18
21
 
19
22
  ClearButton.propTypes = {
@@ -1,11 +1,17 @@
1
1
  /* eslint-disable react-hooks/exhaustive-deps */
2
2
  import React, { useEffect } from 'react';
3
3
  import PropTypes from 'prop-types';
4
- import { FormGroup, TextInput } from '@patternfly/react-core';
4
+ import {
5
+ FormGroup,
6
+ TextInput,
7
+ InputGroup,
8
+ InputGroupItem,
9
+ Button,
10
+ } from '@patternfly/react-core';
11
+ import { TimesIcon } from '@patternfly/react-icons';
5
12
  import { noop } from 'foremanReact/common/helpers';
6
13
  import { translate as __ } from 'foremanReact/common/I18n';
7
14
  import { useForemanOrganization } from 'foremanReact/Root/Context/ForemanContext';
8
- import ClearButton from './Components/ClearButton';
9
15
  import './inventoryFilter.scss';
10
16
  import { ANY_ORGANIZATION } from './InventoryFilterConstants';
11
17
 
@@ -24,15 +30,28 @@ const InventoryFilter = ({
24
30
  return (
25
31
  <form id="inventory_filter_form">
26
32
  <FormGroup>
27
- <TextInput
28
- id="inventory_filter_input"
29
- ouiaId="inventory_filter_input"
30
- value={filterTerm}
31
- type="text"
32
- placeholder={__('Filter..')}
33
- onChange={(e, v) => handleFilterChange(v)}
34
- />
35
- <ClearButton onClear={handleFilterClear} />
33
+ <InputGroup>
34
+ <InputGroupItem isFill>
35
+ <TextInput
36
+ id="inventory_filter_input"
37
+ ouiaId="inventory_filter_input"
38
+ value={filterTerm}
39
+ type="text"
40
+ placeholder={__('Filter..')}
41
+ onChange={(e, v) => handleFilterChange(v)}
42
+ />
43
+ </InputGroupItem>
44
+ <InputGroupItem>
45
+ <Button
46
+ ouiaId="inventory-filter-clear-button"
47
+ variant="plain"
48
+ aria-label={__('Clear')}
49
+ onClick={handleFilterClear}
50
+ >
51
+ <TimesIcon />
52
+ </Button>
53
+ </InputGroupItem>
54
+ </InputGroup>
36
55
  </FormGroup>
37
56
  </form>
38
57
  );
@@ -31,7 +31,9 @@ describe('InventoryFilter integration test', () => {
31
31
 
32
32
  const actions = store.getActions();
33
33
  const filterAction = actions.find(
34
- a => a.type === INVENTORY_FILTER_UPDATE && a.payload.filterTerm === 'some_new_filter'
34
+ a =>
35
+ a.type === INVENTORY_FILTER_UPDATE &&
36
+ a.payload.filterTerm === 'some_new_filter'
35
37
  );
36
38
  expect(filterAction).toBeTruthy();
37
39
  });
@@ -1,28 +1,34 @@
1
1
  #inventory_filter_form {
2
- .pf-v5-c-form__group-control {
3
- display: -ms-flexbox;
4
- display: -webkit-flex;
5
- display: flex;
6
- -webkit-flex-direction: row;
7
- -ms-flex-direction: row;
8
- flex-direction: row;
9
- -webkit-flex-wrap: nowrap;
10
- -ms-flex-wrap: nowrap;
11
- flex-wrap: nowrap;
12
- -webkit-justify-content: flex-end;
13
- -ms-flex-pack: end;
14
- justify-content: flex-end;
15
- -webkit-align-content: center;
16
- -ms-flex-line-pack: center;
17
- align-content: center;
18
- -webkit-align-items: center;
19
- -ms-flex-align: center;
20
- align-items: center;
2
+ .pf-v5-c-form__group {
21
3
  margin-bottom: 0;
4
+ }
5
+
6
+ .pf-v5-c-input-group {
7
+ width: 100%;
8
+ position: relative;
9
+
10
+ .pf-v5-c-input-group__item {
11
+ &:last-child {
12
+ position: absolute;
13
+ right: 0;
14
+ top: 0;
15
+ bottom: 0;
16
+ display: flex;
17
+ align-items: center;
18
+ pointer-events: auto;
19
+
20
+ .pf-v5-c-button {
21
+ margin-right: 8px;
22
+ }
23
+ }
24
+
25
+ &:first-child {
26
+ flex: 1;
22
27
 
23
- .inventory-clear-button {
24
- position: absolute;
25
- right: 27px;
28
+ .pf-v5-c-form-control {
29
+ padding-right: 40px;
30
+ }
31
+ }
26
32
  }
27
33
  }
28
34
  }
@@ -52,12 +52,12 @@ const InventorySettings = () => {
52
52
  })}
53
53
  <MinimalInventoryDropdown setChosenValue={setChosenValue} />
54
54
  {Object.keys(childSettingsDict).length > 0 && ( // Only render this section if there are child settings
55
- <div style={{ marginTop: '1.5em' }}>
55
+ <div className="child-settings">
56
56
  <Flex>
57
- <FlexItem>
58
- <span style={{ width: '6em' }} />
57
+ <FlexItem flex={{ default: 'flexNone' }}>
58
+ <span className="child-settings-spacer" />
59
59
  </FlexItem>
60
- <FlexItem>
60
+ <FlexItem flex={{ default: 'flex_1' }}>
61
61
  {Object.keys(childSettingsDict).map(key => (
62
62
  <AdvancedSetting
63
63
  key={key}
@@ -1,14 +1,27 @@
1
1
  .rh-cloud-inventory-page {
2
2
  .inventory-settings {
3
- border: 1px solid #ededed;
4
- border-radius: 4px;
3
+ border: 1px solid var(--pf-v5-global--BorderColor--100);
4
+ border-radius: var(--pf-v5-global--BorderRadius--sm);
5
5
  padding: 10px;
6
- margin-bottom: 15px;
6
+ margin-bottom: var(--pf-v5-global--spacer--md);
7
+ margin-left: 20px;
7
8
  float: right;
9
+ max-width: 450px;
8
10
 
9
11
  h3 {
10
12
  margin-top: 0;
13
+ margin-bottom: 10px;
11
14
  font-weight: 600;
15
+ font-size: var(--pf-v5-global--FontSize--md);
16
+ }
17
+
18
+ .child-settings {
19
+ margin-top: 10px;
20
+ margin-left: 0;
21
+
22
+ .child-settings-spacer {
23
+ display: none;
24
+ }
12
25
  }
13
26
  }
14
27
  }
@@ -72,6 +72,7 @@ const MinimalInventoryDropdown = ({ setChosenValue }) => {
72
72
  isOpen={isOpen}
73
73
  onSelect={onSelect}
74
74
  onOpenChange={val => setIsOpen(val)}
75
+ popperProps={{ width: '350px' }}
75
76
  toggle={toggleRef => (
76
77
  <MenuToggle
77
78
  ref={toggleRef}
@@ -84,20 +85,18 @@ const MinimalInventoryDropdown = ({ setChosenValue }) => {
84
85
  )}
85
86
  shouldFocusToggleOnSelect
86
87
  >
87
- <div style={{ maxWidth: '28em' }}>
88
- <DropdownList>
89
- {Object.entries(dropdownValues).map(([value, item]) => (
90
- <DropdownItem
91
- value={value}
92
- key={value}
93
- description={item.description}
94
- ouiaId={`inventory-dropdownItem-${value}`}
95
- >
96
- {item.title}
97
- </DropdownItem>
98
- ))}
99
- </DropdownList>
100
- </div>
88
+ <DropdownList>
89
+ {Object.entries(dropdownValues).map(([value, item]) => (
90
+ <DropdownItem
91
+ value={value}
92
+ key={value}
93
+ description={item.description}
94
+ ouiaId={`inventory-dropdownItem-${value}`}
95
+ >
96
+ {item.title}
97
+ </DropdownItem>
98
+ ))}
99
+ </DropdownList>
101
100
  </Dropdown>
102
101
  );
103
102
  };