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.
- checksums.yaml +4 -4
- data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +157 -5
- data/lib/foreman_rh_cloud/plugin.rb +16 -5
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/package.json +1 -1
- data/test/unit/services/foreman_rh_cloud/insights_api_forwarder_test.rb +240 -4
- data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +0 -6
- data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListReducer.js +0 -2
- 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/AccountList/__tests__/AccountListReducer.test.js +0 -2
- 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 -9
- data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.scss +3 -3
- data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +65 -42
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +4 -12
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +110 -6
- 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/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +0 -4
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +18 -47
- data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +4 -4
- 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 +19 -1
- 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 +2 -54
- 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/PageHeader/components/CloudConnectorButton/CloudConnectorActions.js +0 -21
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorButton.js +0 -59
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorConstants.js +0 -6
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorSelectors.js +0 -19
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorButton.test.js +0 -51
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/index.js +0 -34
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.js +0 -66
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.test.js +0 -61
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/index.js +0 -25
- 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
|
@@ -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;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import Immutable from 'seamless-immutable';
|
|
3
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
4
|
+
import { SortByDirection } from '@patternfly/react-table';
|
|
3
5
|
import InsightsTable from '../InsightsTable';
|
|
4
6
|
import { tableProps } from './fixtures';
|
|
5
7
|
|
|
@@ -17,6 +19,36 @@ const buildProps = (overrides = {}) => ({
|
|
|
17
19
|
...overrides,
|
|
18
20
|
});
|
|
19
21
|
|
|
22
|
+
const selectableHits = Immutable([
|
|
23
|
+
{
|
|
24
|
+
id: 16,
|
|
25
|
+
hostname: 'foo.example.com',
|
|
26
|
+
title: 'Decreased security: Yum GPG verification disabled',
|
|
27
|
+
total_risk: 1,
|
|
28
|
+
has_playbook: true,
|
|
29
|
+
results_url: 'https://cloud.redhat.com/foo',
|
|
30
|
+
solution_url: '',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 17,
|
|
34
|
+
hostname: 'bar.example.com',
|
|
35
|
+
title: 'Installation of packages across major releases is not supported',
|
|
36
|
+
total_risk: 2,
|
|
37
|
+
has_playbook: false,
|
|
38
|
+
results_url: 'https://cloud.redhat.com/bar',
|
|
39
|
+
solution_url: 'https://access.redhat.com/node/54483',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 18,
|
|
43
|
+
hostname: 'baz.example.com',
|
|
44
|
+
title: 'Kernel package is old',
|
|
45
|
+
total_risk: 3,
|
|
46
|
+
has_playbook: true,
|
|
47
|
+
results_url: 'https://cloud.redhat.com/baz',
|
|
48
|
+
solution_url: '',
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
|
|
20
52
|
describe('InsightsTable', () => {
|
|
21
53
|
afterEach(() => {
|
|
22
54
|
jest.clearAllMocks();
|
|
@@ -36,9 +68,7 @@ describe('InsightsTable', () => {
|
|
|
36
68
|
const props = buildProps();
|
|
37
69
|
render(<InsightsTable {...props} />);
|
|
38
70
|
|
|
39
|
-
expect(
|
|
40
|
-
screen.getByRole('grid', { name: /Recommendations Table/ })
|
|
41
|
-
).toBeTruthy();
|
|
71
|
+
expect(screen.getByLabelText(/Recommendations Table/)).toBeTruthy();
|
|
42
72
|
});
|
|
43
73
|
|
|
44
74
|
it('re-fetches when hostname changes', () => {
|
|
@@ -46,9 +76,7 @@ describe('InsightsTable', () => {
|
|
|
46
76
|
const { rerender } = render(<InsightsTable {...props} />);
|
|
47
77
|
|
|
48
78
|
props.fetchInsights.mockClear();
|
|
49
|
-
rerender(
|
|
50
|
-
<InsightsTable {...props} hostname="host2.example.com" />
|
|
51
|
-
);
|
|
79
|
+
rerender(<InsightsTable {...props} hostname="host2.example.com" />);
|
|
52
80
|
|
|
53
81
|
expect(props.fetchInsights).toHaveBeenCalledTimes(1);
|
|
54
82
|
});
|
|
@@ -57,6 +85,163 @@ describe('InsightsTable', () => {
|
|
|
57
85
|
const props = buildProps({ hits: [], status: 'RESOLVED' });
|
|
58
86
|
const { container } = render(<InsightsTable {...props} />);
|
|
59
87
|
|
|
60
|
-
expect(
|
|
88
|
+
expect(
|
|
89
|
+
container.querySelector('.rh-cloud-recommendations-table')
|
|
90
|
+
).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('selects all enabled rows via header checkbox', () => {
|
|
94
|
+
const props = buildProps({ hits: selectableHits, selectedIds: {} });
|
|
95
|
+
render(<InsightsTable {...props} />);
|
|
96
|
+
|
|
97
|
+
const headerCheckbox = screen.getByLabelText(/Select all rows/i);
|
|
98
|
+
fireEvent.click(headerCheckbox);
|
|
99
|
+
|
|
100
|
+
expect(props.onTableSelect).toHaveBeenCalledTimes(1);
|
|
101
|
+
const [
|
|
102
|
+
isSelected,
|
|
103
|
+
rowId,
|
|
104
|
+
rowsArg,
|
|
105
|
+
selectedIdsArg,
|
|
106
|
+
] = props.onTableSelect.mock.calls[0];
|
|
107
|
+
|
|
108
|
+
expect(isSelected).toBe(true);
|
|
109
|
+
expect(rowId).toBe(-1);
|
|
110
|
+
expect(selectedIdsArg).toEqual({});
|
|
111
|
+
expect(rowsArg).toHaveLength(selectableHits.length);
|
|
112
|
+
expect(rowsArg.map(row => row.id)).toEqual(
|
|
113
|
+
selectableHits.map(hit => hit.id)
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const disabledRows = rowsArg.filter(row => row.disableCheckbox);
|
|
117
|
+
expect(disabledRows).toHaveLength(1);
|
|
118
|
+
expect(disabledRows[0].id).toBe(17);
|
|
119
|
+
expect(disabledRows[0].selected).toBe(false);
|
|
120
|
+
|
|
121
|
+
const enabledRows = rowsArg.filter(row => !row.disableCheckbox);
|
|
122
|
+
expect(enabledRows).toHaveLength(2);
|
|
123
|
+
enabledRows.forEach(row => {
|
|
124
|
+
expect(row.selected).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('selects an individual row via row checkbox', () => {
|
|
129
|
+
const props = buildProps({ hits: selectableHits, selectedIds: {} });
|
|
130
|
+
render(<InsightsTable {...props} />);
|
|
131
|
+
|
|
132
|
+
const rowCheckboxes = screen.getAllByRole('checkbox').slice(1);
|
|
133
|
+
const firstSelectableCheckbox = rowCheckboxes.find(
|
|
134
|
+
checkbox => !checkbox.disabled
|
|
135
|
+
);
|
|
136
|
+
expect(firstSelectableCheckbox).toBeTruthy();
|
|
137
|
+
fireEvent.click(firstSelectableCheckbox);
|
|
138
|
+
|
|
139
|
+
expect(props.onTableSelect).toHaveBeenCalledTimes(1);
|
|
140
|
+
expect(props.onTableSelect).toHaveBeenCalledWith(
|
|
141
|
+
true,
|
|
142
|
+
0,
|
|
143
|
+
expect.any(Array),
|
|
144
|
+
{}
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('renders disabled checkbox rows as disabled', () => {
|
|
149
|
+
const props = buildProps({ hits: selectableHits, selectedIds: {} });
|
|
150
|
+
render(<InsightsTable {...props} />);
|
|
151
|
+
|
|
152
|
+
const checkboxes = screen.getAllByRole('checkbox');
|
|
153
|
+
const rowCheckboxes = checkboxes.slice(1);
|
|
154
|
+
const disabledRowCheckboxes = rowCheckboxes.filter(
|
|
155
|
+
checkbox => checkbox.disabled
|
|
156
|
+
);
|
|
157
|
+
const enabledRowCheckboxes = rowCheckboxes.filter(
|
|
158
|
+
checkbox => !checkbox.disabled
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(disabledRowCheckboxes).toHaveLength(1);
|
|
162
|
+
expect(enabledRowCheckboxes).toHaveLength(2);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('reflects partial/all header checkbox state from selected ids', () => {
|
|
166
|
+
const props = buildProps({
|
|
167
|
+
hits: selectableHits,
|
|
168
|
+
selectedIds: { 16: true },
|
|
169
|
+
});
|
|
170
|
+
const { rerender } = render(<InsightsTable {...props} />);
|
|
171
|
+
|
|
172
|
+
let [headerCheckbox] = screen.getAllByRole('checkbox');
|
|
173
|
+
expect(headerCheckbox.checked).toBe(false);
|
|
174
|
+
|
|
175
|
+
rerender(<InsightsTable {...props} selectedIds={{ 16: true, 18: true }} />);
|
|
176
|
+
[headerCheckbox] = screen.getAllByRole('checkbox');
|
|
177
|
+
expect(headerCheckbox.checked).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('passes normalized sort index when selection column is present', () => {
|
|
181
|
+
const props = buildProps({
|
|
182
|
+
hits: selectableHits,
|
|
183
|
+
sortBy: '',
|
|
184
|
+
sortOrder: SortByDirection.asc,
|
|
185
|
+
});
|
|
186
|
+
render(<InsightsTable {...props} />);
|
|
187
|
+
|
|
188
|
+
fireEvent.click(screen.getByRole('button', { name: /Recommendation/i }));
|
|
189
|
+
|
|
190
|
+
expect(props.onTableSort).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect(props.onTableSort).toHaveBeenCalledWith(
|
|
192
|
+
expect.any(Array),
|
|
193
|
+
1,
|
|
194
|
+
expect.stringMatching(/asc|desc/)
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('passes base sort index when selection column is absent', () => {
|
|
199
|
+
const props = buildProps({
|
|
200
|
+
hits: Immutable([
|
|
201
|
+
{
|
|
202
|
+
id: 16,
|
|
203
|
+
hostname: 'foo.example.com',
|
|
204
|
+
title: 'Decreased security: Yum GPG verification disabled',
|
|
205
|
+
total_risk: 1,
|
|
206
|
+
has_playbook: false,
|
|
207
|
+
results_url: 'https://cloud.redhat.com/foo',
|
|
208
|
+
solution_url: '',
|
|
209
|
+
},
|
|
210
|
+
]),
|
|
211
|
+
sortBy: '',
|
|
212
|
+
sortOrder: SortByDirection.asc,
|
|
213
|
+
});
|
|
214
|
+
render(<InsightsTable {...props} />);
|
|
215
|
+
|
|
216
|
+
fireEvent.click(screen.getByRole('button', { name: /Hostname/i }));
|
|
217
|
+
|
|
218
|
+
expect(props.onTableSort).toHaveBeenCalledTimes(1);
|
|
219
|
+
expect(props.onTableSort).toHaveBeenCalledWith(
|
|
220
|
+
expect.any(Array),
|
|
221
|
+
0,
|
|
222
|
+
expect.stringMatching(/asc|desc/)
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('does not trigger sort for non-sortable Actions column', () => {
|
|
227
|
+
const props = buildProps({ hits: selectableHits });
|
|
228
|
+
render(<InsightsTable {...props} />);
|
|
229
|
+
|
|
230
|
+
const actionsHeader = screen.getByText(/Actions/i).closest('th');
|
|
231
|
+
expect(actionsHeader).toBeTruthy();
|
|
232
|
+
fireEvent.click(actionsHeader);
|
|
233
|
+
|
|
234
|
+
expect(props.onTableSort).not.toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('does not apply sort aria state for invalid sortOrder', () => {
|
|
238
|
+
const props = buildProps({
|
|
239
|
+
hits: selectableHits,
|
|
240
|
+
sortBy: 'total_risk',
|
|
241
|
+
sortOrder: 'INVALID_DIRECTION',
|
|
242
|
+
});
|
|
243
|
+
const { container } = render(<InsightsTable {...props} />);
|
|
244
|
+
|
|
245
|
+
expect(container.querySelector('[aria-sort]')).toBeNull();
|
|
61
246
|
});
|
|
62
247
|
});
|
data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js
CHANGED
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
} from '../InsightsTableConstants';
|
|
17
17
|
|
|
18
18
|
jest.mock('connected-react-router', () => ({
|
|
19
|
-
push: jest.fn(args => ({
|
|
19
|
+
push: jest.fn(args => ({
|
|
20
|
+
type: '@@router/CALL_HISTORY_METHOD',
|
|
21
|
+
payload: args,
|
|
22
|
+
})),
|
|
20
23
|
}));
|
|
21
24
|
|
|
22
25
|
const buildGetState = (queryOverrides = {}) => () => ({
|
|
@@ -29,8 +29,7 @@ export const modifyRows = (
|
|
|
29
29
|
// For IoP:
|
|
30
30
|
// All of the values will be plain strings
|
|
31
31
|
// {
|
|
32
|
-
//
|
|
33
|
-
// hit_id: "c7c6727e-2966-4f7c-87f1-20ef14db7a2d", <-- this refers to a host by insights ID
|
|
32
|
+
// hit_id: "sample-insights-host-id", <-- this refers to a host by insights ID
|
|
34
33
|
// rule_id: "hardening_ssh_client_alive|OPENSSH_HARDENING_CLIENT_ALIVE",
|
|
35
34
|
// resolution_type: "less_secure",
|
|
36
35
|
// resolution_id:"hardening_ssh_client_alive|OPENSSH_HARDENING_CLIENT_ALIVE_less_secure", <-- joined rule id and resolution type
|
|
@@ -48,9 +47,10 @@ export const modifyRows = (
|
|
|
48
47
|
resolution_id: getResolutionId(selectedResolution, id, isIop),
|
|
49
48
|
});
|
|
50
49
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
id,
|
|
51
|
+
hostname,
|
|
52
|
+
recommendation: title,
|
|
53
|
+
resolution: (
|
|
54
54
|
<div>
|
|
55
55
|
<Resolutions
|
|
56
56
|
hit_id={isIop ? host_id : id}
|
|
@@ -59,10 +59,9 @@ export const modifyRows = (
|
|
|
59
59
|
selectedResolution={selectedResolution}
|
|
60
60
|
isIop={isIop}
|
|
61
61
|
/>
|
|
62
|
-
</div
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
id,
|
|
62
|
+
</div>
|
|
63
|
+
),
|
|
64
|
+
reboot,
|
|
66
65
|
};
|
|
67
66
|
});
|
|
68
67
|
|
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
import React, { useEffect } from 'react';
|
|
3
3
|
import Immutable from 'seamless-immutable';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
|
-
import {
|
|
6
|
-
Table,
|
|
7
|
-
TableHeader,
|
|
8
|
-
TableBody,
|
|
9
|
-
} from '@patternfly/react-table/deprecated';
|
|
5
|
+
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
|
|
10
6
|
import { Modal, ModalVariant, Button } from '@patternfly/react-core';
|
|
11
7
|
import { isEmpty, noop } from 'lodash';
|
|
12
8
|
import { STATUS } from 'foremanReact/constants';
|
|
@@ -21,8 +17,7 @@ import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
|
|
|
21
17
|
// Sample iopData:
|
|
22
18
|
// const iopTestData = Immutable([
|
|
23
19
|
// {
|
|
24
|
-
//
|
|
25
|
-
// hostid: 'c7c6727e-2966-4f7c-87f1-20ef14db7a2d',
|
|
20
|
+
// hostid: 'sample-insights-host-id',
|
|
26
21
|
// host_name: 'advisor-test.local',
|
|
27
22
|
// rulename: 'hardening_cryptopol_krb5|NO_CPOL_KRB5',
|
|
28
23
|
// resolutions: [
|
|
@@ -37,8 +32,7 @@ import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
|
|
|
37
32
|
// description: 'Decreased security: krb5 crypto-policies overridden',
|
|
38
33
|
// },
|
|
39
34
|
// {
|
|
40
|
-
//
|
|
41
|
-
// hostid: 'c7c6727e-2966-4f7c-87f1-20ef14db7a2d',
|
|
35
|
+
// hostid: 'sample-insights-host-id',
|
|
42
36
|
// host_name: 'advisor-test.local',
|
|
43
37
|
// rulename: 'hardening_logging_auditd|HARDENING_LOGGING_5_AUDITD',
|
|
44
38
|
// resolutions: [
|
|
@@ -133,11 +127,30 @@ const RemediationModal = ({
|
|
|
133
127
|
className="remediations-table"
|
|
134
128
|
ouiaId="remediations-table"
|
|
135
129
|
aria-label="remediations Table"
|
|
136
|
-
cells={columns}
|
|
137
|
-
rows={rows}
|
|
138
130
|
>
|
|
139
|
-
<
|
|
140
|
-
|
|
131
|
+
<Thead>
|
|
132
|
+
<Tr ouiaId="remediations-table-head-row">
|
|
133
|
+
{columns.map(column => (
|
|
134
|
+
<Th key={column.id} width={column.width}>
|
|
135
|
+
{column.title}
|
|
136
|
+
</Th>
|
|
137
|
+
))}
|
|
138
|
+
</Tr>
|
|
139
|
+
</Thead>
|
|
140
|
+
<Tbody>
|
|
141
|
+
{rows.map(row => (
|
|
142
|
+
<Tr key={row.id} ouiaId={`remediations-row-${row.id}`}>
|
|
143
|
+
{columns.map(column => {
|
|
144
|
+
const value = row[column.id];
|
|
145
|
+
return (
|
|
146
|
+
<Td key={`${row.id}-${column.id}`}>
|
|
147
|
+
{column.formatter ? column.formatter(value) : value}
|
|
148
|
+
</Td>
|
|
149
|
+
);
|
|
150
|
+
})}
|
|
151
|
+
</Tr>
|
|
152
|
+
))}
|
|
153
|
+
</Tbody>
|
|
141
154
|
</Table>
|
|
142
155
|
<TableEmptyState
|
|
143
156
|
status={status}
|
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { cellWidth } from '@patternfly/react-table';
|
|
3
2
|
import { Icon } from '@patternfly/react-core';
|
|
4
3
|
import { CheckCircleIcon } from '@patternfly/react-icons';
|
|
5
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
6
5
|
import { foremanUrl } from '../../../ForemanRhCloudHelpers';
|
|
7
6
|
|
|
8
|
-
export const rebootFormatter =
|
|
9
|
-
|
|
7
|
+
export const rebootFormatter = reboot =>
|
|
8
|
+
reboot ? (
|
|
10
9
|
<Icon color="green">
|
|
11
10
|
<CheckCircleIcon />
|
|
12
11
|
</Icon>
|
|
13
12
|
) : (
|
|
14
13
|
__('No')
|
|
15
|
-
)
|
|
16
|
-
});
|
|
14
|
+
);
|
|
17
15
|
|
|
18
16
|
export const columns = [
|
|
19
17
|
{
|
|
18
|
+
id: 'hostname',
|
|
20
19
|
sortKey: 'hostname',
|
|
21
20
|
title: __('Hostname'),
|
|
22
|
-
|
|
21
|
+
width: 20,
|
|
23
22
|
},
|
|
24
23
|
{
|
|
24
|
+
id: 'recommendation',
|
|
25
25
|
title: __('Recommendation'),
|
|
26
|
-
|
|
26
|
+
width: 35,
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
+
id: 'resolution',
|
|
29
30
|
title: __('Resolution'),
|
|
30
|
-
|
|
31
|
+
width: 30,
|
|
31
32
|
},
|
|
32
33
|
{
|
|
34
|
+
id: 'reboot',
|
|
33
35
|
title: __('Reboot Required'),
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
width: 15,
|
|
37
|
+
formatter: rebootFormatter,
|
|
36
38
|
},
|
|
37
39
|
];
|
|
38
40
|
|
|
@@ -4,9 +4,10 @@ import { translate as __ } from 'foremanReact/common/I18n';
|
|
|
4
4
|
import {
|
|
5
5
|
Dropdown,
|
|
6
6
|
DropdownItem,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
DropdownList,
|
|
8
|
+
MenuToggle,
|
|
9
|
+
} from '@patternfly/react-core';
|
|
10
|
+
import { EllipsisVIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
|
|
10
11
|
import { redHatAdvisorSystems } from '../InsightsCloudSyncHelpers';
|
|
11
12
|
import { useIopConfig } from '../../common/Hooks/ConfigHooks';
|
|
12
13
|
|
|
@@ -44,13 +45,24 @@ const ToolbarDropdown = ({ onRecommendationSync }) => {
|
|
|
44
45
|
className="title-dropdown"
|
|
45
46
|
ouiaId="title-dropdown"
|
|
46
47
|
onSelect={() => setIsDropdownOpen(false)}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
onOpenChange={setIsDropdownOpen}
|
|
49
|
+
toggle={toggleRef => (
|
|
50
|
+
<MenuToggle
|
|
51
|
+
ref={toggleRef}
|
|
52
|
+
variant="plain"
|
|
53
|
+
aria-label={__('Recommendations actions')}
|
|
54
|
+
onClick={() => setIsDropdownOpen(prev => !prev)}
|
|
55
|
+
isExpanded={isDropdownOpen}
|
|
56
|
+
>
|
|
57
|
+
<EllipsisVIcon />
|
|
58
|
+
</MenuToggle>
|
|
59
|
+
)}
|
|
50
60
|
isOpen={isDropdownOpen}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
shouldFocusToggleOnSelect
|
|
62
|
+
popperProps={{ position: 'right' }}
|
|
63
|
+
>
|
|
64
|
+
<DropdownList>{dropdownItems}</DropdownList>
|
|
65
|
+
</Dropdown>
|
|
54
66
|
);
|
|
55
67
|
};
|
|
56
68
|
|
|
@@ -15,16 +15,15 @@ jest.mock('./Components/InsightsTable', () => () => null);
|
|
|
15
15
|
jest.mock('./Components/RemediationModal', () => () => null);
|
|
16
16
|
jest.mock('./Components/InsightsTable/Pagination', () => () => null);
|
|
17
17
|
jest.mock('./Components/InsightsSettings', () => () => null);
|
|
18
|
-
jest.mock(
|
|
19
|
-
|
|
20
|
-
header,
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
));
|
|
18
|
+
jest.mock(
|
|
19
|
+
'foremanReact/routes/common/PageLayout/PageLayout',
|
|
20
|
+
() => ({ children, header, toolbarButtons }) => (
|
|
21
|
+
<div data-testid="page-layout" data-header={header}>
|
|
22
|
+
{toolbarButtons}
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
);
|
|
28
27
|
jest.mock('@scalprum/react-core', () => ({
|
|
29
28
|
ScalprumComponent: () => <div data-testid="scalprum-component" />,
|
|
30
29
|
ScalprumProvider: ({ children }) => <div>{children}</div>,
|
|
@@ -47,9 +46,7 @@ describe('RecommendationsPage', () => {
|
|
|
47
46
|
|
|
48
47
|
describe('non-IOP mode', () => {
|
|
49
48
|
it('renders with rh-cloud-insights class and correct header', () => {
|
|
50
|
-
const { container } = render(
|
|
51
|
-
<RecommendationsPage {...defaultProps} />
|
|
52
|
-
);
|
|
49
|
+
const { container } = render(<RecommendationsPage {...defaultProps} />);
|
|
53
50
|
|
|
54
51
|
expect(container.querySelector('.rh-cloud-insights')).toBeTruthy();
|
|
55
52
|
expect(
|
|
@@ -58,9 +55,7 @@ describe('RecommendationsPage', () => {
|
|
|
58
55
|
});
|
|
59
56
|
|
|
60
57
|
it('does not render IOP advisor view', () => {
|
|
61
|
-
const { container } = render(
|
|
62
|
-
<RecommendationsPage {...defaultProps} />
|
|
63
|
-
);
|
|
58
|
+
const { container } = render(<RecommendationsPage {...defaultProps} />);
|
|
64
59
|
|
|
65
60
|
expect(container.querySelector('.advisor')).toBeNull();
|
|
66
61
|
});
|
|
@@ -72,9 +67,7 @@ describe('RecommendationsPage', () => {
|
|
|
72
67
|
});
|
|
73
68
|
|
|
74
69
|
it('renders advisor view instead of insights page', () => {
|
|
75
|
-
const { container } = render(
|
|
76
|
-
<RecommendationsPage {...defaultProps} />
|
|
77
|
-
);
|
|
70
|
+
const { container } = render(<RecommendationsPage {...defaultProps} />);
|
|
78
71
|
|
|
79
72
|
expect(container.querySelector('.advisor')).toBeTruthy();
|
|
80
73
|
expect(container.querySelector('.rh-cloud-insights')).toBeNull();
|