foreman_leapp 4.1.2 → 4.3.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_leapp/locale/de/foreman_leapp.js +37 -4
  3. data/app/assets/javascripts/foreman_leapp/locale/en/foreman_leapp.js +36 -3
  4. data/app/assets/javascripts/foreman_leapp/locale/fr/foreman_leapp.js +37 -4
  5. data/app/assets/javascripts/foreman_leapp/locale/ja/foreman_leapp.js +37 -4
  6. data/app/assets/javascripts/foreman_leapp/locale/ka/foreman_leapp.js +40 -7
  7. data/app/assets/javascripts/foreman_leapp/locale/ko/foreman_leapp.js +37 -4
  8. data/app/assets/javascripts/foreman_leapp/locale/zh_CN/foreman_leapp.js +37 -4
  9. data/app/controllers/api/v2/preupgrade_report_entries_controller.rb +17 -29
  10. data/app/controllers/concerns/foreman_leapp/preupgrade_report_entry_bulk_remediate.rb +69 -0
  11. data/app/lib/actions/foreman_leapp/preupgrade_job.rb +1 -2
  12. data/app/lib/helpers/job_helper.rb +8 -0
  13. data/app/views/api/v2/job_invocations/leapp_preupgrade_report.json.rabl +3 -0
  14. data/config/routes.rb +1 -2
  15. data/lib/foreman_leapp/engine.rb +3 -12
  16. data/lib/foreman_leapp/version.rb +1 -1
  17. data/locale/de/LC_MESSAGES/foreman_leapp.mo +0 -0
  18. data/locale/de/foreman_leapp.po +38 -5
  19. data/locale/en/LC_MESSAGES/foreman_leapp.mo +0 -0
  20. data/locale/en/foreman_leapp.po +36 -3
  21. data/locale/foreman_leapp.pot +66 -18
  22. data/locale/fr/LC_MESSAGES/foreman_leapp.mo +0 -0
  23. data/locale/fr/foreman_leapp.po +38 -5
  24. data/locale/ja/LC_MESSAGES/foreman_leapp.mo +0 -0
  25. data/locale/ja/foreman_leapp.po +38 -5
  26. data/locale/ka/LC_MESSAGES/foreman_leapp.mo +0 -0
  27. data/locale/ka/foreman_leapp.po +41 -8
  28. data/locale/ko/LC_MESSAGES/foreman_leapp.mo +0 -0
  29. data/locale/ko/foreman_leapp.po +38 -5
  30. data/locale/zh_CN/LC_MESSAGES/foreman_leapp.mo +0 -0
  31. data/locale/zh_CN/foreman_leapp.po +38 -5
  32. data/package.json +0 -1
  33. data/test/functional/api/v2/job_invocations_controller_test.rb +39 -0
  34. data/test/functional/api/v2/preupgrade_report_entries_controller_test.rb +64 -0
  35. data/test/unit/helpers/job_helper_test.rb +20 -0
  36. data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTableHelpers.js +44 -17
  37. data/webpack/components/PreupgradeReportsTable/__tests__/PreupgradeReportsTable.test.js +131 -10
  38. data/webpack/components/PreupgradeReportsTable/index.js +67 -74
  39. data/webpack/index.js +0 -13
  40. metadata +7 -67
  41. data/app/controllers/preupgrade_reports_controller.rb +0 -17
  42. data/app/views/job_invocations/_leapp_preupgrade_report.html.erb +0 -11
  43. data/app/views/preupgrade_report_entries/base.json.rabl +0 -4
  44. data/app/views/preupgrade_reports/index.json.rabl +0 -3
  45. data/app/views/preupgrade_reports/show.json.rabl +0 -7
  46. data/test/functional/preupgrade_reports_controller_test.rb +0 -39
  47. data/webpack/components/PreupgradeReports/PreupgradeReports.js +0 -154
  48. data/webpack/components/PreupgradeReports/PreupgradeReportsActions.js +0 -31
  49. data/webpack/components/PreupgradeReports/PreupgradeReportsHelpers.js +0 -152
  50. data/webpack/components/PreupgradeReports/PreupgradeReportsReducer.js +0 -37
  51. data/webpack/components/PreupgradeReports/PreupgradeReportsSelectors.js +0 -18
  52. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.fixtures.js +0 -129
  53. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.test.js +0 -159
  54. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReportsHelpers.test.js +0 -78
  55. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReportsReducer.test.js +0 -44
  56. data/webpack/components/PreupgradeReports/__tests__/PreupgradeReportsSelectors.test.js +0 -28
  57. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReportsHelpers.test.js.snap +0 -489
  58. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReportsReducer.test.js.snap +0 -102
  59. data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReportsSelectors.test.js.snap +0 -76
  60. data/webpack/components/PreupgradeReports/components/EntriesFilter.js +0 -121
  61. data/webpack/components/PreupgradeReports/components/EntriesFilter.scss +0 -3
  62. data/webpack/components/PreupgradeReports/components/EntriesFilter.test.js +0 -30
  63. data/webpack/components/PreupgradeReports/components/FixSelectedButton.js +0 -35
  64. data/webpack/components/PreupgradeReports/components/FixSelectedButton.test.js +0 -15
  65. data/webpack/components/PreupgradeReports/components/NoReports.js +0 -35
  66. data/webpack/components/PreupgradeReports/components/NoReports.test.js +0 -29
  67. data/webpack/components/PreupgradeReports/components/UpgradeAllButton.js +0 -28
  68. data/webpack/components/PreupgradeReports/components/UpgradeAllButton.test.js +0 -17
  69. data/webpack/components/PreupgradeReports/components/__snapshots__/EntriesFilter.test.js.snap +0 -330
  70. data/webpack/components/PreupgradeReports/components/__snapshots__/FixSelectedButton.test.js.snap +0 -40
  71. data/webpack/components/PreupgradeReports/components/__snapshots__/UpgradeAllButton.test.js.snap +0 -41
  72. data/webpack/components/PreupgradeReports/index.js +0 -57
  73. data/webpack/components/PreupgradeReportsList/PreupgradeReportList.scss +0 -37
  74. data/webpack/components/PreupgradeReportsList/__tests__/PreupgradeReportsList.test.js +0 -124
  75. data/webpack/components/PreupgradeReportsList/components/EmptyInfoItem.js +0 -16
  76. data/webpack/components/PreupgradeReportsList/components/InfoItem.js +0 -34
  77. data/webpack/components/PreupgradeReportsList/components/InhibitorInfoItem.js +0 -33
  78. data/webpack/components/PreupgradeReportsList/components/PreupgradeReportEntry.js +0 -64
  79. data/webpack/components/PreupgradeReportsList/components/PreupgradeReportsListHeader.js +0 -56
  80. data/webpack/components/PreupgradeReportsList/components/SortableHeaderItem.js +0 -50
  81. data/webpack/components/PreupgradeReportsList/components/__tests__/EmptyInfoItem.test.js +0 -13
  82. data/webpack/components/PreupgradeReportsList/components/__tests__/InhibitorInfoItem.test.js +0 -27
  83. data/webpack/components/PreupgradeReportsList/components/__tests__/PreupgradeReportEntry.fixtures.js +0 -38
  84. data/webpack/components/PreupgradeReportsList/components/__tests__/PreupgradeReportEntry.test.js +0 -14
  85. data/webpack/components/PreupgradeReportsList/components/__tests__/PreupgradeReportsListHeader.test.js +0 -14
  86. data/webpack/components/PreupgradeReportsList/components/__tests__/SortableHeaderItem.test.js +0 -29
  87. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/EmptyInfoItem.test.js.snap +0 -8
  88. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/InhibitorInfoItem.test.js.snap +0 -32
  89. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/PreupgradeReportEntry.test.js.snap +0 -134
  90. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/PreupgradeReportsListHeader.test.js.snap +0 -113
  91. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/SortableHeaderItem.test.js.snap +0 -36
  92. data/webpack/components/PreupgradeReportsList/components/__tests__/__snapshots__/helpers.test.js.snap +0 -197
  93. data/webpack/components/PreupgradeReportsList/components/__tests__/helpers.test.js +0 -28
  94. data/webpack/components/PreupgradeReportsList/components/foreman_leapp.scss +0 -15
  95. data/webpack/components/PreupgradeReportsList/components/helpers.js +0 -186
  96. data/webpack/components/PreupgradeReportsList/components/images/i_severity-critical.svg +0 -61
  97. data/webpack/components/PreupgradeReportsList/components/images/i_severity-high.svg +0 -61
  98. data/webpack/components/PreupgradeReportsList/components/images/i_severity-low.svg +0 -62
  99. data/webpack/components/PreupgradeReportsList/components/images/i_severity-med.svg +0 -62
  100. data/webpack/components/PreupgradeReportsList/index.js +0 -61
  101. data/webpack/consts.js +0 -3
  102. data/webpack/csrf.js +0 -4
  103. data/webpack/reducer.js +0 -5
