foreman_remote_execution 16.0.5 → 16.2.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +54 -30
  3. data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +52 -28
  4. data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +55 -31
  5. data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +61 -37
  6. data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +62 -38
  7. data/app/assets/javascripts/foreman_remote_execution/locale/ka/foreman_remote_execution.js +53 -29
  8. data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +61 -37
  9. data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +55 -31
  10. data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +52 -28
  11. data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +61 -37
  12. data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +52 -28
  13. data/app/controllers/api/v2/job_invocations_controller.rb +33 -6
  14. data/app/helpers/remote_execution_helper.rb +1 -1
  15. data/app/views/api/v2/job_templates/main.json.rabl +1 -1
  16. data/config/routes.rb +3 -2
  17. data/lib/foreman_remote_execution/plugin.rb +1 -7
  18. data/lib/foreman_remote_execution/version.rb +1 -1
  19. data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  20. data/locale/de/foreman_remote_execution.po +54 -30
  21. data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  22. data/locale/en_GB/foreman_remote_execution.po +52 -28
  23. data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  24. data/locale/es/foreman_remote_execution.po +55 -31
  25. data/locale/foreman_remote_execution.pot +193 -156
  26. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  27. data/locale/fr/foreman_remote_execution.po +61 -37
  28. data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  29. data/locale/ja/foreman_remote_execution.po +62 -38
  30. data/locale/ka/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  31. data/locale/ka/foreman_remote_execution.po +53 -29
  32. data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  33. data/locale/ko/foreman_remote_execution.po +61 -37
  34. data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  35. data/locale/pt_BR/foreman_remote_execution.po +55 -31
  36. data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  37. data/locale/ru/foreman_remote_execution.po +52 -28
  38. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  39. data/locale/zh_CN/foreman_remote_execution.po +61 -37
  40. data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  41. data/locale/zh_TW/foreman_remote_execution.po +52 -28
  42. data/webpack/JobInvocationDetail/CheckboxesActions.js +176 -46
  43. data/webpack/JobInvocationDetail/JobInvocationConstants.js +2 -3
  44. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +14 -1
  45. data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +3 -1
  46. data/webpack/JobInvocationDetail/TemplateInvocation.js +63 -50
  47. data/webpack/JobInvocationDetail/TemplateInvocationPage.js +1 -0
  48. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +2 -4
  49. data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +28 -26
  50. data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +4 -0
  51. data/webpack/JobInvocationDetail/index.js +12 -13
  52. data/webpack/Routes/routes.js +1 -1
  53. data/webpack/react_app/components/HostKebab/KebabItems.js +2 -3
  54. metadata +2 -2
