foreman_rh_cloud 14.1.3 → 14.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +157 -5
  3. data/lib/foreman_rh_cloud/plugin.rb +16 -5
  4. data/lib/foreman_rh_cloud/version.rb +1 -1
  5. data/package.json +1 -1
  6. data/test/unit/services/foreman_rh_cloud/insights_api_forwarder_test.rb +240 -4
  7. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +0 -6
  8. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListReducer.js +0 -2
  9. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/EmptyResults.js +15 -6
  10. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyResults/emptyResults.scss +1 -5
  11. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/EmptyState.js +15 -7
  12. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/__tests__/EmptyState.test.js +1 -3
  13. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/EmptyState/emptyState.scss +1 -5
  14. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ErrorState/ErrorState.js +2 -2
  15. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/ListItem.test.js +0 -1
  16. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountList.test.js +2 -6
  17. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/AccountListReducer.test.js +0 -2
  18. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/Components/ClearButton/ClearButton.js +13 -10
  19. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/InventoryFilter.js +30 -11
  20. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/integration.test.js +3 -1
  21. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/inventoryFilter.scss +28 -22
  22. data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.js +4 -4
  23. data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettings.scss +16 -3
  24. data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +13 -14
  25. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +7 -9
  26. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.scss +3 -3
  27. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +65 -42
  28. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +4 -12
  29. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageTitle.test.js +110 -6
  30. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudPingModal/index.js +31 -38
  31. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +3 -1
  32. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +0 -4
  33. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +18 -47
  34. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +4 -4
  35. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/InventoryAutoUpload.js +29 -22
  36. data/webpack/ForemanInventoryUpload/SubscriptionsPageExtension/InventoryAutoUpload/__tests__/InventoryAutoUpload.test.js +6 -2
  37. data/webpack/ForemanInventoryUpload/__tests__/ForemanInventoryHelpers.test.js +19 -1
  38. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +90 -23
  39. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableActions.js +1 -2
  40. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +24 -30
  41. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableHelpers.js +5 -6
  42. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +193 -8
  43. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +4 -1
  44. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +8 -9
  45. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +26 -13
  46. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationTableConstants.js +12 -10
  47. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +21 -9
  48. data/webpack/InsightsCloudSync/InsightsCloudSync.test.js +12 -19
  49. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +0 -1
  50. data/webpack/common/DropdownToggle.js +20 -6
  51. metadata +2 -54
  52. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.fixtures.js +0 -0
  53. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownload.js +0 -25
  54. data/webpack/ForemanInventoryUpload/Components/FileDownload/FileDownloadHelper.js +0 -0
  55. data/webpack/ForemanInventoryUpload/Components/FileDownload/__tests__/FileDownload.test.js +0 -17
  56. data/webpack/ForemanInventoryUpload/Components/FileDownload/fileDownload.scss +0 -5
  57. data/webpack/ForemanInventoryUpload/Components/FileDownload/index.js +0 -1
  58. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.fixtures.js +0 -20
  59. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +0 -66
  60. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainerHelper.js +0 -0
  61. data/webpack/ForemanInventoryUpload/Components/NavContainer/__tests__/NavContainer.test.js +0 -69
  62. data/webpack/ForemanInventoryUpload/Components/NavContainer/index.js +0 -1
  63. data/webpack/ForemanInventoryUpload/Components/NavContainer/navContainer.scss +0 -9
  64. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorActions.js +0 -21
  65. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorButton.js +0 -59
  66. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorConstants.js +0 -6
  67. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/CloudConnectorSelectors.js +0 -19
  68. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/__tests__/CloudConnectorButton.test.js +0 -51
  69. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/CloudConnectorButton/index.js +0 -34
  70. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.js +0 -66
  71. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.test.js +0 -61
  72. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/index.js +0 -25
  73. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.fixtures.js +0 -4
  74. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRun.js +0 -29
  75. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/ScheduledRunHelper.js +0 -0
  76. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/__tests__/ScheduledRun.test.js +0 -33
  77. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/index.js +0 -12
  78. data/webpack/ForemanInventoryUpload/Components/ScheduledRun/scheduledRun.scss +0 -13
  79. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.fixtures.js +0 -0
  80. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChart.js +0 -57
  81. data/webpack/ForemanInventoryUpload/Components/StatusChart/StatusChartHelper.js +0 -0
  82. data/webpack/ForemanInventoryUpload/Components/StatusChart/__tests__/StatusChart.test.js +0 -30
  83. data/webpack/ForemanInventoryUpload/Components/StatusChart/index.js +0 -1
  84. data/webpack/ForemanInventoryUpload/Components/StatusChart/statusChart.scss +0 -10
  85. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.fixtures.js +0 -0
  86. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainer.js +0 -24
  87. data/webpack/ForemanInventoryUpload/Components/TabContainer/TabContainerHelper.js +0 -0
  88. data/webpack/ForemanInventoryUpload/Components/TabContainer/__tests__/TabContainer.test.js +0 -15
  89. data/webpack/ForemanInventoryUpload/Components/TabContainer/index.js +0 -1
  90. data/webpack/ForemanInventoryUpload/Components/TabContainer/tabContainer.scss +0 -10
  91. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.fixtures.js +0 -0
  92. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooter.js +0 -19
  93. data/webpack/ForemanInventoryUpload/Components/TabFooter/TabFooterHelper.js +0 -0
  94. data/webpack/ForemanInventoryUpload/Components/TabFooter/__tests__/TabFooter.test.js +0 -15
  95. data/webpack/ForemanInventoryUpload/Components/TabFooter/index.js +0 -1
  96. data/webpack/ForemanInventoryUpload/Components/TabFooter/tabFooter.scss +0 -0
  97. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.fixtures.js +0 -0
  98. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +0 -85
  99. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeaderHelper.js +0 -0
  100. data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +0 -114
  101. data/webpack/ForemanInventoryUpload/Components/TabHeader/index.js +0 -1
  102. data/webpack/ForemanInventoryUpload/Components/TabHeader/tabHeader.scss +0 -23
  103. data/webpack/common/Switcher/index.js +0 -80
