foreman_remote_execution 16.0.3 → 16.0.5

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_templates_controller.rb +2 -1
  3. data/app/controllers/cockpit_controller.rb +2 -2
  4. data/app/controllers/job_invocations_controller.rb +28 -1
  5. data/app/controllers/template_invocations_controller.rb +1 -1
  6. data/app/helpers/hosts_extensions_helper.rb +1 -1
  7. data/app/lib/actions/remote_execution/run_host_job.rb +5 -28
  8. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -1
  9. data/app/models/job_template.rb +5 -0
  10. data/app/models/{ssh_execution_provider.rb → script_execution_provider.rb} +2 -4
  11. data/config/initializers/inflections.rb +0 -1
  12. data/config/routes.rb +1 -0
  13. data/lib/foreman_remote_execution/engine.rb +10 -257
  14. data/lib/foreman_remote_execution/plugin.rb +246 -0
  15. data/lib/foreman_remote_execution/version.rb +1 -1
  16. data/test/functional/api/v2/job_invocations_controller_test.rb +1 -1
  17. data/test/functional/api/v2/job_templates_controller_test.rb +29 -0
  18. data/test/unit/actions/run_host_job_test.rb +1 -1
  19. data/test/unit/job_invocation_report_template_test.rb +6 -6
  20. data/test/unit/remote_execution_provider_test.rb +19 -19
  21. data/webpack/JobInvocationDetail/CheckboxesActions.js +196 -0
  22. data/webpack/JobInvocationDetail/{JobInvocationHostTableToolbar.js → DropdownFilter.js} +3 -6
  23. data/webpack/JobInvocationDetail/JobInvocationConstants.js +7 -7
  24. data/webpack/JobInvocationDetail/JobInvocationDetail.scss +32 -0
  25. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +221 -91
  26. data/webpack/JobInvocationDetail/JobInvocationSelectors.js +30 -3
  27. data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +8 -22
  28. data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +20 -27
  29. data/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +118 -0
  30. data/webpack/JobInvocationDetail/TemplateInvocation.js +54 -24
  31. data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +8 -10
  32. data/webpack/JobInvocationDetail/TemplateInvocationPage.js +1 -1
  33. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +1 -1
  34. data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +202 -0
  35. data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +34 -28
  36. data/webpack/JobInvocationDetail/index.js +64 -31
  37. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +26 -7
  38. data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +1 -1
  39. metadata +9 -7
  40. data/webpack/JobInvocationDetail/OpenAlInvocations.js +0 -111
  41. data/webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js +0 -110