@@ -17,6 +17,26 @@ module Helpers
17
17
  FactoryBot.create(:template_invocation, template: job_template, job_invocation: job_invocation)
18
18
  end
19
19
 
20
+ describe 'with_leapp_report' do
21
+ test 'returns true for leapp preupgrade feature on template' do
22
+ RemoteExecutionFeature.find_by(label: feature_label).update(job_template: job_template)
23
+ job_invocation.task.stubs(:input).returns({})
24
+ assert helper.with_leapp_report(job_invocation)
25
+ end
26
+
27
+ test 'returns true for leapp remediation feature on template' do
28
+ remediation_label = 'leapp_remediation_plan'
29
+ RemoteExecutionFeature.find_by(label: remediation_label).update(job_template: job_template)
30
+ job_invocation.task.stubs(:input).returns({})
31
+ assert helper.with_leapp_report(job_invocation)
32
+ end
33
+
34
+ test 'returns false when no leapp features apply' do
35
+ job_invocation.task.stubs(:input).returns({ 'job_features' => ['some_other_feature'] })
36
+ assert_not helper.with_leapp_report(job_invocation)
37
+ end
38
+ end
39
+
20
40
  describe 'correct_feature?' do
21
41
  test 'returns true when feature is listed in job_features' do
22
42
  job_invocation.task.stubs(:input).returns({ 'job_features' => [feature_label] })