@@ -1,6 +1,7 @@
1
- /* eslint-disable camelcase */
1
+ /* eslint-disable max-lines */
2
2
  import {
3
3
  Button,
4
+ Divider,
4
5
  Dropdown,
5
6
  DropdownItem,
6
7
  DropdownList,
@@ -11,20 +12,96 @@ import {
11
12
  EllipsisVIcon,
12
13
  OutlinedWindowRestoreIcon,
13
14
  } from '@patternfly/react-icons';
14
- import { sprintf, translate as __ } from 'foremanReact/common/I18n';
15
+ import axios from 'axios';
15
16
  import { foremanUrl } from 'foremanReact/common/helpers';
16
17
  import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
18
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
19
+ import { addToast } from 'foremanReact/components/ToastsList';
17
20
  import PropTypes from 'prop-types';
18
21
  import React, { useState } from 'react';
19
- import { useSelector } from 'react-redux';
22
+ import { useDispatch, useSelector } from 'react-redux';
23
+
20
24
  import {
21
- templateInvocationPageUrl,
22
- MAX_HOSTS_API_SIZE,
23
25
  DIRECT_OPEN_HOST_LIMIT,
26
+ MAX_HOSTS_API_SIZE,
27
+ templateInvocationPageUrl,
24
28
  } from './JobInvocationConstants';
25
- import { selectHasPermission, selectItems } from './JobInvocationSelectors';
29
+ import {
30
+ selectHasPermission,
31
+ selectTaskCancelable,
32
+ } from './JobInvocationSelectors';
26
33
  import OpenAllInvocationsModal, { PopupAlert } from './OpenAllInvocationsModal';
27
34
 
35
+ /* eslint-disable camelcase */
36
+ const ActionsKebab = ({
37
+ selectedIds,
38
+ failedCount,
39
+ isTaskCancelable,
40
+ hasCancelPermission,
41
+ handleTaskAction,
42
+ handleOpenHosts,
43
+ isDropdownOpen,
44
+ setIsDropdownOpen,
45
+ }) => {
46
+ const dropdownItems = [
47
+ <DropdownItem
48
+ ouiaId="cancel-host-dropdown-item"
49
+ onClick={() => handleTaskAction('cancel')}
50
+ key="cancel"
51
+ component="button"
52
+ isDisabled={
53
+ selectedIds.length === 0 || !isTaskCancelable || !hasCancelPermission
54
+ }
55
+ >
56
+ {__('Cancel selected')}
57
+ </DropdownItem>,
58
+ <DropdownItem
59
+ ouiaId="abort-host-dropdown-item"
60
+ onClick={() => handleTaskAction('abort')}
61
+ key="abort"
62
+ component="button"
63
+ isDisabled={
64
+ selectedIds.length === 0 || !isTaskCancelable || !hasCancelPermission
65
+ }
66
+ >
67
+ {__('Abort selected')}
68
+ </DropdownItem>,
69
+ <Divider component="li" key="separator" />,
70
+ <DropdownItem
71
+ ouiaId="open-failed-dropdown-item"
72
+ key="open-failed"
73
+ onClick={() => handleOpenHosts('failed')}
74
+ isDisabled={failedCount === 0}
75
+ >
76
+ {sprintf(__('Open all failed runs (%s)'), failedCount)}
77
+ </DropdownItem>,
78
+ ];
79
+
80
+ return (
81
+ <Dropdown
82
+ isOpen={isDropdownOpen}
83
+ onOpenChange={setIsDropdownOpen}
84
+ onSelect={() => setIsDropdownOpen(false)}
85
+ ouiaId="actions-kebab"
86
+ shouldFocusToggleOnSelect
87
+ toggle={toggleRef => (
88
+ <MenuToggle
89
+ aria-label="actions dropdown toggle"
90
+ id="toggle-kebab"
91
+ isExpanded={isDropdownOpen}
92
+ onClick={() => setIsDropdownOpen(prev => !prev)}
93
+ ref={toggleRef}
94
+ variant="plain"
95
+ >
96
+ <EllipsisVIcon />
97
+ </MenuToggle>
98
+ )}
99
+ >
100
+ <DropdownList>{dropdownItems}</DropdownList>
101
+ </Dropdown>
102
+ );
103
+ };
104
+
28
105
  export const CheckboxesActions = ({
29
106
  selectedIds,
30
107
  failedCount,
@@ -35,10 +112,17 @@ export const CheckboxesActions = ({
35
112
  const [isModalOpen, setIsModalOpen] = useState(false);
36
113
  const [showAlert, setShowAlert] = useState(false);
37
114
  const [isOpenFailed, setIsOpenFailed] = useState(false);
38
- const hasPermission = useSelector(
115
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
116
+ const isTaskCancelable = useSelector(selectTaskCancelable);
117
+ const dispatch = useDispatch();
118
+
119
+ const hasCreatePermission = useSelector(
39
120
  selectHasPermission('create_job_invocations')
40
121
  );
41
- const jobSearchQuery = useSelector(selectItems)?.targeting?.search_query;
122
+ const hasCancelPermission = useSelector(
123
+ selectHasPermission('cancel_job_invocations')
124
+ );
125
+ const jobSearchQuery = `job_invocation.id = ${jobID}`;
42
126
  const filterQuery =
43
127
  filter && filter !== 'all_statuses'
44
128
  ? ` and job_invocation.result = ${filter}`
@@ -89,43 +173,64 @@ export const CheckboxesActions = ({
89
173
  setIsModalOpen(true);
90
174
  };
91
175
 
92
- const ActionsKebab = () => {
93
- const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
94
-
95
- const dropdownItems = [
96
- <DropdownItem
97
- ouiaId="open-failed-dropdown-item"
98
- key="open-failed"
99
- onClick={() => handleOpenHosts('failed')}
100
- isDisabled={failedCount === 0}
101
- >
102
- {sprintf(__('Open all failed runs (%s)'), failedCount)}
103
- </DropdownItem>,
104
- ];
105
-
106
- return (
107
- <Dropdown
108
- isOpen={isDropdownOpen}
109
- onOpenChange={setIsDropdownOpen}
110
- onSelect={() => setIsDropdownOpen(false)}
111
- ouiaId="actions-kebab"
112
- shouldFocusToggleOnSelect
113
- toggle={toggleRef => (
114
- <MenuToggle
115
- aria-label="actions dropdown toggle"
116
- id="toggle-kebab"
117
- isExpanded={isDropdownOpen}
118
- onClick={() => setIsDropdownOpen(prev => !prev)}
119
- ref={toggleRef}
120
- variant="plain"
121
- >
122
- <EllipsisVIcon />
123
- </MenuToggle>
124
- )}
125
- >
126
- <DropdownList>{dropdownItems}</DropdownList>
127
- </Dropdown>
176
+ const cancelJobTasks = (search, action) => async () => {
177
+ dispatch(
178
+ addToast({
179
+ key: `cancel-job-info`,
180
+ type: 'info',
181
+ message: sprintf(__('Trying to %s the task'), action),
182
+ })
128
183
  );
184
+
185
+ try {
186
+ const response = await axios.post(
187
+ `/api/v2/job_invocations/${jobID}/cancel`,
188
+ {
189
+ search,
190
+ force: action !== 'cancel',
191
+ }
192
+ );
193
+
194
+ const cancelledTasks = response.data?.cancelled;
195
+ const pastTenseAction =
196
+ action === 'cancel' ? __('cancelled') : __('aborted');
197
+
198
+ if (cancelledTasks && cancelledTasks.length > 0) {
199
+ const idList = cancelledTasks.join(', ');
200
+ dispatch(
201
+ addToast({
202
+ key: `success-tasks-cancelled`,
203
+ type: 'success',
204
+ message: sprintf(
205
+ __('%s task(s) successfully %s: %s'),
206
+ cancelledTasks.length,
207
+ pastTenseAction,
208
+ idList
209
+ ),
210
+ })
211
+ );
212
+ } else {
213
+ dispatch(
214
+ addToast({
215
+ key: `warn-no-tasks-cancelled-${Date.now()}`,
216
+ type: 'warning',
217
+ message: sprintf(__('Task(s) were not %s'), pastTenseAction),
218
+ })
219
+ );
220
+ }
221
+ } catch (error) {
222
+ dispatch(
223
+ addToast({
224
+ key: `error-cancelling-tasks`,
225
+ type: 'danger',
226
+ message: error.response?.data?.error || __('An error occurred.'),
227
+ })
228
+ );
229
+ }
230
+ };
231
+
232
+ const handleTaskAction = action => {
233
+ dispatch(cancelJobTasks(combinedQuery, action));
129
234
  };
130
235
 
131
236
  const OpenAllButton = () => (
@@ -153,7 +258,7 @@ export const CheckboxesActions = ({
153
258
  `/job_invocations/${jobID}/rerun?search=(${jobSearchQuery}) AND (${combinedQuery})`
154
259
  )}
155
260
  // eslint-disable-next-line camelcase
156
- isDisabled={selectedIds.length === 0 || !hasPermission}
261
+ isDisabled={selectedIds.length === 0 || !hasCreatePermission}
157
262
  isInline
158
263
  ouiaId="template-invocation-rerun-selected-button"
159
264
  variant="secondary"
@@ -166,7 +271,16 @@ export const CheckboxesActions = ({
166
271
  <>
167
272
  <OpenAllButton />
168
273
  <RerunSelectedButton />
169
- <ActionsKebab />
274
+ <ActionsKebab
275
+ selectedIds={selectedIds}
276
+ failedCount={failedCount}
277
+ isTaskCancelable={isTaskCancelable}
278
+ hasCancelPermission={hasCancelPermission}
279
+ handleTaskAction={handleTaskAction}
280
+ handleOpenHosts={handleOpenHosts}
281
+ isDropdownOpen={isDropdownOpen}
282
+ setIsDropdownOpen={setIsDropdownOpen}
283
+ />
170
284
  {showAlert && <PopupAlert setShowAlert={setShowAlert} />}
171
285
  <OpenAllInvocationsModal
172
286
  isOpen={isModalOpen}
@@ -182,6 +296,22 @@ export const CheckboxesActions = ({
182
296
  );
183
297
  };
184
298
 
299
+ ActionsKebab.propTypes = {
300
+ selectedIds: PropTypes.array.isRequired,
301
+ failedCount: PropTypes.number.isRequired,
302
+ isTaskCancelable: PropTypes.bool,
303
+ hasCancelPermission: PropTypes.bool,
304
+ handleTaskAction: PropTypes.func.isRequired,
305
+ handleOpenHosts: PropTypes.func.isRequired,
306
+ isDropdownOpen: PropTypes.bool.isRequired,
307
+ setIsDropdownOpen: PropTypes.func.isRequired,
308
+ };
309
+
310
+ ActionsKebab.defaultProps = {
311
+ isTaskCancelable: false,
312
+ hasCancelPermission: false,
313
+ };
314
+
185
315
  CheckboxesActions.propTypes = {
186
316
  selectedIds: PropTypes.array.isRequired,
187
317
  failedCount: PropTypes.number.isRequired,
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable camelcase */
2
2
  import React from 'react';
3
- import { foremanUrl } from 'foremanReact/common/helpers';
4
3
  import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { foremanUrl } from 'foremanReact/common/helpers';
5
5
  import { useForemanHostDetailsPageUrl } from 'foremanReact/Root/Context/ForemanContext';
6
6
  import JobStatusIcon from '../react_app/components/RecentJobsCard/JobStatusIcon';
7
7
 
@@ -31,8 +31,7 @@ export const LIST_TEMPLATE_INVOCATIONS = 'LIST_TEMPLATE_INVOCATIONS';
31
31
  export const templateInvocationPageUrl = (hostID, jobID) =>
32
32
  `/job_invocations_detail/${jobID}/host_invocation/${hostID}`;
33
33
 
34
- export const jobInvocationDetailsUrl = id =>
35
- `/experimental/job_invocations_detail/${id}`;
34
+ export const jobInvocationDetailsUrl = id => `/job_invocations/${id}`;
36
35
 
37
36
  export const STATUS = {
38
37
  PENDING: 'pending',
@@ -23,7 +23,7 @@ import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableInde
23
23
  import { getControllerSearchProps } from 'foremanReact/constants';
24
24
  import { Icon } from 'patternfly-react';
25
25
  import PropTypes from 'prop-types';
26
- import React, { useEffect, useMemo, useState } from 'react';
26
+ import React, { useEffect, useMemo, useState, useRef } from 'react';
27
27
  import { FormattedMessage } from 'react-intl';
28
28
  import { useHistory } from 'react-router-dom';
29
29
  import URI from 'urijs';
@@ -45,6 +45,7 @@ const JobInvocationHostTable = ({
45
45
  id,
46
46
  initialFilter,
47
47
  onFilterUpdate,
48
+ statusLabel,
48
49
  targeting,
49
50
  }) => {
50
51
  const columns = Columns();
@@ -53,6 +54,7 @@ const JobInvocationHostTable = ({
53
54
  const history = useHistory();
54
55
  const [selectedFilter, setSelectedFilter] = useState(initialFilter);
55
56
  const [expandedHost, setExpandedHost] = useState([]);
57
+ const prevStatusLabel = useRef(statusLabel);
56
58
 
57
59
  useEffect(() => {
58
60
  if (initialFilter !== selectedFilter) {
@@ -138,6 +140,14 @@ const JobInvocationHostTable = ({
138
140
  }
139
141
  }, [allResponse]);
140
142
 
143
+ useEffect(() => {
144
+ if (statusLabel !== prevStatusLabel.current) {
145
+ setAPIOptions(prevOptions => ({ ...prevOptions }));
146
+ prevStatusLabel.current = statusLabel;
147
+ }
148
+ // eslint-disable-next-line react-hooks/exhaustive-deps
149
+ }, [statusLabel]);
150
+
141
151
  const {
142
152
  updateSearchQuery: updateSearchQueryBulk,
143
153
  fetchBulkParams,
@@ -408,6 +418,7 @@ const JobInvocationHostTable = ({
408
418
  </div>
409
419
  ) : (
410
420
  <TemplateInvocation
421
+ key={`${result.id}-${result.job_status}`}
411
422
  hostID={result.id}
412
423
  jobID={id}
413
424
  isInTableView
@@ -430,11 +441,13 @@ JobInvocationHostTable.propTypes = {
430
441
  targeting: PropTypes.object.isRequired,
431
442
  failedCount: PropTypes.number.isRequired,
432
443
  initialFilter: PropTypes.string.isRequired,
444
+ statusLabel: PropTypes.string,
433
445
  onFilterUpdate: PropTypes.func,
434
446
  };
435
447
 
436
448
  JobInvocationHostTable.defaultProps = {
437
449
  onFilterUpdate: () => {},
450
+ statusLabel: undefined,
438
451
  };
439
452
 
440
453
  export default JobInvocationHostTable;
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
2
2
  import React, { useEffect, useState } from 'react';
3
3
  import { useDispatch, useSelector } from 'react-redux';
4
4
  import { Button, Split, SplitItem } from '@patternfly/react-core';
5
+ import { UndoIcon } from '@patternfly/react-icons';
5
6
  import {
6
7
  Dropdown,
7
8
  DropdownItem,
@@ -189,7 +190,8 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
189
190
  <DropdownSeparator ouiaId="dropdown-separator-2" key="separator-2" />,
190
191
  <DropdownItem
191
192
  ouiaId="legacy-ui-dropdown-item"
192
- href={`/job_invocations/${jobId}`}
193
+ icon={<UndoIcon />}
194
+ href={`/legacy/job_invocations/${jobId}`}
193
195
  key="legacy-ui"
194
196
  >
195
197
  {__('Legacy UI')}
@@ -57,9 +57,9 @@ export const TemplateInvocation = ({
57
57
  hostID,
58
58
  jobID,
59
59
  isInTableView,
60
+ isExpanded,
60
61
  hostName,
61
62
  hostProxy,
62
- isExpanded,
63
63
  }) => {
64
64
  const intervalRef = useRef(null);
65
65
  const templateURL = showTemplateInvocationUrl(hostID, jobID);
@@ -67,72 +67,76 @@ export const TemplateInvocation = ({
67
67
 
68
68
  const status = useSelector(selectTemplateInvocationStatus(hostID));
69
69
  const response = useSelector(selectTemplateInvocation(hostID));
70
- const finished = response.finished ?? true;
71
- const autoRefresh = response.auto_refresh || false;
72
70
  const dispatch = useDispatch();
73
71
 
72
+ const responseRef = useRef(response);
74
73
  useEffect(() => {
75
- const getData = async () => {
76
- if (
77
- (!isInTableView || (isInTableView && isExpanded)) &&
78
- (Object.keys(response).length === 0 || autoRefresh)
79
- ) {
80
- dispatch(
81
- APIActions.get({
82
- url: templateURL,
83
- key: `${GET_TEMPLATE_INVOCATION}_${hostID}`,
84
- handleError: () => {
85
- if (intervalRef.current) clearInterval(intervalRef.current);
86
- },
87
- })
88
- );
89
- }
74
+ responseRef.current = response;
75
+ }, [response]);
76
+
77
+ const [showOutputType, setShowOutputType] = useState({
78
+ stderr: true,
79
+ stdout: true,
80
+ debug: true,
81
+ });
82
+ const [showTemplatePreview, setShowTemplatePreview] = useState(false);
83
+ const [showCommand, setShowCommand] = useState(false);
84
+
85
+ useEffect(() => {
86
+ const dispatchFetch = () => {
87
+ dispatch(
88
+ APIActions.get({
89
+ url: templateURL,
90
+ key: `${GET_TEMPLATE_INVOCATION}_${hostID}`,
91
+ })
92
+ );
90
93
  };
91
- getData();
92
- if (!finished && autoRefresh) {
94
+
95
+ if (intervalRef.current) {
96
+ clearInterval(intervalRef.current);
97
+ intervalRef.current = null;
98
+ }
99
+
100
+ if (isExpanded) {
101
+ if (isEmpty(responseRef.current)) {
102
+ dispatchFetch();
103
+ }
104
+
93
105
  intervalRef.current = setInterval(() => {
94
- getData();
106
+ const latestResponse = responseRef.current;
107
+ const finished = latestResponse?.finished ?? true;
108
+ // eslint-disable-next-line camelcase
109
+ const autoRefresh = latestResponse?.auto_refresh || false;
110
+
111
+ if (!finished && autoRefresh) {
112
+ dispatchFetch();
113
+ } else if (intervalRef.current) {
114
+ clearInterval(intervalRef.current);
115
+ }
95
116
  }, 5000);
96
117
  }
97
118
 
98
119
  return () => {
99
120
  if (intervalRef.current) {
100
121
  clearInterval(intervalRef.current);
101
- intervalRef.current = null;
102
122
  }
103
123
  };
104
- }, [
105
- dispatch,
106
- templateURL,
107
- isExpanded,
108
- isInTableView,
109
- finished,
110
- autoRefresh,
111
- hostID,
112
- ]);
124
+ }, [isExpanded, dispatch, templateURL, hostID]);
125
+
126
+ if (!isExpanded) {
127
+ return null;
128
+ }
129
+
130
+ if ((status === STATUS.PENDING && isEmpty(response)) || !response) {
131
+ return <Skeleton />;
132
+ }
113
133
 
114
134
  const errorMessage =
115
135
  response?.response?.data?.error?.message ||
116
136
  response?.response?.data?.error ||
117
137
  JSON.stringify(response);
118
- const {
119
- preview,
120
- output,
121
- input_values: inputValues,
122
- task,
123
- permissions,
124
- } = response;
125
- const { id: taskID, cancellable: taskCancellable } = task || {};
126
- const [showOutputType, setShowOutputType] = useState({
127
- stderr: true,
128
- stdout: true,
129
- debug: true,
130
- });
131
- const [showTemplatePreview, setShowTemplatePreview] = useState(false);
132
- const [showCommand, setCommand] = useState(false);
133
- if (status === STATUS.PENDING && isEmpty(response)) {
134
- return <Skeleton />;
135
- } else if (status === STATUS.ERROR) {
138
+
139
+ if (status === STATUS.ERROR) {
136
140
  return (
137
141
  <Alert
138
142
  ouiaId="template-invocation-error-alert"
@@ -146,6 +150,15 @@ export const TemplateInvocation = ({
146
150
  );
147
151
  }
148
152
 
153
+ const {
154
+ preview,
155
+ output,
156
+ input_values: inputValues,
157
+ task,
158
+ permissions,
159
+ } = response;
160
+ const { id: taskID, cancellable: taskCancellable } = task || {};
161
+
149
162
  return (
150
163
  <div
151
164
  id={`template-invocation-${hostID}`}
@@ -159,7 +172,7 @@ export const TemplateInvocation = ({
159
172
  setShowTemplatePreview={setShowTemplatePreview}
160
173
  showTemplatePreview={showTemplatePreview}
161
174
  showCommand={showCommand}
162
- setShowCommand={setCommand}
175
+ setShowCommand={setShowCommand}
163
176
  newTabUrl={templateInvocationPageUrl(hostID, jobID)}
164
177
  isInTableView={isInTableView}
165
178
  copyToClipboard={
@@ -36,6 +36,7 @@ const TemplateInvocationPage = ({
36
36
  hostID={hostID}
37
37
  jobID={jobID}
38
38
  isInTableView={false}
39
+ isExpanded
39
40
  hostName={hostName}
40
41
  hostProxy={hostProxy}
41
42
  />
@@ -96,9 +96,7 @@ describe('JobInvocationDetailPage', () => {
96
96
  );
97
97
 
98
98
  expect(screen.getByText('Description')).toBeInTheDocument();
99
- expect(
100
- container.querySelector('.chart-donut .pf-v5-c-chart')
101
- ).toBeInTheDocument();
99
+ expect(container.querySelector('.chart-donut')).toBeInTheDocument();
102
100
  expect(screen.getByText('2/6')).toBeInTheDocument();
103
101
  expect(screen.getByText('Systems')).toBeInTheDocument();
104
102
  expect(screen.getByText('System status')).toBeInTheDocument();
@@ -178,7 +176,7 @@ describe('JobInvocationDetailPage', () => {
178
176
  .getByText('Legacy UI')
179
177
  .closest('a')
180
178
  .getAttribute('href')
181
- ).toEqual(`/job_invocations/${jobId}`);
179
+ ).toEqual(`/legacy/job_invocations/${jobId}`);
182
180
  });
183
181
 
184
182
  it('shows scheduled date', async () => {