foreman_rh_cloud 14.1.3 → 14.2.1

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +157 -5
  3. data/lib/foreman_rh_cloud/plugin.rb +16 -5
  4. data/lib/foreman_rh_cloud/version.rb +1 -1
  5. data/package.json +1 -1
  6. data/test/unit/services/foreman_rh_cloud/insights_api_forwarder_test.rb +240 -4
  7. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +0 -6
  8. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListReducer.js +0 -2
  9. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/EmptyResults.js +15 -6
  10. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/emptyResults.scss +1 -5
  11. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/EmptyState.js +15 -7
  12. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/EmptyState.test.js +1 -3
  13. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/emptyState.scss +1 -5
  14. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/ErrorState.js +2 -2
  15. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/ListItem.test.js +0 -1
  16. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountList.test.js +2 -6
  17. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListReducer.test.js +0 -2
  18. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/Components/ClearButton/ClearButton.js +13 -10
  19. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/InventoryFilter.js +30 -11
  20. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/integration.test.js +3 -1
  21. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/inventoryFilter.scss +28 -22
  22. data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.js +4 -4
  23. data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.scss +16 -3
  24. data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +13 -14
  25. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +7 -9
  26. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.scss +3 -3
  27. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +65 -42
  28. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +4 -12
  29. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +110 -6
  30. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudPingModal/index.js +31 -38
  31. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +3 -1
  32. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +0 -4
  33. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +18 -47
  34. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +4 -4
  35. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/InventoryAutoUpload.js +29 -22
  36. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/InventoryAutoUpload.test.js +6 -2
  37. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryHelpers.test.js +19 -1
  38. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +90 -23
  39. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableActions.js +1 -2
  40. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +24 -30
  41. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableHelpers.js +5 -6
  42. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +193 -8
  43. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +4 -1
  44. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +8 -9
  45. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +26 -13
  46. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationTableConstants.js +12 -10
  47. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +21 -9
  48. data/webpack/InsightsCloudSync/InsightsCloudSync.test.js +12 -19
  49. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +0 -1
  50. data/webpack/common/DropdownToggle.js +20 -6
  51. metadata +2 -54
  52. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.fixtures.js +0 -0
  53. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.js +0 -25
  54. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownloadHelper.js +0 -0
  55. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/FileDownload.test.js +0 -17
  56. data/webpack/ForemanInventoryUpload/Components/FileDownload/fileDownload.scss +0 -5
  57. data/webpack/ForemanInventoryUpload/Components/FileDownload/index.js +0 -1
  58. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.fixtures.js +0 -20
  59. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +0 -66
  60. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainerHelper.js +0 -0
  61. data/webpack/ForemanInventoryUpload/Components/NavContainer/__tests__/NavContainer.test.js +0 -69
  62. data/webpack/ForemanInventoryUpload/Components/NavContainer/index.js +0 -1
  63. data/webpack/ForemanInventoryUpload/Components/NavContainer/navContainer.scss +0 -9
  64. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorActions.js +0 -21
  65. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorButton.js +0 -59
  66. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorConstants.js +0 -6
  67. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorSelectors.js +0 -19
  68. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorButton.test.js +0 -51
  69. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/index.js +0 -34
  70. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.js +0 -66
  71. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.test.js +0 -61
  72. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/index.js +0 -25
  73. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.fixtures.js +0 -4
  74. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.js +0 -29
  75. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRunHelper.js +0 -0
  76. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/ScheduledRun.test.js +0 -33
  77. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/index.js +0 -12
  78. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/scheduledRun.scss +0 -13
  79. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.fixtures.js +0 -0
  80. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.js +0 -57
  81. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChartHelper.js +0 -0
  82. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/StatusChart.test.js +0 -30
  83. data/webpack/ForemanInventoryUpload/Components/StatusChart/index.js +0 -1
  84. data/webpack/ForemanInventoryUpload/Components/StatusChart/statusChart.scss +0 -10
  85. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.fixtures.js +0 -0
  86. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.js +0 -24
  87. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainerHelper.js +0 -0
  88. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/TabContainer.test.js +0 -15
  89. data/webpack/ForemanInventoryUpload/Components/TabContainer/index.js +0 -1
  90. data/webpack/ForemanInventoryUpload/Components/TabContainer/tabContainer.scss +0 -10
  91. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.fixtures.js +0 -0
  92. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.js +0 -19
  93. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooterHelper.js +0 -0
  94. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/TabFooter.test.js +0 -15
  95. data/webpack/ForemanInventoryUpload/Components/TabFooter/index.js +0 -1
  96. data/webpack/ForemanInventoryUpload/Components/TabFooter/tabFooter.scss +0 -0
  97. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.fixtures.js +0 -0
  98. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +0 -85
  99. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeaderHelper.js +0 -0
  100. data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +0 -114
  101. data/webpack/ForemanInventoryUpload/Components/TabHeader/index.js +0 -1
  102. data/webpack/ForemanInventoryUpload/Components/TabHeader/tabHeader.scss +0 -23
  103. data/webpack/common/Switcher/index.js +0 -80
