foreman_remote_execution 4.5.6 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/job_invocations_controller.rb +1 -1
  3. data/app/controllers/ui_job_wizard_controller.rb +0 -7
  4. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -5
  5. data/app/helpers/remote_execution_helper.rb +3 -9
  6. data/app/lib/actions/remote_execution/run_host_job.rb +1 -5
  7. data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
  8. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +0 -2
  9. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +70 -0
  10. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +0 -6
  11. data/app/models/host_status/execution_status.rb +3 -3
  12. data/app/models/job_invocation.rb +6 -9
  13. data/app/models/job_invocation_composer.rb +4 -4
  14. data/app/models/remote_execution_feature.rb +1 -5
  15. data/app/models/remote_execution_provider.rb +2 -3
  16. data/app/models/setting/remote_execution.rb +2 -2
  17. data/app/models/targeting.rb +1 -5
  18. data/app/views/job_invocations/index.html.erb +1 -1
  19. data/app/views/templates/ssh/module_action.erb +0 -1
  20. data/app/views/templates/ssh/power_action.erb +0 -2
  21. data/app/views/templates/ssh/puppet_run_once.erb +0 -1
  22. data/foreman_remote_execution.gemspec +0 -1
  23. data/lib/foreman_remote_execution/engine.rb +2 -0
  24. data/lib/foreman_remote_execution/version.rb +1 -1
  25. data/test/models/orchestration/ssh_test.rb +56 -0
  26. data/test/unit/job_invocation_composer_test.rb +1 -14
  27. data/test/unit/job_invocation_test.rb +1 -1
  28. data/test/unit/remote_execution_provider_test.rb +0 -12
  29. data/webpack/JobWizard/JobWizard.js +16 -59
  30. data/webpack/JobWizard/JobWizard.scss +0 -58
  31. data/webpack/JobWizard/JobWizardConstants.js +0 -18
  32. data/webpack/JobWizard/JobWizardSelectors.js +0 -9
  33. data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
  34. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
  35. data/webpack/JobWizard/__tests__/fixtures.js +2 -112
  36. data/webpack/JobWizard/__tests__/integration.test.js +92 -16
  37. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +9 -37
  38. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +55 -116
  39. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +16 -150
  40. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
  41. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +2 -4
  42. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +51 -123
  43. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
  44. data/webpack/JobWizard/steps/form/FormHelpers.js +0 -1
  45. data/webpack/JobWizard/steps/form/SelectField.js +2 -14
  46. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
  47. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
  48. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
  49. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
  50. data/webpack/__mocks__/foremanReact/components/SearchBar.js +1 -18
  51. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +0 -1
  52. metadata +13 -35
  53. data/app/models/host_proxy_invocation.rb +0 -4
  54. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +0 -12
  55. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +0 -67
  56. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
  57. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +0 -25
  58. data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +0 -23
  59. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +0 -37
  60. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +0 -50
  61. data/webpack/JobWizard/steps/HostsAndInputs/index.js +0 -66
  62. data/webpack/JobWizard/steps/Schedule/QueryType.js +0 -48
  63. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +0 -61
  64. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +0 -25
  65. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -51
  66. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -22
  67. data/webpack/JobWizard/steps/Schedule/index.js +0 -44
  68. data/webpack/JobWizard/steps/form/Formatter.js +0 -150
  69. data/webpack/JobWizard/steps/form/NumberInput.js +0 -35
  70. data/webpack/JobWizard/steps/form/WizardTitle.js +0 -14
  71. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +0 -76
