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.
- 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;
|