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
@@ -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
+ };