foreman_leapp 3.4.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.
Files changed (23) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/preupgrade_report_entries_controller.rb +120 -0
  3. data/app/controllers/api/v2/preupgrade_reports_controller.rb +2 -2
  4. data/app/models/preupgrade_report_entry.rb +73 -1
  5. data/app/views/api/v2/preupgrade_report_entries/index.json.rabl +4 -0
  6. data/app/views/api/v2/preupgrade_reports/show.json.rabl +2 -5
  7. data/config/routes.rb +17 -3
  8. data/lib/foreman_leapp/engine.rb +1 -1
  9. data/lib/foreman_leapp/version.rb +1 -1
  10. data/test/functional/api/v2/preupgrade_report_entries_controller_test.rb +258 -0
  11. data/test/functional/api/v2/preupgrade_reports_controller_test.rb +7 -5
  12. data/test/models/preupgrade_report_entry_test.rb +47 -0
  13. data/webpack/components/PreupgradeReports/PreupgradeReports.js +11 -4
  14. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.fixtures.js +43 -0
  15. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.test.js +151 -46
  16. data/webpack/components/PreupgradeReports/components/NoReports.test.js +26 -12
  17. data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTable.scss +9 -1
  18. data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTableHelpers.js +164 -0
  19. data/webpack/components/PreupgradeReportsTable/__tests__/PreupgradeReportsTable.test.js +245 -38
  20. data/webpack/components/PreupgradeReportsTable/index.js +337 -238
  21. metadata +7 -4
  22. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReports.test.js.snap +0 -121
  23. data/webpack/components/PreupgradeReports/components/__snapshots__/NoReports.test.js.snap +0 -25
@@ -1,11 +1,11 @@
1
1
  /* eslint-disable max-lines */
2
2
  import PropTypes from 'prop-types';
3
3
  import React, {
4
- useCallback,
5
- useEffect,
4
+ useState,
6
5
  useMemo,
6
+ useCallback,
7
7
  useRef,
8
- useState,
8
+ useEffect,
9
9
  } from 'react';
10
10
  import { useDispatch } from 'react-redux';
