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
@@ -1,51 +1,73 @@
1
+ /* eslint-disable max-lines */
1
2
  /* eslint-disable camelcase */
2
- import PropTypes from 'prop-types';
3
- import React, { useMemo, useEffect, useState } from 'react';
4
- import { Icon } from 'patternfly-react';
5
- import { translate as __ } from 'foremanReact/common/I18n';
6
- import { FormattedMessage } from 'react-intl';
7
- import { Tr, Td, Tbody, ExpandableRowContent } from '@patternfly/react-table';
8
3
  import {
9
4
  EmptyState,
10
- EmptyStateVariant,
11
5
  EmptyStateBody,
12
6
  EmptyStateHeader,
7
+ EmptyStateVariant,
8
+ ToolbarItem,
13
9
  } from '@patternfly/react-core';
10
+ import { ExpandableRowContent, Tbody, Td, Tr } from '@patternfly/react-table';
11
+ import { translate as __ } from 'foremanReact/common/I18n';
14
12
  import { foremanUrl } from 'foremanReact/common/helpers';
15
13
  import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
14
+ import { RowSelectTd } from 'foremanReact/components/HostsIndex/RowSelectTd';
15
+ import SelectAllCheckbox from 'foremanReact/components/PF4/TableIndexPage/Table/SelectAllCheckbox';
16
16
  import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table';
17
- import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage';
18
- import { useSetParamsAndApiAndSearch } from 'foremanReact/components/PF4/TableIndexPage/Table/TableIndexHooks';
19
17
  import {
20
18
  useBulkSelect,
21
19
  useUrlParams,
22
20
  } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
21
+ import { getPageStats } from 'foremanReact/components/PF4/TableIndexPage/Table/helpers';
22
+ import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage';
23
23
  import { getControllerSearchProps } from 'foremanReact/constants';
24
+ import { Icon } from 'patternfly-react';
25
+ import PropTypes from 'prop-types';
26
+ import React, { useEffect, useMemo, useState } from 'react';
27
+ import { FormattedMessage } from 'react-intl';
28
+ import { useHistory } from 'react-router-dom';
29
+ import URI from 'urijs';
30
+ import { CheckboxesActions } from './CheckboxesActions';
31
+ import DropdownFilter from './DropdownFilter';
24
32
  import Columns, {
25
33
  JOB_INVOCATION_HOSTS,
34
+ MAX_HOSTS_API_SIZE,
26
35
  STATUS_UPPERCASE,
36
+ LIST_TEMPLATE_INVOCATIONS,
37
+ ALL_JOB_HOSTS,
27
38
  } from './JobInvocationConstants';
39
+ import { PopupAlert } from './OpenAllInvocationsModal';
28
40
  import { TemplateInvocation } from './TemplateInvocation';
29
- import { OpenAlInvocations, PopupAlert } from './OpenAlInvocations';
30
41
  import { RowActions } from './TemplateInvocationComponents/TemplateActionButtons';
31
- import JobInvocationHostTableToolbar from './JobInvocationHostTableToolbar';
32
42
 
