foreman_remote_execution 9.1.0 → 10.0.1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +14 -0
  3. data/.packit.yaml +40 -0
  4. data/.tx/config +3 -1
  5. data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +1 -0
  6. data/app/assets/javascripts/foreman_remote_execution/locale/en/foreman_remote_execution.js +1 -0
  7. data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +1 -0
  8. data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +1 -0
  9. data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +1 -0
  10. data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +1 -0
  11. data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +1 -0
  12. data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +1 -0
  13. data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +1 -0
  14. data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +1 -0
  15. data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +1 -0
  16. data/app/assets/javascripts/foreman_remote_execution/template_invocation.js +10 -1
  17. data/app/controllers/job_invocations_controller.rb +20 -1
  18. data/app/controllers/ui_job_wizard_controller.rb +0 -3
  19. data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
  20. data/app/views/job_invocations/_form.html.erb +1 -1
  21. data/app/views/templates/script/run_command.erb +1 -0
  22. data/config/routes.rb +1 -0
  23. data/lib/foreman_remote_execution/engine.rb +8 -8
  24. data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +12 -3
  25. data/lib/foreman_remote_execution/version.rb +1 -1
  26. data/locale/Makefile +6 -3
  27. data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  28. data/locale/de/foreman_remote_execution.po +53 -8
  29. data/locale/en/foreman_remote_execution.po +52 -7
  30. data/locale/en_GB/foreman_remote_execution.po +52 -7
  31. data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  32. data/locale/es/foreman_remote_execution.po +56 -11
  33. data/locale/foreman_remote_execution.pot +193 -126
  34. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  35. data/locale/fr/foreman_remote_execution.po +56 -11
  36. data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  37. data/locale/ja/foreman_remote_execution.po +56 -11
  38. data/locale/ko/foreman_remote_execution.po +52 -7
  39. data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  40. data/locale/pt_BR/foreman_remote_execution.po +56 -11
  41. data/locale/ru/foreman_remote_execution.po +52 -7
  42. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  43. data/locale/zh_CN/foreman_remote_execution.po +56 -11
  44. data/locale/zh_TW/foreman_remote_execution.po +52 -7
  45. data/webpack/JobWizard/Footer.js +104 -0
  46. data/webpack/JobWizard/JobWizard.js +24 -16
  47. data/webpack/JobWizard/JobWizard.scss +6 -17
  48. data/webpack/JobWizard/JobWizardConstants.js +1 -1
  49. data/webpack/JobWizard/JobWizardPageRerun.js +2 -0
  50. data/webpack/JobWizard/__tests__/fixtures.js +8 -1
  51. data/webpack/JobWizard/__tests__/validation.test.js +9 -0
  52. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +2 -0
  53. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +28 -14
  54. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +3 -3
  55. data/webpack/JobWizard/steps/ReviewDetails/ReviewDetails.test.js +117 -0
  56. data/webpack/JobWizard/steps/ReviewDetails/helpers.js +43 -0
  57. data/webpack/JobWizard/steps/ReviewDetails/index.js +169 -18
  58. data/webpack/JobWizard/steps/Schedule/QueryType.js +1 -1
  59. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +0 -1
  60. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  61. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +19 -5
  62. data/webpack/JobWizard/steps/form/DateTimePicker.js +0 -1
  63. data/webpack/JobWizard/steps/form/GroupedSelectField.js +0 -1
  64. data/webpack/JobWizard/steps/form/SelectField.js +0 -1
  65. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
  66. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +2 -2
  67. data/webpack/react_app/components/RecentJobsCard/constants.js +2 -2
  68. data/webpack/react_app/components/RegistrationExtension/RexPull.js +0 -2
  69. metadata +19 -3
@@ -323,6 +323,13 @@ export const jobInvocation = {
323
323
  created_in_katello: false,
324
324
  },
325
325
  inputs: {
326
- '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
+ },
327
334
  },
328
335
  };
@@ -154,6 +154,10 @@ describe('Job wizard validation', () => {
154
154
  });
155
155
 
156
156
  expect(screen.getByText(WIZARD_TITLES.schedule)).toBeDisabled();
