foreman_remote_execution 9.1.0 → 10.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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'));
|