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
@@ -36,6 +36,8 @@ import { useValidation } from './validation';
36
36
  import { useAutoFill } from './autofill';
37
37
  import { submit } from './submit';
38
38
  import { generateDefaultDescription } from './JobWizardHelpers';
39
+ import { StartsBeforeErrorAlert } from './StartsBeforeErrorAlert';
40
+ import { Footer } from './Footer';
39
41
  import './JobWizard.scss';
40
42
 
41
43
  export const JobWizard = ({ rerunData }) => {
@@ -185,6 +187,24 @@ export const JobWizard = ({ rerunData }) => {
185
187
  // eslint-disable-next-line react-hooks/exhaustive-deps
186
188
  }, [rerunData, jobTemplateID, dispatch]);
187
189
 
190
+ const [isStartsBeforeError, setIsStartsBeforeError] = useState(false);
191
+ useEffect(() => {
192
+ const updateStartsBeforeError = () => {
193
+ setIsStartsBeforeError(
194
+ scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE &&
195
+ new Date().getTime() >= new Date(scheduleValue.startsBefore).getTime()
196
+ );
197
+ };
198
+ let interval;
199
+ if (scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE) {
200
+ updateStartsBeforeError();
201
+ interval = setInterval(updateStartsBeforeError, 5000);
202
+ }
203
+ return () => {
204
+ interval && clearInterval(interval);
205
+ };
206
+ }, [scheduleValue.scheduleType, scheduleValue.startsBefore]);
207
+
188
208
  const [valid, setValid] = useValidation({
189
209
  advancedValues,
190
210
  templateValues,
@@ -209,6 +229,11 @@ export const JobWizard = ({ rerunData }) => {
209
229
  !templateError &&
210
230
  !!jobTemplateID &&
211
231
  templateResponse.job_template;
232
+ const areHostsSelected =
233
+ selectedTargets.hosts.length > 0 ||
234
+ selectedTargets.hostCollections.length > 0 ||
235
+ selectedTargets.hostGroups.length > 0 ||
236
+ hostsSearchQuery.length > 0;
212
237
  const steps = [
213
238
  {
214
239
  name: WIZARD_TITLES.categoryAndTemplate,
@@ -238,7 +263,7 @@ export const JobWizard = ({ rerunData }) => {
238
263
  />
239
264
  ),
240
265
  canJumpTo: isTemplate,
241
- enableNext: isTemplate && valid.hostsAndInputs,
266
+ enableNext: isTemplate && valid.hostsAndInputs && areHostsSelected,
242
267
  },
243
268
  {
244
269
  name: WIZARD_TITLES.advanced,
@@ -254,14 +279,26 @@ export const JobWizard = ({ rerunData }) => {
254
279
  templateValues={templateValues}
255
280
  />
256
281
  ),
257
- canJumpTo: isTemplate && valid.hostsAndInputs,
258
- enableNext: isTemplate && valid.hostsAndInputs && valid.advanced,
282
+ canJumpTo: isTemplate && valid.hostsAndInputs && areHostsSelected,
283
+ enableNext:
284
+ isTemplate &&
285
+ valid.hostsAndInputs &&
286
+ areHostsSelected &&
287
+ valid.advanced,
259
288
  },
260
289
  {
261
290
  name: WIZARD_TITLES.schedule,
262
- canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
291
+ canJumpTo:
292
+ isTemplate &&
293
+ valid.hostsAndInputs &&
294
+ areHostsSelected &&
295
+ valid.advanced,
263
296
  enableNext:
264
- isTemplate && valid.hostsAndInputs && valid.advanced && valid.schedule,
297
+ isTemplate &&
298
+ valid.hostsAndInputs &&
299
+ areHostsSelected &&
300
+ valid.advanced &&
301
+ valid.schedule,
265
302
  steps: [
266
303
  {
267
304
  name: WIZARD_TITLES.typeOfExecution,
@@ -278,9 +315,17 @@ export const JobWizard = ({ rerunData }) => {
278
315
  }}
279
316
  />
280
317
  ),
281
- canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
318
+ canJumpTo:
319
+ isTemplate &&
320
+ valid.hostsAndInputs &&
321
+ areHostsSelected &&
322
+ valid.advanced,
282
323
 
283
- enableNext: isTemplate && valid.hostsAndInputs && valid.advanced,
324
+ enableNext:
325
+ isTemplate &&
326
+ valid.hostsAndInputs &&
327
+ areHostsSelected &&
328
+ valid.advanced,
284
329
  },
285
330
  ...(scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE
286
331
  ? [
@@ -298,10 +343,15 @@ export const JobWizard = ({ rerunData }) => {
298
343
  }}
299
344
  />
300
345
  ),
301
- canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
346
+ canJumpTo:
347
+ isTemplate &&
348
+ valid.hostsAndInputs &&
349
+ areHostsSelected &&
350
+ valid.advanced,
302
351
  enableNext:
303
352
  isTemplate &&
304
353
  valid.hostsAndInputs &&
354
+ areHostsSelected &&
305
355
  valid.advanced &&
306
356
  valid.schedule,
307
357
  },
@@ -323,10 +373,15 @@ export const JobWizard = ({ rerunData }) => {
323
373
  }}
324
374
  />
325
375
  ),
326
- canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
376
+ canJumpTo:
377
+ isTemplate &&
378
+ valid.hostsAndInputs &&
379
+ areHostsSelected &&
380
+ valid.advanced,
327
381
  enableNext:
328
382
  isTemplate &&
329
383
  valid.hostsAndInputs &&
384
+ areHostsSelected &&
330
385
  valid.advanced &&
331
386
  valid.schedule,
332
387
  },