157
+ expect(screen.getByText('Run on selected hosts')).toHaveAttribute(
158
+ 'aria-disabled',
159
+ 'true'
160
+ );
157
161
  expect(screen.getByText(WIZARD_TITLES.review)).toBeDisabled();
158
162
 
159
163
  await act(async () => {
@@ -162,6 +166,11 @@ describe('Job wizard validation', () => {
162
166
  });
163
167
  });
164
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
+ );
165
174
  expect(screen.getByText(WIZARD_TITLES.schedule)).toBeEnabled();
166
175
  expect(screen.getByText(WIZARD_TITLES.review)).toBeEnabled();
167
176
  });
@@ -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'),
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Chip, ChipGroup, Button } from '@patternfly/react-core';
4
- import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
5
5
  import { hostMethods } from '../../JobWizardConstants';
6
6
 
7
7
  const SelectedChip = ({ selected, setSelected, categoryName }) => {
@@ -10,19 +10,33 @@ const SelectedChip = ({ selected, setSelected, categoryName }) => {
10
10
  oldSelected.filter(({ id }) => id !== itemToRemove)
11
11
  );
12
12
  };
13
+ const NUM_CHIPS = 3;
13
14
  return (
14
- <ChipGroup className="hosts-chip-group" categoryName={categoryName}>
15
- {selected.map(({ name, id }, index) => (
16
- <Chip
17
- key={index}
18
- id={`${categoryName}-${id}`}
19
- onClick={() => deleteItem(id)}
20
- closeBtnAriaLabel={`Close ${name}`}
21
- >
22
- {name}
23
- </Chip>
24
- ))}
25
- </ChipGroup>
15
+ <>
16
+ <ChipGroup
17
+ className="hosts-chip-group"
18
+ categoryName={categoryName}
19
+ isClosable
20
+ closeBtnAriaLabel="Remove all"
21
+ collapsedText={sprintf(__('%s more'), selected.length - NUM_CHIPS)}
22
+ numChips={NUM_CHIPS}
23
+ onClick={() => {
24
+ setSelected(() => []);
25
+ }}
26
+ >
27
+ {selected.map(({ name, id }, index) => (
28
+ <Chip
29
+ key={index}
30
+ id={`${categoryName}-${id}`}
31
+ onClick={() => deleteItem(id)}
32
+ closeBtnAriaLabel={`Remove ${name}`}
33
+ >
34
+ {name}
35
+ </Chip>
36
+ ))}
37
+ </ChipGroup>
38
+ {selected.length > 0 && <br />}
39
+ </>
26
40
  );
27
41
  };
28
42
 
