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.
- 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/engine.rb +1 -1
- 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/PreupgradeReports.js +11 -4
- data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.fixtures.js +43 -0
- data/webpack/components/PreupgradeReports/__tests__/PreupgradeReports.test.js +151 -46
- 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 -4
- data/webpack/components/PreupgradeReports/__tests__/__snapshots__/PreupgradeReports.test.js.snap +0 -121
- data/webpack/components/PreupgradeReports/components/__snapshots__/NoReports.test.js.snap +0 -25
|
@@ -60,6 +60,49 @@ export const preupgradeReports = [
|
|
|
60
60
|
},
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
+
export const preupgradeReportsWithFixableEntries = [
|
|
64
|
+
{
|
|
65
|
+
hostId: 5,
|
|
66
|
+
entries: [
|
|
67
|
+
{
|
|
68
|
+
title: 'Fixable entry',
|
|
69
|
+
severity: 'high',
|
|
70
|
+
id: 100,
|
|
71
|
+
hostId: 5,
|
|
72
|
+
hostname: 'host.example.com',
|
|
73
|
+
flags: [],
|
|
74
|
+
detail: {
|
|
75
|
+
remediations: [{ type: 'command', context: ['echo', 'fix'] }],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: 'Not fixable entry',
|
|
80
|
+
severity: 'low',
|
|
81
|
+
id: 101,
|
|
82
|
+
hostId: 5,
|
|
83
|
+
hostname: 'host.example.com',
|
|
84
|
+
flags: [],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
hostId: 6,
|
|
90
|
+
entries: [
|
|
91
|
+
{
|
|
92
|
+
title: 'Another fixable entry',
|
|
93
|
+
severity: 'medium',
|
|
94
|
+
id: 102,
|
|
95
|
+
hostId: 6,
|
|
96
|
+
hostname: 'foo.example.com',
|
|
97
|
+
flags: [],
|
|
98
|
+
detail: {
|
|
99
|
+
remediations: [{ type: 'command', context: ['echo', 'fix2'] }],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
63
106
|
export const reportsWithRemediations = [
|
|
64
107
|
{
|
|
65
108
|
hostId: 5,
|
|
@@ -1,54 +1,159 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Provider } from 'react-redux';
|
|
3
|
+
import configureMockStore from 'redux-mock-store';
|
|
4
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
5
|
+
import '@testing-library/jest-dom';
|
|
2
6
|
|
|
3
7
|
import PreupgradeReports from '../PreupgradeReports';
|
|
8
|
+
import {
|
|
9
|
+
preupgradeReports,
|
|
10
|
+
preupgradeReportsWithFixableEntries,
|
|
11
|
+
} from './PreupgradeReports.fixtures';
|
|
4
12
|
|
|
5
|
-
|
|
13
|
+
jest.mock('foremanReact/components/Pagination', () => {
|
|
14
|
+
const MockPagination = () => <div data-testid="pagination">Pagination</div>;
|
|
15
|
+
return MockPagination;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
jest.mock('../../PreupgradeReportsList/components/images/i_severity-high.svg', () => 'severity-high.svg');
|
|
19
|
+
jest.mock('../../PreupgradeReportsList/components/images/i_severity-med.svg', () => 'severity-med.svg');
|
|
20
|
+
jest.mock('../../PreupgradeReportsList/components/images/i_severity-low.svg', () => 'severity-low.svg');
|
|
21
|
+
|
|
22
|
+
const mockStore = configureMockStore([]);
|
|
6
23
|
|
|
7
24
|
const csrfToken = 'xyz';
|
|
8
25
|
const newJobInvocationUrl = '/job_invocations/new';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
newJobInvocationUrl,
|
|
18
|
-
getPreupgradeReports,
|
|
19
|
-
reportsExpected: true,
|
|
20
|
-
},
|
|
21
|
-
'should render when loaded without reports': {
|
|
22
|
-
loading: false,
|
|
23
|
-
error: {},
|
|
24
|
-
preupgradeReports: [],
|
|
25
|
-
csrfToken,
|
|
26
|
-
newJobInvocationUrl,
|
|
27
|
-
getPreupgradeReports,
|
|
28
|
-
reportsExpected: true,
|
|
29
|
-
},
|
|
30
|
-
'should render when loading': {
|
|
31
|
-
loading: true,
|
|
32
|
-
error: {},
|
|
33
|
-
preupgradeReports: [],
|
|
34
|
-
csrfToken,
|
|
35
|
-
newJobInvocationUrl,
|
|
36
|
-
getPreupgradeReports,
|
|
37
|
-
reportsExpected: false,
|
|
38
|
-
},
|
|
39
|
-
'should render error': {
|
|
40
|
-
loading: false,
|
|
41
|
-
error: {
|
|
42
|
-
statusText: 'Internal server error',
|
|
43
|
-
errorMsg: 'Well, this is embarassing',
|
|
44
|
-
},
|
|
45
|
-
preupgradeReports: [],
|
|
46
|
-
csrfToken,
|
|
47
|
-
newJobInvocationUrl,
|
|
48
|
-
getPreupgradeReports,
|
|
49
|
-
reportsExpected: false,
|
|
50
|
-
},
|
|
26
|
+
|
|
27
|
+
const defaultProps = {
|
|
28
|
+
loading: false,
|
|
29
|
+
error: {},
|
|
30
|
+
preupgradeReports,
|
|
31
|
+
csrfToken,
|
|
32
|
+
newJobInvocationUrl,
|
|
33
|
+
reportsExpected: true,
|
|
51
34
|
};
|
|
52
35
|
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
const renderComponent = (props = {}) =>
|
|
37
|
+
render(
|
|
38
|
+
<Provider store={mockStore({})}>
|
|
39
|
+
<PreupgradeReports {...defaultProps} {...props} />
|
|
40
|
+
</Provider>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const getFixSelectedForm = () =>
|
|
44
|
+
screen.getByRole('button', { name: 'Fix Selected' }).closest('form');
|
|
45
|
+
|
|
46
|
+
describe('PreupgradeReports', () => {
|
|
47
|
+
// withLoadingState wraps content in patternfly-react LoadingState, which
|
|
48
|
+
// delays showing the spinner by 300ms and always schedules that timeout on
|
|
49
|
+
// mount — even when loading=false. Without fake timers the timeout fires
|
|
50
|
+
// after the test unmounts and React logs a setState-on-unmounted warning.
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
jest.useFakeTimers();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
jest.runOnlyPendingTimers();
|
|
57
|
+
jest.useRealTimers();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('renders report entries when loaded with reports', () => {
|
|
61
|
+
renderComponent();
|
|
62
|
+
|
|
63
|
+
expect(screen.getByText('Fix me!')).toBeInTheDocument();
|
|
64
|
+
expect(screen.getByText('I am broken too')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('Octocat is not happy')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders empty state when loaded without reports', () => {
|
|
69
|
+
renderComponent({ preupgradeReports: [] });
|
|
70
|
+
|
|
71
|
+
expect(
|
|
72
|
+
screen.getByRole('heading', {
|
|
73
|
+
name: 'No Preupgrade Report Available',
|
|
74
|
+
level: 5,
|
|
75
|
+
})
|
|
76
|
+
).toBeInTheDocument();
|
|
77
|
+
expect(
|
|
78
|
+
screen.getByText(
|
|
79
|
+
'The preupgrade report could not be generated, check the job details for the reason'
|
|
80
|
+
)
|
|
81
|
+
).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders loading state while data is being fetched', () => {
|
|
85
|
+
renderComponent({
|
|
86
|
+
loading: true,
|
|
87
|
+
preupgradeReports: [],
|
|
88
|
+
reportsExpected: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// LoadingState renders nothing until its 300ms timeout elapses.
|
|
92
|
+
jest.advanceTimersByTime(300);
|
|
93
|
+
|
|
94
|
+
expect(screen.getByText('Loading')).toBeInTheDocument();
|
|
95
|
+
expect(
|
|
96
|
+
screen.queryByRole('heading', {
|
|
97
|
+
name: 'No Preupgrade Report Available',
|
|
98
|
+
})
|
|
99
|
+
).not.toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('renders error empty state when data retrieval fails', () => {
|
|
103
|
+
renderComponent({
|
|
104
|
+
error: {
|
|
105
|
+
statusText: 'Internal server error',
|
|
106
|
+
errorMsg: 'Unexpected error',
|
|
107
|
+
},
|
|
108
|
+
preupgradeReports: [],
|
|
109
|
+
reportsExpected: false,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(
|
|
113
|
+
screen.getByRole('heading', {
|
|
114
|
+
name: 'Could not retrieve data: Internal server error - Unexpected error',
|
|
115
|
+
level: 5,
|
|
116
|
+
})
|
|
117
|
+
).toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('disables Fix Selected when no fixable entries are selected', () => {
|
|
121
|
+
renderComponent({ preupgradeReports: preupgradeReportsWithFixableEntries });
|
|
122
|
+
|
|
123
|
+
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeDisabled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('enables Fix Selected and passes selected entry ids when a fixable entry is selected', () => {
|
|
127
|
+
renderComponent({ preupgradeReports: preupgradeReportsWithFixableEntries });
|
|
128
|
+
|
|
129
|
+
const [, fixableEntryCheckbox] = screen.getAllByRole('checkbox');
|
|
130
|
+
|
|
131
|
+
fireEvent.click(fixableEntryCheckbox);
|
|
132
|
+
|
|
133
|
+
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeEnabled();
|
|
134
|
+
expect(
|
|
135
|
+
getFixSelectedForm().querySelector('input[name="inputs[remediation_ids]"]')
|
|
136
|
+
).toHaveValue('100');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('selects all fixable entries when header checkbox is clicked', () => {
|
|
140
|
+
renderComponent({ preupgradeReports: preupgradeReportsWithFixableEntries });
|
|
141
|
+
|
|
142
|
+
const [selectAllCheckbox] = screen.getAllByRole('checkbox');
|
|
143
|
+
|
|
144
|
+
fireEvent.click(selectAllCheckbox);
|
|
145
|
+
|
|
146
|
+
expect(screen.getByRole('button', { name: 'Fix Selected' })).toBeEnabled();
|
|
147
|
+
|
|
148
|
+
const form = getFixSelectedForm();
|
|
149
|
+
|
|
150
|
+
expect(
|
|
151
|
+
form.querySelector('input[name="inputs[remediation_ids]"]')
|
|
152
|
+
).toHaveValue('100,102');
|
|
153
|
+
expect(
|
|
154
|
+
[...form.querySelectorAll('input[name="host_ids[]"]')].map(
|
|
155
|
+
input => input.value
|
|
156
|
+
)
|
|
157
|
+
).toEqual(['5', '6']);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
2
4
|
|
|
3
5
|
import NoReports from './NoReports';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
'should render when reports expected'
|
|
7
|
-
reportsExpected
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
describe('NoReports', () => {
|
|
8
|
+
it('should render when reports expected', () => {
|
|
9
|
+
render(<NoReports reportsExpected />);
|
|
10
|
+
|
|
11
|
+
expect(screen.getByText('No Preupgrade Report Available')).toBeInTheDocument();
|
|
12
|
+
expect(
|
|
13
|
+
screen.getByText(
|
|
14
|
+
'The preupgrade report could not be generated, check the job details for the reason'
|
|
15
|
+
)
|
|
16
|
+
).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should render when reports not expected', () => {
|
|
20
|
+
render(<NoReports reportsExpected={false} />);
|
|
21
|
+
|
|
22
|
+
expect(screen.getByText('No Preupgrade Report Available')).toBeInTheDocument();
|
|
23
|
+
expect(
|
|
24
|
+
screen.getByText(
|
|
25
|
+
'The preupgrade report will be available after the job finishes'
|
|
26
|
+
)
|
|
27
|
+
).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -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
|
+
};
|