@@ -1,19 +1,123 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import PropTypes from 'prop-types';
3
+ import { act, fireEvent, render, screen } from '@testing-library/react';
4
+ import '@testing-library/jest-dom';
3
5
  import PageTitle from '../PageTitle';
4
6
 
5
- jest.mock('../components/CloudPingModal', () => () => (
6
- <div data-testid="cloud-ping-modal">CloudPingModal</div>
7
- ));
7
+ let mockIopMode = false;
8
+
9
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
10
+ useForemanContext: () => ({
11
+ metadata: {
12
+ foreman_rh_cloud: {
13
+ iop: mockIopMode,
14
+ },
15
+ },
16
+ UI: {},
17
+ }),
18
+ }));
19
+
20
+ jest.mock('../components/CloudPingModal', () => {
21
+ const React = require('react');
22
+ const PropTypes = require('prop-types');
23
+
24
+ const MockCloudPingModal = ({ isOpen, title }) =>
25
+ isOpen ? (
26
+ <div role="dialog" aria-label={title}>
27
+ Organization status
28
+ </div>
29
+ ) : null;
30
+
31
+ MockCloudPingModal.propTypes = {
32
+ isOpen: PropTypes.bool.isRequired,
33
+ title: PropTypes.string.isRequired,
34
+ };
35
+
36
+ return {
37
+ __esModule: true,
38
+ default: MockCloudPingModal,
39
+ };
40
+ });
41
+ jest.mock('foremanReact/common/helpers', () => ({
42
+ getDocsURL: () => '/links/manual/test',
43
+ }));
8
44
 