@@ -75,7 +89,7 @@ export const SelectedChips = ({
75
89
  />
76
90
  {showClear && (
77
91
  <Button variant="link" className="clear-chips" onClick={clearAll}>
78
- {__('Clear filters')}
92
+ {__('Clear all filters')}
79
93
  </Button>
80
94
  )}
81
95
  </div>
@@ -80,7 +80,7 @@ describe('Hosts', () => {
80
80
  expect(screen.queryAllByText('host1')).toHaveLength(1);
81
81
  expect(screen.queryAllByText('host2')).toHaveLength(1);
82
82
  expect(screen.queryAllByText('host3')).toHaveLength(0);
83
- const chip1 = screen.getByRole('button', { name: 'Close host1 host1' });
83
+ const chip1 = screen.getByRole('button', { name: 'Remove host1 host1' });
84
84
  await act(async () => {
85
85
  fireEvent.click(chip1);
86
86
  });
@@ -91,7 +91,7 @@ describe('Hosts', () => {
91
91
  expect(screen.queryAllByText('host_collection1')).toHaveLength(1);
92
92
 
93
93
  await act(async () => {
94
- fireEvent.click(screen.getByText('Category and Template'));
94
+ fireEvent.click(screen.getByText('Category and template'));
95
95
  });
96
96
  await act(async () => {
97
97
  fireEvent.click(screen.getByText('Target hosts and inputs'));
@@ -100,7 +100,7 @@ describe('Hosts', () => {
100
100
  expect(screen.queryAllByText('host_group1')).toHaveLength(1);
101
101
 
102
102
  await act(async () => {
103
- fireEvent.click(screen.getByText('Clear filters'));
103
+ fireEvent.click(screen.getByText('Clear all filters'));
104
104
  });
105
105
 
106
106
  expect(screen.queryAllByText('host2')).toHaveLength(0);
@@ -0,0 +1,117 @@
1
+ import React from 'react';
2
+ import { Provider } from 'react-redux';
3
+ import { render, fireEvent, screen, act } from '@testing-library/react';
4
+ import { MockedProvider } from '@apollo/client/testing';
5
+ import * as APIHooks from 'foremanReact/common/hooks/API/APIHooks';
6
+ import * as api from 'foremanReact/redux/API';
7
+
8
+ import JobWizardPageRerun from '../../JobWizardPageRerun';
9
+ import * as selectors from '../../JobWizardSelectors';
10
+ import {
11
+ testSetup,
12
+ mockApi,
13
+ gqlMock,
14
+ jobInvocation,
15
+ } from '../../__tests__/fixtures';
16
+
17
+ const store = testSetup(selectors, api);
18
+ mockApi(api);
19
+ jest.spyOn(APIHooks, 'useAPI');
20
+ APIHooks.useAPI.mockImplementation((action, url) => {
21
+ if (url === '/ui_job_wizard/job_invocation?id=57') {
22
+ return { response: jobInvocation, status: 'RESOLVED' };
23
+ }
24
+ return {};
25
+ });
26
+ jest.useFakeTimers();
27
+
28
+ describe('ReviewDetails', () => {
29
+ it('should call goToStepByName function when StepButton is clicked', async () => {
30
+ render(
31
+ <MockedProvider mocks={gqlMock} addTypename={false}>
32
+ <Provider store={store}>
33
+ <JobWizardPageRerun
34
+ match={{
35
+ params: { id: '57' },
36
+ }}
37
+ />
38
+ </Provider>
39
+ </MockedProvider>
40
+ );
41
+
42
+ act(() => {
43
+ fireEvent.click(screen.getByText('Type of execution'));
44
+ });
45
+ act(() => {
46
+ fireEvent.click(screen.getByText('Future execution'));
47
+
48
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
49
+ });
50
+ act(() => {
51
+ fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
52
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
53
+ });
54
+
55
+ const newStartAtDate = '2030/03/12';
56
+ const newStartAtTime = '14:27';
57
+ const startsAtDateField = () =>
58
+ screen.getByLabelText('starts at datepicker');
59
+ const startsAtTimeField = () =>
60
+ screen.getByLabelText('starts at timepicker');
61
+
62
+ await act(async () => {
63
+ await fireEvent.change(startsAtDateField(), {
64
+ target: { value: newStartAtDate },
65
+ });
66
+ fireEvent.change(startsAtTimeField(), {
67
+ target: { value: newStartAtTime },
68
+ });
69
+ jest.advanceTimersByTime(1000);
70
+ });
71
+
72
+ act(() => {
73
+ fireEvent.click(screen.getByText('Review details'));
74
+ });
75
+ expect(screen.getAllByText('Review details')).toHaveLength(3);
76
+ fireEvent.click(
77
+ screen.getByText('Job template', {
78
+ selector: '.pf-c-button',
79
+ })
80
+ );
81
+ expect(screen.getAllByText('Category and template')).toHaveLength(3);
82
+
83
+ await act(async () => {
84
+ fireEvent.click(screen.getByText('Review details'));
85
+ jest.advanceTimersByTime(1000);
86
+ });
87
+ act(() => {
88
+ fireEvent.click(
89
+ screen.getByText('Target hosts', {
90
+ selector: '.pf-c-button',
91
+ })
92
+ );
93
+ jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
94
+ });
95
+ expect(screen.getAllByText('Target hosts and inputs')).toHaveLength(3);
96
+ act(() => {
97
+ fireEvent.click(screen.getByText('Review details'));
98
+ });
99
+ act(() => {
100
+ fireEvent.click(
101
+ screen.getByText('Advanced fields', {
102
+ selector: '.pf-c-button',
103
+ })
104
+ );
105
+ jest.advanceTimersByTime(1000);
106
+ });
107
+ expect(screen.getAllByText('Advanced fields')).toHaveLength(3);
108
+
109
+ act(() => {
110
+ fireEvent.click(screen.getByText('Review details'));
111
+ });
112
+ act(() => {
113
+ fireEvent.click(screen.getByText('Recurrence'));
114
+ });
115
+ expect(screen.getAllByText('Schedule')).toHaveLength(3);
116
+ });
117
+ });
@@ -0,0 +1,43 @@
1
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
+ import { getWeekDays } from '../Schedule/RepeatWeek';
3
+ import { repeatTypes } from '../../JobWizardConstants';
4
+
5
+ export const parseEnd = (ends, isNeverEnds, repeatAmount) => {
6
+ if (isNeverEnds) {
7
+ return __('Never');
8
+ }
9
+ if (ends) {
10
+ const endsDate = new Date(ends);
11
+ return endsDate.toString();
12
+ }
13
+ return sprintf(__('After %s occurences'), repeatAmount);
14
+ };
15
+
16
+ export const parseRepeat = (repeatType, repeatData) => {
17
+ switch (repeatType) {
18
+ case repeatTypes.hourly:
19
+ return sprintf(__('Every hour at minute %s'), repeatData.minute);
20
+ case repeatTypes.daily:
21
+ return sprintf(__('Every day at %s'), repeatData.at);
22
+ case repeatTypes.weekly: {
23
+ const daysKeys = Object.keys(repeatData.daysOfWeek).filter(
24
+ k => repeatData.daysOfWeek[k]
25
+ );
26
+ const days = getWeekDays()
27
+ .filter((d, index) => index in daysKeys)
28
+ .join(', ');
29
+ return sprintf(__('Every week on %s at %s'), days, repeatData.at);
30
+ }
31
+ case repeatTypes.monthly:
32
+ return sprintf(
33
+ __('Every month on %s at %s'),
34
+ repeatData.days,
35
+ repeatData.at
36
+ );
37
+ case repeatTypes.cronline:
38
+ return `${__('Cron line')} - ${repeatData.cronline}`;
39
+
40
+ default:
41
+ return '';
42
+ }
43
+ };
@@ -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 = [];
@@ -139,7 +139,7 @@ describe('Schedule', () => {
139
139
  });
140
140
 
141
141
  act(() => {
142
- fireEvent.click(screen.getByText('Category and Template'));
142
+ fireEvent.click(screen.getByText('Category and template'));
143
143
  });
144
144
  act(() => {
145
145
  fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
@@ -246,7 +246,7 @@ describe('Schedule', () => {
246
246
  });
247
247
 
248
248
  act(() => {
249
- fireEvent.click(screen.getByText('Category and Template'));
249
+ fireEvent.click(screen.getByText('Category and template'));
250
250
  });
251
251
  act(() => {
252
252
  fireEvent.click(
@@ -294,7 +294,7 @@ describe('Schedule', () => {
294
294
  await act(async () => {
295
295
  fireEvent.click(screen.getByText('Cronline'));
296
296
  });
297
- const newCronline = '1 2';
297
+ const newCronline = '1 2 3 4 5';
298
298
  const cronline = screen.getByLabelText('cronline');
299
299
  expect(cronline.value).toBe('');
300
300
  await act(async () => {
@@ -306,9 +306,9 @@ describe('Schedule', () => {
306
306
  expect(screen.getByText('Review details').disabled).toBeFalsy();
307
307
 
308
308
  await act(async () => {
309
- fireEvent.click(screen.getByText('Category and Template'));
309
+ fireEvent.click(screen.getByText('Category and template'));
310
310
  });
311
- expect(screen.getAllByText('Category and Template')).toHaveLength(3);
311
+ expect(screen.getAllByText('Category and template')).toHaveLength(3);
312
312
 
313
313
  await act(async () => {
314
314
  fireEvent.click(
@@ -319,6 +319,20 @@ describe('Schedule', () => {
319
319
  expect(screen.queryAllByText('Recurring execution')).toHaveLength(3);
320
320
  expect(cronline.value).toBe(newCronline);
321
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
+
322
336
  fireEvent.click(screen.getByText('Cronline'));
323
337
  await act(async () => {
324
338
  fireEvent.click(screen.getByText('Monthly'));