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.
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}