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.
- checksums.yaml +4 -4
- data/app/controllers/job_invocations_controller.rb +1 -1
- data/app/controllers/ui_job_wizard_controller.rb +0 -7
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -5
- data/app/helpers/remote_execution_helper.rb +3 -9
- data/app/lib/actions/remote_execution/run_host_job.rb +1 -5
- data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +0 -2
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +70 -0
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +0 -6
- data/app/models/host_status/execution_status.rb +3 -3
- data/app/models/job_invocation.rb +6 -9
- data/app/models/job_invocation_composer.rb +4 -4
- data/app/models/remote_execution_feature.rb +1 -5
- data/app/models/remote_execution_provider.rb +2 -3
- data/app/models/setting/remote_execution.rb +2 -2
- data/app/models/targeting.rb +1 -5
- data/app/views/job_invocations/index.html.erb +1 -1
- data/app/views/templates/ssh/module_action.erb +0 -1
- data/app/views/templates/ssh/power_action.erb +0 -2
- data/app/views/templates/ssh/puppet_run_once.erb +0 -1
- data/foreman_remote_execution.gemspec +0 -1
- data/lib/foreman_remote_execution/engine.rb +2 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/models/orchestration/ssh_test.rb +56 -0
- data/test/unit/job_invocation_composer_test.rb +1 -14
- data/test/unit/job_invocation_test.rb +1 -1
- data/test/unit/remote_execution_provider_test.rb +0 -12
- data/webpack/JobWizard/JobWizard.js +16 -59
- data/webpack/JobWizard/JobWizard.scss +0 -58
- data/webpack/JobWizard/JobWizardConstants.js +0 -18
- data/webpack/JobWizard/JobWizardSelectors.js +0 -9
- data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
- data/webpack/JobWizard/__tests__/fixtures.js +2 -112
- data/webpack/JobWizard/__tests__/integration.test.js +92 -16
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +9 -37
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +55 -116
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +16 -150
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +2 -4
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +51 -123
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +0 -1
- data/webpack/JobWizard/steps/form/SelectField.js +2 -14
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +1 -18
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +0 -1
- metadata +13 -35
- data/app/models/host_proxy_invocation.rb +0 -4
- data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +0 -12
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +0 -67
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +0 -25
- data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +0 -23
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +0 -37
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +0 -50
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +0 -66
- data/webpack/JobWizard/steps/Schedule/QueryType.js +0 -48
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +0 -61
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +0 -25
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -51
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -22
- data/webpack/JobWizard/steps/Schedule/index.js +0 -44
- data/webpack/JobWizard/steps/form/Formatter.js +0 -150
- data/webpack/JobWizard/steps/form/NumberInput.js +0 -35
- data/webpack/JobWizard/steps/form/WizardTitle.js +0 -14
- 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;
|