foreman_remote_execution 16.2.0 → 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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +3 -0
  3. data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +3 -0
  4. data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +3 -0
  5. data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +3 -0
  6. data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +3 -0
  7. data/app/assets/javascripts/foreman_remote_execution/locale/ka/foreman_remote_execution.js +3 -0
  8. data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +4 -1
  9. data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +3 -0
  10. data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +4 -1
  11. data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +4 -1
  12. data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +3 -0
  13. data/config/routes.rb +2 -1
  14. data/extra/cockpit/foreman-cockpit-session +3 -2
  15. data/lib/foreman_remote_execution/version.rb +1 -1
  16. data/locale/de/foreman_remote_execution.po +3 -0
  17. data/locale/en_GB/foreman_remote_execution.po +3 -0
  18. data/locale/es/foreman_remote_execution.po +3 -0
  19. data/locale/foreman_remote_execution.pot +17 -13
  20. data/locale/fr/foreman_remote_execution.po +3 -0
  21. data/locale/ja/foreman_remote_execution.po +3 -0
  22. data/locale/ka/foreman_remote_execution.po +3 -0
  23. data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  24. data/locale/ko/foreman_remote_execution.po +4 -1
  25. data/locale/pt_BR/foreman_remote_execution.po +3 -0
  26. data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  27. data/locale/ru/foreman_remote_execution.po +4 -1
  28. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  29. data/locale/zh_CN/foreman_remote_execution.po +4 -1
  30. data/locale/zh_TW/foreman_remote_execution.po +3 -0
  31. data/webpack/JobInvocationDetail/CheckboxesActions.js +39 -30
  32. data/webpack/JobInvocationDetail/JobInvocationConstants.js +0 -1
  33. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +156 -170
  34. data/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +27 -32
  35. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +10 -2
  36. data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +46 -30
  37. data/webpack/JobInvocationDetail/index.js +13 -2
  38. data/webpack/JobWizard/Footer.js +7 -1
  39. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +1 -1
  40. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +5 -6
  41. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +5 -19
  42. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +19 -3
  43. metadata +2 -3
  44. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +0 -36
@@ -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 URI from 'urijs';
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
- const apiOptions = { key: JOB_INVOCATION_HOSTS };
52
+
54
53
  const history = useHistory();
55
- const [selectedFilter, setSelectedFilter] = useState(initialFilter);
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
- useEffect(() => {
60
- if (initialFilter !== selectedFilter) {
61
- wrapSetSelectedFilter(initialFilter);
62
- }
63
- // eslint-disable-next-line react-hooks/exhaustive-deps
64
- }, [initialFilter]);
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 constructFilter = (
74
- filter = selectedFilter,
75
- search = urlSearchQuery
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 defaultParams = useMemo(
89
- () => ({
90
- ...(urlPage ? { page: Number(urlPage) } : {}),
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
- useAPI('get', `/job_invocations/${id}/hosts`, {
98
- params: {
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
- const [allPagesResponse, setAllPagesResponse] = useState([]);
112
- const apiAllParams = {
113
- page: 1,
114
- per_page: Math.min(response?.subtotal || 1, MAX_HOSTS_API_SIZE),
115
- search: constructFilter(selectedFilter, urlSearchQuery),
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 { response: allResponse, setAPIOptions: setAllAPIOptions } = useAPI(
119
- 'get',
120
- `/api/job_invocations/${id}/hosts`,
121
- {
122
- params: apiAllParams,
123
- key: ALL_JOB_HOSTS,
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
- if (response?.subtotal) {
129
- setAllAPIOptions(prevOptions => ({
130
- ...prevOptions,
131
- params: apiAllParams,
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
- }, [response?.subtotal, selectedFilter, urlSearchQuery]);
190
+ }, []);
136
191
 
137
192
  useEffect(() => {
138
- if (allResponse?.results) {
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: response?.results,
209
+ results: apiResponse?.results,
159
210
  metadata: {
160
- total: response?.total,
161
- page: response?.page,
162
- selectable: response?.subtotal,
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
- ? allHostIds.filter(hostId => !exclusionSet.has(hostId))
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: response?.results || [],
270
- total: response?.total || 0,
247
+ results: apiResponse?.results || [],
248
+ total: apiResponse?.total || 0,
271
249
  per_page: defaultParams?.perPage,
272
250
  page: defaultParams?.page,
273
- subtotal: response?.subtotal || 0,
274
- message: response?.message || 'error',
251
+ subtotal: apiResponse?.subtotal || 0,
252
+ message: apiResponse?.message || 'error',
275
253
  },
276
254
  status,
277
- setAPIOptions: wrapSetAPIOptions,
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={selectedFilter}
342
- setDropdownFilter={wrapSetSelectedFilter}
327
+ dropdownFilter={initialFilter}
328
+ setDropdownFilter={handleFilterChange}
343
329
  />,
344
330
  <CheckboxesActions
345
331
  bulkParams={selectedCount > 0 ? fetchBulkParams() : null}
346
332
  selectedIds={selectedIds}
347
- failedCount={failedCount}
333
+ allJobs={results}
348
334
  jobID={id}
349
335
  key="checkboxes-actions"
350
- filter={selectedFilter}
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: response?.page || Number(urlPage),
365
- per_page: response?.per_page || Number(urlPerPage),
351
+ page: defaultParams.page || Number(urlPage),
352
+ per_page: defaultParams.per_page || Number(urlPerPage),
366
353
  order: urlOrder,
367
354
  }}
368
- page={response?.page || Number(urlPage)}
369
- perPage={response?.per_page || Number(urlPerPage)}
370
- setParams={wrapSetAPIOptions}
371
- itemCount={response?.subtotal}
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 && response?.message
378
- ? response.message
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 = isOpenFailed ? 'failed' : 'selected';
30
+ const modalText = () => {
31
+ if (isOpenFailed) return 'failed';
32
+ if (selectedIds.length > 0) return 'selected';
33
+ return 'current page';
34
+ };
37
35
 
38
- const openLink = url => {
39
- const newWin = window.open(url);
40
- if (!newWin || newWin.closed || typeof newWin.closed === 'undefined') {
41
- setShowAlert(true);
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
- const hostsToOpen = isOpenFailed
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
- {__('This will open a new tab for each invocation. The maximum is 100.')}
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 match={{ params: { id: `${jobId}` } }} />
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 match={{ params: { id: `${jobId}` } }} />
209
+ <JobInvocationDetailPage
210
+ match={{ params: { id: `${jobId}` } }}
211
+ {...props}
212
+ />
205
213
  </Provider>
206
214
  );
207
215