foreman_remote_execution 8.0.0 → 8.1.0
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/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}
|