foreman_remote_execution 4.5.1 → 4.5.2

Sign up to get free protection for your applications and to get access to all the features.
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
- `;