9
45
  describe('PageTitle', () => {
46
+ afterEach(() => {
47
+ mockIopMode = false;
48
+ });
49
+
10
50
  it('renders the page title', () => {
11
51
  render(<PageTitle />);
12
52
  expect(screen.getByText('Red Hat Inventory')).toBeTruthy();
13
53
  });
14
54
 
15
55
  it('renders the kebab dropdown', () => {
16
- const { container } = render(<PageTitle />);
17
- expect(container.querySelector('.title-dropdown')).toBeTruthy();
56
+ render(<PageTitle />);
57
+ expect(screen.getByLabelText('Actions')).toBeTruthy();
58
+ });
59
+
60
+ it('renders cloud-ping dropdown item when not in IoP mode', async () => {
61
+ mockIopMode = false;
62
+ render(<PageTitle />);
63
+
64
+ await act(async () => {
65
+ fireEvent.click(screen.getByLabelText('Actions'));
66
+ });
67
+
68
+ // Verify all dropdown items are present
69
+ const connectivityItem = screen.getByText('Connectivity test');
70
+ expect(connectivityItem).toBeTruthy();
71
+
72
+ const tasksHistoryLink = screen.getByRole('menuitem', {
73
+ name: 'Actions history',
74
+ });
75
+ const inventoryDocsLink = screen.getByRole('menuitem', {
76
+ name: 'Documentation',
77
+ });
78
+
79
+ expect(tasksHistoryLink).toBeTruthy();
80
+ expect(inventoryDocsLink).toBeTruthy();
81
+
82
+ // Verify links open in a new tab
83
+ expect(tasksHistoryLink).toHaveAttribute('target', '_blank');
84
+ expect(inventoryDocsLink).toHaveAttribute('target', '_blank');
85
+
86
+ // Verify links have the expected URL patterns
87
+ expect(tasksHistoryLink.getAttribute('href')).toContain(
88
+ '/foreman_tasks/tasks'
89
+ );
90
+ expect(inventoryDocsLink.getAttribute('href')).toContain('/links/manual/');
91
+ });
92
+
93
+ it('opens CloudPingModal when clicking Connectivity test', async () => {
94
+ mockIopMode = false;
95
+ render(<PageTitle />);
96
+
97
+ // Modal should not be visible initially
98
+ expect(screen.queryByRole('dialog')).toBeNull();
99
+
100
+ await act(async () => {
101
+ fireEvent.click(screen.getByLabelText('Actions'));
102
+ });
103
+
104
+ await act(async () => {
105
+ fireEvent.click(screen.getByText('Connectivity test'));
106
+ });
107
+
108
+ // Modal should now be visible
109
+ const dialog = screen.getByRole('dialog');
110
+ expect(dialog).toBeTruthy();
111
+ expect(dialog).toHaveTextContent('Organization status');
112
+ });
113
+
114
+ it('does not render cloud-ping dropdown item when in IoP mode', async () => {
115
+ mockIopMode = true;
116
+ render(<PageTitle />);
117
+ await act(async () => {
118
+ fireEvent.click(screen.getByLabelText('Actions'));
119
+ });
120
+ expect(screen.queryByText('Connectivity test')).toBeNull();
121
+ mockIopMode = false;
18
122
  });
19
123
  });
@@ -2,11 +2,7 @@
2
2
  import React, { useCallback, useEffect, useState } from 'react';
3
3
  import PropTypes from 'prop-types';
4
4
  import { useDispatch, useSelector } from 'react-redux';