@@ -349,39 +404,57 @@ export const JobWizard = ({ rerunData }) => {
349
404
  ),
350
405
  nextButtonText: 'Run',
351
406
  canJumpTo:
352
- isTemplate && valid.hostsAndInputs && valid.advanced && valid.schedule,
407
+ isTemplate &&
408
+ valid.advanced &&
409
+ valid.hostsAndInputs &&
410
+ areHostsSelected &&
411
+ valid.schedule,
353
412
  enableNext:
354
413
  isTemplate &&
355
414
  valid.hostsAndInputs &&
415
+ areHostsSelected &&
356
416
  valid.advanced &&
357
417
  valid.schedule &&
358
- !isSubmitting,
418
+ !isSubmitting &&
419
+ !isStartsBeforeError,
359
420
  },
360
421
  ];
361
422
  const location = useForemanLocation();
362
423
  const organization = useForemanOrganization();
424
+ const onSave = () => {
425
+ submit({
426
+ jobTemplateID,
427
+ templateValues,
428
+ advancedValues,
429
+ scheduleValue,
430
+ dispatch,
431
+ selectedTargets,
432
+ hostsSearchQuery,
433
+ location,
434
+ organization,
435
+ feature: routerSearch?.feature,
436
+ provider: templateResponse.provider_name,
437
+ advancedInputs: templateResponse.advanced_template_inputs,
438
+ });
439
+ };
363
440
  return (
364
- <Wizard
365
- onClose={() => history.goBack()}
366
- navAriaLabel="Run Job steps"
367
- steps={steps}
368
- height="100%"
369
- className="job-wizard"
370
- onSave={() => {
371
- submit({
372
- jobTemplateID,
373
- templateValues,
374
- advancedValues,
375
- scheduleValue,
376
- dispatch,
377
- selectedTargets,
378
- hostsSearchQuery,
379
- location,
380
- organization,
381
- feature: routerSearch?.feature,
382
- });
383
- }}
384
- />
441
+ <>
442
+ {isStartsBeforeError && <StartsBeforeErrorAlert />}
443
+ <Wizard
444
+ onClose={() => history.goBack()}
445
+ navAriaLabel="Run Job steps"
446
+ steps={steps}
447
+ height="100%"
448
+ className="job-wizard"
449
+ onSave={onSave}
450
+ footer={
451
+ <Footer
452
+ canSubmit={!!steps[steps.length - 1].enableNext}
453
+ onSave={onSave}
454
+ />
455
+ }
456
+ />
457
+ </>
385
458
  );
