foreman_rh_cloud 14.3.0 → 14.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/rh_cloud/cloud_request_controller.rb +1 -0
  3. data/app/controllers/api/v2/rh_cloud/inventory_controller.rb +11 -5
  4. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +0 -1
  5. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -11
  6. data/app/controllers/foreman_inventory_upload/uploads_settings_controller.rb +0 -8
  7. data/app/services/foreman_rh_cloud/cloud_presence.rb +23 -0
  8. data/config/routes.rb +1 -3
  9. data/lib/foreman_rh_cloud/engine.rb +1 -6
  10. data/lib/foreman_rh_cloud/plugin.rb +3 -2
  11. data/lib/foreman_rh_cloud/version.rb +1 -1
  12. data/lib/insights_cloud/async/cloud_connector_announce_task.rb +76 -19
  13. data/package.json +1 -1
  14. data/test/controllers/inventory_upload/api/inventory_controller_test.rb +10 -8
  15. data/test/jobs/cloud_connector_announce_task_test.rb +116 -106
  16. data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +1 -0
  17. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +1 -0
  18. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +4 -12
  19. data/webpack/ForemanRhCloudPages.js +6 -0
  20. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsLabel.js +8 -7
  21. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +2 -2
  22. data/webpack/InsightsCloudSync/Components/InsightsTable/Pagination.js +1 -0
  23. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +2 -2
  24. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +1 -0
  25. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +7 -5
  26. data/webpack/InsightsHostDetailsTab/InsightsTab.js +53 -39
  27. data/webpack/InsightsHostDetailsTab/InsightsTab.scss +7 -73
  28. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTab.fixtures.js +7 -0
  29. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTab.test.js +52 -30
  30. data/webpack/InsightsHostDetailsTab/components/ListItem/ListItem.js +43 -35
  31. data/webpack/InsightsHostDetailsTab/components/ListItem/__tests__/ListItem.test.js +111 -0
  32. data/webpack/InsightsVulnerabilityActionsBar/__tests__/InsightsVulnerabilityActionsBar.test.js +1 -5
  33. data/webpack/IopPathwayDetails/IopPathwayDetails.js +40 -0
  34. data/webpack/IopRecommendationDetails/IopRecommendationDetails.js +0 -18
  35. data/webpack/common/DropdownToggle.js +1 -0
  36. data/webpack/common/Switcher/HelpLabel.js +7 -5
  37. data/webpack/test_setup.js +1 -0
  38. metadata +4 -8
  39. data/app/services/foreman_rh_cloud/cloud_connector.rb +0 -74
  40. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js +0 -6
  41. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -6
  42. data/webpack/__mocks__/foremanReact/common/helpers.js +0 -14
  43. data/webpack/__mocks__/foremanReact/constants.js +0 -24
  44. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +0 -24
  45. data/webpack/__mocks__/foremanReact/redux/API/index.js +0 -12
