mirror of
https://github.com/ansible/awx.git
synced 2026-03-28 22:35:08 -02:30
Add an interface for new ee options
This commit is contained in:
committed by
Shane McDonald
parent
31e7e10f30
commit
4ca33579a5
@@ -1365,7 +1365,7 @@ class ExecutionEnvironmentSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ExecutionEnvironment
|
model = ExecutionEnvironment
|
||||||
fields = ('*', 'organization', 'image', 'managed_by_tower', 'credential')
|
fields = ('*', 'organization', 'image', 'managed_by_tower', 'credential', 'container_options')
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(ExecutionEnvironmentSerializer, self).get_related(obj)
|
res = super(ExecutionEnvironmentSerializer, self).get_related(obj)
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import React from 'react';
|
|||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
|
|
||||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
import {
|
||||||
|
mountWithContexts,
|
||||||
|
waitForElement,
|
||||||
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
import { ExecutionEnvironmentsAPI } from '../../../api';
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
|
import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
|
||||||
|
|
||||||
@@ -14,11 +17,30 @@ const mockMe = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const executionEnvironmentData = {
|
const executionEnvironmentData = {
|
||||||
|
name: 'Test EE',
|
||||||
credential: 4,
|
credential: 4,
|
||||||
description: 'A simple EE',
|
description: 'A simple EE',
|
||||||
image: 'https://registry.com/image/container',
|
image: 'https://registry.com/image/container',
|
||||||
|
container_options: 'one',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockOptions = {
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
POST: {
|
||||||
|
container_options: {
|
||||||
|
choices: [
|
||||||
|
['one', 'One'],
|
||||||
|
['two', 'Two'],
|
||||||
|
['three', 'Three'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
|
||||||
ExecutionEnvironmentsAPI.create.mockResolvedValue({
|
ExecutionEnvironmentsAPI.create.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
id: 42,
|
id: 42,
|
||||||
@@ -61,6 +83,8 @@ describe('<ExecutionEnvironmentAdd/>', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleCancel should return the user back to the execution environments list', async () => {
|
test('handleCancel should return the user back to the execution environments list', async () => {
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
|
||||||
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
|
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
|
||||||
expect(history.location.pathname).toEqual('/execution_environments');
|
expect(history.location.pathname).toEqual('/execution_environments');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,11 +13,18 @@ import {
|
|||||||
UserDateDetail,
|
UserDateDetail,
|
||||||
} from '../../../components/DetailList';
|
} from '../../../components/DetailList';
|
||||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||||
|
import { toTitleCase } from '../../../util/strings';
|
||||||
import { ExecutionEnvironmentsAPI } from '../../../api';
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
|
|
||||||
function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) {
|
function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { id, image, description } = executionEnvironment;
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
image,
|
||||||
|
description,
|
||||||
|
container_options,
|
||||||
|
} = executionEnvironment;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
request: deleteExecutionEnvironment,
|
request: deleteExecutionEnvironment,
|
||||||
@@ -35,12 +42,25 @@ function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) {
|
|||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<DetailList>
|
<DetailList>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
value={name}
|
||||||
|
dataCy="execution-environment-detail-name"
|
||||||
|
/>
|
||||||
<Detail
|
<Detail
|
||||||
label={i18n._(t`Image`)}
|
label={i18n._(t`Image`)}
|
||||||
value={image}
|
value={image}
|
||||||
dataCy="execution-environment-detail-image"
|
dataCy="execution-environment-detail-image"
|
||||||
/>
|
/>
|
||||||
<Detail label={i18n._(t`Description`)} value={description} />
|
<Detail label={i18n._(t`Description`)} value={description} />
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Container Options`)}
|
||||||
|
value={
|
||||||
|
container_options === ''
|
||||||
|
? i18n._(t`Missing`)
|
||||||
|
: toTitleCase(container_options)
|
||||||
|
}
|
||||||
|
/>
|
||||||
{executionEnvironment.summary_fields.credential && (
|
{executionEnvironment.summary_fields.credential && (
|
||||||
<Detail
|
<Detail
|
||||||
label={i18n._(t`Credential`)}
|
label={i18n._(t`Credential`)}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const executionEnvironment = {
|
|||||||
last_name: '',
|
last_name: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
name: 'Default EE',
|
||||||
created: '2020-09-17T20:14:15.408782Z',
|
created: '2020-09-17T20:14:15.408782Z',
|
||||||
modified: '2020-09-17T20:14:15.408802Z',
|
modified: '2020-09-17T20:14:15.408802Z',
|
||||||
description: 'Foo',
|
description: 'Foo',
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const executionEnvironmentData = {
|
|||||||
credential: { id: 4 },
|
credential: { id: 4 },
|
||||||
description: 'A simple EE',
|
description: 'A simple EE',
|
||||||
image: 'https://registry.com/image/container',
|
image: 'https://registry.com/image/container',
|
||||||
|
container_options: 'one',
|
||||||
|
name: 'Test EE',
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateExecutionEnvironmentData = {
|
const updateExecutionEnvironmentData = {
|
||||||
@@ -26,6 +28,24 @@ const updateExecutionEnvironmentData = {
|
|||||||
description: 'Updated new description',
|
description: 'Updated new description',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockOptions = {
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
POST: {
|
||||||
|
container_options: {
|
||||||
|
choices: [
|
||||||
|
['one', 'One'],
|
||||||
|
['two', 'Two'],
|
||||||
|
['three', 'Three'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
|
||||||
|
|
||||||
describe('<ExecutionEnvironmentEdit/>', () => {
|
describe('<ExecutionEnvironmentEdit/>', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let history;
|
let history;
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { func, shape } from 'prop-types';
|
import { func, shape } from 'prop-types';
|
||||||
import { Formik, useField, useFormikContext } from 'formik';
|
import { Formik, useField, useFormikContext } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form, FormGroup } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
import CredentialLookup from '../../../components/Lookup/CredentialLookup';
|
import CredentialLookup from '../../../components/Lookup/CredentialLookup';
|
||||||
import FormActionGroup from '../../../components/FormActionGroup';
|
import FormActionGroup from '../../../components/FormActionGroup';
|
||||||
import FormField, { FormSubmitError } from '../../../components/FormField';
|
import FormField, { FormSubmitError } from '../../../components/FormField';
|
||||||
|
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||||
import { FormColumnLayout } from '../../../components/FormLayout';
|
import { FormColumnLayout } from '../../../components/FormLayout';
|
||||||
import { OrganizationLookup } from '../../../components/Lookup';
|
import { OrganizationLookup } from '../../../components/Lookup';
|
||||||
|
import ContentError from '../../../components/ContentError';
|
||||||
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
import { required, url } from '../../../util/validators';
|
import { required, url } from '../../../util/validators';
|
||||||
|
import useRequest from '../../../util/useRequest';
|
||||||
|
|
||||||
function ExecutionEnvironmentFormFields({ i18n, me, executionEnvironment }) {
|
function ExecutionEnvironmentFormFields({
|
||||||
|
i18n,
|
||||||
|
me,
|
||||||
|
options,
|
||||||
|
executionEnvironment,
|
||||||
|
}) {
|
||||||
const [credentialField] = useField('credential');
|
const [credentialField] = useField('credential');
|
||||||
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
||||||
name: 'organization',
|
name: 'organization',
|
||||||
@@ -37,8 +47,28 @@ function ExecutionEnvironmentFormFields({ i18n, me, executionEnvironment }) {
|
|||||||
[setFieldValue]
|
[setFieldValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [
|
||||||
|
containerOptionsField,
|
||||||
|
containerOptionsMeta,
|
||||||
|
containerOptionsHelpers,
|
||||||
|
] = useField({
|
||||||
|
name: 'container_options',
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerPullChoices = options?.actions?.POST?.container_options?.choices.map(
|
||||||
|
([value, label]) => ({ value, label, key: value })
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<FormField
|
||||||
|
id="execution-environment-name"
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
validate={required(null, i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
id="execution-environment-image"
|
id="execution-environment-image"
|
||||||
label={i18n._(t`Image name`)}
|
label={i18n._(t`Image name`)}
|
||||||
@@ -50,6 +80,25 @@ function ExecutionEnvironmentFormFields({ i18n, me, executionEnvironment }) {
|
|||||||
t`The registry location where the container is stored.`
|
t`The registry location where the container is stored.`
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormGroup
|
||||||
|
fieldId="execution-environment-container-options"
|
||||||
|
helperTextInvalid={containerOptionsMeta.error}
|
||||||
|
validated={
|
||||||
|
!containerOptionsMeta.touched || !containerOptionsMeta.error
|
||||||
|
? 'default'
|
||||||
|
: 'error'
|
||||||
|
}
|
||||||
|
label={i18n._(t`Container Pull Option`)}
|
||||||
|
>
|
||||||
|
<AnsibleSelect
|
||||||
|
{...containerOptionsField}
|
||||||
|
id="container-pull-options"
|
||||||
|
data={containerPullChoices}
|
||||||
|
onChange={(event, value) => {
|
||||||
|
containerOptionsHelpers.setValue(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
<FormField
|
<FormField
|
||||||
id="execution-environment-description"
|
id="execution-environment-description"
|
||||||
label={i18n._(t`Description`)}
|
label={i18n._(t`Description`)}
|
||||||
@@ -90,8 +139,36 @@ function ExecutionEnvironmentForm({
|
|||||||
me,
|
me,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
request: fetchOptions,
|
||||||
|
result: options,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const res = await ExecutionEnvironmentsAPI.readOptions();
|
||||||
|
const { data } = res;
|
||||||
|
return data;
|
||||||
|
}, []),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchOptions();
|
||||||
|
}, [fetchOptions]);
|
||||||
|
|
||||||
|
if (isLoading || !options) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ContentError error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
|
name: executionEnvironment.name || '',
|
||||||
image: executionEnvironment.image || '',
|
image: executionEnvironment.image || '',
|
||||||
|
container_options: executionEnvironment?.container_options || '',
|
||||||
description: executionEnvironment.description || '',
|
description: executionEnvironment.description || '',
|
||||||
credential: executionEnvironment.summary_fields?.credential || null,
|
credential: executionEnvironment.summary_fields?.credential || null,
|
||||||
organization: executionEnvironment.summary_fields?.organization || null,
|
organization: executionEnvironment.summary_fields?.organization || null,
|
||||||
@@ -101,7 +178,12 @@ function ExecutionEnvironmentForm({
|
|||||||
{formik => (
|
{formik => (
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
<FormColumnLayout>
|
<FormColumnLayout>
|
||||||
<ExecutionEnvironmentFormFields me={me} {...rest} />
|
<ExecutionEnvironmentFormFields
|
||||||
|
me={me}
|
||||||
|
options={options}
|
||||||
|
executionEnvironment={executionEnvironment}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
{submitError && <FormSubmitError error={submitError} />}
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
import {
|
||||||
|
mountWithContexts,
|
||||||
|
waitForElement,
|
||||||
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
|
|
||||||
import ExecutionEnvironmentForm from './ExecutionEnvironmentForm';
|
import ExecutionEnvironmentForm from './ExecutionEnvironmentForm';
|
||||||
|
|
||||||
@@ -13,7 +17,9 @@ const mockMe = {
|
|||||||
|
|
||||||
const executionEnvironment = {
|
const executionEnvironment = {
|
||||||
id: 16,
|
id: 16,
|
||||||
|
name: 'Test EE',
|
||||||
type: 'execution_environment',
|
type: 'execution_environment',
|
||||||
|
container_options: 'one',
|
||||||
url: '/api/v2/execution_environments/16/',
|
url: '/api/v2/execution_environments/16/',
|
||||||
related: {
|
related: {
|
||||||
created_by: '/api/v2/users/1/',
|
created_by: '/api/v2/users/1/',
|
||||||
@@ -38,6 +44,22 @@ const executionEnvironment = {
|
|||||||
credential: 4,
|
credential: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockOptions = {
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
POST: {
|
||||||
|
container_options: {
|
||||||
|
choices: [
|
||||||
|
['one', 'One'],
|
||||||
|
['two', 'Two'],
|
||||||
|
['three', 'Three'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
describe('<ExecutionEnvironmentForm/>', () => {
|
describe('<ExecutionEnvironmentForm/>', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let onCancel;
|
let onCancel;
|
||||||
@@ -46,16 +68,19 @@ describe('<ExecutionEnvironmentForm/>', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
onCancel = jest.fn();
|
onCancel = jest.fn();
|
||||||
onSubmit = jest.fn();
|
onSubmit = jest.fn();
|
||||||
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<ExecutionEnvironmentForm
|
<ExecutionEnvironmentForm
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
executionEnvironment={executionEnvironment}
|
executionEnvironment={executionEnvironment}
|
||||||
|
options={mockOptions}
|
||||||
me={mockMe}
|
me={mockMe}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -83,6 +108,12 @@ describe('<ExecutionEnvironmentForm/>', () => {
|
|||||||
|
|
||||||
test('should update form values', async () => {
|
test('should update form values', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
wrapper.find('input#execution-environment-image').simulate('change', {
|
||||||
|
target: {
|
||||||
|
value: 'Updated EE Name',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
});
|
||||||
wrapper.find('input#execution-environment-image').simulate('change', {
|
wrapper.find('input#execution-environment-image').simulate('change', {
|
||||||
target: {
|
target: {
|
||||||
value: 'https://registry.com/image/container2',
|
value: 'https://registry.com/image/container2',
|
||||||
|
|||||||
Reference in New Issue
Block a user