Add an interface for new ee options

This commit is contained in:
Jake McDermott 2021-02-05 12:48:49 -05:00 committed by Shane McDonald
parent 31e7e10f30
commit 4ca33579a5
7 changed files with 186 additions and 8 deletions

View File

@ -1365,7 +1365,7 @@ class ExecutionEnvironmentSerializer(BaseSerializer):
class Meta:
model = ExecutionEnvironment
fields = ('*', 'organization', 'image', 'managed_by_tower', 'credential')
fields = ('*', 'organization', 'image', 'managed_by_tower', 'credential', 'container_options')
def get_related(self, obj):
res = super(ExecutionEnvironmentSerializer, self).get_related(obj)

View File

@ -2,7 +2,10 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import {
mountWithContexts,
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import { ExecutionEnvironmentsAPI } from '../../../api';
import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
@ -14,11 +17,30 @@ const mockMe = {
};
const executionEnvironmentData = {
name: 'Test EE',
credential: 4,
description: 'A simple EE',
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({
data: {
id: 42,
@ -61,6 +83,8 @@ describe('<ExecutionEnvironmentAdd/>', () => {
});
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');
expect(history.location.pathname).toEqual('/execution_environments');
});

View File

@ -13,11 +13,18 @@ import {
UserDateDetail,
} from '../../../components/DetailList';
import useRequest, { useDismissableError } from '../../../util/useRequest';
import { toTitleCase } from '../../../util/strings';
import { ExecutionEnvironmentsAPI } from '../../../api';
function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) {
const history = useHistory();
const { id, image, description } = executionEnvironment;
const {
id,
name,
image,
description,
container_options,
} = executionEnvironment;
const {
request: deleteExecutionEnvironment,
@ -35,12 +42,25 @@ function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) {
return (
<CardBody>
<DetailList>
<Detail
label={i18n._(t`Name`)}
value={name}
dataCy="execution-environment-detail-name"
/>
<Detail
label={i18n._(t`Image`)}
value={image}
dataCy="execution-environment-detail-image"
/>
<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 && (
<Detail
label={i18n._(t`Credential`)}

View File

@ -39,6 +39,7 @@ const executionEnvironment = {
last_name: '',
},
},
name: 'Default EE',
created: '2020-09-17T20:14:15.408782Z',
modified: '2020-09-17T20:14:15.408802Z',
description: 'Foo',

View File

@ -19,6 +19,8 @@ const executionEnvironmentData = {
credential: { id: 4 },
description: 'A simple EE',
image: 'https://registry.com/image/container',
container_options: 'one',
name: 'Test EE',
};
const updateExecutionEnvironmentData = {
@ -26,6 +28,24 @@ const updateExecutionEnvironmentData = {
description: 'Updated new description',
};
const mockOptions = {
data: {
actions: {
POST: {
container_options: {
choices: [
['one', 'One'],
['two', 'Two'],
['three', 'Three'],
],
},
},
},
},
};
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
describe('<ExecutionEnvironmentEdit/>', () => {
let wrapper;
let history;

View File

@ -1,18 +1,28 @@
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import { func, shape } from 'prop-types';
import { Formik, useField, useFormikContext } from 'formik';
import { withI18n } from '@lingui/react';
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 FormActionGroup from '../../../components/FormActionGroup';
import FormField, { FormSubmitError } from '../../../components/FormField';
import AnsibleSelect from '../../../components/AnsibleSelect';
import { FormColumnLayout } from '../../../components/FormLayout';
import { OrganizationLookup } from '../../../components/Lookup';
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
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 [organizationField, organizationMeta, organizationHelpers] = useField({
name: 'organization',
@ -37,8 +47,28 @@ function ExecutionEnvironmentFormFields({ i18n, me, executionEnvironment }) {
[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 (
<>
<FormField
id="execution-environment-name"
label={i18n._(t`Name`)}
name="name"
type="text"
validate={required(null, i18n)}
isRequired
/>
<FormField
id="execution-environment-image"
label={i18n._(t`Image name`)}
@ -50,6 +80,25 @@ function ExecutionEnvironmentFormFields({ i18n, me, executionEnvironment }) {
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
id="execution-environment-description"
label={i18n._(t`Description`)}
@ -90,8 +139,36 @@ function ExecutionEnvironmentForm({
me,
...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 = {
name: executionEnvironment.name || '',
image: executionEnvironment.image || '',
container_options: executionEnvironment?.container_options || '',
description: executionEnvironment.description || '',
credential: executionEnvironment.summary_fields?.credential || null,
organization: executionEnvironment.summary_fields?.organization || null,
@ -101,7 +178,12 @@ function ExecutionEnvironmentForm({
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<ExecutionEnvironmentFormFields me={me} {...rest} />
<ExecutionEnvironmentFormFields
me={me}
options={options}
executionEnvironment={executionEnvironment}
{...rest}
/>
{submitError && <FormSubmitError error={submitError} />}
<FormActionGroup
onCancel={onCancel}

View File

@ -1,6 +1,10 @@
import React from 'react';
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';
@ -13,7 +17,9 @@ const mockMe = {
const executionEnvironment = {
id: 16,
name: 'Test EE',
type: 'execution_environment',
container_options: 'one',
url: '/api/v2/execution_environments/16/',
related: {
created_by: '/api/v2/users/1/',
@ -38,6 +44,22 @@ const executionEnvironment = {
credential: 4,
};
const mockOptions = {
data: {
actions: {
POST: {
container_options: {
choices: [
['one', 'One'],
['two', 'Two'],
['three', 'Three'],
],
},
},
},
},
};
describe('<ExecutionEnvironmentForm/>', () => {
let wrapper;
let onCancel;
@ -46,16 +68,19 @@ describe('<ExecutionEnvironmentForm/>', () => {
beforeEach(async () => {
onCancel = jest.fn();
onSubmit = jest.fn();
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
await act(async () => {
wrapper = mountWithContexts(
<ExecutionEnvironmentForm
onCancel={onCancel}
onSubmit={onSubmit}
executionEnvironment={executionEnvironment}
options={mockOptions}
me={mockMe}
/>
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
});
afterEach(() => {
@ -83,6 +108,12 @@ describe('<ExecutionEnvironmentForm/>', () => {
test('should update form values', 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', {
target: {
value: 'https://registry.com/image/container2',