foreman_leapp 3.3.0 → 3.4.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/Rakefile +0 -15
- data/lib/foreman_leapp/version.rb +1 -1
- data/webpack/components/PreupgradeReportsTable/PreupgradeReportsTable.scss +9 -0
- data/webpack/components/PreupgradeReportsTable/__tests__/PreupgradeReportsTable.test.js +276 -53
- data/webpack/components/PreupgradeReportsTable/index.js +319 -92
- metadata +3 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7e5030bef562d53e8f4212fa76732f0901ec4b827620a5f07028fd11bcf36d4
|
|
4
|
+
data.tar.gz: eac05d03ce3b746a996751d2b5affb5ea1fabea4d74dd8899f5771b5bc69f8ec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77e129433f0175d3951ccc870e66bda4f37930d888a83704142e80d95490e7a9c76b134ab3337c293e03c15bc9c770e8835ba9bec8e11e32d66087ff0a23a5a4
|
|
7
|
+
data.tar.gz: 29c9135b5faf4f6564c71d24cc989a6adc3d4a67c731a5bfca38d3618ec75745f9f1492295356386a7204971d53853558bf5a6d335f5252f55bc0d557bb42f7d
|
data/Rakefile
CHANGED
|
@@ -6,21 +6,6 @@ begin
|
|
|
6
6
|
rescue LoadError
|
|
7
7
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
8
8
|
end
|
|
9
|
-
begin
|
|
10
|
-
require 'rdoc/task'
|
|
11
|
-
rescue LoadError
|
|
12
|
-
require 'rdoc/rdoc'
|
|
13
|
-
require 'rake/rdoctask'
|
|
14
|
-
RDoc::Task = Rake::RDocTask
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
RDoc::Task.new(:rdoc) do |rdoc|
|
|
18
|
-
rdoc.rdoc_dir = 'rdoc'
|
|
19
|
-
rdoc.title = 'ForemanLeapp'
|
|
20
|
-
rdoc.options << '--line-numbers'
|
|
21
|
-
rdoc.rdoc_files.include('README.rdoc')
|
|
22
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
23
|
-
end
|
|
24
9
|
|
|
25
10
|
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
|
|
26
11
|
|
|
@@ -3,3 +3,12 @@
|
|
|
3
3
|
white-space: pre-wrap;
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
.leapp-expanded-tbody {
|
|
8
|
+
border: 1px solid var(--pf-v5-global--BorderColor--100);
|
|
9
|
+
box-shadow: var(--pf-v5-global--BoxShadow--sm);
|
|
10
|
+
|
|
11
|
+
tr:first-child > td {
|
|
12
|
+
background-color: var(--pf-v5-global--primary-color--light-background, #e7f1fa);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
2
|
+
|
|
2
3
|
import {
|
|
4
|
+
fireEvent,
|
|
3
5
|
render,
|
|
4
6
|
screen,
|
|
5
7
|
waitFor,
|
|
6
|
-
fireEvent,
|
|
7
8
|
within,
|
|
8
9
|
} from '@testing-library/react';
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
import { APIActions } from 'foremanReact/redux/API';
|
|
12
|
+
import PreupgradeReportsTable from '../index';
|
|
10
13
|
import { Provider } from 'react-redux';
|
|
14
|
+
import React from 'react';
|
|
11
15
|
import configureMockStore from 'redux-mock-store';
|
|
12
16
|
import thunk from 'redux-thunk';
|
|
13
|
-
import { APIActions } from 'foremanReact/redux/API';
|
|
14
|
-
import PreupgradeReportsTable from '../index';
|
|
15
17
|
|
|
16
18
|
jest.mock('foremanReact/redux/API');
|
|
17
19
|
|
|
@@ -24,17 +26,25 @@ const mockJobData = {
|
|
|
24
26
|
template_name: 'Run preupgrade via Leapp',
|
|
25
27
|
};
|
|
26
28
|
|
|
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
|
|
27
32
|
const mockEntries = Array.from({ length: 12 }, (_, i) => ({
|
|
28
33
|
id: i + 1,
|
|
29
34
|
title: `Report Entry ${i + 1}`,
|
|
30
35
|
hostname: 'example.com',
|
|
36
|
+
host_id: 100 + i,
|
|
31
37
|
severity: i === 0 ? 'high' : 'low',
|
|
32
38
|
summary: `Summary for report entry ${i + 1}`,
|
|
33
39
|
tags: i === 0 ? ['security', 'network'] : [],
|
|
34
40
|
flags: i === 0 ? ['inhibitor'] : [],
|
|
35
41
|
detail: {
|
|
36
42
|
remediations:
|
|
37
|
-
i === 0
|
|
43
|
+
i === 0
|
|
44
|
+
? [{ type: 'command', context: ['echo', 'fix_command'] }]
|
|
45
|
+
: i === 1
|
|
46
|
+
? [{ type: 'hint', context: 'Do something manually' }]
|
|
47
|
+
: null,
|
|
38
48
|
external:
|
|
39
49
|
i === 0 ? [{ url: 'http://example.com', title: 'External Link' }] : [],
|
|
40
50
|
},
|
|
@@ -59,6 +69,8 @@ describe('PreupgradeReportsTable', () => {
|
|
|
59
69
|
return { type: 'MOCK_API_SUCCESS' };
|
|
60
70
|
};
|
|
61
71
|
});
|
|
72
|
+
|
|
73
|
+
APIActions.post.mockImplementation(() => () => ({ type: 'MOCK_API_POST' }));
|
|
62
74
|
});
|
|
63
75
|
|
|
64
76
|
const renderComponent = (data = mockJobData) =>
|
|
@@ -68,41 +80,106 @@ describe('PreupgradeReportsTable', () => {
|
|
|
68
80
|
</Provider>
|
|
69
81
|
);
|
|
70
82
|
|
|
71
|
-
const expandSection = () =>
|
|
83
|
+
const expandSection = () =>
|
|
72
84
|
fireEvent.click(screen.getByText('Leapp preupgrade report'));
|
|
73
|
-
|
|
85
|
+
|
|
86
|
+
const waitForTable = () =>
|
|
87
|
+
waitFor(() => screen.getByText('Report Entry 1', { selector: 'td' }));
|
|
74
88
|
|
|
75
89
|
it('renders data', async () => {
|
|
76
90
|
renderComponent();
|
|
77
91
|
expandSection();
|
|
78
|
-
await
|
|
92
|
+
await waitForTable();
|
|
79
93
|
expect(
|
|
80
94
|
screen.getByText('Report Entry 1', { selector: 'td' })
|
|
81
95
|
).toBeInTheDocument();
|
|
82
96
|
});
|
|
83
97
|
|
|
84
|
-
it('
|
|
85
|
-
renderComponent();
|
|
98
|
+
it('does not render anything for non-Leapp jobs', () => {
|
|
99
|
+
renderComponent({ id: 55, template_name: 'Standard RHEL Update' });
|
|
100
|
+
expect(
|
|
101
|
+
screen.queryByText('Leapp preupgrade report')
|
|
102
|
+
).not.toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('refetches when status_label transitions (e.g. Running → Succeeded)', async () => {
|
|
106
|
+
const { rerender } = render(
|
|
107
|
+
<Provider store={store}>
|
|
108
|
+
<PreupgradeReportsTable
|
|
109
|
+
data={{ ...mockJobData, status_label: 'Running' }}
|
|
110
|
+
/>
|
|
111
|
+
</Provider>
|
|
112
|
+
);
|
|
86
113
|
expandSection();
|
|
87
|
-
await
|
|
114
|
+
await waitForTable();
|
|
88
115
|
|
|
89
|
-
const
|
|
90
|
-
fireEvent.click(rowExpandButtons[0]);
|
|
116
|
+
const callCountAfterFirstFetch = APIActions.get.mock.calls.length;
|
|
91
117
|
|
|
92
|
-
|
|
118
|
+
rerender(
|
|
119
|
+
<Provider store={store}>
|
|
120
|
+
<PreupgradeReportsTable
|
|
121
|
+
data={{ ...mockJobData, status_label: 'Succeeded' }}
|
|
122
|
+
/>
|
|
123
|
+
</Provider>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await waitFor(() =>
|
|
127
|
+
expect(APIActions.get.mock.calls.length).toBeGreaterThan(
|
|
128
|
+
callCountAfterFirstFetch
|
|
129
|
+
)
|
|
130
|
+
);
|
|
93
131
|
expect(
|
|
94
|
-
|
|
132
|
+
screen.getByText('Report Entry 1', { selector: 'td' })
|
|
95
133
|
).toBeInTheDocument();
|
|
96
134
|
});
|
|
97
135
|
|
|
98
|
-
it('
|
|
136
|
+
it('does not refetch on collapse/re-expand when status_label is unchanged', async () => {
|
|
137
|
+
renderComponent({ ...mockJobData, status_label: 'Succeeded' });
|
|
138
|
+
expandSection();
|
|
139
|
+
await waitForTable();
|
|
140
|
+
|
|
141
|
+
const callCountAfterFirstFetch = APIActions.get.mock.calls.length;
|
|
142
|
+
|
|
143
|
+
fireEvent.click(screen.getByText('Leapp preupgrade report')); // collapse
|
|
144
|
+
fireEvent.click(screen.getByText('Leapp preupgrade report')); // re-expand
|
|
145
|
+
|
|
146
|
+
expect(APIActions.get.mock.calls.length).toBe(callCountAfterFirstFetch);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('renders empty state message when no issues found', async () => {
|
|
150
|
+
APIActions.get.mockImplementation(({ key, handleSuccess }) => {
|
|
151
|
+
return () => {
|
|
152
|
+
if (key.includes('GET_LEAPP_REPORT_LIST'))
|
|
153
|
+
handleSuccess({ results: [{ id: mockReportId }] });
|
|
154
|
+
if (key.includes('GET_LEAPP_REPORT_DETAIL'))
|
|
155
|
+
handleSuccess({ id: mockReportId, preupgrade_report_entries: [] });
|
|
156
|
+
return { type: 'EMPTY' };
|
|
157
|
+
};
|
|
158
|
+
});
|
|
99
159
|
renderComponent();
|
|
100
160
|
expandSection();
|
|
101
|
-
await waitFor(() =>
|
|
161
|
+
await waitFor(() =>
|
|
162
|
+
expect(
|
|
163
|
+
screen.getByText('The preupgrade report shows no issues.')
|
|
164
|
+
).toBeInTheDocument()
|
|
165
|
+
);
|
|
166
|
+
});
|
|
102
167
|
|
|
103
|
-
|
|
104
|
-
|
|
168
|
+
it('expands a row and shows details', async () => {
|
|
169
|
+
renderComponent();
|
|
170
|
+
expandSection();
|
|
171
|
+
await waitForTable();
|
|
172
|
+
fireEvent.click(screen.getAllByLabelText('Details')[0]);
|
|
173
|
+
expect(
|
|
174
|
+
await screen.findByText('Summary for report entry 1')
|
|
175
|
+
).toBeInTheDocument();
|
|
176
|
+
});
|
|
105
177
|
|
|
178
|
+
it('expands all rows', async () => {
|
|
179
|
+
renderComponent();
|
|
180
|
+
expandSection();
|
|
181
|
+
await waitForTable();
|
|
182
|
+
fireEvent.click(screen.getByLabelText('Expand all rows'));
|
|
106
183
|
expect(
|
|
107
184
|
await screen.findByText('Summary for report entry 1')
|
|
108
185
|
).toBeInTheDocument();
|
|
@@ -114,11 +191,9 @@ describe('PreupgradeReportsTable', () => {
|
|
|
114
191
|
it('paginates to the next page', async () => {
|
|
115
192
|
renderComponent();
|
|
116
193
|
expandSection();
|
|
117
|
-
await
|
|
118
|
-
|
|
194
|
+
await waitForTable();
|
|
119
195
|
fireEvent.click(screen.getAllByLabelText('Go to next page')[0]);
|
|
120
196
|
await waitFor(() => screen.getByText('Report Entry 6', { selector: 'td' }));
|
|
121
|
-
|
|
122
197
|
expect(
|
|
123
198
|
screen.getByText('Report Entry 10', { selector: 'td' })
|
|
124
199
|
).toBeInTheDocument();
|
|
@@ -127,19 +202,168 @@ describe('PreupgradeReportsTable', () => {
|
|
|
127
202
|
it('changes perPage limit to 10', async () => {
|
|
128
203
|
renderComponent();
|
|
129
204
|
expandSection();
|
|
130
|
-
await
|
|
131
|
-
|
|
205
|
+
await waitForTable();
|
|
132
206
|
fireEvent.click(screen.getAllByLabelText('Items per page')[0]);
|
|
133
207
|
fireEvent.click(screen.getAllByText('10 per page')[0]);
|
|
134
|
-
|
|
135
|
-
await waitFor(() => {
|
|
208
|
+
await waitFor(() =>
|
|
136
209
|
expect(
|
|
137
210
|
screen.getByText('Report Entry 10', { selector: 'td' })
|
|
138
|
-
).toBeInTheDocument()
|
|
139
|
-
|
|
211
|
+
).toBeInTheDocument()
|
|
212
|
+
);
|
|
140
213
|
});
|
|
141
214
|
|
|
142
|
-
it('
|
|
215
|
+
it('displays correct inhibitor status based on flags', async () => {
|
|
216
|
+
renderComponent();
|
|
217
|
+
expandSection();
|
|
218
|
+
await waitForTable();
|
|
219
|
+
|
|
220
|
+
const row1 = screen
|
|
221
|
+
.getByText('Report Entry 1', { selector: 'td' })
|
|
222
|
+
.closest('tr');
|
|
223
|
+
expect(
|
|
224
|
+
within(row1.querySelector('td[data-label="Inhibitor?"]')).getByText('Yes')
|
|
225
|
+
).toBeInTheDocument();
|
|
226
|
+
|
|
227
|
+
const row2 = screen
|
|
228
|
+
.getByText('Report Entry 2', { selector: 'td' })
|
|
229
|
+
.closest('tr');
|
|
230
|
+
expect(
|
|
231
|
+
within(row2.querySelector('td[data-label="Inhibitor?"]')).getByText('No')
|
|
232
|
+
).toBeInTheDocument();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('shows Has Remediation? Yes for any remediation type, not only command', async () => {
|
|
236
|
+
renderComponent();
|
|
237
|
+
expandSection();
|
|
238
|
+
await waitForTable();
|
|
239
|
+
|
|
240
|
+
// id=1: command → Yes
|
|
241
|
+
const row1 = screen
|
|
242
|
+
.getByText('Report Entry 1', { selector: 'td' })
|
|
243
|
+
.closest('tr');
|
|
244
|
+
expect(
|
|
245
|
+
within(row1.querySelector('td[data-label="Has Remediation?"]')).getByText(
|
|
246
|
+
'Yes'
|
|
247
|
+
)
|
|
248
|
+
).toBeInTheDocument();
|
|
249
|
+
|
|
250
|
+
// id=2: hint-only → still Yes (display column shows any remediations)
|
|
251
|
+
const row2 = screen
|
|
252
|
+
.getByText('Report Entry 2', { selector: 'td' })
|
|
253
|
+
.closest('tr');
|
|
254
|
+
expect(
|
|
255
|
+
within(row2.querySelector('td[data-label="Has Remediation?"]')).getByText(
|
|
256
|
+
'Yes'
|
|
257
|
+
)
|
|
258
|
+
).toBeInTheDocument();
|
|
259
|
+
|
|
260
|
+
// id=3: no remediations → No
|
|
261
|
+
const row3 = screen
|
|
262
|
+
.getByText('Report Entry 3', { selector: 'td' })
|
|
263
|
+
.closest('tr');
|
|
264
|
+
expect(
|
|
265
|
+
within(row3.querySelector('td[data-label="Has Remediation?"]')).getByText(
|
|
266
|
+
'No'
|
|
267
|
+
)
|
|
268
|
+
).toBeInTheDocument();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('renders Fix Selected button disabled initially', async () => {
|
|
272
|
+
renderComponent();
|
|
273
|
+
expandSection();
|
|
274
|
+
await waitForTable();
|
|
275
|
+
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeDisabled();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('enables Fix Selected after selecting a fixable (command) row', async () => {
|
|
279
|
+
renderComponent();
|
|
280
|
+
expandSection();
|
|
281
|
+
await waitForTable();
|
|
282
|
+
|
|
283
|
+
// checkboxes[0] = SelectAll, checkboxes[1] = entry id=1 (command remediation)
|
|
284
|
+
fireEvent.click(screen.getAllByRole('checkbox')[1]);
|
|
285
|
+
|
|
286
|
+
await waitFor(() =>
|
|
287
|
+
expect(
|
|
288
|
+
screen.getByRole('button', { name: 'Fix Selected' })
|
|
289
|
+
).not.toBeDisabled()
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('Fix Selected dispatches APIActions.post with correct feature, host_ids and remediation_ids', async () => {
|
|
294
|
+
renderComponent();
|
|
295
|
+
expandSection();
|
|
296
|
+
await waitForTable();
|
|
297
|
+
|
|
298
|
+
fireEvent.click(screen.getAllByRole('checkbox')[1]); // select entry id=1 (host_id=100)
|
|
299
|
+
|
|
300
|
+
await waitFor(() =>
|
|
301
|
+
expect(
|
|
302
|
+
screen.getByRole('button', { name: 'Fix Selected' })
|
|
303
|
+
).not.toBeDisabled()
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
fireEvent.click(screen.getByRole('button', { name: 'Fix Selected' }));
|
|
307
|
+
|
|
308
|
+
expect(APIActions.post).toHaveBeenCalledWith(
|
|
309
|
+
expect.objectContaining({
|
|
310
|
+
url: expect.stringContaining('/api/job_invocations'),
|
|
311
|
+
params: {
|
|
312
|
+
job_invocation: {
|
|
313
|
+
feature: 'leapp_remediation_plan',
|
|
314
|
+
host_ids: [100],
|
|
315
|
+
inputs: { remediation_ids: '1' },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('hint-only row checkbox is disabled and does not enable Fix Selected', async () => {
|
|
323
|
+
renderComponent();
|
|
324
|
+
expandSection();
|
|
325
|
+
await waitForTable();
|
|
326
|
+
|
|
327
|
+
// checkboxes[2] = entry id=2, which has hint-only remediation — must be disabled
|
|
328
|
+
const hintCheckbox = screen.getAllByRole('checkbox')[2];
|
|
329
|
+
expect(hintCheckbox).toBeDisabled();
|
|
330
|
+
|
|
331
|
+
fireEvent.click(hintCheckbox);
|
|
332
|
+
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeDisabled();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('renders Run Upgrade button enabled when entries are present', async () => {
|
|
336
|
+
renderComponent();
|
|
337
|
+
expandSection();
|
|
338
|
+
await waitForTable();
|
|
339
|
+
expect(
|
|
340
|
+
screen.getByRole('button', { name: 'Run Upgrade' })
|
|
341
|
+
).not.toBeDisabled();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('Run Upgrade dispatches APIActions.post with correct feature and all host_ids', async () => {
|
|
345
|
+
renderComponent();
|
|
346
|
+
expandSection();
|
|
347
|
+
await waitForTable();
|
|
348
|
+
|
|
349
|
+
fireEvent.click(screen.getByRole('button', { name: 'Run Upgrade' }));
|
|
350
|
+
|
|
351
|
+
expect(APIActions.post).toHaveBeenCalledWith(
|
|
352
|
+
expect.objectContaining({
|
|
353
|
+
url: expect.stringContaining('/api/job_invocations'),
|
|
354
|
+
params: {
|
|
355
|
+
job_invocation: {
|
|
356
|
+
feature: 'leapp_upgrade',
|
|
357
|
+
host_ids: expect.arrayContaining([100, 101, 102]),
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
})
|
|
361
|
+
);
|
|
362
|
+
const callParams = APIActions.post.mock.calls[0][0].params.job_invocation;
|
|
363
|
+
expect(callParams.inputs).toBeUndefined();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('does not render toolbar buttons when report has no entries', async () => {
|
|
143
367
|
APIActions.get.mockImplementation(({ key, handleSuccess }) => {
|
|
144
368
|
return () => {
|
|
145
369
|
if (key.includes('GET_LEAPP_REPORT_LIST'))
|
|
@@ -151,36 +375,35 @@ describe('PreupgradeReportsTable', () => {
|
|
|
151
375
|
});
|
|
152
376
|
renderComponent();
|
|
153
377
|
expandSection();
|
|
154
|
-
await waitFor(() =>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
).toBeInTheDocument();
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('does not render anything for non-Leapp jobs', () => {
|
|
162
|
-
const nonLeappData = { id: 55, template_name: 'Standard RHEL Update' };
|
|
163
|
-
renderComponent(nonLeappData);
|
|
378
|
+
await waitFor(() =>
|
|
379
|
+
screen.getByText('The preupgrade report shows no issues.')
|
|
380
|
+
);
|
|
164
381
|
expect(
|
|
165
|
-
screen.
|
|
382
|
+
screen.queryByRole('button', { name: 'Fix Selected' })
|
|
383
|
+
).not.toBeInTheDocument();
|
|
384
|
+
expect(
|
|
385
|
+
screen.queryByRole('button', { name: 'Run Upgrade' })
|
|
166
386
|
).not.toBeInTheDocument();
|
|
167
387
|
});
|
|
168
388
|
|
|
169
|
-
it('
|
|
389
|
+
it('renders the SelectAll checkbox', async () => {
|
|
170
390
|
renderComponent();
|
|
171
391
|
expandSection();
|
|
172
|
-
await
|
|
392
|
+
await waitForTable();
|
|
393
|
+
expect(screen.getAllByRole('checkbox').length).toBeGreaterThan(0);
|
|
394
|
+
});
|
|
173
395
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
expect(within(inhibitorCell1).getByText('Yes')).toBeInTheDocument();
|
|
396
|
+
it('selecting all fixable rows enables Fix Selected', async () => {
|
|
397
|
+
renderComponent();
|
|
398
|
+
expandSection();
|
|
399
|
+
await waitForTable();
|
|
179
400
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
401
|
+
fireEvent.click(screen.getByLabelText('Select all'));
|
|
402
|
+
|
|
403
|
+
await waitFor(() =>
|
|
404
|
+
expect(
|
|
405
|
+
screen.getByRole('button', { name: 'Fix Selected' })
|
|
406
|
+
).not.toBeDisabled()
|
|
407
|
+
);
|
|
185
408
|
});
|
|
186
409
|
});
|
|
@@ -1,163 +1,374 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
1
2
|
import PropTypes from 'prop-types';
|
|
2
|
-
import React, {
|
|
3
|
+
import React, {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
3
10
|
import { useDispatch } from 'react-redux';
|
|
4
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Button,
|
|
13
|
+
ExpandableSection,
|
|
14
|
+
Toolbar,
|
|
15
|
+
ToolbarContent,
|
|
16
|
+
ToolbarGroup,
|
|
17
|
+
ToolbarItem,
|
|
18
|
+
Tooltip,
|
|
19
|
+
} from '@patternfly/react-core';
|
|
5
20
|
import { ExpandableRowContent, Tbody, Td, Tr } from '@patternfly/react-table';
|
|
6
21
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
22
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
7
23
|
import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table';
|
|
24
|
+
import SelectAllCheckbox from 'foremanReact/components/PF4/TableIndexPage/Table/SelectAllCheckbox';
|
|
25
|
+
import { useBulkSelect } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
|
|
26
|
+
import { RowSelectTd } from 'foremanReact/components/PF4/TableIndexPage/RowSelectTd';
|
|
8
27
|
import { getColumnHelpers } from 'foremanReact/components/PF4/TableIndexPage/Table/helpers';
|
|
9
28
|
import { APIActions } from 'foremanReact/redux/API';
|
|
10
29
|
import { STATUS } from 'foremanReact/constants';
|
|
11
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
entriesPage,
|
|
32
|
+
entryFixable,
|
|
33
|
+
} from '../PreupgradeReports/PreupgradeReportsHelpers';
|
|
12
34
|
import ReportDetails, { renderSeverityLabel } from './ReportDetails';
|
|
35
|
+
import './PreupgradeReportsTable.scss';
|
|
36
|
+
|
|
37
|
+
const LEAPP_TEMPLATE_NAME = 'Run preupgrade via Leapp';
|
|
38
|
+
|
|
39
|
+
const isRowFixable = entryFixable;
|
|
40
|
+
|
|
41
|
+
const submitJobInvocation = (
|
|
42
|
+
dispatch,
|
|
43
|
+
setError,
|
|
44
|
+
feature,
|
|
45
|
+
hostIds,
|
|
46
|
+
remediationIds
|
|
47
|
+
) => {
|
|
48
|
+
const payload = {
|
|
49
|
+
job_invocation: {
|
|
50
|
+
feature,
|
|
51
|
+
host_ids: hostIds,
|
|
52
|
+
...(remediationIds != null
|
|
53
|
+
? { inputs: { remediation_ids: remediationIds } }
|
|
54
|
+
: {}),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
dispatch(
|
|
59
|
+
APIActions.post({
|
|
60
|
+
key: `CREATE_JOB_INVOCATION_${feature}`,
|
|
61
|
+
url: foremanUrl('/api/job_invocations'),
|
|
62
|
+
params: payload,
|
|
63
|
+
handleSuccess: response => {
|
|
64
|
+
const result = response.data || response;
|
|
65
|
+
if (result?.id) {
|
|
66
|
+
window.location.assign(foremanUrl(`/job_invocations/${result.id}`));
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
handleError: err => setError(err),
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
};
|
|
13
73
|
|
|
14
74
|
const PreupgradeReportsTable = ({ data = {} }) => {
|
|
15
75
|
const [error, setError] = useState(null);
|
|
16
|
-
|
|
17
|
-
const [isReportExpanded, setIsReportExpanded] = useState(false); // Outer expansion state (Leapp Report Section)
|
|
76
|
+
const [isReportExpanded, setIsReportExpanded] = useState(false);
|
|
18
77
|
const [pagination, setPagination] = useState({ page: 1, perPage: 5 });
|
|
19
78
|
const [reportData, setReportData] = useState(null);
|
|
20
79
|
const [status, setStatus] = useState(STATUS.RESOLVED);
|
|
21
|
-
const [expandedRowIds, setExpandedRowIds] = useState(new Set());
|
|
80
|
+
const [expandedRowIds, setExpandedRowIds] = useState(new Set());
|
|
22
81
|
|
|
23
82
|
const dispatch = useDispatch();
|
|
24
83
|
// eslint-disable-next-line camelcase
|
|
25
|
-
const isLeappJob = data?.template_name?.includes(
|
|
84
|
+
const isLeappJob = data?.template_name?.includes(LEAPP_TEMPLATE_NAME);
|
|
26
85
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
86
|
+
// eslint-disable-next-line camelcase
|
|
87
|
+
const jobStatusLabel = data?.status_label;
|
|
88
|
+
|
|
89
|
+
const lastFetchedKeyRef = useRef(null);
|
|
90
|
+
|
|
91
|
+
const columns = useMemo(
|
|
92
|
+
() => ({
|
|
93
|
+
title: { title: __('Title') },
|
|
94
|
+
host: {
|
|
95
|
+
title: __('Host'),
|
|
96
|
+
wrapper: entry => entry.hostname || reportData?.hostname || '-',
|
|
97
|
+
},
|
|
98
|
+
risk_factor: {
|
|
99
|
+
title: __('Risk Factor'),
|
|
100
|
+
wrapper: ({ severity }) => renderSeverityLabel(severity),
|
|
101
|
+
},
|
|
102
|
+
has_remediation: {
|
|
103
|
+
title: __('Has Remediation?'),
|
|
104
|
+
wrapper: entry => (entry.detail?.remediations ? __('Yes') : __('No')),
|
|
105
|
+
},
|
|
106
|
+
inhibitor: {
|
|
107
|
+
title: __('Inhibitor?'),
|
|
108
|
+
wrapper: entry =>
|
|
109
|
+
entry.flags?.some(flag => flag === 'inhibitor') ? (
|
|
110
|
+
<Tooltip content={__('This issue inhibits the upgrade.')}>
|
|
111
|
+
<span>{__('Yes')}</span>
|
|
112
|
+
</Tooltip>
|
|
113
|
+
) : (
|
|
114
|
+
__('No')
|
|
115
|
+
),
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
+
[reportData?.hostname]
|
|
120
|
+
);
|
|
57
121
|
|
|
58
122
|
useEffect(() => {
|
|
59
123
|
let isMounted = true;
|
|
60
|
-
|
|
124
|
+
const fetchKey = `${data.id}:${jobStatusLabel}`;
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
!isLeappJob ||
|
|
128
|
+
!isReportExpanded ||
|
|
129
|
+
lastFetchedKeyRef.current === fetchKey
|
|
130
|
+
)
|
|
61
131
|
return undefined;
|
|
62
|
-
}
|
|
63
|
-
setStatus(STATUS.PENDING);
|
|
64
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);
|
|
65
147
|
dispatch(
|
|
66
148
|
APIActions.get({
|
|
67
149
|
key: `GET_LEAPP_REPORT_LIST_${data.id}`,
|
|
68
150
|
url: `/api/job_invocations/${data.id}/preupgrade_reports`,
|
|
69
151
|
handleSuccess: listResponse => {
|
|
70
152
|
if (!isMounted) return;
|
|
71
|
-
const
|
|
72
|
-
const summary = listPayload.results?.[0];
|
|
153
|
+
const summary = (listResponse.data || listResponse).results?.[0];
|
|
73
154
|
if (summary?.id) {
|
|
74
155
|
dispatch(
|
|
75
156
|
APIActions.get({
|
|
76
157
|
key: `GET_LEAPP_REPORT_DETAIL_${summary.id}`,
|
|
77
158
|
url: `/api/preupgrade_reports/${summary.id}`,
|
|
78
|
-
handleSuccess: detailResponse =>
|
|
79
|
-
|
|
80
|
-
const detailPayload = detailResponse.data || detailResponse;
|
|
81
|
-
setReportData(detailPayload);
|
|
82
|
-
setStatus(STATUS.RESOLVED);
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
handleError: err => {
|
|
86
|
-
if (isMounted) {
|
|
87
|
-
setError(err);
|
|
88
|
-
setStatus(STATUS.ERROR);
|
|
89
|
-
}
|
|
90
|
-
},
|
|
159
|
+
handleSuccess: detailResponse => succeed(detailResponse),
|
|
160
|
+
handleError: err => fail(err),
|
|
91
161
|
})
|
|
92
162
|
);
|
|
93
|
-
|
|
94
|
-
setReportData({});
|
|
95
|
-
setStatus(STATUS.RESOLVED);
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
handleError: err => {
|
|
99
|
-
if (isMounted) {
|
|
100
|
-
setError(err);
|
|
101
|
-
setStatus(STATUS.ERROR);
|
|
163
|
+
return;
|
|
102
164
|
}
|
|
165
|
+
succeed(null);
|
|
103
166
|
},
|
|
167
|
+
handleError: err => fail(err),
|
|
104
168
|
})
|
|
105
169
|
);
|
|
106
170
|
|
|
107
171
|
return () => {
|
|
108
172
|
isMounted = false;
|
|
109
173
|
};
|
|
110
|
-
}, [isReportExpanded, data.id, isLeappJob,
|
|
174
|
+
}, [isReportExpanded, data.id, isLeappJob, dispatch, jobStatusLabel]);
|
|
111
175
|
|
|
112
176
|
// eslint-disable-next-line camelcase
|
|
113
|
-
const entries = reportData?.preupgrade_report_entries || []
|
|
114
|
-
|
|
177
|
+
const entries = useMemo(() => reportData?.preupgrade_report_entries || [], [
|
|
178
|
+
reportData,
|
|
179
|
+
]);
|
|
115
180
|
|
|
116
|
-
const
|
|
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
|
+
);
|
|
198
|
+
|
|
199
|
+
const handleParamsChange = useCallback(newParams => {
|
|
117
200
|
setPagination(prev => ({
|
|
118
201
|
...prev,
|
|
119
202
|
page: newParams.page || prev.page,
|
|
120
203
|
perPage: newParams.per_page || prev.perPage,
|
|
121
204
|
}));
|
|
122
205
|
setExpandedRowIds(new Set());
|
|
123
|
-
};
|
|
206
|
+
}, []);
|
|
124
207
|
|
|
125
|
-
const toggleRowExpansion = (id, isExpanding) => {
|
|
208
|
+
const toggleRowExpansion = useCallback((id, isExpanding) => {
|
|
126
209
|
setExpandedRowIds(prev => {
|
|
127
|
-
const
|
|
128
|
-
if (isExpanding)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
newSet.delete(id);
|
|
132
|
-
}
|
|
133
|
-
return newSet;
|
|
210
|
+
const next = new Set(prev);
|
|
211
|
+
if (isExpanding) next.add(id);
|
|
212
|
+
else next.delete(id);
|
|
213
|
+
return next;
|
|
134
214
|
});
|
|
135
|
-
};
|
|
215
|
+
}, []);
|
|
216
|
+
|
|
217
|
+
const { inclusionSet, exclusionSet, ...selectAllOptions } = useBulkSelect({
|
|
218
|
+
results: pagedEntries,
|
|
219
|
+
metadata: {
|
|
220
|
+
total: entries.length,
|
|
221
|
+
page: pagination.page,
|
|
222
|
+
selectable: entries.length,
|
|
223
|
+
},
|
|
224
|
+
initialSearchQuery: '',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const {
|
|
228
|
+
selectAll,
|
|
229
|
+
selectPage,
|
|
230
|
+
selectNone,
|
|
231
|
+
selectOne,
|
|
232
|
+
areAllRowsSelected,
|
|
233
|
+
isSelected,
|
|
234
|
+
} = selectAllOptions;
|
|
235
|
+
|
|
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]
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const pagedFixableEntries = useMemo(() => pagedEntries.filter(isRowFixable), [
|
|
254
|
+
pagedEntries,
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
const areAllPageFixableSelected =
|
|
258
|
+
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));
|
|
136
264
|
|
|
137
265
|
const areAllRowsExpanded =
|
|
138
266
|
pagedEntries.length > 0 &&
|
|
139
267
|
pagedEntries.every(entry => expandedRowIds.has(entry.id));
|
|
140
268
|
|
|
141
|
-
const onExpandAll = () => {
|
|
142
|
-
setExpandedRowIds(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
269
|
+
const onExpandAll = useCallback(() => {
|
|
270
|
+
setExpandedRowIds(
|
|
271
|
+
areAllRowsExpanded ? new Set() : new Set(pagedEntries.map(e => e.id))
|
|
272
|
+
);
|
|
273
|
+
}, [areAllRowsExpanded, pagedEntries]);
|
|
274
|
+
|
|
275
|
+
const [columnKeys, keysToColumnNames] = useMemo(
|
|
276
|
+
() => getColumnHelpers(columns),
|
|
277
|
+
[columns]
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const hostIdsForSelected = useMemo(
|
|
281
|
+
() =>
|
|
282
|
+
Array.from(
|
|
283
|
+
new Set(
|
|
284
|
+
entries
|
|
285
|
+
.filter(e => selectedIds.includes(e.id))
|
|
286
|
+
.map(getHostId)
|
|
287
|
+
.filter(Boolean)
|
|
288
|
+
)
|
|
289
|
+
),
|
|
290
|
+
[entries, selectedIds, getHostId]
|
|
291
|
+
);
|
|
149
292
|
|
|
150
|
-
const
|
|
293
|
+
const allHostIds = useMemo(
|
|
294
|
+
() => Array.from(new Set(entries.map(getHostId).filter(Boolean))),
|
|
295
|
+
[entries, getHostId]
|
|
296
|
+
);
|
|
151
297
|
|
|
152
298
|
if (!isLeappJob) return null;
|
|
153
299
|
|
|
300
|
+
const isFixSelectedDisabled =
|
|
301
|
+
validFixableIds.length === 0 ||
|
|
302
|
+
selectedIds.length === 0 ||
|
|
303
|
+
hostIdsForSelected.length === 0;
|
|
304
|
+
|
|
154
305
|
return (
|
|
155
306
|
<ExpandableSection
|
|
156
|
-
className="leapp-report-section"
|
|
157
307
|
isExpanded={isReportExpanded}
|
|
158
308
|
onToggle={(_event, val) => setIsReportExpanded(val)}
|
|
159
309
|
toggleText={__('Leapp preupgrade report')}
|
|
160
310
|
>
|
|
311
|
+
{entries.length > 0 && status === STATUS.RESOLVED && (
|
|
312
|
+
<Toolbar ouiaId="leapp-report-toolbar">
|
|
313
|
+
<ToolbarContent>
|
|
314
|
+
<ToolbarGroup variant="filter-group">
|
|
315
|
+
<ToolbarItem>
|
|
316
|
+
<SelectAllCheckbox
|
|
317
|
+
selectAll={selectAll}
|
|
318
|
+
selectPage={selectPage}
|
|
319
|
+
selectNone={selectNone}
|
|
320
|
+
selectedCount={selectedIds.length}
|
|
321
|
+
pageRowCount={pagedFixableEntries.length}
|
|
322
|
+
totalCount={validFixableIds.length}
|
|
323
|
+
areAllRowsOnPageSelected={areAllPageFixableSelected}
|
|
324
|
+
areAllRowsSelected={areAllFixableSelected}
|
|
325
|
+
/>
|
|
326
|
+
</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>
|
|
366
|
+
</ToolbarItem>
|
|
367
|
+
</ToolbarGroup>
|
|
368
|
+
</ToolbarContent>
|
|
369
|
+
</Toolbar>
|
|
370
|
+
)}
|
|
371
|
+
|
|
161
372
|
<Table
|
|
162
373
|
ouiaId="leapp-report-table"
|
|
163
374
|
columns={columns}
|
|
@@ -174,20 +385,24 @@ const PreupgradeReportsTable = ({ data = {} }) => {
|
|
|
174
385
|
errorMessage={
|
|
175
386
|
status === STATUS.ERROR && error?.message ? error.message : null
|
|
176
387
|
}
|
|
177
|
-
showCheckboxes
|
|
388
|
+
showCheckboxes
|
|
178
389
|
refreshData={() => {}}
|
|
179
390
|
isDeleteable={false}
|
|
180
391
|
emptyMessage={__('The preupgrade report shows no issues.')}
|
|
181
392
|
setParams={handleParamsChange}
|
|
182
393
|
childrenOutsideTbody
|
|
183
394
|
onExpandAll={onExpandAll}
|
|
184
|
-
// Inverted per PatternFly implementation to ensure correct toggle icon state
|
|
185
395
|
areAllRowsExpanded={!areAllRowsExpanded}
|
|
186
396
|
>
|
|
187
397
|
{pagedEntries.map((entry, rowIndex) => {
|
|
188
398
|
const isRowExpanded = expandedRowIds.has(entry.id);
|
|
399
|
+
|
|
189
400
|
return (
|
|
190
|
-
<Tbody
|
|
401
|
+
<Tbody
|
|
402
|
+
key={entry.id}
|
|
403
|
+
isExpanded={isRowExpanded}
|
|
404
|
+
className={isRowExpanded ? 'leapp-expanded-tbody' : ''}
|
|
405
|
+
>
|
|
191
406
|
<Tr ouiaId={`table-row-${rowIndex}`}>
|
|
192
407
|
<Td
|
|
193
408
|
expand={{
|
|
@@ -197,6 +412,12 @@ const PreupgradeReportsTable = ({ data = {} }) => {
|
|
|
197
412
|
toggleRowExpansion(entry.id, isOpen),
|
|
198
413
|
}}
|
|
199
414
|
/>
|
|
415
|
+
<RowSelectTd
|
|
416
|
+
rowData={entry}
|
|
417
|
+
selectOne={selectOne}
|
|
418
|
+
isSelected={id => isRowFixable(entry) && isSelected(id)}
|
|
419
|
+
isSelectable={isRowFixable}
|
|
420
|
+
/>
|
|
200
421
|
{columnKeys.map(key => (
|
|
201
422
|
<Td key={key} dataLabel={keysToColumnNames[key]}>
|
|
202
423
|
{columns[key].wrapper
|
|
@@ -209,7 +430,7 @@ const PreupgradeReportsTable = ({ data = {} }) => {
|
|
|
209
430
|
isExpanded={isRowExpanded}
|
|
210
431
|
ouiaId={`table-row-details-${rowIndex}`}
|
|
211
432
|
>
|
|
212
|
-
<Td colSpan={columnKeys.length +
|
|
433
|
+
<Td colSpan={columnKeys.length + 2}>
|
|
213
434
|
<ExpandableRowContent>
|
|
214
435
|
{isRowExpanded && <ReportDetails entry={entry} />}
|
|
215
436
|
</ExpandableRowContent>
|
|
@@ -226,7 +447,13 @@ const PreupgradeReportsTable = ({ data = {} }) => {
|
|
|
226
447
|
PreupgradeReportsTable.propTypes = {
|
|
227
448
|
data: PropTypes.shape({
|
|
228
449
|
id: PropTypes.number,
|
|
450
|
+
// eslint-disable-next-line camelcase
|
|
229
451
|
template_name: PropTypes.string,
|
|
452
|
+
// eslint-disable-next-line camelcase
|
|
453
|
+
status_label: PropTypes.string,
|
|
454
|
+
targeting: PropTypes.shape({
|
|
455
|
+
host_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
456
|
+
}),
|
|
230
457
|
}),
|
|
231
458
|
};
|
|
232
459
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_leapp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Foreman Leapp team
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-05-15 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: foreman_remote_execution
|
|
@@ -37,20 +37,6 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '5.0'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: rdoc
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - "~>"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '6.2'
|
|
47
|
-
type: :development
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - "~>"
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '6.2'
|
|
54
40
|
description: A Foreman plugin to support inplace RHEL upgrades with Leapp utility.
|
|
55
41
|
email:
|
|
56
42
|
- foreman-dev@googlegroups.com
|
|
@@ -214,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
214
200
|
- !ruby/object:Gem::Version
|
|
215
201
|
version: '0'
|
|
216
202
|
requirements: []
|
|
217
|
-
rubygems_version: 4.0.
|
|
203
|
+
rubygems_version: 4.0.10
|
|
218
204
|
specification_version: 4
|
|
219
205
|
summary: A Foreman plugin for Leapp utility.
|
|
220
206
|
test_files:
|