foreman_leapp 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/preupgrade_report_entries_controller.rb +120 -0
- data/app/controllers/api/v2/preupgrade_reports_controller.rb +2 -2
- data/app/models/preupgrade_report_entry.rb +73 -1
- data/app/views/api/v2/preupgrade_report_entries/index.json.rabl +4 -0
- data/app/views/api/v2/preupgrade_reports/show.json.rabl +2 -5
- data/config/routes.rb +17 -3
- data/lib/foreman_leapp/version.rb +1 -1
- data/test/functional/api/v2/preupgrade_report_entries_controller_test.rb +258 -0
- data/test/functional/api/v2/preupgrade_reports_controller_test.rb +7 -5
- data/test/models/preupgrade_report_entry_test.rb +47 -0
- data/webpack/components/PreupgradeReports/components/NoReports.test.js +26 -12
- data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTable.scss +9 -1
- data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTableHelpers.js +164 -0
- data/webpack/components/PreupgradeReportsTable/__tests__/PreupgradeReportsTable.test.js +245 -38
- data/webpack/components/PreupgradeReportsTable/index.js +337 -238
- metadata +7 -3
- data/webpack/components/PreupgradeReports/components/__snapshots__/NoReports.test.js.snap +0 -27
|
@@ -4,11 +4,19 @@
|
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
.leapp-searchbar-item {
|
|
8
|
+
min-width: 200px;
|
|
9
|
+
flex-grow: 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
.leapp-expanded-tbody {
|
|
8
13
|
border: 1px solid var(--pf-v5-global--BorderColor--100);
|
|
9
14
|
box-shadow: var(--pf-v5-global--BoxShadow--sm);
|
|
10
15
|
|
|
11
16
|
tr:first-child > td {
|
|
12
|
-
background-color: var(
|
|
17
|
+
background-color: var(
|
|
18
|
+
--pf-v5-global--primary-color--light-background,
|
|
19
|
+
#e7f1fa
|
|
20
|
+
);
|
|
13
21
|
}
|
|
14
22
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { useDispatch } from 'react-redux';
|
|
3
|
+
import { APIActions } from 'foremanReact/redux/API';
|
|
4
|
+
import { STATUS } from 'foremanReact/constants';
|
|
5
|
+
import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
|
|
6
|
+
|
|
7
|
+
export const usePreupgradeTableState = (data, isExpanded) => {
|
|
8
|
+
const dispatch = useDispatch();
|
|
9
|
+
const lastFetchedKeyRef = useRef(null);
|
|
10
|
+
const lastEntriesFetchKeyRef = useRef(null);
|
|
11
|
+
const { perPage: foremanPerPage } = useForemanSettings();
|
|
12
|
+
|
|
13
|
+
const [status, setStatus] = useState(STATUS.RESOLVED);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [reportId, setReportId] = useState(null);
|
|
16
|
+
const [rows, setRows] = useState([]);
|
|
17
|
+
const [totalCount, setTotalCount] = useState(0);
|
|
18
|
+
const [searchValue, setSearchValue] = useState('');
|
|
19
|
+
const [pagination, setPagination] = useState({
|
|
20
|
+
page: 1,
|
|
21
|
+
perPage: foremanPerPage,
|
|
22
|
+
});
|
|
23
|
+
const [sortBy, setSortBy] = useState({ index: '', direction: 'asc' });
|
|
24
|
+
const [expandedRowIds, setExpandedRowIds] = useState(new Set());
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line camelcase
|
|
27
|
+
const isLeappJob = data?.template_name?.includes('Run preupgrade via Leapp');
|
|
28
|
+
// eslint-disable-next-line camelcase
|
|
29
|
+
const jobStatusLabel = data?.status_label;
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const fetchKey = `${data.id}:${jobStatusLabel}`;
|
|
33
|
+
if (!isLeappJob || !isExpanded || lastFetchedKeyRef.current === fetchKey)
|
|
34
|
+
return undefined;
|
|
35
|
+
|
|
36
|
+
let ignore = false;
|
|
37
|
+
setStatus(STATUS.PENDING);
|
|
38
|
+
|
|
39
|
+
dispatch(
|
|
40
|
+
APIActions.get({
|
|
41
|
+
key: `GET_LEAPP_REPORT_LIST_${data.id}`,
|
|
42
|
+
url: `/api/v2/job_invocations/${data.id}/preupgrade_reports`,
|
|
43
|
+
handleSuccess: res => {
|
|
44
|
+
if (ignore) return;
|
|
45
|
+
lastFetchedKeyRef.current = fetchKey;
|
|
46
|
+
const payload = res.data || res;
|
|
47
|
+
const rawResults = payload.results;
|
|
48
|
+
const resultsArray = rawResults ? [].concat(rawResults) : [];
|
|
49
|
+
|
|
50
|
+
if (resultsArray[0]?.id) {
|
|
51
|
+
setReportId(resultsArray[0].id);
|
|
52
|
+
} else {
|
|
53
|
+
setRows([]);
|
|
54
|
+
setTotalCount(0);
|
|
55
|
+
setStatus(STATUS.RESOLVED);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
handleError: err => {
|
|
59
|
+
if (ignore) return;
|
|
60
|
+
setError(err);
|
|
61
|
+
setStatus(STATUS.ERROR);
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
ignore = true;
|
|
68
|
+
};
|
|
69
|
+
}, [isExpanded, data.id, isLeappJob, dispatch, jobStatusLabel]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!isLeappJob || !isExpanded || !reportId) return undefined;
|
|
73
|
+
|
|
74
|
+
const entriesFetchKey = `${reportId}_P${pagination.page}_PP${pagination.perPage}_S${searchValue}_O${sortBy.index}${sortBy.direction}_J${jobStatusLabel}`;
|
|
75
|
+
if (lastEntriesFetchKeyRef.current === entriesFetchKey) return undefined;
|
|
76
|
+
|
|
77
|
+
let ignore = false;
|
|
78
|
+
setStatus(STATUS.PENDING);
|
|
79
|
+
|
|
80
|
+
const orderParam = sortBy.index
|
|
81
|
+
? `${sortBy.index} ${sortBy.direction.toUpperCase()}`
|
|
82
|
+
: undefined;
|
|
83
|
+
|
|
84
|
+
dispatch(
|
|
85
|
+
APIActions.get({
|
|
86
|
+
key: `GET_LEAPP_REPORT_ENTRIES_${entriesFetchKey}`,
|
|
87
|
+
url: `/api/v2/preupgrade_reports/${reportId}/preupgrade_report_entries`,
|
|
88
|
+
params: {
|
|
89
|
+
page: pagination.page,
|
|
90
|
+
per_page: pagination.perPage,
|
|
91
|
+
...(searchValue && { search: searchValue }),
|
|
92
|
+
...(orderParam && { order: orderParam }),
|
|
93
|
+
},
|
|
94
|
+
handleSuccess: res => {
|
|
95
|
+
if (ignore) return;
|
|
96
|
+
lastEntriesFetchKeyRef.current = entriesFetchKey;
|
|
97
|
+
const payload = res.data || res;
|
|
98
|
+
const fetchedResults = payload.results;
|
|
99
|
+
setRows(fetchedResults ? [].concat(fetchedResults) : []);
|
|
100
|
+
setTotalCount(payload.subtotal ?? 0);
|
|
101
|
+
setStatus(STATUS.RESOLVED);
|
|
102
|
+
setExpandedRowIds(new Set());
|
|
103
|
+
},
|
|
104
|
+
handleError: err => {
|
|
105
|
+
if (ignore) return;
|
|
106
|
+
setError(err);
|
|
107
|
+
setStatus(STATUS.ERROR);
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
ignore = true;
|
|
114
|
+
};
|
|
115
|
+
}, [
|
|
116
|
+
isExpanded,
|
|
117
|
+
isLeappJob,
|
|
118
|
+
reportId,
|
|
119
|
+
searchValue,
|
|
120
|
+
pagination.page,
|
|
121
|
+
pagination.perPage,
|
|
122
|
+
sortBy.index,
|
|
123
|
+
sortBy.direction,
|
|
124
|
+
dispatch,
|
|
125
|
+
jobStatusLabel,
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
const areAllRowsExpanded =
|
|
129
|
+
rows.length > 0 && rows.every(row => expandedRowIds.has(row.id));
|
|
130
|
+
|
|
131
|
+
const onExpandAll = () => {
|
|
132
|
+
setExpandedRowIds(
|
|
133
|
+
areAllRowsExpanded ? new Set() : new Set(rows.map(r => r.id))
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const toggleRowExpansion = (id, isOpen) => {
|
|
138
|
+
setExpandedRowIds(prev => {
|
|
139
|
+
const next = new Set(prev);
|
|
140
|
+
if (isOpen) next.add(id);
|
|
141
|
+
else next.delete(id);
|
|
142
|
+
return next;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
isLeappJob,
|
|
148
|
+
status,
|
|
149
|
+
error,
|
|
150
|
+
reportId,
|
|
151
|
+
rows,
|
|
152
|
+
totalCount,
|
|
153
|
+
pagination,
|
|
154
|
+
sortBy,
|
|
155
|
+
searchValue,
|
|
156
|
+
setSearchValue,
|
|
157
|
+
setPagination,
|
|
158
|
+
setSortBy,
|
|
159
|
+
expandedRowIds,
|
|
160
|
+
toggleRowExpansion,
|
|
161
|
+
areAllRowsExpanded,
|
|
162
|
+
onExpandAll,
|
|
163
|
+
};
|
|
164
|
+
};
|
|
@@ -1,23 +1,49 @@
|
|
|
1
|
-
import '
|
|
2
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Provider } from 'react-redux';
|
|
3
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
4
|
+
import Immutable from 'seamless-immutable';
|
|
3
5
|
import {
|
|
4
|
-
fireEvent,
|
|
5
6
|
render,
|
|
6
7
|
screen,
|
|
7
8
|
waitFor,
|
|
9
|
+
fireEvent,
|
|
8
10
|
within,
|
|
11
|
+
cleanup,
|
|
9
12
|
} from '@testing-library/react';
|
|
10
|
-
|
|
13
|
+
import '@testing-library/jest-dom';
|
|
11
14
|
import { APIActions } from 'foremanReact/redux/API';
|
|
12
15
|
import PreupgradeReportsTable from '../index';
|
|
13
|
-
import { Provider } from 'react-redux';
|
|
14
|
-
import React from 'react';
|
|
15
|
-
import configureMockStore from 'redux-mock-store';
|
|
16
|
-
import thunk from 'redux-thunk';
|
|
17
16
|
|
|
18
17
|
jest.mock('foremanReact/redux/API');
|
|
19
18
|
|
|
20
|
-
const
|
|
19
|
+
const mockReducer = (state = Immutable({ API: {} })) => state;
|
|
20
|
+
|
|
21
|
+
jest.mock('foremanReact/constants', () => ({
|
|
22
|
+
...jest.requireActual('foremanReact/constants'),
|
|
23
|
+
getControllerSearchProps: jest.fn(() => ({
|
|
24
|
+
autocomplete: { url: '' },
|
|
25
|
+
bookmarks: { controller: 'preupgrade_report_entries', url: '/bookmarks' },
|
|
26
|
+
})),
|
|
27
|
+
STATUS: { PENDING: 'PENDING', RESOLVED: 'RESOLVED', ERROR: 'ERROR' },
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
|
|
31
|
+
useForemanSettings: jest.fn(() => ({ perPage: 5 })),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock('foremanReact/components/SearchBar', () => {
|
|
35
|
+
const MockSearchBar = ({ onSearch, onChange }) => (
|
|
36
|
+
<input
|
|
37
|
+
data-testid="search-input"
|
|
38
|
+
aria-label="Search preupgrade report entries"
|
|
39
|
+
onChange={e => onChange && onChange(e.target.value)}
|
|
40
|
+
onKeyDown={e => {
|
|
41
|
+
if (e.key === 'Enter') onSearch(e.target.value);
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
return MockSearchBar;
|
|
46
|
+
});
|
|
21
47
|
|
|
22
48
|
const mockJobId = 42;
|
|
23
49
|
const mockReportId = 999;
|
|
@@ -26,9 +52,6 @@ const mockJobData = {
|
|
|
26
52
|
template_name: 'Run preupgrade via Leapp',
|
|
27
53
|
};
|
|
28
54
|
|
|
29
|
-
// Entry 0 (id=1): command remediation + inhibitor flag → fixable + selectable
|
|
30
|
-
// Entry 1 (id=2): hint-only remediation → has_remediation=Yes, NOT selectable
|
|
31
|
-
// Entries 2-11: no remediations → has_remediation=No, NOT selectable
|
|
32
55
|
const mockEntries = Array.from({ length: 12 }, (_, i) => ({
|
|
33
56
|
id: i + 1,
|
|
34
57
|
title: `Report Entry ${i + 1}`,
|
|
@@ -51,21 +74,25 @@ const mockEntries = Array.from({ length: 12 }, (_, i) => ({
|
|
|
51
74
|
}));
|
|
52
75
|
|
|
53
76
|
describe('PreupgradeReportsTable', () => {
|
|
54
|
-
let store;
|
|
55
|
-
|
|
56
77
|
beforeEach(() => {
|
|
57
|
-
store = mockStore({ API: {} });
|
|
58
78
|
jest.clearAllMocks();
|
|
59
79
|
|
|
60
80
|
APIActions.get.mockImplementation(({ key, handleSuccess }) => {
|
|
61
|
-
return
|
|
62
|
-
if (key.includes('GET_LEAPP_REPORT_LIST'))
|
|
81
|
+
return () => {
|
|
82
|
+
if (key.includes('GET_LEAPP_REPORT_LIST')) {
|
|
63
83
|
handleSuccess({ results: [{ id: mockReportId }] });
|
|
64
|
-
|
|
84
|
+
}
|
|
85
|
+
if (key.includes('GET_LEAPP_REPORT_ENTRIES')) {
|
|
65
86
|
handleSuccess({
|
|
66
87
|
id: mockReportId,
|
|
67
|
-
|
|
88
|
+
results: mockEntries,
|
|
89
|
+
total: mockEntries.length,
|
|
90
|
+
subtotal: mockEntries.length,
|
|
68
91
|
});
|
|
92
|
+
}
|
|
93
|
+
if (key.includes('GET_FIXABLE_COUNT')) {
|
|
94
|
+
handleSuccess({ total: 1, subtotal: 1 });
|
|
95
|
+
}
|
|
69
96
|
return { type: 'MOCK_API_SUCCESS' };
|
|
70
97
|
};
|
|
71
98
|
});
|
|
@@ -73,15 +100,24 @@ describe('PreupgradeReportsTable', () => {
|
|
|
73
100
|
APIActions.post.mockImplementation(() => () => ({ type: 'MOCK_API_POST' }));
|
|
74
101
|
});
|
|
75
102
|
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
cleanup();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const renderComponent = (data = mockJobData) => {
|
|
108
|
+
const store = configureStore({
|
|
109
|
+
reducer: mockReducer,
|
|
110
|
+
});
|
|
111
|
+
return render(
|
|
78
112
|
<Provider store={store}>
|
|
79
113
|
<PreupgradeReportsTable data={data} />
|
|
80
114
|
</Provider>
|
|
81
115
|
);
|
|
116
|
+
};
|
|
82
117
|
|
|
83
|
-
const expandSection = () =>
|
|
118
|
+
const expandSection = () => {
|
|
84
119
|
fireEvent.click(screen.getByText('Leapp preupgrade report'));
|
|
120
|
+
};
|
|
85
121
|
|
|
86
122
|
const waitForTable = () =>
|
|
87
123
|
waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
|
|
@@ -103,6 +139,9 @@ describe('PreupgradeReportsTable', () => {
|
|
|
103
139
|
});
|
|
104
140
|
|
|
105
141
|
it('refetches when status_label transitions (e.g. Running → Succeeded)', async () => {
|
|
142
|
+
const store = configureStore({
|
|
143
|
+
reducer: mockReducer,
|
|
144
|
+
});
|
|
106
145
|
const { rerender } = render(
|
|
107
146
|
<Provider store={store}>
|
|
108
147
|
<PreupgradeReportsTable
|
|
@@ -151,8 +190,13 @@ describe('PreupgradeReportsTable', () => {
|
|
|
151
190
|
return () => {
|
|
152
191
|
if (key.includes('GET_LEAPP_REPORT_LIST'))
|
|
153
192
|
handleSuccess({ results: [{ id: mockReportId }] });
|
|
154
|
-
if (key.includes('
|
|
155
|
-
handleSuccess({
|
|
193
|
+
if (key.includes('GET_LEAPP_REPORT_ENTRIES'))
|
|
194
|
+
handleSuccess({
|
|
195
|
+
id: mockReportId,
|
|
196
|
+
results: [],
|
|
197
|
+
total: 0,
|
|
198
|
+
subtotal: 0,
|
|
199
|
+
});
|
|
156
200
|
return { type: 'EMPTY' };
|
|
157
201
|
};
|
|
158
202
|
});
|
|
@@ -175,7 +219,7 @@ describe('PreupgradeReportsTable', () => {
|
|
|
175
219
|
).toBeInTheDocument();
|
|
176
220
|
});
|
|
177
221
|
|
|
178
|
-
it('expands all rows', async () => {
|
|
222
|
+
it('expands all rows when expand-all is clicked', async () => {
|
|
179
223
|
renderComponent();
|
|
180
224
|
expandSection();
|
|
181
225
|
await waitForTable();
|
|
@@ -188,6 +232,65 @@ describe('PreupgradeReportsTable', () => {
|
|
|
188
232
|
).toBeInTheDocument();
|
|
189
233
|
});
|
|
190
234
|
|
|
235
|
+
it('collapses all rows when expand-all is clicked a second time', async () => {
|
|
236
|
+
renderComponent();
|
|
237
|
+
expandSection();
|
|
238
|
+
await waitForTable();
|
|
239
|
+
|
|
240
|
+
const expandAllButton = screen.getByLabelText('Expand all rows');
|
|
241
|
+
fireEvent.click(expandAllButton); // expand
|
|
242
|
+
await screen.findByText('Summary for report entry 1');
|
|
243
|
+
fireEvent.click(expandAllButton); // collapse
|
|
244
|
+
|
|
245
|
+
await waitFor(() => {
|
|
246
|
+
expect(
|
|
247
|
+
screen.queryByText('Summary for report entry 1')
|
|
248
|
+
).not.toBeInTheDocument();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('calls the show endpoint with search param', async () => {
|
|
253
|
+
renderComponent();
|
|
254
|
+
expandSection();
|
|
255
|
+
await waitForTable();
|
|
256
|
+
|
|
257
|
+
const input = screen.getByTestId('search-input');
|
|
258
|
+
fireEvent.change(input, { target: { value: 'title = Report' } });
|
|
259
|
+
fireEvent.keyDown(input, { key: 'Enter' });
|
|
260
|
+
|
|
261
|
+
await waitFor(() => {
|
|
262
|
+
const calls = APIActions.get.mock.calls
|
|
263
|
+
.flat()
|
|
264
|
+
.filter(
|
|
265
|
+
arg =>
|
|
266
|
+
arg?.key?.includes('GET_LEAPP_REPORT_ENTRIES') &&
|
|
267
|
+
arg?.params?.search
|
|
268
|
+
);
|
|
269
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
270
|
+
expect(calls[0].params.search).toBe('title = Report');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('calls the endpoint with sort parameters when a column header is clicked', async () => {
|
|
275
|
+
renderComponent();
|
|
276
|
+
expandSection();
|
|
277
|
+
await waitForTable();
|
|
278
|
+
|
|
279
|
+
const titleSortButton = screen.getByRole('button', { name: /Title/i });
|
|
280
|
+
fireEvent.click(titleSortButton);
|
|
281
|
+
|
|
282
|
+
await waitFor(() => {
|
|
283
|
+
const calls = APIActions.get.mock.calls
|
|
284
|
+
.flat()
|
|
285
|
+
.filter(
|
|
286
|
+
arg =>
|
|
287
|
+
arg?.key?.includes('GET_LEAPP_REPORT_ENTRIES') && arg?.params?.order
|
|
288
|
+
);
|
|
289
|
+
const lastCall = calls[calls.length - 1];
|
|
290
|
+
expect(lastCall.params.order).toContain('title');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
191
294
|
it('paginates to the next page', async () => {
|
|
192
295
|
renderComponent();
|
|
193
296
|
expandSection();
|
|
@@ -237,7 +340,6 @@ describe('PreupgradeReportsTable', () => {
|
|
|
237
340
|
expandSection();
|
|
238
341
|
await waitForTable();
|
|
239
342
|
|
|
240
|
-
// id=1: command → Yes
|
|
241
343
|
const row1 = screen
|
|
242
344
|
.getByText('Report Entry 1', { selector: 'td' })
|
|
243
345
|
.closest('tr');
|
|
@@ -247,7 +349,6 @@ describe('PreupgradeReportsTable', () => {
|
|
|
247
349
|
)
|
|
248
350
|
).toBeInTheDocument();
|
|
249
351
|
|
|
250
|
-
// id=2: hint-only → still Yes (display column shows any remediations)
|
|
251
352
|
const row2 = screen
|
|
252
353
|
.getByText('Report Entry 2', { selector: 'td' })
|
|
253
354
|
.closest('tr');
|
|
@@ -257,7 +358,6 @@ describe('PreupgradeReportsTable', () => {
|
|
|
257
358
|
)
|
|
258
359
|
).toBeInTheDocument();
|
|
259
360
|
|
|
260
|
-
// id=3: no remediations → No
|
|
261
361
|
const row3 = screen
|
|
262
362
|
.getByText('Report Entry 3', { selector: 'td' })
|
|
263
363
|
.closest('tr');
|
|
@@ -326,9 +426,52 @@ describe('PreupgradeReportsTable', () => {
|
|
|
326
426
|
|
|
327
427
|
// checkboxes[2] = entry id=2, which has hint-only remediation — must be disabled
|
|
328
428
|
const hintCheckbox = screen.getAllByRole('checkbox')[2];
|
|
429
|
+
|
|
329
430
|
expect(hintCheckbox).toBeDisabled();
|
|
431
|
+
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeDisabled();
|
|
432
|
+
});
|
|
330
433
|
|
|
331
|
-
|
|
434
|
+
it('disables Fix Selected when selecting all on page with no fixable entries', async () => {
|
|
435
|
+
APIActions.get.mockImplementation(({ key, handleSuccess }) => {
|
|
436
|
+
return () => {
|
|
437
|
+
if (key.includes('GET_LEAPP_REPORT_LIST')) {
|
|
438
|
+
handleSuccess({ results: [{ id: mockReportId }] });
|
|
439
|
+
}
|
|
440
|
+
if (key.includes('GET_LEAPP_REPORT_ENTRIES')) {
|
|
441
|
+
handleSuccess({
|
|
442
|
+
id: mockReportId,
|
|
443
|
+
results: [
|
|
444
|
+
{
|
|
445
|
+
id: 999,
|
|
446
|
+
title: 'Non-fixable Entry',
|
|
447
|
+
hostname: 'test.com',
|
|
448
|
+
host_id: 200,
|
|
449
|
+
severity: 'low',
|
|
450
|
+
summary: 'This has no command remediation',
|
|
451
|
+
detail: {
|
|
452
|
+
remediations: [{ type: 'hint', context: 'Manual fix only' }],
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
total: 1,
|
|
457
|
+
subtotal: 1,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (key.includes('GET_FIXABLE_COUNT')) {
|
|
461
|
+
// No fixable entries in this test
|
|
462
|
+
handleSuccess({ total: 0, subtotal: 0 });
|
|
463
|
+
}
|
|
464
|
+
return { type: 'MOCK_API_SUCCESS' };
|
|
465
|
+
};
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
renderComponent();
|
|
469
|
+
expandSection();
|
|
470
|
+
await waitFor(() =>
|
|
471
|
+
screen.getByText('Non-fixable Entry', { selector: 'td' })
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
fireEvent.click(screen.getByLabelText('Select all'));
|
|
332
475
|
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeDisabled();
|
|
333
476
|
});
|
|
334
477
|
|
|
@@ -363,13 +506,18 @@ describe('PreupgradeReportsTable', () => {
|
|
|
363
506
|
expect(callParams.inputs).toBeUndefined();
|
|
364
507
|
});
|
|
365
508
|
|
|
366
|
-
it('
|
|
509
|
+
it('renders toolbar buttons as disabled when report has no entries', async () => {
|
|
367
510
|
APIActions.get.mockImplementation(({ key, handleSuccess }) => {
|
|
368
511
|
return () => {
|
|
369
512
|
if (key.includes('GET_LEAPP_REPORT_LIST'))
|
|
370
513
|
handleSuccess({ results: [{ id: mockReportId }] });
|
|
371
|
-
if (key.includes('
|
|
372
|
-
handleSuccess({
|
|
514
|
+
if (key.includes('GET_LEAPP_REPORT_ENTRIES'))
|
|
515
|
+
handleSuccess({
|
|
516
|
+
id: mockReportId,
|
|
517
|
+
results: [],
|
|
518
|
+
total: 0,
|
|
519
|
+
subtotal: 0,
|
|
520
|
+
});
|
|
373
521
|
return { type: 'EMPTY' };
|
|
374
522
|
};
|
|
375
523
|
});
|
|
@@ -378,12 +526,9 @@ describe('PreupgradeReportsTable', () => {
|
|
|
378
526
|
await waitFor(() =>
|
|
379
527
|
screen.getByText('The preupgrade report shows no issues.')
|
|
380
528
|
);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
).
|
|
384
|
-
expect(
|
|
385
|
-
screen.queryByRole('button', { name: 'Run Upgrade' })
|
|
386
|
-
).not.toBeInTheDocument();
|
|
529
|
+
|
|
530
|
+
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeDisabled();
|
|
531
|
+
expect(screen.getByRole('button', { name: 'Run Upgrade' })).toBeDisabled();
|
|
387
532
|
});
|
|
388
533
|
|
|
389
534
|
it('renders the SelectAll checkbox', async () => {
|
|
@@ -406,4 +551,66 @@ describe('PreupgradeReportsTable', () => {
|
|
|
406
551
|
).not.toBeDisabled()
|
|
407
552
|
);
|
|
408
553
|
});
|
|
554
|
+
|
|
555
|
+
it('dispatches bulk_remediate API call when Fix Selected is clicked for all pages', async () => {
|
|
556
|
+
renderComponent();
|
|
557
|
+
expandSection();
|
|
558
|
+
await waitForTable();
|
|
559
|
+
|
|
560
|
+
fireEvent.click(screen.getByLabelText('Select all'));
|
|
561
|
+
|
|
562
|
+
await waitFor(() =>
|
|
563
|
+
expect(
|
|
564
|
+
screen.getByRole('button', { name: 'Fix Selected' })
|
|
565
|
+
).not.toBeDisabled()
|
|
566
|
+
);
|
|
567
|
+
fireEvent.click(screen.getByRole('button', { name: 'Fix Selected' }));
|
|
568
|
+
|
|
569
|
+
expect(APIActions.post).toHaveBeenCalledWith(
|
|
570
|
+
expect.objectContaining({
|
|
571
|
+
url: expect.stringContaining('/bulk_remediate'),
|
|
572
|
+
params: {
|
|
573
|
+
search: '',
|
|
574
|
+
excluded_ids: [],
|
|
575
|
+
},
|
|
576
|
+
})
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('fetches fixable count on demand when Select All is clicked', async () => {
|
|
581
|
+
let fixableCountFetched = false;
|
|
582
|
+
|
|
583
|
+
APIActions.get.mockImplementation(({ key, handleSuccess, params }) => {
|
|
584
|
+
return () => {
|
|
585
|
+
if (key.includes('GET_LEAPP_REPORT_LIST')) {
|
|
586
|
+
handleSuccess({ results: [{ id: mockReportId }] });
|
|
587
|
+
}
|
|
588
|
+
if (key.includes('GET_LEAPP_REPORT_ENTRIES')) {
|
|
589
|
+
handleSuccess({
|
|
590
|
+
id: mockReportId,
|
|
591
|
+
results: mockEntries,
|
|
592
|
+
total: mockEntries.length,
|
|
593
|
+
subtotal: mockEntries.length,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
if (key.includes('GET_FIXABLE_COUNT')) {
|
|
597
|
+
fixableCountFetched = true;
|
|
598
|
+
expect(params.search).toBe('fix_type = command');
|
|
599
|
+
expect(params.per_page).toBe(0);
|
|
600
|
+
handleSuccess({ total: 1, subtotal: 1 });
|
|
601
|
+
}
|
|
602
|
+
return { type: 'MOCK_API_SUCCESS' };
|
|
603
|
+
};
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
renderComponent();
|
|
607
|
+
expandSection();
|
|
608
|
+
await waitForTable();
|
|
609
|
+
|
|
610
|
+
fireEvent.click(screen.getByLabelText('Select all'));
|
|
611
|
+
|
|
612
|
+
await waitFor(() => {
|
|
613
|
+
expect(fixableCountFetched).toBe(true);
|
|
614
|
+
});
|
|
615
|
+
});
|
|
409
616
|
});
|