11
11
  import {
@@ -25,17 +25,14 @@ import SelectAllCheckbox from 'foremanReact/components/PF4/TableIndexPage/Table/
25
25
  import { useBulkSelect } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
26
26
  import { RowSelectTd } from 'foremanReact/components/PF4/TableIndexPage/RowSelectTd';
27
27
  import { getColumnHelpers } from 'foremanReact/components/PF4/TableIndexPage/Table/helpers';
28
+ import { getControllerSearchProps, STATUS } from 'foremanReact/constants';
29
+ import SearchBar from 'foremanReact/components/SearchBar';
28
30
  import { APIActions } from 'foremanReact/redux/API';
29
- import { STATUS } from 'foremanReact/constants';
30
- import {
31
- entriesPage,
32
- entryFixable,
33
- } from '../PreupgradeReports/PreupgradeReportsHelpers';
31
+ import { usePreupgradeTableState } from './PreupgradeReportsTableHelpers';
32
+ import { entryFixable } from '../PreupgradeReports/PreupgradeReportsHelpers';
34
33
  import ReportDetails, { renderSeverityLabel } from './ReportDetails';
35
34
  import './PreupgradeReportsTable.scss';
36
35
 
37
- const LEAPP_TEMPLATE_NAME = 'Run preupgrade via Leapp';
38
-
39
36
  const isRowFixable = entryFixable;
40
37
 
41
38
  const submitJobInvocation = (
@@ -43,8 +40,12 @@ const submitJobInvocation = (
43
40
  setError,
44
41
  feature,
45
42
  hostIds,
46
- remediationIds
43
+ remediationIds,
44
+ setIsSubmitting
47
45
  ) => {
46
+ setError(null);
47
+ setIsSubmitting(true);
48
+
48
49
  const payload = {
49
50
  job_invocation: {
50
51
  feature,
@@ -64,168 +65,131 @@ const submitJobInvocation = (
64
65
  const result = response.data || response;
65
66
  if (result?.id) {
66
67
  window.location.assign(foremanUrl(`/job_invocations/${result.id}`));
68
+ } else {
69
+ setIsSubmitting(false);
67
70
  }
68
71
  },
69
- handleError: err => setError(err),
72
+ handleError: err => {
73
+ setError(err);
74
+ setIsSubmitting(false);
75
+ },
70
76
  })
71
77
  );
72
78
  };
73
79
 
74
80
  const PreupgradeReportsTable = ({ data = {} }) => {
75
- const [error, setError] = useState(null);
76
81
  const [isReportExpanded, setIsReportExpanded] = useState(false);
77
- const [pagination, setPagination] = useState({ page: 1, perPage: 5 });
78
- const [reportData, setReportData] = useState(null);
79
- const [status, setStatus] = useState(STATUS.RESOLVED);
80
- const [expandedRowIds, setExpandedRowIds] = useState(new Set());
81
-
82
+ const [submitError, setSubmitError] = useState(null);
83
+ const [isSubmitting, setIsSubmitting] = useState(false);
84
+ const [fixableCount, setFixableCount] = useState(null);
82
85
  const dispatch = useDispatch();
83
- // eslint-disable-next-line camelcase
84
- const isLeappJob = data?.template_name?.includes(LEAPP_TEMPLATE_NAME);
86
+ const hasLoadedDataRef = useRef(false);
85
87
 
86
- // eslint-disable-next-line camelcase
87
- const jobStatusLabel = data?.status_label;
88
+ const {
89
+ isLeappJob,
90
+ status,
91
+ error,
92
+ reportId,
93
+ rows,
94
+ totalCount,
95
+ pagination,
96
+ sortBy,
97
+ searchValue,
98
+ setSearchValue,
99
+ setPagination,
100
+ setSortBy,
101
+ expandedRowIds,
102
+ toggleRowExpansion,
103
+ areAllRowsExpanded,
104
+ onExpandAll,
105
+ } = usePreupgradeTableState(data, isReportExpanded);
106
+
107
+ useEffect(() => {
108
+ if (totalCount > 0 && !hasLoadedDataRef.current) {
109
+ hasLoadedDataRef.current = true;
110
+ }
111
+ }, [totalCount]);
112
+ const hasData = totalCount > 0 || hasLoadedDataRef.current;
88
113
 
89
- const lastFetchedKeyRef = useRef(null);
114
+ useEffect(() => {
115
+ setFixableCount(null);
116
+ }, [searchValue, reportId]);
117
+
118
+ const searchProps = useMemo(() => {
119
+ if (!reportId) return null;
120
+ const baseProps = getControllerSearchProps('preupgrade_report_entries');
121
+ return {
122
+ ...baseProps,
123
+ autocomplete: {
124
+ ...baseProps.autocomplete,
125
+ url: foremanUrl(
126
+ `/api/v2/preupgrade_reports/${reportId}/preupgrade_report_entries/auto_complete_search`
127
+ ),
128
+ },
129
+ };
130
+ }, [reportId]);
90
131
 
91
132
  const columns = useMemo(
92
133
  () => ({
93
- title: { title: __('Title') },
94
- host: {
134
+ title: {
135
+ title: __('Title'),
136
+ isSorted: true,
137
+ width: 30,
138
+ },
139
+ hostname: {
95
140
  title: __('Host'),
96
- wrapper: entry => entry.hostname || reportData?.hostname || '-',
141
+ wrapper: e => e.hostname || '-',
142
+ isSorted: true,
143
+ width: 20,
97
144
  },
98
145
  risk_factor: {
99
146
  title: __('Risk Factor'),
100
147
  wrapper: ({ severity }) => renderSeverityLabel(severity),
148
+ isSorted: true,
149
+ width: 15,
101
150
  },
102
151
  has_remediation: {
103
152
  title: __('Has Remediation?'),
104
- wrapper: entry => (entry.detail?.remediations ? __('Yes') : __('No')),
153
+ wrapper: e => (e.detail?.remediations ? __('Yes') : __('No')),
154
+ isSorted: true,
155
+ width: 20,
105
156
  },
106
157
  inhibitor: {
107
158
  title: __('Inhibitor?'),
108
- wrapper: entry =>
109
- entry.flags?.some(flag => flag === 'inhibitor') ? (
159
+ wrapper: e =>
160
+ e.flags?.includes('inhibitor') ? (
110
161
  <Tooltip content={__('This issue inhibits the upgrade.')}>
111
162
  <span>{__('Yes')}</span>
112
163
  </Tooltip>
113
164
  ) : (
114
165
  __('No')
115
166
  ),
167
+ isSorted: true,
168
+ width: 15,
116
169
  },
117
170
  }),
118
- // eslint-disable-next-line react-hooks/exhaustive-deps
119
- [reportData?.hostname]
171
+ []
120
172
  );
121
173
 
122
- useEffect(() => {
123
- let isMounted = true;
124
- const fetchKey = `${data.id}:${jobStatusLabel}`;
125
-
126
- if (
127
- !isLeappJob ||
128
- !isReportExpanded ||
129
- lastFetchedKeyRef.current === fetchKey
130
- )
131
- return undefined;
132
-
133
- const fail = err => {
134
- if (!isMounted) return;
135
- setError(err);
136
- setStatus(STATUS.ERROR);
137
- };
138
-
139
- const succeed = response => {
140
- if (!isMounted) return;
141
- lastFetchedKeyRef.current = fetchKey;
142
- setReportData(response?.data || response || null);
143
- setStatus(STATUS.RESOLVED);
144
- };
145
-
146
- setStatus(STATUS.PENDING);
147
- dispatch(
148
- APIActions.get({
149
- key: `GET_LEAPP_REPORT_LIST_${data.id}`,
150
- url: `/api/job_invocations/${data.id}/preupgrade_reports`,
151
- handleSuccess: listResponse => {
152
- if (!isMounted) return;
153
- const summary = (listResponse.data || listResponse).results?.[0];
154
- if (summary?.id) {
155
- dispatch(
156
- APIActions.get({
157
- key: `GET_LEAPP_REPORT_DETAIL_${summary.id}`,
158
- url: `/api/preupgrade_reports/${summary.id}`,
159
- handleSuccess: detailResponse => succeed(detailResponse),
160
- handleError: err => fail(err),
161
- })
162
- );
163
- return;
164
- }
165
- succeed(null);
166
- },
167
- handleError: err => fail(err),
168
- })
169
- );
170
-
171
- return () => {
172
- isMounted = false;
173
- };
174
- }, [isReportExpanded, data.id, isLeappJob, dispatch, jobStatusLabel]);
175
-
176
- // eslint-disable-next-line camelcase
177
- const entries = useMemo(() => reportData?.preupgrade_report_entries || [], [
178
- reportData,
179
- ]);
180
-
181
- const pagedEntries = useMemo(
182
- () => entriesPage(entries, pagination),
183
- // eslint-disable-next-line react-hooks/exhaustive-deps
184
- [entries, pagination.page, pagination.perPage]
185
- );
186
-
187
- const getHostId = useCallback(
188
- entry =>
189
- entry.host_id ||
190
- entry.hostId ||
191
- // eslint-disable-next-line camelcase
192
- reportData?.host_id ||
193
- reportData?.host?.id ||
194
- // eslint-disable-next-line camelcase
195
- data?.targeting?.host_id,
196
- [reportData, data]
197
- );
174
+ const pagedFixableEntries = useMemo(() => rows.filter(isRowFixable), [rows]);
198
175
 
199
- const handleParamsChange = useCallback(newParams => {
200
- setPagination(prev => ({
201
- ...prev,
202
- page: newParams.page || prev.page,
203
- perPage: newParams.per_page || prev.perPage,
204
- }));
205
- setExpandedRowIds(new Set());
206
- }, []);
207
-
208
- const toggleRowExpansion = useCallback((id, isExpanding) => {
209
- setExpandedRowIds(prev => {
210
- const next = new Set(prev);
211
- if (isExpanding) next.add(id);
212
- else next.delete(id);
213
- return next;
214
- });
215
- }, []);
216
-
217
- const { inclusionSet, exclusionSet, ...selectAllOptions } = useBulkSelect({
218
- results: pagedEntries,
176
+ const {
177
+ inclusionSet,
178
+ exclusionSet,
179
+ selectedCount,
180
+ ...selectAllOptions
181
+ } = useBulkSelect({
182
+ results: pagedFixableEntries,
219
183
  metadata: {
220
- total: entries.length,
184
+ total: totalCount,
221
185
  page: pagination.page,
222
- selectable: entries.length,
186
+ selectable: fixableCount,
223
187
  },
224
- initialSearchQuery: '',
188
+ initialSearchQuery: searchValue,
225
189
  });
226
190
 
227
191
  const {
228
- selectAll,
192
+ selectAll: originalSelectAll,
229
193
  selectPage,
230
194
  selectNone,
231
195
  selectOne,
@@ -233,141 +197,266 @@ const PreupgradeReportsTable = ({ data = {} }) => {
233
197
  isSelected,
234
198
  } = selectAllOptions;
235
199
 
236
- const rawSelectedIds =
237
- areAllRowsSelected() || exclusionSet.size > 0
238
- ? entries.map(e => e.id).filter(id => !exclusionSet.has(id))
239
- : Array.from(inclusionSet);
240
-
241
- const validFixableIds = useMemo(
242
- () => entries.filter(isRowFixable).map(e => e.id),
243
- [entries]
244
- );
245
-
246
- // eslint-disable-next-line react-hooks/exhaustive-deps
247
- const selectedIds = useMemo(
248
- () => rawSelectedIds.filter(id => validFixableIds.includes(id)),
249
- // eslint-disable-next-line react-hooks/exhaustive-deps
250
- [rawSelectedIds.join(','), validFixableIds]
200
+ const selectAll = useCallback(
201
+ (...args) => {
202
+ // Get count of all fixable entries before selecting
203
+ if (fixableCount === null) {
204
+ dispatch(
205
+ APIActions.get({
206
+ key: `GET_FIXABLE_COUNT_${reportId}`,
207
+ url: `/api/v2/preupgrade_reports/${reportId}/preupgrade_report_entries`,
208
+ params: {
209
+ search: [searchValue, 'fix_type = command']
210
+ .filter(Boolean)
211
+ .join(' AND '),
212
+ per_page: 0,
213
+ },
214
+ handleSuccess: res => {
215
+ const payload = res.data || res;
216
+ const count = payload.subtotal ?? 0;
217
+ setFixableCount(count);
218
+ originalSelectAll(...args);
219
+ },
220
+ handleError: () => {
221
+ setFixableCount(0);
222
+ },
223
+ })
224
+ );
225
+ } else {
226
+ originalSelectAll(...args);
227
+ }
228
+ },
229
+ [dispatch, reportId, searchValue, fixableCount, originalSelectAll]
251
230
  );
252
231
 
253
- const pagedFixableEntries = useMemo(() => pagedEntries.filter(isRowFixable), [
254
- pagedEntries,
255
- ]);
232
+ const selectedIds = Array.from(inclusionSet);
256
233
 
257
234
  const areAllPageFixableSelected =
258
235
  pagedFixableEntries.length > 0 &&
259
- pagedFixableEntries.every(e => selectedIds.includes(e.id));
260
-
261
- const areAllFixableSelected =
262
- validFixableIds.length > 0 &&
263
- validFixableIds.every(id => selectedIds.includes(id));
264
-
265
- const areAllRowsExpanded =
266
- pagedEntries.length > 0 &&
267
- pagedEntries.every(entry => expandedRowIds.has(entry.id));
236
+ pagedFixableEntries.every(e => inclusionSet.has(e.id));
268
237
 
269
- const onExpandAll = useCallback(() => {
270
- setExpandedRowIds(
271
- areAllRowsExpanded ? new Set() : new Set(pagedEntries.map(e => e.id))
272
- );
273
- }, [areAllRowsExpanded, pagedEntries]);
238
+ // eslint-disable-next-line camelcase
239
+ const globalHostId = data?.targeting?.host_id;
274
240
 
275
- const [columnKeys, keysToColumnNames] = useMemo(
276
- () => getColumnHelpers(columns),
277
- [columns]
241
+ const getHostId = useCallback(
242
+ // eslint-disable-next-line camelcase
243
+ entry => entry.host_id || entry.hostId || globalHostId,
244
+ [globalHostId]
278
245
  );
279
246
 
280
247
  const hostIdsForSelected = useMemo(
281
248
  () =>
282
249
  Array.from(
283
250
  new Set(
284
- entries
251
+ rows
285
252
  .filter(e => selectedIds.includes(e.id))
286
253
  .map(getHostId)
287
254
  .filter(Boolean)
288
255
  )
289
256
  ),
290
- [entries, selectedIds, getHostId]
257
+ [rows, selectedIds, getHostId]
291
258
  );
292
259
 
293
260
  const allHostIds = useMemo(
294
- () => Array.from(new Set(entries.map(getHostId).filter(Boolean))),
295
- [entries, getHostId]
261
+ () => Array.from(new Set(rows.map(getHostId).filter(Boolean))),
262
+ [rows, getHostId]
263
+ );
264
+
265
+ const handleParamsChange = useCallback(
266
+ newParams => {
267
+ let sortChanged = false;
268
+ if (newParams.order !== undefined) {
269
+ const parts = newParams.order.split(' ');
270
+ const newIndex = parts[0] || '';
271
+ const newDirection = (parts[1] || 'ASC').toLowerCase();
272
+ sortChanged =
273
+ newIndex !== sortBy.index || newDirection !== sortBy.direction;
274
+ if (sortChanged)
275
+ setSortBy({ index: newIndex, direction: newDirection });
276
+ }
277
+
278
+ let newPage = pagination.page;
279
+ if (sortChanged) {
280
+ newPage = 1;
281
+ } else if (newParams.page !== undefined) {
282
+ newPage = Number(newParams.page);
283
+ }
284
+
285
+ // eslint-disable-next-line camelcase
286
+ const newPerPage =
287
+ newParams.per_page !== undefined
288
+ ? Number(newParams.per_page)
289
+ : pagination.perPage;
290
+
291
+ setPagination({ page: newPage, perPage: newPerPage });
292
+ },
293
+ [sortBy, pagination, setSortBy, setPagination]
294
+ );
295
+
296
+ const commitSearch = val => {
297
+ setSubmitError(null);
298
+ setSearchValue(val);
299
+ setPagination(prev => ({ ...prev, page: 1 }));
300
+ };
301
+
302
+ const handleFixSelected = () => {
303
+ if (areAllRowsSelected() || exclusionSet.size > 0) {
304
+ setSubmitError(null);
305
+ setIsSubmitting(true);
306
+
307
+ dispatch(
308
+ APIActions.post({
309
+ key: `BULK_REMEDIATE_${reportId}`,
310
+ url: foremanUrl(
311
+ `/api/v2/preupgrade_reports/${reportId}/preupgrade_report_entries/bulk_remediate`
312
+ ),
313
+ params: {
314
+ search: searchValue,
315
+ excluded_ids: Array.from(exclusionSet),
316
+ },
317
+ handleSuccess: response => {
318
+ const result = response.data || response;
319
+ if (result?.id) {
320
+ window.location.assign(
321
+ foremanUrl(`/job_invocations/${result.id}`)
322
+ );
323
+ } else {
324
+ setIsSubmitting(false);
325
+ }
326
+ },
327
+ handleError: err => {
328
+ setSubmitError(err);
329
+ setIsSubmitting(false);
330
+ },
331
+ })
332
+ );
333
+ } else {
334
+ if (selectedIds.length === 0) return;
335
+
336
+ const hostIds =
337
+ hostIdsForSelected.length > 0
338
+ ? hostIdsForSelected
339
+ : [globalHostId].filter(Boolean);
340
+
341
+ submitJobInvocation(
342
+ dispatch,
343
+ setSubmitError,
344
+ 'leapp_remediation_plan',
345
+ hostIds,
346
+ selectedIds.join(','),
347
+ setIsSubmitting
348
+ );
349
+ }
350
+ };
351
+
352
+ const handleRunUpgrade = () => {
353
+ const targetHostIds = globalHostId ? [globalHostId] : allHostIds;
354
+ submitJobInvocation(
355
+ dispatch,
356
+ setSubmitError,
357
+ 'leapp_upgrade',
358
+ targetHostIds,
359
+ null,
360
+ setIsSubmitting
361
+ );
362
+ };
363
+
364
+ const [columnKeys, keysToColumnNames] = useMemo(
365
+ () => getColumnHelpers(columns),
366
+ [columns]
296
367
  );
297
368
 
298
369
  if (!isLeappJob) return null;
299
370
 
371
+ const hasAnySelection =
372
+ areAllRowsSelected() || exclusionSet.size > 0 || selectedIds.length > 0;
373
+
300
374
  const isFixSelectedDisabled =
301
- validFixableIds.length === 0 ||
302
- selectedIds.length === 0 ||
303
- hostIdsForSelected.length === 0;
375
+ status === STATUS.PENDING ||
376
+ isSubmitting ||
377
+ !hasAnySelection ||
378
+ // When using select-all, check if any fixable entries exist on current page
379
+ (areAllRowsSelected() && pagedFixableEntries.length === 0);
380
+
381
+ const isRunUpgradeDisabled =
382
+ status === STATUS.PENDING ||
383
+ isSubmitting ||
384
+ (!globalHostId && allHostIds.length === 0);
385
+
386
+ const combinedErrorMessage =
387
+ (status === STATUS.ERROR && error?.message ? error.message : null) ||
388
+ submitError?.message ||
389
+ submitError?.error;
304
390
 
305
391
  return (
306
392
  <ExpandableSection
393
+ className="leapp-report-section"
307
394
  isExpanded={isReportExpanded}
308
- onToggle={(_event, val) => setIsReportExpanded(val)}
395
+ onToggle={(_e, val) => setIsReportExpanded(val)}
309
396
  toggleText={__('Leapp preupgrade report')}
310
397
  >
311
- {entries.length > 0 && status === STATUS.RESOLVED && (
312
- <Toolbar ouiaId="leapp-report-toolbar">
313
- <ToolbarContent>
314
- <ToolbarGroup variant="filter-group">
315
- <ToolbarItem>
398
+ <Toolbar ouiaId="leapp-report-toolbar">
399
+ <ToolbarContent>
400
+ <ToolbarGroup
401
+ variant="filter-group"
402
+ className="leapp-toolbar-filter-group"
403
+ >
404
+ {hasData && (
405
+ <ToolbarItem spacer={{ default: 'spacerNone' }}>
316
406
  <SelectAllCheckbox
317
407
  selectAll={selectAll}
318
408
  selectPage={selectPage}
319
409
  selectNone={selectNone}
320
- selectedCount={selectedIds.length}
410
+ selectedCount={selectedCount}
321
411
  pageRowCount={pagedFixableEntries.length}
322
- totalCount={validFixableIds.length}
412
+ totalCount={fixableCount}
323
413
  areAllRowsOnPageSelected={areAllPageFixableSelected}
324
- areAllRowsSelected={areAllFixableSelected}
414
+ areAllRowsSelected={areAllRowsSelected()}
325
415
  />
326
416
  </ToolbarItem>
327
- </ToolbarGroup>
328
- <ToolbarGroup
329
- align={{ default: 'alignRight' }}
330
- variant="button-group"
331
- >
332
- <ToolbarItem>
333
- <Button
334
- variant="secondary"
335
- isDisabled={isFixSelectedDisabled}
336
- onClick={() =>
337
- submitJobInvocation(
338
- dispatch,
339
- setError,
340
- 'leapp_remediation_plan',
341
- hostIdsForSelected,
342
- selectedIds.join(',')
343
- )
344
- }
345
- ouiaId="fix-selected-button"
346
- >
347
- {__('Fix Selected')}
348
- </Button>
349
- </ToolbarItem>
350
- <ToolbarItem>
351
- <Button
352
- variant="primary"
353
- isDisabled={allHostIds.length === 0}
354
- onClick={() =>
355
- submitJobInvocation(
356
- dispatch,
357
- setError,
358
- 'leapp_upgrade',
359
- allHostIds
360
- )
361
- }
362
- ouiaId="run-upgrade-button"
363
- >
364
- {__('Run Upgrade')}
365
- </Button>
417
+ )}
418
+
419
+ {reportId && searchProps && (
420
+ <ToolbarItem className="leapp-searchbar-item">
421
+ <SearchBar
422
+ data={searchProps}
423
+ searchQuery={searchValue}
424
+ onSearch={commitSearch}
425
+ bookmarks={searchProps.bookmarks}
426
+ />
366
427
  </ToolbarItem>
367
- </ToolbarGroup>
368
- </ToolbarContent>
369
- </Toolbar>
370
- )}
428
+ )}
429
+ </ToolbarGroup>
430
+
431
+ <ToolbarGroup
432
+ align={{ default: 'alignRight' }}
433
+ variant="button-group"
434
+ >
435
+ <ToolbarItem>
436
+ <Button
437
+ variant="secondary"
438
+ isDisabled={isFixSelectedDisabled}
439
+ onClick={handleFixSelected}
440
+ ouiaId="fix-selected-button"
441
+ isLoading={isSubmitting}
442
+ >
443
+ {__('Fix Selected')}
444
+ </Button>
445
+ </ToolbarItem>
446
+ <ToolbarItem>
447
+ <Button
448
+ variant="primary"
449
+ isDisabled={isRunUpgradeDisabled}
450
+ onClick={handleRunUpgrade}
451
+ ouiaId="run-upgrade-button"
452
+ isLoading={isSubmitting}
453
+ >
454
+ {__('Run Upgrade')}
455
+ </Button>
456
+ </ToolbarItem>
457
+ </ToolbarGroup>
458
+ </ToolbarContent>
459
+ </Toolbar>
371
460
 
372
461
  <Table
373
462
  ouiaId="leapp-report-table"
@@ -375,31 +464,38 @@ const PreupgradeReportsTable = ({ data = {} }) => {
375
464
  isEmbedded
376
465
  params={{
377
466
  page: pagination.page,
378
- perPage: pagination.perPage,
379
- order: '',
467
+ per_page: pagination.perPage,
468
+ search: searchValue,
469
+ order: sortBy.index
470
+ ? `${sortBy.index} ${sortBy.direction.toUpperCase()}`
471
+ : '',
380
472
  }}
381
- results={pagedEntries}
382
- itemCount={entries.length}
473
+ results={rows}
474
+ itemCount={totalCount}
383
475
  url=""
384
476
  isPending={status === STATUS.PENDING}
385
- errorMessage={
386
- status === STATUS.ERROR && error?.message ? error.message : null
387
- }
477
+ errorMessage={combinedErrorMessage}
388
478
  showCheckboxes
389
479
  refreshData={() => {}}
390
480
  isDeleteable={false}
391
- emptyMessage={__('The preupgrade report shows no issues.')}
481
+ emptyMessage={
482
+ searchValue
483
+ ? __('No results found for your search.')
484
+ : __('The preupgrade report shows no issues.')
485
+ }
392
486
  setParams={handleParamsChange}
393
487
  childrenOutsideTbody
394
488
  onExpandAll={onExpandAll}
395
489
  areAllRowsExpanded={!areAllRowsExpanded}
396
490
  >
397
- {pagedEntries.map((entry, rowIndex) => {
491
+ {rows.map((entry, rowIndex) => {
492
+ const rowKey = entry.id
493
+ ? `entry-${entry.id}`
494
+ : `entry-row-${rowIndex}`;
398
495
  const isRowExpanded = expandedRowIds.has(entry.id);
399
-
400
496
  return (
401
497
  <Tbody
402
- key={entry.id}
498
+ key={rowKey}
403
499
  isExpanded={isRowExpanded}
404
500
  className={isRowExpanded ? 'leapp-expanded-tbody' : ''}
405
501
  >
@@ -408,7 +504,7 @@ const PreupgradeReportsTable = ({ data = {} }) => {
408
504
  expand={{
409
505
  rowIndex,
410
506
  isExpanded: isRowExpanded,
411
- onToggle: (_event, _rowIndex, isOpen) =>
507
+ onToggle: (_e, _idx, isOpen) =>
412
508
  toggleRowExpansion(entry.id, isOpen),
413
509
  }}
414
510
  />
@@ -419,7 +515,10 @@ const PreupgradeReportsTable = ({ data = {} }) => {
419
515
  isSelectable={isRowFixable}
420
516
  />
421
517
  {columnKeys.map(key => (
422
- <Td key={key} dataLabel={keysToColumnNames[key]}>
518
+ <Td
519
+ key={`${rowKey}-col-${key}`}
520
+ dataLabel={keysToColumnNames[key]}
521
+ >
423
522
  {columns[key].wrapper
424
523
  ? columns[key].wrapper(entry)
425
524
  : entry[key]}