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
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Grid } from 'patternfly-react';
2
+ import { Grid, GridItem } from '@patternfly/react-core';
3
3
  import InventorySettings from '../InventorySettings';
4
4
  import PageDescription from './components/PageDescription';
5
5
  import InventoryFilter from '../InventoryFilter';
@@ -20,14 +20,14 @@ const PageHeader = () => {
20
20
  <PageDescription />
21
21
  </div>
22
22
  )}
23
- <Grid.Row>
24
- <Grid.Col xs={4}>
23
+ <Grid hasGutter>
24
+ <GridItem span={4}>
25
25
  <InventoryFilter />
26
- </Grid.Col>
27
- <Grid.Col xs={7} xsOffset={1}>
26
+ </GridItem>
27
+ <GridItem span={8}>
28
28
  <ToolbarButtons />
29
- </Grid.Col>
30
- </Grid.Row>
29
+ </GridItem>
30
+ </Grid>
31
31
  </div>
32
32
  );
33
33
  };
@@ -1,6 +1,6 @@
1
1
  .rh-cloud-inventory-page {
2
2
  .inventory-upload-header {
3
- padding: var(--pf-v5-global--spacer--md) var(--pf-v5-global--spacer--lg) var(--pf-v5-global--spacer--lg) var(--pf-v5-global--spacer--lg);
3
+ padding: var(--pf-v5-global--spacer--md) 0 var(--pf-v5-global--spacer--lg) var(--pf-v5-global--spacer--lg);
4
4
 
5
5
  h1 {
6
6
  font-family: 'RedHatDisplay';
@@ -13,7 +13,7 @@
13
13
  }
14
14
 
15
15
  .pf-v5-c-dropdown__toggle {
16
- margin-right: -20px;
16
+ margin-right: 0;
17
17
  }
18
18
 
19
19
  .settings-alert {
@@ -38,6 +38,7 @@
38
38
 
39
39
  .title-dropdown {
40
40
  float: right;
41
+ margin-right: -24px;
41
42
  }
42
43
  }
43
44
  }
@@ -1,11 +1,14 @@
1
1
  import React, { useState } from 'react';
2
- import { Grid, GridItem } from '@patternfly/react-core';
3
2
  import {
3
+ Grid,
4
+ GridItem,
4
5
  Dropdown,
5
6
  DropdownItem,
6
- KebabToggle,
7
- DropdownPosition,
8
- } from '@patternfly/react-core/deprecated';
7
+ DropdownList,
8
+ MenuToggle,
9
+ } from '@patternfly/react-core';
10
+ import { EllipsisVIcon } from '@patternfly/react-icons';
11
+ import { translate as __ } from 'foremanReact/common/I18n';
9
12
  import Head from 'foremanReact/components/Head';
10
13
  import {
11
14
  INVENTORY_PAGE_TITLE,
@@ -25,37 +28,7 @@ const PageTitle = () => {
25
28
  const isIop = useIopConfig();
26
29
  const [showPingModal, setPingModal] = useState(false);
27
30
  const togglePingModal = () => setPingModal(v => !v);
28
- const dropdownItems = [
29
- <DropdownItem
30
- key="tasks-history-button"
31
- ouiaId="tasks-history-button"
32
- href={getActionsHistoryUrl()}
33
- target="_blank"
34
- rel="noopener noreferrer"
35
- >
36
- {ACTIONS_HISTORY_BUTTON_TEXT}
37
- </DropdownItem>,
38
- <DropdownItem
39
- key="inventory-documentation-button"
40
- ouiaId="inventory-documentation-button"
41
- href={getInventoryDocsUrl()}
42
- target="_blank"
43
- rel="noopener noreferrer"
44
- >
45
- {DOCS_BUTTON_TEXT}
46
- </DropdownItem>,
47
- ...(!isIop
48
- ? [
49
- <DropdownItem
50
- key="cloud-ping"
51
- ouiaId="dropdownItem-cloud-ping"
52
- onClick={togglePingModal}
53
- >
54
- {CLOUD_PING_TITLE}
55
- </DropdownItem>,
56
- ]
57
- : []),
58
- ];
31
+
59
32
  return (
60
33
  <Grid className="inventory-upload-header-title">
61
34
  <GridItem span={6}>
@@ -64,21 +37,65 @@ const PageTitle = () => {
64
37
  </Head>
65
38
  <h1>{INVENTORY_PAGE_TITLE}</h1>
66
39
  </GridItem>
67
- <GridItem span={6}>
40
+ <GridItem
41
+ span={6}
42
+ style={{
43
+ display: 'flex',
44
+ justifyContent: 'flex-end',
45
+ marginRight: '-16px',
46
+ }}
47
+ >
68
48
  <Dropdown
69
49
  className="title-dropdown"
70
50
  ouiaId="title-dropdown"
71
- onSelect={() => setIsDropdownOpen(false)}
72
- toggle={
73
- <KebabToggle
74
- onToggle={(_event, isOpen) => setIsDropdownOpen(isOpen)}
75
- />
76
- }
77
51
  isOpen={isDropdownOpen}
78
- isPlain
79
- dropdownItems={dropdownItems}
80
- position={DropdownPosition.right}
81
- />
52
+ onSelect={() => setIsDropdownOpen(false)}
53
+ onOpenChange={val => setIsDropdownOpen(val)}
54
+ popperProps={{ position: 'end' }}
55
+ toggle={toggleRef => (
56
+ <MenuToggle
57
+ ref={toggleRef}
58
+ aria-label={__('Actions')}
59
+ variant="plain"
60
+ onClick={() => setIsDropdownOpen(prev => !prev)}
61
+ isExpanded={isDropdownOpen}
62
+ >
63
+ <EllipsisVIcon />
64
+ </MenuToggle>
65
+ )}
66
+ >
67
+ <DropdownList>
68
+ <DropdownItem
69
+ key="tasks-history-button"
70
+ ouiaId="tasks-history-button"
71
+ to={getActionsHistoryUrl()}
72
+ component="a"
73
+ target="_blank"
74
+ rel="noopener noreferrer"
75
+ >
76
+ {ACTIONS_HISTORY_BUTTON_TEXT}
77
+ </DropdownItem>
78
+ <DropdownItem
79
+ key="inventory-documentation-button"
80
+ ouiaId="inventory-documentation-button"
81
+ to={getInventoryDocsUrl()}
82
+ component="a"
83
+ target="_blank"
84
+ rel="noopener noreferrer"
85
+ >
86
+ {DOCS_BUTTON_TEXT}
87
+ </DropdownItem>
88
+ {!isIop && (
89
+ <DropdownItem
90
+ key="cloud-ping"
91
+ ouiaId="dropdownItem-cloud-ping"
92
+ onClick={togglePingModal}
93
+ >
94
+ {CLOUD_PING_TITLE}
95
+ </DropdownItem>
96
+ )}
97
+ </DropdownList>
98
+ </Dropdown>
82
99
  <CloudPingModal
83
100
  isOpen={showPingModal}
84
101
  toggle={togglePingModal}
@@ -121,14 +121,11 @@ describe('PageHeader', () => {
121
121
  },
122
122
  });
123
123
 
124
- const gridRow = container.querySelector('.row');
125
- expect(gridRow).toBeTruthy();
124
+ const grid = container.querySelector('.pf-v5-l-grid');
125
+ expect(grid).toBeTruthy();
126
126
 
127
- const filterColumn = container.querySelector('.col-xs-4');
128
- expect(filterColumn).toBeTruthy();
129
-
130
- const toolbarColumn = container.querySelector('.col-xs-7');
131
- expect(toolbarColumn).toBeTruthy();
127
+ expect(screen.getByTestId('inventory-filter')).toBeTruthy();
128
+ expect(screen.getByTestId('toolbar-buttons')).toBeTruthy();
132
129
  });
133
130
 
134
131
  test('renders description section only when not in IoP mode', () => {
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
- import { fireEvent, 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
7
  let mockIopMode = false;
@@ -15,10 +17,30 @@ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
15
17
  }),
16
18
  }));
