mirror of
https://github.com/ansible/awx.git
synced 2026-05-23 16:47:45 -02:30
Add EE to inventory sources
Add EE to inventory sources See: https://github.com/ansible/awx/issues/9189
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { string, func, bool } from 'prop-types';
|
import { string, func, bool } from 'prop-types';
|
||||||
import { withRouter, useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
import { FormGroup, Tooltip } from '@patternfly/react-core';
|
||||||
@@ -164,4 +164,4 @@ ExecutionEnvironmentLookup.defaultProps = {
|
|||||||
value: null,
|
value: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(withRouter(ExecutionEnvironmentLookup));
|
export default withI18n()(ExecutionEnvironmentLookup);
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { Card } from '@patternfly/react-core';
|
import { Card } from '@patternfly/react-core';
|
||||||
import { InventorySourcesAPI } from '../../../api';
|
import { InventorySourcesAPI } from '../../../api';
|
||||||
import useRequest from '../../../util/useRequest';
|
import useRequest from '../../../util/useRequest';
|
||||||
import { CardBody } from '../../../components/Card';
|
import { CardBody } from '../../../components/Card';
|
||||||
import InventorySourceForm from '../shared/InventorySourceForm';
|
import InventorySourceForm from '../shared/InventorySourceForm';
|
||||||
|
|
||||||
function InventorySourceAdd() {
|
function InventorySourceAdd({ inventory }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { id } = useParams();
|
const { id, organization } = inventory;
|
||||||
|
|
||||||
const { error, request, result } = useRequest(
|
const { error, request, result } = useRequest(
|
||||||
useCallback(async values => {
|
useCallback(async values => {
|
||||||
@@ -31,6 +31,7 @@ function InventorySourceAdd() {
|
|||||||
source_path,
|
source_path,
|
||||||
source_project,
|
source_project,
|
||||||
source_script,
|
source_script,
|
||||||
|
execution_environment,
|
||||||
...remainingForm
|
...remainingForm
|
||||||
} = form;
|
} = form;
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ function InventorySourceAdd() {
|
|||||||
credential: credential?.id || null,
|
credential: credential?.id || null,
|
||||||
inventory: id,
|
inventory: id,
|
||||||
source_script: source_script?.id || null,
|
source_script: source_script?.id || null,
|
||||||
|
execution_environment: execution_environment?.id || null,
|
||||||
...sourcePath,
|
...sourcePath,
|
||||||
...sourceProject,
|
...sourceProject,
|
||||||
...remainingForm,
|
...remainingForm,
|
||||||
@@ -63,6 +65,7 @@ function InventorySourceAdd() {
|
|||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitError={error}
|
submitError={error}
|
||||||
|
organizationId={organization}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
verbosity: 1,
|
verbosity: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockInventory = {
|
||||||
|
id: 111,
|
||||||
|
name: 'Foo',
|
||||||
|
organization: 2,
|
||||||
|
};
|
||||||
|
|
||||||
InventorySourcesAPI.readOptions.mockResolvedValue({
|
InventorySourcesAPI.readOptions.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
actions: {
|
actions: {
|
||||||
@@ -72,9 +78,12 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
custom_virtualenvs: ['venv/foo', 'venv/bar'],
|
custom_virtualenvs: ['venv/foo', 'venv/bar'],
|
||||||
};
|
};
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventorySourceAdd />, {
|
wrapper = mountWithContexts(
|
||||||
|
<InventorySourceAdd inventory={mockInventory} />,
|
||||||
|
{
|
||||||
context: { config },
|
context: { config },
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
expect(wrapper.find('FormGroup[label="Name"]')).toHaveLength(1);
|
expect(wrapper.find('FormGroup[label="Name"]')).toHaveLength(1);
|
||||||
@@ -88,9 +97,12 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
test('should navigate to inventory sources list when cancel is clicked', async () => {
|
test('should navigate to inventory sources list when cancel is clicked', async () => {
|
||||||
const history = createMemoryHistory({});
|
const history = createMemoryHistory({});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventorySourceAdd />, {
|
wrapper = mountWithContexts(
|
||||||
|
<InventorySourceAdd inventory={mockInventory} />,
|
||||||
|
{
|
||||||
context: { router: { history } },
|
context: { router: { history } },
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('InventorySourceForm').invoke('onCancel')();
|
wrapper.find('InventorySourceForm').invoke('onCancel')();
|
||||||
@@ -103,7 +115,9 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
test('should post to the api when submit is clicked', async () => {
|
test('should post to the api when submit is clicked', async () => {
|
||||||
InventorySourcesAPI.create.mockResolvedValueOnce({ data: {} });
|
InventorySourcesAPI.create.mockResolvedValueOnce({ data: {} });
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventorySourceAdd />);
|
wrapper = mountWithContexts(
|
||||||
|
<InventorySourceAdd inventory={mockInventory} />
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData);
|
wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData);
|
||||||
@@ -114,6 +128,7 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
credential: 222,
|
credential: 222,
|
||||||
source_project: 999,
|
source_project: 999,
|
||||||
source_script: null,
|
source_script: null,
|
||||||
|
execution_environment: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,9 +138,12 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
data: { id: 123, inventory: 111 },
|
data: { id: 123, inventory: 111 },
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventorySourceAdd />, {
|
wrapper = mountWithContexts(
|
||||||
|
<InventorySourceAdd inventory={mockInventory} />,
|
||||||
|
{
|
||||||
context: { router: { history } },
|
context: { router: { history } },
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData);
|
wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData);
|
||||||
@@ -143,7 +161,9 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
};
|
};
|
||||||
InventorySourcesAPI.create.mockImplementation(() => Promise.reject(error));
|
InventorySourcesAPI.create.mockImplementation(() => Promise.reject(error));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventorySourceAdd />);
|
wrapper = mountWithContexts(
|
||||||
|
<InventorySourceAdd inventory={mockInventory} />
|
||||||
|
);
|
||||||
});
|
});
|
||||||
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
|||||||
organization,
|
organization,
|
||||||
source_project,
|
source_project,
|
||||||
user_capabilities,
|
user_capabilities,
|
||||||
|
execution_environment,
|
||||||
},
|
},
|
||||||
} = inventorySource;
|
} = inventorySource;
|
||||||
const [deletionError, setDeletionError] = useState(false);
|
const [deletionError, setDeletionError] = useState(false);
|
||||||
@@ -214,6 +215,18 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{execution_environment?.name && (
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Execution Environment`)}
|
||||||
|
value={
|
||||||
|
<Link
|
||||||
|
to={`/execution_environments/${execution_environment.id}/details`}
|
||||||
|
>
|
||||||
|
{execution_environment.name}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{source === 'scm' ? (
|
{source === 'scm' ? (
|
||||||
<Detail
|
<Detail
|
||||||
label={i18n._(t`Inventory file`)}
|
label={i18n._(t`Inventory file`)}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { Card } from '@patternfly/react-core';
|
import { Card } from '@patternfly/react-core';
|
||||||
import { CardBody } from '../../../components/Card';
|
import { CardBody } from '../../../components/Card';
|
||||||
import useRequest from '../../../util/useRequest';
|
import useRequest from '../../../util/useRequest';
|
||||||
import { InventorySourcesAPI } from '../../../api';
|
import { InventorySourcesAPI } from '../../../api';
|
||||||
import InventorySourceForm from '../shared/InventorySourceForm';
|
import InventorySourceForm from '../shared/InventorySourceForm';
|
||||||
|
|
||||||
function InventorySourceEdit({ source }) {
|
function InventorySourceEdit({ source, inventory }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { id } = useParams();
|
const { id, organization } = inventory;
|
||||||
const detailsUrl = `/inventories/inventory/${id}/sources/${source.id}/details`;
|
const detailsUrl = `/inventories/inventory/${id}/sources/${source.id}/details`;
|
||||||
|
|
||||||
const { error, request, result } = useRequest(
|
const { error, request, result } = useRequest(
|
||||||
@@ -34,6 +34,7 @@ function InventorySourceEdit({ source }) {
|
|||||||
source_path,
|
source_path,
|
||||||
source_project,
|
source_project,
|
||||||
source_script,
|
source_script,
|
||||||
|
execution_environment,
|
||||||
...remainingForm
|
...remainingForm
|
||||||
} = form;
|
} = form;
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ function InventorySourceEdit({ source }) {
|
|||||||
credential: credential?.id || null,
|
credential: credential?.id || null,
|
||||||
inventory: id,
|
inventory: id,
|
||||||
source_script: source_script?.id || null,
|
source_script: source_script?.id || null,
|
||||||
|
execution_environment: execution_environment?.id || null,
|
||||||
...sourcePath,
|
...sourcePath,
|
||||||
...sourceProject,
|
...sourceProject,
|
||||||
...remainingForm,
|
...remainingForm,
|
||||||
@@ -67,6 +69,7 @@ function InventorySourceEdit({ source }) {
|
|||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitError={error}
|
submitError={error}
|
||||||
|
organizationId={organization}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jest.mock('react-router-dom', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('<InventorySourceAdd />', () => {
|
describe('<InventorySourceEdit />', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let history;
|
let history;
|
||||||
const mockInvSrc = {
|
const mockInvSrc = {
|
||||||
@@ -37,6 +37,11 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
update_on_project_update: false,
|
update_on_project_update: false,
|
||||||
verbosity: 1,
|
verbosity: 1,
|
||||||
};
|
};
|
||||||
|
const mockInventory = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Foo',
|
||||||
|
organization: 1,
|
||||||
|
};
|
||||||
InventorySourcesAPI.readOptions.mockResolvedValue({
|
InventorySourcesAPI.readOptions.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
actions: {
|
actions: {
|
||||||
@@ -89,9 +94,12 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
history = createMemoryHistory();
|
history = createMemoryHistory();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventorySourceEdit source={mockInvSrc} />, {
|
wrapper = mountWithContexts(
|
||||||
|
<InventorySourceEdit inventory={mockInventory} source={mockInvSrc} />,
|
||||||
|
{
|
||||||
context: { router: { history } },
|
context: { router: { history } },
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
@@ -133,7 +141,9 @@ describe('<InventorySourceAdd />', () => {
|
|||||||
};
|
};
|
||||||
InventorySourcesAPI.replace.mockImplementation(() => Promise.reject(error));
|
InventorySourcesAPI.replace.mockImplementation(() => Promise.reject(error));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventorySourceEdit source={mockInvSrc} />);
|
wrapper = mountWithContexts(
|
||||||
|
<InventorySourceEdit inventory={mockInventory} source={mockInvSrc} />
|
||||||
|
);
|
||||||
});
|
});
|
||||||
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function InventorySources({ inventory, setBreadcrumb }) {
|
|||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route key="add" path="/inventories/inventory/:id/sources/add">
|
<Route key="add" path="/inventories/inventory/:id/sources/add">
|
||||||
<InventorySourceAdd />
|
<InventorySourceAdd inventory={inventory} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/inventories/inventory/:id/sources/:sourceId">
|
<Route path="/inventories/inventory/:id/sources/:sourceId">
|
||||||
<Config>
|
<Config>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
VMwareSubForm,
|
VMwareSubForm,
|
||||||
VirtualizationSubForm,
|
VirtualizationSubForm,
|
||||||
} from './InventorySourceSubForms';
|
} from './InventorySourceSubForms';
|
||||||
|
import { ExecutionEnvironmentLookup } from '../../../components/Lookup';
|
||||||
|
|
||||||
const buildSourceChoiceOptions = options => {
|
const buildSourceChoiceOptions = options => {
|
||||||
const sourceChoices = options.actions.GET.source.choices.map(
|
const sourceChoices = options.actions.GET.source.choices.map(
|
||||||
@@ -39,7 +40,12 @@ const buildSourceChoiceOptions = options => {
|
|||||||
return sourceChoices.filter(({ key }) => key !== 'file');
|
return sourceChoices.filter(({ key }) => key !== 'file');
|
||||||
};
|
};
|
||||||
|
|
||||||
const InventorySourceFormFields = ({ source, sourceOptions, i18n }) => {
|
const InventorySourceFormFields = ({
|
||||||
|
source,
|
||||||
|
sourceOptions,
|
||||||
|
organizationId,
|
||||||
|
i18n,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
values,
|
values,
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -51,6 +57,13 @@ const InventorySourceFormFields = ({ source, sourceOptions, i18n }) => {
|
|||||||
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),
|
||||||
});
|
});
|
||||||
|
const [
|
||||||
|
executionEnvironmentField,
|
||||||
|
executionEnvironmentMeta,
|
||||||
|
executionEnvironmentHelpers,
|
||||||
|
] = useField({
|
||||||
|
name: 'execution_environment',
|
||||||
|
});
|
||||||
const { custom_virtualenvs } = useContext(ConfigContext);
|
const { custom_virtualenvs } = useContext(ConfigContext);
|
||||||
const [venvField] = useField('custom_virtualenv');
|
const [venvField] = useField('custom_virtualenv');
|
||||||
const defaultVenv = {
|
const defaultVenv = {
|
||||||
@@ -111,6 +124,17 @@ const InventorySourceFormFields = ({ source, sourceOptions, i18n }) => {
|
|||||||
name="description"
|
name="description"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
|
<ExecutionEnvironmentLookup
|
||||||
|
helperTextInvalid={executionEnvironmentMeta.error}
|
||||||
|
isValid={
|
||||||
|
!executionEnvironmentMeta.touched || !executionEnvironmentMeta.error
|
||||||
|
}
|
||||||
|
onBlur={() => executionEnvironmentHelpers.setTouched()}
|
||||||
|
value={executionEnvironmentField.value}
|
||||||
|
onChange={value => executionEnvironmentHelpers.setValue(value)}
|
||||||
|
globallyAvailable
|
||||||
|
organizationId={organizationId}
|
||||||
|
/>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="source"
|
fieldId="source"
|
||||||
helperTextInvalid={sourceMeta.error}
|
helperTextInvalid={sourceMeta.error}
|
||||||
@@ -244,6 +268,7 @@ const InventorySourceForm = ({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
source,
|
source,
|
||||||
submitError = null,
|
submitError = null,
|
||||||
|
organizationId,
|
||||||
}) => {
|
}) => {
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
credential: source?.summary_fields?.credential || null,
|
credential: source?.summary_fields?.credential || null,
|
||||||
@@ -264,6 +289,8 @@ const InventorySourceForm = ({
|
|||||||
enabled_var: source?.enabled_var || '',
|
enabled_var: source?.enabled_var || '',
|
||||||
enabled_value: source?.enabled_value || '',
|
enabled_value: source?.enabled_value || '',
|
||||||
host_filter: source?.host_filter || '',
|
host_filter: source?.host_filter || '',
|
||||||
|
execution_environment:
|
||||||
|
source?.summary_fields?.execution_environment || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -306,6 +333,7 @@ const InventorySourceForm = ({
|
|||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
source={source}
|
source={source}
|
||||||
sourceOptions={sourceOptions}
|
sourceOptions={sourceOptions}
|
||||||
|
organizationId={organizationId}
|
||||||
/>
|
/>
|
||||||
{submitError && <FormSubmitError error={submitError} />}
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ describe('<InventorySourceForm />', () => {
|
|||||||
expect(
|
expect(
|
||||||
wrapper.find('FormGroup[label="Ansible Environment"]')
|
wrapper.find('FormGroup[label="Ansible Environment"]')
|
||||||
).toHaveLength(1);
|
).toHaveLength(1);
|
||||||
|
expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display subform when source dropdown has a value', async () => {
|
test('should display subform when source dropdown has a value', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user