foreman_remote_execution 8.0.0 → 8.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/job_invocations_controller.rb +1 -2
- data/app/controllers/job_templates_controller.rb +1 -1
- data/app/controllers/ui_job_wizard_controller.rb +1 -1
- data/app/helpers/job_invocations_helper.rb +0 -7
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/lib/actions/remote_execution/proxy_action.rb +46 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +38 -11
- data/app/lib/actions/remote_execution/run_hosts_job.rb +7 -6
- data/app/lib/actions/remote_execution/template_invocation_progress_logging.rb +27 -0
- data/app/models/job_invocation.rb +5 -9
- data/app/models/job_invocation_composer.rb +4 -0
- data/app/models/remote_execution_provider.rb +10 -2
- data/app/models/ssh_execution_provider.rb +1 -0
- data/app/models/template_invocation.rb +1 -0
- data/app/models/template_invocation_event.rb +11 -0
- data/app/views/job_invocations/_form.html.erb +4 -0
- data/app/views/job_invocations/new.html.erb +5 -0
- data/app/views/templates/script/package_action.erb +1 -1
- data/config/routes.rb +5 -5
- data/db/migrate/20220713095705_create_template_invocation_events.rb +17 -0
- data/db/migrate/20220822155946_add_time_to_pickup_to_job_invocation.rb +5 -0
- data/extra/cockpit/foreman-cockpit-session +303 -230
- data/extra/cockpit/foreman-cockpit.service +1 -0
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +12 -7
- data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +131 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/unit/remote_execution_provider_test.rb +22 -0
- data/webpack/JobWizard/JobWizard.js +53 -18
- data/webpack/JobWizard/JobWizard.scss +3 -0
- data/webpack/JobWizard/JobWizardConstants.js +1 -1
- data/webpack/JobWizard/JobWizardHelpers.js +15 -0
- data/webpack/JobWizard/JobWizardPageRerun.js +29 -5
- data/webpack/JobWizard/JobWizardSelectors.js +8 -2
- data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +5 -0
- data/webpack/JobWizard/__tests__/fixtures.js +26 -2
- data/webpack/JobWizard/autofill.js +32 -10
- data/webpack/JobWizard/index.js +25 -6
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +25 -0
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +12 -1
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +41 -6
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +1 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +1 -1
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +4 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +6 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +28 -20
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +32 -0
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +2 -2
- data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +21 -1
- data/webpack/JobWizard/steps/form/Formatter.js +22 -6
- data/webpack/JobWizard/steps/form/ResourceSelect.js +97 -10
- data/webpack/JobWizard/steps/form/SearchSelect.js +2 -2
- data/webpack/JobWizard/steps/form/SelectField.js +4 -0
- data/webpack/JobWizard/submit.js +3 -1
- data/webpack/JobWizard/validation.js +1 -0
- data/webpack/Routes/routes.js +3 -3
- data/webpack/react_app/components/FeaturesDropdown/actions.js +23 -2
- data/webpack/react_app/components/FeaturesDropdown/index.js +2 -0
- data/webpack/react_app/components/HostKebab/KebabItems.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +5 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +51 -59
- data/webpack/react_app/extend/Fills.js +3 -3
- metadata +12 -5
@@ -1,3 +1,4 @@
|
|
1
|
+
/* eslint-disable camelcase */
|
1
2
|
import React from 'react';
|
2
3
|
import PropTypes from 'prop-types';
|
3
4
|
import { useSelector } from 'react-redux';
|
@@ -5,10 +6,13 @@ import { Form } from '@patternfly/react-core';
|
|
5
6
|
import {
|
6
7
|
selectEffectiveUser,
|
7
8
|
selectAdvancedTemplateInputs,
|
9
|
+
selectJobTemplate,
|
8
10
|
} from '../../JobWizardSelectors';
|
11
|
+
import { generateDefaultDescription } from '../../JobWizardHelpers';
|
9
12
|
import {
|
10
13
|
EffectiveUserField,
|
11
14
|
TimeoutToKillField,
|
15
|
+
TimeToPickupField,
|
12
16
|
PasswordField,
|
13
17
|
KeyPassphraseField,
|
14
18
|
EffectiveUserPasswordField,
|
@@ -29,6 +33,7 @@ export const AdvancedFields = ({
|
|
29
33
|
}) => {
|
30
34
|
const effectiveUser = useSelector(selectEffectiveUser);
|
31
35
|
const advancedTemplateInputs = useSelector(selectAdvancedTemplateInputs);
|
36
|
+
const jobTemplate = useSelector(selectJobTemplate);
|
32
37
|
return (
|
33
38
|
<>
|
34
39
|
<WizardTitle
|
@@ -40,9 +45,11 @@ export const AdvancedFields = ({
|
|
40
45
|
inputs={advancedTemplateInputs}
|
41
46
|
value={advancedValues.templateValues}
|
42
47
|
setValue={newValue => setAdvancedValues({ templateValues: newValue })}
|
48
|
+
defaultValue={jobTemplate}
|
43
49
|
/>
|
44
50
|
<SSHUserField
|
45
51
|
value={advancedValues.sshUser}
|
52
|
+
defaultValue={jobTemplate.ssh_user}
|
46
53
|
setValue={newValue =>
|
47
54
|
setAdvancedValues({
|
48
55
|
sshUser: newValue,
|
@@ -52,6 +59,7 @@ export const AdvancedFields = ({
|
|
52
59
|
{effectiveUser?.overridable && (
|
53
60
|
<EffectiveUserField
|
54
61
|
value={advancedValues.effectiveUserValue}
|
62
|
+
defaultValue={jobTemplate.effective_user?.value}
|
55
63
|
setValue={newValue =>
|
56
64
|
setAdvancedValues({
|
57
65
|
effectiveUserValue: newValue,
|
@@ -63,15 +71,30 @@ export const AdvancedFields = ({
|
|
63
71
|
inputValues={{ ...templateValues, ...advancedValues.templateValues }}
|
64
72
|
value={advancedValues.description}
|
65
73
|
setValue={newValue => setAdvancedValues({ description: newValue })}
|
74
|
+
defaultValue={generateDefaultDescription({
|
75
|
+
description_format: jobTemplate.job_template.description_format,
|
76
|
+
advancedInputs: jobTemplate.advanced_template_inputs,
|
77
|
+
inputs: jobTemplate.template_inputs,
|
78
|
+
name: jobTemplate.job_template.name,
|
79
|
+
})}
|
66
80
|
/>
|
67
81
|
<TimeoutToKillField
|
68
82
|
value={advancedValues.timeoutToKill}
|
83
|
+
defaultValue={jobTemplate.job_template.execution_timeout_interval}
|
69
84
|
setValue={newValue =>
|
70
85
|
setAdvancedValues({
|
71
86
|
timeoutToKill: newValue,
|
72
87
|
})
|
73
88
|
}
|
74
89
|
/>
|
90
|
+
<TimeToPickupField
|
91
|
+
value={advancedValues.timeToPickup}
|
92
|
+
setValue={newValue =>
|
93
|
+
setAdvancedValues({
|
94
|
+
timeToPickup: newValue,
|
95
|
+
})
|
96
|
+
}
|
97
|
+
/>
|
75
98
|
<PasswordField
|
76
99
|
value={advancedValues.password}
|
77
100
|
setValue={newValue =>
|
@@ -98,6 +121,7 @@ export const AdvancedFields = ({
|
|
98
121
|
/>
|
99
122
|
<ConcurrencyLevelField
|
100
123
|
value={advancedValues.concurrencyLevel}
|
124
|
+
defaultValue={jobTemplate.concurrency_control?.level}
|
101
125
|
setValue={newValue =>
|
102
126
|
setAdvancedValues({
|
103
127
|
concurrencyLevel: newValue,
|
@@ -106,6 +130,7 @@ export const AdvancedFields = ({
|
|
106
130
|
/>
|
107
131
|
<TimeSpanLevelField
|
108
132
|
value={advancedValues.timeSpan}
|
133
|
+
defaultValue={jobTemplate.concurrency_control?.time_span}
|
109
134
|
setValue={newValue =>
|
110
135
|
setAdvancedValues({
|
111
136
|
timeSpan: newValue,
|
@@ -7,8 +7,14 @@ import {
|
|
7
7
|
selectTemplateInputs,
|
8
8
|
selectAdvancedTemplateInputs,
|
9
9
|
} from '../../JobWizardSelectors';
|
10
|
+
import { ResetDefault } from '../form/FormHelpers';
|
10
11
|
|
11
|
-
export const DescriptionField = ({
|
12
|
+
export const DescriptionField = ({
|
13
|
+
inputValues,
|
14
|
+
value,
|
15
|
+
setValue,
|
16
|
+
defaultValue,
|
17
|
+
}) => {
|
12
18
|
const inputs = [
|
13
19
|
...useSelector(selectTemplateInputs),
|
14
20
|
...useSelector(selectAdvancedTemplateInputs),
|
@@ -45,6 +51,9 @@ export const DescriptionField = ({ inputValues, value, setValue }) => {
|
|
45
51
|
return (
|
46
52
|
<FormGroup
|
47
53
|
label={__('Description')}
|
54
|
+
labelInfo={
|
55
|
+
<ResetDefault setValue={setValue} defaultValue={defaultValue} />
|
56
|
+
}
|
48
57
|
fieldId="description"
|
49
58
|
helperText={
|
50
59
|
<Button variant="link" isInline onClick={togglePreview}>
|
@@ -84,7 +93,9 @@ DescriptionField.propTypes = {
|
|
84
93
|
inputValues: PropTypes.object.isRequired,
|
85
94
|
value: PropTypes.string,
|
86
95
|
setValue: PropTypes.func.isRequired,
|
96
|
+
defaultValue: PropTypes.string,
|
87
97
|
};
|
88
98
|
DescriptionField.defaultProps = {
|
89
99
|
value: '',
|
100
|
+
defaultValue: '',
|
90
101
|
};
|
@@ -2,11 +2,11 @@ import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import { FormGroup, TextInput, Radio } from '@patternfly/react-core';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
-
import { helpLabel } from '../form/FormHelpers';
|
5
|
+
import { helpLabel, ResetDefault } from '../form/FormHelpers';
|
6
6
|
import { formatter } from '../form/Formatter';
|
7
7
|
import { NumberInput } from '../form/NumberInput';
|
8
8
|
|
9
|
-
export const EffectiveUserField = ({ value, setValue }) => (
|
9
|
+
export const EffectiveUserField = ({ value, setValue, defaultValue }) => (
|
10
10
|
<FormGroup
|
11
11
|
label={__('Effective user')}
|
12
12
|
labelIcon={helpLabel(
|
@@ -16,6 +16,7 @@ export const EffectiveUserField = ({ value, setValue }) => (
|
|
16
16
|
'effective-user'
|
17
17
|
)}
|
18
18
|
fieldId="effective-user"
|
19
|
+
labelInfo={<ResetDefault setValue={setValue} defaultValue={defaultValue} />}
|
19
20
|
>
|
20
21
|
<TextInput
|
21
22
|
aria-label="effective user"
|
@@ -28,7 +29,7 @@ export const EffectiveUserField = ({ value, setValue }) => (
|
|
28
29
|
</FormGroup>
|
29
30
|
);
|
30
31
|
|
31
|
-
export const TimeoutToKillField = ({ value, setValue }) => (
|
32
|
+
export const TimeoutToKillField = ({ value, setValue, defaultValue }) => (
|
32
33
|
<NumberInput
|
33
34
|
formProps={{
|
34
35
|
label: __('Timeout to kill'),
|
@@ -39,6 +40,9 @@ export const TimeoutToKillField = ({ value, setValue }) => (
|
|
39
40
|
'timeout-to-kill'
|
40
41
|
),
|
41
42
|
fieldId: 'timeout-to-kill',
|
43
|
+
labelInfo: (
|
44
|
+
<ResetDefault setValue={setValue} defaultValue={defaultValue} />
|
45
|
+
),
|
42
46
|
}}
|
43
47
|
inputProps={{
|
44
48
|
value,
|
@@ -49,6 +53,26 @@ export const TimeoutToKillField = ({ value, setValue }) => (
|
|
49
53
|
}}
|
50
54
|
/>
|
51
55
|
);
|
56
|
+
export const TimeToPickupField = ({ value, setValue }) => (
|
57
|
+
<NumberInput
|
58
|
+
formProps={{
|
59
|
+
label: __('Time to pickup'),
|
60
|
+
labelIcon: helpLabel(
|
61
|
+
__(
|
62
|
+
'Interval in seconds, if the job is not picked up by a client within this interval it will be cancelled.'
|
63
|
+
),
|
64
|
+
'time-to-pickup'
|
65
|
+
),
|
66
|
+
fieldId: 'time-to-pickup',
|
67
|
+
}}
|
68
|
+
inputProps={{
|
69
|
+
value,
|
70
|
+
autoComplete: 'time-to-pickup',
|
71
|
+
id: 'time-to-pickup',
|
72
|
+
onChange: newValue => setValue(newValue),
|
73
|
+
}}
|
74
|
+
/>
|
75
|
+
);
|
52
76
|
|
53
77
|
export const PasswordField = ({ value, setValue }) => (
|
54
78
|
<FormGroup
|
@@ -119,7 +143,7 @@ export const EffectiveUserPasswordField = ({ value, setValue }) => (
|
|
119
143
|
</FormGroup>
|
120
144
|
);
|
121
145
|
|
122
|
-
export const ConcurrencyLevelField = ({ value, setValue }) => (
|
146
|
+
export const ConcurrencyLevelField = ({ value, setValue, defaultValue }) => (
|
123
147
|
<NumberInput
|
124
148
|
formProps={{
|
125
149
|
label: __('Concurrency level'),
|
@@ -130,6 +154,9 @@ export const ConcurrencyLevelField = ({ value, setValue }) => (
|
|
130
154
|
'concurrency-level'
|
131
155
|
),
|
132
156
|
fieldId: 'concurrency-level',
|
157
|
+
labelInfo: (
|
158
|
+
<ResetDefault setValue={setValue} defaultValue={defaultValue} />
|
159
|
+
),
|
133
160
|
}}
|
134
161
|
inputProps={{
|
135
162
|
min: 1,
|
@@ -142,7 +169,7 @@ export const ConcurrencyLevelField = ({ value, setValue }) => (
|
|
142
169
|
/>
|
143
170
|
);
|
144
171
|
|
145
|
-
export const TimeSpanLevelField = ({ value, setValue }) => (
|
172
|
+
export const TimeSpanLevelField = ({ value, setValue, defaultValue }) => (
|
146
173
|
<NumberInput
|
147
174
|
formProps={{
|
148
175
|
label: __('Time span'),
|
@@ -153,6 +180,9 @@ export const TimeSpanLevelField = ({ value, setValue }) => (
|
|
153
180
|
'time-span'
|
154
181
|
),
|
155
182
|
fieldId: 'time-span',
|
183
|
+
labelInfo: (
|
184
|
+
<ResetDefault setValue={setValue} defaultValue={defaultValue} />
|
185
|
+
),
|
156
186
|
}}
|
157
187
|
inputProps={{
|
158
188
|
min: 1,
|
@@ -204,11 +234,12 @@ export const TemplateInputsFields = ({ inputs, value, setValue }) => (
|
|
204
234
|
<>{inputs?.map(input => formatter(input, value, setValue))}</>
|
205
235
|
);
|
206
236
|
|
207
|
-
export const SSHUserField = ({ value, setValue }) => (
|
237
|
+
export const SSHUserField = ({ value, setValue, defaultValue }) => (
|
208
238
|
<FormGroup
|
209
239
|
label={__('SSH user')}
|
210
240
|
labelIcon={helpLabel(__('A user to be used for SSH.'), 'ssh-user')}
|
211
241
|
fieldId="ssh-user"
|
242
|
+
labelInfo={<ResetDefault setValue={setValue} defaultValue={defaultValue} />}
|
212
243
|
>
|
213
244
|
<TextInput
|
214
245
|
aria-label="ssh user"
|
@@ -224,13 +255,17 @@ export const SSHUserField = ({ value, setValue }) => (
|
|
224
255
|
EffectiveUserField.propTypes = {
|
225
256
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
226
257
|
setValue: PropTypes.func.isRequired,
|
258
|
+
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
227
259
|
};
|
228
260
|
EffectiveUserField.defaultProps = {
|
229
261
|
value: '',
|
262
|
+
defaultValue: null,
|
230
263
|
};
|
231
264
|
|
232
265
|
TimeoutToKillField.propTypes = EffectiveUserField.propTypes;
|
233
266
|
TimeoutToKillField.defaultProps = EffectiveUserField.defaultProps;
|
267
|
+
TimeToPickupField.propTypes = EffectiveUserField.propTypes;
|
268
|
+
TimeToPickupField.defaultProps = EffectiveUserField.defaultProps;
|
234
269
|
PasswordField.propTypes = EffectiveUserField.propTypes;
|
235
270
|
PasswordField.defaultProps = EffectiveUserField.defaultProps;
|
236
271
|
KeyPassphraseField.propTypes = EffectiveUserField.propTypes;
|
@@ -42,8 +42,10 @@ export const CategoryAndTemplate = ({
|
|
42
42
|
)?.name;
|
43
43
|
|
44
44
|
const onSelectCategory = newCategory => {
|
45
|
-
|
46
|
-
|
45
|
+
if (selectedCategory !== newCategory) {
|
46
|
+
setCategory(newCategory);
|
47
|
+
setJobTemplate(null);
|
48
|
+
}
|
47
49
|
};
|
48
50
|
const { categoryError, allTemplatesError, templateError } = errors;
|
49
51
|
const isError = !!(categoryError || allTemplatesError || templateError);
|
@@ -43,14 +43,18 @@ describe('Category And Template', () => {
|
|
43
43
|
|
44
44
|
// Template
|
45
45
|
fireEvent.click(
|
46
|
-
screen.getByDisplayValue('
|
46
|
+
screen.getByDisplayValue('Puppet Agent Disable - Script Default', {
|
47
|
+
selector: 'button',
|
48
|
+
})
|
47
49
|
);
|
48
50
|
await act(async () => {
|
49
51
|
await fireEvent.click(screen.getByText('template2'));
|
50
52
|
});
|
51
53
|
fireEvent.click(screen.getAllByText(WIZARD_TITLES.categoryAndTemplate)[0]); // to remove focus
|
52
54
|
expect(
|
53
|
-
screen.queryAllByDisplayValue('
|
55
|
+
screen.queryAllByDisplayValue('Puppet Agent Disable - Script Default', {
|
56
|
+
selector: 'button',
|
57
|
+
})
|
54
58
|
).toHaveLength(0);
|
55
59
|
expect(
|
56
60
|
screen.queryAllByDisplayValue('template2', { selector: 'button' })
|
@@ -11,6 +11,7 @@ import {
|
|
11
11
|
selectCategoryError,
|
12
12
|
selectAllTemplatesError,
|
13
13
|
selectTemplateError,
|
14
|
+
selectJobTemplatesSearch,
|
14
15
|
} from '../../JobWizardSelectors';
|
15
16
|
import { CategoryAndTemplate } from './CategoryAndTemplate';
|
16
17
|
|
@@ -46,7 +47,8 @@ const ConnectedCategoryAndTemplate = ({
|
|
46
47
|
}) => {
|
47
48
|
if (!isCategoryPreselected) {
|
48
49
|
setCategory(defaultCategory || jobCategories[0] || '');
|
49
|
-
if (defaultTemplate)
|
50
|
+
if (defaultTemplate)
|
51
|
+
setJobTemplate(current => current || defaultTemplate);
|
50
52
|
}
|
51
53
|
},
|
52
54
|
})
|
@@ -56,27 +58,33 @@ const ConnectedCategoryAndTemplate = ({
|
|
56
58
|
}, [jobCategoriesStatus]);
|
57
59
|
|
58
60
|
const jobCategories = useSelector(selectJobCategories);
|
61
|
+
const jobTemplatesSearch = useSelector(selectJobTemplatesSearch);
|
59
62
|
useEffect(() => {
|
60
63
|
if (category) {
|
61
|
-
const
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
64
|
+
const newJobTemplatesSearch = `job_category="${category}"`;
|
65
|
+
if (jobTemplatesSearch !== newJobTemplatesSearch) {
|
66
|
+
const templatesUrlObject = new URI(templatesUrl);
|
67
|
+
dispatch(
|
68
|
+
get({
|
69
|
+
key: JOB_TEMPLATES,
|
70
|
+
url: templatesUrlObject.addSearch({
|
71
|
+
search: newJobTemplatesSearch,
|
72
|
+
per_page: 'all',
|
73
|
+
}),
|
74
|
+
handleSuccess: response => {
|
75
|
+
if (!jobTemplate)
|
76
|
+
setJobTemplate(
|
77
|
+
current =>
|
78
|
+
current ||
|
79
|
+
Number(
|
80
|
+
filterJobTemplates(response?.data?.results)[0]?.id
|
81
|
+
) ||
|
82
|
+
null
|
83
|
+
);
|
84
|
+
},
|
85
|
+
})
|
86
|
+
);
|
87
|
+
}
|
80
88
|
}
|
81
89
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
82
90
|
}, [category, dispatch]);
|
@@ -50,4 +50,36 @@ describe('TemplateInputs', () => {
|
|
50
50
|
});
|
51
51
|
expect(textField.value).toBe(textValue);
|
52
52
|
});
|
53
|
+
it('should set back default data', async () => {
|
54
|
+
render(
|
55
|
+
<MockedProvider mocks={gqlMock} addTypename={false}>
|
56
|
+
<Provider store={store}>
|
57
|
+
<JobWizard />
|
58
|
+
</Provider>
|
59
|
+
</MockedProvider>
|
60
|
+
);
|
61
|
+
await act(async () => {
|
62
|
+
fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
|
63
|
+
});
|
64
|
+
const textValue = 'I am a plain text';
|
65
|
+
const textField = screen.getByLabelText('plain hidden', {
|
66
|
+
selector: 'textarea',
|
67
|
+
});
|
68
|
+
|
69
|
+
await act(async () => {
|
70
|
+
await fireEvent.change(textField, {
|
71
|
+
target: { value: textValue },
|
72
|
+
});
|
73
|
+
});
|
74
|
+
expect(
|
75
|
+
screen.getByLabelText('plain hidden', {
|
76
|
+
selector: 'textarea',
|
77
|
+
}).value
|
78
|
+
).toBe(textValue);
|
79
|
+
|
80
|
+
await act(async () => {
|
81
|
+
fireEvent.click(screen.getByText('Reset to default'));
|
82
|
+
});
|
83
|
+
expect(textField.value).toBe('Default val');
|
84
|
+
});
|
53
85
|
});
|
@@ -35,7 +35,7 @@ import {
|
|
35
35
|
hostQuerySearchID,
|
36
36
|
HOSTS_API,
|
37
37
|
HOSTS_TO_PREVIEW_AMOUNT,
|
38
|
-
|
38
|
+
DEBOUNCE_API,
|
39
39
|
} from '../../JobWizardConstants';
|
40
40
|
import { WizardTitle } from '../form/WizardTitle';
|
41
41
|
import { SelectAPI } from './SelectAPI';
|
@@ -66,7 +66,7 @@ const HostsAndInputs = ({
|
|
66
66
|
},
|
67
67
|
})
|
68
68
|
);
|
69
|
-
},
|
69
|
+
}, DEBOUNCE_API)();
|
70
70
|
}, [
|
71
71
|
dispatch,
|
72
72
|
selected,
|
@@ -127,6 +127,7 @@ const ReviewDetails = ({
|
|
127
127
|
{ label: __('Effective user'), value: advancedValues.effectiveUserValue },
|
128
128
|
{ label: __('Description Template'), value: advancedValues.description },
|
129
129
|
{ label: __('Timeout to kill'), value: advancedValues.timeoutToKill },
|
130
|
+
{ label: __('Time to pickup'), value: advancedValues.timeToPickup },
|
130
131
|
{ label: __('Concurrency level'), value: advancedValues.concurrencyLevel },
|
131
132
|
{ label: __('Time span'), value: advancedValues.timeSpan },
|
132
133
|
{
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Popover, Button } from '@patternfly/react-core';
|
3
4
|
import { HelpIcon } from '@patternfly/react-icons';
|
4
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
6
|
|
@@ -22,3 +23,22 @@ export const helpLabel = (text, id) => {
|
|
22
23
|
export const isPositiveNumber = text => parseInt(text, 10) > 0;
|
23
24
|
|
24
25
|
export const isValidDate = d => d instanceof Date && !Number.isNaN(d);
|
26
|
+
|
27
|
+
export const ResetDefault = ({ setValue, defaultValue }) =>
|
28
|
+
defaultValue && (
|
29
|
+
<Button
|
30
|
+
className="reset-default"
|
31
|
+
component="a"
|
32
|
+
variant="link"
|
33
|
+
isSmall
|
34
|
+
onClick={() => setValue(defaultValue)}
|
35
|
+
>
|
36
|
+
{__('Reset to default')}
|
37
|
+
</Button>
|
38
|
+
);
|
39
|
+
|
40
|
+
ResetDefault.propTypes = {
|
41
|
+
setValue: PropTypes.func.isRequired,
|
42
|
+
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
43
|
+
};
|
44
|
+
ResetDefault.defaultProps = { defaultValue: null };
|
@@ -6,9 +6,9 @@ import SearchBar from 'foremanReact/components/SearchBar';
|
|
6
6
|
import { getControllerSearchProps } from 'foremanReact/constants';
|
7
7
|
import { TRIGGERS } from 'foremanReact/components/AutoComplete/AutoCompleteConstants';
|
8
8
|
import { getResults } from 'foremanReact/components/AutoComplete/AutoCompleteActions';
|
9
|
-
import { helpLabel } from './FormHelpers';
|
9
|
+
import { helpLabel, ResetDefault } from './FormHelpers';
|
10
10
|
import { SelectField } from './SelectField';
|
11
|
-
import {
|
11
|
+
import { ResourceSelect } from './ResourceSelect';
|
12
12
|
import { DateTimePicker } from '../form/DateTimePicker';
|
13
13
|
import { noop } from '../../../helpers';
|
14
14
|
|
@@ -48,12 +48,15 @@ const TemplateSearchField = ({
|
|
48
48
|
<FormGroup
|
49
49
|
label={name}
|
50
50
|
labelIcon={helpLabel(labelText, name)}
|
51
|
+
labelInfo={
|
52
|
+
<ResetDefault defaultValue={defaultValue} setValue={setSearch} />
|
53
|
+
}
|
51
54
|
fieldId={id}
|
52
55
|
isRequired={required}
|
53
56
|
className="foreman-search-field"
|
54
57
|
>
|
55
58
|
<SearchBar
|
56
|
-
initialQuery={
|
59
|
+
initialQuery={values[name]}
|
57
60
|
data={{
|
58
61
|
...props,
|
59
62
|
autocomplete: {
|
@@ -80,10 +83,19 @@ export const formatter = (input, values, setValue) => {
|
|
80
83
|
hidden_value: hidden,
|
81
84
|
resource_type: resourceType,
|
82
85
|
value_type: valueType,
|
86
|
+
default: defaultValue,
|
83
87
|
} = input;
|
84
88
|
const labelText = input.description;
|
85
89
|
const value = values[name];
|
86
90
|
const id = name.replace(/ /g, '-');
|
91
|
+
|
92
|
+
const labelInfo = (
|
93
|
+
<ResetDefault
|
94
|
+
defaultValue={defaultValue}
|
95
|
+
setValue={newValue => setValue({ ...values, [name]: newValue })}
|
96
|
+
/>
|
97
|
+
);
|
98
|
+
|
87
99
|
if (valueType === 'resource') {
|
88
100
|
return (
|
89
101
|
<FormGroup
|
@@ -92,12 +104,13 @@ export const formatter = (input, values, setValue) => {
|
|
92
104
|
labelIcon={helpLabel(labelText, name)}
|
93
105
|
isRequired={required}
|
94
106
|
key={id}
|
107
|
+
labelInfo={labelInfo}
|
95
108
|
>
|
96
|
-
<
|
109
|
+
<ResourceSelect
|
97
110
|
name={name}
|
98
111
|
apiKey={resourceType.replace('::', '')}
|
99
112
|
url={`/ui_job_wizard/resources?resource=${resourceType}`}
|
100
|
-
selected={value ||
|
113
|
+
selected={value || ''}
|
101
114
|
setSelected={newValue => setValue({ ...values, [name]: newValue })}
|
102
115
|
/>
|
103
116
|
</FormGroup>
|
@@ -107,6 +120,7 @@ export const formatter = (input, values, setValue) => {
|
|
107
120
|
const options = input.options.split(/\r?\n/).map(option => option.trim());
|
108
121
|
return (
|
109
122
|
<SelectField
|
123
|
+
labelInfo={labelInfo}
|
110
124
|
key={id}
|
111
125
|
isRequired={required}
|
112
126
|
label={name}
|
@@ -121,6 +135,7 @@ export const formatter = (input, values, setValue) => {
|
|
121
135
|
if (isTextType) {
|
122
136
|
return (
|
123
137
|
<FormGroup
|
138
|
+
labelInfo={labelInfo}
|
124
139
|
key={name}
|
125
140
|
label={name}
|
126
141
|
labelIcon={helpLabel(labelText, name)}
|
@@ -142,6 +157,7 @@ export const formatter = (input, values, setValue) => {
|
|
142
157
|
if (inputType === 'date') {
|
143
158
|
return (
|
144
159
|
<FormGroup
|
160
|
+
labelInfo={labelInfo}
|
145
161
|
key={name}
|
146
162
|
label={name}
|
147
163
|
labelIcon={helpLabel(labelText, name)}
|
@@ -165,7 +181,7 @@ export const formatter = (input, values, setValue) => {
|
|
165
181
|
<TemplateSearchField
|
166
182
|
key={id}
|
167
183
|
name={name}
|
168
|
-
defaultValue={
|
184
|
+
defaultValue={defaultValue}
|
169
185
|
controller={controller}
|
170
186
|
url={`/${controller}/auto_complete_search`}
|
171
187
|
labelText={labelText}
|