foreman_remote_execution 16.7.0 → 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/views/api/v2/job_invocations/main.json.rabl +1 -0
- 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/webpack/JobInvocationDetail/JobInvocationActions.js +80 -91
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +3 -2
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +76 -42
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +1 -4
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +10 -12
- data/webpack/JobInvocationDetail/TemplateInvocation.js +31 -25
- 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 +205 -154
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocationPolling.test.js +254 -0
- data/webpack/JobInvocationDetail/__tests__/fixtures.js +2 -0
- data/webpack/JobInvocationDetail/index.js +12 -25
- data/webpack/test_setup.js +1 -13
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0c5f9e975897ee24ccf2c91fafe81e998742ec7453ca0e7cf3aea4846962b23
|
|
4
|
+
data.tar.gz: 20f9ca73d78bd41ce758b3ac39e1e880be6630230eb64d735850a88ae7b9cbec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57609a9b217ab4e7e5e60536a6a4de788b26d46d09f68330ca385fbd4df59d65cabc33581026ba15795aacbfc96b1fe6a97b735e006dfcf0a51188032ea525b9
|
|
7
|
+
data.tar.gz: 23d742ef78d0f9dc5cc205d541c74a8c3a3472618c4e67a6cb1c5c94c552a2ea6aa601ce38b498f9fae2403227f0efdf44055fefe8cc90df1de86e3c7b6fc96f
|
|
@@ -1,52 +1,67 @@
|
|
|
1
1
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
2
|
-
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
3
2
|
import { addToast } from 'foremanReact/components/ToastsList';
|
|
4
|
-
import { APIActions
|
|
5
|
-
import {
|
|
6
|
-
stopInterval,
|
|
7
|
-
withInterval,
|
|
8
|
-
} from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
|
3
|
+
import { APIActions } from 'foremanReact/redux/API';
|
|
9
4
|
import {
|
|
10
5
|
CANCEL_JOB,
|
|
11
6
|
CANCEL_RECURRING_LOGIC,
|
|
12
7
|
CHANGE_ENABLED_RECURRING_LOGIC,
|
|
13
|
-
GET_TASK,
|
|
14
8
|
JOB_INVOCATION_KEY,
|
|
15
|
-
|
|
9
|
+
STATUS,
|
|
16
10
|
} from './JobInvocationConstants';
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
response?.data?.error?.message ||
|
|
33
|
-
'Error',
|
|
34
|
-
}),
|
|
35
|
-
1000
|
|
36
|
-
);
|
|
12
|
+
const POLL_INTERVAL = 5000;
|
|
13
|
+
|
|
14
|
+
export const isJobFinished = statusLabel =>
|
|
15
|
+
statusLabel === STATUS.FAILED ||
|
|
16
|
+
statusLabel === STATUS.SUCCEEDED ||
|
|
17
|
+
statusLabel === STATUS.CANCELLED;
|
|
18
|
+
|
|
19
|
+
const extractErrorMessage = response =>
|
|
20
|
+
// eslint-disable-next-line camelcase
|
|
21
|
+
response?.data?.error?.full_messages?.[0] ||
|
|
22
|
+
response?.data?.error?.message ||
|
|
23
|
+
__('Unknown error.');
|
|
24
|
+
|
|
25
|
+
const createPollState = cancel => ({ timeoutId: null, cancel });
|
|
37
26
|
|
|
38
|
-
|
|
27
|
+
export const getJobInvocation = (url, pollTimeoutRef) => dispatch => {
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
|
|
30
|
+
const poll = () => {
|
|
31
|
+
if (cancelled) return;
|
|
32
|
+
dispatch(
|
|
33
|
+
APIActions.get({
|
|
34
|
+
key: JOB_INVOCATION_KEY,
|
|
35
|
+
params: { include_permissions: true, include_hosts: false },
|
|
36
|
+
url,
|
|
37
|
+
handleSuccess: ({ data }) => {
|
|
38
|
+
if (cancelled) return;
|
|
39
|
+
// eslint-disable-next-line camelcase
|
|
40
|
+
pollTimeoutRef.current.timeoutId = isJobFinished(data?.status_label)
|
|
41
|
+
? null
|
|
42
|
+
: setTimeout(poll, POLL_INTERVAL);
|
|
43
|
+
},
|
|
44
|
+
handleError: () => {
|
|
45
|
+
if (cancelled) return;
|
|
46
|
+
pollTimeoutRef.current.timeoutId = null;
|
|
47
|
+
},
|
|
48
|
+
errorToast: ({ response }) => extractErrorMessage(response),
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
stopJobInvocationPolling(pollTimeoutRef);
|
|
54
|
+
pollTimeoutRef.current = createPollState(() => {
|
|
55
|
+
cancelled = true;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
poll();
|
|
39
59
|
};
|
|
40
60
|
|
|
41
|
-
export const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
url,
|
|
46
|
-
key: UPDATE_JOB,
|
|
47
|
-
params: { include_hosts: false },
|
|
48
|
-
})
|
|
49
|
-
);
|
|
61
|
+
export const stopJobInvocationPolling = pollTimeoutRef => {
|
|
62
|
+
clearTimeout(pollTimeoutRef.current.timeoutId);
|
|
63
|
+
pollTimeoutRef.current.cancel();
|
|
64
|
+
pollTimeoutRef.current = createPollState(() => {});
|
|
50
65
|
};
|
|
51
66
|
|
|
52
67
|
export const cancelJob = (jobId, force) => dispatch => {
|
|
@@ -54,10 +69,6 @@ export const cancelJob = (jobId, force) => dispatch => {
|
|
|
54
69
|
force
|
|
55
70
|
? sprintf(__('Trying to abort the job %s.'), jobId)
|
|
56
71
|
: sprintf(__('Trying to cancel the job %s.'), jobId);
|
|
57
|
-
const errorToast = response =>
|
|
58
|
-
force
|
|
59
|
-
? sprintf(__(`Could not abort the job %s: ${response}`), jobId)
|
|
60
|
-
: sprintf(__(`Could not cancel the job %s: ${response}`), jobId);
|
|
61
72
|
const url = force
|
|
62
73
|
? `/job_invocations/${jobId}/cancel?force=true`
|
|
63
74
|
: `/job_invocations/${jobId}/cancel`;
|
|
@@ -67,12 +78,17 @@ export const cancelJob = (jobId, force) => dispatch => {
|
|
|
67
78
|
url,
|
|
68
79
|
key: CANCEL_JOB,
|
|
69
80
|
errorToast: ({ response }) =>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
force
|
|
82
|
+
? sprintf(
|
|
83
|
+
__('Could not abort the job %s: %s'),
|
|
84
|
+
jobId,
|
|
85
|
+
extractErrorMessage(response)
|
|
86
|
+
)
|
|
87
|
+
: sprintf(
|
|
88
|
+
__('Could not cancel the job %s: %s'),
|
|
89
|
+
jobId,
|
|
90
|
+
extractErrorMessage(response)
|
|
91
|
+
),
|
|
76
92
|
handleSuccess: () => {
|
|
77
93
|
dispatch(
|
|
78
94
|
addToast({
|
|
@@ -81,40 +97,16 @@ export const cancelJob = (jobId, force) => dispatch => {
|
|
|
81
97
|
message: infoToast(),
|
|
82
98
|
})
|
|
83
99
|
);
|
|
84
|
-
dispatch(updateJob(jobId));
|
|
85
100
|
},
|
|
86
101
|
})
|
|
87
102
|
);
|
|
88
103
|
};
|
|
89
104
|
|
|
90
|
-
export const
|
|
91
|
-
dispatch(
|
|
92
|
-
get({
|
|
93
|
-
key: GET_TASK,
|
|
94
|
-
url: `/foreman_tasks/api/tasks/${taskId}`,
|
|
95
|
-
})
|
|
96
|
-
);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
export const enableRecurringLogic = (
|
|
100
|
-
recurrenceId,
|
|
101
|
-
enabled,
|
|
102
|
-
jobId
|
|
103
|
-
) => dispatch => {
|
|
105
|
+
export const enableRecurringLogic = (recurrenceId, enabled) => dispatch => {
|
|
104
106
|
const successToast = () =>
|
|
105
107
|
enabled
|
|
106
108
|
? sprintf(__('Recurring logic %s disabled successfully.'), recurrenceId)
|
|
107
109
|
: sprintf(__('Recurring logic %s enabled successfully.'), recurrenceId);
|
|
108
|
-
const errorToast = response =>
|
|
109
|
-
enabled
|
|
110
|
-
? sprintf(
|
|
111
|
-
__(`Could not disable recurring logic %s: ${response}`),
|
|
112
|
-
recurrenceId
|
|
113
|
-
)
|
|
114
|
-
: sprintf(
|
|
115
|
-
__(`Could not enable recurring logic %s: ${response}`),
|
|
116
|
-
recurrenceId
|
|
117
|
-
);
|
|
118
110
|
const url = `/foreman_tasks/api/recurring_logics/${recurrenceId}`;
|
|
119
111
|
dispatch(
|
|
120
112
|
APIActions.put({
|
|
@@ -123,25 +115,24 @@ export const enableRecurringLogic = (
|
|
|
123
115
|
params: { recurring_logic: { enabled: !enabled } },
|
|
124
116
|
successToast,
|
|
125
117
|
errorToast: ({ response }) =>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
118
|
+
enabled
|
|
119
|
+
? sprintf(
|
|
120
|
+
__('Could not disable recurring logic %s: %s'),
|
|
121
|
+
recurrenceId,
|
|
122
|
+
extractErrorMessage(response)
|
|
123
|
+
)
|
|
124
|
+
: sprintf(
|
|
125
|
+
__('Could not enable recurring logic %s: %s'),
|
|
126
|
+
recurrenceId,
|
|
127
|
+
extractErrorMessage(response)
|
|
128
|
+
),
|
|
133
129
|
})
|
|
134
130
|
);
|
|
135
131
|
};
|
|
136
132
|
|
|
137
|
-
export const cancelRecurringLogic =
|
|
133
|
+
export const cancelRecurringLogic = recurrenceId => dispatch => {
|
|
138
134
|
const successToast = () =>
|
|
139
135
|
sprintf(__('Recurring logic %s cancelled successfully.'), recurrenceId);
|
|
140
|
-
const errorToast = response =>
|
|
141
|
-
sprintf(
|
|
142
|
-
__(`Could not cancel recurring logic %s: ${response}`),
|
|
143
|
-
recurrenceId
|
|
144
|
-
);
|
|
145
136
|
const url = `/foreman_tasks/recurring_logics/${recurrenceId}/cancel`;
|
|
146
137
|
dispatch(
|
|
147
138
|
APIActions.post({
|
|
@@ -149,13 +140,11 @@ export const cancelRecurringLogic = (recurrenceId, jobId) => dispatch => {
|
|
|
149
140
|
key: CANCEL_RECURRING_LOGIC,
|
|
150
141
|
successToast,
|
|
151
142
|
errorToast: ({ response }) =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
'Unknown error.'
|
|
143
|
+
sprintf(
|
|
144
|
+
__('Could not cancel recurring logic %s: %s'),
|
|
145
|
+
recurrenceId,
|
|
146
|
+
extractErrorMessage(response)
|
|
157
147
|
),
|
|
158
|
-
handleSuccess: () => dispatch(updateJob(jobId)),
|
|
159
148
|
})
|
|
160
149
|
);
|
|
161
150
|
};
|
|
@@ -6,9 +6,7 @@ import { useForemanHostDetailsPageUrl } from 'foremanReact/Root/Context/ForemanC
|
|
|
6
6
|
import JobStatusIcon from '../react_app/components/RecentJobsCard/JobStatusIcon';
|
|
7
7
|
|
|
8
8
|
export const JOB_INVOCATION_KEY = 'JOB_INVOCATION_KEY';
|
|
9
|
-
export const UPDATE_JOB = 'UPDATE_JOB';
|
|
10
9
|
export const CANCEL_JOB = 'CANCEL_JOB';
|
|
11
|
-
export const GET_TASK = 'GET_TASK';
|
|
12
10
|
export const GET_TEMPLATE_INVOCATIONS = 'GET_TEMPLATE_INVOCATIONS';
|
|
13
11
|
export const CHANGE_ENABLED_RECURRING_LOGIC = 'CHANGE_ENABLED_RECURRING_LOGIC';
|
|
14
12
|
export const CANCEL_RECURRING_LOGIC = 'CANCEL_RECURRING_LOGIC';
|
|
@@ -20,6 +18,8 @@ export const DIRECT_OPEN_HOST_LIMIT = 3;
|
|
|
20
18
|
export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS';
|
|
21
19
|
export const AWAITING_STATUS_FILTER = '(job_invocation.result = N/A)';
|
|
22
20
|
|
|
21
|
+
export const AUTO_REFRESH_INTERVAL_MS = 5000;
|
|
22
|
+
|
|
23
23
|
export const showTemplateInvocationUrl = (hostID, jobID) =>
|
|
24
24
|
`/show_template_invocation_by_host/${hostID}/job_invocation/${jobID}`;
|
|
25
25
|
export const LIST_TEMPLATE_INVOCATIONS = 'LIST_TEMPLATE_INVOCATIONS';
|
|
@@ -38,6 +38,7 @@ export const STATUS = {
|
|
|
38
38
|
SUCCEEDED: 'succeeded',
|
|
39
39
|
FAILED: 'failed',
|
|
40
40
|
CANCELLED: 'cancelled',
|
|
41
|
+
RUNNING: 'running',
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
export const STATUS_UPPERCASE = {
|
|
@@ -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;
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
} from 'foremanReact/redux/API/APISelectors';
|
|
6
6
|
import {
|
|
7
7
|
JOB_INVOCATION_KEY,
|
|
8
|
-
GET_TASK,
|
|
9
8
|
GET_TEMPLATE_INVOCATION,
|
|
10
9
|
LIST_TEMPLATE_INVOCATIONS,
|
|
11
10
|
} from './JobInvocationConstants';
|
|
@@ -13,10 +12,8 @@ import {
|
|
|
13
12
|
export const selectItems = state =>
|
|
14
13
|
selectAPIResponse(state, JOB_INVOCATION_KEY);
|
|
15
14
|
|
|
16
|
-
export const selectTask = state => selectAPIResponse(state, GET_TASK);
|
|
17
|
-
|
|
18
15
|
export const selectTaskCancelable = state =>
|
|
19
|
-
|
|
16
|
+
selectItems(state).task?.cancellable || false;
|
|
20
17
|
|
|
21
18
|
export const selectTemplateInvocation = hostID => state =>
|
|
22
19
|
selectAPIResponse(state, `${GET_TEMPLATE_INVOCATION}_${hostID}`);
|
|
@@ -58,6 +58,12 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
58
58
|
);
|
|
59
59
|
}, [jobId, reportTemplateJobId, templateInputId]);
|
|
60
60
|
|
|
61
|
+
const isCreateReportDisabled =
|
|
62
|
+
!canGenerateReportTemplates ||
|
|
63
|
+
task?.state === STATUS.RUNNING ||
|
|
64
|
+
task?.state === STATUS.PENDING ||
|
|
65
|
+
reportHref === undefined;
|
|
66
|
+
|
|
61
67
|
const onActionFocus = useCallback(() => {
|
|
62
68
|
const element = document.getElementById(
|
|
63
69
|
`toggle-split-button-action-primary-${jobId}`
|
|
@@ -135,9 +141,7 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
135
141
|
<DropdownItem
|
|
136
142
|
ouiaId="change-enabled-recurring-dropdown-item"
|
|
137
143
|
onClick={() =>
|
|
138
|
-
dispatch(
|
|
139
|
-
enableRecurringLogic(recurrence?.id, recurringEnabled, jobId)
|
|
140
|
-
)
|
|
144
|
+
dispatch(enableRecurringLogic(recurrence?.id, recurringEnabled))
|
|
141
145
|
}
|
|
142
146
|
key="change-enabled-recurring"
|
|
143
147
|
component="button"
|
|
@@ -153,9 +157,7 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
153
157
|
</DropdownItem>,
|
|
154
158
|
<DropdownItem
|
|
155
159
|
ouiaId="cancel-recurring-dropdown-item"
|
|
156
|
-
onClick={() =>
|
|
157
|
-
dispatch(cancelRecurringLogic(recurrence?.id, jobId))
|
|
158
|
-
}
|
|
160
|
+
onClick={() => dispatch(cancelRecurringLogic(recurrence?.id))}
|
|
159
161
|
key="cancel-recurring"
|
|
160
162
|
component="button"
|
|
161
163
|
isDisabled={
|
|
@@ -168,7 +170,7 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
168
170
|
</DropdownItem>,
|
|
169
171
|
]
|
|
170
172
|
: [],
|
|
171
|
-
[recurrence, recurringEnabled, canEditRecurringLogic, dispatch
|
|
173
|
+
[recurrence, recurringEnabled, canEditRecurringLogic, dispatch]
|
|
172
174
|
);
|
|
173
175
|
|
|
174
176
|
const dropdownItems = useMemo(
|
|
@@ -280,11 +282,7 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
280
282
|
className="button-create-report"
|
|
281
283
|
href={reportHref}
|
|
282
284
|
variant="secondary"
|
|
283
|
-
isDisabled={
|
|
284
|
-
!canGenerateReportTemplates ||
|
|
285
|
-
task?.state === STATUS.PENDING ||
|
|
286
|
-
reportHref === undefined
|
|
287
|
-
}
|
|
285
|
+
isDisabled={isCreateReportDisabled}
|
|
288
286
|
>
|
|
289
287
|
{__(`Create report`)}
|
|
290
288
|
</Button>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
showTemplateInvocationUrl,
|
|
12
12
|
templateInvocationPageUrl,
|
|
13
13
|
GET_TEMPLATE_INVOCATION,
|
|
14
|
+
AUTO_REFRESH_INTERVAL_MS,
|
|
14
15
|
} from './JobInvocationConstants';
|
|
15
16
|
import {
|
|
16
17
|
selectTemplateInvocationStatus,
|
|
@@ -67,7 +68,7 @@ export const TemplateInvocation = ({
|
|
|
67
68
|
showCommand,
|
|
68
69
|
setShowCommand,
|
|
69
70
|
}) => {
|
|
70
|
-
const
|
|
71
|
+
const timeoutRef = useRef(null);
|
|
71
72
|
const templateURL = showTemplateInvocationUrl(hostID, jobID);
|
|
72
73
|
const hostDetailsPageUrl = useForemanHostDetailsPageUrl();
|
|
73
74
|
|
|
@@ -81,43 +82,48 @@ export const TemplateInvocation = ({
|
|
|
81
82
|
}, [response]);
|
|
82
83
|
|
|
83
84
|
useEffect(() => {
|
|
84
|
-
|
|
85
|
+
let cancelled = false;
|
|
86
|
+
|
|
87
|
+
const schedulePoll = () => {
|
|
88
|
+
if (cancelled) return;
|
|
85
89
|
dispatch(
|
|
86
90
|
APIActions.get({
|
|
87
91
|
url: templateURL,
|
|
88
92
|
key: `${GET_TEMPLATE_INVOCATION}_${hostID}`,
|
|
93
|
+
handleSuccess: ({ data }) => {
|
|
94
|
+
if (cancelled) return;
|
|
95
|
+
const isFinished = data?.finished ?? true;
|
|
96
|
+
// eslint-disable-next-line camelcase
|
|
97
|
+
const autoRefresh = data?.auto_refresh || false;
|
|
98
|
+
if (!isFinished && autoRefresh) {
|
|
99
|
+
timeoutRef.current = setTimeout(
|
|
100
|
+
schedulePoll,
|
|
101
|
+
AUTO_REFRESH_INTERVAL_MS
|
|
102
|
+
);
|
|
103
|
+
} else {
|
|
104
|
+
timeoutRef.current = null;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
handleError: () => {
|
|
108
|
+
if (cancelled) return;
|
|
109
|
+
timeoutRef.current = null;
|
|
110
|
+
},
|
|
89
111
|
})
|
|
90
112
|
);
|
|
91
113
|
};
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
intervalRef.current = null;
|
|
96
|
-
}
|
|
115
|
+
clearTimeout(timeoutRef.current);
|
|
116
|
+
timeoutRef.current = null;
|
|
97
117
|
|
|
98
118
|
if (isExpanded) {
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
intervalRef.current = setInterval(() => {
|
|
104
|
-
const latestResponse = responseRef.current;
|
|
105
|
-
const finished = latestResponse?.finished ?? true;
|
|
106
|
-
// eslint-disable-next-line camelcase
|
|
107
|
-
const autoRefresh = latestResponse?.auto_refresh || false;
|
|
108
|
-
|
|
109
|
-
if (!finished && autoRefresh) {
|
|
110
|
-
dispatchFetch();
|
|
111
|
-
} else if (intervalRef.current) {
|
|
112
|
-
clearInterval(intervalRef.current);
|
|
113
|
-
}
|
|
114
|
-
}, 5000);
|
|
119
|
+
if (responseRef.current?.finished) return undefined;
|
|
120
|
+
schedulePoll();
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
return () => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
cancelled = true;
|
|
125
|
+
clearTimeout(timeoutRef.current);
|
|
126
|
+
timeoutRef.current = null;
|
|
121
127
|
};
|
|
122
128
|
}, [isExpanded, dispatch, templateURL, hostID]);
|
|
123
129
|
|