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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +14 -0
- data/.packit.yaml +40 -0
- data/.tx/config +3 -1
- data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/en/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/template_invocation.js +10 -1
- data/app/controllers/job_invocations_controller.rb +20 -1
- data/app/controllers/ui_job_wizard_controller.rb +0 -3
- data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
- data/app/views/job_invocations/_form.html.erb +1 -1
- data/app/views/templates/script/run_command.erb +1 -0
- data/config/routes.rb +1 -0
- data/lib/foreman_remote_execution/engine.rb +8 -8
- data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +12 -3
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/Makefile +6 -3
- data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/de/foreman_remote_execution.po +53 -8
- data/locale/en/foreman_remote_execution.po +52 -7
- data/locale/en_GB/foreman_remote_execution.po +52 -7
- data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/es/foreman_remote_execution.po +56 -11
- data/locale/foreman_remote_execution.pot +193 -126
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +56 -11
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +56 -11
- data/locale/ko/foreman_remote_execution.po +52 -7
- data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/pt_BR/foreman_remote_execution.po +56 -11
- data/locale/ru/foreman_remote_execution.po +52 -7
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +56 -11
- data/locale/zh_TW/foreman_remote_execution.po +52 -7
- data/webpack/JobWizard/Footer.js +104 -0
- data/webpack/JobWizard/JobWizard.js +24 -16
- data/webpack/JobWizard/JobWizard.scss +6 -17
- data/webpack/JobWizard/JobWizardConstants.js +1 -1
- data/webpack/JobWizard/JobWizardPageRerun.js +2 -0
- data/webpack/JobWizard/__tests__/fixtures.js +8 -1
- data/webpack/JobWizard/__tests__/validation.test.js +9 -0
- data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +2 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +28 -14
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +3 -3
- data/webpack/JobWizard/steps/ReviewDetails/ReviewDetails.test.js +117 -0
- data/webpack/JobWizard/steps/ReviewDetails/helpers.js +43 -0
- data/webpack/JobWizard/steps/ReviewDetails/index.js +169 -18
- data/webpack/JobWizard/steps/Schedule/QueryType.js +1 -1
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +0 -1
- data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +19 -5
- data/webpack/JobWizard/steps/form/DateTimePicker.js +0 -1
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +0 -1
- data/webpack/JobWizard/steps/form/SelectField.js +0 -1
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +2 -2
- data/webpack/react_app/components/RecentJobsCard/constants.js +2 -2
- data/webpack/react_app/components/RegistrationExtension/RexPull.js +0 -2
- 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]':
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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: '
|
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
|
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 __
|
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
|
69
|
-
|
70
|
-
|
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
|
-
{
|
76
|
-
|
77
|
-
|
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:
|
126
|
+
label: (
|
127
|
+
<StepButton stepName={WIZARD_TITLES.hostsAndInputs}>{name}</StepButton>
|
128
|
+
),
|
80
129
|
value: templateValues[name],
|
81
130
|
})),
|
82
131
|
{
|
83
|
-
label:
|
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:
|
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:
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
309
|
+
fireEvent.click(screen.getByText('Category and template'));
|
310
310
|
});
|
311
|
-
expect(screen.getAllByText('Category and
|
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'));
|