foreman_remote_execution 7.2.1 → 8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby_ci.yml +2 -2
- data/app/controllers/ui_job_wizard_controller.rb +15 -0
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/models/job_invocation.rb +2 -4
- data/app/models/job_invocation_composer.rb +5 -2
- data/app/views/templates/script/package_action.erb +8 -3
- data/config/routes.rb +3 -1
- data/lib/foreman_remote_execution/engine.rb +5 -5
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
- data/test/helpers/remote_execution_helper_test.rb +4 -0
- data/test/unit/job_invocation_report_template_test.rb +1 -1
- data/test/unit/job_invocation_test.rb +1 -2
- data/webpack/JobWizard/JobWizard.js +154 -20
- data/webpack/JobWizard/JobWizard.scss +43 -1
- data/webpack/JobWizard/JobWizardConstants.js +11 -1
- data/webpack/JobWizard/JobWizardPageRerun.js +112 -0
- data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +79 -0
- data/webpack/JobWizard/__tests__/fixtures.js +73 -0
- data/webpack/JobWizard/__tests__/integration.test.js +17 -3
- data/webpack/JobWizard/autofill.js +8 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +36 -17
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -3
- data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -3
- data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
- data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
- data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
- data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
- data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
- data/webpack/JobWizard/steps/form/Formatter.js +7 -8
- data/webpack/JobWizard/submit.js +8 -3
- data/webpack/Routes/routes.js +8 -2
- data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
- data/webpack/react_app/components/HostKebab/KebabItems.js +0 -1
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +0 -5
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +59 -51
- data/webpack/react_app/extend/Fills.js +4 -4
- metadata +8 -6
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
- data/webpack/JobWizard/steps/Schedule/index.js +0 -178
@@ -0,0 +1,112 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import URI from 'urijs';
|
4
|
+
import { Alert, Title, Divider, Skeleton } from '@patternfly/react-core';
|
5
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
6
|
+
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
7
|
+
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
8
|
+
import { STATUS } from 'foremanReact/constants';
|
9
|
+
import {
|
10
|
+
useForemanOrganization,
|
11
|
+
useForemanLocation,
|
12
|
+
} from 'foremanReact/Root/Context/ForemanContext';
|
13
|
+
import { JobWizard } from './JobWizard';
|
14
|
+
import { JOB_API_KEY } from './JobWizardConstants';
|
15
|
+
|
16
|
+
const JobWizardPageRerun = ({
|
17
|
+
match: {
|
18
|
+
params: { id },
|
19
|
+
},
|
20
|
+
location: { search },
|
21
|
+
}) => {
|
22
|
+
const uri = new URI(search);
|
23
|
+
const { failed_only: failedOnly } = uri.search(true);
|
24
|
+
const { response, status } = useAPI(
|
25
|
+
'get',
|
26
|
+
`/ui_job_wizard/job_invocation?id=${id}${
|
27
|
+
failedOnly ? '&failed_only=1' : ''
|
28
|
+
}`,
|
29
|
+
JOB_API_KEY
|
30
|
+
);
|
31
|
+
const title = __('Run job');
|
32
|
+
const breadcrumbOptions = {
|
33
|
+
breadcrumbItems: [
|
34
|
+
{ caption: __('Jobs'), url: `/jobs` },
|
35
|
+
{ caption: title },
|
36
|
+
],
|
37
|
+
};
|
38
|
+
|
39
|
+
const jobOrganization = response.job_organization;
|
40
|
+
const jobLocation = response.job_location;
|
41
|
+
const currentOrganization = useForemanOrganization();
|
42
|
+
const currentLocation = useForemanLocation();
|
43
|
+
|
44
|
+
return (
|
45
|
+
<PageLayout
|
46
|
+
header={title}
|
47
|
+
breadcrumbOptions={breadcrumbOptions}
|
48
|
+
searchable={false}
|
49
|
+
>
|
50
|
+
<React.Fragment>
|
51
|
+
<Title headingLevel="h2" size="2xl">
|
52
|
+
{title}
|
53
|
+
</Title>
|
54
|
+
{!status || status === STATUS.PENDING ? (
|
55
|
+
<div style={{ height: '400px' }}>
|
56
|
+
<Skeleton
|
57
|
+
height="100%"
|
58
|
+
screenreaderText="Loading large rectangle contents"
|
59
|
+
/>
|
60
|
+
</div>
|
61
|
+
) : (
|
62
|
+
<React.Fragment>
|
63
|
+
{jobOrganization?.id !== currentOrganization?.id && (
|
64
|
+
<Alert
|
65
|
+
className="job-wizard-alert"
|
66
|
+
variant="warning"
|
67
|
+
title={sprintf(
|
68
|
+
__(
|
69
|
+
"Current organization %s is different from job's organization %s. This job may run on different hosts than before.",
|
70
|
+
currentOrganization,
|
71
|
+
jobOrganization
|
72
|
+
)
|
73
|
+
)}
|
74
|
+
/>
|
75
|
+
)}
|
76
|
+
{jobLocation?.id !== currentLocation?.id && (
|
77
|
+
<Alert
|
78
|
+
className="job-wizard-alert"
|
79
|
+
variant="warning"
|
80
|
+
title={sprintf(
|
81
|
+
__(
|
82
|
+
"Current location %s is different from job's location %s. This job may run on different hosts than before.",
|
83
|
+
currentLocation,
|
84
|
+
jobLocation
|
85
|
+
)
|
86
|
+
)}
|
87
|
+
/>
|
88
|
+
)}
|
89
|
+
<Divider component="div" />
|
90
|
+
<JobWizard
|
91
|
+
rerunData={{ ...response?.job, inputs: response?.inputs } || null}
|
92
|
+
/>
|
93
|
+
</React.Fragment>
|
94
|
+
)}
|
95
|
+
</React.Fragment>
|
96
|
+
</PageLayout>
|
97
|
+
);
|
98
|
+
};
|
99
|
+
JobWizardPageRerun.propTypes = {
|
100
|
+
match: PropTypes.shape({
|
101
|
+
params: PropTypes.shape({
|
102
|
+
id: PropTypes.string.isRequired,
|
103
|
+
}),
|
104
|
+
}).isRequired,
|
105
|
+
location: PropTypes.shape({
|
106
|
+
search: PropTypes.string,
|
107
|
+
}),
|
108
|
+
};
|
109
|
+
JobWizardPageRerun.defaultProps = {
|
110
|
+
location: { search: '' },
|
111
|
+
};
|
112
|
+
export default JobWizardPageRerun;
|
@@ -0,0 +1,79 @@
|
|
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
|
+
|
6
|
+
import * as APIHooks from 'foremanReact/common/hooks/API/APIHooks';
|
7
|
+
import * as api from 'foremanReact/redux/API';
|
8
|
+
import JobWizardPageRerun from '../JobWizardPageRerun';
|
9
|
+
import * as selectors from '../JobWizardSelectors';
|
10
|
+
import { testSetup, mockApi, gqlMock, jobInvocation } from './fixtures';
|
11
|
+
|
12
|
+
const store = testSetup(selectors, api);
|
13
|
+
mockApi(api);
|
14
|
+
jest.spyOn(APIHooks, 'useAPI');
|
15
|
+
APIHooks.useAPI.mockImplementation((action, url) => {
|
16
|
+
if (url === '/ui_job_wizard/job_invocation?id=57') {
|
17
|
+
return { response: jobInvocation, status: 'RESOLVED' };
|
18
|
+
}
|
19
|
+
return {};
|
20
|
+
});
|
21
|
+
|
22
|
+
describe('Job wizard fill', () => {
|
23
|
+
it('fill defaults into fields', async () => {
|
24
|
+
render(
|
25
|
+
<MockedProvider mocks={gqlMock} addTypename={false}>
|
26
|
+
<Provider store={store}>
|
27
|
+
<JobWizardPageRerun
|
28
|
+
match={{
|
29
|
+
params: { id: '57' },
|
30
|
+
}}
|
31
|
+
/>
|
32
|
+
</Provider>
|
33
|
+
</MockedProvider>
|
34
|
+
);
|
35
|
+
await act(async () => {
|
36
|
+
fireEvent.click(screen.getByText('Target hosts and inputs'));
|
37
|
+
});
|
38
|
+
await screen.findByLabelText('plain hidden', {
|
39
|
+
selector: 'textarea',
|
40
|
+
});
|
41
|
+
|
42
|
+
expect(
|
43
|
+
screen.getByLabelText('plain hidden', {
|
44
|
+
selector: 'textarea',
|
45
|
+
}).value
|
46
|
+
).toBe('test command');
|
47
|
+
|
48
|
+
await act(async () => {
|
49
|
+
fireEvent.click(screen.getByText('Advanced fields'));
|
50
|
+
});
|
51
|
+
|
52
|
+
expect(
|
53
|
+
screen.getByLabelText('ssh user', {
|
54
|
+
selector: 'input',
|
55
|
+
}).value
|
56
|
+
).toBe('ssh user');
|
57
|
+
expect(
|
58
|
+
screen.getByLabelText('effective user', {
|
59
|
+
selector: 'input',
|
60
|
+
}).value
|
61
|
+
).toBe('Effective user');
|
62
|
+
expect(
|
63
|
+
screen.getByLabelText('timeout to kill', {
|
64
|
+
selector: 'input',
|
65
|
+
}).value
|
66
|
+
).toBe('1');
|
67
|
+
|
68
|
+
expect(
|
69
|
+
screen.getByLabelText('Concurrency level', {
|
70
|
+
selector: 'input',
|
71
|
+
}).value
|
72
|
+
).toBe('6');
|
73
|
+
expect(
|
74
|
+
screen.getByLabelText('Time span', {
|
75
|
+
selector: 'input',
|
76
|
+
}).value
|
77
|
+
).toBe('4');
|
78
|
+
});
|
79
|
+
});
|
@@ -106,6 +106,7 @@ export const testSetup = (selectors, api) => {
|
|
106
106
|
jest.spyOn(selectors, 'selectJobCategories');
|
107
107
|
jest.spyOn(selectors, 'selectJobCategoriesStatus');
|
108
108
|
jest.spyOn(selectors, 'selectWithKatello');
|
109
|
+
jest.spyOn(selectors, 'selectEffectiveUser');
|
109
110
|
|
110
111
|
jest.spyOn(selectors, 'selectTemplateInputs');
|
111
112
|
jest.spyOn(selectors, 'selectAdvancedTemplateInputs');
|
@@ -122,6 +123,10 @@ export const testSetup = (selectors, api) => {
|
|
122
123
|
{ ...jobTemplate, id: 2, name: 'template2' },
|
123
124
|
]);
|
124
125
|
selectors.selectJobTemplate.mockImplementation(() => jobTemplateResponse);
|
126
|
+
|
127
|
+
selectors.selectEffectiveUser.mockImplementation(
|
128
|
+
() => jobTemplateResponse.effective_user
|
129
|
+
);
|
125
130
|
const mockStore = configureMockStore([]);
|
126
131
|
const store = mockStore({
|
127
132
|
ForemanTasksTask: {
|
@@ -224,3 +229,71 @@ export const gqlMock = [
|
|
224
229
|
},
|
225
230
|
},
|
226
231
|
];
|
232
|
+
|
233
|
+
export const jobInvocation = {
|
234
|
+
job: {
|
235
|
+
job_category: 'Ansible Commands',
|
236
|
+
targeting: {
|
237
|
+
user_id: 4,
|
238
|
+
search_query: 'name ~ *',
|
239
|
+
bookmark_id: null,
|
240
|
+
targeting_type: 'static_query',
|
241
|
+
randomized_ordering: true,
|
242
|
+
},
|
243
|
+
triggering: {
|
244
|
+
mode: 'immediate',
|
245
|
+
start_at: null,
|
246
|
+
start_before: null,
|
247
|
+
},
|
248
|
+
ssh_user: 'ssh user',
|
249
|
+
description_format: null,
|
250
|
+
concurrency_control: {
|
251
|
+
level: 6,
|
252
|
+
time_span: 4,
|
253
|
+
},
|
254
|
+
execution_timeout_interval: 1,
|
255
|
+
remote_execution_feature_id: null,
|
256
|
+
template_invocations: [
|
257
|
+
{
|
258
|
+
template_id: 263,
|
259
|
+
effective_user: 'Effective user',
|
260
|
+
input_values: [
|
261
|
+
{
|
262
|
+
template_input_id: 162,
|
263
|
+
value: 'test command',
|
264
|
+
},
|
265
|
+
],
|
266
|
+
},
|
267
|
+
],
|
268
|
+
reruns: 57,
|
269
|
+
},
|
270
|
+
job_organization: {
|
271
|
+
id: 5,
|
272
|
+
name: 'ana-praley',
|
273
|
+
created_at: '2021-08-26T13:47:35.655+02:00',
|
274
|
+
updated_at: '2021-08-26T13:48:21.435+02:00',
|
275
|
+
ignore_types: [],
|
276
|
+
description: null,
|
277
|
+
label: 'ana-praley',
|
278
|
+
ancestry: null,
|
279
|
+
title: 'ana-praley',
|
280
|
+
manifest_refreshed_at: null,
|
281
|
+
created_in_katello: true,
|
282
|
+
},
|
283
|
+
job_location: {
|
284
|
+
id: 2,
|
285
|
+
name: 'Default Location',
|
286
|
+
created_at: '2021-08-24T15:32:18.830+02:00',
|
287
|
+
updated_at: '2021-08-24T15:32:18.830+02:00',
|
288
|
+
ignore_types: ['ProvisioningTemplate', 'Hostgroup'],
|
289
|
+
description: null,
|
290
|
+
label: null,
|
291
|
+
ancestry: null,
|
292
|
+
title: 'Default Location',
|
293
|
+
manifest_refreshed_at: null,
|
294
|
+
created_in_katello: false,
|
295
|
+
},
|
296
|
+
inputs: {
|
297
|
+
'inputs[plain hidden]': 'test command',
|
298
|
+
},
|
299
|
+
};
|
@@ -45,7 +45,7 @@ describe('Job wizard fill', () => {
|
|
45
45
|
</Provider>
|
46
46
|
);
|
47
47
|
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
48
|
-
|
48
|
+
5
|
49
49
|
);
|
50
50
|
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
51
51
|
expect(store.getActions()).toMatchSnapshot('initial');
|
@@ -79,8 +79,12 @@ describe('Job wizard fill', () => {
|
|
79
79
|
</Provider>
|
80
80
|
</MockedProvider>
|
81
81
|
);
|
82
|
-
const
|
83
|
-
|
82
|
+
const steps = [
|
83
|
+
WIZARD_TITLES.hostsAndInputs,
|
84
|
+
WIZARD_TITLES.categoryAndTemplate,
|
85
|
+
WIZARD_TITLES.advanced,
|
86
|
+
WIZARD_TITLES.review,
|
87
|
+
];
|
84
88
|
// eslint-disable-next-line no-unused-vars
|
85
89
|
for await (const step of steps) {
|
86
90
|
const stepSelector = screen.getByText(step);
|
@@ -92,5 +96,15 @@ describe('Job wizard fill', () => {
|
|
92
96
|
const stepTitles = screen.getAllByText(step);
|
93
97
|
expect(stepTitles).toHaveLength(3);
|
94
98
|
}
|
99
|
+
const step = WIZARD_TITLES.typeOfExecution;
|
100
|
+
const stepTitle = screen.getAllByText(step);
|
101
|
+
expect(stepTitle).toHaveLength(1);
|
102
|
+
expect(screen.queryAllByText('Select the type of execution')).toHaveLength(
|
103
|
+
0
|
104
|
+
);
|
105
|
+
await act(async () => {
|
106
|
+
await fireEvent.click(stepTitle[0]);
|
107
|
+
});
|
108
|
+
expect(screen.getAllByText('Select the type of execution')).toHaveLength(1);
|
95
109
|
});
|
96
110
|
});
|
@@ -11,6 +11,7 @@ export const useAutoFill = ({
|
|
11
11
|
setHostsSearchQuery,
|
12
12
|
setJobTemplateID,
|
13
13
|
setTemplateValues,
|
14
|
+
setAdvancedValues,
|
14
15
|
}) => {
|
15
16
|
const dispatch = useDispatch();
|
16
17
|
|
@@ -49,15 +50,21 @@ export const useAutoFill = ({
|
|
49
50
|
},
|
50
51
|
})
|
51
52
|
);
|
53
|
+
}
|
54
|
+
if (rest) {
|
52
55
|
Object.keys(rest).forEach(key => {
|
53
56
|
const re = /inputs\[(?<input>.*)\]/g;
|
54
57
|
const input = re.exec(key)?.groups?.input;
|
55
58
|
if (input) {
|
56
59
|
setTemplateValues(prev => ({ ...prev, [input]: rest[key] }));
|
60
|
+
setAdvancedValues(prev => ({
|
61
|
+
...prev,
|
62
|
+
templateValues: { ...prev.templateValues, [input]: rest[key] },
|
63
|
+
}));
|
57
64
|
}
|
58
65
|
});
|
59
66
|
}
|
60
67
|
}
|
61
68
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
62
|
-
}, []);
|
69
|
+
}, [fills]);
|
63
70
|
};
|
@@ -17,14 +17,14 @@ import {
|
|
17
17
|
} from '../../../__tests__/fixtures';
|
18
18
|
import { WIZARD_TITLES } from '../../../JobWizardConstants';
|
19
19
|
|
20
|
+
const lodash = require('lodash');
|
21
|
+
|
22
|
+
lodash.debounce = fn => fn;
|
20
23
|
const store = testSetup(selectors, api);
|
21
24
|
mockApi(api);
|
22
25
|
|
23
|
-
jest.
|
26
|
+
jest.useFakeTimers();
|
24
27
|
|
25
|
-
selectors.selectEffectiveUser.mockImplementation(
|
26
|
-
() => jobTemplateResponse.effective_user
|
27
|
-
);
|
28
28
|
describe('AdvancedFields', () => {
|
29
29
|
it('should save data between steps for advanced fields', async () => {
|
30
30
|
const wrapper = mount(
|
@@ -49,6 +49,10 @@ describe('AdvancedFields', () => {
|
|
49
49
|
.find('.pf-c-wizard__nav-link')
|
50
50
|
.at(2)
|
51
51
|
.simulate('click'); // Advanced step
|
52
|
+
|
53
|
+
await act(async () => {
|
54
|
+
jest.runAllTimers(); // to handle pf4 date picker popover
|
55
|
+
});
|
52
56
|
const effectiveUserInput = () => wrapper.find('input#effective-user');
|
53
57
|
const advancedTemplateInput = () =>
|
54
58
|
wrapper.find('.pf-c-form__group-control textarea');
|
@@ -78,6 +82,9 @@ describe('AdvancedFields', () => {
|
|
78
82
|
.at(2)
|
79
83
|
.simulate('click'); // Advanced step
|
80
84
|
|
85
|
+
await act(async () => {
|
86
|
+
jest.runOnlyPendingTimers(); // to handle pf4 date picker popover
|
87
|
+
});
|
81
88
|
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
82
89
|
expect(advancedTemplateInput().prop('value')).toEqual(
|
83
90
|
advancedTemplateInputValue
|
@@ -93,10 +100,13 @@ describe('AdvancedFields', () => {
|
|
93
100
|
);
|
94
101
|
await act(async () => {
|
95
102
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
103
|
+
jest.runOnlyPendingTimers(); // to handle pf4 date picker popover
|
96
104
|
});
|
105
|
+
|
97
106
|
const searchValue = 'search test';
|
98
107
|
const textValue = 'I am a text';
|
99
|
-
const dateValue = '
|
108
|
+
const dateValue = '2022/06/24';
|
109
|
+
const timeValue = '12:34:56';
|
100
110
|
const textField = screen.getByLabelText('adv plain hidden', {
|
101
111
|
selector: 'textarea',
|
102
112
|
});
|
@@ -105,9 +115,8 @@ describe('AdvancedFields', () => {
|
|
105
115
|
'adv resource select toggle'
|
106
116
|
);
|
107
117
|
const searchField = screen.getByPlaceholderText('Filter...');
|
108
|
-
const dateField = screen.getByLabelText('adv date'
|
109
|
-
|
110
|
-
});
|
118
|
+
const dateField = screen.getByLabelText('adv date datepicker');
|
119
|
+
const timeField = screen.getByLabelText('adv date timepicker');
|
111
120
|
|
112
121
|
fireEvent.click(selectField);
|
113
122
|
await act(async () => {
|
@@ -115,18 +124,20 @@ describe('AdvancedFields', () => {
|
|
115
124
|
fireEvent.click(screen.getAllByText(WIZARD_TITLES.advanced)[0]); // to remove focus
|
116
125
|
|
117
126
|
fireEvent.click(resourceSelectField);
|
118
|
-
|
119
|
-
|
120
|
-
await fireEvent.change(textField, {
|
127
|
+
fireEvent.click(screen.getByText('resource2'));
|
128
|
+
fireEvent.change(textField, {
|
121
129
|
target: { value: textValue },
|
122
130
|
});
|
123
|
-
|
124
|
-
await fireEvent.change(searchField, {
|
131
|
+
fireEvent.change(searchField, {
|
125
132
|
target: { value: searchValue },
|
126
133
|
});
|
127
134
|
await fireEvent.change(dateField, {
|
128
135
|
target: { value: dateValue },
|
129
136
|
});
|
137
|
+
fireEvent.change(timeField, {
|
138
|
+
target: { value: timeValue },
|
139
|
+
});
|
140
|
+
jest.runAllTimers(); // to handle pf4 date picker popover
|
130
141
|
});
|
131
142
|
expect(
|
132
143
|
screen.getByLabelText('adv plain hidden', {
|
@@ -134,7 +145,8 @@ describe('AdvancedFields', () => {
|
|
134
145
|
}).value
|
135
146
|
).toBe(textValue);
|
136
147
|
expect(searchField.value).toBe(searchValue);
|
137
|
-
expect(
|
148
|
+
expect(screen.getByLabelText('adv date datepicker').value).toBe(dateValue);
|
149
|
+
expect(timeField.value).toBe(timeValue);
|
138
150
|
await act(async () => {
|
139
151
|
fireEvent.click(screen.getByText(WIZARD_TITLES.categoryAndTemplate));
|
140
152
|
});
|
@@ -143,11 +155,13 @@ describe('AdvancedFields', () => {
|
|
143
155
|
);
|
144
156
|
|
145
157
|
await act(async () => {
|
146
|
-
fireEvent.click(screen.getByText(
|
158
|
+
await fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
159
|
+
jest.runOnlyPendingTimers();
|
147
160
|
});
|
148
161
|
expect(textField.value).toBe(textValue);
|
149
162
|
expect(searchField.value).toBe(searchValue);
|
150
163
|
expect(dateField.value).toBe(dateValue);
|
164
|
+
expect(timeField.value).toBe(timeValue);
|
151
165
|
expect(screen.queryAllByText('option 1')).toHaveLength(0);
|
152
166
|
expect(screen.queryAllByText('option 2')).toHaveLength(1);
|
153
167
|
expect(screen.queryAllByDisplayValue('resource1')).toHaveLength(0);
|
@@ -162,7 +176,8 @@ describe('AdvancedFields', () => {
|
|
162
176
|
</MockedProvider>
|
163
177
|
);
|
164
178
|
await act(async () => {
|
165
|
-
fireEvent.click(screen.getByText(
|
179
|
+
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
180
|
+
jest.runAllTimers(); // to handle pf4 date picker popover
|
166
181
|
});
|
167
182
|
|
168
183
|
expect(
|
@@ -197,6 +212,7 @@ describe('AdvancedFields', () => {
|
|
197
212
|
);
|
198
213
|
await act(async () => {
|
199
214
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
215
|
+
jest.runAllTimers(); // to handle pf4 date picker popover
|
200
216
|
});
|
201
217
|
|
202
218
|
const textField = screen.getByLabelText('adv plain hidden', {
|
@@ -264,6 +280,7 @@ describe('AdvancedFields', () => {
|
|
264
280
|
);
|
265
281
|
await act(async () => {
|
266
282
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
283
|
+
jest.runAllTimers(); // to handle pf4 date picker popover
|
267
284
|
});
|
268
285
|
expect(
|
269
286
|
screen.getByLabelText('description preview', {
|
@@ -332,6 +349,7 @@ describe('AdvancedFields', () => {
|
|
332
349
|
);
|
333
350
|
await act(async () => {
|
334
351
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
352
|
+
jest.runAllTimers(); // to handle pf4 date picker popover
|
335
353
|
});
|
336
354
|
expect(
|
337
355
|
screen.getByLabelText('description preview', {
|
@@ -351,6 +369,7 @@ describe('AdvancedFields', () => {
|
|
351
369
|
);
|
352
370
|
await act(async () => {
|
353
371
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
372
|
+
jest.runAllTimers(); // to handle pf4 date picker popover
|
354
373
|
});
|
355
374
|
const resourceSelectField = screen.getByLabelText(
|
356
375
|
'adv resource select typeahead input'
|
@@ -361,7 +380,7 @@ describe('AdvancedFields', () => {
|
|
361
380
|
target: { value: 'some search' },
|
362
381
|
});
|
363
382
|
|
364
|
-
|
383
|
+
jest.runAllTimers();
|
365
384
|
});
|
366
385
|
expect(newStore.getActions()).toMatchSnapshot('resource search');
|
367
386
|
});
|
@@ -25,7 +25,7 @@ const ConnectedCategoryAndTemplate = ({
|
|
25
25
|
setJobTemplate,
|
26
26
|
category,
|
27
27
|
setCategory,
|
28
|
-
|
28
|
+
isCategoryPreselected,
|
29
29
|
}) => {
|
30
30
|
const dispatch = useDispatch();
|
31
31
|
|
@@ -44,7 +44,7 @@ const ConnectedCategoryAndTemplate = ({
|
|
44
44
|
default_template: defaultTemplate,
|
45
45
|
},
|
46
46
|
}) => {
|
47
|
-
if (!
|
47
|
+
if (!isCategoryPreselected) {
|
48
48
|
setCategory(defaultCategory || jobCategories[0] || '');
|
49
49
|
if (defaultTemplate) setJobTemplate(defaultTemplate);
|
50
50
|
}
|
@@ -106,7 +106,7 @@ ConnectedCategoryAndTemplate.propTypes = {
|
|
106
106
|
setJobTemplate: PropTypes.func.isRequired,
|
107
107
|
category: PropTypes.string.isRequired,
|
108
108
|
setCategory: PropTypes.func.isRequired,
|
109
|
-
|
109
|
+
isCategoryPreselected: PropTypes.bool.isRequired,
|
110
110
|
};
|
111
111
|
ConnectedCategoryAndTemplate.defaultProps = { jobTemplate: null };
|
112
112
|
|
@@ -108,9 +108,7 @@ const ReviewDetails = ({
|
|
108
108
|
const detailsSecondHalf = [
|
109
109
|
{
|
110
110
|
label: __('Schedule type'),
|
111
|
-
value: scheduleValue.
|
112
|
-
? __('Schedule for future execution')
|
113
|
-
: __('Execute now'),
|
111
|
+
value: scheduleValue.scheduleType,
|
114
112
|
},
|
115
113
|
{
|
116
114
|
label: __('Recurrence'),
|
@@ -4,7 +4,7 @@ import { TextInput, FormGroup } from '@patternfly/react-core';
|
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
import { helpLabel } from '../form/FormHelpers';
|
6
6
|
|
7
|
-
export const PurposeField = ({
|
7
|
+
export const PurposeField = ({ purpose, setPurpose }) => (
|
8
8
|
<FormGroup
|
9
9
|
label={__('Purpose')}
|
10
10
|
labelIcon={helpLabel(
|
@@ -14,7 +14,6 @@ export const PurposeField = ({ isDisabled, purpose, setPurpose }) => (
|
|
14
14
|
)}
|
15
15
|
>
|
16
16
|
<TextInput
|
17
|
-
isDisabled={isDisabled}
|
18
17
|
aria-label="purpose"
|
19
18
|
type="text"
|
20
19
|
value={purpose}
|
@@ -25,7 +24,6 @@ export const PurposeField = ({ isDisabled, purpose, setPurpose }) => (
|
|
25
24
|
</FormGroup>
|
26
25
|
);
|
27
26
|
PurposeField.propTypes = {
|
28
|
-
isDisabled: PropTypes.bool.isRequired,
|
29
27
|
purpose: PropTypes.string.isRequired,
|
30
28
|
setPurpose: PropTypes.func.isRequired,
|
31
29
|
};
|
@@ -1,48 +1,41 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
1
|
import { FormGroup, Radio } from '@patternfly/react-core';
|
4
2
|
import { translate as __ } from 'foremanReact/common/I18n';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import React from 'react';
|
5
5
|
import { helpLabel } from '../form/FormHelpers';
|
6
6
|
|
7
7
|
export const QueryType = ({ isTypeStatic, setIsTypeStatic }) => (
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
isChecked={!isTypeStatic}
|
40
|
-
name="query-type"
|
41
|
-
onChange={() => setIsTypeStatic(false)}
|
42
|
-
id="query-type-dynamic"
|
43
|
-
label={__('Dynamic query')}
|
44
|
-
/>
|
45
|
-
</FormGroup>
|
8
|
+
<>
|
9
|
+
<FormGroup
|
10
|
+
label={__('Query type')}
|
11
|
+
fieldId="query-type-static"
|
12
|
+
labelIcon={helpLabel(
|
13
|
+
__('Type has impact on when is the query evaluated to hosts.'),
|
14
|
+
'query-type'
|
15
|
+
)}
|
16
|
+
>
|
17
|
+
<Radio
|
18
|
+
isChecked={isTypeStatic}
|
19
|
+
name="query-type"
|
20
|
+
onChange={() => setIsTypeStatic(true)}
|
21
|
+
id="query-type-static"
|
22
|
+
label={__('Static query')}
|
23
|
+
body={__('evaluates just after you submit this form')}
|
24
|
+
/>
|
25
|
+
</FormGroup>
|
26
|
+
<FormGroup fieldId="query-type-dynamic">
|
27
|
+
<Radio
|
28
|
+
isChecked={!isTypeStatic}
|
29
|
+
name="query-type"
|
30
|
+
onChange={() => setIsTypeStatic(false)}
|
31
|
+
id="query-type-dynamic"
|
32
|
+
label={__('Dynamic query')}
|
33
|
+
body={__(
|
34
|
+
"evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it"
|
35
|
+
)}
|
36
|
+
/>
|
37
|
+
</FormGroup>
|
38
|
+
</>
|
46
39
|
);
|
47
40
|
|
48
41
|
QueryType.propTypes = {
|