foreman_remote_execution 16.6.5 → 17.0.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/job_invocations_controller.rb +22 -13
- data/app/views/api/v2/job_invocations/main.json.rabl +16 -11
- data/app/views/job_templates/_alerts.html.erb +4 -0
- data/lib/foreman_remote_execution/plugin.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +3 -3
- data/test/functional/api/v2/job_invocations_controller_test.rb +68 -3
- data/webpack/JobInvocationDetail/CheckboxesActions.js +4 -10
- data/webpack/JobInvocationDetail/JobInvocationActions.js +80 -90
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +7 -6
- data/webpack/JobInvocationDetail/JobInvocationEmptyState.js +52 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +76 -42
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +1 -19
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +202 -153
- data/webpack/JobInvocationDetail/TemplateInvocation.js +31 -25
- data/webpack/JobInvocationDetail/__tests__/JobInvocationDetailEmptyState.test.js +53 -0
- data/webpack/JobInvocationDetail/__tests__/JobInvocationHostTablePolling.test.js +336 -0
- data/webpack/JobInvocationDetail/__tests__/JobInvocationPolling.test.js +223 -0
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +260 -111
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +3 -1
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocationPolling.test.js +254 -0
- data/webpack/JobInvocationDetail/__tests__/fixtures.js +2 -0
- data/webpack/JobInvocationDetail/__tests__/foremanTestHelpers.js +30 -0
- data/webpack/JobInvocationDetail/index.js +50 -30
- data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +15 -0
- data/webpack/__mocks__/foremanReact/common/hooks/Permissions/permissionHooks.js +1 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +5 -0
- data/webpack/test_setup.js +1 -13
- metadata +11 -3
|
@@ -41,8 +41,8 @@ import Columns, {
|
|
|
41
41
|
JOB_INVOCATION_HOSTS,
|
|
42
42
|
LIST_TEMPLATE_INVOCATIONS,
|
|
43
43
|
STATUS_UPPERCASE,
|
|
44
|
-
ALL_JOB_HOSTS,
|
|
45
44
|
AWAITING_STATUS_FILTER,
|
|
45
|
+
AUTO_REFRESH_INTERVAL_MS,
|
|
46
46
|
} from './JobInvocationConstants';
|
|
47
47
|
import { TemplateInvocation } from './TemplateInvocation';
|
|
48
48
|
import { RowActions } from './TemplateInvocationComponents/TemplateActionButtons';
|
|
@@ -51,8 +51,8 @@ import { PopupAlert } from './OpenAllInvocationsModal';
|
|
|
51
51
|
const JobInvocationHostTable = ({
|
|
52
52
|
id,
|
|
53
53
|
initialFilter,
|
|
54
|
+
jobFinished,
|
|
54
55
|
onFilterUpdate,
|
|
55
|
-
statusLabel,
|
|
56
56
|
targeting,
|
|
57
57
|
}) => {
|
|
58
58
|
const columns = Columns();
|
|
@@ -69,7 +69,17 @@ const JobInvocationHostTable = ({
|
|
|
69
69
|
|
|
70
70
|
// Expansive items
|
|
71
71
|
const [expandedHost, setExpandedHost] = useState(new Set());
|
|
72
|
-
const
|
|
72
|
+
const prevJobFinished = useRef(jobFinished);
|
|
73
|
+
const prevFilter = useRef('');
|
|
74
|
+
const prevId = useRef(id);
|
|
75
|
+
const pollTimeoutId = useRef(null);
|
|
76
|
+
const currentPollParams = useRef({});
|
|
77
|
+
const mountedRef = useRef(true);
|
|
78
|
+
const requestIdRef = useRef(0);
|
|
79
|
+
const jobFinishedRef = useRef(jobFinished);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
jobFinishedRef.current = jobFinished;
|
|
82
|
+
}, [jobFinished]);
|
|
73
83
|
|
|
74
84
|
const [hostInvocationStates, setHostInvocationStates] = useState({});
|
|
75
85
|
|
|
@@ -153,33 +163,50 @@ const JobInvocationHostTable = ({
|
|
|
153
163
|
[initialFilter, urlSearchQuery]
|
|
154
164
|
);
|
|
155
165
|
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
setApiResponse(data.data);
|
|
161
|
-
setAllHostsIds(ids);
|
|
162
|
-
}
|
|
163
|
-
|
|
166
|
+
const updateHostsState = useCallback(data => {
|
|
167
|
+
const ids = data.data.results.map(i => i.id);
|
|
168
|
+
setApiResponse(data.data);
|
|
169
|
+
setAllHostsIds(ids);
|
|
164
170
|
setStatus(STATUS_UPPERCASE.RESOLVED);
|
|
165
171
|
}, []);
|
|
166
172
|
|
|
167
173
|
// Call hosts data with params
|
|
168
174
|
const makeApiCall = useCallback(
|
|
169
|
-
|
|
175
|
+
requestParams => {
|
|
176
|
+
requestIdRef.current += 1;
|
|
177
|
+
const thisRequest = requestIdRef.current;
|
|
170
178
|
dispatch(
|
|
171
179
|
APIActions.get({
|
|
172
|
-
key:
|
|
173
|
-
url:
|
|
180
|
+
key: JOB_INVOCATION_HOSTS,
|
|
181
|
+
url: `/api/job_invocations/${id}/hosts`,
|
|
174
182
|
params: requestParams,
|
|
175
|
-
handleSuccess: data =>
|
|
176
|
-
|
|
183
|
+
handleSuccess: data => {
|
|
184
|
+
if (thisRequest !== requestIdRef.current) return;
|
|
185
|
+
if (!mountedRef.current) return;
|
|
186
|
+
updateHostsState(data);
|
|
187
|
+
if (!jobFinishedRef.current) {
|
|
188
|
+
pollTimeoutId.current = setTimeout(
|
|
189
|
+
() => makeApiCall(currentPollParams.current),
|
|
190
|
+
AUTO_REFRESH_INTERVAL_MS
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
pollTimeoutId.current = null;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
handleError: () => {
|
|
197
|
+
if (thisRequest !== requestIdRef.current) return;
|
|
198
|
+
if (!mountedRef.current) return;
|
|
199
|
+
pollTimeoutId.current = null;
|
|
200
|
+
setStatus(STATUS_UPPERCASE.ERROR);
|
|
201
|
+
},
|
|
177
202
|
errorToast: ({ response }) =>
|
|
178
|
-
response?.data?.error?.full_messages?.[0] ||
|
|
203
|
+
response?.data?.error?.full_messages?.[0] ||
|
|
204
|
+
response?.data?.error?.message ||
|
|
205
|
+
__('Failed to load host invocation data'),
|
|
179
206
|
})
|
|
180
207
|
);
|
|
181
208
|
},
|
|
182
|
-
[dispatch, id,
|
|
209
|
+
[dispatch, id, updateHostsState]
|
|
183
210
|
);
|
|
184
211
|
|
|
185
212
|
const filterApiCall = useCallback(
|
|
@@ -202,7 +229,11 @@ const JobInvocationHostTable = ({
|
|
|
202
229
|
finalParams.search = filterSearch;
|
|
203
230
|
}
|
|
204
231
|
|
|
205
|
-
|
|
232
|
+
currentPollParams.current = finalParams;
|
|
233
|
+
clearTimeout(pollTimeoutId.current);
|
|
234
|
+
pollTimeoutId.current = null;
|
|
235
|
+
|
|
236
|
+
makeApiCall(finalParams);
|
|
206
237
|
|
|
207
238
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
|
208
239
|
|
|
@@ -222,26 +253,17 @@ const JobInvocationHostTable = ({
|
|
|
222
253
|
]
|
|
223
254
|
);
|
|
224
255
|
|
|
225
|
-
// Filter change
|
|
226
|
-
const handleFilterChange = useCallback(
|
|
227
|
-
newFilter => {
|
|
228
|
-
onFilterUpdate(newFilter);
|
|
229
|
-
},
|
|
230
|
-
[onFilterUpdate]
|
|
231
|
-
);
|
|
232
|
-
|
|
233
256
|
// Effects
|
|
234
257
|
// run after mount
|
|
235
258
|
const initializedRef = useRef(false);
|
|
236
259
|
useEffect(() => {
|
|
237
260
|
if (!initializedRef.current) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
{},
|
|
241
|
-
{
|
|
242
|
-
url: `/job_invocations/${id}/hosts`,
|
|
261
|
+
dispatch(
|
|
262
|
+
APIActions.get({
|
|
243
263
|
key: LIST_TEMPLATE_INVOCATIONS,
|
|
244
|
-
|
|
264
|
+
url: `/job_invocations/${id}/hosts`,
|
|
265
|
+
params: {},
|
|
266
|
+
})
|
|
245
267
|
);
|
|
246
268
|
|
|
247
269
|
if (initialFilter === '') {
|
|
@@ -249,16 +271,28 @@ const JobInvocationHostTable = ({
|
|
|
249
271
|
}
|
|
250
272
|
initializedRef.current = true;
|
|
251
273
|
}
|
|
252
|
-
}, [
|
|
274
|
+
}, [dispatch, id, initialFilter, onFilterUpdate]);
|
|
253
275
|
|
|
254
276
|
useEffect(() => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
277
|
+
const filterChanged = initialFilter !== prevFilter.current;
|
|
278
|
+
const statusChanged = jobFinished !== prevJobFinished.current;
|
|
279
|
+
const idChanged = id !== prevId.current;
|
|
280
|
+
|
|
281
|
+
if ((filterChanged || statusChanged || idChanged) && initialFilter !== '') {
|
|
282
|
+
prevFilter.current = initialFilter;
|
|
283
|
+
prevJobFinished.current = jobFinished;
|
|
284
|
+
prevId.current = id;
|
|
259
285
|
filterApiCall();
|
|
260
286
|
}
|
|
261
|
-
}, [initialFilter,
|
|
287
|
+
}, [initialFilter, jobFinished, id, filterApiCall]);
|
|
288
|
+
|
|
289
|
+
useEffect(
|
|
290
|
+
() => () => {
|
|
291
|
+
mountedRef.current = false;
|
|
292
|
+
clearTimeout(pollTimeoutId.current);
|
|
293
|
+
},
|
|
294
|
+
[]
|
|
295
|
+
);
|
|
262
296
|
|
|
263
297
|
const {
|
|
264
298
|
updateSearchQuery: updateSearchQueryBulk,
|
|
@@ -403,7 +437,7 @@ const JobInvocationHostTable = ({
|
|
|
403
437
|
<DropdownFilter
|
|
404
438
|
key="dropdown-filter"
|
|
405
439
|
dropdownFilter={initialFilter}
|
|
406
|
-
setDropdownFilter={
|
|
440
|
+
setDropdownFilter={onFilterUpdate}
|
|
407
441
|
/>,
|
|
408
442
|
<CheckboxesActions
|
|
409
443
|
bulkParams={selectedCount > 0 ? fetchBulkParams() : null}
|
|
@@ -541,13 +575,13 @@ JobInvocationHostTable.propTypes = {
|
|
|
541
575
|
id: PropTypes.string.isRequired,
|
|
542
576
|
targeting: PropTypes.object.isRequired,
|
|
543
577
|
initialFilter: PropTypes.string.isRequired,
|
|
544
|
-
|
|
578
|
+
jobFinished: PropTypes.bool,
|
|
545
579
|
onFilterUpdate: PropTypes.func,
|
|
546
580
|
};
|
|
547
581
|
|
|
548
582
|
JobInvocationHostTable.defaultProps = {
|
|
549
583
|
onFilterUpdate: () => {},
|
|
550
|
-
|
|
584
|
+
jobFinished: false,
|
|
551
585
|
};
|
|
552
586
|
|
|
553
587
|
export default JobInvocationHostTable;
|
|
@@ -3,22 +3,17 @@ import {
|
|
|
3
3
|
selectAPIResponse,
|
|
4
4
|
selectAPIStatus,
|
|
5
5
|
} from 'foremanReact/redux/API/APISelectors';
|
|
6
|
-
import { STATUS as APIStatus } from 'foremanReact/constants';
|
|
7
6
|
import {
|
|
8
7
|
JOB_INVOCATION_KEY,
|
|
9
|
-
GET_TASK,
|
|
10
8
|
GET_TEMPLATE_INVOCATION,
|
|
11
9
|
LIST_TEMPLATE_INVOCATIONS,
|
|
12
|
-
CURRENT_PERMISSIONS,
|
|
13
10
|
} from './JobInvocationConstants';
|
|
14
11
|
|
|
15
12
|
export const selectItems = state =>
|
|
16
13
|
selectAPIResponse(state, JOB_INVOCATION_KEY);
|
|
17
14
|
|
|
18
|
-
export const selectTask = state => selectAPIResponse(state, GET_TASK);
|
|
19
|
-
|
|
20
15
|
export const selectTaskCancelable = state =>
|
|
21
|
-
|
|
16
|
+
selectItems(state).task?.cancellable || false;
|
|
22
17
|
|
|
23
18
|
export const selectTemplateInvocation = hostID => state =>
|
|
24
19
|
selectAPIResponse(state, `${GET_TEMPLATE_INVOCATION}_${hostID}`);
|
|
@@ -29,16 +24,3 @@ export const selectTemplateInvocationStatus = hostID => state =>
|
|
|
29
24
|
export const selectTemplateInvocationList = state =>
|
|
30
25
|
selectAPIResponse(state, LIST_TEMPLATE_INVOCATIONS)
|
|
31
26
|
?.template_invocations_task_by_hosts;
|
|
32
|
-
|
|
33
|
-
export const selectCurrentPermisions = state =>
|
|
34
|
-
selectAPIResponse(state, CURRENT_PERMISSIONS);
|
|
35
|
-
|
|
36
|
-
export const selectHasPermission = permissionRequired => state => {
|
|
37
|
-
const status = selectAPIStatus(state, CURRENT_PERMISSIONS);
|
|
38
|
-
const selectCurrentPermissions = selectCurrentPermisions(state)?.results;
|
|
39
|
-
return status === APIStatus.RESOLVED
|
|
40
|
-
? selectCurrentPermissions?.some(
|
|
41
|
-
permission => permission.name === permissionRequired
|
|
42
|
-
)
|
|
43
|
-
: false;
|
|
44
|
-
};
|