@@ -0,0 +1,196 @@
1
+ /* eslint-disable camelcase */
2
+ import {
3
+ Button,
4
+ Dropdown,
5
+ DropdownItem,
6
+ DropdownList,
7
+ MenuToggle,
8
+ Tooltip,
9
+ } from '@patternfly/react-core';
10
+ import {
11
+ EllipsisVIcon,
12
+ OutlinedWindowRestoreIcon,
13
+ } from '@patternfly/react-icons';
14
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
15
+ import { foremanUrl } from 'foremanReact/common/helpers';
16
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
17
+ import PropTypes from 'prop-types';
18
+ import React, { useState } from 'react';
19
+ import { useSelector } from 'react-redux';
20
+ import {
21
+ templateInvocationPageUrl,
22
+ MAX_HOSTS_API_SIZE,
23
+ DIRECT_OPEN_HOST_LIMIT,
24
+ } from './JobInvocationConstants';
25
+ import { selectHasPermission, selectItems } from './JobInvocationSelectors';
26
+ import OpenAllInvocationsModal, { PopupAlert } from './OpenAllInvocationsModal';
27
+
28
+ export const CheckboxesActions = ({
29
+ selectedIds,
30
+ failedCount,
31
+ jobID,
32
+ filter,
33
+ bulkParams,
34
+ }) => {
35
+ const [isModalOpen, setIsModalOpen] = useState(false);
36
+ const [showAlert, setShowAlert] = useState(false);
37
+ const [isOpenFailed, setIsOpenFailed] = useState(false);
38
+ const hasPermission = useSelector(
39
+ selectHasPermission('create_job_invocations')
40
+ );
41
+ const jobSearchQuery = useSelector(selectItems)?.targeting?.search_query;
42
+ const filterQuery =
43
+ filter && filter !== 'all_statuses'
44
+ ? ` and job_invocation.result = ${filter}`
45
+ : '';
46
+ const combinedQuery = `${bulkParams}${filterQuery}`;
47
+
48
+ const { response: failedHostsData } = useAPI(
49
+ 'get',
50
+ foremanUrl(`/api/job_invocations/${jobID}/hosts`),
51
+ {
52
+ params: {
53
+ per_page: MAX_HOSTS_API_SIZE,
54
+ search: `job_invocation.result = failed`,
55
+ },
56
+ skip: failedCount === 0,
57
+ }
58
+ );
59
+
60
+ const failedHosts = failedHostsData?.results || [];
61
+
62
+ const openLink = url => {
63
+ const newWin = window.open(url);
64
+
65
+ if (!newWin || newWin.closed || typeof newWin.closed === 'undefined') {
66
+ setShowAlert(true);
67
+ }
68
+ };
69
+
70
+ const handleOpenHosts = async (type = 'all') => {
71
+ if (type === 'failed') {
72
+ if (failedCount <= DIRECT_OPEN_HOST_LIMIT) {
73
+ failedHosts.forEach(host =>
74
+ openLink(templateInvocationPageUrl(host.id, jobID))
75
+ );
76
+ return;
77
+ }
78
+ setIsOpenFailed(true);
79
+ setIsModalOpen(true);
80
+ return;
81
+ }
82
+
83
+ if (selectedIds.length <= DIRECT_OPEN_HOST_LIMIT) {
84
+ selectedIds.forEach(id => openLink(templateInvocationPageUrl(id, jobID)));
85
+ return;
86
+ }
87
+
88
+ setIsOpenFailed(false);
89
+ setIsModalOpen(true);
90
+ };
91
+
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>
128
+ );
129
+ };
130
+
131
+ const OpenAllButton = () => (
132
+ <Button
133
+ aria-label="open all template invocations in new tab"
134
+ className="open-all-button"
135
+ isDisabled={selectedIds.length === 0}
136
+ isInline
137
+ onClick={() => handleOpenHosts('all')}
138
+ ouiaId="template-invocation-new-tab-button"
139
+ variant="link"
140
+ >
141
+ <Tooltip content={__('Open selected in new tab')}>
142
+ <OutlinedWindowRestoreIcon />
143
+ </Tooltip>
144
+ </Button>
145
+ );
146
+
147
+ const RerunSelectedButton = () => (
148
+ <Button
149
+ aria-label="rerun selected template invocations"
150
+ className="rerun-selected-button"
151
+ component="a"
152
+ href={foremanUrl(
153
+ `/job_invocations/${jobID}/rerun?search=(${jobSearchQuery}) AND (${combinedQuery})`
154
+ )}
155
+ // eslint-disable-next-line camelcase
156
+ isDisabled={selectedIds.length === 0 || !hasPermission}
157
+ isInline
158
+ ouiaId="template-invocation-rerun-selected-button"
159
+ variant="secondary"
160
+ >
161
+ {__('Rerun')}
162
+ </Button>
163
+ );
164
+
165
+ return (
166
+ <>
167
+ <OpenAllButton />
168
+ <RerunSelectedButton />
169
+ <ActionsKebab />
170
+ {showAlert && <PopupAlert setShowAlert={setShowAlert} />}
171
+ <OpenAllInvocationsModal
172
+ isOpen={isModalOpen}
173
+ onClose={() => setIsModalOpen(false)}
174
+ failedCount={failedCount}
175
+ failedHosts={failedHosts}
176
+ jobID={jobID}
177
+ isOpenFailed={isOpenFailed}
178
+ setShowAlert={setShowAlert}
179
+ selectedIds={selectedIds}
180
+ />
181
+ </>
182
+ );
183
+ };
184
+
185
+ CheckboxesActions.propTypes = {
186
+ selectedIds: PropTypes.array.isRequired,
187
+ failedCount: PropTypes.number.isRequired,
188
+ jobID: PropTypes.string.isRequired,
189
+ bulkParams: PropTypes.string,
190
+ filter: PropTypes.string,
191
+ };
192
+
193
+ CheckboxesActions.defaultProps = {
194
+ bulkParams: '',
195
+ filter: '',
196
+ };
@@ -10,10 +10,7 @@ import {
10
10
  } from '@patternfly/react-core';
