foreman_remote_execution 4.3.1 → 4.4.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/api/v2/job_invocations_controller.rb +16 -0
- data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
- data/app/helpers/remote_execution_helper.rb +27 -0
- data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
- data/app/models/invocation_provider_input_value.rb +12 -0
- data/app/models/job_invocation.rb +4 -0
- data/app/models/job_invocation_composer.rb +13 -0
- data/app/models/remote_execution_provider.rb +17 -2
- data/app/models/setting/remote_execution.rb +10 -0
- data/app/models/template_invocation.rb +2 -0
- data/app/services/renderer_methods.rb +12 -0
- data/app/views/job_invocations/_form.html.erb +8 -0
- data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +5 -6
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/action_names.rb +1 -0
- data/locale/de/foreman_remote_execution.po +77 -27
- data/locale/en/foreman_remote_execution.po +77 -27
- data/locale/en_GB/foreman_remote_execution.po +77 -27
- data/locale/es/foreman_remote_execution.po +77 -27
- data/locale/foreman_remote_execution.pot +241 -163
- data/locale/fr/foreman_remote_execution.po +77 -27
- data/locale/ja/foreman_remote_execution.po +77 -27
- data/locale/ko/foreman_remote_execution.po +77 -27
- data/locale/pt_BR/foreman_remote_execution.po +77 -27
- data/locale/ru/foreman_remote_execution.po +77 -27
- data/locale/zh_CN/foreman_remote_execution.po +77 -27
- data/locale/zh_TW/foreman_remote_execution.po +77 -27
- data/package.json +3 -2
- data/test/helpers/remote_execution_helper_test.rb +16 -0
- data/test/unit/job_invocation_composer_test.rb +41 -1
- data/test/unit/job_invocation_report_template_test.rb +57 -0
- data/webpack/JobWizard/JobWizard.js +30 -7
- data/webpack/JobWizard/JobWizard.scss +12 -0
- data/webpack/JobWizard/JobWizardConstants.js +5 -0
- data/webpack/JobWizard/JobWizardSelectors.js +21 -0
- data/webpack/JobWizard/__tests__/JobWizard.test.js +20 -0
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +83 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +77 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +45 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +64 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +86 -0
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +88 -0
- data/webpack/JobWizard/steps/form/SelectField.js +39 -0
- 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 +36 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +22 -0
- data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
- data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
- data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
- data/webpack/fills_index.js +11 -0
- data/webpack/global_index.js +4 -0
- data/webpack/index.js +0 -4
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +87 -0
- data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
- data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
- data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
- data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
- data/webpack/react_app/extend/fills.js +10 -0
- data/webpack/react_app/extend/reducers.js +4 -0
- metadata +39 -5
- data/app/views/api/v2/registration/_form.html.erb +0 -12
@@ -0,0 +1,21 @@
|
|
1
|
+
import {
|
2
|
+
selectAPIResponse,
|
3
|
+
selectAPIStatus,
|
4
|
+
} from 'foremanReact/redux/API/APISelectors';
|
5
|
+
|
6
|
+
import { JOB_TEMPLATES, JOB_CATEGORIES } from './JobWizardConstants';
|
7
|
+
|
8
|
+
export const selectJobTemplatesStatus = state =>
|
9
|
+
selectAPIStatus(state, JOB_TEMPLATES);
|
10
|
+
|
11
|
+
export const filterJobTemplates = templates =>
|
12
|
+
templates?.filter(template => !template.snippet) || [];
|
13
|
+
|
14
|
+
export const selectJobTemplates = state =>
|
15
|
+
filterJobTemplates(selectAPIResponse(state, JOB_TEMPLATES)?.results);
|
16
|
+
|
17
|
+
export const selectJobCategories = state =>
|
18
|
+
selectAPIResponse(state, JOB_CATEGORIES).job_categories || [];
|
19
|
+
|
20
|
+
export const selectJobCategoriesStatus = state =>
|
21
|
+
selectAPIStatus(state, JOB_CATEGORIES);
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import * as patternfly from '@patternfly/react-core';
|
3
|
+
import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
|
4
|
+
import JobWizardPage from '../index';
|
5
|
+
import { JobWizard } from '../JobWizard';
|
6
|
+
|
7
|
+
jest.spyOn(patternfly, 'Wizard');
|
8
|
+
patternfly.Wizard.mockImplementation(props => <div>{props}</div>);
|
9
|
+
const fixtures = {
|
10
|
+
'renders ': {},
|
11
|
+
};
|
12
|
+
describe('JobWizardPage', () => {
|
13
|
+
describe('rendering', () =>
|
14
|
+
testComponentSnapshotsWithFixtures(JobWizardPage, fixtures));
|
15
|
+
});
|
16
|
+
|
17
|
+
describe('JobWizard', () => {
|
18
|
+
describe('rendering', () =>
|
19
|
+
testComponentSnapshotsWithFixtures(JobWizard, fixtures));
|
20
|
+
});
|
@@ -0,0 +1,83 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`JobWizard rendering renders 1`] = `
|
4
|
+
<mockConstructor
|
5
|
+
className="job-wizard"
|
6
|
+
height="70vh"
|
7
|
+
navAriaLabel="Run Job steps"
|
8
|
+
onClose={[Function]}
|
9
|
+
steps={
|
10
|
+
Array [
|
11
|
+
Object {
|
12
|
+
"component": <ConnectedCategoryAndTemplate
|
13
|
+
category=""
|
14
|
+
jobTemplate={null}
|
15
|
+
setCategory={[Function]}
|
16
|
+
setJobTemplate={[Function]}
|
17
|
+
/>,
|
18
|
+
"name": "Category and template",
|
19
|
+
},
|
20
|
+
Object {
|
21
|
+
"canJumpTo": false,
|
22
|
+
"component": <p>
|
23
|
+
TargetHosts
|
24
|
+
</p>,
|
25
|
+
"name": "Target hosts",
|
26
|
+
},
|
27
|
+
Object {
|
28
|
+
"canJumpTo": false,
|
29
|
+
"component": <p>
|
30
|
+
AdvancedFields
|
31
|
+
</p>,
|
32
|
+
"name": "Advanced fields",
|
33
|
+
},
|
34
|
+
Object {
|
35
|
+
"canJumpTo": false,
|
36
|
+
"component": <p>
|
37
|
+
Schedule
|
38
|
+
</p>,
|
39
|
+
"name": "Schedule",
|
40
|
+
},
|
41
|
+
Object {
|
42
|
+
"canJumpTo": false,
|
43
|
+
"component": <p>
|
44
|
+
ReviewDetails
|
45
|
+
</p>,
|
46
|
+
"name": "Review details",
|
47
|
+
"nextButtonText": "Run",
|
48
|
+
},
|
49
|
+
]
|
50
|
+
}
|
51
|
+
/>
|
52
|
+
`;
|
53
|
+
|
54
|
+
exports[`JobWizardPage rendering renders 1`] = `
|
55
|
+
<PageLayout
|
56
|
+
breadcrumbOptions={
|
57
|
+
Object {
|
58
|
+
"breadcrumbItems": Array [
|
59
|
+
Object {
|
60
|
+
"caption": "Jobs",
|
61
|
+
"url": "/jobs",
|
62
|
+
},
|
63
|
+
Object {
|
64
|
+
"caption": "Run job",
|
65
|
+
},
|
66
|
+
],
|
67
|
+
}
|
68
|
+
}
|
69
|
+
header="Run job"
|
70
|
+
searchable={false}
|
71
|
+
>
|
72
|
+
<Title
|
73
|
+
headingLevel="h2"
|
74
|
+
size="2xl"
|
75
|
+
>
|
76
|
+
Run job
|
77
|
+
</Title>
|
78
|
+
<Divider
|
79
|
+
component="div"
|
80
|
+
/>
|
81
|
+
<JobWizard />
|
82
|
+
</PageLayout>
|
83
|
+
`;
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Title, Text, TextVariants, Form } from '@patternfly/react-core';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import { SelectField } from '../form/SelectField';
|
6
|
+
import { GroupedSelectField } from '../form/GroupedSelectField';
|
7
|
+
|
8
|
+
export const CategoryAndTemplate = ({
|
9
|
+
jobCategories,
|
10
|
+
jobTemplates,
|
11
|
+
setJobTemplate,
|
12
|
+
selectedTemplateID,
|
13
|
+
selectedCategory,
|
14
|
+
setCategory,
|
15
|
+
}) => {
|
16
|
+
const templatesGroups = {};
|
17
|
+
jobTemplates.forEach(template => {
|
18
|
+
if (templatesGroups[template.provider_type]?.options)
|
19
|
+
templatesGroups[template.provider_type].options.push({
|
20
|
+
label: template.name,
|
21
|
+
value: template.id,
|
22
|
+
});
|
23
|
+
else
|
24
|
+
templatesGroups[template.provider_type] = {
|
25
|
+
options: [{ label: template.name, value: template.id }],
|
26
|
+
groupLabel: template.provider_type,
|
27
|
+
};
|
28
|
+
});
|
29
|
+
|
30
|
+
const selectedTemplate = jobTemplates.find(
|
31
|
+
template => template.id === selectedTemplateID
|
32
|
+
)?.name;
|
33
|
+
|
34
|
+
const onSelectCategory = newCategory => {
|
35
|
+
setCategory(newCategory);
|
36
|
+
setJobTemplate(null);
|
37
|
+
};
|
38
|
+
return (
|
39
|
+
<>
|
40
|
+
<Title headingLevel="h2">{__('Category And Template')}</Title>
|
41
|
+
<Text component={TextVariants.p}>{__('All fields are required.')}</Text>
|
42
|
+
<Form>
|
43
|
+
<SelectField
|
44
|
+
label={__('Job category')}
|
45
|
+
fieldId="job_category"
|
46
|
+
options={jobCategories}
|
47
|
+
setValue={onSelectCategory}
|
48
|
+
value={selectedCategory}
|
49
|
+
/>
|
50
|
+
<GroupedSelectField
|
51
|
+
label={__('Job template')}
|
52
|
+
fieldId="job_template"
|
53
|
+
groups={Object.values(templatesGroups)}
|
54
|
+
setSelected={setJobTemplate}
|
55
|
+
selected={selectedTemplate}
|
56
|
+
/>
|
57
|
+
</Form>
|
58
|
+
</>
|
59
|
+
);
|
60
|
+
};
|
61
|
+
|
62
|
+
CategoryAndTemplate.propTypes = {
|
63
|
+
jobCategories: PropTypes.array,
|
64
|
+
jobTemplates: PropTypes.array,
|
65
|
+
setJobTemplate: PropTypes.func.isRequired,
|
66
|
+
selectedTemplateID: PropTypes.number,
|
67
|
+
setCategory: PropTypes.func.isRequired,
|
68
|
+
selectedCategory: PropTypes.string,
|
69
|
+
};
|
70
|
+
CategoryAndTemplate.defaultProps = {
|
71
|
+
jobCategories: [],
|
72
|
+
jobTemplates: [],
|
73
|
+
selectedTemplateID: null,
|
74
|
+
selectedCategory: null,
|
75
|
+
};
|
76
|
+
|
77
|
+
export default CategoryAndTemplate;
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
|
2
|
+
import { CategoryAndTemplate } from './CategoryAndTemplate';
|
3
|
+
|
4
|
+
const fixtures = {
|
5
|
+
'renders with props': {
|
6
|
+
jobCategories: [
|
7
|
+
'Commands',
|
8
|
+
'Ansible Playbook',
|
9
|
+
'Ansible Galaxy',
|
10
|
+
'Ansible Roles Installation',
|
11
|
+
],
|
12
|
+
jobTemplates: [
|
13
|
+
{
|
14
|
+
id: 190,
|
15
|
+
name: 'ab Run Command - SSH Default clone',
|
16
|
+
job_category: 'Commands',
|
17
|
+
provider_type: 'SSH',
|
18
|
+
snippet: false,
|
19
|
+
},
|
20
|
+
{
|
21
|
+
id: 168,
|
22
|
+
name: 'Ansible Roles - Ansible Default',
|
23
|
+
job_category: 'Ansible Playbook',
|
24
|
+
provider_type: 'Ansible',
|
25
|
+
snippet: false,
|
26
|
+
},
|
27
|
+
{
|
28
|
+
id: 170,
|
29
|
+
name: 'Ansible Roles - Install from git',
|
30
|
+
job_category: 'Ansible Roles Installation',
|
31
|
+
provider_type: 'Ansible',
|
32
|
+
snippet: false,
|
33
|
+
},
|
34
|
+
],
|
35
|
+
setJobTemplate: jest.fn(),
|
36
|
+
selectedTemplateID: 190,
|
37
|
+
setCategory: jest.fn(),
|
38
|
+
selectedCategory: 'I am a category',
|
39
|
+
},
|
40
|
+
};
|
41
|
+
|
42
|
+
describe('CategoryAndTemplate', () => {
|
43
|
+
describe('rendering', () =>
|
44
|
+
testComponentSnapshotsWithFixtures(CategoryAndTemplate, fixtures));
|
45
|
+
});
|
data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`CategoryAndTemplate rendering renders with props 1`] = `
|
4
|
+
<Fragment>
|
5
|
+
<Title
|
6
|
+
headingLevel="h2"
|
7
|
+
>
|
8
|
+
Category And Template
|
9
|
+
</Title>
|
10
|
+
<Text
|
11
|
+
component="p"
|
12
|
+
>
|
13
|
+
All fields are required.
|
14
|
+
</Text>
|
15
|
+
<Form>
|
16
|
+
<SelectField
|
17
|
+
fieldId="job_category"
|
18
|
+
label="Job category"
|
19
|
+
options={
|
20
|
+
Array [
|
21
|
+
"Commands",
|
22
|
+
"Ansible Playbook",
|
23
|
+
"Ansible Galaxy",
|
24
|
+
"Ansible Roles Installation",
|
25
|
+
]
|
26
|
+
}
|
27
|
+
setValue={[Function]}
|
28
|
+
value="I am a category"
|
29
|
+
/>
|
30
|
+
<GroupedSelectField
|
31
|
+
fieldId="job_template"
|
32
|
+
groups={
|
33
|
+
Array [
|
34
|
+
Object {
|
35
|
+
"groupLabel": "SSH",
|
36
|
+
"options": Array [
|
37
|
+
Object {
|
38
|
+
"label": "ab Run Command - SSH Default clone",
|
39
|
+
"value": 190,
|
40
|
+
},
|
41
|
+
],
|
42
|
+
},
|
43
|
+
Object {
|
44
|
+
"groupLabel": "Ansible",
|
45
|
+
"options": Array [
|
46
|
+
Object {
|
47
|
+
"label": "Ansible Roles - Ansible Default",
|
48
|
+
"value": 168,
|
49
|
+
},
|
50
|
+
Object {
|
51
|
+
"label": "Ansible Roles - Install from git",
|
52
|
+
"value": 170,
|
53
|
+
},
|
54
|
+
],
|
55
|
+
},
|
56
|
+
]
|
57
|
+
}
|
58
|
+
label="Job template"
|
59
|
+
selected="ab Run Command - SSH Default clone"
|
60
|
+
setSelected={[MockFunction]}
|
61
|
+
/>
|
62
|
+
</Form>
|
63
|
+
</Fragment>
|
64
|
+
`;
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import React, { useEffect } from 'react';
|
2
|
+
import { useSelector, useDispatch } from 'react-redux';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import URI from 'urijs';
|
5
|
+
import { get } from 'foremanReact/redux/API';
|
6
|
+
import {
|
7
|
+
selectJobCategories,
|
8
|
+
selectJobTemplates,
|
9
|
+
selectJobCategoriesStatus,
|
10
|
+
filterJobTemplates,
|
11
|
+
} from '../../JobWizardSelectors';
|
12
|
+
import { CategoryAndTemplate } from './CategoryAndTemplate';
|
13
|
+
|
14
|
+
import {
|
15
|
+
JOB_TEMPLATES,
|
16
|
+
JOB_CATEGORIES,
|
17
|
+
templatesUrl,
|
18
|
+
} from '../../JobWizardConstants';
|
19
|
+
|
20
|
+
const ConnectedCategoryAndTemplate = ({
|
21
|
+
jobTemplate,
|
22
|
+
setJobTemplate,
|
23
|
+
category,
|
24
|
+
setCategory,
|
25
|
+
}) => {
|
26
|
+
const dispatch = useDispatch();
|
27
|
+
|
28
|
+
const jobCategoriesStatus = useSelector(selectJobCategoriesStatus);
|
29
|
+
useEffect(() => {
|
30
|
+
if (!jobCategoriesStatus) {
|
31
|
+
// Don't reload categories if they are already loaded
|
32
|
+
dispatch(
|
33
|
+
get({
|
34
|
+
key: JOB_CATEGORIES,
|
35
|
+
url: '/ui_job_wizard/categories',
|
36
|
+
handleSuccess: response =>
|
37
|
+
setCategory(response.data.job_categories[0] || ''),
|
38
|
+
})
|
39
|
+
);
|
40
|
+
}
|
41
|
+
}, [jobCategoriesStatus, dispatch, setCategory]);
|
42
|
+
|
43
|
+
const jobCategories = useSelector(selectJobCategories);
|
44
|
+
|
45
|
+
useEffect(() => {
|
46
|
+
if (category) {
|
47
|
+
const templatesUrlObject = new URI(templatesUrl);
|
48
|
+
dispatch(
|
49
|
+
get({
|
50
|
+
key: JOB_TEMPLATES,
|
51
|
+
url: templatesUrlObject.addSearch({
|
52
|
+
search: `job_category="${category}"`,
|
53
|
+
per_page: 'all',
|
54
|
+
}),
|
55
|
+
handleSuccess: response =>
|
56
|
+
setJobTemplate(
|
57
|
+
Number(filterJobTemplates(response?.data?.results)[0]?.id) || null
|
58
|
+
),
|
59
|
+
})
|
60
|
+
);
|
61
|
+
}
|
62
|
+
}, [category, dispatch, setJobTemplate]);
|
63
|
+
|
64
|
+
const jobTemplates = useSelector(selectJobTemplates);
|
65
|
+
|
66
|
+
return (
|
67
|
+
<CategoryAndTemplate
|
68
|
+
jobTemplates={jobTemplates}
|
69
|
+
jobCategories={jobCategories}
|
70
|
+
setJobTemplate={setJobTemplate}
|
71
|
+
selectedTemplateID={jobTemplate}
|
72
|
+
setCategory={setCategory}
|
73
|
+
selectedCategory={category}
|
74
|
+
/>
|
75
|
+
);
|
76
|
+
};
|
77
|
+
|
78
|
+
ConnectedCategoryAndTemplate.propTypes = {
|
79
|
+
jobTemplate: PropTypes.number,
|
80
|
+
setJobTemplate: PropTypes.func.isRequired,
|
81
|
+
category: PropTypes.string.isRequired,
|
82
|
+
setCategory: PropTypes.func.isRequired,
|
83
|
+
};
|
84
|
+
ConnectedCategoryAndTemplate.defaultProps = { jobTemplate: null };
|
85
|
+
|
86
|
+
export default ConnectedCategoryAndTemplate;
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import {
|
4
|
+
SelectOption,
|
5
|
+
Select,
|
6
|
+
SelectGroup,
|
7
|
+
SelectVariant,
|
8
|
+
FormGroup,
|
9
|
+
} from '@patternfly/react-core';
|
10
|
+
|
11
|
+
export const GroupedSelectField = ({
|
12
|
+
label,
|
13
|
+
fieldId,
|
14
|
+
groups,
|
15
|
+
selected,
|
16
|
+
setSelected,
|
17
|
+
}) => {
|
18
|
+
const [isOpen, setIsOpen] = useState(false);
|
19
|
+
const onSelect = selection => {
|
20
|
+
setIsOpen(false);
|
21
|
+
setSelected(selection);
|
22
|
+
};
|
23
|
+
|
24
|
+
const onClear = () => {
|
25
|
+
onSelect(null);
|
26
|
+
};
|
27
|
+
|
28
|
+
const options = groups.map((group, groupIndex) => (
|
29
|
+
<SelectGroup key={groupIndex} label={group.groupLabel}>
|
30
|
+
{group.options.map((option, optionIndex) => (
|
31
|
+
<SelectOption
|
32
|
+
key={optionIndex}
|
33
|
+
value={option.label}
|
34
|
+
onClick={() => onSelect(option.value)}
|
35
|
+
/>
|
36
|
+
))}
|
37
|
+
</SelectGroup>
|
38
|
+
));
|
39
|
+
|
40
|
+
const onFilter = evt => {
|
41
|
+
const textInput = evt?.target?.value || '';
|
42
|
+
if (textInput === '') {
|
43
|
+
return options;
|
44
|
+
}
|
45
|
+
return options
|
46
|
+
.map(group => {
|
47
|
+
const filteredGroup = React.cloneElement(group, {
|
48
|
+
children: group.props.children.filter(item =>
|
49
|
+
item.props.value.toLowerCase().includes(textInput.toLowerCase())
|
50
|
+
),
|
51
|
+
});
|
52
|
+
if (filteredGroup.props.children.length > 0) return filteredGroup;
|
53
|
+
return null;
|
54
|
+
})
|
55
|
+
.filter(newGroup => newGroup);
|
56
|
+
};
|
57
|
+
|
58
|
+
return (
|
59
|
+
<FormGroup label={label} fieldId={fieldId}>
|
60
|
+
<Select
|
61
|
+
isGrouped
|
62
|
+
variant={SelectVariant.typeahead}
|
63
|
+
onToggle={setIsOpen}
|
64
|
+
onFilter={onFilter}
|
65
|
+
isOpen={isOpen}
|
66
|
+
onSelect={() => null}
|
67
|
+
selections={selected}
|
68
|
+
className="without_select2"
|
69
|
+
onClear={onClear}
|
70
|
+
>
|
71
|
+
{options}
|
72
|
+
</Select>
|
73
|
+
</FormGroup>
|
74
|
+
);
|
75
|
+
};
|
76
|
+
|
77
|
+
GroupedSelectField.propTypes = {
|
78
|
+
label: PropTypes.string.isRequired,
|
79
|
+
fieldId: PropTypes.string.isRequired,
|
80
|
+
groups: PropTypes.array,
|
81
|
+
selected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
82
|
+
setSelected: PropTypes.func.isRequired,
|
83
|
+
};
|
84
|
+
|
85
|
+
GroupedSelectField.defaultProps = {
|
86
|
+
groups: [],
|
87
|
+
selected: null,
|
88
|
+
};
|