mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 15:21:13 +03:00
Add inventory source subforms
This commit is contained in:
parent
97dbfee162
commit
6ed611c27c
@ -126,7 +126,7 @@ SUMMARIZABLE_FK_FIELDS = {
|
||||
'current_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
|
||||
'inventory_source': ('source', 'last_updated', 'status'),
|
||||
'custom_inventory_script': DEFAULT_SUMMARY_FIELDS,
|
||||
'source_script': ('name', 'description'),
|
||||
'source_script': DEFAULT_SUMMARY_FIELDS,
|
||||
'role': ('id', 'role_field'),
|
||||
'notification_template': DEFAULT_SUMMARY_FIELDS,
|
||||
'instance_group': ('id', 'name', 'controller_id', 'is_containerized'),
|
||||
|
@ -8,6 +8,7 @@ import Groups from './models/Groups';
|
||||
import Hosts from './models/Hosts';
|
||||
import InstanceGroups from './models/InstanceGroups';
|
||||
import Inventories from './models/Inventories';
|
||||
import InventoryScripts from './models/InventoryScripts';
|
||||
import InventorySources from './models/InventorySources';
|
||||
import InventoryUpdates from './models/InventoryUpdates';
|
||||
import JobTemplates from './models/JobTemplates';
|
||||
@ -41,6 +42,7 @@ const GroupsAPI = new Groups();
|
||||
const HostsAPI = new Hosts();
|
||||
const InstanceGroupsAPI = new InstanceGroups();
|
||||
const InventoriesAPI = new Inventories();
|
||||
const InventoryScriptsAPI = new InventoryScripts();
|
||||
const InventorySourcesAPI = new InventorySources();
|
||||
const InventoryUpdatesAPI = new InventoryUpdates();
|
||||
const JobTemplatesAPI = new JobTemplates();
|
||||
@ -75,6 +77,7 @@ export {
|
||||
HostsAPI,
|
||||
InstanceGroupsAPI,
|
||||
InventoriesAPI,
|
||||
InventoryScriptsAPI,
|
||||
InventorySourcesAPI,
|
||||
InventoryUpdatesAPI,
|
||||
JobTemplatesAPI,
|
||||
|
10
awx/ui_next/src/api/models/InventoryScripts.js
Normal file
10
awx/ui_next/src/api/models/InventoryScripts.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Base from '../Base';
|
||||
|
||||
class InventoryScripts extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/inventory_scripts/';
|
||||
}
|
||||
}
|
||||
|
||||
export default InventoryScripts;
|
@ -28,6 +28,7 @@ function CredentialLookup({
|
||||
required,
|
||||
credentialTypeId,
|
||||
credentialTypeKind,
|
||||
credentialTypeNamespace,
|
||||
value,
|
||||
history,
|
||||
i18n,
|
||||
@ -46,15 +47,27 @@ function CredentialLookup({
|
||||
const typeKindParams = credentialTypeKind
|
||||
? { credential_type__kind: credentialTypeKind }
|
||||
: {};
|
||||
const typeNamespaceParams = credentialTypeNamespace
|
||||
? { credential_type__namespace: credentialTypeNamespace }
|
||||
: {};
|
||||
|
||||
const { data } = await CredentialsAPI.read(
|
||||
mergeParams(params, { ...typeIdParams, ...typeKindParams })
|
||||
mergeParams(params, {
|
||||
...typeIdParams,
|
||||
...typeKindParams,
|
||||
...typeNamespaceParams,
|
||||
})
|
||||
);
|
||||
return {
|
||||
count: data.count,
|
||||
credentials: data.results,
|
||||
};
|
||||
}, [credentialTypeId, credentialTypeKind, history.location.search]),
|
||||
}, [
|
||||
credentialTypeId,
|
||||
credentialTypeKind,
|
||||
credentialTypeNamespace,
|
||||
history.location.search,
|
||||
]),
|
||||
{
|
||||
count: 0,
|
||||
credentials: [],
|
||||
|
137
awx/ui_next/src/components/Lookup/InventoryScriptLookup.jsx
Normal file
137
awx/ui_next/src/components/Lookup/InventoryScriptLookup.jsx
Normal file
@ -0,0 +1,137 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { func, bool, number, node, string, oneOfType } from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { FormGroup } from '@patternfly/react-core';
|
||||
import Lookup from './Lookup';
|
||||
import LookupErrorMessage from './shared/LookupErrorMessage';
|
||||
import OptionsList from '../OptionsList';
|
||||
import { InventoriesAPI, InventoryScriptsAPI } from '../../api';
|
||||
import { InventoryScript } from '../../types';
|
||||
import useRequest from '../../util/useRequest';
|
||||
import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs';
|
||||
|
||||
const QS_CONFIG = getQSConfig('inventory_scripts', {
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
role_level: 'admin_role',
|
||||
});
|
||||
|
||||
function InventoryScriptLookup({
|
||||
helperTextInvalid,
|
||||
history,
|
||||
i18n,
|
||||
inventoryId,
|
||||
isValid,
|
||||
onBlur,
|
||||
onChange,
|
||||
required,
|
||||
value,
|
||||
}) {
|
||||
const {
|
||||
result: { count, inventoryScripts },
|
||||
error,
|
||||
request: fetchInventoryScripts,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const parsedParams = parseQueryString(QS_CONFIG, history.location.search);
|
||||
const {
|
||||
data: { organization },
|
||||
} = await InventoriesAPI.readDetail(inventoryId);
|
||||
const { data } = await InventoryScriptsAPI.read(
|
||||
mergeParams(parsedParams, { organization })
|
||||
);
|
||||
return {
|
||||
count: data.count,
|
||||
inventoryScripts: data.results,
|
||||
};
|
||||
}, [history.location.search, inventoryId]),
|
||||
{
|
||||
count: 0,
|
||||
inventoryScripts: [],
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchInventoryScripts();
|
||||
}, [fetchInventoryScripts]);
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="inventory-script"
|
||||
helperTextInvalid={helperTextInvalid}
|
||||
isRequired={required}
|
||||
isValid={isValid}
|
||||
label={i18n._(t`Inventory script`)}
|
||||
>
|
||||
<Lookup
|
||||
id="inventory-script-lookup"
|
||||
header={i18n._(t`Inventory script`)}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
required={required}
|
||||
qsConfig={QS_CONFIG}
|
||||
renderOptionsList={({ state, dispatch, canDelete }) => (
|
||||
<OptionsList
|
||||
header={i18n._(t`Inventory script`)}
|
||||
multiple={state.multiple}
|
||||
name="inventory-script"
|
||||
optionCount={count}
|
||||
options={inventoryScripts}
|
||||
qsConfig={QS_CONFIG}
|
||||
readOnly={!canDelete}
|
||||
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
|
||||
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
|
||||
value={state.selectedItems}
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<LookupErrorMessage error={error} />
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
InventoryScriptLookup.propTypes = {
|
||||
helperTextInvalid: node,
|
||||
inventoryId: oneOfType([number, string]).isRequired,
|
||||
isValid: bool,
|
||||
onBlur: func,
|
||||
onChange: func.isRequired,
|
||||
required: bool,
|
||||
value: InventoryScript,
|
||||
};
|
||||
|
||||
InventoryScriptLookup.defaultProps = {
|
||||
helperTextInvalid: '',
|
||||
isValid: true,
|
||||
onBlur: () => {},
|
||||
required: false,
|
||||
value: null,
|
||||
};
|
||||
|
||||
export default withI18n()(withRouter(InventoryScriptLookup));
|
@ -1,14 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { func, string } from 'prop-types';
|
||||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
||||
|
||||
function arrayToString(tags) {
|
||||
return tags.join(',');
|
||||
}
|
||||
|
||||
function stringToArray(value) {
|
||||
return value.split(',').filter(val => !!val);
|
||||
}
|
||||
import { arrayToString, stringToArray } from '../../util/strings';
|
||||
|
||||
function TagMultiSelect({ onChange, value }) {
|
||||
const selections = stringToArray(value);
|
||||
|
@ -26,7 +26,13 @@ function InventorySourceAdd() {
|
||||
}, [result, history]);
|
||||
|
||||
const handleSubmit = async form => {
|
||||
const { credential, source_path, source_project, ...remainingForm } = form;
|
||||
const {
|
||||
credential,
|
||||
source_path,
|
||||
source_project,
|
||||
source_script,
|
||||
...remainingForm
|
||||
} = form;
|
||||
|
||||
const sourcePath = {};
|
||||
const sourceProject = {};
|
||||
@ -39,6 +45,7 @@ function InventorySourceAdd() {
|
||||
await request({
|
||||
credential: credential?.id || null,
|
||||
inventory: id,
|
||||
source_script: source_script?.id || null,
|
||||
...sourcePath,
|
||||
...sourceProject,
|
||||
...remainingForm,
|
||||
|
@ -115,6 +115,7 @@ describe('<InventorySourceAdd />', () => {
|
||||
...invSourceData,
|
||||
credential: 222,
|
||||
source_project: 999,
|
||||
source_script: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -29,7 +29,13 @@ function InventorySourceEdit({ source }) {
|
||||
}, [result, detailsUrl, history]);
|
||||
|
||||
const handleSubmit = async form => {
|
||||
const { credential, source_path, source_project, ...remainingForm } = form;
|
||||
const {
|
||||
credential,
|
||||
source_path,
|
||||
source_project,
|
||||
source_script,
|
||||
...remainingForm
|
||||
} = form;
|
||||
|
||||
const sourcePath = {};
|
||||
const sourceProject = {};
|
||||
@ -38,9 +44,11 @@ function InventorySourceEdit({ source }) {
|
||||
source_path === '/ (project root)' ? '' : source_path;
|
||||
sourceProject.source_project = source_project.id;
|
||||
}
|
||||
|
||||
await request({
|
||||
credential: credential?.id || null,
|
||||
inventory: id,
|
||||
source_script: source_script?.id || null,
|
||||
...sourcePath,
|
||||
...sourceProject,
|
||||
...remainingForm,
|
||||
|
@ -22,10 +22,34 @@ import {
|
||||
SubFormLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
|
||||
import SCMSubForm from './InventorySourceSubForms';
|
||||
import {
|
||||
AzureSubForm,
|
||||
CloudFormsSubForm,
|
||||
CustomScriptSubForm,
|
||||
EC2SubForm,
|
||||
GCESubForm,
|
||||
OpenStackSubForm,
|
||||
SCMSubForm,
|
||||
SatelliteSubForm,
|
||||
TowerSubForm,
|
||||
VMwareSubForm,
|
||||
VirtualizationSubForm,
|
||||
} from './InventorySourceSubForms';
|
||||
|
||||
const buildSourceChoiceOptions = options => {
|
||||
const sourceChoices = options.actions.GET.source.choices.map(
|
||||
([choice, label]) => ({ label, key: choice, value: choice })
|
||||
);
|
||||
return sourceChoices.filter(({ key }) => key !== 'file');
|
||||
};
|
||||
|
||||
const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||
const { values, initialValues, resetForm } = useFormikContext();
|
||||
const {
|
||||
values,
|
||||
initialValues,
|
||||
resetForm,
|
||||
setFieldValue,
|
||||
} = useFormikContext();
|
||||
const [sourceField, sourceMeta] = useField({
|
||||
name: 'source',
|
||||
validate: required(i18n._(t`Set a value for this field`), i18n),
|
||||
@ -39,15 +63,38 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||
};
|
||||
|
||||
const resetSubFormFields = sourceType => {
|
||||
resetForm({
|
||||
values: {
|
||||
...initialValues,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
custom_virtualenv: values.custom_virtualenv,
|
||||
if (sourceType === initialValues.source) {
|
||||
resetForm({
|
||||
values: {
|
||||
...initialValues,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
custom_virtualenv: values.custom_virtualenv,
|
||||
source: sourceType,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const defaults = {
|
||||
credential: null,
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source: sourceType,
|
||||
},
|
||||
});
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: false,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
Object.keys(defaults).forEach(label => {
|
||||
setFieldValue(label, defaults[label]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -83,7 +130,7 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||
label: i18n._(t`Choose a source`),
|
||||
isDisabled: true,
|
||||
},
|
||||
...sourceOptions,
|
||||
...buildSourceChoiceOptions(sourceOptions),
|
||||
]}
|
||||
onChange={(event, value) => {
|
||||
resetSubFormFields(value);
|
||||
@ -112,14 +159,23 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
{sourceField.value !== '' && (
|
||||
<SubFormLayout>
|
||||
<Title size="md">{i18n._(t`Source details`)}</Title>
|
||||
<FormColumnLayout>
|
||||
{
|
||||
{
|
||||
azure_rm: <AzureSubForm sourceOptions={sourceOptions} />,
|
||||
cloudforms: <CloudFormsSubForm />,
|
||||
custom: <CustomScriptSubForm />,
|
||||
ec2: <EC2SubForm sourceOptions={sourceOptions} />,
|
||||
gce: <GCESubForm sourceOptions={sourceOptions} />,
|
||||
openstack: <OpenStackSubForm />,
|
||||
rhv: <VirtualizationSubForm />,
|
||||
satellite6: <SatelliteSubForm />,
|
||||
scm: <SCMSubForm />,
|
||||
tower: <TowerSubForm />,
|
||||
vmware: <VMwareSubForm sourceOptions={sourceOptions} />,
|
||||
}[sourceField.value]
|
||||
}
|
||||
</FormColumnLayout>
|
||||
@ -140,12 +196,16 @@ const InventorySourceForm = ({
|
||||
credential: source?.summary_fields?.credential || null,
|
||||
custom_virtualenv: source?.custom_virtualenv || '',
|
||||
description: source?.description || '',
|
||||
group_by: source?.group_by || '',
|
||||
instance_filters: source?.instance_filters || '',
|
||||
name: source?.name || '',
|
||||
overwrite: source?.overwrite || false,
|
||||
overwrite_vars: source?.overwrite_vars || false,
|
||||
source: source?.source || '',
|
||||
source_path: source?.source_path === '' ? '/ (project root)' : '',
|
||||
source_project: source?.summary_fields?.source_project || null,
|
||||
source_regions: source?.source_regions || '',
|
||||
source_script: source?.summary_fields?.source_script || null,
|
||||
source_vars: source?.source_vars || '---\n',
|
||||
update_cache_timeout: source?.update_cache_timeout || 0,
|
||||
update_on_launch: source?.update_on_launch || false,
|
||||
@ -161,17 +221,7 @@ const InventorySourceForm = ({
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const { data } = await InventorySourcesAPI.readOptions();
|
||||
const sourceChoices = Object.assign(
|
||||
...data.actions.GET.source.choices.map(([key, val]) => ({ [key]: val }))
|
||||
);
|
||||
delete sourceChoices.file;
|
||||
return Object.keys(sourceChoices).map(choice => {
|
||||
return {
|
||||
value: choice,
|
||||
key: choice,
|
||||
label: sourceChoices[choice],
|
||||
};
|
||||
});
|
||||
return data;
|
||||
}, []),
|
||||
null
|
||||
);
|
||||
|
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import {
|
||||
OptionsField,
|
||||
RegionsField,
|
||||
SourceVarsField,
|
||||
VerbosityField,
|
||||
} from './SharedFields';
|
||||
|
||||
const AzureSubForm = ({ i18n, sourceOptions }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="azure_rm"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<RegionsField
|
||||
regionOptions={
|
||||
sourceOptions?.actions?.POST?.source_regions?.azure_rm_region_choices
|
||||
}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(AzureSubForm);
|
@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import AzureSubForm from './AzureSubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
const mockSourceOptions = {
|
||||
actions: {
|
||||
POST: {
|
||||
source_regions: {
|
||||
azure_rm_region_choices: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('<AzureSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<AzureSubForm sourceOptions={mockSourceOptions} />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Regions"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'azure_rm',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import { OptionsField, SourceVarsField, VerbosityField } from './SharedFields';
|
||||
|
||||
const CloudFormsSubForm = ({ i18n }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="cloudforms"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(CloudFormsSubForm);
|
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import CloudFormsSubForm from './CloudFormsSubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
describe('<CloudFormsSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<CloudFormsSubForm />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'cloudforms',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import InventoryScriptLookup from '../../../../components/Lookup/InventoryScriptLookup';
|
||||
import { OptionsField, SourceVarsField, VerbosityField } from './SharedFields';
|
||||
|
||||
const CustomScriptSubForm = ({ i18n }) => {
|
||||
const { id } = useParams();
|
||||
const [credentialField, , credentialHelpers] = useField('credential');
|
||||
const [scriptField, scriptMeta, scriptHelpers] = useField('source_script');
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="cloud"
|
||||
label={i18n._(t`Credential`)}
|
||||
value={credentialField.value}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
/>
|
||||
<InventoryScriptLookup
|
||||
helperTextInvalid={scriptMeta.error}
|
||||
isValid={!scriptMeta.touched || !scriptMeta.error}
|
||||
onBlur={() => scriptHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
scriptHelpers.setValue(value);
|
||||
}}
|
||||
inventoryId={id}
|
||||
value={scriptField.value}
|
||||
required
|
||||
/>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(CustomScriptSubForm);
|
@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import CustomScriptSubForm from './CustomScriptSubForm';
|
||||
import {
|
||||
CredentialsAPI,
|
||||
InventoriesAPI,
|
||||
InventoryScriptsAPI,
|
||||
} from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
jest.mock('../../../../api/models/Inventories');
|
||||
jest.mock('../../../../api/models/InventoryScripts');
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: () => ({
|
||||
id: 789,
|
||||
}),
|
||||
}));
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
describe('<CustomScriptSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
InventoriesAPI.readDetail.mockResolvedValue({
|
||||
data: { organization: 123 },
|
||||
});
|
||||
InventoryScriptsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<CustomScriptSubForm />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Inventory script"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'cloud',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
expect(InventoriesAPI.readDetail).toHaveBeenCalledTimes(1);
|
||||
expect(InventoriesAPI.readDetail).toHaveBeenCalledWith(789);
|
||||
expect(InventoryScriptsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(InventoryScriptsAPI.read).toHaveBeenCalledWith({
|
||||
organization: 123,
|
||||
role_level: 'admin_role',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import {
|
||||
GroupByField,
|
||||
InstanceFiltersField,
|
||||
OptionsField,
|
||||
RegionsField,
|
||||
SourceVarsField,
|
||||
VerbosityField,
|
||||
} from './SharedFields';
|
||||
|
||||
const EC2SubForm = ({ i18n, sourceOptions }) => {
|
||||
const [credentialField, , credentialHelpers] = useField('credential');
|
||||
const groupByOptionsObj = Object.assign(
|
||||
{},
|
||||
...sourceOptions?.actions?.POST?.group_by?.ec2_group_by_choices.map(
|
||||
([key, val]) => ({ [key]: val })
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="aws"
|
||||
label={i18n._(t`Credential`)}
|
||||
value={credentialField.value}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
/>
|
||||
<RegionsField
|
||||
regionOptions={
|
||||
sourceOptions?.actions?.POST?.source_regions?.ec2_region_choices
|
||||
}
|
||||
/>
|
||||
<InstanceFiltersField />
|
||||
<GroupByField fixedOptions={groupByOptionsObj} />
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(EC2SubForm);
|
@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import EC2SubForm from './EC2SubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
const mockSourceOptions = {
|
||||
actions: {
|
||||
POST: {
|
||||
source_regions: {
|
||||
ec2_region_choices: [],
|
||||
},
|
||||
group_by: {
|
||||
ec2_group_by_choices: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('<EC2SubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<EC2SubForm sourceOptions={mockSourceOptions} />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Regions"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Instance filters"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Only group by"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'aws',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import { OptionsField, RegionsField, VerbosityField } from './SharedFields';
|
||||
|
||||
const GCESubForm = ({ i18n, sourceOptions }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="gce"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<RegionsField
|
||||
regionOptions={
|
||||
sourceOptions?.actions?.POST?.source_regions?.gce_region_choices
|
||||
}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(GCESubForm);
|
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import GCESubForm from './GCESubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
const mockSourceOptions = {
|
||||
actions: {
|
||||
POST: {
|
||||
source_regions: {
|
||||
gce_region_choices: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('<GCESubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<GCESubForm sourceOptions={mockSourceOptions} />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Regions"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'gce',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import { OptionsField, SourceVarsField, VerbosityField } from './SharedFields';
|
||||
|
||||
const OpenStackSubForm = ({ i18n }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="openstack"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(OpenStackSubForm);
|
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import OpenStackSubForm from './OpenStackSubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
describe('<OpenStackSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<OpenStackSubForm />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'openstack',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -117,7 +117,7 @@ const SCMSubForm = ({ i18n }) => {
|
||||
/>
|
||||
</FormGroup>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<OptionsField showProjectUpdate />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
|
@ -11,13 +11,17 @@ jest.mock('../../../../api/models/Credentials');
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: false,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
@ -68,7 +72,10 @@ describe('<SCMSubForm />', () => {
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Environment variables"]')
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import { OptionsField, SourceVarsField, VerbosityField } from './SharedFields';
|
||||
|
||||
const SatelliteSubForm = ({ i18n }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="satellite6"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(SatelliteSubForm);
|
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import SatelliteSubForm from './SatelliteSubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
describe('<SatelliteSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<SatelliteSubForm />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'satellite6',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -1,9 +1,16 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { t, Trans } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
import { FormGroup } from '@patternfly/react-core';
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from '@patternfly/react-core';
|
||||
import { arrayToString, stringToArray } from '../../../../util/strings';
|
||||
import { minMaxValue } from '../../../../util/validators';
|
||||
import { BrandName } from '../../../../variables';
|
||||
import AnsibleSelect from '../../../../components/AnsibleSelect';
|
||||
import { VariablesField } from '../../../../components/CodeMirrorInput';
|
||||
import FormField, {
|
||||
@ -20,11 +27,197 @@ export const SourceVarsField = withI18n()(({ i18n }) => (
|
||||
<VariablesField
|
||||
id="source_vars"
|
||||
name="source_vars"
|
||||
label={i18n._(t`Environment variables`)}
|
||||
label={i18n._(t`Source variables`)}
|
||||
/>
|
||||
</FormFullWidthLayout>
|
||||
));
|
||||
|
||||
export const RegionsField = withI18n()(({ i18n, regionOptions }) => {
|
||||
const [field, meta, helpers] = useField('source_regions');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const options = Object.assign(
|
||||
{},
|
||||
...regionOptions.map(([key, val]) => ({ [key]: val }))
|
||||
);
|
||||
const selected = stringToArray(field?.value)
|
||||
.filter(i => options[i])
|
||||
.map(val => options[val]);
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="regions"
|
||||
helperTextInvalid={meta.error}
|
||||
validated="default"
|
||||
label={i18n._(t`Regions`)}
|
||||
>
|
||||
<FieldTooltip
|
||||
content={
|
||||
<Trans>
|
||||
Click on the regions field to see a list of regions for your cloud
|
||||
provider. You can select multiple regions, or choose
|
||||
<em> All</em> to include all regions. Only Hosts associated with the
|
||||
selected regions will be updated.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
id="regions"
|
||||
onToggle={setIsOpen}
|
||||
onClear={() => helpers.setValue('')}
|
||||
onSelect={(event, option) => {
|
||||
let selectedValues;
|
||||
if (selected.includes(option)) {
|
||||
selectedValues = selected.filter(o => o !== option);
|
||||
} else {
|
||||
selectedValues = selected.concat(option);
|
||||
}
|
||||
const selectedKeys = selectedValues.map(val =>
|
||||
Object.keys(options).find(key => options[key] === val)
|
||||
);
|
||||
helpers.setValue(arrayToString(selectedKeys));
|
||||
}}
|
||||
isExpanded={isOpen}
|
||||
placeholderText={i18n._(t`Select a region`)}
|
||||
selections={selected}
|
||||
>
|
||||
{regionOptions.map(([key, val]) => (
|
||||
<SelectOption key={key} value={val} />
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
);
|
||||
});
|
||||
|
||||
export const GroupByField = withI18n()(
|
||||
({ i18n, fixedOptions, isCreatable = false }) => {
|
||||
const [field, meta, helpers] = useField('group_by');
|
||||
const fixedOptionLabels = fixedOptions && Object.values(fixedOptions);
|
||||
const selections = fixedOptions
|
||||
? stringToArray(field.value).map(o => fixedOptions[o])
|
||||
: stringToArray(field.value);
|
||||
const [options, setOptions] = useState(selections);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const renderOptions = opts => {
|
||||
return opts.map(option => (
|
||||
<SelectOption key={option} value={option}>
|
||||
{option}
|
||||
</SelectOption>
|
||||
));
|
||||
};
|
||||
|
||||
const handleFilter = event => {
|
||||
const str = event.target.value.toLowerCase();
|
||||
let matches;
|
||||
if (fixedOptions) {
|
||||
matches = fixedOptionLabels.filter(o => o.toLowerCase().includes(str));
|
||||
} else {
|
||||
matches = options.filter(o => o.toLowerCase().includes(str));
|
||||
}
|
||||
return renderOptions(matches);
|
||||
};
|
||||
|
||||
const handleSelect = (e, option) => {
|
||||
let selectedValues;
|
||||
if (selections.includes(option)) {
|
||||
selectedValues = selections.filter(o => o !== option);
|
||||
} else {
|
||||
selectedValues = selections.concat(option);
|
||||
}
|
||||
if (fixedOptions) {
|
||||
selectedValues = selectedValues.map(val =>
|
||||
Object.keys(fixedOptions).find(key => fixedOptions[key] === val)
|
||||
);
|
||||
}
|
||||
helpers.setValue(arrayToString(selectedValues));
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="group-by"
|
||||
helperTextInvalid={meta.error}
|
||||
validated="default"
|
||||
label={i18n._(t`Only group by`)}
|
||||
>
|
||||
<FieldTooltip
|
||||
content={
|
||||
<Trans>
|
||||
Select which groups to create automatically. AWX will create group
|
||||
names similar to the following examples based on the options
|
||||
selected:
|
||||
<br />
|
||||
<br />
|
||||
<ul>
|
||||
<li>
|
||||
Availability Zone: <strong>zones » us-east-1b</strong>
|
||||
</li>
|
||||
<li>
|
||||
Image ID: <strong>images » ami-b007ab1e</strong>
|
||||
</li>
|
||||
<li>
|
||||
Instance ID: <strong>instances » i-ca11ab1e </strong>
|
||||
</li>
|
||||
<li>
|
||||
Instance Type: <strong>types » type_m1_medium</strong>
|
||||
</li>
|
||||
<li>
|
||||
Key Name: <strong>keys » key_testing</strong>
|
||||
</li>
|
||||
<li>
|
||||
Region: <strong>regions » us-east-1</strong>
|
||||
</li>
|
||||
<li>
|
||||
Security Group:{' '}
|
||||
<strong>
|
||||
security_groups » security_group_default
|
||||
</strong>
|
||||
</li>
|
||||
<li>
|
||||
Tags: <strong>tags » tag_Name_host1</strong>
|
||||
</li>
|
||||
<li>
|
||||
VPC ID: <strong>vpcs » vpc-5ca1ab1e</strong>
|
||||
</li>
|
||||
<li>
|
||||
Tag None: <strong>tags » tag_none</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
If blank, all groups above are created except <em>Instance ID</em>
|
||||
.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
id="group-by"
|
||||
onToggle={setIsOpen}
|
||||
onClear={() => helpers.setValue('')}
|
||||
isCreatable={isCreatable}
|
||||
createText={i18n._(t`Create`)}
|
||||
onCreateOption={name => {
|
||||
name = name.trim();
|
||||
if (!options.find(opt => opt === name)) {
|
||||
setOptions(options.concat(name));
|
||||
}
|
||||
return name;
|
||||
}}
|
||||
onFilter={handleFilter}
|
||||
onSelect={handleSelect}
|
||||
isExpanded={isOpen}
|
||||
placeholderText={i18n._(t`Select a group`)}
|
||||
selections={selections}
|
||||
>
|
||||
{fixedOptions
|
||||
? renderOptions(fixedOptionLabels)
|
||||
: renderOptions(options)}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const VerbosityField = withI18n()(({ i18n }) => {
|
||||
const [field, meta, helpers] = useField('verbosity');
|
||||
const isValid = !(meta.touched && meta.error);
|
||||
@ -53,98 +246,148 @@ export const VerbosityField = withI18n()(({ i18n }) => {
|
||||
);
|
||||
});
|
||||
|
||||
export const OptionsField = withI18n()(({ i18n }) => {
|
||||
const [updateOnLaunchField] = useField('update_on_launch');
|
||||
const [, , updateCacheTimeoutHelper] = useField('update_cache_timeout');
|
||||
export const OptionsField = withI18n()(
|
||||
({ i18n, showProjectUpdate = false }) => {
|
||||
const [updateOnLaunchField] = useField('update_on_launch');
|
||||
const [, , updateCacheTimeoutHelper] = useField('update_cache_timeout');
|
||||
|
||||
useEffect(() => {
|
||||
if (!updateOnLaunchField.value) {
|
||||
updateCacheTimeoutHelper.setValue(0);
|
||||
}
|
||||
}, [updateOnLaunchField.value]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
if (!updateOnLaunchField.value) {
|
||||
updateCacheTimeoutHelper.setValue(0);
|
||||
}
|
||||
}, [updateOnLaunchField.value]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormFullWidthLayout>
|
||||
<FormGroup
|
||||
fieldId="option-checkboxes"
|
||||
label={i18n._(t`Update options`)}
|
||||
>
|
||||
<FormCheckboxLayout>
|
||||
<CheckboxField
|
||||
id="overwrite"
|
||||
name="overwrite"
|
||||
label={i18n._(t`Overwrite`)}
|
||||
tooltip={
|
||||
<>
|
||||
{i18n._(t`If checked, any hosts and groups that were
|
||||
return (
|
||||
<>
|
||||
<FormFullWidthLayout>
|
||||
<FormGroup
|
||||
fieldId="option-checkboxes"
|
||||
label={i18n._(t`Update options`)}
|
||||
>
|
||||
<FormCheckboxLayout>
|
||||
<CheckboxField
|
||||
id="overwrite"
|
||||
name="overwrite"
|
||||
label={i18n._(t`Overwrite`)}
|
||||
tooltip={
|
||||
<>
|
||||
{i18n._(t`If checked, any hosts and groups that were
|
||||
previously present on the external source but are now removed
|
||||
will be removed from the Tower inventory. Hosts and groups
|
||||
that were not managed by the inventory source will be promoted
|
||||
to the next manually created group or if there is no manually
|
||||
created group to promote them into, they will be left in the "all"
|
||||
default group for the inventory.`)}
|
||||
<br />
|
||||
<br />
|
||||
{i18n._(t`When not checked, local child
|
||||
<br />
|
||||
<br />
|
||||
{i18n._(t`When not checked, local child
|
||||
hosts and groups not found on the external source will remain
|
||||
untouched by the inventory update process.`)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<CheckboxField
|
||||
id="overwrite_vars"
|
||||
name="overwrite_vars"
|
||||
label={i18n._(t`Overwrite variables`)}
|
||||
tooltip={
|
||||
<>
|
||||
{i18n._(t`If checked, all variables for child groups
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<CheckboxField
|
||||
id="overwrite_vars"
|
||||
name="overwrite_vars"
|
||||
label={i18n._(t`Overwrite variables`)}
|
||||
tooltip={
|
||||
<>
|
||||
{i18n._(t`If checked, all variables for child groups
|
||||
and hosts will be removed and replaced by those found
|
||||
on the external source.`)}
|
||||
<br />
|
||||
<br />
|
||||
{i18n._(t`When not checked, a merge will be performed,
|
||||
<br />
|
||||
<br />
|
||||
{i18n._(t`When not checked, a merge will be performed,
|
||||
combining local variables with those found on the
|
||||
external source.`)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<CheckboxField
|
||||
id="update_on_launch"
|
||||
name="update_on_launch"
|
||||
label={i18n._(t`Update on launch`)}
|
||||
tooltip={i18n._(t`Each time a job runs using this inventory,
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<CheckboxField
|
||||
id="update_on_launch"
|
||||
name="update_on_launch"
|
||||
label={i18n._(t`Update on launch`)}
|
||||
tooltip={i18n._(t`Each time a job runs using this inventory,
|
||||
refresh the inventory from the selected source before
|
||||
executing job tasks.`)}
|
||||
/>
|
||||
<CheckboxField
|
||||
id="update_on_project_update"
|
||||
name="update_on_project_update"
|
||||
label={i18n._(t`Update on project update`)}
|
||||
tooltip={i18n._(t`After every project update where the SCM revision
|
||||
changes, refresh the inventory from the selected source
|
||||
before executing job tasks. This is intended for static content,
|
||||
like the Ansible inventory .ini file format.`)}
|
||||
/>
|
||||
</FormCheckboxLayout>
|
||||
</FormGroup>
|
||||
</FormFullWidthLayout>
|
||||
{updateOnLaunchField.value && (
|
||||
<FormField
|
||||
id="cache-timeout"
|
||||
name="update_cache_timeout"
|
||||
type="number"
|
||||
min="0"
|
||||
max="2147483647"
|
||||
validate={minMaxValue(0, 2147483647, i18n)}
|
||||
label={i18n._(t`Cache timeout (seconds)`)}
|
||||
tooltip={i18n._(t`Time in seconds to consider an inventory sync
|
||||
/>
|
||||
{showProjectUpdate && (
|
||||
<CheckboxField
|
||||
id="update_on_project_update"
|
||||
name="update_on_project_update"
|
||||
label={i18n._(t`Update on project update`)}
|
||||
tooltip={i18n._(t`After every project update where the SCM revision
|
||||
changes, refresh the inventory from the selected source
|
||||
before executing job tasks. This is intended for static content,
|
||||
like the Ansible inventory .ini file format.`)}
|
||||
/>
|
||||
)}
|
||||
</FormCheckboxLayout>
|
||||
</FormGroup>
|
||||
</FormFullWidthLayout>
|
||||
{updateOnLaunchField.value && (
|
||||
<FormField
|
||||
id="cache-timeout"
|
||||
name="update_cache_timeout"
|
||||
type="number"
|
||||
min="0"
|
||||
max="2147483647"
|
||||
validate={minMaxValue(0, 2147483647, i18n)}
|
||||
label={i18n._(t`Cache timeout (seconds)`)}
|
||||
tooltip={i18n._(t`Time in seconds to consider an inventory sync
|
||||
to be current. During job runs and callbacks the task system will
|
||||
evaluate the timestamp of the latest sync. If it is older than
|
||||
Cache Timeout, it is not considered current, and a new
|
||||
inventory sync will be performed.`)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const InstanceFiltersField = withI18n()(({ i18n }) => {
|
||||
// Setting BrandName to a variable here is necessary to get the jest tests
|
||||
// passing. Attempting to use BrandName in the template literal results
|
||||
// in failing tests.
|
||||
const brandName = BrandName;
|
||||
return (
|
||||
<FormField
|
||||
id="instance-filters"
|
||||
label={i18n._(t`Instance filters`)}
|
||||
name="instance_filters"
|
||||
type="text"
|
||||
tooltip={
|
||||
<Trans>
|
||||
Provide a comma-separated list of filter expressions. Hosts are
|
||||
imported to {brandName} when <em>ANY</em> of the filters match.
|
||||
<br />
|
||||
<br />
|
||||
Limit to hosts having a tag:
|
||||
<br />
|
||||
tag-key=TowerManaged
|
||||
<br />
|
||||
<br />
|
||||
Limit to hosts using either key pair:
|
||||
<br />
|
||||
key-name=staging, key-name=production
|
||||
<br />
|
||||
<br />
|
||||
Limit to hosts where the Name tag begins with <em>test</em>:<br />
|
||||
tag:Name=test*
|
||||
<br />
|
||||
<br />
|
||||
View the
|
||||
<a
|
||||
href="http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html\"
|
||||
target="_blank\"
|
||||
>
|
||||
{' '}
|
||||
Describe Instances documentation{' '}
|
||||
</a>
|
||||
for a complete list of supported filters.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import {
|
||||
InstanceFiltersField,
|
||||
OptionsField,
|
||||
VerbosityField,
|
||||
} from './SharedFields';
|
||||
|
||||
const TowerSubForm = ({ i18n }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="tower"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<InstanceFiltersField />
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(TowerSubForm);
|
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import TowerSubForm from './TowerSubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
describe('<TowerSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<TowerSubForm />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Instance filters"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'tower',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import {
|
||||
InstanceFiltersField,
|
||||
GroupByField,
|
||||
OptionsField,
|
||||
SourceVarsField,
|
||||
VerbosityField,
|
||||
} from './SharedFields';
|
||||
|
||||
const VMwareSubForm = ({ i18n }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="vmware"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<InstanceFiltersField />
|
||||
<GroupByField isCreatable />
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
<SourceVarsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(VMwareSubForm);
|
@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import VMwareSubForm from './VMwareSubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
const mockSourceOptions = {
|
||||
actions: {
|
||||
POST: {
|
||||
source_regions: {
|
||||
gce_region_choices: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('<VMwareSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<VMwareSubForm sourceOptions={mockSourceOptions} />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Instance filters"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Only group by"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('VariablesField[label="Source variables"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'vmware',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import { OptionsField, VerbosityField } from './SharedFields';
|
||||
|
||||
const VirtualizationSubForm = ({ i18n }) => {
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="rhv"
|
||||
label={i18n._(t`Credential`)}
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
<VerbosityField />
|
||||
<OptionsField />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(VirtualizationSubForm);
|
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
|
||||
import VirtualizationSubForm from './VirtualizationSubForm';
|
||||
import { CredentialsAPI } from '../../../../api';
|
||||
|
||||
jest.mock('../../../../api/models/Credentials');
|
||||
|
||||
const initialValues = {
|
||||
credential: null,
|
||||
custom_virtualenv: '',
|
||||
group_by: '',
|
||||
instance_filters: '',
|
||||
overwrite: false,
|
||||
overwrite_vars: false,
|
||||
source_path: '',
|
||||
source_project: null,
|
||||
source_regions: '',
|
||||
source_script: null,
|
||||
source_vars: '---\n',
|
||||
update_cache_timeout: 0,
|
||||
update_on_launch: true,
|
||||
update_on_project_update: false,
|
||||
verbosity: 1,
|
||||
};
|
||||
|
||||
describe('<VirtualizationSubForm />', () => {
|
||||
let wrapper;
|
||||
CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 0, results: [] },
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik initialValues={initialValues}>
|
||||
<VirtualizationSubForm />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render subform fields', () => {
|
||||
expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should make expected api calls', () => {
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
|
||||
expect(CredentialsAPI.read).toHaveBeenCalledWith({
|
||||
credential_type__namespace: 'rhv',
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
});
|
@ -1 +1,11 @@
|
||||
export { default } from './SCMSubForm';
|
||||
export { default as AzureSubForm } from './AzureSubForm';
|
||||
export { default as CloudFormsSubForm } from './CloudFormsSubForm';
|
||||
export { default as CustomScriptSubForm } from './CustomScriptSubForm';
|
||||
export { default as EC2SubForm } from './EC2SubForm';
|
||||
export { default as GCESubForm } from './GCESubForm';
|
||||
export { default as OpenStackSubForm } from './OpenStackSubForm';
|
||||
export { default as SCMSubForm } from './SCMSubForm';
|
||||
export { default as SatelliteSubForm } from './SatelliteSubForm';
|
||||
export { default as TowerSubForm } from './TowerSubForm';
|
||||
export { default as VMwareSubForm } from './VMwareSubForm';
|
||||
export { default as VirtualizationSubForm } from './VirtualizationSubForm';
|
||||
|
@ -107,6 +107,12 @@ export const Inventory = shape({
|
||||
total_inventory_sources: number,
|
||||
});
|
||||
|
||||
export const InventoryScript = shape({
|
||||
description: string,
|
||||
id: number.isRequired,
|
||||
name: string,
|
||||
});
|
||||
|
||||
export const InstanceGroup = shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired,
|
||||
|
@ -17,3 +17,7 @@ export const toTitleCase = string => {
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
export const arrayToString = value => value.join(',');
|
||||
|
||||
export const stringToArray = value => value.split(',').filter(val => !!val);
|
||||
|
Loading…
Reference in New Issue
Block a user