@@ -1,67 +0,0 @@
1
- import React, { useState } from 'react';
2
- import PropTypes from 'prop-types';
3
- import { FormGroup, TextInput, Button } from '@patternfly/react-core';
4
- import { translate as __ } from 'foremanReact/common/I18n';
5
-
6
- export const DescriptionField = ({ inputs, value, setValue }) => {
7
- const generateDesc = () => {
8
- let newDesc = value;
9
- if (value) {
10
- const re = new RegExp('%\\{([^\\}]+)\\}', 'gm');
11
- const results = [...newDesc.matchAll(re)].map(result => ({
12
- name: result[1],
13
- text: result[0],
14
- }));
15
- results.forEach(result => {
16
- newDesc = newDesc.replace(
17
- result.text,
18
- // TODO: Replace with the value of the input from Target Hosts step
19
- inputs.find(input => input.name === result.name)?.name || result.text
20
- );
21
- });
22
- }
23
- return newDesc;
24
- };
25
- const [generatedDesc, setGeneratedDesc] = useState(generateDesc());
26
- const [isPreview, setIsPreview] = useState(true);
27
-
28
- const togglePreview = () => {
29
- setGeneratedDesc(generateDesc());
30
- setIsPreview(v => !v);
31
- };
32
-
33
- return (
34
- <FormGroup
35
- label={__('Description')}
36
- fieldId="description"
37
- helperText={
38
- <Button variant="link" isInline onClick={togglePreview}>
39
- {isPreview
40
- ? __('Edit job description template')
41
- : __('Preview job description')}
42
- </Button>
43
- }
44
- >
45
- {isPreview ? (
46
- <TextInput id="description-preview" value={generatedDesc} isDisabled />
47
- ) : (
48
- <TextInput
49
- type="text"
50
- autoComplete="description"
51
- id="description"
52
- value={value}
53
- onChange={newValue => setValue(newValue)}
54
- />
55
- )}
56
- </FormGroup>
57
- );
58
- };
59
-
60
- DescriptionField.propTypes = {
61
- inputs: PropTypes.array.isRequired,
62
- value: PropTypes.string,
63
- setValue: PropTypes.func.isRequired,
64
- };
65
- DescriptionField.defaultProps = {
66
- value: '',
67
- };
@@ -1,23 +0,0 @@
1
- import React from 'react';
2
- import { mount } from '@theforeman/test';
3
- import { DescriptionField } from '../DescriptionField';
4
-
5
- describe('DescriptionField', () => {
6
- it('rendring', () => {
7
- const component = mount(
8
- <DescriptionField
9
- inputs={[{ name: 'command' }]}
10
- value="Run %{command}"
11
- setValue={jest.fn()}
12
- />
13
- );
14
- const preview = component.find('#description-preview').hostNodes();
15
- const findLink = () => component.find('.pf-m-link.pf-m-inline');
16
- expect(findLink().text()).toEqual('Edit job description template');
17
- expect(preview.props().value).toEqual('Run command');
18
- findLink().simulate('click');
19
- const description = component.find('#description').hostNodes();
20
- expect(description.props().value).toEqual('Run %{command}');
21
- expect(findLink().text()).toEqual('Preview job description');
22
- });
23
- });
@@ -1,25 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { Chip, ChipGroup } from '@patternfly/react-core';
4
-
5
- export const SelectedChips = ({ selected, setSelected }) => {
6
- const deleteItem = itemToRemove => {
7
- setSelected(oldSelected =>
8
- oldSelected.filter(item => item !== itemToRemove)
9
- );
10
- };
11
- return (
12
- <ChipGroup className="hosts-chip-group">
13
- {selected.map(chip => (
14
- <Chip key={chip} id={chip} onClick={() => deleteItem(chip)}>
15
- {chip}
16
- </Chip>
17
- ))}
18
- </ChipGroup>
19
- );
20
- };
21
-
22
- SelectedChips.propTypes = {
23
- selected: PropTypes.array.isRequired,
24
- setSelected: PropTypes.func.isRequired,
25
- };
@@ -1,23 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { translate as __ } from 'foremanReact/common/I18n';
4
- import { formatter } from '../form/Formatter';
5
-
6
- export const TemplateInputs = ({ inputs, value, setValue }) => {
7
- if (inputs.length)
8
- return inputs.map(input => formatter(input, value, setValue));
9
- return (
10
- <p className="gray-text">
11
- {__('There are no available input fields for the selected template.')}
12
- </p>
13
- );
14
- };
15
- TemplateInputs.propTypes = {
16
- inputs: PropTypes.array.isRequired,
17
- value: PropTypes.object,
18
- setValue: PropTypes.func.isRequired,
19
- };
20
-
21
- TemplateInputs.defaultProps = {
22
- value: {},
23
- };
@@ -1,37 +0,0 @@
1
- import React from 'react';
2
- import { Provider } from 'react-redux';
3
- import { fireEvent, screen, render, act } from '@testing-library/react';
4
- import * as api from 'foremanReact/redux/API';
5
- import { JobWizard } from '../../../JobWizard';
6
- import * as selectors from '../../../JobWizardSelectors';
7
- import { testSetup, mockApi } from '../../../__tests__/fixtures';
8
-
9
- const store = testSetup(selectors, api);
10
- mockApi(api);
11
-
12
- describe('TemplateInputs', () => {
13
- it('should save data between steps for template input fields', async () => {
14
- render(
15
- <Provider store={store}>
16
- <JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
17
- </Provider>
18
- );
19
- await act(async () => {
20
- await fireEvent.click(
21
- screen.getByText('Target hosts and inputs', { selector: 'button' })
22
- );
23
- });
24
-
25
- expect(
26
- screen.getAllByLabelText('host2', { selector: 'button' })
27
- ).toHaveLength(1);
28
- const chip1 = screen.getByLabelText('host1', { selector: 'button' });
29
- fireEvent.click(chip1);
30
- expect(
31
- screen.queryAllByLabelText('host1', { selector: 'button' })
32
- ).toHaveLength(0);
33
- expect(
34
- screen.queryAllByLabelText('host2', { selector: 'button' })
35
- ).toHaveLength(1);
36
- });
37
- });
@@ -1,50 +0,0 @@
1
- import React from 'react';
2
- import { Provider } from 'react-redux';
3
- import { fireEvent, screen, render, act } from '@testing-library/react';
4
- import * as api from 'foremanReact/redux/API';
5
- import { JobWizard } from '../../../JobWizard';
6
- import * as selectors from '../../../JobWizardSelectors';
7
- import { testSetup, mockApi } from '../../../__tests__/fixtures';
8
- import { WIZARD_TITLES } from '../../../JobWizardConstants';
9
-
10
- const store = testSetup(selectors, api);
11
- mockApi(api);
12
-
13
- describe('TemplateInputs', () => {
14
- it('should save data between steps for template input fields', async () => {
15
- render(
16
- <Provider store={store}>
17
- <JobWizard />
18
- </Provider>
19
- );
20
- await act(async () => {
21
- fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
22
- });
23
- const textValue = 'I am a plain text';
24
- const textField = screen.getByLabelText('plain hidden', {
25
- selector: 'textarea',
26
- });
27
-
28
- await act(async () => {
29
- await fireEvent.change(textField, {
30
- target: { value: textValue },
31
- });
32
- });
33
- expect(
34
- screen.getByLabelText('plain hidden', {
35
- selector: 'textarea',
36
- }).value
37
- ).toBe(textValue);
38
- await act(async () => {
39
- fireEvent.click(screen.getByText(WIZARD_TITLES.categoryAndTemplate));
40
- });
41
- expect(screen.getAllByText(WIZARD_TITLES.categoryAndTemplate)).toHaveLength(
42
- 3
43
- );
44
-
45
- await act(async () => {
46
- fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
47
- });
48
- expect(textField.value).toBe(textValue);
49
- });
50
- });
@@ -1,66 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Button, Form, FormGroup } from '@patternfly/react-core';
3
- import PropTypes from 'prop-types';
4
- import { useSelector } from 'react-redux';
5
- import { translate as __ } from 'foremanReact/common/I18n';
6
- import { selectTemplateInputs } from '../../JobWizardSelectors';
7
- import { SelectField } from '../form/SelectField';
8
- import { SelectedChips } from './SelectedChips';
9
- import { TemplateInputs } from './TemplateInputs';
10
- import { WIZARD_TITLES } from '../../JobWizardConstants';
11
- import { WizardTitle } from '../form/WizardTitle';
12
-
13
- const HostsAndInputs = ({
14
- templateValues,
15
- setTemplateValues,
16
- selectedHosts,
17
- setSelectedHosts,
18
- }) => {
19
- const templateInputs = useSelector(selectTemplateInputs);
20
- const hostMethods = [
21
- __('Hosts'),
22
- __('Host collection'),
23
- __('Host group'),
24
- __('Search query'),
25
- ];
26
- const [hostMethod, setHostMethod] = useState(hostMethods[0]);
27
- return (
28
- <>
29
- <WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
30
- <Form>
31
- <FormGroup fieldId="host_selection">
32
- <SelectField
33
- fieldId="host_methods"
34
- options={hostMethods}
35
- setValue={setHostMethod}
36
- value={hostMethod}
37
- />
38
- <SelectedChips
39
- selected={selectedHosts}
40
- setSelected={setSelectedHosts}
41
- />
42
- </FormGroup>
43
- <span>
44
- {__('Apply to')}{' '}
45
- <Button variant="link" isInline>
46
- {selectedHosts.length} {__('hosts')}
47
- </Button>
48
- </span>
49
- <TemplateInputs
50
- inputs={templateInputs}
51
- value={templateValues}
52
- setValue={setTemplateValues}
53
- />
54
- </Form>
55
- </>
56
- );
57
- };
58
-
59
- HostsAndInputs.propTypes = {
60
- templateValues: PropTypes.object.isRequired,
61
- setTemplateValues: PropTypes.func.isRequired,
62
- selectedHosts: PropTypes.array.isRequired,
63
- setSelectedHosts: PropTypes.func.isRequired,
64
- };
65
-
66
- export default HostsAndInputs;
@@ -1,48 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { FormGroup, Radio } from '@patternfly/react-core';
3
- import { translate as __ } from 'foremanReact/common/I18n';
4
- import { helpLabel } from '../form/FormHelpers';
5
-
6
- export const QueryType = () => {
7
- const [isTypeStatic, setIsTypeStatic] = useState(true);
8
- return (
9
- <FormGroup
10
- label={__('Query type')}
11
- fieldId="query-type"
12
- labelIcon={helpLabel(
13
- <p>
14
- {__('Type has impact on when is the query evaluated to hosts.')}
15
- <br />
16
- <ul>
17
- <li>
18
- <b>{__('Static')}</b> -{' '}
19
- {__('evaluates just after you submit this form')}
20
- </li>
21
- <li>
22
- <b>{__('Dynamic')}</b> -{' '}
23
- {__(
24
- "evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it"
25
- )}
26
- </li>
27
- </ul>
28
- </p>,
29
- 'query-type'
30
- )}
31
- >
32
- <Radio
33
- isChecked={isTypeStatic}
34
- name="query-type"
35
- onChange={() => setIsTypeStatic(true)}
36
- id="query-type-static"
37
- label={__('Static query')}
38
- />
39
- <Radio
40
- isChecked={!isTypeStatic}
41
- name="query-type"
42
- onChange={() => setIsTypeStatic(false)}
43
- id="query-type-dynamic"
44
- label={__('Dynamic query')}
45
- />
46
- </FormGroup>
47
- );
48
- };
@@ -1,61 +0,0 @@
1
- import React, { useState } from 'react';
2
- import PropTypes from 'prop-types';
3
- import { TextInput, Grid, GridItem, FormGroup } from '@patternfly/react-core';
4
- import { translate as __ } from 'foremanReact/common/I18n';
5
- import { SelectField } from '../form/SelectField';
6
- import { repeatTypes } from '../../JobWizardConstants';
7
-
8
- export const RepeatOn = ({
9
- repeatType,
10
- setRepeatType,
11
- repeatAmount,
12
- setRepeatAmount,
13
- }) => {
14
- const [repeatValidated, setRepeatValidated] = useState('default');
15
- const handleRepeatInputChange = newValue => {
16
- setRepeatValidated(newValue >= 1 ? 'default' : 'error');
17
- setRepeatAmount(newValue);
18
- };
19
- return (
20
- <Grid>
21
- <GridItem span={6}>
22
- <SelectField
23
- fieldId="repeat-select"
24
- options={Object.values(repeatTypes)}
25
- setValue={newValue => {
26
- setRepeatType(newValue);
27
- if (newValue === repeatTypes.noRepeat) {
28
- setRepeatValidated('default');
29
- }
30
- }}
31
- value={repeatType}
32
- />
33
- </GridItem>
34
- <GridItem span={1} />
35
- <GridItem span={5}>
36
- <FormGroup
37
- isInline
38
- helperTextInvalid={__('Repeat amount can only be a positive number')}
39
- validated={repeatValidated}
40
- >
41
- <TextInput
42
- isDisabled={repeatType === repeatTypes.noRepeat}
43
- id="repeat-amount"
44
- value={repeatAmount}
45
- type="text"
46
- onChange={newValue => handleRepeatInputChange(newValue)}
47
- placeholder={__('Repeat N times')}
48
- />
49
- </FormGroup>
50
- </GridItem>
51
- </Grid>
52
- );
53
- };
54
-
55
- RepeatOn.propTypes = {
56
- repeatType: PropTypes.oneOf(Object.values(repeatTypes)).isRequired,
57
- setRepeatType: PropTypes.func.isRequired,
58
- repeatAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
59
- .isRequired,
60
- setRepeatAmount: PropTypes.func.isRequired,
61
- };
@@ -1,25 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { FormGroup, Radio } from '@patternfly/react-core';
3
- import { translate as __ } from 'foremanReact/common/I18n';
4
-
5
- export const ScheduleType = () => {
6
- const [isFuture, setIsFuture] = useState(false);
7
- return (
8
- <FormGroup label={__('Schedule type')} fieldId="schedule-type">
9
- <Radio
10
- isChecked={!isFuture}
11
- name="schedule-type"
12
- onChange={() => setIsFuture(false)}
13
- id="schedule-type-now"
14
- label={__('Execute now')}
15
- />
16
- <Radio
17
- isChecked={isFuture}
18
- name="schedule-type"
19
- onChange={() => setIsFuture(true)}
20
- id="schedule-type-future"
21
- label={__('Schedule for future execution')}
22
- />
23
- </FormGroup>
24
- );
25
- };
@@ -1,51 +0,0 @@
1
- import React, { useState } from 'react';
2
- import PropTypes from 'prop-types';
3
- import { FormGroup, TextInput, Checkbox } from '@patternfly/react-core';
4
- import { translate as __ } from 'foremanReact/common/I18n';
5
-
6
- // TODO: change to datepicker
7
- export const StartEndDates = ({ starts, setStarts, ends, setEnds }) => {
8
- const [isNeverEnds, setIsNeverEnds] = useState(false);
9
- const toggleIsNeverEnds = (checked, event) => {
10
- const value = event?.target?.checked;
11
- setIsNeverEnds(value);
12
- setEnds('');
13
- };
14
- return (
15
- <>
16
- <FormGroup label={__('Starts')} fieldId="start-date">
17
- <TextInput
18
- id="start-date"
19
- value={starts}
20
- type="text"
21
- onChange={newValue => setStarts(newValue)}
22
- placeholder="mm/dd/yy, hh:mm UTC"
23
- />
24
- </FormGroup>
25
- <FormGroup label={__('Ends')} fieldId="end-date">
26
- <TextInput
27
- isDisabled={isNeverEnds}
28
- id="end-date"
29
- value={ends}
30
- type="text"
31
- onChange={newValue => setEnds(newValue)}
32
- placeholder="mm/dd/yy, hh:mm UTC"
33
- />
34
- <Checkbox
35
- label={__('Never ends')}
36
- isChecked={isNeverEnds}
37
- onChange={toggleIsNeverEnds}
38
- id="never-ends"
39
- name="never-ends"
40
- />
41
- </FormGroup>
42
- </>
43
- );
44
- };
45
-
46
- StartEndDates.propTypes = {
47
- starts: PropTypes.string.isRequired,
48
- setStarts: PropTypes.func.isRequired,
49
- ends: PropTypes.string.isRequired,
50
- setEnds: PropTypes.func.isRequired,
51
- };
@@ -1,22 +0,0 @@
1
- import React from 'react';
2
- import { render, fireEvent, screen } from '@testing-library/react';
3
- import { StartEndDates } from '../StartEndDates';
4
-
5
- const setEnds = jest.fn();
6
- const props = {
7
- starts: '',
8
- setStarts: jest.fn(),
9
- ends: 'some-end-date',
10
- setEnds,
11
- };
12
-
13
- describe('StartEndDates', () => {
14
- it('never ends', () => {
15
- render(<StartEndDates {...props} />);
16
- const neverEnds = screen.getByLabelText('Never ends', {
17
- selector: 'input',
18
- });
19
- fireEvent.click(neverEnds);
20
- expect(setEnds).toBeCalledWith('');
21
- });
22
- });
@@ -1,44 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Button, Form } from '@patternfly/react-core';
3
- import { translate as __ } from 'foremanReact/common/I18n';
4
- import { ScheduleType } from './ScheduleType';
5
- import { RepeatOn } from './RepeatOn';
6
- import { QueryType } from './QueryType';
7
- import { StartEndDates } from './StartEndDates';
8
- import { repeatTypes, WIZARD_TITLES } from '../../JobWizardConstants';
9
- import { WizardTitle } from '../form/WizardTitle';
10
-
11
- const Schedule = () => {
12
- const [repeatType, setRepeatType] = useState(repeatTypes.noRepeat);
13
- const [repeatAmount, setRepeatAmount] = useState('');
14
- const [starts, setStarts] = useState('');
15
- const [ends, setEnds] = useState('');
16
-
17
- return (
18
- <>
19
- <WizardTitle title={WIZARD_TITLES.schedule} />
20
- <Form className="schedule-tab">
21
- <ScheduleType />
22
-
23
- <RepeatOn
24
- repeatType={repeatType}
25
- setRepeatType={setRepeatType}
26
- repeatAmount={repeatAmount}
27
- setRepeatAmount={setRepeatAmount}
28
- />
29
- <StartEndDates
30
- starts={starts}
31
- setStarts={setStarts}
32
- ends={ends}
33
- setEnds={setEnds}
34
- />
35
- <Button variant="link" className="advanced-scheduling-button" isInline>
36
- {__('Advanced scheduling')}
37
- </Button>
38
- <QueryType />
39
- </Form>
40
- </>
41
- );
42
- };
43
-
44
- export default Schedule;