@@ -12,7 +12,7 @@ export const usePreupgradeTableState = (data, isExpanded) => {
12
12
 
13
13
  const [status, setStatus] = useState(STATUS.RESOLVED);
14
14
  const [error, setError] = useState(null);
15
- const [reportId, setReportId] = useState(null);
15
+ const [hasReports, setHasReports] = useState(false);
16
16
  const [rows, setRows] = useState([]);
17
17
  const [totalCount, setTotalCount] = useState(0);
18
18
  const [searchValue, setSearchValue] = useState('');
@@ -24,14 +24,15 @@ export const usePreupgradeTableState = (data, isExpanded) => {
24
24
  const [expandedRowIds, setExpandedRowIds] = useState(new Set());
25
25
 
26
26
  // eslint-disable-next-line camelcase
27
- const isLeappJob = data?.template_name?.includes('Run preupgrade via Leapp');
27
+ const isLeappJob = Boolean(data?.has_leapp_report);
28
28
  // eslint-disable-next-line camelcase
29
29
  const jobStatusLabel = data?.status_label;
30
30
 
31
31
  useEffect(() => {
32
32
  const fetchKey = `${data.id}:${jobStatusLabel}`;
33
- if (!isLeappJob || !isExpanded || lastFetchedKeyRef.current === fetchKey)
33
+ if (!isLeappJob || !isExpanded || lastFetchedKeyRef.current === fetchKey) {
34
34
  return undefined;
35
+ }
35
36
 
36
37
  let ignore = false;
37
38
  setStatus(STATUS.PENDING);
@@ -41,22 +42,29 @@ export const usePreupgradeTableState = (data, isExpanded) => {
41
42
  key: `GET_LEAPP_REPORT_LIST_${data.id}`,
42
43
  url: `/api/v2/job_invocations/${data.id}/preupgrade_reports`,
43
44
  handleSuccess: res => {
44
- if (ignore) return;
45
+ if (ignore) {
46
+ return;
47
+ }
48
+
45
49
  lastFetchedKeyRef.current = fetchKey;
46
50
  const payload = res.data || res;
47
51
  const rawResults = payload.results;
48
52
  const resultsArray = rawResults ? [].concat(rawResults) : [];
49
53
 
50
- if (resultsArray[0]?.id) {
51
- setReportId(resultsArray[0].id);
54
+ if (resultsArray.length > 0) {
55
+ setHasReports(true);
52
56
  } else {
57
+ setHasReports(false);
53
58
  setRows([]);
54
59
  setTotalCount(0);
55
60
  setStatus(STATUS.RESOLVED);
56
61
  }
57
62
  },
58
63
  handleError: err => {
59
- if (ignore) return;
64
+ if (ignore) {
65
+ return;
66
+ }
67
+
60
68
  setError(err);
61
69
  setStatus(STATUS.ERROR);
62
70
  },
@@ -69,10 +77,15 @@ export const usePreupgradeTableState = (data, isExpanded) => {
69
77
  }, [isExpanded, data.id, isLeappJob, dispatch, jobStatusLabel]);
70
78
 
71
79
  useEffect(() => {
72
- if (!isLeappJob || !isExpanded || !reportId) return undefined;
80
+ if (!isLeappJob || !isExpanded || !hasReports || !data.id) {
81
+ return undefined;
82
+ }
83
+
84
+ const entriesFetchKey = `${data.id}_P${pagination.page}_PP${pagination.perPage}_S${searchValue}_O${sortBy.index}${sortBy.direction}_J${jobStatusLabel}`;
73
85
 
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;
86
+ if (lastEntriesFetchKeyRef.current === entriesFetchKey) {
87
+ return undefined;
88
+ }
76
89
 
77
90
  let ignore = false;
78
91
  setStatus(STATUS.PENDING);
@@ -84,15 +97,19 @@ export const usePreupgradeTableState = (data, isExpanded) => {
84
97
  dispatch(
85
98
  APIActions.get({
86
99
  key: `GET_LEAPP_REPORT_ENTRIES_${entriesFetchKey}`,
87
- url: `/api/v2/preupgrade_reports/${reportId}/preupgrade_report_entries`,
100
+ url: '/api/v2/preupgrade_report_entries',
88
101
  params: {
89
102
  page: pagination.page,
90
103
  per_page: pagination.perPage,
104
+ job_invocation_id: data.id,
91
105
  ...(searchValue && { search: searchValue }),
92
106
  ...(orderParam && { order: orderParam }),
93
107
  },
94
108
  handleSuccess: res => {
95
- if (ignore) return;
109
+ if (ignore) {
110
+ return;
111
+ }
112
+
96
113
  lastEntriesFetchKeyRef.current = entriesFetchKey;
97
114
  const payload = res.data || res;
98
115
  const fetchedResults = payload.results;
@@ -102,7 +119,10 @@ export const usePreupgradeTableState = (data, isExpanded) => {
102
119
  setExpandedRowIds(new Set());
103
120
  },
104
121
  handleError: err => {
105
- if (ignore) return;
122
+ if (ignore) {
123
+ return;
124
+ }
125
+
106
126
  setError(err);
107
127
  setStatus(STATUS.ERROR);
108
128
  },
@@ -115,7 +135,8 @@ export const usePreupgradeTableState = (data, isExpanded) => {
115
135
  }, [
116
136
  isExpanded,
117
137
  isLeappJob,
118
- reportId,
138
+ hasReports,
139
+ data.id,
119
140
  searchValue,
120
141
  pagination.page,
121
142
  pagination.perPage,
@@ -137,8 +158,13 @@ export const usePreupgradeTableState = (data, isExpanded) => {
137
158
  const toggleRowExpansion = (id, isOpen) => {
138
159
  setExpandedRowIds(prev => {
139
160
  const next = new Set(prev);
140
- if (isOpen) next.add(id);
141
- else next.delete(id);
161
+
162
+ if (isOpen) {
163
+ next.add(id);
164
+ } else {
165
+ next.delete(id);
166
+ }
167
+
142
168
  return next;
143
169
  });
144
170
  };
@@ -147,7 +173,8 @@ export const usePreupgradeTableState = (data, isExpanded) => {
147
173
  isLeappJob,
148
174
  status,
149
175
  error,
150
- reportId,
176
+ hasReports,
177
+ jobInvocationId: data.id,
151
178
  rows,
152
179
  totalCount,
153
180
  pagination,
@@ -49,7 +49,7 @@ const mockJobId = 42;
49
49
  const mockReportId = 999;
50
50
  const mockJobData = {
51
51
  id: mockJobId,
52
- template_name: 'Run preupgrade via Leapp',
52
+ has_leapp_report: true,
53
53
  };
54
54
 
55
55
  const mockEntries = Array.from({ length: 12 }, (_, i) => ({
@@ -122,6 +122,42 @@ describe('PreupgradeReportsTable', () => {
122
122
  const waitForTable = () =>
123
123
  waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
124
124
 
125
+ it('loads entries for the whole job invocation when multiple preupgrade reports exist', async () => {
126
+ const host2Entry = {
127
+ id: 501,
128
+ title: 'Host 2 Issue',
129
+ hostname: 'host2.example.com',
130
+ host_id: 202,
131
+ severity: 'medium',
132
+ detail: {},
133
+ };
134
+
135
+ APIActions.get.mockImplementation(({ key, handleSuccess, params }) => {
136
+ return () => {
137
+ if (key.includes('GET_LEAPP_REPORT_LIST')) {
138
+ handleSuccess({ results: [{ id: 1 }, { id: 2 }] });
139
+ }
140
+ if (key.includes('GET_LEAPP_REPORT_ENTRIES')) {
141
+ expect(params.job_invocation_id).toBe(mockJobId);
142
+ expect(params.search).toBeUndefined();
143
+ handleSuccess({
144
+ results: [host2Entry, ...mockEntries],
145
+ subtotal: mockEntries.length + 1,
146
+ });
147
+ }
148
+ return { type: 'MOCK_API_SUCCESS' };
149
+ };
150
+ });
151
+
152
+ renderComponent();
153
+ expandSection();
154
+ await waitFor(() =>
155
+ expect(
156
+ screen.getByText('Host 2 Issue', { selector: 'td' })
157
+ ).toBeInTheDocument()
158
+ );
159
+ });
160
+
125
161
  it('renders data', async () => {
126
162
  renderComponent();
127
163
  expandSection();
@@ -132,12 +168,24 @@ describe('PreupgradeReportsTable', () => {
132
168
  });
133
169
 
134
170
  it('does not render anything for non-Leapp jobs', () => {
135
- renderComponent({ id: 55, template_name: 'Standard RHEL Update' });
171
+ renderComponent({ id: 55, has_leapp_report: false });
136
172
  expect(
137
173
  screen.queryByText('Leapp preupgrade report')
138
174
  ).not.toBeInTheDocument();
139
175
  });
140
176
 
177
+ it('renders when has_leapp_report is true (e.g. remediation jobs)', async () => {
178
+ renderComponent({
179
+ id: mockJobId,
180
+ has_leapp_report: true,
181
+ });
182
+ expandSection();
183
+ await waitForTable();
184
+ expect(
185
+ screen.getByText('Report Entry 1', { selector: 'td' })
186
+ ).toBeInTheDocument();
187
+ });
188
+
141
189
  it('refetches when status_label transitions (e.g. Running → Succeeded)', async () => {
142
190
  const store = configureStore({
143
191
  reducer: mockReducer,
@@ -390,7 +438,7 @@ describe('PreupgradeReportsTable', () => {
390
438
  );
391
439
  });
392
440
 
393
- it('Fix Selected dispatches APIActions.post with correct feature, host_ids and remediation_ids', async () => {
441
+ it('Fix Selected dispatches bulk_remediate with fixable entry ids', async () => {
394
442
  renderComponent();
395
443
  expandSection();
396
444
  await waitForTable();
@@ -407,13 +455,10 @@ describe('PreupgradeReportsTable', () => {
407
455
 
408
456
  expect(APIActions.post).toHaveBeenCalledWith(
409
457
  expect.objectContaining({
410
- url: expect.stringContaining('/api/job_invocations'),
458
+ url: expect.stringContaining('/bulk_remediate'),
411
459
  params: {
412
- job_invocation: {
413
- feature: 'leapp_remediation_plan',
414
- host_ids: [100],
415
- inputs: { remediation_ids: '1' },
416
- },
460
+ job_invocation_id: mockJobId,
461
+ ids: [1],
417
462
  },
418
463
  })
419
464
  );
@@ -570,13 +615,89 @@ describe('PreupgradeReportsTable', () => {
570
615
  expect.objectContaining({
571
616
  url: expect.stringContaining('/bulk_remediate'),
572
617
  params: {
573
- search: '',
618
+ job_invocation_id: mockJobId,
574
619
  excluded_ids: [],
575
620
  },
576
621
  })
577
622
  );
578
623
  });
579
624
 
625
+ it('keeps Fix Selected enabled on page 1 when select-all is active but fixable rows are only on later pages', async () => {
626
+ const entriesFixableOnLaterPages = mockEntries.map((entry, index) => {
627
+ if (index < 5) {
628
+ return {
629
+ ...entry,
630
+ detail: {
631
+ ...entry.detail,
632
+ remediations:
633
+ index === 1
634
+ ? [{ type: 'hint', context: 'Manual fix only' }]
635
+ : null,
636
+ },
637
+ };
638
+ }
639
+
640
+ if (index === 5) {
641
+ return {
642
+ ...entry,
643
+ detail: {
644
+ remediations: [{ type: 'command', context: ['echo', 'fix'] }],
645
+ },
646
+ };
647
+ }
648
+
649
+ return {
650
+ ...entry,
651
+ detail: { ...entry.detail, remediations: null },
652
+ };
653
+ });
654
+
655
+ const fixableEntryCount = entriesFixableOnLaterPages.filter(entry =>
656
+ entry.detail?.remediations?.some(r => r.type === 'command')
657
+ ).length;
658
+
659
+ APIActions.get.mockImplementation(({ key, handleSuccess, params }) => {
660
+ return () => {
661
+ if (key.includes('GET_LEAPP_REPORT_LIST')) {
662
+ handleSuccess({ results: [{ id: mockReportId }] });
663
+ }
664
+ if (key.includes('GET_LEAPP_REPORT_ENTRIES')) {
665
+ const page = Number(params?.page ?? 1);
666
+ const perPage = Number(params?.per_page ?? 5);
667
+ const start = (page - 1) * perPage;
668
+ const results = entriesFixableOnLaterPages.slice(start, start + perPage);
669
+ handleSuccess({
670
+ id: mockReportId,
671
+ results,
672
+ total: entriesFixableOnLaterPages.length,
673
+ subtotal: entriesFixableOnLaterPages.length,
674
+ });
675
+ }
676
+ if (key.includes('GET_FIXABLE_COUNT')) {
677
+ handleSuccess({
678
+ total: fixableEntryCount,
679
+ subtotal: fixableEntryCount,
680
+ });
681
+ }
682
+ return { type: 'MOCK_API_SUCCESS' };
683
+ };
684
+ });
685
+
686
+ renderComponent();
687
+ expandSection();
688
+ await waitFor(() =>
689
+ screen.getByText('Report Entry 1', { selector: 'td' })
690
+ );
691
+
692
+ fireEvent.click(screen.getByLabelText('Select all'));
693
+
694
+ await waitFor(() =>
695
+ expect(
696
+ screen.getByRole('button', { name: 'Fix Selected' })
697
+ ).not.toBeDisabled()
698
+ );
699
+ });
700
+
580
701
  it('fetches fixable count on demand when Select All is clicked', async () => {
581
702
  let fixableCountFetched = false;
582
703
 
@@ -29,10 +29,16 @@ import { getControllerSearchProps, STATUS } from 'foremanReact/constants';
29
29
  import SearchBar from 'foremanReact/components/SearchBar';
30
30
  import { APIActions } from 'foremanReact/redux/API';
31
31
  import { usePreupgradeTableState } from './PreupgradeReportsTableHelpers';
32
- import { entryFixable } from '../PreupgradeReports/PreupgradeReportsHelpers';
33
32
  import ReportDetails, { renderSeverityLabel } from './ReportDetails';
34
33
  import './PreupgradeReportsTable.scss';
35
34
 
35
+ const entryWithFixKind = kind => entry =>
36
+ entry.detail &&
37
+ entry.detail.remediations &&
38
+ entry.detail.remediations.some(remediation => remediation.type === kind);
39
+
40
+ const entryFixable = entryWithFixKind('command');
41
+
36
42
  const isRowFixable = entryFixable;
37
43
 
38
44
  const submitJobInvocation = (
@@ -89,7 +95,8 @@ const PreupgradeReportsTable = ({ data = {} }) => {
89
95
  isLeappJob,
90
96
  status,
91
97
  error,
92
- reportId,
98
+ hasReports,
99
+ jobInvocationId,
93
100
  rows,
94
101
  totalCount,
95
102
  pagination,
@@ -113,21 +120,25 @@ const PreupgradeReportsTable = ({ data = {} }) => {
113
120
 
114
121
  useEffect(() => {
115
122
  setFixableCount(null);
116
- }, [searchValue, reportId]);
123
+ }, [searchValue, jobInvocationId]);
117
124
 
118
125
  const searchProps = useMemo(() => {
119
- if (!reportId) return null;
126
+ if (!hasReports || !jobInvocationId) {
127
+ return null;
128
+ }
129
+
120
130
  const baseProps = getControllerSearchProps('preupgrade_report_entries');
131
+
121
132
  return {
122
133
  ...baseProps,
123
134
  autocomplete: {
124
135
  ...baseProps.autocomplete,
125
136
  url: foremanUrl(
126
- `/api/v2/preupgrade_reports/${reportId}/preupgrade_report_entries/auto_complete_search`
137
+ `/api/v2/preupgrade_report_entries/auto_complete_search?job_invocation_id=${jobInvocationId}`
127
138
  ),
128
139
  },
129
140
  };
130
- }, [reportId]);
141
+ }, [hasReports, jobInvocationId]);
131
142
 
132
143
  const columns = useMemo(
133
144
  () => ({
@@ -203,9 +214,10 @@ const PreupgradeReportsTable = ({ data = {} }) => {
203
214
  if (fixableCount === null) {
204
215
  dispatch(
205
216
  APIActions.get({
206
- key: `GET_FIXABLE_COUNT_${reportId}`,
207
- url: `/api/v2/preupgrade_reports/${reportId}/preupgrade_report_entries`,
217
+ key: `GET_FIXABLE_COUNT_${jobInvocationId}`,
218
+ url: '/api/v2/preupgrade_report_entries',
208
219
  params: {
220
+ job_invocation_id: jobInvocationId,
209
221
  search: [searchValue, 'fix_type = command']
210
222
  .filter(Boolean)
211
223
  .join(' AND '),
@@ -226,7 +238,7 @@ const PreupgradeReportsTable = ({ data = {} }) => {
226
238
  originalSelectAll(...args);
227
239
  }
228
240
  },
229
- [dispatch, reportId, searchValue, fixableCount, originalSelectAll]
241
+ [dispatch, jobInvocationId, searchValue, fixableCount, originalSelectAll]
230
242
  );
231
243
 
232
244
  const selectedIds = Array.from(inclusionSet);
@@ -244,19 +256,6 @@ const PreupgradeReportsTable = ({ data = {} }) => {
244
256
  [globalHostId]
245
257
  );
246
258
 
247
- const hostIdsForSelected = useMemo(
248
- () =>
249
- Array.from(
250
- new Set(
251
- rows
252
- .filter(e => selectedIds.includes(e.id))
253
- .map(getHostId)
254
- .filter(Boolean)
255
- )
256
- ),
257
- [rows, selectedIds, getHostId]
258
- );
259
-
260
259
  const allHostIds = useMemo(
261
260
  () => Array.from(new Set(rows.map(getHostId).filter(Boolean))),
262
261
  [rows, getHostId]
@@ -271,11 +270,14 @@ const PreupgradeReportsTable = ({ data = {} }) => {
271
270
  const newDirection = (parts[1] || 'ASC').toLowerCase();
272
271
  sortChanged =
273
272
  newIndex !== sortBy.index || newDirection !== sortBy.direction;
274
- if (sortChanged)
273
+
274
+ if (sortChanged) {
275
275
  setSortBy({ index: newIndex, direction: newDirection });
276
+ }
276
277
  }
277
278
 
278
279
  let newPage = pagination.page;
280
+
279
281
  if (sortChanged) {
280
282
  newPage = 1;
281
283
  } else if (newParams.page !== undefined) {
@@ -300,53 +302,42 @@ const PreupgradeReportsTable = ({ data = {} }) => {
300
302
  };
301
303
 
302
304
  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
- );
305
+ const isBulkSelection = areAllRowsSelected() || exclusionSet.size > 0;
306
+
307
+ if (!isBulkSelection && selectedIds.length === 0) {
308
+ return;
349
309
  }
310
+
311
+ setSubmitError(null);
312
+ setIsSubmitting(true);
313
+
314
+ dispatch(
315
+ APIActions.post({
316
+ key: `BULK_REMEDIATE_${jobInvocationId}`,
317
+ url: foremanUrl('/api/v2/preupgrade_report_entries/bulk_remediate'),
318
+ params: {
319
+ job_invocation_id: jobInvocationId,
320
+ ...(isBulkSelection
321
+ ? {
322
+ ...(searchValue && { search: searchValue }),
323
+ excluded_ids: Array.from(exclusionSet),
324
+ }
325
+ : { ids: selectedIds }),
326
+ },
327
+ handleSuccess: response => {
328
+ const result = response.data || response;
329
+ if (result?.id) {
330
+ window.location.assign(foremanUrl(`/job_invocations/${result.id}`));
331
+ } else {
332
+ setIsSubmitting(false);
333
+ }
334
+ },
335
+ handleError: err => {
336
+ setSubmitError(err);
337
+ setIsSubmitting(false);
338
+ },
339
+ })
340
+ );
350
341
  };
351
342
 
352
343
  const handleRunUpgrade = () => {
@@ -366,7 +357,9 @@ const PreupgradeReportsTable = ({ data = {} }) => {
366
357
  [columns]
367
358
  );
368
359
 
369
- if (!isLeappJob) return null;
360
+ if (!isLeappJob) {
361
+ return null;
362
+ }
370
363
 
371
364
  const hasAnySelection =
372
365
  areAllRowsSelected() || exclusionSet.size > 0 || selectedIds.length > 0;
@@ -375,8 +368,8 @@ const PreupgradeReportsTable = ({ data = {} }) => {
375
368
  status === STATUS.PENDING ||
376
369
  isSubmitting ||
377
370
  !hasAnySelection ||
378
- // When using select-all, check if any fixable entries exist on current page
379
- (areAllRowsSelected() && pagedFixableEntries.length === 0);
371
+ // Global select-all with zero fixable entries in the report (not per-page)
372
+ (areAllRowsSelected() && fixableCount === 0);
380
373
 
381
374
  const isRunUpgradeDisabled =
382
375
  status === STATUS.PENDING ||
@@ -416,7 +409,7 @@ const PreupgradeReportsTable = ({ data = {} }) => {
416
409
  </ToolbarItem>
417
410
  )}
418
411
 
419
- {reportId && searchProps && (
412
+ {hasReports && searchProps && (
420
413
  <ToolbarItem className="leapp-searchbar-item">
421
414
  <SearchBar
422
415
  data={searchProps}
@@ -547,7 +540,7 @@ PreupgradeReportsTable.propTypes = {
547
540
  data: PropTypes.shape({
548
541
  id: PropTypes.number,
549
542
  // eslint-disable-next-line camelcase
550
- template_name: PropTypes.string,
543
+ has_leapp_report: PropTypes.bool,
551
544
  // eslint-disable-next-line camelcase
552
545
  status_label: PropTypes.string,
553
546
  targeting: PropTypes.shape({
data/webpack/index.js CHANGED
@@ -1,13 +0,0 @@
1
- import componentRegistry from 'foremanReact/components/componentRegistry';
2
-
3
- import injectReducer from 'foremanReact/redux/reducers/registerReducer';
4
-
5
- import PreupgradeReports from './components/PreupgradeReports';
6
- import reducer from './reducer';
7
-
8
- componentRegistry.register({
9
- name: 'PreupgradeReports',
10
- type: PreupgradeReports,
11
- });
12
-
13
- injectReducer('foremanLeapp', reducer);