mirror of
https://github.com/ansible/awx.git
synced 2026-05-10 02:47:36 -02:30
Clear inv src subform values when source value changes
* Test that inv file field resets when project value changes * Remove project and inv file path from API request when type is SCM * Update checkbox tooltip to accept node proptypes * Format option field tooltips
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { string, func } from 'prop-types';
|
import { string, func, node } from 'prop-types';
|
||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
import { Checkbox, Tooltip } from '@patternfly/react-core';
|
import { Checkbox, Tooltip } from '@patternfly/react-core';
|
||||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
||||||
@@ -40,7 +40,7 @@ CheckboxField.propTypes = {
|
|||||||
name: string.isRequired,
|
name: string.isRequired,
|
||||||
label: string.isRequired,
|
label: string.isRequired,
|
||||||
validate: func,
|
validate: func,
|
||||||
tooltip: string,
|
tooltip: node,
|
||||||
};
|
};
|
||||||
CheckboxField.defaultProps = {
|
CheckboxField.defaultProps = {
|
||||||
validate: () => {},
|
validate: () => {},
|
||||||
|
|||||||
@@ -26,12 +26,21 @@ function InventorySourceAdd() {
|
|||||||
}, [result, history]);
|
}, [result, history]);
|
||||||
|
|
||||||
const handleSubmit = async form => {
|
const handleSubmit = async form => {
|
||||||
const { credential, source_project, ...remainingForm } = form;
|
const { credential, source_path, source_project, ...remainingForm } = form;
|
||||||
|
|
||||||
|
const sourcePath = {};
|
||||||
|
const sourceProject = {};
|
||||||
|
if (form.source === 'scm') {
|
||||||
|
sourcePath.source_path =
|
||||||
|
source_path === '/ (project root)' ? '' : source_path;
|
||||||
|
sourceProject.source_project = source_project.id;
|
||||||
|
}
|
||||||
|
|
||||||
await request({
|
await request({
|
||||||
credential: credential?.id || null,
|
credential: credential?.id || null,
|
||||||
source_project: source_project?.id || null,
|
|
||||||
inventory: id,
|
inventory: id,
|
||||||
|
...sourcePath,
|
||||||
|
...sourceProject,
|
||||||
...remainingForm,
|
...remainingForm,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useCallback, useContext } from 'react';
|
import React, { useEffect, useCallback, useContext } from 'react';
|
||||||
import { Formik, useField } from 'formik';
|
import { Formik, useField, useFormikContext } from 'formik';
|
||||||
|
import { func, shape } from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { InventorySourcesAPI } from '@api';
|
import { InventorySourcesAPI } from '@api';
|
||||||
@@ -21,7 +22,8 @@ import { FormColumnLayout, SubFormLayout } from '@components/FormLayout';
|
|||||||
import SCMSubForm from './InventorySourceSubForms';
|
import SCMSubForm from './InventorySourceSubForms';
|
||||||
|
|
||||||
const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||||
const [sourceField, sourceMeta, sourceHelpers] = useField({
|
const { values, initialValues, resetForm } = useFormikContext();
|
||||||
|
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),
|
||||||
});
|
});
|
||||||
@@ -33,6 +35,18 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
|||||||
key: 'default',
|
key: 'default',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetSubFormFields = sourceType => {
|
||||||
|
resetForm({
|
||||||
|
values: {
|
||||||
|
...initialValues,
|
||||||
|
name: values.name,
|
||||||
|
description: values.description,
|
||||||
|
custom_virtualenv: values.custom_virtualenv,
|
||||||
|
source: sourceType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -69,7 +83,7 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
|||||||
...sourceOptions,
|
...sourceOptions,
|
||||||
]}
|
]}
|
||||||
onChange={(event, value) => {
|
onChange={(event, value) => {
|
||||||
sourceHelpers.setValue(value);
|
resetSubFormFields(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
@@ -197,4 +211,14 @@ const InventorySourceForm = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
InventorySourceForm.propTypes = {
|
||||||
|
onCancel: func.isRequired,
|
||||||
|
onSubmit: func.isRequired,
|
||||||
|
submitError: shape({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
InventorySourceForm.defaultProps = {
|
||||||
|
submitError: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default withI18n()(InventorySourceForm);
|
export default withI18n()(InventorySourceForm);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe('<InventorySourceForm />', () => {
|
|||||||
data: { count: 0, results: [] },
|
data: { count: 0, results: [] },
|
||||||
});
|
});
|
||||||
ProjectsAPI.readInventories.mockResolvedValue({
|
ProjectsAPI.readInventories.mockResolvedValue({
|
||||||
data: ['foo'],
|
data: ['foo', 'bar'],
|
||||||
});
|
});
|
||||||
InventorySourcesAPI.readOptions.mockResolvedValue({
|
InventorySourcesAPI.readOptions.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ const SCMSubForm = ({ i18n }) => {
|
|||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async projectId => {
|
useCallback(async projectId => {
|
||||||
const { data } = await ProjectsAPI.readInventories(projectId);
|
const { data } = await ProjectsAPI.readInventories(projectId);
|
||||||
data.push('/ (project root)');
|
return [...data, '/ (project root)'];
|
||||||
return data;
|
|
||||||
}, []),
|
}, []),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { act } from 'react-dom/test-utils';
|
|||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import SCMSubForm from './SCMSubForm';
|
import SCMSubForm from './SCMSubForm';
|
||||||
import { ProjectsAPI } from '@api';
|
import { ProjectsAPI, CredentialsAPI } from '@api';
|
||||||
|
|
||||||
jest.mock('@api/models/Projects');
|
jest.mock('@api/models/Projects');
|
||||||
|
jest.mock('@api/models/Credentials');
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
credential: null,
|
credential: null,
|
||||||
@@ -23,9 +24,26 @@ const initialValues = {
|
|||||||
|
|
||||||
describe('<SCMSubForm />', () => {
|
describe('<SCMSubForm />', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
CredentialsAPI.read.mockResolvedValue({
|
||||||
|
data: { count: 0, results: [] },
|
||||||
|
});
|
||||||
ProjectsAPI.readInventories.mockResolvedValue({
|
ProjectsAPI.readInventories.mockResolvedValue({
|
||||||
data: ['foo'],
|
data: ['foo', 'bar'],
|
||||||
|
});
|
||||||
|
ProjectsAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 2,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'mock proj one',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'mock proj two',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -59,10 +77,34 @@ describe('<SCMSubForm />', () => {
|
|||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('ProjectLookup').invoke('onChange')({
|
wrapper.find('ProjectLookup').invoke('onChange')({
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'mock proj',
|
name: 'mock proj two',
|
||||||
});
|
});
|
||||||
wrapper.find('ProjectLookup').invoke('onBlur')();
|
wrapper.find('ProjectLookup').invoke('onBlur')();
|
||||||
});
|
});
|
||||||
expect(ProjectsAPI.readInventories).toHaveBeenCalledWith(2);
|
expect(ProjectsAPI.readInventories).toHaveBeenCalledWith(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('changing source project should reset source path dropdown', async () => {
|
||||||
|
expect(wrapper.find('AnsibleSelect#source_path').prop('value')).toEqual('');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await wrapper.find('AnsibleSelect#source_path').prop('onChange')(
|
||||||
|
null,
|
||||||
|
'bar'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('AnsibleSelect#source_path').prop('value')).toEqual(
|
||||||
|
'bar'
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ProjectLookup').invoke('onChange')({
|
||||||
|
id: 1,
|
||||||
|
name: 'mock proj one',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('AnsibleSelect#source_path').prop('value')).toEqual('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,24 +63,39 @@ export const OptionsField = withI18n()(({ i18n }) => {
|
|||||||
id="overwrite"
|
id="overwrite"
|
||||||
name="overwrite"
|
name="overwrite"
|
||||||
label={i18n._(t`Overwrite`)}
|
label={i18n._(t`Overwrite`)}
|
||||||
tooltip={i18n._(t`If checked, any hosts and groups that were
|
tooltip={
|
||||||
previously present on the external source but are now removed
|
<>
|
||||||
will be removed from the Tower inventory. Hosts and groups
|
{i18n._(t`If checked, any hosts and groups that were
|
||||||
that were not managed by the inventory source will be promoted
|
previously present on the external source but are now removed
|
||||||
to the next manually created group or if there is no manually
|
will be removed from the Tower inventory. Hosts and groups
|
||||||
created group to promote them into, they will be left in the "all"
|
that were not managed by the inventory source will be promoted
|
||||||
default group for the inventory. When not checked, local child
|
to the next manually created group or if there is no manually
|
||||||
hosts and groups not found on the external source will remain
|
created group to promote them into, they will be left in the "all"
|
||||||
untouched by the inventory update process.`)}
|
default group for the inventory.`)}
|
||||||
|
<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
|
<CheckboxField
|
||||||
id="overwrite_vars"
|
id="overwrite_vars"
|
||||||
name="overwrite_vars"
|
name="overwrite_vars"
|
||||||
label={i18n._(t`Overwrite variables`)}
|
label={i18n._(t`Overwrite variables`)}
|
||||||
tooltip={i18n._(t`If checked, all variables for child groups and hosts
|
tooltip={
|
||||||
will be removed and replaced by those found on the external source.
|
<>
|
||||||
When not checked, a merge will be performed, combining local
|
{i18n._(t`If checked, all variables for child groups
|
||||||
variables with those found on the external source.`)}
|
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,
|
||||||
|
combining local variables with those found on the
|
||||||
|
external source.`)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
id="update_on_launch"
|
id="update_on_launch"
|
||||||
|
|||||||
Reference in New Issue
Block a user