foreman_remote_execution 4.5.6 → 4.6.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 (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;