foreman_remote_execution 9.0.1 → 10.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +14 -0
  3. data/.tx/config +3 -1
  4. data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +1 -0
  5. data/app/assets/javascripts/foreman_remote_execution/locale/en/foreman_remote_execution.js +1 -0
  6. data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +1 -0
  7. data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +1 -0
  8. data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +1 -0
  9. data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +1 -0
  10. data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +1 -0
  11. data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +1 -0
  12. data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +1 -0
  13. data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +1 -0
  14. data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +1 -0
  15. data/app/assets/javascripts/foreman_remote_execution/template_invocation.js +10 -1
  16. data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
  17. data/app/controllers/job_invocations_controller.rb +30 -1
  18. data/app/controllers/ui_job_wizard_controller.rb +3 -1
  19. data/app/helpers/remote_execution_helper.rb +1 -1
  20. data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
  21. data/app/lib/actions/remote_execution/run_hosts_job.rb +28 -2
  22. data/app/models/remote_execution_feature.rb +11 -8
  23. data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
  24. data/app/views/job_invocations/_form.html.erb +1 -1
  25. data/app/views/job_invocations/show.html.erb +1 -1
  26. data/app/views/job_invocations/welcome.html.erb +1 -1
  27. data/app/views/templates/script/run_command.erb +1 -0
  28. data/config/routes.rb +2 -0
  29. data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +1 -1
  30. data/db/migrate/20220426145007_add_unique_feature_label_index.rb +14 -0
  31. data/lib/foreman_remote_execution/engine.rb +9 -9
  32. data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +12 -3
  33. data/lib/foreman_remote_execution/version.rb +1 -1
  34. data/locale/Makefile +6 -3
  35. data/locale/action_names.rb +1 -1
  36. data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  37. data/locale/de/foreman_remote_execution.po +67 -19
  38. data/locale/en/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  39. data/locale/en/foreman_remote_execution.po +56 -8
  40. data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  41. data/locale/en_GB/foreman_remote_execution.po +58 -10
  42. data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  43. data/locale/es/foreman_remote_execution.po +71 -23
  44. data/locale/foreman_remote_execution.pot +216 -141
  45. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  46. data/locale/fr/foreman_remote_execution.po +104 -56
  47. data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  48. data/locale/ja/foreman_remote_execution.po +72 -24
  49. data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  50. data/locale/ko/foreman_remote_execution.po +63 -15
  51. data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  52. data/locale/pt_BR/foreman_remote_execution.po +68 -20
  53. data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  54. data/locale/ru/foreman_remote_execution.po +63 -15
  55. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  56. data/locale/zh_CN/foreman_remote_execution.po +71 -23
  57. data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  58. data/locale/zh_TW/foreman_remote_execution.po +63 -15
  59. data/package.json +6 -6
  60. data/webpack/JobWizard/Footer.js +104 -0
  61. data/webpack/JobWizard/JobWizard.js +105 -32
  62. data/webpack/JobWizard/JobWizard.scss +6 -17
  63. data/webpack/JobWizard/JobWizardConstants.js +1 -1
  64. data/webpack/JobWizard/JobWizardPageRerun.js +2 -0
  65. data/webpack/JobWizard/StartsBeforeErrorAlert.js +17 -0
  66. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +8 -0
  67. data/webpack/JobWizard/__tests__/fixtures.js +13 -1
  68. data/webpack/JobWizard/__tests__/integration.test.js +15 -0
  69. data/webpack/JobWizard/__tests__/validation.test.js +36 -0
  70. data/webpack/JobWizard/autofill.js +1 -0
  71. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +29 -10
  72. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +8 -0
  73. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +5 -0
  74. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +28 -14
  75. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +41 -4
  76. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +16 -10
  77. data/webpack/JobWizard/steps/HostsAndInputs/index.js +51 -3
  78. data/webpack/JobWizard/steps/ReviewDetails/ReviewDetails.test.js +117 -0
  79. data/webpack/JobWizard/steps/ReviewDetails/helpers.js +43 -0
  80. data/webpack/JobWizard/steps/ReviewDetails/index.js +169 -18
  81. data/webpack/JobWizard/steps/Schedule/QueryType.js +1 -1
  82. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +0 -1
  83. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  84. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +52 -18
  85. data/webpack/JobWizard/steps/form/DateTimePicker.js +1 -2
  86. data/webpack/JobWizard/steps/form/GroupedSelectField.js +0 -1
  87. data/webpack/JobWizard/steps/form/SelectField.js +0 -1
  88. data/webpack/JobWizard/submit.js +14 -3
  89. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
  90. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +2 -2
  91. data/webpack/react_app/components/RecentJobsCard/constants.js +2 -2
  92. data/webpack/react_app/components/RegistrationExtension/RexPull.js +0 -2
  93. metadata +23 -6
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  import React, { useEffect, useState } from 'react';
2
3
  import {
3
4
  Button,
@@ -5,12 +6,12 @@ import {
5
6
  DescriptionListTerm,
6
7
  DescriptionListGroup,
7
8
  DescriptionListDescription,
9
+ WizardContextConsumer,
8
10
  } from '@patternfly/react-core';
9
11
  import PropTypes from 'prop-types';
10
12
  import { useDispatch, useSelector } from 'react-redux';
11
- import EllipsisWithTooltip from 'react-ellipsis-with-tooltip';
12
13
  import { get } from 'foremanReact/redux/API';
13
- import { translate as __, sprintf } from 'foremanReact/common/I18n';
14
+ import { translate as __ } from 'foremanReact/common/I18n';
14
15
  import {
15
16
  selectJobTemplates,
16
17
  selectHosts,
@@ -22,9 +23,12 @@ import {
22
23
  HOSTS_API,
23
24
  HOSTS_TO_PREVIEW_AMOUNT,
24
25
  WIZARD_TITLES,
26
+ SCHEDULE_TYPES,
25
27
  } from '../../JobWizardConstants';
26
28
  import { buildHostQuery } from '../HostsAndInputs/buildHostQuery';
27
29
  import { WizardTitle } from '../form/WizardTitle';
30
+ import { parseEnd, parseRepeat } from './helpers';
31
+ import { HostPreviewModal } from '../HostsAndInputs/HostPreviewModal';
28
32
 
29
33
  const ReviewDetails = ({
30
34
  jobCategory,
@@ -34,7 +38,20 @@ const ReviewDetails = ({
34
38
  templateValues,
35
39
  selectedTargets,
36
40
  hostsSearchQuery,
41
+ goToStepByName,
37
42
  }) => {
43
+ // eslint-disable-next-line react/prop-types
44
+ const StepButton = ({ stepName, children }) => (
45
+ <Button
46
+ variant="link"
47
+ isInline
48
+ onClick={() => {
49
+ goToStepByName(stepName);
50
+ }}
51
+ >
52
+ {children}
53
+ </Button>
54
+ );
38
55
  const dispatch = useDispatch();
39
56
  useEffect(() => {
40
57
  dispatch(
@@ -58,6 +75,7 @@ const ReviewDetails = ({
58
75
  const hosts = useSelector(selectHosts);
59
76
 
60
77
  const hostsCount = useSelector(selectHostCount);
78
+ const [hostPreviewOpen, setHostPreviewOpen] = useState(false);
61
79
  const stringHosts = () => {
62
80
  if (hosts.length === 0) {
63
81
  return __('No Target Hosts');
@@ -65,22 +83,57 @@ const ReviewDetails = ({
65
83
  if (hosts.length === 1 || hosts.length === 2) {
66
84
  return hosts.join(', ');
67
85
  }
68
- return `${hosts[0]}, ${hosts[1]} ${sprintf(
69
- __(', and %s more'),
70
- hostsCount - 2
71
- )}`;
86
+ return (
87
+ <div>
88
+ {hostsCount} {__('hosts')}{' '}
89
+ <Button
90
+ variant="link"
91
+ isInline
92
+ onClick={() => setHostPreviewOpen(true)}
93
+ >
94
+ {__('view host names')}
95
+ </Button>
96
+ </div>
97
+ );
72
98
  };
73
99
  const [isAdvancedShown, setIsAdvancedShown] = useState(false);
74
100
  const detailsFirstHalf = [
75
- { label: __('Job category'), value: jobCategory },
76
- { label: __('Job template'), value: jobTemplate },
77
- { label: __('Target hosts'), value: stringHosts() },
101
+ {
102
+ label: (
103
+ <StepButton stepName={WIZARD_TITLES.categoryAndTemplate}>
104
+ {__('Job category')}
105
+ </StepButton>
106
+ ),
107
+ value: jobCategory,
108
+ },
109
+ {
110
+ label: (
111
+ <StepButton stepName={WIZARD_TITLES.categoryAndTemplate}>
112
+ {__('Job template')}
113
+ </StepButton>
114
+ ),
115
+ value: jobTemplate,
116
+ },
117
+ {
118
+ label: (
119
+ <StepButton stepName={WIZARD_TITLES.hostsAndInputs}>
120
+ {__('Target hosts')}
121
+ </StepButton>
122
+ ),
123
+ value: stringHosts(),
124
+ },
78
125
  ...templateInputs.map(({ name }) => ({
79
- label: name,
126
+ label: (
127
+ <StepButton stepName={WIZARD_TITLES.hostsAndInputs}>{name}</StepButton>
128
+ ),
80
129
  value: templateValues[name],
81
130
  })),
82
131
  {
83
- label: __('Advanced fields'),
132
+ label: (
133
+ <StepButton stepName={WIZARD_TITLES.advanced}>
134
+ {__('Advanced fields')}
135
+ </StepButton>
136
+ ),
84
137
  value: isAdvancedShown ? (
85
138
  <Button
86
139
  variant="link"
@@ -107,15 +160,98 @@ const ReviewDetails = ({
107
160
 
108
161
  const detailsSecondHalf = [
109
162
  {
110
- label: __('Schedule type'),
163
+ label: (
164
+ <StepButton stepName={WIZARD_TITLES.typeOfExecution}>
165
+ {__('Schedule type')}
166
+ </StepButton>
167
+ ),
111
168
  value: scheduleValue.scheduleType,
112
169
  },
113
170
  {
114
- label: __('Recurrence'),
171
+ label: (
172
+ <StepButton
173
+ stepName={
174
+ scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING
175
+ ? SCHEDULE_TYPES.RECURRING
176
+ : WIZARD_TITLES.typeOfExecution
177
+ }
178
+ >
179
+ {__('Recurrence')}
180
+ </StepButton>
181
+ ),
115
182
  value: scheduleValue.repeatType,
116
183
  },
184
+ scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE &&
185
+ scheduleValue.startsAt && {
186
+ label: (
187
+ <StepButton
188
+ stepName={
189
+ scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING
190
+ ? SCHEDULE_TYPES.RECURRING
191
+ : SCHEDULE_TYPES.FUTURE
192
+ }
193
+ >
194
+ {__('Starts at')}
195
+ </StepButton>
196
+ ),
197
+ value: new Date(scheduleValue.startsAt).toString(),
198
+ },
199
+ scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE &&
200
+ scheduleValue.startsBefore && {
201
+ label: (
202
+ <StepButton stepName={SCHEDULE_TYPES.FUTURE}>
203
+ {__('Starts Before')}
204
+ </StepButton>
205
+ ),
206
+ value: new Date(scheduleValue.startsBefore).toString(),
207
+ },
208
+
209
+ scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING && {
210
+ label: (
211
+ <StepButton stepName={SCHEDULE_TYPES.RECURRING}>
212
+ {__('Starts')}
213
+ </StepButton>
214
+ ),
215
+ value: scheduleValue.isFuture
216
+ ? new Date(scheduleValue.startsAt).toString()
217
+ : __('Now'),
218
+ },
219
+
220
+ scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING && {
221
+ label: (
222
+ <StepButton stepName={SCHEDULE_TYPES.RECURRING}>
223
+ {__('Repeats')}
224
+ </StepButton>
225
+ ),
226
+ value: parseRepeat(scheduleValue.repeatType, scheduleValue.repeatData),
227
+ },
228
+ scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING && {
229
+ label: (
230
+ <StepButton stepName={SCHEDULE_TYPES.RECURRING}>
231
+ {__('Ends')}
232
+ </StepButton>
233
+ ),
234
+ value: parseEnd(
235
+ scheduleValue.ends,
236
+ scheduleValue.isNeverEnds,
237
+ scheduleValue.repeatAmount
238
+ ),
239
+ },
240
+
241
+ scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING && {
242
+ label: (
243
+ <StepButton stepName={SCHEDULE_TYPES.RECURRING}>
244
+ {__('Purpose')}
245
+ </StepButton>
246
+ ),
247
+ value: scheduleValue.purpose,
248
+ },
117
249
  {
118
- label: __('Type of query'),
250
+ label: (
251
+ <StepButton stepName={WIZARD_TITLES.typeOfExecution}>
252
+ {__('Type of query')}
253
+ </StepButton>
254
+ ),
119
255
  value: scheduleValue.isTypeStatic
120
256
  ? __('Static query')
121
257
  : __('Dynamic query'),
@@ -144,6 +280,11 @@ const ReviewDetails = ({
144
280
 
145
281
  return (
146
282
  <>
283
+ <HostPreviewModal
284
+ isOpen={hostPreviewOpen}
285
+ setIsOpen={setHostPreviewOpen}
286
+ searchQuery={buildHostQuery(selectedTargets, hostsSearchQuery)}
287
+ />
147
288
  <WizardTitle
148
289
  title={WIZARD_TITLES.review}
149
290
  className="advanced-fields-title"
@@ -153,7 +294,7 @@ const ReviewDetails = ({
153
294
  <DescriptionListGroup key={index}>
154
295
  <DescriptionListTerm>{label}</DescriptionListTerm>
155
296
  <DescriptionListDescription>
156
- <EllipsisWithTooltip>{value || ''}</EllipsisWithTooltip>
297
+ {value || ''}
157
298
  </DescriptionListDescription>
158
299
  </DescriptionListGroup>
159
300
  ))}
@@ -162,7 +303,7 @@ const ReviewDetails = ({
162
303
  <DescriptionListGroup key={index} className="advanced-fields">
163
304
  <DescriptionListTerm>{label}</DescriptionListTerm>
164
305
  <DescriptionListDescription>
165
- <EllipsisWithTooltip>{value || ''}</EllipsisWithTooltip>
306
+ {value || ''}
166
307
  </DescriptionListDescription>
167
308
  </DescriptionListGroup>
168
309
  ))}
@@ -170,7 +311,7 @@ const ReviewDetails = ({
170
311
  <DescriptionListGroup key={index}>
171
312
  <DescriptionListTerm>{label}</DescriptionListTerm>
172
313
  <DescriptionListDescription>
173
- <EllipsisWithTooltip>{value || ''}</EllipsisWithTooltip>
314
+ {value || ''}
174
315
  </DescriptionListDescription>
175
316
  </DescriptionListGroup>
176
317
  ))}
@@ -187,7 +328,17 @@ ReviewDetails.propTypes = {
187
328
  templateValues: PropTypes.object.isRequired,
188
329
  selectedTargets: PropTypes.object.isRequired,
189
330
  hostsSearchQuery: PropTypes.string.isRequired,
331
+ goToStepByName: PropTypes.func.isRequired,
190
332
  };
191
333
 
192
334
  ReviewDetails.defaultProps = { jobTemplateID: null };
193
- export default ReviewDetails;
335
+
336
+ const WrappedReviewDetails = props => (
337
+ <WizardContextConsumer>
338
+ {({ goToStepByName }) => (
339
+ <ReviewDetails goToStepByName={goToStepByName} {...props} />
340
+ )}
341
+ </WizardContextConsumer>
342
+ );
343
+
344
+ export default WrappedReviewDetails;
@@ -31,7 +31,7 @@ export const QueryType = ({ isTypeStatic, setIsTypeStatic }) => (
31
31
  id="query-type-dynamic"
32
32
  label={__('Dynamic query')}
33
33
  body={__(
34
- "evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it"
34
+ "evaluates just before the execution is started, so if it's planned in future, targeted hosts set may change before it"
35
35
  )}
36
36
  />
37
37
  </FormGroup>
@@ -47,7 +47,6 @@ export const RepeatHour = ({ repeatData, setRepeatData }) => {
47
47
  }}
48
48
  isOpen={minuteOpen}
49
49
  width={125}
50
- menuAppendTo={() => document.querySelector('.pf-c-form.schedule-tab')}
51
50
  toggleAriaLabel="select minute toggle"
52
51
  validated={
53
52
  isValidMinute(minute)
@@ -5,7 +5,7 @@ import { translate as __, documentLocale } from 'foremanReact/common/I18n';
5
5
  import { RepeatDaily } from './RepeatDaily';
6
6
  import { noop } from '../../../helpers';
7
7
 
8
- const getWeekDays = () => {
8
+ export const getWeekDays = () => {
9
9
  const locale = documentLocale().replace(/-/g, '_');
10
10
  const baseDate = new Date(Date.UTC(2017, 0, 1)); // just a Sunday
11
11
  const weekDays = [];
@@ -39,15 +39,35 @@ api.get.mockImplementation(({ handleSuccess, ...action }) => {
39
39
  handleSuccess({
40
40
  data: { results: [jobTemplateResponse.job_template] },
41
41
  });
42
+ } else if (action.key === 'HOST_IDS') {
43
+ handleSuccess &&
44
+ handleSuccess({
45
+ data: { results: [{ name: 'host1' }, { name: 'host3' }] },
46
+ });
42
47
  }
43
48
  return { type: 'get', ...action };
44
49
  });
45
50
 
46
51
  const mockStore = configureMockStore([]);
47
- const store = mockStore({});
52
+ const store = mockStore({
53
+ HOSTS_API: {
54
+ response: {
55
+ subtotal: 3,
56
+ },
57
+ },
58
+ });
48
59
  jest.useFakeTimers();
49
60
 
50
61
  describe('Schedule', () => {
62
+ beforeEach(() => {
63
+ jest.spyOn(selectors, 'selectRouterSearch');
64
+ selectors.selectRouterSearch.mockImplementation(() => ({
65
+ 'host_ids[]': ['105', '37'],
66
+ }));
67
+ });
68
+ afterEach(() => {
69
+ selectors.selectRouterSearch.mockRestore();
70
+ });
51
71
  it('sub steps appear', () => {
52
72
  render(
53
73
  <Provider store={store}>
@@ -85,7 +105,7 @@ describe('Schedule', () => {
85
105
  });
86
106
  act(() => {
87
107
  fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
88
- jest.runAllTimers(); // to handle pf4 date picker popover useTimer
108
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
89
109
  });
90
110
 
91
111
  const newStartAtDate = '2030/03/12';
@@ -115,15 +135,15 @@ describe('Schedule', () => {
115
135
  fireEvent.change(startsBeforeTimeField(), {
116
136
  target: { value: newStartBeforeTime },
117
137
  });
118
- jest.runOnlyPendingTimers();
138
+ jest.advanceTimersByTime(1000);
119
139
  });
120
140
 
121
141
  act(() => {
122
- fireEvent.click(screen.getByText('Category and Template'));
142
+ fireEvent.click(screen.getByText('Category and template'));
123
143
  });
124
144
  act(() => {
125
145
  fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
126
- jest.runAllTimers(); // to handle pf4 date picker popover useTimer
146
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
127
147
  });
128
148
  expect(startsAtDateField().value).toBe(newStartAtDate);
129
149
  expect(startsAtTimeField().value).toBe(newStartAtTime);
@@ -140,7 +160,7 @@ describe('Schedule', () => {
140
160
  target: { value: '2030/03/11' },
141
161
  });
142
162
  await fireEvent.click(startsBeforeTimeField());
143
- await jest.runOnlyPendingTimers();
163
+ await jest.advanceTimersByTime(1000);
144
164
  });
145
165
  expect(startsBeforeDateField().value).toBe('2030/03/11');
146
166
  expect(
@@ -157,13 +177,13 @@ describe('Schedule', () => {
157
177
  await fireEvent.change(startsAtDateField(), {
158
178
  target: { value: '' },
159
179
  });
160
- jest.runOnlyPendingTimers();
180
+ jest.advanceTimersByTime(1000);
161
181
  });
162
182
 
163
183
  expect(startsBeforeDateField().value).toBe('2019/03/11');
164
184
  expect(
165
185
  screen.getAllByText("'Starts before' date must in the future")
166
- ).toHaveLength(1);
186
+ ).toHaveLength(2);
167
187
  });
168
188
 
169
189
  it('Recurring execution - date pickers', async () => {
@@ -182,7 +202,7 @@ describe('Schedule', () => {
182
202
  fireEvent.click(
183
203
  screen.getByRole('button', { name: 'Recurring execution' })
184
204
  );
185
- jest.runAllTimers(); // to handle pf4 date picker popover useTimer
205
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
186
206
  });
187
207
 
188
208
  const newStartAtDate = '2030/03/12';
@@ -207,7 +227,7 @@ describe('Schedule', () => {
207
227
  fireEvent.change(startsAtTimeField(), {
208
228
  target: { value: newStartAtTime },
209
229
  });
210
- jest.runOnlyPendingTimers();
230
+ jest.advanceTimersByTime(1000);
211
231
  });
212
232
 
213
233
  expect(endsAtDateField().disabled).toBeTruthy();
@@ -222,17 +242,17 @@ describe('Schedule', () => {
222
242
  fireEvent.change(endsAtTimeField(), {
223
243
  target: { value: newStartAtTime },
224
244
  });
225
- jest.runOnlyPendingTimers();
245
+ jest.advanceTimersByTime(1000);
226
246
  });
227
247
 
228
248
  act(() => {
229
- fireEvent.click(screen.getByText('Category and Template'));
249
+ fireEvent.click(screen.getByText('Category and template'));
230
250
  });
231
251
  act(() => {
232
252
  fireEvent.click(
233
253
  screen.getByRole('button', { name: 'Recurring execution' })
234
254
  );
235
- jest.runAllTimers(); // to handle pf4 date picker popover useTimer
255
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
236
256
  });
237
257
  expect(startsAtDateField().value).toBe(newStartAtDate);
238
258
  expect(startsAtTimeField().value).toBe(newStartAtTime);
@@ -266,7 +286,7 @@ describe('Schedule', () => {
266
286
  fireEvent.click(
267
287
  screen.getByRole('button', { name: 'Recurring execution' })
268
288
  );
269
- jest.runAllTimers(); // to handle pf4 date picker popover useTimer
289
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
270
290
  });
271
291
  await act(async () => {
272
292
  fireEvent.click(screen.getByLabelText('Daily', { selector: 'button' }));
@@ -274,7 +294,7 @@ describe('Schedule', () => {
274
294
  await act(async () => {
275
295
  fireEvent.click(screen.getByText('Cronline'));
276
296
  });
277
- const newCronline = '1 2';
297
+ const newCronline = '1 2 3 4 5';
278
298
  const cronline = screen.getByLabelText('cronline');
279
299
  expect(cronline.value).toBe('');
280
300
  await act(async () => {
@@ -286,19 +306,33 @@ describe('Schedule', () => {
286
306
  expect(screen.getByText('Review details').disabled).toBeFalsy();
287
307
 
288
308
  await act(async () => {
289
- fireEvent.click(screen.getByText('Category and Template'));
309
+ fireEvent.click(screen.getByText('Category and template'));
290
310
  });
291
- expect(screen.getAllByText('Category and Template')).toHaveLength(3);
311
+ expect(screen.getAllByText('Category and template')).toHaveLength(3);
292
312
 
293
313
  await act(async () => {
294
314
  fireEvent.click(
295
315
  screen.getByRole('button', { name: 'Recurring execution' })
296
316
  );
297
- jest.runAllTimers();
317
+ jest.advanceTimersByTime(1000);
298
318
  });
299
319
  expect(screen.queryAllByText('Recurring execution')).toHaveLength(3);
300
320
  expect(cronline.value).toBe(newCronline);
301
321
 
322
+ await act(async () => {
323
+ fireEvent.click(screen.getByText('Review details'));
324
+ });
325
+ expect(screen.queryAllByText('Review details')).toHaveLength(3);
326
+ expect(screen.getAllByText('Cron line - 1 2 3 4 5')).toHaveLength(1);
327
+
328
+ await act(async () => {
329
+ fireEvent.click(
330
+ screen.getByRole('button', { name: 'Recurring execution' })
331
+ );
332
+ jest.runAllTimers();
333
+ });
334
+ expect(screen.queryAllByText('Recurring execution')).toHaveLength(3);
335
+
302
336
  fireEvent.click(screen.getByText('Cronline'));
303
337
  await act(async () => {
304
338
  fireEvent.click(screen.getByText('Monthly'));
@@ -59,7 +59,7 @@ export const DateTimePicker = ({
59
59
  return false;
60
60
  };
61
61
 
62
- const onDateChange = newDate => {
62
+ const onDateChange = (e, newDate) => {
63
63
  const parsedNewDate = new Date(newDate);
64
64
  if (!newDate.length && allowEmpty) {
65
65
  setDateTime('');
@@ -119,7 +119,6 @@ export const DateTimePicker = ({
119
119
  is24Hour
120
120
  isDisabled={isDisabled || formattedDate.length === 0}
121
121
  invalidFormatErrorMessage={__('Invalid time format')}
122
- menuAppendTo={() => document.body}
123
122
  includeSeconds={includeSeconds}
124
123
  />
125
124
  </>
@@ -68,7 +68,6 @@ export const GroupedSelectField = ({
68
68
  selections={selected}
69
69
  className="without_select2"
70
70
  onClear={onClear}
71
- menuAppendTo={() => document.body}
72
71
  aria-labelledby={fieldId}
73
72
  toggleAriaLabel={`${label} toggle`}
74
73
  {...props}
@@ -43,7 +43,6 @@ export const SelectField = ({
43
43
  isOpen={isOpen}
44
44
  className="without_select2"
45
45
  maxHeight="45vh"
46
- menuAppendTo={() => document.body}
47
46
  placeholderText=" " // To prevent showing first option as selected
48
47
  aria-labelledby={fieldId}
49
48
  toggleAriaLabel={`${label} toggle`}
@@ -12,6 +12,8 @@ export const submit = ({
12
12
  location,
13
13
  organization,
14
14
  feature,
15
+ provider,
16
+ advancedInputs,
15
17
  dispatch,
16
18
  }) => {
17
19
  const {
@@ -37,6 +39,13 @@ export const submit = ({
37
39
  keyPassphrase,
38
40
  timeToPickup,
39
41
  } = advancedValues;
42
+ const providerInputs = advancedInputs.filter(v => v.provider_input);
43
+ const providerValues = {};
44
+ providerInputs.forEach(({ name }) => {
45
+ providerValues[name] = advancedTemplateValues[name];
46
+ delete advancedTemplateValues[name];
47
+ });
48
+
40
49
  const getCronLine = () => {
41
50
  const [hour, minute] = repeatData.at
42
51
  ? repeatData.at.split(':')
@@ -104,14 +113,16 @@ export const submit = ({
104
113
  concurrency_level: concurrencyLevel,
105
114
  },
106
115
  bookmark_id: null,
107
- search_query:
108
- buildHostQuery(selectedTargets, hostsSearchQuery) || 'name ~ *',
116
+ search_query: buildHostQuery(selectedTargets, hostsSearchQuery),
109
117
  description_format: description,
110
118
  execution_timeout_interval: timeoutToKill,
111
119
  feature,
112
120
  time_to_pickup: timeToPickup,
113
121
  },
114
122
  };
123
+ if (Object.keys(providerValues).length) {
124
+ api.job_invocation[provider] = providerValues;
125
+ }
115
126
 
116
127
  dispatch(
117
128
  post({
@@ -122,7 +133,7 @@ export const submit = ({
122
133
  window.location.href = `/job_invocations/${id}`;
123
134
  },
124
135
  errorToast: ({ response }) =>
125
- response?.date?.error?.message ||
136
+ response?.data?.error?.message ||
126
137
  response?.message ||
127
138
  response?.statusText,
128
139
  })
@@ -25,7 +25,7 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
25
25
  header={__('Recent jobs')}
26
26
  dropdownItems={[
27
27
  <DropdownItem
28
- href={foremanUrl(`${JOB_BASE_URL}${name}`)}
28
+ href={foremanUrl(`${JOB_BASE_URL}${id}`)}
29
29
  key="link-to-all"
30
30
  ouiaId="link-to-all-dropdown-item"
31
31
  >
@@ -33,7 +33,7 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
33
33
  </DropdownItem>,
34
34
  <DropdownItem
35
35
  href={foremanUrl(
36
- `${JOB_BASE_URL}${name}+and+status+%3D+failed+or+status%3D+succeeded`
36
+ `${JOB_BASE_URL}${id}+and+status+%3D+failed+or+status%3D+succeeded`
37
37
  )}
38
38
  key="link-to-finished"
39
39
  ouiaId="link-to-finished-dropdown-item"
@@ -41,14 +41,14 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
41
41
  {__('View finished jobs')}
42
42
  </DropdownItem>,
43
43
  <DropdownItem
44
- href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+running`)}
44
+ href={foremanUrl(`${JOB_BASE_URL}${id}+and+status+%3D+running`)}
45
45
  key="link-to-running"
46
46
  ouiaId="link-to-running-dropdown-item"
47
47
  >
48
48
  {__('View running jobs')}
49
49
  </DropdownItem>,
50
50
  <DropdownItem
51
- href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+queued`)}
51
+ href={foremanUrl(`${JOB_BASE_URL}${id}+and+status+%3D+queued`)}
52
52
  key="link-to-scheduled"
53
53
  ouiaId="link-to-scheduled-dropdown-item"
54
54
  >
@@ -17,10 +17,10 @@ const RecentJobsTable = ({ status, hostId }) => {
17
17
  const jobsUrl =
18
18
  hostId &&
19
19
  foremanUrl(
20
- `${JOB_API_URL}${hostId}+and+status%3D${status}&per_page=${JOBS_IN_CARD}`
20
+ `${JOB_API_URL}${hostId}&status=${status}&limit=${JOBS_IN_CARD}`
21
21
  );
22
22
  const {
23
- response: { results: jobs },
23
+ response: { job_invocations: jobs },
24
24
  status: responseStatus,
25
25
  } = useAPI('get', jobsUrl, RECENT_JOBS_KEY);
26
26
 
@@ -6,8 +6,8 @@ export const SCHEDULED_TAB = 2;
6
6
  export const JOB_SUCCESS_STATUS = 0;
7
7
  export const JOB_ERROR_STATUS = 1;
8
8
 
9
- export const JOB_BASE_URL = '/job_invocations?search=host+%3D+';
9
+ export const JOB_BASE_URL = '/job_invocations?search=targeted_host_id+%3D+';
10
10
  export const JOB_API_URL =
11
- '/api/job_invocations?order=start_at+DESC&search=targeted_host_id%3D';
11
+ '/job_invocations/preview_job_invocations_per_host?host_id=';
12
12
  export const JOBS_IN_CARD = 3;
13
13
  export const RECENT_JOBS_KEY = { key: 'RECENT_JOBS_KEY' };
@@ -26,7 +26,6 @@ const options = (value = '') => {
26
26
  const RexPull = ({ isLoading, onChange, pluginValues, configParams }) => (
27
27
  <FormGroup
28
28
  label={__('REX pull mode')}
29
- isRequired
30
29
  labelIcon={
31
30
  <LabelIcon
32
31
  text={__(
@@ -44,7 +43,6 @@ const RexPull = ({ isLoading, onChange, pluginValues, configParams }) => (
44
43
  className="without_select2"
45
44
  id="registration_setup_remote_execution_pull"
46
45
  isDisabled={isLoading}
47
- isRequired
48
46
  >
49
47
  {/* eslint-disable-next-line camelcase */
50
48
  options(configParams?.host_registration_remote_execution_pull)}