5
- import {
6
- Table,
7
- TableBody,
8
- TableHeader,
9
- } from '@patternfly/react-table/deprecated';
5
+ import { Table, Tbody, Tr, Td } from '@patternfly/react-table';
10
6
  import {
11
7
  Card,
12
8
  CardTitle,
@@ -21,6 +17,10 @@ import {
21
17
  CheckCircleIcon,
22
18
  ExclamationCircleIcon,
23
19
  } from '@patternfly/react-icons';
20
+ import {
21
+ global_danger_color_200 as dangerColor,
22
+ global_success_color_100 as successColor,
23
+ } from '@patternfly/react-tokens';
24
24
  import { get } from 'foremanReact/redux/API';
25
25
  import { translate as __, sprintf } from 'foremanReact/common/I18n';
26
26
  import { STATUS } from 'foremanReact/constants';
@@ -30,34 +30,20 @@ import { inventoryUrl } from '../../../../ForemanInventoryHelpers';
30
30
  export const API_KEY = 'CLOUD_PING';
31
31
 
32
32
  const CloudPingModal = ({ title, isOpen, toggle }) => {
33
- const [rows, setRows] = useState([]);
33
+ const [certAuths, setCertAuths] = useState([]);
34
34
  const dispatch = useDispatch();
35
+ const status = useSelector(state => selectAPIStatus(state, API_KEY));
36
+ const isPending = status === STATUS.PENDING;
37
+
35
38
  const handleSuccess = useCallback(
36
39
  ({
37
40
  data: {
38
41
  ping: { cert_auth = [] },
39
42
  },
40
43
  }) => {
41
- cert_auth.length &&
42
- setRows(
43
- cert_auth.map(cert => ({
44
- cells: [
45
- {
46
- title: (
47
- <>
48
- <StatusIcon
49
- isPending={status === STATUS.PENDING}
50
- authStatus={cert}
51
- />{' '}
52
- {cert.org_name} {cert.error}
53
- </>
54
- ),
55
- },
56
- ],
57
- }))
58
- );
44
+ setCertAuths(cert_auth);
59
45
  },
60
- [status]
46
+ []
61
47
  );
62
48
 
63
49
  useEffect(() => {
@@ -71,10 +57,6 @@ const CloudPingModal = ({ title, isOpen, toggle }) => {
71
57
  );
72
58
  }, [isOpen, dispatch, handleSuccess]);
73
59
 
74
- const status = useSelector(state => selectAPIStatus(state, API_KEY));
75
- const isPending = status === STATUS.PENDING;
76
- // const error = useSelector(state => selectAPIErrorMessage(state, API_KEY));
77
-
78
60
  return (
79
61
  <>
80
62
  <Modal
@@ -97,17 +79,28 @@ const CloudPingModal = ({ title, isOpen, toggle }) => {
97
79
  ) : (
98
80
  <>
99
81
  <Text className="pull-right" ouiaId="text-org-count">
100
- {sprintf(__('%s organizations'), rows.length)}
82
+ {sprintf(__('%s organizations'), certAuths.length)}
101
83
  </Text>
102
84
  <Table
103
- aria-label="Simple Table"
85
+ aria-label="Organization status"
104
86
  ouiaId="simple-table"
105
- cells={['']}
106
- rows={rows}
87
+ variant="compact"
88
+ borders={false}
107
89
  >
108
- <TableHeader />
109
- <TableBody />
110
- </Table>{' '}
90
+ <Tbody>
91
+ {certAuths.map((cert, idx) => (
92
+ <Tr
93
+ key={cert.org_name || idx}
94
+ ouiaId={`org-status-row-${idx}`}
95
+ >
96
+ <Td dataLabel={__('Organization')}>
97
+ <StatusIcon isPending={isPending} authStatus={cert} />{' '}
98
+ {cert.org_name} {cert.error}
99
+ </Td>
100
+ </Tr>
101
+ ))}
102
+ </Tbody>
103
+ </Table>
111
104
  </>
112
105
  )}
113
106
  </CardBody>
@@ -121,13 +114,13 @@ const StatusIcon = ({ isPending, authStatus }) => {
121
114
  if (isPending) return <Spinner size="sm" />;
122
115
  if (authStatus.success)
123
116
  return (
124
- <Icon color="green">
117
+ <Icon color={successColor.value}>
125
118
  <CheckCircleIcon />
126
119
  </Icon>
127
120
  );
128
121
  if (authStatus.error)
129
122
  return (
130
- <Icon color="red">
123
+ <Icon color={dangerColor.value}>
131
124
  <ExclamationCircleIcon />
132
125
  </Icon>
133
126
  );
@@ -5,7 +5,9 @@ import SyncButton from '../SyncButton';
5
5
  describe('SyncButton', () => {
6
6
  it('renders the sync button text', () => {
7
7
  render(<SyncButton handleSync={jest.fn()} />);
8
- expect(screen.getByRole('button', { name: /Sync all inventory status/ })).toBeTruthy();
8
+ expect(
9
+ screen.getByRole('button', { name: /Sync all inventory status/ })
10
+ ).toBeTruthy();
9
11
  });
10
12
 
11
13
  it('calls handleSync on click', () => {
@@ -1,16 +1,13 @@
1
1
  import React from 'react';
2
2
  import { useSelector } from 'react-redux';
3
3
  import SyncButton from '../SyncButton';
4
- import CloudConnectorButton from '../CloudConnectorButton';
5
4
  import './toolbarButtons.scss';
6
5
  import { selectSubscriptionConnectionEnabled } from '../../../InventorySettings/InventorySettingsSelectors';
7
- import { useIopConfig } from '../../../../../common/Hooks/ConfigHooks';
8
6
 
9
7
  const ToolbarButtons = () => {
10
8
  const subscriptionConnectionEnabled = useSelector(
11
9
  selectSubscriptionConnectionEnabled
12
10
  );
13
- const isIop = useIopConfig();
14
11
 
15
12
  if (!subscriptionConnectionEnabled) {
16
13
  return null;
@@ -18,7 +15,6 @@ const ToolbarButtons = () => {
18
15
 
19
16
  return (
20
17
  <div className="inventory_toolbar_buttons">
21
- {!isIop && <CloudConnectorButton />}
22
18
  <SyncButton />
23
19
  </div>
24
20
  );
@@ -1,21 +1,13 @@
1
1
  import React from 'react';
2
- import { screen } from '@testing-library/react';
3
- import { rtlHelpers } from 'foremanReact/common/rtlTestHelpers';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import * as reactRedux from 'react-redux';
4
5
  import ToolbarButtons from '../ToolbarButtons';
5
- import { useIopConfig } from '../../../../../../common/Hooks/ConfigHooks';
6
- import { selectSubscriptionConnectionEnabled } from '../../../../InventorySettings/InventorySettingsSelectors';
7
6
 
8
- // Mock the config hook
9
- jest.mock('../../../../../../common/Hooks/ConfigHooks', () => ({
10
- useIopConfig: jest.fn(),
7
+ jest.mock('react-redux', () => ({
8
+ useSelector: jest.fn(),
11
9
  }));
12
10
 
13
- // Mock the selector
14
- jest.mock('../../../../InventorySettings/InventorySettingsSelectors', () => ({
15
- selectSubscriptionConnectionEnabled: jest.fn(),
16
- }));
17
-
18
- // Mock child components to isolate ToolbarButtons testing
19
11
  jest.mock(
20
12
  '../../SyncButton',
21
13
  () =>
@@ -23,56 +15,35 @@ jest.mock(
23
15
  return <div data-testid="sync-button">Sync all inventory status</div>;
24
16
  }
25
17
  );
26
- jest.mock(
27
- '../../CloudConnectorButton',
28
- () =>
29
- function MockCloudConnectorButton() {
30
- return (
31
- <div data-testid="cloud-connector-button">
32
- Configure cloud connector
33
- </div>
34
- );
35
- }
36
- );
37
-
38
- const { renderWithStore } = rtlHelpers;
39
18
 
40
19
  describe('ToolbarButtons', () => {
41
- test('renders both buttons when subscription connection is enabled and not in IOP mode', () => {
42
- useIopConfig.mockReturnValue(false);
43
- selectSubscriptionConnectionEnabled.mockReturnValue(true);
44
-
45
- renderWithStore(<ToolbarButtons />);
46
-
47
- expect(screen.getByTestId('cloud-connector-button')).toBeTruthy();
48
- expect(screen.getByTestId('sync-button')).toBeTruthy();
20
+ afterEach(() => {
21
+ jest.clearAllMocks();
49
22
  });
50
23
 
51
- test('renders only sync button when in IOP mode', () => {
52
- useIopConfig.mockReturnValue(true);
53
- selectSubscriptionConnectionEnabled.mockReturnValue(true);
24
+ test('renders sync button when subscription connection is enabled', () => {
25
+ reactRedux.useSelector.mockReturnValue(true);
54
26
 
55
- renderWithStore(<ToolbarButtons />);
27
+ render(<ToolbarButtons />);
56
28
 
57
- expect(screen.queryByTestId('cloud-connector-button')).toBeNull();
58
- expect(screen.getByTestId('sync-button')).toBeTruthy();
29
+ expect(screen.getByTestId('sync-button')).toBeInTheDocument();
59
30
  });
60
31
 
61
32
  test('renders nothing when subscription connection is not enabled', () => {
62
- useIopConfig.mockReturnValue(false);
63
- selectSubscriptionConnectionEnabled.mockReturnValue(false);
33
+ reactRedux.useSelector.mockReturnValue(false);
64
34
 
65
- const { container } = renderWithStore(<ToolbarButtons />);
35
+ const { container } = render(<ToolbarButtons />);
66
36
 
67
37
  expect(container.firstChild).toBeNull();
68
38
  });
69
39
 
70
40
  test('renders toolbar buttons container with correct className when enabled', () => {
71
- useIopConfig.mockReturnValue(false);
72
- selectSubscriptionConnectionEnabled.mockReturnValue(true);
41
+ reactRedux.useSelector.mockReturnValue(true);
73
42
 
74
- const { container } = renderWithStore(<ToolbarButtons />);
43
+ const { container } = render(<ToolbarButtons />);
75
44
 
76
- expect(container.querySelector('.inventory_toolbar_buttons')).toBeTruthy();
45
+ expect(
46
+ container.querySelector('.inventory_toolbar_buttons')
47
+ ).toBeInTheDocument();
77
48
  });
78
49
  });
@@ -1,14 +1,14 @@
1
1
  import URI from 'urijs';
2
+ import { getDocsURL } from 'foremanReact/common/helpers';
2
3
  import { foremanUrl } from '../ForemanRhCloudHelpers';
3
4
 
4
5
  export const inventoryUrl = path =>
5
6
  foremanUrl(`/foreman_inventory_upload/${path}`);
6
7
 
7
8
  export const getInventoryDocsUrl = () =>
8
- foremanUrl(
9
- `/links/manual/?root_url=${URI.encode(
10
- 'https://docs.redhat.com/en/documentation/red_hat_lightspeed/1-latest/html-single/red_hat_lightspeed_remediations_guide/index'
11
- )}`
9
+ getDocsURL(
10
+ 'Managing_Hosts',
11
+ 'configuring-foreman-server-for-cloud-connection'
12
12
  );
13
13
 
14
14
  export const getSubscriptionServiceDocsUrl = () =>
@@ -1,11 +1,18 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Text, TextVariants, Popover, Button } from '@patternfly/react-core';
3
+ import {
4
+ Text,
5
+ TextVariants,
6
+ Popover,
7
+ Button,
8
+ FormGroup,
9
+ Grid,
10
+ GridItem,
11
+ } from '@patternfly/react-core';
4
12
  import { InfoAltIcon, CaretRightIcon } from '@patternfly/react-icons';
5
- import { FormGroup, Grid } from 'patternfly-react';
6
13
  import { translate as __ } from 'foremanReact/common/I18n';
7
14
  import { foremanUrl } from '../../../ForemanRhCloudHelpers';
8
- import Switcher from '../../../common/Switcher';
15
+ import SwitcherPF4 from '../../../common/Switcher/SwitcherPF4';
9
16
  import { settingsDict } from '../../Components/InventorySettings/AdvancedSetting/AdvancedSettingsConstants';
10
17
  import InventorySettings from '../../Components/InventorySettings/InventorySettings';
11
18
 
@@ -27,19 +34,19 @@ const InventoryAutoUploadSwitcher = ({
27
34
  <Grid>
28
35
  <h3>{__('Red Hat Cloud Inventory')}</h3>
29
36
  <hr />
30
- <Grid.Row>
31
- <Switcher
32
- id="auto-upload"
33
- label={__('Inventory Auto Upload')}
34
- tooltip={__(
35
- 'Enable automatic upload of your hosts inventory to the Red Hat cloud'
36
- )}
37
- isChecked={autoUploadEnabled}
38
- onChange={handleToggle}
39
- labelCol={5}
40
- />
41
-
42
- <Grid.Col sm={5}>
37
+ <Grid hasGutter>
38
+ <GridItem span={7}>
39
+ <SwitcherPF4
40
+ id="auto-upload"
41
+ label={__('Inventory Auto Upload')}
42
+ tooltip={__(
43
+ 'Enable automatic upload of your hosts inventory to the Red Hat cloud'
44
+ )}
45
+ isChecked={autoUploadEnabled}
46
+ onChange={handleToggle}
47
+ />
48
+ </GridItem>
49
+ <GridItem span={5}>
43
50
  <Popover
44
51
  headerContent={
45
52
  <strong>{__('Advanced Inventory Settings')}</strong>
@@ -55,11 +62,11 @@ const InventoryAutoUploadSwitcher = ({
55
62
  {__('Show Advanced Settings')} <CaretRightIcon />
56
63
  </Button>
57
64
  </Popover>
58
- </Grid.Col>
59
- </Grid.Row>
65
+ </GridItem>
66
+ </Grid>
60
67
  <br />
61
- <Grid.Row>
62
- <Grid.Col sm={12}>
68
+ <Grid>
69
+ <GridItem span={12}>
63
70
  <Text component={TextVariants.p} ouiaId="text-more-details">
64
71
  <InfoAltIcon /> {__('More details can be found in')}{' '}
65
72
  <Text
@@ -76,8 +83,8 @@ const InventoryAutoUploadSwitcher = ({
76
83
  </strong>
77
84
  </Text>
78
85
  </Text>
79
- </Grid.Col>
80
- </Grid.Row>
86
+ </GridItem>
87
+ </Grid>
81
88
  </Grid>
82
89
  </FormGroup>
83
90
  );
@@ -22,12 +22,16 @@ describe('InventoryAutoUpload', () => {
22
22
 
23
23
  it('renders the auto upload switcher', () => {
24
24
  render(<InventoryAutoUpload {...buildProps()} />);
25
- expect(screen.getByText('Inventory Auto Upload')).toBeTruthy();
25
+ expect(
26
+ screen.getByRole('checkbox', { name: /Inventory Auto Upload/ })
27
+ ).toBeTruthy();
26
28
  });
27
29
 
28
30
  it('renders the advanced settings button', () => {
29
31
  render(<InventoryAutoUpload {...buildProps()} />);
30
- expect(screen.getByRole('button', { name: /Show Advanced Settings/ })).toBeTruthy();
32
+ expect(
33
+ screen.getByRole('button', { name: /Show Advanced Settings/ })
34
+ ).toBeTruthy();
31
35
  });
32
36
 
33
37
  it('calls getSettings on mount', () => {
@@ -1,6 +1,24 @@
1
- import { isExitCodeLoading } from '../ForemanInventoryHelpers';
1
+ import { getDocsURL } from 'foremanReact/common/helpers';
2
+ import {
3
+ getInventoryDocsUrl,
4
+ isExitCodeLoading,
5
+ } from '../ForemanInventoryHelpers';
6
+
7
+ jest.mock('foremanReact/common/helpers', () => ({
8
+ getDocsURL: jest.fn(),
9
+ }));
2
10
 
3
11
  describe('ForemanInventoryUpload helpers', () => {
12
+ describe('getInventoryDocsUrl', () => {
13
+ it('requests the Managing Hosts guide at the cloud connection chapter', () => {
14
+ getInventoryDocsUrl();
15
+ expect(getDocsURL).toHaveBeenCalledWith(
16
+ 'Managing_Hosts',
17
+ 'configuring-foreman-server-for-cloud-connection'
18
+ );
19
+ });
20
+ });
21
+
4
22
  describe('isExitCodeLoading', () => {
5
23
  it('returns true when exit code contains "running"', () => {
6
24
  expect(isExitCodeLoading('currently running')).toBe(true);
@@ -3,9 +3,14 @@ import React, { useEffect } from 'react';
3
3
  import PropTypes from 'prop-types';
4
4
  import {
5
5
  Table,
6
- TableHeader,
7
- TableBody,
8
- } from '@patternfly/react-table/deprecated';
6
+ Tbody,
7
+ Td,
8
+ Th,
9
+ Thead,
10
+ Tr,
11
+ SortByDirection,
12
+ } from '@patternfly/react-table';
13
+ import { translate as __ } from 'foremanReact/common/I18n';
9
14
  import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
10
15
  import SelectAllAlert from './SelectAllAlert';
11
16
  import {
@@ -51,14 +56,33 @@ const InsightsTable = ({
51
56
  const isIop = useIopConfig();
52
57
 
53
58
  useEffect(() => {
54
- setRows(
55
- modifySelectedRows(hits, selectedIds, showSelectAllAlert, hideHost, isIop)
56
- );
59
+ setRows(modifySelectedRows(hits, selectedIds, showSelectAllAlert, isIop));
57
60
 
58
61
  if (hideHost) setColumns(getColumnsWithoutHostname());
59
- }, [hits, selectedIds, hideHost]);
62
+ }, [hits, selectedIds, hideHost, isIop]);
60
63
 
61
64
  const hasSelectableRows = rows.some(row => !row.disableCheckbox);
65
+ const hasRows = rows.length > 0;
66
+ const selectedRowsCount = rows.filter(
67
+ row => !row.disableCheckbox && row.selected
68
+ ).length;
69
+ const selectableRowsCount = rows.filter(row => !row.disableCheckbox).length;
70
+ const allSelected = hasRows && selectableRowsCount === selectedRowsCount;
71
+ const isSortDirectionValid =
72
+ sortOrder === SortByDirection.asc || sortOrder === SortByDirection.desc;
73
+ const sortIndex = getSortColumnIndex(columns, sortBy);
74
+ const sortByState = isSortDirectionValid
75
+ ? {
76
+ index: hasSelectableRows ? sortIndex + 1 : sortIndex,
77
+ direction: sortOrder,
78
+ }
79
+ : undefined;
80
+
81
+ const getCellContent = (row, col) => {
82
+ if (col.id === 'actions') return col.formatter(row);
83
+ const value = row[col.id];
84
+ return col.formatter ? col.formatter(value) : value;
85
+ };
62
86
 
63
87
  return (
64
88
  <React.Fragment>
@@ -73,24 +97,67 @@ const InsightsTable = ({
73
97
  className="rh-cloud-recommendations-table"
74
98
  ouiaId="rh-cloud-recommendations-table"
75
99
  aria-label="Recommendations Table"
76
- {...(hasSelectableRows && {
77
- onSelect: (_event, isSelected, rowId) =>
78
- onTableSelect(isSelected, rowId, rows, selectedIds),
79
- })}
80
- canSelectAll={hasSelectableRows}
81
- sortBy={{
82
- index: getSortColumnIndex(columns, sortBy),
83
- direction: sortOrder,
84
- }}
85
- onSort={(_event, index, direction) =>
86
- onTableSort(columns, index, direction)
87
- }
88
- cells={columns}
89
- rows={rows}
90
100
  variant="compact"
91
101
  >
92
- <TableHeader />
93
- <TableBody />
102
+ <Thead>
103
+ <Tr ouiaId="recommendations-table-head-row">
104
+ {hasSelectableRows && (
105
+ <Th
106
+ aria-label={__('Select all recommendations')}
107
+ select={{
108
+ onSelect: (_event, isSelected) =>
109
+ onTableSelect(isSelected, -1, rows, selectedIds),
110
+ isSelected: allSelected,
111
+ }}
112
+ />
113
+ )}
114
+ {columns.map((column, index) => (
115
+ <Th
116
+ key={column.id}
117
+ width={column.width}
118
+ screenReaderText={!column.title ? __('Actions') : undefined}
119
+ sort={
120
+ column.sortKey
121
+ ? {
122
+ sortBy: sortByState,
123
+ onSort: (_event, colIndex, direction) =>
124
+ onTableSort(
125
+ columns,
126
+ colIndex - (hasSelectableRows ? 1 : 0),
127
+ direction
128
+ ),
129
+ columnIndex: hasSelectableRows ? index + 1 : index,
130
+ }
131
+ : undefined
132
+ }
133
+ >
134
+ {column.title}
135
+ </Th>
136
+ ))}
137
+ </Tr>
138
+ </Thead>
139
+ <Tbody>
140
+ {rows.map((row, rowIndex) => (
141
+ <Tr key={row.id} ouiaId={`recommendations-row-${row.id}`}>
142
+ {hasSelectableRows && (
143
+ <Td
144
+ select={{
145
+ rowIndex,
146
+ isDisabled: row.disableCheckbox,
147
+ onSelect: (_event, isSelected) =>
148
+ onTableSelect(isSelected, rowIndex, rows, selectedIds),
149
+ isSelected: row.selected,
150
+ }}
151
+ />
152
+ )}
153
+ {columns.map(column => (
154
+ <Td key={`${row.id}-${column.id}`}>
155
+ {getCellContent(row, column)}
156
+ </Td>
157
+ ))}
158
+ </Tr>
159
+ ))}
160
+ </Tbody>
94
161
  </Table>
95
162
  <TableEmptyState status={status} error={error} rowsLength={rows.length} />
96
163
  <Pagination variant="bottom" />