386
459
  };
387
460
 
@@ -6,20 +6,15 @@
6
6
 
7
7
  .pf-c-wizard__nav.pf-m-expanded {
8
8
  z-index: calc(
9
- var(--pf-c-wizard__footer--ZIndex) + 2
10
- ); // So the small screen navigation can be shown above the select box
9
+ var(--pf-c-wizard__toggle--ZIndex) + 2
10
+ ); // So the small screen navigation can be shown above the select box and wizard body
11
11
  }
12
12
 
13
13
  .pf-c-wizard__main {
14
14
  overflow: visible;
15
15
  z-index: calc(
16
- var(--pf-c-wizard__footer--ZIndex) + 1
17
- ); // So the select box can be shown above the wizard footer
18
- }
19
- .pf-c-wizard__nav {
20
- z-index: calc(
21
- var(--pf-c-wizard__footer--ZIndex) + 2
22
- ); // So the navigation box can be shown above the wizard body
16
+ var(--pf-c-wizard__toggle--ZIndex) + 1
17
+ ); // So the select box can be shown above the wizard footer and navigation toggle
23
18
  }
24
19
  .pf-c-wizard__main-body {
25
20
  max-width: 500px;
@@ -34,14 +29,8 @@
34
29
  }
35
30
 
36
31
  .target-hosts-and-inputs {
37
- .hosts-chip-group {
38
- margin-top: 8px;
39
- float: left;
40
- clear: left;
41
- display: block;
42
- }
43
- .clear-chips {
44
- margin-top: 8px;
32
+ .pf-c-chip-group.pf-m-category {
33
+ margin-bottom: 10px;
45
34
  }
46
35
  .pf-c-select__toggle-typeahead {
47
36
  border: 0px;
@@ -23,7 +23,7 @@ export const SCHEDULE_TYPES = {
23
23
  };
24
24
 
25
25
  export const WIZARD_TITLES = {
26
- categoryAndTemplate: __('Category and Template'),
26
+ categoryAndTemplate: __('Category and template'),
27
27
  hostsAndInputs: __('Target hosts and inputs'),
28
28
  advanced: __('Advanced fields'),
29
29
  schedule: __('Schedule'),
@@ -86,6 +86,7 @@ const JobWizardPageRerun = ({
86
86
  <React.Fragment>
87
87
  {jobOrganization?.id !== currentOrganization?.id && (
88
88
  <Alert
89
+ isInline
89
90
  className="job-wizard-alert"
90
91
  variant="warning"
91
92
  title={sprintf(
@@ -99,6 +100,7 @@ const JobWizardPageRerun = ({
99
100
  )}
100
101
  {jobLocation?.id !== currentLocation?.id && (
101
102
  <Alert
103
+ isInline
102
104
  className="job-wizard-alert"
103
105
  variant="warning"
104
106
  title={sprintf(
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { Divider, Alert } from '@patternfly/react-core';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ export const StartsBeforeErrorAlert = () => (
6
+ <>
7
+ <Alert
8
+ variant="danger"
9
+ title={__("'Starts before' date must in the future")}
10
+ >
11
+ {__(
12
+ 'Please go back to "Schedule" - "Future execution" step to fix the error'
13
+ )}
14
+ </Alert>
15
+ <Divider component="div" />
16
+ </>
17
+ );
@@ -7,6 +7,14 @@ Array [
7
7
  "type": "get",
8
8
  "url": "/ui_job_wizard/categories",
9
9
  },
10
+ Object {
11
+ "key": "HOST_IDS",
12
+ "params": Object {
13
+ "search": "id = 105 or id = 37",
14
+ },
15
+ "type": "get",
16
+ "url": "/api/hosts",
17
+ },
10
18
  Object {
11
19
  "key": "JOB_TEMPLATES",
12
20
  "type": "get",
@@ -161,6 +161,11 @@ export const testSetup = (selectors, api) => {
161
161
  ],
162
162
  },
163
163
  },
164
+ HOSTS_API: {
165
+ response: {
166
+ subtotal: 3,
167
+ },
168
+ },
164
169
  });
165
170
  return store;
166
171
  };
@@ -318,6 +323,13 @@ export const jobInvocation = {
318
323
  created_in_katello: false,
319
324
  },
320
325
  inputs: {
321
- 'inputs[plain hidden]': 'test command',
326
+ 'inputs[adv plain hidden]': {
327
+ advanced: true,
328
+ value: 'adv_test_command',
329
+ },
330
+ 'inputs[plain hidden]': {
331
+ advanced: false,
332
+ value: 'test command',
333
+ },
322
334
  },
323
335
  };
@@ -18,6 +18,15 @@ import {
18
18
  const store = testSetup(selectors, api);
19
19
 
20
20
  describe('Job wizard fill', () => {
21
+ beforeEach(() => {
22
+ jest.spyOn(selectors, 'selectRouterSearch');
23
+ selectors.selectRouterSearch.mockImplementation(() => ({
24
+ 'host_ids[]': ['105', '37'],
25
+ }));
26
+ });
27
+ afterEach(() => {
28
+ selectors.selectRouterSearch.mockRestore();
29
+ });
21
30
  it('should select template', async () => {
22
31
  api.get.mockImplementation(({ handleSuccess, ...action }) => {
23
32
  if (action.key === 'JOB_CATEGORIES') {
@@ -33,7 +42,13 @@ describe('Job wizard fill', () => {
33
42
  handleSuccess({
34
43
  data: jobTemplate,
35
44
  });
45
+ } else if (action.key === 'HOST_IDS') {
46
+ handleSuccess &&
47
+ handleSuccess({
48
+ data: { results: [{ name: 'host1' }, { name: 'host3' }] },
49
+ });
36
50
  }
51
+
37
52
  return { type: 'get', ...action };
38
53
  });
39
54
  selectors.selectJobTemplate.mockRestore();
@@ -41,11 +41,26 @@ describe('Job wizard validation', () => {
41
41
  expect(screen.getByText(WIZARD_TITLES.review)).toBeDisabled();
42
42
  await act(async () => {
43
43
  fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
44
+ await new Promise(resolve => setTimeout(resolve, 0)); // to resolve gql
44
45
  });
46
+ const select = name =>
47
+ screen.getByRole('button', { name: `${name} toggle` });
48
+ fireEvent.click(select('hosts'));
49
+ await act(async () => {
50
+ fireEvent.click(screen.getByText('host1'));
51
+ });
52
+
53
+ expect(screen.getByText(WIZARD_TITLES.advanced)).toBeDisabled();
54
+ expect(screen.getByText(WIZARD_TITLES.schedule)).toBeDisabled();
55
+ expect(screen.getByText(WIZARD_TITLES.review)).toBeDisabled();
45
56
  const textField = screen.getByLabelText('plain hidden', {
46
57
  selector: 'textarea',
47
58
  });
48
59
  await act(async () => {
60
+ fireEvent.click(
61
+ // Close the select
62
+ select('hosts')
63
+ );
49
64
  await fireEvent.change(textField, {
50
65
  target: { value: 'text' },
51
66
  });
@@ -85,8 +100,20 @@ describe('Job wizard validation', () => {
85
100
  // setup
86
101
  await act(async () => {
87
102
  fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
103
+ await new Promise(resolve => setTimeout(resolve, 0)); // to resolve gql
104
+ });
105
+
106
+ const select = name =>
107
+ screen.getByRole('button', { name: `${name} toggle` });
108
+ fireEvent.click(select('hosts'));
109
+ await act(async () => {
110
+ fireEvent.click(screen.getByText('host1'));
88
111
  });
89
112
  await act(async () => {
113
+ fireEvent.click(
114
+ // Close the host select
115
+ select('hosts')
116
+ );
90
117
  await fireEvent.change(
91
118
  screen.getByLabelText('plain hidden', {
92
119
  selector: 'textarea',
@@ -127,6 +154,10 @@ describe('Job wizard validation', () => {
127
154
  });
128
155
 
129
156
  expect(screen.getByText(WIZARD_TITLES.schedule)).toBeDisabled();
157
+ expect(screen.getByText('Run on selected hosts')).toHaveAttribute(
158
+ 'aria-disabled',
159
+ 'true'
160
+ );
130
161
  expect(screen.getByText(WIZARD_TITLES.review)).toBeDisabled();
131
162
 
132
163
  await act(async () => {
@@ -135,6 +166,11 @@ describe('Job wizard validation', () => {
135
166
  });
136
167
  });
137
168
 
169
+ expect(screen.getByText('Run on selected hosts')).toBeEnabled();
170
+ expect(screen.getByText('Run on selected hosts')).toHaveAttribute(
171
+ 'aria-disabled',
172
+ 'false'
173
+ );
138
174
  expect(screen.getByText(WIZARD_TITLES.schedule)).toBeEnabled();
139
175
  expect(screen.getByText(WIZARD_TITLES.review)).toBeEnabled();
140
176
  });
@@ -72,6 +72,7 @@ export const useAutoFill = ({
72
72
  if (input) {
73
73
  if (typeof rest[key] === 'string') {
74
74
  setTemplateValues(prev => ({ ...prev, [input]: rest[key] }));
75
+ setAdvancedValues(prev => ({ ...prev, [input]: rest[key] }));
75
76
  } else {
76
77
  const { value, advanced } = rest[key];
77
78
  if (advanced) {
@@ -26,6 +26,15 @@ mockApi(api);
26
26
  jest.useFakeTimers();
27
27
 
28
28
  describe('AdvancedFields', () => {
29
+ beforeEach(() => {
30
+ jest.spyOn(selectors, 'selectRouterSearch');
31
+ selectors.selectRouterSearch.mockImplementation(() => ({
32
+ 'host_ids[]': ['105', '37'],
33
+ }));
34
+ });
35
+ afterEach(() => {
36
+ selectors.selectRouterSearch.mockRestore();
37
+ });
29
38
  it('should save data between steps for advanced fields', async () => {
30
39
  const wrapper = mount(
31
40
  <MockedProvider mocks={gqlMock} addTypename={false}>
@@ -51,7 +60,7 @@ describe('AdvancedFields', () => {
51
60
  .simulate('click'); // Advanced step
52
61
 
53
62
  await act(async () => {
54
- jest.runAllTimers(); // to handle pf4 date picker popover
63
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
55
64
  });
56
65
  const effectiveUserInput = () => wrapper.find('input#effective-user');
57
66
  const advancedTemplateInput = () =>
@@ -83,7 +92,7 @@ describe('AdvancedFields', () => {
83
92
  .simulate('click'); // Advanced step
84
93
 
85
94
  await act(async () => {
86
- jest.runOnlyPendingTimers(); // to handle pf4 date picker popover
95
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
87
96
  });
88
97
  expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
89
98
  expect(advancedTemplateInput().prop('value')).toEqual(
@@ -100,7 +109,7 @@ describe('AdvancedFields', () => {
100
109
  );
101
110
  await act(async () => {
102
111
  fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
103
- jest.runOnlyPendingTimers(); // to handle pf4 date picker popover
112
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
104
113
  });
105
114
 
106
115
  const searchValue = 'search test';
@@ -137,7 +146,7 @@ describe('AdvancedFields', () => {
137
146
  fireEvent.change(timeField, {
138
147
  target: { value: timeValue },
139
148
  });
140
- jest.runAllTimers(); // to handle pf4 date picker popover
149
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
141
150
  });
142
151
  expect(
143
152
  screen.getByLabelText('adv plain hidden', {
@@ -156,7 +165,7 @@ describe('AdvancedFields', () => {
156
165
 
157
166
  await act(async () => {
158
167
  await fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
159
- jest.runOnlyPendingTimers();
168
+ jest.advanceTimersByTime(1000);
160
169
  });
161
170
  expect(textField.value).toBe(textValue);
162
171
  expect(searchField.value).toBe(searchValue);
@@ -177,7 +186,7 @@ describe('AdvancedFields', () => {
177
186
  );
178
187
  await act(async () => {
179
188
  fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
180
- jest.runAllTimers(); // to handle pf4 date picker popover
189
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
181
190
  });
182
191
 
183
192
  expect(
@@ -212,7 +221,7 @@ describe('AdvancedFields', () => {
212
221
  );
213
222
  await act(async () => {
214
223
  fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
215
- jest.runAllTimers(); // to handle pf4 date picker popover
224
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
216
225
  });
217
226
 
218
227
  const textField = screen.getByLabelText('adv plain hidden', {
@@ -270,6 +279,11 @@ describe('AdvancedFields', () => {
270
279
  handleSuccess({
271
280
  data: { results: [jobTemplate] },
272
281
  });
282
+ } else if (action.key === 'HOST_IDS') {
283
+ handleSuccess &&
284
+ handleSuccess({
285
+ data: { results: [{ name: 'host1' }, { name: 'host3' }] },
286
+ });
273
287
  }
274
288
  return { type: 'get', ...action };
275
289
  });
@@ -280,7 +294,7 @@ describe('AdvancedFields', () => {
280
294
  );
281
295
  await act(async () => {
282
296
  fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
283
- jest.runAllTimers(); // to handle pf4 date picker popover
297
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
284
298
  });
285
299
  expect(
286
300
  screen.getByLabelText('description preview', {
@@ -339,6 +353,11 @@ describe('AdvancedFields', () => {
339
353
  handleSuccess({
340
354
  data: { results: [jobTemplate] },
341
355
  });
356
+ } else if (action.key === 'HOST_IDS') {
357
+ handleSuccess &&
358
+ handleSuccess({
359
+ data: { results: [{ name: 'host1' }, { name: 'host3' }] },
360
+ });
342
361
  }
343
362
  return { type: 'get', ...action };
344
363
  });
@@ -349,7 +368,7 @@ describe('AdvancedFields', () => {
349
368
  );
350
369
  await act(async () => {
351
370
  fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
352
- jest.runAllTimers(); // to handle pf4 date picker popover
371
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
353
372
  });
354
373
  expect(
355
374
  screen.getByLabelText('description preview', {
@@ -369,7 +388,7 @@ describe('AdvancedFields', () => {
369
388
  );
370
389
  await act(async () => {
371
390
  fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
372
- jest.runAllTimers(); // to handle pf4 date picker popover
391
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
373
392
  });
374
393
  const resourceSelectField = screen.getByLabelText(
375
394
  'adv resource select typeahead input'
@@ -7,6 +7,14 @@ Array [
7
7
  "type": "get",
8
8
  "url": "/ui_job_wizard/categories",
9
9
  },
10
+ Object {
11
+ "key": "HOST_IDS",
12
+ "params": Object {
13
+ "search": "id = 105 or id = 37",
14
+ },
15
+ "type": "get",
16
+ "url": "/api/hosts",
17
+ },
10
18
  Object {
11
19
  "key": "JOB_TEMPLATES",
12
20
  "type": "get",
@@ -29,6 +29,7 @@ export const HostPreviewModal = ({ isOpen, setIsOpen, searchQuery }) => {
29
29
  variant="link"
30
30
  target="_blank"
31
31
  rel="noreferrer"
32
+ isInline
32
33
  >
33
34
  {host}
34
35
  </Button>
@@ -42,6 +43,7 @@ export const HostPreviewModal = ({ isOpen, setIsOpen, searchQuery }) => {
42
43
  variant="link"
43
44
  target="_blank"
44
45
  rel="noreferrer"
46
+ isInline
45
47
  >
46
48
  {sprintf(
47
49
  __('...and %s more'),
@@ -60,3 +62,6 @@ HostPreviewModal.propTypes = {
60
62
  setIsOpen: PropTypes.func.isRequired,
61
63
  searchQuery: PropTypes.string.isRequired,
62
64
  };
65
+ HostPreviewModal.defaultPropTypes = {
66
+ searchQuery: '',
67
+ };