17
19
 
18
- jest.mock('../components/CloudPingModal', () => () => (
19
- <div data-testid="cloud-ping-modal">CloudPingModal</div>
20
- ));
21
- jest.mock('foremanReact/common/helpers', () => ({ getDocsURL: () => {} }));
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
+ }));
22
44
 
23
45
  describe('PageTitle', () => {
24
46
  afterEach(() => {
@@ -31,21 +53,70 @@ describe('PageTitle', () => {
31
53
  });
32
54
 
33
55
  it('renders the kebab dropdown', () => {
34
- const { container } = render(<PageTitle />);
35
- 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/');
36
91
  });
37
92
 
38
- it('renders cloud-ping dropdown item when not in IoP mode', () => {
93
+ it('opens CloudPingModal when clicking Connectivity test', async () => {
39
94
  mockIopMode = false;
40
95
  render(<PageTitle />);
41
- fireEvent.click(screen.getByLabelText('Actions'));
42
- expect(screen.getByText('Connectivity test')).toBeTruthy();
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');
43
112
  });
44
113
 
45
- it('does not render cloud-ping dropdown item when in IoP mode', () => {
114
+ it('does not render cloud-ping dropdown item when in IoP mode', async () => {
46
115
  mockIopMode = true;
47
116
  render(<PageTitle />);
48
- fireEvent.click(screen.getByLabelText('Actions'));
117
+ await act(async () => {
118
+ fireEvent.click(screen.getByLabelText('Actions'));
119
+ });
49
120
  expect(screen.queryByText('Connectivity test')).toBeNull();
50
121
  mockIopMode = false;
51
122
  });
@@ -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', () => {
@@ -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
 
@@ -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,17 +1,20 @@
1
+ import { getDocsURL } from 'foremanReact/common/helpers';
2
+ import {
3
+ getInventoryDocsUrl,
4
+ isExitCodeLoading,
5
+ } from '../ForemanInventoryHelpers';
6
+
1
7
  jest.mock('foremanReact/common/helpers', () => ({
2
8
  getDocsURL: jest.fn(),
3
9
  }));
4
10
 
5
- import { getDocsURL } from 'foremanReact/common/helpers';
6
- import { getInventoryDocsUrl, isExitCodeLoading } from '../ForemanInventoryHelpers';
7
-
8
11
  describe('ForemanInventoryUpload helpers', () => {
9
12
  describe('getInventoryDocsUrl', () => {
10
- it('requests the Managing Hosts guide at the cloud connection chapter', () => {
13
+ it('requests the admin guide cloud connection chapter', () => {
11
14
  getInventoryDocsUrl();
12
15
  expect(getDocsURL).toHaveBeenCalledWith(
13
- 'Managing_Hosts',
14
- 'configuring-foreman-server-for-cloud-connection'
16
+ 'Administering_Project',
17
+ 'configuring-foreman-server-for-cloud-connection'
15
18
  );
16
19
  });
17
20
  });