foreman_remote_execution 16.2.1 → 16.2.2
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/lib/foreman_remote_execution/version.rb +1 -1
- data/webpack/JobInvocationDetail/CheckboxesActions.js +39 -30
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +0 -1
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +156 -170
- data/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +27 -32
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +10 -2
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +46 -30
- data/webpack/JobInvocationDetail/index.js +13 -2
- data/webpack/react_app/components/RegistrationExtension/RexInterface.js +5 -19
- data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +19 -3
- metadata +2 -3
- data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3241ac38107bfc2d42a461621df7f94bcd05220e7443572cc9b16c6c50986316
|
4
|
+
data.tar.gz: 27e015c4f820e21d746f703f1619c40ec73ca88b089846232e2b256841a9c24e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b92135ebe56b899799ffaf114de5023a4eda5b16b282bf6149387be0caa603968c8639d4a88490f57717f186ed6e4297bbcb04fa93b6a9072abb5320962a853
|
7
|
+
data.tar.gz: 336bf208eb1afbcfe7858b51e703e9e1d5df0f536fcec9eb4d619ede3822c015f4f41fbccc0284875cc94733f23209e5b3699c9331aa87b522b3d9dd02a368d0
|
@@ -14,23 +14,21 @@ import {
|
|
14
14
|
} from '@patternfly/react-icons';
|
15
15
|
import axios from 'axios';
|
16
16
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
17
|
-
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
18
17
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
19
18
|
import { addToast } from 'foremanReact/components/ToastsList';
|
20
19
|
import PropTypes from 'prop-types';
|
21
|
-
import React, { useState } from 'react';
|
20
|
+
import React, { useEffect, useState } from 'react';
|
22
21
|
import { useDispatch, useSelector } from 'react-redux';
|
23
22
|
|
24
23
|
import {
|
25
24
|
DIRECT_OPEN_HOST_LIMIT,
|
26
|
-
MAX_HOSTS_API_SIZE,
|
27
25
|
templateInvocationPageUrl,
|
28
26
|
} from './JobInvocationConstants';
|
29
27
|
import {
|
30
28
|
selectHasPermission,
|
31
29
|
selectTaskCancelable,
|
32
30
|
} from './JobInvocationSelectors';
|
33
|
-
import OpenAllInvocationsModal
|
31
|
+
import OpenAllInvocationsModal from './OpenAllInvocationsModal';
|
34
32
|
|
35
33
|
/* eslint-disable camelcase */
|
36
34
|
const ActionsKebab = ({
|
@@ -73,7 +71,7 @@ const ActionsKebab = ({
|
|
73
71
|
onClick={() => handleOpenHosts('failed')}
|
74
72
|
isDisabled={failedCount === 0}
|
75
73
|
>
|
76
|
-
{sprintf(__('Open all failed runs (%s)'), failedCount)}
|
74
|
+
{sprintf(__('Open all failed runs on this page (%s)'), failedCount)}
|
77
75
|
</DropdownItem>,
|
78
76
|
];
|
79
77
|
|
@@ -104,17 +102,18 @@ const ActionsKebab = ({
|
|
104
102
|
|
105
103
|
export const CheckboxesActions = ({
|
106
104
|
selectedIds,
|
107
|
-
|
105
|
+
allJobs,
|
108
106
|
jobID,
|
109
107
|
filter,
|
110
108
|
bulkParams,
|
109
|
+
setShowAlert,
|
111
110
|
}) => {
|
112
111
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
113
|
-
const [showAlert, setShowAlert] = useState(false);
|
114
112
|
const [isOpenFailed, setIsOpenFailed] = useState(false);
|
115
113
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
116
114
|
const isTaskCancelable = useSelector(selectTaskCancelable);
|
117
115
|
const dispatch = useDispatch();
|
116
|
+
const [toBeOpened, setToBeOpened] = useState([]);
|
118
117
|
|
119
118
|
const hasCreatePermission = useSelector(
|
120
119
|
selectHasPermission('create_job_invocations')
|
@@ -129,19 +128,14 @@ export const CheckboxesActions = ({
|
|
129
128
|
: '';
|
130
129
|
const combinedQuery = `${bulkParams}${filterQuery}`;
|
131
130
|
|
132
|
-
const
|
133
|
-
'get',
|
134
|
-
foremanUrl(`/api/job_invocations/${jobID}/hosts`),
|
135
|
-
{
|
136
|
-
params: {
|
137
|
-
per_page: MAX_HOSTS_API_SIZE,
|
138
|
-
search: `job_invocation.result = failed`,
|
139
|
-
},
|
140
|
-
skip: failedCount === 0,
|
141
|
-
}
|
142
|
-
);
|
131
|
+
const [failedHosts, setFailedHosts] = useState([]);
|
143
132
|
|
144
|
-
|
133
|
+
useEffect(() => {
|
134
|
+
const failed = allJobs.filter(i => i.job_status === 'error');
|
135
|
+
setFailedHosts(failed);
|
136
|
+
}, [allJobs]);
|
137
|
+
|
138
|
+
const failedCount = failedHosts.length;
|
145
139
|
|
146
140
|
const openLink = url => {
|
147
141
|
const newWin = window.open(url);
|
@@ -151,24 +145,35 @@ export const CheckboxesActions = ({
|
|
151
145
|
}
|
152
146
|
};
|
153
147
|
|
148
|
+
const openTabs = tabs => {
|
149
|
+
tabs.forEach(open => {
|
150
|
+
const openId = open.id ?? open;
|
151
|
+
openLink(templateInvocationPageUrl(openId, jobID));
|
152
|
+
});
|
153
|
+
};
|
154
|
+
|
154
155
|
const handleOpenHosts = async (type = 'all') => {
|
155
156
|
if (type === 'failed') {
|
156
157
|
if (failedCount <= DIRECT_OPEN_HOST_LIMIT) {
|
157
|
-
failedHosts
|
158
|
-
openLink(templateInvocationPageUrl(host.id, jobID))
|
159
|
-
);
|
158
|
+
openTabs(failedHosts);
|
160
159
|
return;
|
161
160
|
}
|
161
|
+
setToBeOpened(failedHosts);
|
162
162
|
setIsOpenFailed(true);
|
163
163
|
setIsModalOpen(true);
|
164
164
|
return;
|
165
165
|
}
|
166
166
|
|
167
|
+
if (selectedIds.length === 0) {
|
168
|
+
selectedIds = allJobs;
|
169
|
+
}
|
170
|
+
|
167
171
|
if (selectedIds.length <= DIRECT_OPEN_HOST_LIMIT) {
|
168
|
-
selectedIds
|
172
|
+
openTabs(selectedIds);
|
169
173
|
return;
|
170
174
|
}
|
171
175
|
|
176
|
+
setToBeOpened(selectedIds);
|
172
177
|
setIsOpenFailed(false);
|
173
178
|
setIsModalOpen(true);
|
174
179
|
};
|
@@ -237,13 +242,19 @@ export const CheckboxesActions = ({
|
|
237
242
|
<Button
|
238
243
|
aria-label="open all template invocations in new tab"
|
239
244
|
className="open-all-button"
|
240
|
-
isDisabled={
|
245
|
+
isDisabled={allJobs.length === 0}
|
241
246
|
isInline
|
242
247
|
onClick={() => handleOpenHosts('all')}
|
243
248
|
ouiaId="template-invocation-new-tab-button"
|
244
249
|
variant="link"
|
245
250
|
>
|
246
|
-
<Tooltip
|
251
|
+
<Tooltip
|
252
|
+
content={
|
253
|
+
selectedIds.length === 0
|
254
|
+
? __('Open all rows of the table in new tabs')
|
255
|
+
: __('Open selected in new tab')
|
256
|
+
}
|
257
|
+
>
|
247
258
|
<OutlinedWindowRestoreIcon />
|
248
259
|
</Tooltip>
|
249
260
|
</Button>
|
@@ -281,16 +292,13 @@ export const CheckboxesActions = ({
|
|
281
292
|
isDropdownOpen={isDropdownOpen}
|
282
293
|
setIsDropdownOpen={setIsDropdownOpen}
|
283
294
|
/>
|
284
|
-
{showAlert && <PopupAlert setShowAlert={setShowAlert} />}
|
285
295
|
<OpenAllInvocationsModal
|
286
296
|
isOpen={isModalOpen}
|
287
297
|
onClose={() => setIsModalOpen(false)}
|
288
298
|
failedCount={failedCount}
|
289
|
-
failedHosts={failedHosts}
|
290
|
-
jobID={jobID}
|
291
299
|
isOpenFailed={isOpenFailed}
|
292
|
-
setShowAlert={setShowAlert}
|
293
300
|
selectedIds={selectedIds}
|
301
|
+
confirmCallback={() => openTabs(toBeOpened)}
|
294
302
|
/>
|
295
303
|
</>
|
296
304
|
);
|
@@ -314,10 +322,11 @@ ActionsKebab.defaultProps = {
|
|
314
322
|
|
315
323
|
CheckboxesActions.propTypes = {
|
316
324
|
selectedIds: PropTypes.array.isRequired,
|
317
|
-
|
325
|
+
allJobs: PropTypes.array.isRequired,
|
318
326
|
jobID: PropTypes.string.isRequired,
|
319
327
|
bulkParams: PropTypes.string,
|
320
328
|
filter: PropTypes.string,
|
329
|
+
setShowAlert: PropTypes.func.isRequired,
|
321
330
|
};
|
322
331
|
|
323
332
|
CheckboxesActions.defaultProps = {
|
@@ -17,7 +17,6 @@ export const GET_REPORT_TEMPLATES = 'GET_REPORT_TEMPLATES';
|
|
17
17
|
export const GET_REPORT_TEMPLATE_INPUTS = 'GET_REPORT_TEMPLATE_INPUTS';
|
18
18
|
export const JOB_INVOCATION_HOSTS = 'JOB_INVOCATION_HOSTS';
|
19
19
|
export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
|
20
|
-
export const MAX_HOSTS_API_SIZE = 100;
|
21
20
|
export const DIRECT_OPEN_HOST_LIMIT = 3;
|
22
21
|
export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS';
|
23
22
|
export const currentPermissionsUrl = foremanUrl(
|
@@ -8,9 +8,10 @@ import {
|
|
8
8
|
ToolbarItem,
|
9
9
|
} from '@patternfly/react-core';
|
10
10
|
import { ExpandableRowContent, Tbody, Td, Tr } from '@patternfly/react-table';
|
11
|
+
import { useDispatch } from 'react-redux';
|
12
|
+
import { APIActions } from 'foremanReact/redux/API';
|
11
13
|
import { translate as __ } from 'foremanReact/common/I18n';
|
12
14
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
13
|
-
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
14
15
|
import { RowSelectTd } from 'foremanReact/components/HostsIndex/RowSelectTd';
|
15
16
|
import SelectAllCheckbox from 'foremanReact/components/PF4/TableIndexPage/Table/SelectAllCheckbox';
|
16
17
|
import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table';
|
@@ -26,22 +27,20 @@ import PropTypes from 'prop-types';
|
|
26
27
|
import React, { useEffect, useMemo, useState, useRef } from 'react';
|
27
28
|
import { FormattedMessage } from 'react-intl';
|
28
29
|
import { useHistory } from 'react-router-dom';
|
29
|
-
import
|
30
|
+
import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
|
30
31
|
import { CheckboxesActions } from './CheckboxesActions';
|
31
32
|
import DropdownFilter from './DropdownFilter';
|
32
33
|
import Columns, {
|
33
34
|
JOB_INVOCATION_HOSTS,
|
34
|
-
MAX_HOSTS_API_SIZE,
|
35
|
-
STATUS_UPPERCASE,
|
36
35
|
LIST_TEMPLATE_INVOCATIONS,
|
36
|
+
STATUS_UPPERCASE,
|
37
37
|
ALL_JOB_HOSTS,
|
38
38
|
} from './JobInvocationConstants';
|
39
|
-
import { PopupAlert } from './OpenAllInvocationsModal';
|
40
39
|
import { TemplateInvocation } from './TemplateInvocation';
|
41
40
|
import { RowActions } from './TemplateInvocationComponents/TemplateActionButtons';
|
41
|
+
import { PopupAlert } from './OpenAllInvocationsModal';
|
42
42
|
|
43
43
|
const JobInvocationHostTable = ({
|
44
|
-
failedCount,
|
45
44
|
id,
|
46
45
|
initialFilter,
|
47
46
|
onFilterUpdate,
|
@@ -50,19 +49,29 @@ const JobInvocationHostTable = ({
|
|
50
49
|
}) => {
|
51
50
|
const columns = Columns();
|
52
51
|
const columnNamesKeys = Object.keys(columns);
|
53
|
-
|
52
|
+
|
54
53
|
const history = useHistory();
|
55
|
-
const
|
54
|
+
const dispatch = useDispatch();
|
55
|
+
|
56
|
+
const [showAlert, setShowAlert] = useState(false);
|
57
|
+
|
58
|
+
const [apiResponse, setApiResponse] = useState([]);
|
59
|
+
const [status, setStatus] = useState(STATUS_UPPERCASE.PENDING);
|
60
|
+
const [allHostsIds, setAllHostsIds] = useState([]);
|
61
|
+
|
62
|
+
// Expansive items
|
56
63
|
const [expandedHost, setExpandedHost] = useState([]);
|
57
64
|
const prevStatusLabel = useRef(statusLabel);
|
58
65
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
66
|
+
const isHostExpanded = host => expandedHost.includes(host);
|
67
|
+
const setHostExpanded = (host, isExpanding = true) =>
|
68
|
+
setExpandedHost(prevExpanded => {
|
69
|
+
const otherExpandedHosts = prevExpanded.filter(h => h !== host);
|
70
|
+
return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts;
|
71
|
+
});
|
65
72
|
|
73
|
+
// Page table params
|
74
|
+
// Parse URL
|
66
75
|
const {
|
67
76
|
searchParam: urlSearchQuery = '',
|
68
77
|
page: urlPage,
|
@@ -70,10 +79,27 @@ const JobInvocationHostTable = ({
|
|
70
79
|
order: urlOrder,
|
71
80
|
} = useUrlParams();
|
72
81
|
|
73
|
-
const
|
74
|
-
|
75
|
-
|
76
|
-
|
82
|
+
const { perPage: foremanPerPage } = useForemanSettings();
|
83
|
+
|
84
|
+
// default
|
85
|
+
const defaultParams = useMemo(
|
86
|
+
() => ({
|
87
|
+
page: urlPage ? Number(urlPage) : 1,
|
88
|
+
per_page: urlPerPage || Number(urlPerPage) || foremanPerPage,
|
89
|
+
order: urlOrder || '',
|
90
|
+
}),
|
91
|
+
[urlPage, urlPerPage, foremanPerPage, urlOrder]
|
92
|
+
);
|
93
|
+
|
94
|
+
// Page row for table
|
95
|
+
const { pageRowCount } = getPageStats({
|
96
|
+
total: apiResponse?.total || 0,
|
97
|
+
page: apiResponse?.page || urlPage || 1,
|
98
|
+
perPage: apiResponse?.per_page || urlPerPage || 0,
|
99
|
+
});
|
100
|
+
|
101
|
+
// Search filter
|
102
|
+
const constructFilter = (filter = initialFilter, search = urlSearchQuery) => {
|
77
103
|
const dropdownFilterClause =
|
78
104
|
filter && filter !== 'all_statuses'
|
79
105
|
? `job_invocation.result = ${filter}`
|
@@ -85,68 +111,93 @@ const JobInvocationHostTable = ({
|
|
85
111
|
.join(' AND ');
|
86
112
|
};
|
87
113
|
|
88
|
-
const
|
89
|
-
()
|
90
|
-
|
91
|
-
...(urlPerPage ? { per_page: Number(urlPerPage) } : {}),
|
92
|
-
...(urlOrder ? { order: urlOrder } : {}),
|
93
|
-
}),
|
94
|
-
[urlPage, urlPerPage, urlOrder]
|
95
|
-
);
|
114
|
+
const handleResponse = (data, key) => {
|
115
|
+
if (key === JOB_INVOCATION_HOSTS) {
|
116
|
+
const ids = data.data.results.map(i => i.id);
|
96
117
|
|
97
|
-
|
98
|
-
|
99
|
-
search: defaultParams.search,
|
100
|
-
},
|
101
|
-
key: LIST_TEMPLATE_INVOCATIONS,
|
102
|
-
});
|
103
|
-
const { response, status, setAPIOptions } = useAPI(
|
104
|
-
'get',
|
105
|
-
`/api/job_invocations/${id}/hosts`,
|
106
|
-
{
|
107
|
-
params: defaultParams,
|
118
|
+
setApiResponse(data.data);
|
119
|
+
setAllHostsIds(ids);
|
108
120
|
}
|
109
|
-
);
|
110
121
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
122
|
+
setStatus(STATUS_UPPERCASE.RESOLVED);
|
123
|
+
};
|
124
|
+
|
125
|
+
// Call hosts data with params
|
126
|
+
const makeApiCall = (requestParams, callParams = {}) => {
|
127
|
+
dispatch(
|
128
|
+
APIActions.get({
|
129
|
+
key: callParams.key ?? ALL_JOB_HOSTS,
|
130
|
+
url: callParams.url ?? `/api/job_invocations/${id}/hosts`,
|
131
|
+
params: requestParams,
|
132
|
+
handleSuccess: data => handleResponse(data, callParams.key),
|
133
|
+
handleError: () => setStatus(STATUS_UPPERCASE.ERROR),
|
134
|
+
errorToast: ({ response }) =>
|
135
|
+
response?.data?.error?.full_messages?.[0] || response,
|
136
|
+
})
|
137
|
+
);
|
116
138
|
};
|
117
139
|
|
118
|
-
const
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
140
|
+
const filterApiCall = newAPIOptions => {
|
141
|
+
const newParams = newAPIOptions?.params ?? newAPIOptions ?? {};
|
142
|
+
|
143
|
+
const filterSearch = constructFilter(
|
144
|
+
initialFilter,
|
145
|
+
newParams.search ?? urlSearchQuery
|
146
|
+
);
|
147
|
+
|
148
|
+
const finalParams = {
|
149
|
+
...defaultParams,
|
150
|
+
...newParams,
|
151
|
+
};
|
152
|
+
|
153
|
+
if (filterSearch !== '') {
|
154
|
+
finalParams.search = filterSearch;
|
124
155
|
}
|
125
|
-
);
|
126
156
|
|
157
|
+
makeApiCall(finalParams, { key: JOB_INVOCATION_HOSTS });
|
158
|
+
|
159
|
+
const urlSearchParams = new URLSearchParams(window.location.search);
|
160
|
+
|
161
|
+
['page', 'per_page', 'order'].forEach(key => {
|
162
|
+
if (finalParams[key]) urlSearchParams.set(key, finalParams[key]);
|
163
|
+
});
|
164
|
+
|
165
|
+
history.push({ search: urlSearchParams.toString() });
|
166
|
+
};
|
167
|
+
|
168
|
+
// Filter change
|
169
|
+
const handleFilterChange = newFilter => {
|
170
|
+
onFilterUpdate(newFilter);
|
171
|
+
};
|
172
|
+
|
173
|
+
// Effects
|
174
|
+
// run after mount
|
127
175
|
useEffect(() => {
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
176
|
+
// Job Invo template load
|
177
|
+
makeApiCall(
|
178
|
+
{},
|
179
|
+
{
|
180
|
+
url: `/job_invocations/${id}/hosts`,
|
181
|
+
key: LIST_TEMPLATE_INVOCATIONS,
|
182
|
+
}
|
183
|
+
);
|
184
|
+
|
185
|
+
if (initialFilter === '') {
|
186
|
+
onFilterUpdate('all_statuses');
|
133
187
|
}
|
188
|
+
|
134
189
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
135
|
-
}, [
|
190
|
+
}, []);
|
136
191
|
|
137
192
|
useEffect(() => {
|
138
|
-
if (
|
139
|
-
setAllPagesResponse(allResponse.results);
|
140
|
-
}
|
141
|
-
}, [allResponse]);
|
193
|
+
if (initialFilter !== '') filterApiCall();
|
142
194
|
|
143
|
-
useEffect(() => {
|
144
195
|
if (statusLabel !== prevStatusLabel.current) {
|
145
|
-
setAPIOptions(prevOptions => ({ ...prevOptions }));
|
146
196
|
prevStatusLabel.current = statusLabel;
|
197
|
+
filterApiCall();
|
147
198
|
}
|
148
199
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
149
|
-
}, [statusLabel]);
|
200
|
+
}, [initialFilter, statusLabel, id]);
|
150
201
|
|
151
202
|
const {
|
152
203
|
updateSearchQuery: updateSearchQueryBulk,
|
@@ -155,11 +206,11 @@ const JobInvocationHostTable = ({
|
|
155
206
|
exclusionSet,
|
156
207
|
...selectAllOptions
|
157
208
|
} = useBulkSelect({
|
158
|
-
results:
|
209
|
+
results: apiResponse?.results,
|
159
210
|
metadata: {
|
160
|
-
total:
|
161
|
-
page:
|
162
|
-
selectable:
|
211
|
+
total: apiResponse?.total,
|
212
|
+
page: apiResponse?.page,
|
213
|
+
selectable: apiResponse?.subtotal,
|
163
214
|
},
|
164
215
|
initialSearchQuery: urlSearchQuery,
|
165
216
|
});
|
@@ -175,35 +226,11 @@ const JobInvocationHostTable = ({
|
|
175
226
|
isSelected,
|
176
227
|
} = selectAllOptions;
|
177
228
|
|
178
|
-
const allHostIds = allPagesResponse?.map(item => item.id) || [];
|
179
229
|
const selectedIds =
|
180
230
|
areAllRowsSelected() || exclusionSet.size > 0
|
181
|
-
?
|
231
|
+
? allHostsIds.filter(hostId => !exclusionSet.has(hostId))
|
182
232
|
: Array.from(inclusionSet);
|
183
233
|
|
184
|
-
const { pageRowCount } = getPageStats({
|
185
|
-
total: response?.total || 0,
|
186
|
-
page: response?.page || urlPage || 1,
|
187
|
-
perPage: response?.per_page || urlPerPage || 0,
|
188
|
-
});
|
189
|
-
|
190
|
-
const selectionToolbar = (
|
191
|
-
<ToolbarItem key="selectAll">
|
192
|
-
<SelectAllCheckbox
|
193
|
-
{...{
|
194
|
-
selectAll,
|
195
|
-
selectPage,
|
196
|
-
selectNone,
|
197
|
-
selectedCount,
|
198
|
-
pageRowCount,
|
199
|
-
}}
|
200
|
-
totalCount={response?.total}
|
201
|
-
areAllRowsOnPageSelected={areAllRowsOnPageSelected()}
|
202
|
-
areAllRowsSelected={areAllRowsSelected()}
|
203
|
-
/>
|
204
|
-
</ToolbarItem>
|
205
|
-
);
|
206
|
-
|
207
234
|
const controller = 'hosts';
|
208
235
|
const memoDefaultSearchProps = useMemo(
|
209
236
|
() => getControllerSearchProps(controller),
|
@@ -213,70 +240,40 @@ const JobInvocationHostTable = ({
|
|
213
240
|
`/${controller}/auto_complete_search`
|
214
241
|
);
|
215
242
|
|
216
|
-
const wrapSetSelectedFilter = newFilter => {
|
217
|
-
setSelectedFilter(newFilter);
|
218
|
-
onFilterUpdate(newFilter);
|
219
|
-
|
220
|
-
const filterSearch = constructFilter(newFilter, urlSearchQuery);
|
221
|
-
|
222
|
-
const newParams = {
|
223
|
-
...defaultParams,
|
224
|
-
page: 1,
|
225
|
-
};
|
226
|
-
|
227
|
-
if (filterSearch !== '') {
|
228
|
-
newParams.search = filterSearch;
|
229
|
-
}
|
230
|
-
|
231
|
-
setAPIOptions(prev => ({ ...prev, params: newParams }));
|
232
|
-
|
233
|
-
const urlSearchParams = new URLSearchParams(window.location.search);
|
234
|
-
urlSearchParams.set('page', '1');
|
235
|
-
history.push({ search: urlSearchParams.toString() });
|
236
|
-
};
|
237
|
-
|
238
|
-
const wrapSetAPIOptions = newAPIOptions => {
|
239
|
-
const newParams = newAPIOptions?.params ?? newAPIOptions ?? {};
|
240
|
-
|
241
|
-
const filterSearch = constructFilter(
|
242
|
-
selectedFilter,
|
243
|
-
newParams.search ?? urlSearchQuery
|
244
|
-
);
|
245
|
-
|
246
|
-
const mergedParams = {
|
247
|
-
...defaultParams,
|
248
|
-
...newParams,
|
249
|
-
};
|
250
|
-
|
251
|
-
if (filterSearch !== '') {
|
252
|
-
mergedParams.search = filterSearch;
|
253
|
-
} else if ('search' in mergedParams) {
|
254
|
-
delete mergedParams.search;
|
255
|
-
}
|
256
|
-
|
257
|
-
setAPIOptions(prev => ({ ...prev, params: mergedParams }));
|
258
|
-
|
259
|
-
const { search: _search, ...paramsForUrl } = mergedParams;
|
260
|
-
const uri = new URI();
|
261
|
-
uri.setSearch(paramsForUrl);
|
262
|
-
history.push({ search: uri.search() });
|
263
|
-
};
|
264
|
-
|
265
243
|
const combinedResponse = {
|
266
244
|
response: {
|
267
245
|
search: urlSearchQuery,
|
268
246
|
can_create: false,
|
269
|
-
results:
|
270
|
-
total:
|
247
|
+
results: apiResponse?.results || [],
|
248
|
+
total: apiResponse?.total || 0,
|
271
249
|
per_page: defaultParams?.perPage,
|
272
250
|
page: defaultParams?.page,
|
273
|
-
subtotal:
|
274
|
-
message:
|
251
|
+
subtotal: apiResponse?.subtotal || 0,
|
252
|
+
message: apiResponse?.message || 'error',
|
275
253
|
},
|
276
254
|
status,
|
277
|
-
setAPIOptions:
|
255
|
+
setAPIOptions: filterApiCall,
|
278
256
|
};
|
279
257
|
|
258
|
+
const results = apiResponse.results ?? [];
|
259
|
+
|
260
|
+
const selectionToolbar = (
|
261
|
+
<ToolbarItem key="selectAll">
|
262
|
+
<SelectAllCheckbox
|
263
|
+
{...{
|
264
|
+
selectAll,
|
265
|
+
selectPage,
|
266
|
+
selectNone,
|
267
|
+
selectedCount,
|
268
|
+
pageRowCount,
|
269
|
+
}}
|
270
|
+
totalCount={apiResponse?.total}
|
271
|
+
areAllRowsOnPageSelected={areAllRowsOnPageSelected()}
|
272
|
+
areAllRowsSelected={areAllRowsSelected()}
|
273
|
+
/>
|
274
|
+
</ToolbarItem>
|
275
|
+
);
|
276
|
+
|
280
277
|
const customEmptyState = (
|
281
278
|
<Tr ouiaId="table-empty">
|
282
279
|
<Td colSpan={100}>
|
@@ -314,22 +311,11 @@ const JobInvocationHostTable = ({
|
|
314
311
|
</Tr>
|
315
312
|
);
|
316
313
|
|
317
|
-
const { results = [] } = response;
|
318
|
-
|
319
|
-
const isHostExpanded = host => expandedHost.includes(host);
|
320
|
-
const setHostExpanded = (host, isExpanding = true) =>
|
321
|
-
setExpandedHost(prevExpanded => {
|
322
|
-
const otherExpandedHosts = prevExpanded.filter(h => h !== host);
|
323
|
-
return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts;
|
324
|
-
});
|
325
|
-
const [showAlert, setShowAlert] = useState(false);
|
326
|
-
|
327
314
|
return (
|
328
315
|
<>
|
329
316
|
{showAlert && <PopupAlert setShowAlert={setShowAlert} />}
|
330
317
|
<TableIndexPage
|
331
318
|
apiUrl=""
|
332
|
-
apiOptions={apiOptions}
|
333
319
|
customSearchProps={memoDefaultSearchProps}
|
334
320
|
controller="hosts"
|
335
321
|
creatable={false}
|
@@ -338,16 +324,17 @@ const JobInvocationHostTable = ({
|
|
338
324
|
customToolbarItems={[
|
339
325
|
<DropdownFilter
|
340
326
|
key="dropdown-filter"
|
341
|
-
dropdownFilter={
|
342
|
-
setDropdownFilter={
|
327
|
+
dropdownFilter={initialFilter}
|
328
|
+
setDropdownFilter={handleFilterChange}
|
343
329
|
/>,
|
344
330
|
<CheckboxesActions
|
345
331
|
bulkParams={selectedCount > 0 ? fetchBulkParams() : null}
|
346
332
|
selectedIds={selectedIds}
|
347
|
-
|
333
|
+
allJobs={results}
|
348
334
|
jobID={id}
|
349
335
|
key="checkboxes-actions"
|
350
|
-
filter={
|
336
|
+
filter={initialFilter}
|
337
|
+
setShowAlert={setShowAlert}
|
351
338
|
/>,
|
352
339
|
]}
|
353
340
|
selectionToolbar={selectionToolbar}
|
@@ -361,21 +348,21 @@ const JobInvocationHostTable = ({
|
|
361
348
|
: null
|
362
349
|
}
|
363
350
|
params={{
|
364
|
-
page:
|
365
|
-
per_page:
|
351
|
+
page: defaultParams.page || Number(urlPage),
|
352
|
+
per_page: defaultParams.per_page || Number(urlPerPage),
|
366
353
|
order: urlOrder,
|
367
354
|
}}
|
368
|
-
page={
|
369
|
-
perPage={
|
370
|
-
setParams={
|
371
|
-
itemCount={
|
355
|
+
page={defaultParams.page || Number(urlPage)}
|
356
|
+
perPage={defaultParams.per_page || Number(urlPerPage)}
|
357
|
+
setParams={filterApiCall}
|
358
|
+
itemCount={apiResponse?.subtotal}
|
372
359
|
results={results}
|
373
360
|
url=""
|
374
361
|
showCheckboxes
|
375
362
|
refreshData={() => {}}
|
376
363
|
errorMessage={
|
377
|
-
status === STATUS_UPPERCASE.ERROR &&
|
378
|
-
?
|
364
|
+
status === STATUS_UPPERCASE.ERROR && apiResponse?.message
|
365
|
+
? apiResponse.message
|
379
366
|
: null
|
380
367
|
}
|
381
368
|
isPending={status === STATUS_UPPERCASE.PENDING}
|
@@ -439,7 +426,6 @@ const JobInvocationHostTable = ({
|
|
439
426
|
JobInvocationHostTable.propTypes = {
|
440
427
|
id: PropTypes.string.isRequired,
|
441
428
|
targeting: PropTypes.object.isRequired,
|
442
|
-
failedCount: PropTypes.number.isRequired,
|
443
429
|
initialFilter: PropTypes.string.isRequired,
|
444
430
|
statusLabel: PropTypes.string,
|
445
431
|
onFilterUpdate: PropTypes.func,
|
@@ -7,10 +7,6 @@ import {
|
|
7
7
|
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
8
8
|
import PropTypes from 'prop-types';
|
9
9
|
import React from 'react';
|
10
|
-
import {
|
11
|
-
templateInvocationPageUrl,
|
12
|
-
MAX_HOSTS_API_SIZE,
|
13
|
-
} from './JobInvocationConstants';
|
14
10
|
|
15
11
|
export const PopupAlert = ({ setShowAlert }) => (
|
16
12
|
<Alert
|
@@ -25,21 +21,35 @@ export const PopupAlert = ({ setShowAlert }) => (
|
|
25
21
|
|
26
22
|
const OpenAllInvocationsModal = ({
|
27
23
|
failedCount,
|
28
|
-
failedHosts,
|
29
24
|
isOpen,
|
30
25
|
isOpenFailed,
|
31
|
-
jobID,
|
32
26
|
onClose,
|
33
|
-
setShowAlert,
|
34
27
|
selectedIds,
|
28
|
+
confirmCallback,
|
35
29
|
}) => {
|
36
|
-
const modalText =
|
30
|
+
const modalText = () => {
|
31
|
+
if (isOpenFailed) return 'failed';
|
32
|
+
if (selectedIds.length > 0) return 'selected';
|
33
|
+
return 'current page';
|
34
|
+
};
|
37
35
|
|
38
|
-
const
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
const selectedText = () => {
|
37
|
+
if (isOpenFailed) {
|
38
|
+
return (
|
39
|
+
<>
|
40
|
+
{__('The number of failed invocations is:')} <b>{failedCount}</b>
|
41
|
+
</>
|
42
|
+
);
|
42
43
|
}
|
44
|
+
if (selectedIds.length > 0) {
|
45
|
+
return (
|
46
|
+
<>
|
47
|
+
{__('The number of selected invocations is:')}{' '}
|
48
|
+
<b>{selectedIds.length}</b>
|
49
|
+
</>
|
50
|
+
);
|
51
|
+
}
|
52
|
+
return <></>;
|
43
53
|
};
|
44
54
|
|
45
55
|
return (
|
@@ -48,7 +58,7 @@ const OpenAllInvocationsModal = ({
|
|
48
58
|
isOpen={isOpen}
|
49
59
|
onClose={onClose}
|
50
60
|
ouiaId="template-invocation-new-tab-modal"
|
51
|
-
title={sprintf(__('Open all %s invocations in new tabs'), modalText)}
|
61
|
+
title={sprintf(__('Open all %s invocations in new tabs'), modalText())}
|
52
62
|
titleIconVariant="warning"
|
53
63
|
width={590}
|
54
64
|
actions={[
|
@@ -57,16 +67,7 @@ const OpenAllInvocationsModal = ({
|
|
57
67
|
key="confirm"
|
58
68
|
variant="primary"
|
59
69
|
onClick={() => {
|
60
|
-
|
61
|
-
? failedHosts
|
62
|
-
: selectedIds.map(id => ({ id }));
|
63
|
-
|
64
|
-
hostsToOpen
|
65
|
-
.slice(0, MAX_HOSTS_API_SIZE)
|
66
|
-
.forEach(({ id }) =>
|
67
|
-
openLink(templateInvocationPageUrl(id, jobID), '_blank')
|
68
|
-
);
|
69
|
-
|
70
|
+
confirmCallback();
|
70
71
|
onClose();
|
71
72
|
}}
|
72
73
|
>
|
@@ -84,30 +85,24 @@ const OpenAllInvocationsModal = ({
|
|
84
85
|
>
|
85
86
|
{sprintf(
|
86
87
|
__('Are you sure you want to open all %s invocations in new tabs?'),
|
87
|
-
modalText
|
88
|
+
modalText()
|
88
89
|
)}
|
89
90
|
<br />
|
90
|
-
{
|
91
|
-
<br />
|
92
|
-
{sprintf(__('The number of %s invocations is:'), modalText)}{' '}
|
93
|
-
<b>{isOpenFailed ? failedCount : selectedIds.length}</b>
|
91
|
+
{selectedText()}
|
94
92
|
</Modal>
|
95
93
|
);
|
96
94
|
};
|
97
95
|
|
98
96
|
OpenAllInvocationsModal.propTypes = {
|
99
97
|
failedCount: PropTypes.number.isRequired,
|
100
|
-
failedHosts: PropTypes.array,
|
101
98
|
isOpen: PropTypes.bool.isRequired,
|
102
99
|
isOpenFailed: PropTypes.bool,
|
103
|
-
jobID: PropTypes.string.isRequired,
|
104
100
|
onClose: PropTypes.func.isRequired,
|
105
|
-
setShowAlert: PropTypes.func.isRequired,
|
106
101
|
selectedIds: PropTypes.array.isRequired,
|
102
|
+
confirmCallback: PropTypes.func.isRequired,
|
107
103
|
};
|
108
104
|
|
109
105
|
OpenAllInvocationsModal.defaultProps = {
|
110
|
-
failedHosts: [],
|
111
106
|
isOpenFailed: false,
|
112
107
|
};
|
113
108
|
|
@@ -83,6 +83,7 @@ jest.mock('../JobInvocationHostTable.js', () => () => (
|
|
83
83
|
const reportTemplateJobId = mockReportTemplatesResponse.results[0].id;
|
84
84
|
|
85
85
|
const mockStore = configureMockStore([thunk]);
|
86
|
+
const props = { history: { push: jest.fn() } };
|
86
87
|
|
87
88
|
describe('JobInvocationDetailPage', () => {
|
88
89
|
it('renders main information', async () => {
|
@@ -91,7 +92,10 @@ describe('JobInvocationDetailPage', () => {
|
|
91
92
|
|
92
93
|
const { container } = render(
|
93
94
|
<Provider store={store}>
|
94
|
-
<JobInvocationDetailPage
|
95
|
+
<JobInvocationDetailPage
|
96
|
+
match={{ params: { id: `${jobId}` } }}
|
97
|
+
{...props}
|
98
|
+
/>
|
95
99
|
</Provider>
|
96
100
|
);
|
97
101
|
|
@@ -185,6 +189,7 @@ describe('JobInvocationDetailPage', () => {
|
|
185
189
|
<Provider store={store}>
|
186
190
|
<JobInvocationDetailPage
|
187
191
|
match={{ params: { id: `${jobInvocationDataScheduled.id}` } }}
|
192
|
+
{...props}
|
188
193
|
/>
|
189
194
|
</Provider>
|
190
195
|
);
|
@@ -201,7 +206,10 @@ describe('JobInvocationDetailPage', () => {
|
|
201
206
|
const store = mockStore(jobInvocationDataRecurring);
|
202
207
|
render(
|
203
208
|
<Provider store={store}>
|
204
|
-
<JobInvocationDetailPage
|
209
|
+
<JobInvocationDetailPage
|
210
|
+
match={{ params: { id: `${jobId}` } }}
|
211
|
+
{...props}
|
212
|
+
/>
|
205
213
|
</Provider>
|
206
214
|
);
|
207
215
|
|
@@ -32,6 +32,29 @@ selectors.selectTaskCancelable.mockImplementation(() => true);
|
|
32
32
|
const mockStore = configureStore([]);
|
33
33
|
const store = mockStore({});
|
34
34
|
|
35
|
+
const allJobs = [
|
36
|
+
{
|
37
|
+
id: 1,
|
38
|
+
job_status: 'error',
|
39
|
+
},
|
40
|
+
{
|
41
|
+
id: 2,
|
42
|
+
job_status: 'error',
|
43
|
+
},
|
44
|
+
{
|
45
|
+
id: 3,
|
46
|
+
job_status: 'error',
|
47
|
+
},
|
48
|
+
{
|
49
|
+
id: 4,
|
50
|
+
job_status: 'error',
|
51
|
+
},
|
52
|
+
{
|
53
|
+
id: 5,
|
54
|
+
job_status: 'error',
|
55
|
+
},
|
56
|
+
];
|
57
|
+
|
35
58
|
describe('TableToolbarActions', () => {
|
36
59
|
const jobID = '42';
|
37
60
|
let openSpy;
|
@@ -58,8 +81,9 @@ describe('TableToolbarActions', () => {
|
|
58
81
|
<Provider store={store}>
|
59
82
|
<CheckboxesActions
|
60
83
|
selectedIds={selectedIds}
|
61
|
-
failedCount={0}
|
62
84
|
jobID={jobID}
|
85
|
+
allJobs={allJobs}
|
86
|
+
setShowAlert={jest.fn()}
|
63
87
|
/>
|
64
88
|
</Provider>
|
65
89
|
);
|
@@ -76,8 +100,9 @@ describe('TableToolbarActions', () => {
|
|
76
100
|
<Provider store={store}>
|
77
101
|
<CheckboxesActions
|
78
102
|
selectedIds={selectedIds}
|
79
|
-
failedCount={0}
|
80
103
|
jobID={jobID}
|
104
|
+
allJobs={allJobs}
|
105
|
+
setShowAlert={jest.fn()}
|
81
106
|
/>
|
82
107
|
</Provider>
|
83
108
|
);
|
@@ -94,47 +119,52 @@ describe('TableToolbarActions', () => {
|
|
94
119
|
test('shows alert when popups are blocked', async () => {
|
95
120
|
openSpy.mockReturnValue(null);
|
96
121
|
const selectedIds = [1, 2];
|
122
|
+
const setShowMock = jest.fn();
|
97
123
|
render(
|
98
124
|
<Provider store={store}>
|
99
125
|
<CheckboxesActions
|
100
126
|
selectedIds={selectedIds}
|
101
|
-
failedCount={0}
|
102
127
|
jobID={jobID}
|
128
|
+
allJobs={allJobs}
|
129
|
+
setShowAlert={setShowMock}
|
103
130
|
/>
|
104
131
|
</Provider>
|
105
132
|
);
|
106
133
|
fireEvent.click(
|
107
134
|
screen.getByLabelText(/open all template invocations in new tab/i)
|
108
135
|
);
|
109
|
-
expect(
|
110
|
-
await screen.findByText(/Popups are blocked by your browser/)
|
111
|
-
).toBeInTheDocument();
|
136
|
+
expect(setShowMock).toHaveBeenCalledWith(true);
|
112
137
|
});
|
113
138
|
});
|
114
139
|
|
115
140
|
describe('Opening failed in new tabs', () => {
|
116
141
|
test('opens links when results length is less than or equal to 3', async () => {
|
117
|
-
const failedHosts = [{ id: 301 }, { id: 302 }];
|
118
|
-
useAPI.mockReturnValue({
|
119
|
-
response: { results: failedHosts },
|
120
|
-
status: 'success',
|
121
|
-
});
|
122
142
|
render(
|
123
143
|
<Provider store={store}>
|
124
|
-
<CheckboxesActions
|
144
|
+
<CheckboxesActions
|
145
|
+
selectedIds={[]}
|
146
|
+
jobID={jobID}
|
147
|
+
allJobs={allJobs.slice(0, 2)}
|
148
|
+
setShowAlert={jest.fn()}
|
149
|
+
/>
|
125
150
|
</Provider>
|
126
151
|
);
|
127
152
|
fireEvent.click(screen.getByLabelText(/actions dropdown toggle/i));
|
128
153
|
fireEvent.click(await screen.findByText(/open all failed runs/i));
|
129
154
|
await waitFor(() => {
|
130
|
-
expect(openSpy).toHaveBeenCalledTimes(
|
155
|
+
expect(openSpy).toHaveBeenCalledTimes(2);
|
131
156
|
});
|
132
157
|
});
|
133
158
|
|
134
159
|
test('shows modal when results length is greater than 3', async () => {
|
135
160
|
render(
|
136
161
|
<Provider store={store}>
|
137
|
-
<CheckboxesActions
|
162
|
+
<CheckboxesActions
|
163
|
+
selectedIds={[]}
|
164
|
+
jobID={jobID}
|
165
|
+
allJobs={allJobs}
|
166
|
+
setShowAlert={jest.fn()}
|
167
|
+
/>
|
138
168
|
</Provider>
|
139
169
|
);
|
140
170
|
fireEvent.click(screen.getByLabelText(/actions dropdown toggle/i));
|
@@ -145,21 +175,6 @@ describe('TableToolbarActions', () => {
|
|
145
175
|
})
|
146
176
|
).toBeInTheDocument();
|
147
177
|
});
|
148
|
-
|
149
|
-
test('calls useApi with skip: true when failedCount is 0', () => {
|
150
|
-
render(
|
151
|
-
<Provider store={store}>
|
152
|
-
<CheckboxesActions selectedIds={[]} failedCount={0} jobID={jobID} />
|
153
|
-
</Provider>
|
154
|
-
);
|
155
|
-
expect(useAPI).toHaveBeenCalledWith(
|
156
|
-
'get',
|
157
|
-
foremanUrl(`/api/job_invocations/${jobID}/hosts`),
|
158
|
-
expect.objectContaining({
|
159
|
-
skip: true,
|
160
|
-
})
|
161
|
-
);
|
162
|
-
});
|
163
178
|
});
|
164
179
|
|
165
180
|
describe('PopupAlert', () => {
|
@@ -188,8 +203,9 @@ describe('TableToolbarActions', () => {
|
|
188
203
|
<CheckboxesActions
|
189
204
|
bulkParams="(id ^ (101, 102, 103))"
|
190
205
|
selectedIds={selectedIds}
|
191
|
-
failedCount={1}
|
192
206
|
jobID={jobID}
|
207
|
+
allJobs={allJobs}
|
208
|
+
setShowAlert={jest.fn()}
|
193
209
|
/>
|
194
210
|
</Provider>
|
195
211
|
);
|
@@ -35,12 +35,12 @@ const JobInvocationDetailPage = ({
|
|
35
35
|
match: {
|
36
36
|
params: { id },
|
37
37
|
},
|
38
|
+
history,
|
38
39
|
}) => {
|
39
40
|
const dispatch = useDispatch();
|
40
41
|
const items = useSelector(selectItems);
|
41
42
|
const {
|
42
43
|
description,
|
43
|
-
failed = 0,
|
44
44
|
status_label: statusLabel,
|
45
45
|
task,
|
46
46
|
start_at: startAt,
|
@@ -95,6 +95,16 @@ const JobInvocationDetailPage = ({
|
|
95
95
|
: STATUS_UPPERCASE.RESOLVED;
|
96
96
|
|
97
97
|
const breadcrumbOptions = {
|
98
|
+
isSwitchable: true,
|
99
|
+
onSwitcherItemClick: (e, href) => {
|
100
|
+
e.preventDefault();
|
101
|
+
history.push(href);
|
102
|
+
},
|
103
|
+
resource: {
|
104
|
+
nameField: 'description',
|
105
|
+
resourceUrl: '/api/v2/job_invocations',
|
106
|
+
switcherItemUrl: '/job_invocations/:id',
|
107
|
+
},
|
98
108
|
breadcrumbItems: [
|
99
109
|
{ caption: __('Jobs'), url: `/job_invocations` },
|
100
110
|
{
|
@@ -182,7 +192,7 @@ const JobInvocationDetailPage = ({
|
|
182
192
|
<JobInvocationHostTable
|
183
193
|
id={id}
|
184
194
|
targeting={targeting}
|
185
|
-
|
195
|
+
finished={finished}
|
186
196
|
autoRefresh={autoRefresh}
|
187
197
|
initialFilter={selectedFilter}
|
188
198
|
statusLabel={statusLabel}
|
@@ -200,6 +210,7 @@ JobInvocationDetailPage.propTypes = {
|
|
200
210
|
id: PropTypes.string.isRequired,
|
201
211
|
}),
|
202
212
|
}).isRequired,
|
213
|
+
history: PropTypes.object.isRequired,
|
203
214
|
};
|
204
215
|
|
205
216
|
export default JobInvocationDetailPage;
|
@@ -2,32 +2,18 @@ import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import LabelIcon from 'foremanReact/components/common/LabelIcon';
|
5
6
|
|
6
|
-
import { FormGroup, TextInput
|
7
|
-
|
8
|
-
import { HelpIcon } from '@patternfly/react-icons';
|
7
|
+
import { FormGroup, TextInput } from '@patternfly/react-core';
|
9
8
|
|
10
9
|
const RexInterface = ({ isLoading, onChange }) => (
|
11
10
|
<FormGroup
|
12
11
|
label={__('Remote Execution Interface')}
|
13
12
|
fieldId="reg_rex_interface"
|
14
13
|
labelIcon={
|
15
|
-
<
|
16
|
-
|
17
|
-
|
18
|
-
{__('Identifier of the Host interface for Remote execution')}
|
19
|
-
</div>
|
20
|
-
}
|
21
|
-
>
|
22
|
-
<button
|
23
|
-
className="pf-v5-cform__group-label-help"
|
24
|
-
onClick={e => e.preventDefault()}
|
25
|
-
>
|
26
|
-
<Icon isInline>
|
27
|
-
<HelpIcon />
|
28
|
-
</Icon>
|
29
|
-
</button>
|
30
|
-
</Popover>
|
14
|
+
<LabelIcon
|
15
|
+
text={__('Identifier of the Host interface for Remote execution')}
|
16
|
+
/>
|
31
17
|
}
|
32
18
|
>
|
33
19
|
<TextInput
|
@@ -1,9 +1,25 @@
|
|
1
|
-
import
|
1
|
+
import React from 'react';
|
2
|
+
import { screen, fireEvent, render, act } from '@testing-library/react';
|
3
|
+
import '@testing-library/jest-dom/extend-expect';
|
2
4
|
import RexInterface from '../RexInterface';
|
3
5
|
|
4
6
|
const fixtures = {
|
5
7
|
renders: { isLoading: false, onChange: () => {} },
|
6
8
|
};
|
7
9
|
|
8
|
-
describe('RexInterface', () =>
|
9
|
-
|
10
|
+
describe('RexInterface', () => {
|
11
|
+
it('should render label with help icon and popover instructions', async () => {
|
12
|
+
jest.useFakeTimers();
|
13
|
+
render(<RexInterface {...fixtures.renders} />);
|
14
|
+
await act(async () => {
|
15
|
+
await fireEvent.click(screen.getByRole('button'));
|
16
|
+
|
17
|
+
jest.advanceTimersByTime(500);
|
18
|
+
});
|
19
|
+
|
20
|
+
expect(screen.getByText('Remote Execution Interface')).toBeInTheDocument();
|
21
|
+
expect(
|
22
|
+
screen.getByText('Identifier of the Host interface for Remote execution')
|
23
|
+
).toBeInTheDocument();
|
24
|
+
});
|
25
|
+
});
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 16.2.
|
4
|
+
version: 16.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-10-07 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: deface
|
@@ -551,7 +551,6 @@ files:
|
|
551
551
|
- webpack/react_app/components/RegistrationExtension/RexInterface.js
|
552
552
|
- webpack/react_app/components/RegistrationExtension/RexPull.js
|
553
553
|
- webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js
|
554
|
-
- webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap
|
555
554
|
- webpack/react_app/components/TargetingHosts/TargetingHosts.js
|
556
555
|
- webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js
|
557
556
|
- webpack/react_app/components/TargetingHosts/TargetingHostsHelpers.js
|
@@ -1,36 +0,0 @@
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
-
|
3
|
-
exports[`RexInterface renders 1`] = `
|
4
|
-
<FormGroup
|
5
|
-
fieldId="reg_rex_interface"
|
6
|
-
label="Remote Execution Interface"
|
7
|
-
labelIcon={
|
8
|
-
<Popover
|
9
|
-
bodyContent={
|
10
|
-
<div>
|
11
|
-
Identifier of the Host interface for Remote execution
|
12
|
-
</div>
|
13
|
-
}
|
14
|
-
>
|
15
|
-
<button
|
16
|
-
className="pf-v5-cform__group-label-help"
|
17
|
-
onClick={[Function]}
|
18
|
-
>
|
19
|
-
<Icon
|
20
|
-
isInline={true}
|
21
|
-
>
|
22
|
-
<HelpIcon />
|
23
|
-
</Icon>
|
24
|
-
</button>
|
25
|
-
</Popover>
|
26
|
-
}
|
27
|
-
>
|
28
|
-
<TextInput
|
29
|
-
id="reg_rex_interface_input"
|
30
|
-
isDisabled={false}
|
31
|
-
onBlur={[Function]}
|
32
|
-
ouiaId="reg_rex_interface_input"
|
33
|
-
type="text"
|
34
|
-
/>
|
35
|
-
</FormGroup>
|
36
|
-
`;
|