mirror of
https://github.com/ansible/awx.git
synced 2026-03-03 01:38:50 -03:30
Merge pull request #7287 from marshmalien/6899-inv-src-subform
Hook up all inventory source subforms Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -126,7 +126,7 @@ SUMMARIZABLE_FK_FIELDS = {
|
|||||||
'current_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
|
'current_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
|
||||||
'inventory_source': ('source', 'last_updated', 'status'),
|
'inventory_source': ('source', 'last_updated', 'status'),
|
||||||
'custom_inventory_script': DEFAULT_SUMMARY_FIELDS,
|
'custom_inventory_script': DEFAULT_SUMMARY_FIELDS,
|
||||||
'source_script': ('name', 'description'),
|
'source_script': DEFAULT_SUMMARY_FIELDS,
|
||||||
'role': ('id', 'role_field'),
|
'role': ('id', 'role_field'),
|
||||||
'notification_template': DEFAULT_SUMMARY_FIELDS,
|
'notification_template': DEFAULT_SUMMARY_FIELDS,
|
||||||
'instance_group': ('id', 'name', 'controller_id', 'is_containerized'),
|
'instance_group': ('id', 'name', 'controller_id', 'is_containerized'),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Groups from './models/Groups';
|
|||||||
import Hosts from './models/Hosts';
|
import Hosts from './models/Hosts';
|
||||||
import InstanceGroups from './models/InstanceGroups';
|
import InstanceGroups from './models/InstanceGroups';
|
||||||
import Inventories from './models/Inventories';
|
import Inventories from './models/Inventories';
|
||||||
|
import InventoryScripts from './models/InventoryScripts';
|
||||||
import InventorySources from './models/InventorySources';
|
import InventorySources from './models/InventorySources';
|
||||||
import InventoryUpdates from './models/InventoryUpdates';
|
import InventoryUpdates from './models/InventoryUpdates';
|
||||||
import JobTemplates from './models/JobTemplates';
|
import JobTemplates from './models/JobTemplates';
|
||||||
@@ -41,6 +42,7 @@ const GroupsAPI = new Groups();
|
|||||||
const HostsAPI = new Hosts();
|
const HostsAPI = new Hosts();
|
||||||
const InstanceGroupsAPI = new InstanceGroups();
|
const InstanceGroupsAPI = new InstanceGroups();
|
||||||
const InventoriesAPI = new Inventories();
|
const InventoriesAPI = new Inventories();
|
||||||
|
const InventoryScriptsAPI = new InventoryScripts();
|
||||||
const InventorySourcesAPI = new InventorySources();
|
const InventorySourcesAPI = new InventorySources();
|
||||||
const InventoryUpdatesAPI = new InventoryUpdates();
|
const InventoryUpdatesAPI = new InventoryUpdates();
|
||||||
const JobTemplatesAPI = new JobTemplates();
|
const JobTemplatesAPI = new JobTemplates();
|
||||||
@@ -75,6 +77,7 @@ export {
|
|||||||
HostsAPI,
|
HostsAPI,
|
||||||
InstanceGroupsAPI,
|
InstanceGroupsAPI,
|
||||||
InventoriesAPI,
|
InventoriesAPI,
|
||||||
|
InventoryScriptsAPI,
|
||||||
InventorySourcesAPI,
|
InventorySourcesAPI,
|
||||||
InventoryUpdatesAPI,
|
InventoryUpdatesAPI,
|
||||||
JobTemplatesAPI,
|
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,
|
required,
|
||||||
credentialTypeId,
|
credentialTypeId,
|
||||||
credentialTypeKind,
|
credentialTypeKind,
|
||||||
|
credentialTypeNamespace,
|
||||||
value,
|
value,
|
||||||
history,
|
history,
|
||||||
i18n,
|
i18n,
|
||||||
@@ -46,15 +47,27 @@ function CredentialLookup({
|
|||||||
const typeKindParams = credentialTypeKind
|
const typeKindParams = credentialTypeKind
|
||||||
? { credential_type__kind: credentialTypeKind }
|
? { credential_type__kind: credentialTypeKind }
|
||||||
: {};
|
: {};
|
||||||
|
const typeNamespaceParams = credentialTypeNamespace
|
||||||
|
? { credential_type__namespace: credentialTypeNamespace }
|
||||||
|
: {};
|
||||||
|
|
||||||
const { data } = await CredentialsAPI.read(
|
const { data } = await CredentialsAPI.read(
|
||||||
mergeParams(params, { ...typeIdParams, ...typeKindParams })
|
mergeParams(params, {
|
||||||
|
...typeIdParams,
|
||||||
|
...typeKindParams,
|
||||||
|
...typeNamespaceParams,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
count: data.count,
|
count: data.count,
|
||||||
credentials: data.results,
|
credentials: data.results,
|
||||||
};
|
};
|
||||||
}, [credentialTypeId, credentialTypeKind, history.location.search]),
|
}, [
|
||||||
|
credentialTypeId,
|
||||||
|
credentialTypeKind,
|
||||||
|
credentialTypeNamespace,
|
||||||
|
history.location.search,
|
||||||
|
]),
|
||||||
{
|
{
|
||||||
count: 0,
|
count: 0,
|
||||||
credentials: [],
|
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 React, { useState } from 'react';
|
||||||
import { func, string } from 'prop-types';
|
import { func, string } from 'prop-types';
|
||||||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
||||||
|
import { arrayToString, stringToArray } from '../../util/strings';
|
||||||
function arrayToString(tags) {
|
|
||||||
return tags.join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringToArray(value) {
|
|
||||||
return value.split(',').filter(val => !!val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TagMultiSelect({ onChange, value }) {
|
function TagMultiSelect({ onChange, value }) {
|
||||||
const selections = stringToArray(value);
|
const selections = stringToArray(value);
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ function InventorySourceAdd() {
|
|||||||
}, [result, history]);
|
}, [result, history]);
|
||||||
|
|
||||||
const handleSubmit = async form => {
|
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 sourcePath = {};
|
||||||
const sourceProject = {};
|
const sourceProject = {};
|
||||||
@@ -39,6 +45,7 @@ function InventorySourceAdd() {
|
|||||||
await request({
|
await request({
|
||||||
credential: credential?.id || null,
|
credential: credential?.id || null,
|
||||||
inventory: id,
|
inventory: id,
|
||||||
|
source_script: source_script?.id || null,
|
||||||
...sourcePath,
|
...sourcePath,
|
||||||
...sourceProject,
|
...sourceProject,
|
||||||
...remainingForm,
|
...remainingForm,
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
...invSourceData,
|
...invSourceData,
|
||||||
credential: 222,
|
credential: 222,
|
||||||
source_project: 999,
|
source_project: 999,
|
||||||
|
source_script: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ function InventorySourceEdit({ source }) {
|
|||||||
}, [result, detailsUrl, history]);
|
}, [result, detailsUrl, history]);
|
||||||
|
|
||||||
const handleSubmit = async form => {
|
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 sourcePath = {};
|
||||||
const sourceProject = {};
|
const sourceProject = {};
|
||||||
@@ -38,9 +44,11 @@ function InventorySourceEdit({ source }) {
|
|||||||
source_path === '/ (project root)' ? '' : source_path;
|
source_path === '/ (project root)' ? '' : source_path;
|
||||||
sourceProject.source_project = source_project.id;
|
sourceProject.source_project = source_project.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await request({
|
await request({
|
||||||
credential: credential?.id || null,
|
credential: credential?.id || null,
|
||||||
inventory: id,
|
inventory: id,
|
||||||
|
source_script: source_script?.id || null,
|
||||||
...sourcePath,
|
...sourcePath,
|
||||||
...sourceProject,
|
...sourceProject,
|
||||||
...remainingForm,
|
...remainingForm,
|
||||||
|
|||||||
@@ -22,10 +22,34 @@ import {
|
|||||||
SubFormLayout,
|
SubFormLayout,
|
||||||
} from '../../../components/FormLayout';
|
} 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 InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||||
const { values, initialValues, resetForm } = useFormikContext();
|
const {
|
||||||
|
values,
|
||||||
|
initialValues,
|
||||||
|
resetForm,
|
||||||
|
setFieldValue,
|
||||||
|
} = useFormikContext();
|
||||||
const [sourceField, sourceMeta] = useField({
|
const [sourceField, sourceMeta] = useField({
|
||||||
name: 'source',
|
name: 'source',
|
||||||
validate: required(i18n._(t`Set a value for this field`), i18n),
|
validate: required(i18n._(t`Set a value for this field`), i18n),
|
||||||
@@ -39,15 +63,38 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resetSubFormFields = sourceType => {
|
const resetSubFormFields = sourceType => {
|
||||||
resetForm({
|
if (sourceType === initialValues.source) {
|
||||||
values: {
|
resetForm({
|
||||||
...initialValues,
|
values: {
|
||||||
name: values.name,
|
...initialValues,
|
||||||
description: values.description,
|
name: values.name,
|
||||||
custom_virtualenv: values.custom_virtualenv,
|
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: 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 (
|
return (
|
||||||
@@ -83,7 +130,7 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
|||||||
label: i18n._(t`Choose a source`),
|
label: i18n._(t`Choose a source`),
|
||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
},
|
},
|
||||||
...sourceOptions,
|
...buildSourceChoiceOptions(sourceOptions),
|
||||||
]}
|
]}
|
||||||
onChange={(event, value) => {
|
onChange={(event, value) => {
|
||||||
resetSubFormFields(value);
|
resetSubFormFields(value);
|
||||||
@@ -112,14 +159,23 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sourceField.value !== '' && (
|
{sourceField.value !== '' && (
|
||||||
<SubFormLayout>
|
<SubFormLayout>
|
||||||
<Title size="md">{i18n._(t`Source details`)}</Title>
|
<Title size="md">{i18n._(t`Source details`)}</Title>
|
||||||
<FormColumnLayout>
|
<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 />,
|
scm: <SCMSubForm />,
|
||||||
|
tower: <TowerSubForm />,
|
||||||
|
vmware: <VMwareSubForm sourceOptions={sourceOptions} />,
|
||||||
}[sourceField.value]
|
}[sourceField.value]
|
||||||
}
|
}
|
||||||
</FormColumnLayout>
|
</FormColumnLayout>
|
||||||
@@ -140,12 +196,16 @@ const InventorySourceForm = ({
|
|||||||
credential: source?.summary_fields?.credential || null,
|
credential: source?.summary_fields?.credential || null,
|
||||||
custom_virtualenv: source?.custom_virtualenv || '',
|
custom_virtualenv: source?.custom_virtualenv || '',
|
||||||
description: source?.description || '',
|
description: source?.description || '',
|
||||||
|
group_by: source?.group_by || '',
|
||||||
|
instance_filters: source?.instance_filters || '',
|
||||||
name: source?.name || '',
|
name: source?.name || '',
|
||||||
overwrite: source?.overwrite || false,
|
overwrite: source?.overwrite || false,
|
||||||
overwrite_vars: source?.overwrite_vars || false,
|
overwrite_vars: source?.overwrite_vars || false,
|
||||||
source: source?.source || '',
|
source: source?.source || '',
|
||||||
source_path: source?.source_path === '' ? '/ (project root)' : '',
|
source_path: source?.source_path === '' ? '/ (project root)' : '',
|
||||||
source_project: source?.summary_fields?.source_project || null,
|
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',
|
source_vars: source?.source_vars || '---\n',
|
||||||
update_cache_timeout: source?.update_cache_timeout || 0,
|
update_cache_timeout: source?.update_cache_timeout || 0,
|
||||||
update_on_launch: source?.update_on_launch || false,
|
update_on_launch: source?.update_on_launch || false,
|
||||||
@@ -161,17 +221,7 @@ const InventorySourceForm = ({
|
|||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const { data } = await InventorySourcesAPI.readOptions();
|
const { data } = await InventorySourcesAPI.readOptions();
|
||||||
const sourceChoices = Object.assign(
|
return data;
|
||||||
...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],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, []),
|
}, []),
|
||||||
null
|
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>
|
</FormGroup>
|
||||||
<VerbosityField />
|
<VerbosityField />
|
||||||
<OptionsField />
|
<OptionsField showProjectUpdate />
|
||||||
<SourceVarsField />
|
<SourceVarsField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,13 +11,17 @@ jest.mock('../../../../api/models/Credentials');
|
|||||||
const initialValues = {
|
const initialValues = {
|
||||||
credential: null,
|
credential: null,
|
||||||
custom_virtualenv: '',
|
custom_virtualenv: '',
|
||||||
|
group_by: '',
|
||||||
|
instance_filters: '',
|
||||||
overwrite: false,
|
overwrite: false,
|
||||||
overwrite_vars: false,
|
overwrite_vars: false,
|
||||||
source_path: '',
|
source_path: '',
|
||||||
source_project: null,
|
source_project: null,
|
||||||
|
source_regions: '',
|
||||||
|
source_script: null,
|
||||||
source_vars: '---\n',
|
source_vars: '---\n',
|
||||||
update_cache_timeout: 0,
|
update_cache_timeout: 0,
|
||||||
update_on_launch: false,
|
update_on_launch: true,
|
||||||
update_on_project_update: false,
|
update_on_project_update: false,
|
||||||
verbosity: 1,
|
verbosity: 1,
|
||||||
};
|
};
|
||||||
@@ -68,7 +72,10 @@ describe('<SCMSubForm />', () => {
|
|||||||
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
|
||||||
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
|
||||||
expect(
|
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);
|
).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 { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t, Trans } from '@lingui/macro';
|
||||||
import { useField } from 'formik';
|
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 { minMaxValue } from '../../../../util/validators';
|
||||||
|
import { BrandName } from '../../../../variables';
|
||||||
import AnsibleSelect from '../../../../components/AnsibleSelect';
|
import AnsibleSelect from '../../../../components/AnsibleSelect';
|
||||||
import { VariablesField } from '../../../../components/CodeMirrorInput';
|
import { VariablesField } from '../../../../components/CodeMirrorInput';
|
||||||
import FormField, {
|
import FormField, {
|
||||||
@@ -20,11 +27,197 @@ export const SourceVarsField = withI18n()(({ i18n }) => (
|
|||||||
<VariablesField
|
<VariablesField
|
||||||
id="source_vars"
|
id="source_vars"
|
||||||
name="source_vars"
|
name="source_vars"
|
||||||
label={i18n._(t`Environment variables`)}
|
label={i18n._(t`Source variables`)}
|
||||||
/>
|
/>
|
||||||
</FormFullWidthLayout>
|
</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 }) => {
|
export const VerbosityField = withI18n()(({ i18n }) => {
|
||||||
const [field, meta, helpers] = useField('verbosity');
|
const [field, meta, helpers] = useField('verbosity');
|
||||||
const isValid = !(meta.touched && meta.error);
|
const isValid = !(meta.touched && meta.error);
|
||||||
@@ -53,98 +246,148 @@ export const VerbosityField = withI18n()(({ i18n }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const OptionsField = withI18n()(({ i18n }) => {
|
export const OptionsField = withI18n()(
|
||||||
const [updateOnLaunchField] = useField('update_on_launch');
|
({ i18n, showProjectUpdate = false }) => {
|
||||||
const [, , updateCacheTimeoutHelper] = useField('update_cache_timeout');
|
const [updateOnLaunchField] = useField('update_on_launch');
|
||||||
|
const [, , updateCacheTimeoutHelper] = useField('update_cache_timeout');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!updateOnLaunchField.value) {
|
if (!updateOnLaunchField.value) {
|
||||||
updateCacheTimeoutHelper.setValue(0);
|
updateCacheTimeoutHelper.setValue(0);
|
||||||
}
|
}
|
||||||
}, [updateOnLaunchField.value]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [updateOnLaunchField.value]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormFullWidthLayout>
|
<FormFullWidthLayout>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="option-checkboxes"
|
fieldId="option-checkboxes"
|
||||||
label={i18n._(t`Update options`)}
|
label={i18n._(t`Update options`)}
|
||||||
>
|
>
|
||||||
<FormCheckboxLayout>
|
<FormCheckboxLayout>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
id="overwrite"
|
id="overwrite"
|
||||||
name="overwrite"
|
name="overwrite"
|
||||||
label={i18n._(t`Overwrite`)}
|
label={i18n._(t`Overwrite`)}
|
||||||
tooltip={
|
tooltip={
|
||||||
<>
|
<>
|
||||||
{i18n._(t`If checked, any hosts and groups that were
|
{i18n._(t`If checked, any hosts and groups that were
|
||||||
previously present on the external source but are now removed
|
previously present on the external source but are now removed
|
||||||
will be removed from the Tower inventory. Hosts and groups
|
will be removed from the Tower inventory. Hosts and groups
|
||||||
that were not managed by the inventory source will be promoted
|
that were not managed by the inventory source will be promoted
|
||||||
to the next manually created group or if there is no manually
|
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"
|
created group to promote them into, they will be left in the "all"
|
||||||
default group for the inventory.`)}
|
default group for the inventory.`)}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{i18n._(t`When not checked, local child
|
{i18n._(t`When not checked, local child
|
||||||
hosts and groups not found on the external source will remain
|
hosts and groups not found on the external source will remain
|
||||||
untouched by the inventory update process.`)}
|
untouched by the inventory update process.`)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
id="overwrite_vars"
|
id="overwrite_vars"
|
||||||
name="overwrite_vars"
|
name="overwrite_vars"
|
||||||
label={i18n._(t`Overwrite variables`)}
|
label={i18n._(t`Overwrite variables`)}
|
||||||
tooltip={
|
tooltip={
|
||||||
<>
|
<>
|
||||||
{i18n._(t`If checked, all variables for child groups
|
{i18n._(t`If checked, all variables for child groups
|
||||||
and hosts will be removed and replaced by those found
|
and hosts will be removed and replaced by those found
|
||||||
on the external source.`)}
|
on the external source.`)}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{i18n._(t`When not checked, a merge will be performed,
|
{i18n._(t`When not checked, a merge will be performed,
|
||||||
combining local variables with those found on the
|
combining local variables with those found on the
|
||||||
external source.`)}
|
external source.`)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
id="update_on_launch"
|
id="update_on_launch"
|
||||||
name="update_on_launch"
|
name="update_on_launch"
|
||||||
label={i18n._(t`Update on launch`)}
|
label={i18n._(t`Update on launch`)}
|
||||||
tooltip={i18n._(t`Each time a job runs using this inventory,
|
tooltip={i18n._(t`Each time a job runs using this inventory,
|
||||||
refresh the inventory from the selected source before
|
refresh the inventory from the selected source before
|
||||||
executing job tasks.`)}
|
executing job tasks.`)}
|
||||||
/>
|
/>
|
||||||
<CheckboxField
|
{showProjectUpdate && (
|
||||||
id="update_on_project_update"
|
<CheckboxField
|
||||||
name="update_on_project_update"
|
id="update_on_project_update"
|
||||||
label={i18n._(t`Update on project update`)}
|
name="update_on_project_update"
|
||||||
tooltip={i18n._(t`After every project update where the SCM revision
|
label={i18n._(t`Update on project update`)}
|
||||||
changes, refresh the inventory from the selected source
|
tooltip={i18n._(t`After every project update where the SCM revision
|
||||||
before executing job tasks. This is intended for static content,
|
changes, refresh the inventory from the selected source
|
||||||
like the Ansible inventory .ini file format.`)}
|
before executing job tasks. This is intended for static content,
|
||||||
/>
|
like the Ansible inventory .ini file format.`)}
|
||||||
</FormCheckboxLayout>
|
/>
|
||||||
</FormGroup>
|
)}
|
||||||
</FormFullWidthLayout>
|
</FormCheckboxLayout>
|
||||||
{updateOnLaunchField.value && (
|
</FormGroup>
|
||||||
<FormField
|
</FormFullWidthLayout>
|
||||||
id="cache-timeout"
|
{updateOnLaunchField.value && (
|
||||||
name="update_cache_timeout"
|
<FormField
|
||||||
type="number"
|
id="cache-timeout"
|
||||||
min="0"
|
name="update_cache_timeout"
|
||||||
max="2147483647"
|
type="number"
|
||||||
validate={minMaxValue(0, 2147483647, i18n)}
|
min="0"
|
||||||
label={i18n._(t`Cache timeout (seconds)`)}
|
max="2147483647"
|
||||||
tooltip={i18n._(t`Time in seconds to consider an inventory sync
|
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
|
to be current. During job runs and callbacks the task system will
|
||||||
evaluate the timestamp of the latest sync. If it is older than
|
evaluate the timestamp of the latest sync. If it is older than
|
||||||
Cache Timeout, it is not considered current, and a new
|
Cache Timeout, it is not considered current, and a new
|
||||||
inventory sync will be performed.`)}
|
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,
|
total_inventory_sources: number,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const InventoryScript = shape({
|
||||||
|
description: string,
|
||||||
|
id: number.isRequired,
|
||||||
|
name: string,
|
||||||
|
});
|
||||||
|
|
||||||
export const InstanceGroup = shape({
|
export const InstanceGroup = shape({
|
||||||
id: number.isRequired,
|
id: number.isRequired,
|
||||||
name: string.isRequired,
|
name: string.isRequired,
|
||||||
|
|||||||
@@ -17,3 +17,7 @@ export const toTitleCase = string => {
|
|||||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
.join(' ');
|
.join(' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const arrayToString = value => value.join(',');
|
||||||
|
|
||||||
|
export const stringToArray = value => value.split(',').filter(val => !!val);
|
||||||
|
|||||||
Reference in New Issue
Block a user