11
11
  import { STATUS_TITLES } from './JobInvocationConstants';
12
12
 
13
- const JobInvocationHostTableToolbar = ({
14
- dropdownFilter,
15
- setDropdownFilter,
16
- }) => {
13
+ const DropdownFilter = ({ dropdownFilter, setDropdownFilter }) => {
17
14
  const [isOpen, setIsOpen] = React.useState(false);
18
15
  const onSelect = (_event, itemId) => {
19
16
  setDropdownFilter(itemId);
@@ -60,9 +57,9 @@ const JobInvocationHostTableToolbar = ({
60
57
  );
61
58
  };
62
59
 
63
- JobInvocationHostTableToolbar.propTypes = {
60
+ DropdownFilter.propTypes = {
64
61
  dropdownFilter: PropTypes.string.isRequired,
65
62
  setDropdownFilter: PropTypes.func.isRequired,
66
63
  };
67
64
 
68
- export default JobInvocationHostTableToolbar;
65
+ export default DropdownFilter;
@@ -16,12 +16,17 @@ export const CANCEL_RECURRING_LOGIC = 'CANCEL_RECURRING_LOGIC';
16
16
  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
+ export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
20
+ export const MAX_HOSTS_API_SIZE = 100;
21
+ export const DIRECT_OPEN_HOST_LIMIT = 3;
22
+ export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS';
19
23
  export const currentPermissionsUrl = foremanUrl(
20
24
  '/api/v2/permissions/current_permissions'
21
25
  );
22
- export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
26
+
23
27
  export const showTemplateInvocationUrl = (hostID, jobID) =>
24
28
  `/show_template_invocation_by_host/${hostID}/job_invocation/${jobID}`;
29
+ export const LIST_TEMPLATE_INVOCATIONS = 'LIST_TEMPLATE_INVOCATIONS';
25
30
 
26
31
  export const templateInvocationPageUrl = (hostID, jobID) =>
27
32
  `/job_invocations_detail/${jobID}/host_invocation/${hostID}`;
@@ -83,7 +88,7 @@ const Columns = () => {
83
88
 
84
89
  return {
85
90
  expand: {
86
- title: '',
91
+ title: ' ',
87
92
  weight: 0,
88
93
  wrapper: () => null,
89
94
  },
@@ -135,11 +140,6 @@ const Columns = () => {
135
140
  },
136
141
  weight: 5,
137
142
  },
138
- actions: {
139
- title: '',
140
- weight: 6,
141
- wrapper: () => null,
142
- },
143
143
  };
144
144
  };
145
145
 
@@ -7,6 +7,16 @@
7
7
  height: $chart_size;
8
8
  width: $chart_size;
9
9
  margin-bottom: 10px;
10
+
11
+ svg {
12
+ position: relative;
13
+ overflow: visible;
14
+ z-index: 999;
15
+ }
16
+
17
+ path {
18
+ cursor: pointer;
19
+ }
10
20
  }
11
21
 
12
22
  .chart-legend {
@@ -28,6 +38,10 @@
28
38
  font-weight: normal;
29
39
  }
30
40
  }
41
+
42
+ text, path {
43
+ cursor: pointer;
44
+ }
31
45
  }
32
46
 
33
47
  .pf-v5-c-divider {
@@ -38,14 +52,26 @@
38
52
  height: $chart_size;
39
53
  }
40
54
  }
55
+
41
56
  .job-additional-info {
42
57
  padding: 0;
43
58
  margin-bottom: -10px;
44
59
  }
60
+
45
61
  .job-details-table-section {
46
62
  section:nth-child(1) {
47
63
  padding: 0;
48
64
  }
65
+
66
+ .pf-v5-c-toolbar__group.pf-m-button-group {
67
+ display: flex;
68
+ align-items: center;
69
+ }
70
+
71
+ .open-all-button {
72
+ margin-left: 10px;
73
+ margin-right: 15px;
74
+ }
49
75
  }
50
76
 
51
77
  .template-invocation {
@@ -108,3 +134,9 @@
108
134
  margin-bottom: 10px;
109
135
  }
110
136
  }
137
+
138
+ .open-all-modal .pf-v5-c-modal-box__title-text {
139
+ white-space: normal;
140
+ text-overflow: unset;
141
+ word-break: break-word;
142
+ }