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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/job_invocations_controller.rb +1 -2
  3. data/app/controllers/job_templates_controller.rb +1 -1
  4. data/app/controllers/ui_job_wizard_controller.rb +1 -1
  5. data/app/helpers/job_invocations_helper.rb +0 -7
  6. data/app/helpers/remote_execution_helper.rb +1 -1
  7. data/app/lib/actions/remote_execution/proxy_action.rb +46 -0
  8. data/app/lib/actions/remote_execution/run_host_job.rb +38 -11
  9. data/app/lib/actions/remote_execution/run_hosts_job.rb +7 -6
  10. data/app/lib/actions/remote_execution/template_invocation_progress_logging.rb +27 -0
  11. data/app/models/job_invocation.rb +5 -9
  12. data/app/models/job_invocation_composer.rb +4 -0
  13. data/app/models/remote_execution_provider.rb +10 -2
  14. data/app/models/ssh_execution_provider.rb +1 -0
  15. data/app/models/template_invocation.rb +1 -0
  16. data/app/models/template_invocation_event.rb +11 -0
  17. data/app/views/job_invocations/_form.html.erb +4 -0
  18. data/app/views/job_invocations/new.html.erb +5 -0
  19. data/app/views/templates/script/package_action.erb +1 -1
  20. data/config/routes.rb +5 -5
  21. data/db/migrate/20220713095705_create_template_invocation_events.rb +17 -0
  22. data/db/migrate/20220822155946_add_time_to_pickup_to_job_invocation.rb +5 -0
  23. data/extra/cockpit/foreman-cockpit-session +303 -230
  24. data/extra/cockpit/foreman-cockpit.service +1 -0
  25. data/foreman_remote_execution.gemspec +1 -1
  26. data/lib/foreman_remote_execution/engine.rb +12 -7
  27. data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +131 -0
  28. data/lib/foreman_remote_execution/version.rb +1 -1
  29. data/test/unit/remote_execution_provider_test.rb +22 -0
  30. data/webpack/JobWizard/JobWizard.js +53 -18
  31. data/webpack/JobWizard/JobWizard.scss +3 -0
  32. data/webpack/JobWizard/JobWizardConstants.js +1 -1
  33. data/webpack/JobWizard/JobWizardHelpers.js +15 -0
  34. data/webpack/JobWizard/JobWizardPageRerun.js +29 -5
  35. data/webpack/JobWizard/JobWizardSelectors.js +8 -2
  36. data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +5 -0
  37. data/webpack/JobWizard/__tests__/fixtures.js +26 -2
  38. data/webpack/JobWizard/autofill.js +32 -10
  39. data/webpack/JobWizard/index.js +25 -6
  40. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +25 -0
  41. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +12 -1
  42. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +41 -6
  43. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +1 -1
  44. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +1 -1
  45. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +4 -2
  46. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +6 -2
  47. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +28 -20
  48. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +32 -0
  49. data/webpack/JobWizard/steps/HostsAndInputs/index.js +2 -2
  50. data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -0
  51. data/webpack/JobWizard/steps/form/FormHelpers.js +21 -1
  52. data/webpack/JobWizard/steps/form/Formatter.js +22 -6
  53. data/webpack/JobWizard/steps/form/ResourceSelect.js +97 -10
  54. data/webpack/JobWizard/steps/form/SearchSelect.js +2 -2
  55. data/webpack/JobWizard/steps/form/SelectField.js +4 -0
  56. data/webpack/JobWizard/submit.js +3 -1
  57. data/webpack/JobWizard/validation.js +1 -0
  58. data/webpack/Routes/routes.js +3 -3
  59. data/webpack/react_app/components/FeaturesDropdown/actions.js +23 -2
  60. data/webpack/react_app/components/FeaturesDropdown/index.js +2 -0
  61. data/webpack/react_app/components/HostKebab/KebabItems.js +1 -0
  62. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +5 -0
  63. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +51 -59
  64. data/webpack/react_app/extend/Fills.js +3 -3
  65. 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 = ({ inputValues, value, setValue }) => {
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;
@@ -315,7 +315,7 @@ describe('AdvancedFields', () => {
315
315
  data: {
316
316
  ...jobTemplateResponse,
317
317
  job_template: {
318
- ...jobTemplateResponse.jobTemplate,
318
+ ...jobTemplateResponse.job_template,
319
319
  description_format: 'Run %{command}',
320
320
  },
321
321
 
@@ -49,7 +49,7 @@ Array [
49
49
  "port": null,
50
50
  "preventInvalidHostname": false,
51
51
  "protocol": null,
52
- "query": "resource=ForemanTasks%3A%3ATask&name=",
52
+ "query": "resource=ForemanTasks%3A%3ATask",
53
53
  "urn": null,
54
54
  "username": null,
55
55
  },
@@ -42,8 +42,10 @@ export const CategoryAndTemplate = ({
42
42
  )?.name;
43
43
 
44
44
  const onSelectCategory = newCategory => {
45
- setCategory(newCategory);
46
- setJobTemplate(null);
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('template1', { selector: 'button' })
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('template1', { selector: 'button' })
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) setJobTemplate(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 templatesUrlObject = new URI(templatesUrl);
62
- dispatch(
63
- get({
64
- key: JOB_TEMPLATES,
65
- url: templatesUrlObject.addSearch({
66
- search: `job_category="${category}"`,
67
- per_page: 'all',
68
- }),
69
- handleSuccess: response => {
70
- if (!jobTemplate)
71
- setJobTemplate(
72
- current =>
73
- current ||
74
- Number(filterJobTemplates(response?.data?.results)[0]?.id) ||
75
- null
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
- DEBOUNCE_HOST_COUNT,
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
- }, DEBOUNCE_HOST_COUNT)();
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 { Popover } from '@patternfly/react-core';
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 { ResourceSelectAPI } from './ResourceSelect';
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={defaultValue}
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
- <ResourceSelectAPI
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={value}
184
+ defaultValue={defaultValue}
169
185
  controller={controller}
170
186
  url={`/${controller}/auto_complete_search`}
171
187
  labelText={labelText}