@@ -0,0 +1,111 @@
1
+ import React from 'react';
2
+ import { screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { Accordion } from '@patternfly/react-core';
6
+ import { rtlHelpers } from 'foremanReact/common/rtlTestHelpers';
7
+ import ListItem from '../ListItem';
8
+
9
+ const { renderWithI18n } = rtlHelpers;
10
+
11
+ const defaultProps = {
12
+ title: 'Test recommendation title',
13
+ totalRisk: 2,
14
+ resultsUrl:
15
+ 'https://cloud.redhat.com/insights/advisor/recommendations/example',
16
+ solutionUrl: 'https://access.redhat.com/solutions/123456',
17
+ };
18
+
19
+ const renderListItem = (props = {}) =>
20
+ renderWithI18n(
21
+ <Accordion>
22
+ <ListItem {...defaultProps} {...props} />
23
+ </Accordion>
24
+ );
25
+
26
+ describe('ListItem', () => {
27
+ it('renders the recommendation title when collapsed', async () => {
28
+ renderListItem();
29
+
30
+ expect(
31
+ await screen.findByText('Test recommendation title')
32
+ ).toBeInTheDocument();
33
+ });
34
+
35
+ it('renders the total risk label when collapsed', async () => {
36
+ renderListItem({ totalRisk: 2 });
37
+
38
+ expect(await screen.findByText('Moderate')).toBeInTheDocument();
39
+ });
40
+
41
+ it('does not show detail links before expanding', async () => {
42
+ renderListItem();
43
+
44
+ await screen.findByText('Test recommendation title');
45
+
46
+ expect(
47
+ screen.queryByRole('link', { name: /Knowledgebase article/i })
48
+ ).not.toBeInTheDocument();
49
+ expect(
50
+ screen.queryByRole('link', {
51
+ name: /Read more about it in RH cloud insights/i,
52
+ })
53
+ ).not.toBeInTheDocument();
54
+ });
55
+
56
+ it('shows detail links after expanding the recommendation', async () => {
57
+ renderListItem();
58
+
59
+ await userEvent.click(await screen.findByRole('button'));
60
+
61
+ const knowledgebaseLink = screen.getByRole('link', {
62
+ name: /Knowledgebase article/i,
63
+ });
64
+ expect(knowledgebaseLink).toHaveAttribute('href', defaultProps.solutionUrl);
65
+ expect(knowledgebaseLink).toHaveAttribute('target', '_blank');
66
+ expect(knowledgebaseLink).toHaveAttribute('rel', 'noopener noreferrer');
67
+
68
+ const insightsCloudLink = screen.getByRole('link', {
69
+ name: /Read more about it in RH cloud insights/i,
70
+ });
71
+ expect(insightsCloudLink).toHaveAttribute('href', defaultProps.resultsUrl);
72
+ expect(insightsCloudLink).toHaveAttribute('target', '_blank');
73
+ expect(insightsCloudLink).toHaveAttribute('rel', 'noopener noreferrer');
74
+ });
75
+
76
+ it('omits the knowledgebase link when no solution URL is provided', async () => {
77
+ renderListItem({ solutionUrl: '' });
78
+
79
+ await userEvent.click(await screen.findByRole('button'));
80
+
81
+ expect(
82
+ screen.queryByRole('link', { name: /Knowledgebase article/i })
83
+ ).not.toBeInTheDocument();
84
+
85
+ const insightsCloudLink = screen.getByRole('link', {
86
+ name: /Read more about it in RH cloud insights/i,
87
+ });
88
+ expect(insightsCloudLink).toBeInTheDocument();
89
+ expect(insightsCloudLink).toHaveAttribute('target', '_blank');
90
+ expect(insightsCloudLink).toHaveAttribute('rel', 'noopener noreferrer');
91
+ });
92
+
93
+ it('omits the insights cloud link when no results URL is provided', async () => {
94
+ renderListItem({ resultsUrl: '' });
95
+
96
+ await userEvent.click(await screen.findByRole('button'));
97
+
98
+ const knowledgebaseLink = screen.getByRole('link', {
99
+ name: /Knowledgebase article/i,
100
+ });
101
+ expect(knowledgebaseLink).toBeInTheDocument();
102
+ expect(knowledgebaseLink).toHaveAttribute('target', '_blank');
103
+ expect(knowledgebaseLink).toHaveAttribute('rel', 'noopener noreferrer');
104
+
105
+ expect(
106
+ screen.queryByRole('link', {
107
+ name: /Read more about it in RH cloud insights/i,
108
+ })
109
+ ).not.toBeInTheDocument();
110
+ });
111
+ });
@@ -1,13 +1,11 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom';
4
- import * as ForemanContext from 'foremanReact/Root/Context/ForemanContext';
5
4
  import { ForemanHostsIndexActionsBarContext } from 'foremanReact/components/HostsIndex';
6
5
  import * as ConfigHooks from '../../common/Hooks/ConfigHooks';
7
6
  import InsightsVulnerabilityActionsBar from '../index';
8
7
 
9
8
  jest.mock('../../common/Hooks/ConfigHooks');
10
- jest.mock('foremanReact/Root/Context/ForemanContext');
11
9
  jest.mock('../InsightsVulnerabilityActionsBarActions');
12
10
 
13
11
  const mockContextValue = {
@@ -16,11 +14,9 @@ const mockContextValue = {
16
14
  setMenuOpen: jest.fn(),
17
15
  };
18
16
 
19
- const renderComponent = (contextOverrides = {}, orgId = 1) => {
17
+ const renderComponent = (contextOverrides = {}) => {
20
18
  const contextValue = { ...mockContextValue, ...contextOverrides };
21
19
 
22
- ForemanContext.useForemanOrganization.mockReturnValue({ id: orgId });
23
-
24
20
  return render(
25
21
  <ForemanHostsIndexActionsBarContext.Provider value={contextValue}>
26
22
  <InsightsVulnerabilityActionsBar />
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { useRouteMatch } from 'react-router-dom';
3
+ import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core';
4
+
5
+ import RemediationModal from '../InsightsCloudSync/Components/RemediationModal';
6
+ import { createProviderOptions } from '../common/ScalprumModule/ScalprumContext';
7
+ import { useInsightsPermissions } from '../common/Hooks/PermissionsHooks';
8
+
9
+ const scope = 'advisor';
10
+ const pathwayModule = './PathwayDetailsWrapped';
11
+
12
+ const IopPathwayDetails = props => {
13
+ const pathwayMatch = useRouteMatch(
14
+ '/foreman_rh_cloud/recommendations/pathways/:slug'
15
+ );
16
+ const slug = pathwayMatch?.params?.slug;
17
+
18
+ return (
19
+ <div className="iop-pathway-details-scalprum advisor">
20
+ <ScalprumComponent
21
+ scope={scope}
22
+ module={pathwayModule}
23
+ pathwayId={slug}
24
+ IopRemediationModal={RemediationModal}
25
+ {...props}
26
+ />
27
+ </div>
28
+ );
29
+ };
30
+
31
+ const IopPathwayDetailsWrapped = props => {
32
+ const permissions = useInsightsPermissions();
33
+ return (
34
+ <ScalprumProvider {...createProviderOptions(permissions)}>
35
+ <IopPathwayDetails {...props} />
36
+ </ScalprumProvider>
37
+ );
38
+ };
39
+
40
+ export default IopPathwayDetailsWrapped;
@@ -8,30 +8,12 @@ import { useInsightsPermissions } from '../common/Hooks/PermissionsHooks';
8
8
 
9
9
  const scope = 'advisor';
10
10
  const module = './RecommendationDetailsWrapped';
11
- const pathwayModule = './PathwayDetailsWrapped';
12
11
 
13
12
  const IopRecommendationDetails = props => {
14
- const pathwayMatch = useRouteMatch(
15
- '/foreman_rh_cloud/recommendations/pathways/:slug'
16
- );
17
13
  const urlParams = useRouteMatch('/foreman_rh_cloud/recommendations/:rule_id');
18
14
  // eslint-disable-next-line camelcase
19
15
  const ruleId = urlParams?.params?.rule_id;
20
16
 
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
-
35
17
  return (
36
18
  <div className="iop-recommendation-details-scalprum advisor">
37
19
  <ScalprumComponent
@@ -18,6 +18,7 @@ const DropdownToggle = ({ items, ...props }) => {
18
18
  aria-label={__('Table actions')}
19
19
  onClick={() => setOpen(prev => !prev)}
20
20
  isExpanded={isOpen}
21
+ ouiaId="toggle-dropdown-toggle"
21
22
  >
22
23
  <EllipsisVIcon />
23
24
  </MenuToggle>
@@ -1,18 +1,20 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Popover } from '@patternfly/react-core';
3
+ import { Button, Popover } from '@patternfly/react-core';
4
4
  import { HelpIcon } from '@patternfly/react-icons';
5
5
 
6
6
  export const HelpLabel = ({ text, id, className }) => {
7
7
  if (!text) return null;
8
+
8
9
  return (
9
10
  <Popover id={`${id}-help`} bodyContent={text} aria-label="help-text">
10
- <button
11
- onClick={e => e.preventDefault()}
12
- className={`pf-v5-c-form__group-label-help ${className}`}
11
+ <Button
12
+ variant="plain"
13
+ ouiaId={`help-button-${id}`}
14
+ className={className}
13
15
  >
14
16
  <HelpIcon />
15
- </button>
17
+ </Button>
16
18
  </Popover>
17
19
  );
18
20
  };
@@ -0,0 +1 @@
1
+ import 'foremanJSTestSetup';
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.3.0
4
+ version: 14.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Red Hat Cloud team
@@ -107,7 +107,6 @@ files:
107
107
  - app/overrides/layouts/base/styles.html.erb.deface
108
108
  - app/services/foreman_rh_cloud/branch_info.rb
109
109
  - app/services/foreman_rh_cloud/cert_auth.rb
110
- - app/services/foreman_rh_cloud/cloud_connector.rb
111
110
  - app/services/foreman_rh_cloud/cloud_ping_service.rb
112
111
  - app/services/foreman_rh_cloud/cloud_presence.rb
113
112
  - app/services/foreman_rh_cloud/cloud_request.rb
@@ -464,6 +463,7 @@ files:
464
463
  - webpack/InsightsHostDetailsTab/__tests__/InsightsTotalRiskChart.test.js
465
464
  - webpack/InsightsHostDetailsTab/__tests__/NewHostDetailsTab.test.js
466
465
  - webpack/InsightsHostDetailsTab/components/ListItem/ListItem.js
466
+ - webpack/InsightsHostDetailsTab/components/ListItem/__tests__/ListItem.test.js
467
467
  - webpack/InsightsHostDetailsTab/components/ListItem/index.js
468
468
  - webpack/InsightsHostDetailsTab/index.js
469
469
  - webpack/InsightsVulnerability/InsightsVulnerabilityListPage.js
@@ -475,21 +475,16 @@ files:
475
475
  - webpack/InsightsVulnerabilityActionsBar/index.js
476
476
  - webpack/InsightsVulnerabilityHostIndexExtensions/CVECountCell.js
477
477
  - webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js
478
+ - webpack/IopPathwayDetails/IopPathwayDetails.js
478
479
  - webpack/IopRecommendationDetails/IopRecommendationDetails.js
479
- - webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js
480
- - webpack/__mocks__/foremanReact/common/I18n.js
481
480
  - webpack/__mocks__/foremanReact/common/MountingService.js
482
- - webpack/__mocks__/foremanReact/common/helpers.js
483
481
  - webpack/__mocks__/foremanReact/components/ConfirmModal/index.js
484
482
  - webpack/__mocks__/foremanReact/components/Head.js
485
483
  - webpack/__mocks__/foremanReact/components/HostsIndex/index.js
486
484
  - webpack/__mocks__/foremanReact/components/Layout/LayoutConstants.js
487
485
  - webpack/__mocks__/foremanReact/components/ToastsList/index.js
488
486
  - webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js
489
- - webpack/__mocks__/foremanReact/constants.js
490
487
  - webpack/__mocks__/foremanReact/redux.js
491
- - webpack/__mocks__/foremanReact/redux/API/APISelectors.js
492
- - webpack/__mocks__/foremanReact/redux/API/index.js
493
488
  - webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware.js
494
489
  - webpack/__mocks__/foremanReact/routes/RouterSelector.js
495
490
  - webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js
@@ -512,6 +507,7 @@ files:
512
507
  - webpack/common/table/helpers.js
513
508
  - webpack/global_index.js
514
509
  - webpack/index.js
510
+ - webpack/test_setup.js
515
511
  homepage: https://github.com/theforeman/foreman_rh_cloud
516
512
  licenses:
517
513
  - GPL-3.0
@@ -1,74 +0,0 @@
1
- require 'securerandom'
2
-
3
- module ForemanRhCloud
4
- class CloudConnector
5
- CLOUD_CONNECTOR_USER = 'cloud_connector_user'.freeze
6
- CLOUD_CONNECTOR_TOKEN_NAME = 'Cloud connector access token'.freeze
7
- CLOUD_CONNECTOR_FEATURE = 'ansible_configure_cloud_connector'.freeze
8
-
9
- def install
10
- user = service_user
11
- token_value = personal_access_token(user)
12
- target_host = foreman_host
13
- composer = nil
14
-
15
- input = {
16
- :satellite_cloud_connector_user => service_user.login,
17
- :satellite_cloud_connector_password => token_value,
18
- }
19
-
20
- if (http_proxy = ForemanRhCloud.proxy_setting)
21
- input[:satellite_cloud_connector_http_proxy] = http_proxy
22
- end
23
-
24
- Taxonomy.as_taxonomy(target_host.organization, target_host.location) do
25
- composer = ::JobInvocationComposer.for_feature(
26
- CLOUD_CONNECTOR_FEATURE,
27
- [target_host.id],
28
- input
29
- )
30
- composer.trigger!
31
- end
32
-
33
- composer.job_invocation
34
- end
35
-
36
- def latest_job
37
- feature_id = RemoteExecutionFeature.find_by_label(CLOUD_CONNECTOR_FEATURE)&.id
38
- return nil unless feature_id
39
- JobInvocation.where(:remote_execution_feature_id => feature_id).includes(:task).reorder('foreman_tasks_tasks.started_at DESC').first
40
- end
41
-
42
- def personal_access_token(user)
43
- access_token = PersonalAccessToken.find_by_name(CLOUD_CONNECTOR_TOKEN_NAME)
44
-
45
- access_token&.destroy! # destroy the old token if exists
46
-
47
- personal_access_token = PersonalAccessToken.new(:name => CLOUD_CONNECTOR_TOKEN_NAME, :user => user)
48
- token_value = personal_access_token.generate_token
49
- personal_access_token.save!
50
-
51
- token_value
52
- end
53
-
54
- def service_user
55
- user = User.find_by_login(CLOUD_CONNECTOR_USER)
56
-
57
- if user.nil?
58
- user = User.create!(
59
- :login => CLOUD_CONNECTOR_USER,
60
- :password => SecureRandom.base64(255),
61
- :description => "This is a service user used by cloud connector to talk to the Satellite API",
62
- :auth_source => AuthSourceInternal.first,
63
- :admin => true
64
- )
65
- end
66
-
67
- user
68
- end
69
-
70
- def foreman_host
71
- ForemanRhCloud.foreman_host
72
- end
73
- end
74
- end
@@ -1,6 +0,0 @@
1
- export const useForemanSettings = jest.fn(() => ({ perPage: 20 }));
2
- export const useForemanOrganization = jest.fn(() => ({
3
- id: 1,
4
- title: 'some-org',
5
- }));
6
- export const useForemanContext = jest.fn(() => ({ metadata: {} }));
@@ -1,6 +0,0 @@
1
- export { sprintf } from 'jed';
2
-
3
- export const translate = s => s;
4
-
5
- export const ngettext = (singular, plural, count) =>
6
- count === 1 ? singular : plural;
@@ -1,14 +0,0 @@
1
- import camelCase from 'lodash/camelCase';
2
-
3
- export const getURIQuery = jest.fn(() => ({}));
4
-
5
- export const noop = Function.prototype;
6
-
7
- export const propsToCamelCase = ob => {
8
- if (typeof ob !== 'object' || ob === null) return ob;
9
-
10
- return Object.keys(ob).reduce((memo, key) => {
11
- memo[camelCase(key)] = ob[key];
12
- return memo;
13
- }, {});
14
- };
@@ -1,24 +0,0 @@
1
- export const STATUS = {
2
- PENDING: 'PENDING',
3
- RESOLVED: 'RESOLVED',
4
- ERROR: 'ERROR',
5
- };
6
-
7
- export const getControllerSearchProps = (
8
- controller,
9
- id = 'searchBar',
10
- canCreateBookmarks = true
11
- ) => ({
12
- controller,
13
- autocomplete: {
14
- id,
15
- searchQuery: '',
16
- url: `${controller}/auto_complete_search`,
17
- useKeyShortcuts: true,
18
- },
19
- bookmarks: {
20
- url: '/api/bookmarks',
21
- canCreateBookmarks,
22
- documentationUrl: `4.1.5Searching`,
23
- },
24
- });
@@ -1,24 +0,0 @@
1
- import { STATUS } from '../../constants';
2
-
3
- export const selectAPI = state => state.API || {};
4
-
5
- export const selectAPIByKey = (state, key) => selectAPI(state)[key] || {};
6
-
7
- export const selectAPIStatus = (state, key) =>
8
- selectAPIByKey(state, key).status;
9
-
10
- export const selectAPIPayload = (state, key) =>
11
- selectAPIByKey(state, key).payload || {};
12
-
13
- export const selectAPIResponse = (state, key) =>
14
- selectAPIByKey(state, key).response || {};
15
-
16
- export const selectAPIError = (state, key) =>
17
- selectAPIStatus(state, key) === STATUS.ERROR
18
- ? selectAPIResponse(state, key)
19
- : null;
20
-
21
- export const selectAPIErrorMessage = (state, key) => {
22
- const error = selectAPIError(state, key);
23
- return error && error.message;
24
- };
@@ -1,12 +0,0 @@
1
- export const get = data => ({ type: 'get-some-type', ...data });
2
- export const put = data => ({ type: 'put-some-type', ...data });
3
- export const post = data => ({ type: 'post-some-type', ...data });
4
- export const patch = data => ({ type: 'patch-some-type', ...data });
5
-
6
- export const API = {
7
- get: jest.fn(),
8
- put: jest.fn(),
9
- post: jest.fn(),
10
- delete: jest.fn(),
11
- patch: jest.fn(),
12
- };