foreman_remote_execution 4.5.1 → 4.5.2

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/ui_job_wizard_controller.rb +7 -0
  3. data/app/helpers/remote_execution_helper.rb +5 -1
  4. data/app/views/templates/ssh/module_action.erb +1 -0
  5. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  6. data/lib/foreman_remote_execution/version.rb +1 -1
  7. data/webpack/JobWizard/JobWizard.js +28 -8
  8. data/webpack/JobWizard/JobWizard.scss +39 -0
  9. data/webpack/JobWizard/JobWizardConstants.js +10 -0
  10. data/webpack/JobWizard/JobWizardSelectors.js +9 -0
  11. data/webpack/JobWizard/__tests__/fixtures.js +104 -2
  12. data/webpack/JobWizard/__tests__/integration.test.js +13 -85
  13. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
  14. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  15. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
  16. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
  17. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  18. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
  19. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  20. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  21. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  22. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  23. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  24. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  25. data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
  26. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  27. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  28. data/webpack/JobWizard/steps/form/SelectField.js +14 -2
  29. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  30. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  31. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  32. metadata +13 -10
  33. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
  34. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
  35. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
  36. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
  37. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  38. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  39. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
  40. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
@@ -8,6 +8,7 @@ export const helpLabel = (text, id) => {
8
8
  return (
9
9
  <Popover id={`${id}-help`} bodyContent={text} aria-label="help-text">
10
10
  <button
11
+ type="button"
11
12
  aria-label={__('open-help-tooltip-button')}
12
13
  onClick={e => e.preventDefault()}
13
14
  className="pf-c-form__group-label-help"
@@ -0,0 +1,149 @@
1
+ import React, { useEffect } from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { FormGroup, TextInput, TextArea } from '@patternfly/react-core';
4
+ import PropTypes from 'prop-types';
5
+ import SearchBar from 'foremanReact/components/SearchBar';
6
+ import { helpLabel } from './FormHelpers';
7
+ import { SelectField } from './SelectField';
8
+
9
+ const TemplateSearchField = ({
10
+ name,
11
+ controller,
12
+ labelText,
13
+ required,
14
+ defaultValue,
15
+ setValue,
16
+ values,
17
+ }) => {
18
+ const searchQuery = useSelector(
19
+ state => state.autocomplete?.[name]?.searchQuery
20
+ );
21
+ useEffect(() => {
22
+ setValue({ ...values, [name]: searchQuery });
23
+ // eslint-disable-next-line react-hooks/exhaustive-deps
24
+ }, [searchQuery]);
25
+ return (
26
+ <FormGroup
27
+ label={name}
28
+ labelIcon={helpLabel(labelText, name)}
29
+ fieldId={name}
30
+ isRequired={required}
31
+ className="foreman-search-field"
32
+ >
33
+ <SearchBar
34
+ initialQuery={defaultValue}
35
+ data={{
36
+ controller,
37
+ autocomplete: {
38
+ id: name,
39
+ url: `/${controller}/auto_complete_search`,
40
+ useKeyShortcuts: true,
41
+ },
42
+ }}
43
+ onSearch={() => null}
44
+ />
45
+ </FormGroup>
46
+ );
47
+ };
48
+
49
+ export const formatter = (input, values, setValue) => {
50
+ const isSelectType = !!input?.options;
51
+ const inputType = input.value_type;
52
+ const isTextType = inputType === 'plain' || !inputType; // null defaults to plain
53
+
54
+ const { name, required, hidden_value: hidden } = input;
55
+ const labelText = input.description;
56
+ const value = values[name];
57
+
58
+ if (isSelectType) {
59
+ const options = input.options.split(/\r?\n/).map(option => option.trim());
60
+ return (
61
+ <SelectField
62
+ aria-label={name}
63
+ key={name}
64
+ isRequired={required}
65
+ label={name}
66
+ fieldId={name}
67
+ options={options}
68
+ labelIcon={helpLabel(labelText, name)}
69
+ value={value}
70
+ setValue={newValue => setValue({ ...values, [name]: newValue })}
71
+ />
72
+ );
73
+ }
74
+ if (isTextType) {
75
+ return (
76
+ <FormGroup
77
+ key={name}
78
+ label={name}
79
+ labelIcon={helpLabel(labelText, name)}
80
+ fieldId={name}
81
+ isRequired={required}
82
+ >
83
+ <TextArea
84
+ aria-label={name}
85
+ className={hidden ? 'masked-input' : null}
86
+ required={required}
87
+ rows={2}
88
+ id={name}
89
+ value={value}
90
+ onChange={newValue => setValue({ ...values, [name]: newValue })}
91
+ />
92
+ </FormGroup>
93
+ );
94
+ }
95
+ if (inputType === 'date') {
96
+ return (
97
+ <FormGroup
98
+ key={name}
99
+ label={name}
100
+ labelIcon={helpLabel(labelText, name)}
101
+ fieldId={name}
102
+ isRequired={required}
103
+ >
104
+ <TextInput
105
+ aria-label={name}
106
+ placeholder="YYYY-mm-dd HH:MM"
107
+ className={hidden ? 'masked-input' : null}
108
+ required={required}
109
+ id={name}
110
+ type="text"
111
+ value={value}
112
+ onChange={newValue => setValue({ ...values, [name]: newValue })}
113
+ />
114
+ </FormGroup>
115
+ );
116
+ }
117
+ if (inputType === 'search') {
118
+ const controller = input.resource_type;
119
+ // TODO: get text from redux autocomplete
120
+ return (
121
+ <TemplateSearchField
122
+ key={name}
123
+ name={name}
124
+ defaultValue={value}
125
+ controller={controller}
126
+ labelText={labelText}
127
+ required={required}
128
+ setValue={setValue}
129
+ values={values}
130
+ />
131
+ );
132
+ }
133
+
134
+ return null;
135
+ };
136
+
137
+ TemplateSearchField.propTypes = {
138
+ name: PropTypes.string.isRequired,
139
+ controller: PropTypes.string.isRequired,
140
+ labelText: PropTypes.string,
141
+ required: PropTypes.bool.isRequired,
142
+ defaultValue: PropTypes.string,
143
+ setValue: PropTypes.func.isRequired,
144
+ values: PropTypes.object.isRequired,
145
+ };
146
+ TemplateSearchField.defaultProps = {
147
+ labelText: null,
148
+ defaultValue: '',
149
+ };
@@ -0,0 +1,33 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormGroup, TextInput, ValidatedOptions } from '@patternfly/react-core';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ export const NumberInput = ({ formProps, inputProps }) => {
7
+ const [validated, setValidated] = useState();
8
+ return (
9
+ <FormGroup
10
+ {...formProps}
11
+ helperTextInvalid={__('Has to be a number')}
12
+ validated={validated}
13
+ >
14
+ <TextInput
15
+ type="text"
16
+ {...inputProps}
17
+ onChange={newValue => {
18
+ setValidated(
19
+ /^\d+$/.test(newValue) || newValue === ''
20
+ ? ValidatedOptions.noval
21
+ : ValidatedOptions.error
22
+ );
23
+ inputProps.onChange(newValue);
24
+ }}
25
+ />
26
+ </FormGroup>
27
+ );
28
+ };
29
+
30
+ NumberInput.propTypes = {
31
+ formProps: PropTypes.object.isRequired,
32
+ inputProps: PropTypes.object.isRequired,
33
+ };
@@ -8,6 +8,8 @@ export const SelectField = ({
8
8
  options,
9
9
  value,
10
10
  setValue,
11
+ labelIcon,
12
+ isRequired,
11
13
  ...props
12
14
  }) => {
13
15
  const onSelect = (event, selection) => {
@@ -16,7 +18,12 @@ export const SelectField = ({
16
18
  };
17
19
  const [isOpen, setIsOpen] = useState(false);
18
20
  return (
19
- <FormGroup label={label} fieldId={fieldId}>
21
+ <FormGroup
22
+ label={label}
23
+ fieldId={fieldId}
24
+ labelIcon={labelIcon}
25
+ isRequired={isRequired}
26
+ >
20
27
  <Select
21
28
  selections={value}
22
29
  onSelect={onSelect}
@@ -35,14 +42,19 @@ export const SelectField = ({
35
42
  );
36
43
  };
37
44
  SelectField.propTypes = {
38
- label: PropTypes.string.isRequired,
45
+ label: PropTypes.string,
39
46
  fieldId: PropTypes.string.isRequired,
40
47
  options: PropTypes.array,
41
48
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
42
49
  setValue: PropTypes.func.isRequired,
50
+ labelIcon: PropTypes.node,
51
+ isRequired: PropTypes.bool,
43
52
  };
44
53
 
45
54
  SelectField.defaultProps = {
55
+ label: null,
46
56
  options: [],
57
+ labelIcon: null,
47
58
  value: null,
59
+ isRequired: false,
48
60
  };
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ import { Provider } from 'react-redux';
3
+ import configureMockStore from 'redux-mock-store';
4
+ import * as patternfly from '@patternfly/react-core';
5
+ import { mount, shallow } from '@theforeman/test';
6
+ import { formatter } from '../Formatter';
7
+
8
+ jest.spyOn(patternfly, 'Select');
9
+ jest.spyOn(patternfly, 'SelectOption');
10
+ jest.spyOn(patternfly, 'FormGroup');
11
+ patternfly.Select.mockImplementation(props => <div props={props} />);
12
+ patternfly.SelectOption.mockImplementation(props => <div props={props} />);
13
+ patternfly.FormGroup.mockImplementation(props => <div props={props} />);
14
+ const mockStore = configureMockStore([]);
15
+ const store = mockStore({});
16
+
17
+ describe('formatter', () => {
18
+ it('render date input', () => {
19
+ const props = {
20
+ name: 'date adv',
21
+ required: false,
22
+ options: '',
23
+ advanced: true,
24
+ value_type: 'date',
25
+ resource_type: 'ansible_roles',
26
+ default: '',
27
+ hidden_value: false,
28
+ };
29
+ expect(shallow(formatter(props, {}, jest.fn()))).toMatchSnapshot();
30
+ });
31
+ it('render text input', () => {
32
+ const props = {
33
+ name: 'plain adv hidden',
34
+ required: true,
35
+ description: 'some Description',
36
+ options: '',
37
+ advanced: true,
38
+ value_type: 'plain',
39
+ resource_type: 'ansible_roles',
40
+ default: 'Default val',
41
+ hidden_value: true,
42
+ };
43
+ expect(shallow(formatter(props, {}, jest.fn()))).toMatchSnapshot();
44
+ });
45
+ it('render select input', () => {
46
+ const props = {
47
+ name: 'adv plain search',
48
+ required: false,
49
+ input_type: 'user',
50
+ options: 'option 1\r\noption 2\r\noption 3\r\noption 4',
51
+ advanced: true,
52
+ value_type: 'plain',
53
+ resource_type: 'ansible_roles',
54
+ default: '',
55
+ hidden_value: false,
56
+ };
57
+ expect(shallow(formatter(props, {}, jest.fn()))).toMatchSnapshot();
58
+ });
59
+ it('render search input', () => {
60
+ const props = {
61
+ name: 'search adv',
62
+ required: false,
63
+ options: '',
64
+ advanced: true,
65
+ value_type: 'search',
66
+ resource_type: 'foreman_tasks/tasks',
67
+ default: '',
68
+ hidden_value: false,
69
+ };
70
+ expect(
71
+ mount(
72
+ <Provider store={store}>{formatter(props, {}, jest.fn())}</Provider>
73
+ )
74
+ ).toMatchSnapshot();
75
+ });
76
+ });
@@ -1,2 +1,19 @@
1
- const SearchBar = () => jest.fn();
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const SearchBar = ({ onChange }) => (
5
+ <input
6
+ className="foreman-search"
7
+ onChange={onChange}
8
+ placeholder="Filter..."
9
+ />
10
+ );
2
11
  export default SearchBar;
12
+
13
+ SearchBar.propTypes = {
14
+ onChange: PropTypes.func,
15
+ };
16
+
17
+ SearchBar.defaultProps = {
18
+ onChange: () => null,
19
+ };
@@ -27,6 +27,7 @@ exports[`TargetingHostsPage renders 1`] = `
27
27
  "controller": "hosts",
28
28
  }
29
29
  }
30
+ onChange={[Function]}
30
31
  onSearch={[Function]}
31
32
  />
32
33
  </Col>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.5.1
4
+ version: 4.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-13 00:00:00.000000000 Z
11
+ date: 2021-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -404,27 +404,30 @@ files:
404
404
  - webpack/JobWizard/JobWizard.scss
405
405
  - webpack/JobWizard/JobWizardConstants.js
406
406
  - webpack/JobWizard/JobWizardSelectors.js
407
- - webpack/JobWizard/__tests__/JobWizard.test.js
408
- - webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap
409
407
  - webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap
410
408
  - webpack/JobWizard/__tests__/fixtures.js
411
409
  - webpack/JobWizard/__tests__/integration.test.js
412
410
  - webpack/JobWizard/index.js
413
411
  - webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js
412
+ - webpack/JobWizard/steps/AdvancedFields/DescriptionField.js
414
413
  - webpack/JobWizard/steps/AdvancedFields/Fields.js
415
414
  - webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js
416
- - webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap
415
+ - webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js
417
416
  - webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js
418
417
  - webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js
419
- - webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap
420
418
  - webpack/JobWizard/steps/CategoryAndTemplate/index.js
419
+ - webpack/JobWizard/steps/Schedule/QueryType.js
420
+ - webpack/JobWizard/steps/Schedule/RepeatOn.js
421
+ - webpack/JobWizard/steps/Schedule/ScheduleType.js
422
+ - webpack/JobWizard/steps/Schedule/StartEndDates.js
423
+ - webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js
424
+ - webpack/JobWizard/steps/Schedule/index.js
421
425
  - webpack/JobWizard/steps/form/FormHelpers.js
426
+ - webpack/JobWizard/steps/form/Formatter.js
422
427
  - webpack/JobWizard/steps/form/GroupedSelectField.js
428
+ - webpack/JobWizard/steps/form/NumberInput.js
423
429
  - webpack/JobWizard/steps/form/SelectField.js
424
- - webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js
425
- - webpack/JobWizard/steps/form/__tests__/SelectField.test.js
426
- - webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap
427
- - webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap
430
+ - webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example
428
431
  - webpack/Routes/routes.js
429
432
  - webpack/__mocks__/foremanReact/common/I18n.js
430
433
  - webpack/__mocks__/foremanReact/common/helpers.js
@@ -1,13 +0,0 @@
1
- import React from 'react';
2
- import * as patternfly from '@patternfly/react-core';
3
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
- import JobWizardPage from '../index';
5
-
6
- jest.spyOn(patternfly, 'Wizard');
7
- patternfly.Wizard.mockImplementation(props => <div>{props.navAriaLabel}</div>);
8
-
9
- const fixtures = {
10
- 'renders ': {},
11
- };
12
- describe('JobWizardPage rendering', () =>
13
- testComponentSnapshotsWithFixtures(JobWizardPage, fixtures));
@@ -1,32 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`JobWizardPage rendering renders 1`] = `
4
- <PageLayout
5
- breadcrumbOptions={
6
- Object {
7
- "breadcrumbItems": Array [
8
- Object {
9
- "caption": "Jobs",
10
- "url": "/jobs",
11
- },
12
- Object {
13
- "caption": "Run job",
14
- },
15
- ],
16
- }
17
- }
18
- header="Run job"
19
- searchable={false}
20
- >
21
- <Title
22
- headingLevel="h2"
23
- size="2xl"
24
- >
25
- Run job
26
- </Title>
27
- <Divider
28
- component="div"
29
- />
30
- <JobWizard />
31
- </PageLayout>
32
- `;