33
43
  const JobInvocationHostTable = ({
44
+ failedCount,
34
45
  id,
35
- targeting,
36
- finished,
37
- autoRefresh,
38
46
  initialFilter,
47
+ onFilterUpdate,
48
+ targeting,
39
49
  }) => {
40
50
  const columns = Columns();
41
51
  const columnNamesKeys = Object.keys(columns);
42
52
  const apiOptions = { key: JOB_INVOCATION_HOSTS };
43
- const [selectedFilter, setSelectedFilter] = useState(initialFilter || '');
53
+ const history = useHistory();
54
+ const [selectedFilter, setSelectedFilter] = useState(initialFilter);
55
+ const [expandedHost, setExpandedHost] = useState([]);
56
+
57
+ useEffect(() => {
58
+ if (initialFilter !== selectedFilter) {
59
+ wrapSetSelectedFilter(initialFilter);
60
+ }
61
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
+ }, [initialFilter]);
63
+
44
64
  const {
45
65
  searchParam: urlSearchQuery = '',
46
66
  page: urlPage,
47
67
  per_page: urlPerPage,
68
+ order: urlOrder,
48
69
  } = useUrlParams();
70
+
49
71
  const constructFilter = (
50
72
  filter = selectedFilter,
51
73
  search = urlSearchQuery
@@ -61,11 +83,21 @@ const JobInvocationHostTable = ({
61
83
  .join(' AND ');
62
84
  };
63
85
 
64
- const search = constructFilter();
65
- const defaultParams = search !== '' ? { search } : {};
66
- if (urlPage) defaultParams.page = Number(urlPage);
67
- if (urlPerPage) defaultParams.per_page = Number(urlPerPage);
68
- const [expandedHost, setExpandedHost] = useState([]);
86
+ const defaultParams = useMemo(
87
+ () => ({
88
+ ...(urlPage ? { page: Number(urlPage) } : {}),
89
+ ...(urlPerPage ? { per_page: Number(urlPerPage) } : {}),
90
+ ...(urlOrder ? { order: urlOrder } : {}),
91
+ }),
92
+ [urlPage, urlPerPage, urlOrder]
93
+ );
94
+
95
+ useAPI('get', `/job_invocations/${id}/hosts`, {
96
+ params: {
97
+ search: defaultParams.search,
98
+ },
99
+ key: LIST_TEMPLATE_INVOCATIONS,
100
+ });
69
101
  const { response, status, setAPIOptions } = useAPI(
70
102
  'get',
71
103
  `/api/job_invocations/${id}/hosts`,
@@ -74,18 +106,93 @@ const JobInvocationHostTable = ({
74
106
  }
75
107
  );
76
108
 
77
- const { params } = useSetParamsAndApiAndSearch({
78
- defaultParams,
79
- apiOptions,
80
- setAPIOptions,
81
- });
109
+ const [allPagesResponse, setAllPagesResponse] = useState([]);
110
+ const apiAllParams = {
111
+ page: 1,
112
+ per_page: Math.min(response?.subtotal || 1, MAX_HOSTS_API_SIZE),
113
+ search: constructFilter(selectedFilter, urlSearchQuery),
114
+ };
115
+
116
+ const { response: allResponse, setAPIOptions: setAllAPIOptions } = useAPI(
117
+ 'get',
118
+ `/api/job_invocations/${id}/hosts`,
119
+ {
120
+ params: apiAllParams,
121
+ key: ALL_JOB_HOSTS,
122
+ }
123
+ );
124
+
125
+ useEffect(() => {
126
+ if (response?.subtotal) {
127
+ setAllAPIOptions(prevOptions => ({
128
+ ...prevOptions,
129
+ params: apiAllParams,
130
+ }));
131
+ }
132
+ // eslint-disable-next-line react-hooks/exhaustive-deps
133
+ }, [response?.subtotal, selectedFilter, urlSearchQuery]);
82
134
 
83
- const { updateSearchQuery: updateSearchQueryBulk } = useBulkSelect({
135
+ useEffect(() => {
136
+ if (allResponse?.results) {
137
+ setAllPagesResponse(allResponse.results);
138
+ }
139
+ }, [allResponse]);
140
+
141
+ const {
142
+ updateSearchQuery: updateSearchQueryBulk,
143
+ fetchBulkParams,
144
+ inclusionSet,
145
+ exclusionSet,
146
+ ...selectAllOptions
147
+ } = useBulkSelect({
148
+ results: response?.results,
149
+ metadata: {
150
+ total: response?.total,
151
+ page: response?.page,
152
+ selectable: response?.subtotal,
153
+ },
84
154
  initialSearchQuery: urlSearchQuery,
85
155
  });
86
- const updateSearchQuery = searchQuery => {
87
- updateSearchQueryBulk(searchQuery);
88
- };
156
+
157
+ const {
158
+ selectAll,
159
+ selectPage,
160
+ selectNone,
161
+ selectedCount,
162
+ selectOne,
163
+ areAllRowsOnPageSelected,
164
+ areAllRowsSelected,
165
+ isSelected,
166
+ } = selectAllOptions;
167
+
168
+ const allHostIds = allPagesResponse?.map(item => item.id) || [];
169
+ const selectedIds =
170
+ areAllRowsSelected() || exclusionSet.size > 0
171
+ ? allHostIds.filter(hostId => !exclusionSet.has(hostId))
172
+ : Array.from(inclusionSet);
173
+
174
+ const { pageRowCount } = getPageStats({
175
+ total: response?.total || 0,
176
+ page: response?.page || urlPage || 1,
177
+ perPage: response?.per_page || urlPerPage || 0,
178
+ });
179
+
180
+ const selectionToolbar = (
181
+ <ToolbarItem key="selectAll">
182
+ <SelectAllCheckbox
183
+ {...{
184
+ selectAll,
185
+ selectPage,
186
+ selectNone,
187
+ selectedCount,
188
+ pageRowCount,
189
+ }}
190
+ totalCount={response?.total}
191
+ areAllRowsOnPageSelected={areAllRowsOnPageSelected()}
192
+ areAllRowsSelected={areAllRowsSelected()}
193
+ />
194
+ </ToolbarItem>
195
+ );
89
196
 
90
197
  const controller = 'hosts';
91
198
  const memoDefaultSearchProps = useMemo(
@@ -96,49 +203,53 @@ const JobInvocationHostTable = ({
96
203
  `/${controller}/auto_complete_search`
97
204
  );
98
205
 
99
- const wrapSetSelectedFilter = filter => {
100
- const filterSearch = constructFilter(filter);
101
- setAPIOptions(prevOptions => {
102
- if (prevOptions.params.search !== filterSearch) {
103
- return {
104
- ...prevOptions,
105
- params: {
106
- ...prevOptions.params,
107
- search: filterSearch,
108
- },
109
- };
110
- }
111
- return prevOptions;
112
- });
113
- setSelectedFilter(filter);
114
- };
206
+ const wrapSetSelectedFilter = newFilter => {
207
+ setSelectedFilter(newFilter);
208
+ onFilterUpdate(newFilter);
115
209
 
116
- useEffect(() => {
117
- const intervalId = setInterval(() => {
118
- if (!finished || autoRefresh) {
119
- setAPIOptions(prevOptions => ({
120
- ...prevOptions,
121
- params: {
122
- ...prevOptions.params,
123
- },
124
- }));
125
- }
126
- }, 5000);
127
-
128
- return () => {
129
- clearInterval(intervalId);
210
+ const filterSearch = constructFilter(newFilter, urlSearchQuery);
211
+
212
+ const newParams = {
213
+ ...defaultParams,
214
+ page: 1,
130
215
  };
131
- }, [finished, autoRefresh, setAPIOptions]);
216
+
217
+ if (filterSearch !== '') {
218
+ newParams.search = filterSearch;
219
+ }
220
+
221
+ setAPIOptions(prev => ({ ...prev, params: newParams }));
222
+
223
+ const urlSearchParams = new URLSearchParams(window.location.search);
224
+ urlSearchParams.set('page', '1');
225
+ history.push({ search: urlSearchParams.toString() });
226
+ };
132
227
 
133
228
  const wrapSetAPIOptions = newAPIOptions => {
134
- setAPIOptions(prevOptions => ({
135
- ...prevOptions,
136
- params: {
137
- ...prevOptions.params,
138
- ...newAPIOptions.params,
139
- search: constructFilter(undefined, newAPIOptions?.params?.search),
140
- },
141
- }));
229
+ const newParams = newAPIOptions?.params ?? newAPIOptions ?? {};
230
+
231
+ const filterSearch = constructFilter(
232
+ selectedFilter,
233
+ newParams.search ?? urlSearchQuery
234
+ );
235
+
236
+ const mergedParams = {
237
+ ...defaultParams,
238
+ ...newParams,
239
+ };
240
+
241
+ if (filterSearch !== '') {
242
+ mergedParams.search = filterSearch;
243
+ } else if ('search' in mergedParams) {
244
+ delete mergedParams.search;
245
+ }
246
+
247
+ setAPIOptions(prev => ({ ...prev, params: mergedParams }));
248
+
249
+ const { search: _search, ...paramsForUrl } = mergedParams;
250
+ const uri = new URI();
251
+ uri.setSearch(paramsForUrl);
252
+ history.push({ search: uri.search() });
142
253
  };
143
254
 
144
255
  const combinedResponse = {
@@ -147,8 +258,8 @@ const JobInvocationHostTable = ({
147
258
  can_create: false,
148
259
  results: response?.results || [],
149
260
  total: response?.total || 0,
150
- per_page: response?.perPage,
151
- page: response?.page,
261
+ per_page: defaultParams?.perPage,
262
+ page: defaultParams?.page,
152
263
  subtotal: response?.subtotal || 0,
153
264
  message: response?.message || 'error',
154
265
  },
@@ -202,6 +313,7 @@ const JobInvocationHostTable = ({
202
313
  return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts;
203
314
  });
204
315
  const [showAlert, setShowAlert] = useState(false);
316
+
205
317
  return (
206
318
  <>
207
319
  {showAlert && <PopupAlert setShowAlert={setShowAlert} />}
@@ -212,34 +324,44 @@ const JobInvocationHostTable = ({
212
324
  controller="hosts"
213
325
  creatable={false}
214
326
  replacementResponse={combinedResponse}
215
- updateSearchQuery={updateSearchQuery}
327
+ updateSearchQuery={updateSearchQueryBulk}
216
328
  customToolbarItems={[
217
- <OpenAlInvocations
218
- setShowAlert={setShowAlert}
219
- results={results}
220
- id={id}
221
- key="OpenAlInvocations"
222
- />,
223
- <JobInvocationHostTableToolbar
329
+ <DropdownFilter
330
+ key="dropdown-filter"
224
331
  dropdownFilter={selectedFilter}
225
332
  setDropdownFilter={wrapSetSelectedFilter}
226
- key="JobInvocationHostTableToolbar"
333
+ />,
334
+ <CheckboxesActions
335
+ bulkParams={selectedCount > 0 ? fetchBulkParams() : null}
336
+ selectedIds={selectedIds}
337
+ failedCount={failedCount}
338
+ jobID={id}
339
+ key="checkboxes-actions"
340
+ filter={selectedFilter}
227
341
  />,
228
342
  ]}
343
+ selectionToolbar={selectionToolbar}
229
344
  >
230
345
  <Table
231
346
  ouiaId="job-invocation-hosts-table"
232
347
  columns={columns}
233
348
  customEmptyState={
234
- status === STATUS_UPPERCASE.RESOLVED && !response?.results?.length
349
+ status === STATUS_UPPERCASE.RESOLVED && !results.length
235
350
  ? customEmptyState
236
351
  : null
237
352
  }
238
- params={params}
353
+ params={{
354
+ page: response?.page || Number(urlPage),
355
+ per_page: response?.per_page || Number(urlPerPage),
356
+ order: urlOrder,
357
+ }}
358
+ page={response?.page || Number(urlPage)}
359
+ perPage={response?.per_page || Number(urlPerPage)}
239
360
  setParams={wrapSetAPIOptions}
240
361
  itemCount={response?.subtotal}
241
- results={response?.results}
362
+ results={results}
242
363
  url=""
364
+ showCheckboxes
243
365
  refreshData={() => {}}
244
366
  errorMessage={
245
367
  status === STATUS_UPPERCASE.ERROR && response?.message
@@ -250,9 +372,9 @@ const JobInvocationHostTable = ({
250
372
  isDeleteable={false}
251
373
  childrenOutsideTbody
252
374
  >
253
- {results?.map((result, rowIndex) => (
254
- <Tbody key={rowIndex}>
255
- <Tr ouiaId={`table-row-${rowIndex}`}>
375
+ {results.map((result, rowIndex) => (
376
+ <Tbody key={result.id}>
377
+ <Tr ouiaId={`table-row-${result.id}`}>
256
378
  <Td
257
379
  expand={{
258
380
  rowIndex,
@@ -262,7 +384,8 @@ const JobInvocationHostTable = ({
262
384
  expandId: 'host-expandable',
263
385
  }}
264
386
  />
265
- {columnNamesKeys.slice(1).map(k => (
387
+ <RowSelectTd rowData={result} {...{ selectOne, isSelected }} />
388
+ {columnNamesKeys.map(k => (
266
389
  <Td key={k}>{columns[k].wrapper(result)}</Td>
267
390
  ))}
268
391
  <Td isActionCell>
@@ -275,7 +398,7 @@ const JobInvocationHostTable = ({
275
398
  >
276
399
  <Td
277
400
  dataLabel={`${result.id}-expandable-content`}
278
- colSpan={columnNamesKeys.length + 1}
401
+ colSpan={columnNamesKeys.length + 3}
279
402
  >
280
403
  <ExpandableRowContent>
281
404
  {result.job_status === 'cancelled' ||
@@ -284,7 +407,12 @@ const JobInvocationHostTable = ({
284
407
  {__('A task for this host has not been started')}
285
408
  </div>
286
409
  ) : (
287
- <TemplateInvocation hostID={result.id} jobID={id} />
410
+ <TemplateInvocation
411
+ hostID={result.id}
412
+ jobID={id}
413
+ isInTableView
414
+ isExpanded={isHostExpanded(result.id)}
415
+ />
288
416
  )}
289
417
  </ExpandableRowContent>
290
418
  </Td>
@@ -300,11 +428,13 @@ const JobInvocationHostTable = ({
300
428
  JobInvocationHostTable.propTypes = {
301
429
  id: PropTypes.string.isRequired,
302
430
  targeting: PropTypes.object.isRequired,
303
- finished: PropTypes.bool.isRequired,
304
- autoRefresh: PropTypes.bool.isRequired,
431
+ failedCount: PropTypes.number.isRequired,
305
432
  initialFilter: PropTypes.string.isRequired,
433
+ onFilterUpdate: PropTypes.func,
306
434
  };
307
435
 
308
- JobInvocationHostTable.defaultProps = {};
436
+ JobInvocationHostTable.defaultProps = {
437
+ onFilterUpdate: () => {},
438
+ };
309
439
 
310
440
  export default JobInvocationHostTable;
@@ -1,8 +1,15 @@
1
- import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors';
1
+ /* eslint-disable camelcase */
2
+ import {
3
+ selectAPIResponse,
4
+ selectAPIStatus,
5
+ } from 'foremanReact/redux/API/APISelectors';
6
+ import { STATUS as APIStatus } from 'foremanReact/constants';
2
7
  import {
3
8
  JOB_INVOCATION_KEY,
4
9
  GET_TASK,
5
10
  GET_TEMPLATE_INVOCATION,
11
+ LIST_TEMPLATE_INVOCATIONS,
12
+ CURRENT_PERMISSIONS,
6
13
  } from './JobInvocationConstants';
7
14
 
8
15
  export const selectItems = state =>
@@ -13,5 +20,25 @@ export const selectTask = state => selectAPIResponse(state, GET_TASK);
13
20
  export const selectTaskCancelable = state =>
14
21
  selectTask(state).available_actions?.cancellable || false;
15
22
 
16
- export const selectTemplateInvocation = state =>
17
- selectAPIResponse(state, GET_TEMPLATE_INVOCATION);
23
+ export const selectTemplateInvocation = hostID => state =>
24
+ selectAPIResponse(state, `${GET_TEMPLATE_INVOCATION}_${hostID}`);
25
+
26
+ export const selectTemplateInvocationStatus = hostID => state =>
27
+ selectAPIStatus(state, `${GET_TEMPLATE_INVOCATION}_${hostID}`);
28
+
29
+ export const selectTemplateInvocationList = state =>
30
+ selectAPIResponse(state, LIST_TEMPLATE_INVOCATIONS)
31
+ ?.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
+ };
@@ -53,7 +53,6 @@ const JobInvocationSystemStatusChart = ({
53
53
  };
54
54
  const chartSize = 105;
55
55
  const [legendWidth, setLegendWidth] = useState(270);
56
- const [cursor, setCursor] = useState('default');
57
56
 
58
57
  // Calculates chart legend width based on its content
59
58
  useEffect(() => {
@@ -79,7 +78,7 @@ const JobInvocationSystemStatusChart = ({
79
78
 
80
79
  return (
81
80
  <>
82
- <FlexItem className="chart-donut" style={{ cursor }}>
81
+ <FlexItem className="chart-donut">
83
82
  <ChartDonut
84
83
  allowTooltip
85
84
  constrainToVisibleArea
@@ -96,12 +95,6 @@ const JobInvocationSystemStatusChart = ({
96
95
  target: 'data',
97
96
  eventHandlers: {
98
97
  onClick: onChartClick,
99
- onMouseOver: () => {
100
- setCursor('pointer');
101
- },
102
- onMouseOut: () => {
103
- setCursor('default');
104
- },
105
98
  },
106
99
  },
107
100
  ]}
@@ -109,7 +102,12 @@ const JobInvocationSystemStatusChart = ({
109
102
  total > 0 ? chartData.map(d => d.color) : [emptyChartDonut.value]
110
103
  }
111
104
  labelComponent={
112
- <ChartTooltip pointerLength={0} renderInPortal={false} />
105
+ <ChartTooltip
106
+ pointerLength={0}
107
+ renderInPortal={false}
108
+ constrainToVisibleArea
109
+ center={{ x: 15, y: 0 }}
110
+ />
113
111
  }
114
112
  title={chartDonutTitle}
115
113
  titleComponent={
@@ -133,7 +131,7 @@ const JobInvocationSystemStatusChart = ({
133
131
  height={chartSize}
134
132
  />
135
133
  </FlexItem>
136
- <FlexItem className="chart-legend" style={{ cursor }}>
134
+ <FlexItem className="chart-legend">
137
135
  <Text ouiaId="legend-title" className="legend-title">
138
136
  {__('System status')}
139
137
  </Text>
@@ -156,24 +154,12 @@ const JobInvocationSystemStatusChart = ({
156
154
  target: 'data',
157
155
  eventHandlers: {
158
156
  onClick: onChartClick,
159
- onMouseOver: () => {
160
- setCursor('pointer');
161
- },
162
- onMouseOut: () => {
163
- setCursor('default');
164
- },
165
157
  },
166
158
  },
167
159
  {
168
160
  target: 'labels',
169
161
  eventHandlers: {
170
162
  onClick: onChartClick,
171
- onMouseOver: () => {
172
- setCursor('pointer');
173
- },
174
- onMouseOut: () => {
175
- setCursor('default');
176
- },
177
163
  },
178
164
  },
179
165
  ]}
@@ -11,7 +11,6 @@ import {
11
11
  } from '@patternfly/react-core/deprecated';
12
12
  import { translate as __ } from 'foremanReact/common/I18n';
13
13
  import { foremanUrl } from 'foremanReact/common/helpers';
14
- import { STATUS as APIStatus } from 'foremanReact/constants';
15
14
  import { get } from 'foremanReact/redux/API';
16
15
  import {
17
16
  cancelJob,
@@ -23,14 +22,12 @@ import {
23
22
  GET_REPORT_TEMPLATES,
24
23
  GET_REPORT_TEMPLATE_INPUTS,
25
24
  } from './JobInvocationConstants';
26
- import { selectTaskCancelable } from './JobInvocationSelectors';
25
+ import {
26
+ selectTaskCancelable,
27
+ selectHasPermission,
28
+ } from './JobInvocationSelectors';
27
29
 
28
- const JobInvocationToolbarButtons = ({
29
- jobId,
30
- data,
31
- currentPermissions,
32
- permissionsStatus,
33
- }) => {
30
+ const JobInvocationToolbarButtons = ({ jobId, data }) => {
34
31
  const { succeeded, failed, task, recurrence, permissions } = data;
35
32
  const recurringEnabled = recurrence?.state === 'active';
36
33
  const canViewForemanTasks = permissions
@@ -40,6 +37,8 @@ const JobInvocationToolbarButtons = ({
40
37
  ? permissions.edit_recurring_logics
41
38
  : false;
42
39
  const isTaskCancelable = useSelector(selectTaskCancelable);
40
+ const useHasPermission = permissionRequired =>
41
+ useSelector(selectHasPermission(permissionRequired));
43
42
  const [isActionOpen, setIsActionOpen] = useState(false);
44
43
  const [reportTemplateJobId, setReportTemplateJobId] = useState(undefined);
45
44
  const [templateInputId, setTemplateInputId] = useState(undefined);
@@ -58,12 +57,6 @@ const JobInvocationToolbarButtons = ({
58
57
  setIsActionOpen(false);
59
58
  onActionFocus();
60
59
  };
61
- const hasPermission = permissionRequired =>
62
- permissionsStatus === APIStatus.RESOLVED
63
- ? currentPermissions?.some(
64
- permission => permission.name === permissionRequired
65
- )
66
- : false;
67
60
 
68
61
  useEffect(() => {
69
62
  dispatch(
@@ -142,7 +135,9 @@ const JobInvocationToolbarButtons = ({
142
135
  ouiaId="rerun-succeeded-dropdown-item"
143
136
  href={foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`)}
144
137
  key="rerun-succeeded"
145
- isDisabled={!(succeeded > 0) || !hasPermission('create_job_invocations')}
138
+ isDisabled={
139
+ !useHasPermission('create_job_invocations') || !(succeeded > 0)
140
+ }
146
141
  description="Rerun job on successful hosts"
147
142
  >
148
143
  {__('Rerun successful')}
@@ -151,7 +146,7 @@ const JobInvocationToolbarButtons = ({
151
146
  ouiaId="rerun-failed-dropdown-item"
152
147
  href={foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`)}
153
148
  key="rerun-failed"
154
- isDisabled={!(failed > 0) || !hasPermission('create_job_invocations')}
149
+ isDisabled={!useHasPermission('create_job_invocations') || !(failed > 0)}
155
150
  description="Rerun job on failed hosts"
156
151
  >
157
152
  {__('Rerun failed')}
@@ -171,7 +166,9 @@ const JobInvocationToolbarButtons = ({
171
166
  onClick={() => dispatch(cancelJob(jobId, false))}
172
167
  key="cancel"
173
168
  component="button"
174
- isDisabled={!isTaskCancelable || !hasPermission('cancel_job_invocations')}
169
+ isDisabled={
170
+ !useHasPermission('cancel_job_invocations') || !isTaskCancelable
171
+ }
175
172
  description="Cancel job gracefully"
176
173
  >
177
174
  {__('Cancel')}
@@ -181,7 +178,9 @@ const JobInvocationToolbarButtons = ({
181
178
  onClick={() => dispatch(cancelJob(jobId, true))}
182
179
  key="abort"
183
180
  component="button"
184
- isDisabled={!isTaskCancelable || !hasPermission('cancel_job_invocations')}
181
+ isDisabled={
182
+ !useHasPermission('cancel_job_invocations') || !isTaskCancelable
183
+ }
185
184
  description="Cancel job immediately"
186
185
  >
187
186
  {__('Abort')}
@@ -210,9 +209,9 @@ const JobInvocationToolbarButtons = ({
210
209
  )}
211
210
  variant="secondary"
212
211
  isDisabled={
212
+ !useHasPermission('generate_report_templates') ||
213
213
  task?.state === STATUS.PENDING ||
214
- templateInputId === undefined ||
215
- !hasPermission('generate_report_templates')
214
+ templateInputId === undefined
216
215
  }
217
216
  >
218
217
  {__(`Create report`)}
@@ -234,7 +233,7 @@ const JobInvocationToolbarButtons = ({
234
233
  key="rerun"
235
234
  href={foremanUrl(`/job_invocations/${jobId}/rerun`)}
236
235
  variant="control"
237
- isDisabled={!hasPermission('create_job_invocations')}
236
+ isDisabled={!useHasPermission('create_job_invocations')}
238
237
  >
239
238
  {__(`Rerun all`)}
240
239
  </Button>,
@@ -255,12 +254,6 @@ const JobInvocationToolbarButtons = ({
255
254
  JobInvocationToolbarButtons.propTypes = {
256
255
  jobId: PropTypes.string.isRequired,
257
256
  data: PropTypes.object.isRequired,
258
- currentPermissions: PropTypes.array,
259
- permissionsStatus: PropTypes.string,
260
- };
261
- JobInvocationToolbarButtons.defaultProps = {
262
- currentPermissions: undefined,
263
- permissionsStatus: undefined,
264
257
  };
265
258
 
266
259
  export default JobInvocationToolbarButtons;