foreman_remote_execution 9.0.1 → 10.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)}