foreman_rh_cloud 14.2.0 → 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.
- checksums.yaml +4 -4
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/package.json +1 -1
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/EmptyResults.js +15 -6
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/emptyResults.scss +1 -5
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/EmptyState.js +15 -7
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/EmptyState.test.js +1 -3
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/emptyState.scss +1 -5
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/ErrorState.js +2 -2
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/ListItem.test.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountList.test.js +2 -6
- data/webpack/ForemanInventoryUpload/Components/InventoryFilter/Components/ClearButton/ClearButton.js +13 -10
- data/webpack/ForemanInventoryUpload/Components/InventoryFilter/InventoryFilter.js +30 -11
- data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/integration.test.js +3 -1
- data/webpack/ForemanInventoryUpload/Components/InventoryFilter/inventoryFilter.scss +28 -22
- data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.js +4 -4
- data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.scss +16 -3
- data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +13 -14
- data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +7 -7
- data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.scss +3 -2
- data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +63 -46
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +4 -7
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +83 -12
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudPingModal/index.js +31 -38
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +3 -1
- data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/InventoryAutoUpload.js +29 -22
- data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/InventoryAutoUpload.test.js +6 -2
- data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryHelpers.test.js +6 -3
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +90 -23
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableActions.js +1 -2
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +24 -30
- data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableHelpers.js +5 -6
- data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +193 -8
- data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +4 -1
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +8 -9
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +26 -13
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationTableConstants.js +12 -10
- data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +21 -9
- data/webpack/InsightsCloudSync/InsightsCloudSync.test.js +12 -19
- data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +0 -1
- data/webpack/common/DropdownToggle.js +20 -6
- metadata +1 -44
- data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.fixtures.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.js +0 -25
- data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownloadHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/FileDownload.test.js +0 -17
- data/webpack/ForemanInventoryUpload/Components/FileDownload/fileDownload.scss +0 -5
- data/webpack/ForemanInventoryUpload/Components/FileDownload/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.fixtures.js +0 -20
- data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +0 -66
- data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainerHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/NavContainer/__tests__/NavContainer.test.js +0 -69
- data/webpack/ForemanInventoryUpload/Components/NavContainer/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/NavContainer/navContainer.scss +0 -9
- data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.fixtures.js +0 -4
- data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.js +0 -29
- data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRunHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/ScheduledRun.test.js +0 -33
- data/webpack/ForemanInventoryUpload/Components/ScheduledRun/index.js +0 -12
- data/webpack/ForemanInventoryUpload/Components/ScheduledRun/scheduledRun.scss +0 -13
- data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.fixtures.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.js +0 -57
- data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChartHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/StatusChart.test.js +0 -30
- data/webpack/ForemanInventoryUpload/Components/StatusChart/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/StatusChart/statusChart.scss +0 -10
- data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.fixtures.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.js +0 -24
- data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainerHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/TabContainer.test.js +0 -15
- data/webpack/ForemanInventoryUpload/Components/TabContainer/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/TabContainer/tabContainer.scss +0 -10
- data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.fixtures.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.js +0 -19
- data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooterHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/TabFooter.test.js +0 -15
- data/webpack/ForemanInventoryUpload/Components/TabFooter/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/TabFooter/tabFooter.scss +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.fixtures.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +0 -85
- data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeaderHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +0 -114
- data/webpack/ForemanInventoryUpload/Components/TabHeader/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/TabHeader/tabHeader.scss +0 -23
- data/webpack/common/Switcher/index.js +0 -80
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
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
|
-
|
|
20
|
-
)
|
|
21
|
-
|
|
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
|
-
|
|
35
|
-
expect(
|
|
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('
|
|
93
|
+
it('opens CloudPingModal when clicking Connectivity test', async () => {
|
|
39
94
|
mockIopMode = false;
|
|
40
95
|
render(<PageTitle />);
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
});
|
data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudPingModal/index.js
CHANGED
|
@@ -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 [
|
|
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
|
|
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
|
-
[
|
|
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'),
|
|
82
|
+
{sprintf(__('%s organizations'), certAuths.length)}
|
|
101
83
|
</Text>
|
|
102
84
|
<Table
|
|
103
|
-
aria-label="
|
|
85
|
+
aria-label="Organization status"
|
|
104
86
|
ouiaId="simple-table"
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
variant="compact"
|
|
88
|
+
borders={false}
|
|
107
89
|
>
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
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=
|
|
117
|
+
<Icon color={successColor.value}>
|
|
125
118
|
<CheckCircleIcon />
|
|
126
119
|
</Icon>
|
|
127
120
|
);
|
|
128
121
|
if (authStatus.error)
|
|
129
122
|
return (
|
|
130
|
-
<Icon color=
|
|
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(
|
|
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,11 +1,18 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
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
|
-
</
|
|
59
|
-
</Grid
|
|
65
|
+
</GridItem>
|
|
66
|
+
</Grid>
|
|
60
67
|
<br />
|
|
61
|
-
<Grid
|
|
62
|
-
<
|
|
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
|
-
</
|
|
80
|
-
</Grid
|
|
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(
|
|
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(
|
|
32
|
+
expect(
|
|
33
|
+
screen.getByRole('button', { name: /Show Advanced Settings/ })
|
|
34
|
+
).toBeTruthy();
|
|
31
35
|
});
|
|
32
36
|
|
|
33
37
|
it('calls getSettings on mount', () => {
|
|
@@ -1,10 +1,13 @@
|
|
|
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
13
|
it('requests the Managing Hosts guide at the cloud connection chapter', () => {
|
|
@@ -3,9 +3,14 @@ import React, { useEffect } from 'react';
|
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import {
|
|
5
5
|
Table,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
<
|
|
93
|
-
|
|
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" />
|
|
@@ -96,8 +96,7 @@ export const clearAllSelection = () => dispatch => {
|
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
export const onTableSort = (columns, index, direction) => {
|
|
99
|
-
|
|
100
|
-
const { sortKey } = columns[index - 1];
|
|
99
|
+
const { sortKey } = columns[index];
|
|
101
100
|
return fetchInsights({
|
|
102
101
|
sortBy: sortKey,
|
|
103
102
|
sortOrder: direction,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* eslint-disable camelcase */
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { DropdownItem } from '@patternfly/react-core
|
|
4
|
-
import { sortable, cellWidth } from '@patternfly/react-table';
|
|
3
|
+
import { DropdownItem } from '@patternfly/react-core';
|
|
5
4
|
import { AnsibeTowerIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
|
|
6
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
7
6
|
import { foremanUrl } from '../../../ForemanRhCloudHelpers';
|
|
@@ -9,26 +8,23 @@ import DropdownToggle from '../../../common/DropdownToggle';
|
|
|
9
8
|
import InsightsSection from './InsightsSection';
|
|
10
9
|
import InsightsLabel from './InsightsLabel';
|
|
11
10
|
|
|
12
|
-
export const totalRiskFormatter =
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
),
|
|
18
|
-
});
|
|
11
|
+
export const totalRiskFormatter = totalRisk => (
|
|
12
|
+
<InsightsSection className="insights-total-risk" type="icon-group">
|
|
13
|
+
<InsightsLabel value={totalRisk} />
|
|
14
|
+
</InsightsSection>
|
|
15
|
+
);
|
|
19
16
|
|
|
20
|
-
export const hasPlaybookFormatter =
|
|
21
|
-
|
|
17
|
+
export const hasPlaybookFormatter = hasPlaybook =>
|
|
18
|
+
hasPlaybook ? (
|
|
22
19
|
<span className="td-insights-remediate-playbook">
|
|
23
20
|
<AnsibeTowerIcon />
|
|
24
21
|
{__('Playbook')}
|
|
25
22
|
</span>
|
|
26
23
|
) : (
|
|
27
24
|
<span className="td-insights-remediate-manual">{__('Manual')}</span>
|
|
28
|
-
)
|
|
29
|
-
});
|
|
25
|
+
);
|
|
30
26
|
|
|
31
|
-
export const actionsFormatter =
|
|
27
|
+
export const actionsFormatter = rowData => {
|
|
32
28
|
const { recommendationUrl, accessRHUrl, isLocalAdvisorEngine } = rowData;
|
|
33
29
|
const dropdownItems = [];
|
|
34
30
|
|
|
@@ -51,9 +47,7 @@ export const actionsFormatter = (props, { rowData = {} }) => {
|
|
|
51
47
|
</DropdownItem>
|
|
52
48
|
);
|
|
53
49
|
|
|
54
|
-
return {
|
|
55
|
-
children: <DropdownToggle items={dropdownItems} />,
|
|
56
|
-
};
|
|
50
|
+
return <DropdownToggle items={dropdownItems} />;
|
|
57
51
|
};
|
|
58
52
|
|
|
59
53
|
export const columns = [
|
|
@@ -61,39 +55,39 @@ export const columns = [
|
|
|
61
55
|
id: 'hostname',
|
|
62
56
|
sortKey: 'hostname',
|
|
63
57
|
title: __('Hostname'),
|
|
64
|
-
|
|
58
|
+
width: 20,
|
|
65
59
|
},
|
|
66
60
|
{
|
|
67
61
|
id: 'recommendation',
|
|
68
62
|
sortKey: 'title',
|
|
69
63
|
title: __('Recommendation'),
|
|
70
|
-
|
|
64
|
+
width: 50,
|
|
71
65
|
},
|
|
72
66
|
{
|
|
73
|
-
id: '
|
|
67
|
+
id: 'total_risk',
|
|
74
68
|
sortKey: 'total_risk',
|
|
75
69
|
title: __('Total risk'),
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
width: 15,
|
|
71
|
+
formatter: totalRiskFormatter,
|
|
78
72
|
},
|
|
79
73
|
{
|
|
80
|
-
id: '
|
|
74
|
+
id: 'has_playbook',
|
|
81
75
|
title: __('Remediate'),
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
width: 10,
|
|
77
|
+
formatter: hasPlaybookFormatter,
|
|
84
78
|
},
|
|
85
79
|
{
|
|
86
80
|
id: 'actions',
|
|
87
81
|
title: '',
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
width: 5,
|
|
83
|
+
formatter: actionsFormatter,
|
|
90
84
|
},
|
|
91
85
|
];
|
|
92
86
|
|
|
93
87
|
export const getColumnsWithoutHostname = () => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
return columns
|
|
89
|
+
.slice(1)
|
|
90
|
+
.map(col => (col.id === 'recommendation' ? { ...col, width: 70 } : col));
|
|
97
91
|
};
|
|
98
92
|
|
|
99
93
|
export const paginationTitles = {
|
|
@@ -6,7 +6,6 @@ export const modifySelectedRows = (
|
|
|
6
6
|
hits,
|
|
7
7
|
selectedIds,
|
|
8
8
|
showSelectAllAlert,
|
|
9
|
-
hideHost,
|
|
10
9
|
isLocalAdvisorEngine
|
|
11
10
|
) => {
|
|
12
11
|
if (hits.length === 0) return [];
|
|
@@ -24,12 +23,13 @@ export const modifySelectedRows = (
|
|
|
24
23
|
solution_url,
|
|
25
24
|
}) => {
|
|
26
25
|
const disableCheckbox = !has_playbook;
|
|
27
|
-
const cells = [hostname, title, total_risk, has_playbook, results_url];
|
|
28
|
-
if (hideHost) cells.shift();
|
|
29
26
|
return {
|
|
30
|
-
cells,
|
|
31
27
|
disableCheckbox,
|
|
32
28
|
id,
|
|
29
|
+
hostname,
|
|
30
|
+
recommendation: title,
|
|
31
|
+
total_risk,
|
|
32
|
+
has_playbook,
|
|
33
33
|
/** The main table checkbox will be seen as selected only if all rows are selected,
|
|
34
34
|
* in this case we need to select also the disabled once and hide it with css */
|
|
35
35
|
selected: selectedIds[id] || (disableCheckbox && showSelectAllAlert),
|
|
@@ -45,8 +45,7 @@ export const getSortColumnIndex = (columns, sortBy) => {
|
|
|
45
45
|
let colIndex = 0;
|
|
46
46
|
columns.forEach((col, index) => {
|
|
47
47
|
if (col.sortKey === sortBy) {
|
|
48
|
-
|
|
49
|
-
colIndex = index + 1;
|
|
48
|
+
colIndex = index;
|
|
50
49
|
}
|
|
51
50
|
});
|
|
52
51
|
return colIndex;
|