foreman_rh_cloud 14.2.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89dec7188d18373800314f614305ccece5e477bb6d1db9ce24c35c288b101ee2
4
- data.tar.gz: 4d7fe9c4aff407cbf2945a45f4d2c032f7a2d5672740c890e3691287c3cd35c6
3
+ metadata.gz: 1c2ad7078753ff687e7dfa1cf410efbfbc3f1ad9b8dc74500f9e77bf0c9a7fc9
4
+ data.tar.gz: 9eb2679eb38c91fa3c1163915328d91959f7e6408e73bd4fc0c4d7fe98b9a0cf
5
5
  SHA512:
6
- metadata.gz: 6e2daa770c20d244f2b86f81cc447661de17c1b7ead87cf1989bf1a5fe50858601a1f847c4fdaff88501313da45cb0d826d169906cd4682f32ebb8b0c6e3e5f4
7
- data.tar.gz: 830c5dc063d390c5b76d8e637b89eebbafc77add2d2245f71a5372b292c324057c5ba0c3394f4ae436c2a718979c352a405b127b8f38e578513d3460b1c1fffa
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.1'.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.1",
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;
@@ -0,0 +1,6 @@
1
+ // Task durations reported by the API are in seconds; used to derive
2
+ // minutes/seconds for display in TaskHistory and TaskProgress.
3
+ export const SECONDS_PER_MINUTE = 60;
4
+
5
+ // Progress percentage to show once a task has finished running.
6
+ export const PERCENT_COMPLETE = 100;
@@ -18,6 +18,7 @@ import {
18
18
  } from '@patternfly/react-icons';
19
19
  import { translate as __ } from 'foremanReact/common/I18n';
20
20
  import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
21
+ import { SECONDS_PER_MINUTE } from '../TaskConstants';
21
22
  import './taskHistory.scss';
22
23
 