@@ -96,8 +96,7 @@ export const clearAllSelection = () => dispatch => {
96
96
  };
97
97
 
98
98
  export const onTableSort = (columns, index, direction) => {
99
- // The checkbox column shifts the data columns by 1;
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/deprecated';
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 = ({ title: totalRisk }) => ({
13
- children: (
14
- <InsightsSection className="insights-total-risk" type="icon-group">
15
- <InsightsLabel value={totalRisk} />
16
- </InsightsSection>
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 = ({ title: hasPlaybook }) => ({
21
- children: hasPlaybook ? (
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 = (props, { rowData = {} }) => {
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
- transforms: [cellWidth(20), sortable],
58
+ width: 20,
65
59
  },
66
60
  {
67
61
  id: 'recommendation',
68
62
  sortKey: 'title',
69
63
  title: __('Recommendation'),
70
- transforms: [cellWidth(50), sortable],
64
+ width: 50,
71
65
  },
72
66
  {
73
- id: 'total risk',
67
+ id: 'total_risk',
74
68
  sortKey: 'total_risk',
75
69
  title: __('Total risk'),
76
- transforms: [cellWidth(15), sortable],
77
- cellTransforms: [totalRiskFormatter],
70
+ width: 15,
71
+ formatter: totalRiskFormatter,
78
72
  },
79
73
  {
80
- id: 'remediate',
74
+ id: 'has_playbook',
81
75
  title: __('Remediate'),
82
- transforms: [cellWidth(10)],
83
- cellTransforms: [hasPlaybookFormatter],
76
+ width: 10,
77
+ formatter: hasPlaybookFormatter,
84
78
  },
85
79
  {
86
80
  id: 'actions',
87
81
  title: '',
88
- transforms: [cellWidth(5)],
89
- cellTransforms: [actionsFormatter],
82
+ width: 5,
83
+ formatter: actionsFormatter,
90
84
  },
91
85
  ];
92
86
 
93
87
  export const getColumnsWithoutHostname = () => {
94
- const nextCols = columns.slice(1);
95
- nextCols[0].transforms = [cellWidth(70), sortable];
96
- return nextCols;
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
- // The checkbox column shifts the data columns by 1;
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 { render, screen } from '@testing-library/react';
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(container.querySelector('.rh-cloud-recommendations-table')).toBeTruthy();
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
  });
@@ -16,7 +16,10 @@ import {
16
16
  } from '../InsightsTableConstants';
17
17
 
18
18
  jest.mock('connected-react-router', () => ({
19
- push: jest.fn(args => ({ type: '@@router/CALL_HISTORY_METHOD', payload: 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
- // eslint-disable-next-line spellcheck/spell-checker
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
- cells: [
52
- hostname,
53
- title,
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
- reboot,
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
- // eslint-disable-next-line spellcheck/spell-checker
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
- // eslint-disable-next-line spellcheck/spell-checker
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
- <TableHeader />
140
- <TableBody />
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 = ({ title: reboot }) => ({
9
- children: reboot ? (
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
- transforms: [cellWidth(20)],
21
+ width: 20,
23
22
  },
24
23
  {
24
+ id: 'recommendation',
25
25
  title: __('Recommendation'),
26
- transforms: [cellWidth(35)],
26
+ width: 35,
27
27
  },
28
28
  {
29
+ id: 'resolution',
29
30
  title: __('Resolution'),
30
- transforms: [cellWidth(30)],
31
+ width: 30,
31
32
  },
32
33
  {
34
+ id: 'reboot',
33
35
  title: __('Reboot Required'),
34
- transforms: [cellWidth(15)],
35
- cellTransforms: [rebootFormatter],
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
- KebabToggle,
8
- } from '@patternfly/react-core/deprecated';
9
- import { ExternalLinkAltIcon } from '@patternfly/react-icons';
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
- toggle={
48
- <KebabToggle onToggle={(_event, isOpen) => setIsDropdownOpen(isOpen)} />
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
- isPlain
52
- dropdownItems={dropdownItems}
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('foremanReact/routes/common/PageLayout/PageLayout', () => ({
19
- children,
20
- header,
21
- toolbarButtons,
22
- }) => (
23
- <div data-testid="page-layout" data-header={header}>
24
- {toolbarButtons}
25
- {children}
26
- </div>
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();
@@ -1,7 +1,6 @@
1
1
  import { syncInsights } from '../InsightsCloudSyncActions';
2
2
  import { INSIGHTS_CLOUD_SYNC } from '../InsightsCloudSyncConstants';
3
3
 
4
-
5
4
  jest.mock('../../common/ForemanTasks');
6
5
 
7
6
  describe('InsightsCloudSync actions', () => {