23
24
  const TaskHistory = ({ tasks, title }) => {
@@ -52,8 +53,8 @@ const TaskHistory = ({ tasks, title }) => {
52
53
  if (!Number.isFinite(seconds) || seconds < 0) {
53
54
  return __('N/A');
54
55
  }
55
- const mins = Math.floor(seconds / 60);
56
- const secs = Math.floor(seconds % 60);
56
+ const mins = Math.floor(seconds / SECONDS_PER_MINUTE);
57
+ const secs = Math.floor(seconds % SECONDS_PER_MINUTE);
57
58
  if (mins > 0) {
58
59
  return `${mins}m ${secs}s`;
59
60
  }
@@ -29,6 +29,7 @@ import { useDispatch, useSelector } from 'react-redux';
29
29
  import { inventoryUrl } from '../../ForemanInventoryHelpers';
30
30
  import { selectSubscriptionConnectionEnabled } from '../InventorySettings/InventorySettingsSelectors';
31
31
  import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
32
+ import { SECONDS_PER_MINUTE, PERCENT_COMPLETE } from '../TaskConstants';
32
33
  import './taskProgress.scss';
33
34
 
34
35
  const TaskProgress = ({
@@ -213,8 +214,8 @@ const TaskProgress = ({
213
214
  ) {
214
215
  return '';
215
216
  }
216
- const mins = Math.floor(seconds / 60);
217
- const secs = Math.floor(seconds % 60);
217
+ const mins = Math.floor(seconds / SECONDS_PER_MINUTE);
218
+ const secs = Math.floor(seconds % SECONDS_PER_MINUTE);
218
219
  if (mins > 0) {
219
220
  return `${mins}m ${secs}s`;
220
221
  }
@@ -232,7 +233,7 @@ const TaskProgress = ({
232
233
  if (isStartingNewTask) {
233
234
  progressValue = 0;
234
235
  } else if (task.state === 'stopped') {
235
- progressValue = 100;
236
+ progressValue = PERCENT_COMPLETE;
236
237
  } else {
237
238
  progressValue = task.progress || 0;
238
239
  }
@@ -7,7 +7,7 @@ export const inventoryUrl = path =>
7
7
 
8
8
  export const getInventoryDocsUrl = () =>
9
9
  getDocsURL(
10
- 'Managing_Hosts',
10
+ 'Administering_Project',
11
11
  'configuring-foreman-server-for-cloud-connection'
12
12
  );
13
13
 
@@ -10,11 +10,11 @@ jest.mock('foremanReact/common/helpers', () => ({
10
10
 
11
11
  describe('ForemanInventoryUpload helpers', () => {
12
12
  describe('getInventoryDocsUrl', () => {
13
- it('requests the Managing Hosts guide at the cloud connection chapter', () => {
13
+ it('requests the admin guide cloud connection chapter', () => {
14
14
  getInventoryDocsUrl();
15
15
  expect(getDocsURL).toHaveBeenCalledWith(
16
- 'Managing_Hosts',
17
- 'configuring-foreman-server-for-cloud-connection'
16
+ 'Administering_Project',
17
+ 'configuring-foreman-server-for-cloud-connection'
18
18
  );
19
19
  });
20
20
  });
@@ -3,7 +3,10 @@ import PropTypes from 'prop-types';
3
3
  import { Button } from '@patternfly/react-core';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
5
  import { useBulkSelect } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
6
- import { JOB_INVOCATION_PATH } from './RemediationTableConstants';
6
+ import {
7
+ JOB_INVOCATION_PATH,
8
+ FORM_SUBMIT_DELAY_MS,
9
+ } from './RemediationTableConstants';
7
10
 
8
11
  const ModalFooter = ({ toggleModal, resolutions, hostsIds, isIop }) => {
9
12
  let token = document.querySelector('meta[name="csrf-token"]');
@@ -24,7 +27,7 @@ const ModalFooter = ({ toggleModal, resolutions, hostsIds, isIop }) => {
24
27
  setTimeout(() => {
25
28
  // eslint-disable-next-line no-unused-expressions
26
29
  formRef.current?.submit?.();
27
- }, 100);
30
+ }, FORM_SUBMIT_DELAY_MS);
28
31
  };
29
32
  return (
30
33
  <form
@@ -45,3 +45,7 @@ export const JOB_INVOCATION_PATH = foremanUrl('/job_invocations/new');
45
45
  export const REMEDIATIONS_API_KEY = 'INSIGHTS_REMEDIATIONS';
46
46
 
47
47
  export const SUBMIT_RESOLUTIONS = 'SUBMIT_INSIGHTS_RESOLUTIONS';
48
+
49
+ // Brief delay before the (non-React) form submit/navigation fires, so the
50
+ // button's isLoading spinner has a chance to render first.
51
+ export const FORM_SUBMIT_DELAY_MS = 100;
@@ -8,11 +8,30 @@ import { useInsightsPermissions } from '../common/Hooks/PermissionsHooks';
8
8
 
9
9
  const scope = 'advisor';
10
10
  const module = './RecommendationDetailsWrapped';
11
+ const pathwayModule = './PathwayDetailsWrapped';
11
12
 
12
13
  const IopRecommendationDetails = props => {
14
+ const pathwayMatch = useRouteMatch(
15
+ '/foreman_rh_cloud/recommendations/pathways/:slug'
16
+ );
13
17
  const urlParams = useRouteMatch('/foreman_rh_cloud/recommendations/:rule_id');
14
18
  // eslint-disable-next-line camelcase
15
19
  const ruleId = urlParams?.params?.rule_id;
20
+
21
+ if (pathwayMatch) {
22
+ return (
23
+ <div className="iop-pathway-details-scalprum advisor">
24
+ <ScalprumComponent
25
+ scope={scope}
26
+ module={pathwayModule}
27
+ pathwayId={pathwayMatch.params.slug}
28
+ IopRemediationModal={RemediationModal}
29
+ {...props}
30
+ />
31
+ </div>
32
+ );
33
+ }
34
+
16
35
  return (
17
36
  <div className="iop-recommendation-details-scalprum advisor">
18
37
  <ScalprumComponent
@@ -1,4 +1,8 @@
1
- export const withInterval = (action, interval = 3000) => ({
1
+ // Arbitrary fallback for this test double only; the real foremanReact
2
+ // default lives in IntervalMiddleware/IntervalConstants.js.
3
+ const DEFAULT_INTERVAL_MS = 3000;
4
+
5
+ export const withInterval = (action, interval = DEFAULT_INTERVAL_MS) => ({
2
6
  ...action,
3
7
  interval,
4
8
  });
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_rh_cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.2.1
4
+ version: 14.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Red Hat Cloud team
@@ -381,6 +381,7 @@ files:
381
381
  - webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/index.js
382
382
  - webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/toolbarButtons.scss
383
383
  - webpack/ForemanInventoryUpload/Components/PageHeader/index.js
384
+ - webpack/ForemanInventoryUpload/Components/TaskConstants.js
384
385
  - webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js
385
386
  - webpack/ForemanInventoryUpload/Components/TaskHistory/index.js
386
387
  - webpack/ForemanInventoryUpload/Components/TaskHistory/taskHistory.scss
@@ -532,7 +533,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
532
533
  - !ruby/object:Gem::Version
533
534
  version: '0'
534
535
  requirements: []
535
- rubygems_version: 4.0.10
536
+ rubygems_version: 4.0.16
536
537
  specification_version: 4
537
538
  summary: Summary of ForemanRhCloud.
538
539
  test_files: