mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 01:47:35 -02:30
Add feature to Add/Edit Execution Environments (#8165)
* Add feature to Add/Edit Execution Environments Add feature to Add/Edit Execution Environments. Also, add key for `ExecutionEnvironmentsList`. See: https://github.com/ansible/awx/issues/7887 * Update registry credential label
This commit is contained in:
@@ -140,7 +140,7 @@ function getRouteConfig(i18n) {
|
|||||||
screen: Applications,
|
screen: Applications,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n._(t`Execution environments`),
|
title: i18n._(t`Execution Environments`),
|
||||||
path: '/execution_environments',
|
path: '/execution_environments',
|
||||||
screen: ExecutionEnvironments,
|
screen: ExecutionEnvironments,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,25 +1,124 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import { Route, Redirect, Switch } from 'react-router-dom';
|
import {
|
||||||
|
Link,
|
||||||
|
Redirect,
|
||||||
|
Route,
|
||||||
|
Switch,
|
||||||
|
useLocation,
|
||||||
|
useParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
import { CaretLeftIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
import useRequest from '../../util/useRequest';
|
||||||
|
import { ExecutionEnvironmentsAPI } from '../../api';
|
||||||
|
import RoutedTabs from '../../components/RoutedTabs';
|
||||||
|
import ContentError from '../../components/ContentError';
|
||||||
|
import ContentLoading from '../../components/ContentLoading';
|
||||||
|
|
||||||
import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails';
|
import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails';
|
||||||
import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
|
import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
|
||||||
|
|
||||||
function ExecutionEnvironment() {
|
function ExecutionEnvironment({ i18n, setBreadcrumb }) {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
error: contentError,
|
||||||
|
request: fetchExecutionEnvironments,
|
||||||
|
result: executionEnvironment,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const { data } = await ExecutionEnvironmentsAPI.readDetail(id);
|
||||||
|
return data;
|
||||||
|
}, [id]),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchExecutionEnvironments();
|
||||||
|
}, [fetchExecutionEnvironments, pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (executionEnvironment) {
|
||||||
|
setBreadcrumb(executionEnvironment);
|
||||||
|
}
|
||||||
|
}, [executionEnvironment, setBreadcrumb]);
|
||||||
|
|
||||||
|
const tabsArray = [
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<>
|
||||||
|
<CaretLeftIcon />
|
||||||
|
{i18n._(t`Back to execution environments`)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
link: '/execution_environments',
|
||||||
|
id: 99,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Details`),
|
||||||
|
link: `/execution_environments/${id}/details`,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isLoading && contentError) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<ContentError error={contentError}>
|
||||||
|
{contentError.response?.status === 404 && (
|
||||||
|
<span>
|
||||||
|
{i18n._(t`Execution environment not found.`)}{' '}
|
||||||
|
<Link to="/execution_environments">
|
||||||
|
{i18n._(t`View all execution environments`)}
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</ContentError>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardHeader = <RoutedTabs tabsArray={tabsArray} />;
|
||||||
|
if (pathname.endsWith('edit')) {
|
||||||
|
cardHeader = null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<PageSection>
|
||||||
<Redirect
|
<Card>
|
||||||
from="/execution_environments/:id"
|
{cardHeader}
|
||||||
to="/execution_environments/:id/details"
|
{isLoading && <ContentLoading />}
|
||||||
exact
|
{!isLoading && executionEnvironment && (
|
||||||
/>
|
<Switch>
|
||||||
<Route path="/execution_environments/:id/edit">
|
<Redirect
|
||||||
<ExecutionEnvironmentEdit />
|
from="/execution_environments/:id"
|
||||||
</Route>
|
to="/execution_environments/:id/details"
|
||||||
<Route path="/execution_environments/:id/details">
|
exact
|
||||||
<ExecutionEnvironmentDetails />
|
/>
|
||||||
</Route>
|
{executionEnvironment && (
|
||||||
</Switch>
|
<>
|
||||||
|
<Route path="/execution_environments/:id/edit">
|
||||||
|
<ExecutionEnvironmentEdit
|
||||||
|
executionEnvironment={executionEnvironment}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path="/execution_environments/:id/details">
|
||||||
|
<ExecutionEnvironmentDetails />
|
||||||
|
</Route>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Switch>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ExecutionEnvironment;
|
export default withI18n()(ExecutionEnvironment);
|
||||||
|
|||||||
@@ -1,11 +1,40 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
|
||||||
|
import { CardBody } from '../../../components/Card';
|
||||||
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
|
|
||||||
function ExecutionEnvironmentAdd() {
|
function ExecutionEnvironmentAdd() {
|
||||||
|
const history = useHistory();
|
||||||
|
const [submitError, setSubmitError] = useState(null);
|
||||||
|
|
||||||
|
const handleSubmit = async values => {
|
||||||
|
try {
|
||||||
|
const { data: response } = await ExecutionEnvironmentsAPI.create({
|
||||||
|
...values,
|
||||||
|
credential: values?.credential?.id,
|
||||||
|
});
|
||||||
|
history.push(`/execution_environments/${response.id}/details`);
|
||||||
|
} catch (error) {
|
||||||
|
setSubmitError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
history.push(`/execution_environments`);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<div>Add Execution Environments</div>
|
<CardBody>
|
||||||
|
<ExecutionEnvironmentForm
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
submitError={submitError}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
|
import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const executionEnvironmentData = {
|
||||||
|
credential: 4,
|
||||||
|
description: 'A simple EE',
|
||||||
|
image: 'https://registry.com/image/container',
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecutionEnvironmentsAPI.create.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 42,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<ExecutionEnvironmentAdd/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
let history;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
history = createMemoryHistory({
|
||||||
|
initialEntries: ['/execution_environments'],
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<ExecutionEnvironmentAdd />, {
|
||||||
|
context: { router: { history } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleSubmit should call the api and redirect to details page', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ExecutionEnvironmentForm').prop('onSubmit')({
|
||||||
|
executionEnvironmentData,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(ExecutionEnvironmentsAPI.create).toHaveBeenCalledWith({
|
||||||
|
executionEnvironmentData,
|
||||||
|
});
|
||||||
|
expect(history.location.pathname).toBe(
|
||||||
|
'/execution_environments/42/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleCancel should return the user back to the execution environments list', async () => {
|
||||||
|
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
|
||||||
|
expect(history.location.pathname).toEqual('/execution_environments');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failed form submission should show an error message', async () => {
|
||||||
|
const error = {
|
||||||
|
response: {
|
||||||
|
data: { detail: 'An error occurred' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ExecutionEnvironmentsAPI.create.mockImplementationOnce(() =>
|
||||||
|
Promise.reject(error)
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')(
|
||||||
|
executionEnvironmentData
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('FormSubmitError').length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,13 +1,39 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
function ExecutionEnvironmentEdit() {
|
import { CardBody } from '../../../components/Card';
|
||||||
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
|
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
|
||||||
|
|
||||||
|
function ExecutionEnvironmentEdit({ executionEnvironment }) {
|
||||||
|
const history = useHistory();
|
||||||
|
const [submitError, setSubmitError] = useState(null);
|
||||||
|
const detailsUrl = `/execution_environments/${executionEnvironment.id}/details`;
|
||||||
|
|
||||||
|
const handleSubmit = async values => {
|
||||||
|
try {
|
||||||
|
await ExecutionEnvironmentsAPI.update(executionEnvironment.id, {
|
||||||
|
...values,
|
||||||
|
credential: values.credential ? values.credential.id : null,
|
||||||
|
});
|
||||||
|
history.push(detailsUrl);
|
||||||
|
} catch (error) {
|
||||||
|
setSubmitError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
history.push(detailsUrl);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<CardBody>
|
||||||
<Card>
|
<ExecutionEnvironmentForm
|
||||||
<div>Edit Execution environments</div>
|
executionEnvironment={executionEnvironment}
|
||||||
</Card>
|
onSubmit={handleSubmit}
|
||||||
</PageSection>
|
submitError={submitError}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
|
|
||||||
|
import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const executionEnvironmentData = {
|
||||||
|
id: 42,
|
||||||
|
credential: { id: 4 },
|
||||||
|
description: 'A simple EE',
|
||||||
|
image: 'https://registry.com/image/container',
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateExecutionEnvironmentData = {
|
||||||
|
image: 'https://registry.com/image/container2',
|
||||||
|
description: 'Updated new description',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<ExecutionEnvironmentEdit/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
let history;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
history = createMemoryHistory();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ExecutionEnvironmentEdit
|
||||||
|
executionEnvironment={executionEnvironmentData}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: { router: { history } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleSubmit should call the api and redirect to details page', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')(
|
||||||
|
updateExecutionEnvironmentData
|
||||||
|
);
|
||||||
|
wrapper.update();
|
||||||
|
expect(ExecutionEnvironmentsAPI.update).toHaveBeenCalledWith(42, {
|
||||||
|
...updateExecutionEnvironmentData,
|
||||||
|
credential: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(history.location.pathname).toEqual(
|
||||||
|
'/execution_environments/42/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should navigate to execution environments details when cancel is clicked', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
|
||||||
|
});
|
||||||
|
expect(history.location.pathname).toEqual(
|
||||||
|
'/execution_environments/42/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should navigate to execution environments detail after successful submission', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')({
|
||||||
|
updateExecutionEnvironmentData,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
||||||
|
expect(history.location.pathname).toEqual(
|
||||||
|
'/execution_environments/42/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failed form submission should show an error message', async () => {
|
||||||
|
const error = {
|
||||||
|
response: {
|
||||||
|
data: { detail: 'An error occurred' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ExecutionEnvironmentsAPI.update.mockImplementationOnce(() =>
|
||||||
|
Promise.reject(error)
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')(
|
||||||
|
updateExecutionEnvironmentData
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('FormSubmitError').length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -19,12 +19,14 @@ const executionEnvironments = {
|
|||||||
image: 'https://registry.com/r/image/manifest',
|
image: 'https://registry.com/r/image/manifest',
|
||||||
organization: null,
|
organization: null,
|
||||||
credential: null,
|
credential: null,
|
||||||
|
url: '/api/v2/execution_environments/1/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
image: 'https://registry.com/r/image2/manifest',
|
image: 'https://registry.com/r/image2/manifest',
|
||||||
organization: null,
|
organization: null,
|
||||||
credential: null,
|
credential: null,
|
||||||
|
url: '/api/v2/execution_environments/2/',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
count: 2,
|
count: 2,
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ function ExecutionEnvironmentList({ i18n }) {
|
|||||||
)}
|
)}
|
||||||
renderItem={executionEnvironment => (
|
renderItem={executionEnvironment => (
|
||||||
<ExecutionEnvironmentsListItem
|
<ExecutionEnvironmentsListItem
|
||||||
|
key={executionEnvironment.id}
|
||||||
executionEnvironment={executionEnvironment}
|
executionEnvironment={executionEnvironment}
|
||||||
detailUrl={`${match.url}/${executionEnvironment.id}/details`}
|
detailUrl={`${match.url}/${executionEnvironment.id}/details`}
|
||||||
onSelect={() => handleSelect(executionEnvironment)}
|
onSelect={() => handleSelect(executionEnvironment)}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function ExecutionEnvironments({ i18n }) {
|
|||||||
setBreadcrumbConfig({
|
setBreadcrumbConfig({
|
||||||
'/execution_environments': i18n._(t`Execution environments`),
|
'/execution_environments': i18n._(t`Execution environments`),
|
||||||
'/execution_environments/add': i18n._(t`Create Execution environments`),
|
'/execution_environments/add': i18n._(t`Create Execution environments`),
|
||||||
[`/execution_environments/${executionEnvironments.id}`]: `${executionEnvironments.name}`,
|
[`/execution_environments/${executionEnvironments.id}`]: `${executionEnvironments.image}`,
|
||||||
[`/execution_environments/${executionEnvironments.id}/edit`]: i18n._(
|
[`/execution_environments/${executionEnvironments.id}/edit`]: i18n._(
|
||||||
t`Edit details`
|
t`Edit details`
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { func, shape } from 'prop-types';
|
||||||
|
import { Formik, useField } from 'formik';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { Form } from '@patternfly/react-core';
|
||||||
|
import FormField, { FormSubmitError } from '../../../components/FormField';
|
||||||
|
import FormActionGroup from '../../../components/FormActionGroup';
|
||||||
|
import CredentialLookup from '../../../components/Lookup/CredentialLookup';
|
||||||
|
import { url } from '../../../util/validators';
|
||||||
|
import { FormColumnLayout } from '../../../components/FormLayout';
|
||||||
|
|
||||||
|
function ExecutionEnvironmentFormFields({ i18n }) {
|
||||||
|
const [credentialField, , credentialHelpers] = useField('credential');
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
id="execution-environment-image"
|
||||||
|
label={i18n._(t`Image`)}
|
||||||
|
name="image"
|
||||||
|
type="text"
|
||||||
|
validate={url(i18n)}
|
||||||
|
isRequired
|
||||||
|
tooltip={i18n._(
|
||||||
|
t`The registry location where the container is stored.`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="execution-environment-description"
|
||||||
|
label={i18n._(t`Description`)}
|
||||||
|
name="description"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<CredentialLookup
|
||||||
|
label={i18n._(t`Registry Credential`)}
|
||||||
|
onChange={value => credentialHelpers.setValue(value)}
|
||||||
|
value={credentialField.value || null}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExecutionEnvironmentForm({
|
||||||
|
executionEnvironment = {},
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
submitError,
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
const initialValues = {
|
||||||
|
image: executionEnvironment.image || '',
|
||||||
|
description: executionEnvironment.description || '',
|
||||||
|
credential: executionEnvironment?.summary_fields?.credential || null,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Formik initialValues={initialValues} onSubmit={values => onSubmit(values)}>
|
||||||
|
{formik => (
|
||||||
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
|
<FormColumnLayout>
|
||||||
|
<ExecutionEnvironmentFormFields {...rest} />
|
||||||
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
|
<FormActionGroup
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
/>
|
||||||
|
</FormColumnLayout>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionEnvironmentForm.propTypes = {
|
||||||
|
executionEnvironment: shape({}),
|
||||||
|
onCancel: func.isRequired,
|
||||||
|
onSubmit: func.isRequired,
|
||||||
|
submitError: shape({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecutionEnvironmentForm.defaultProps = {
|
||||||
|
executionEnvironment: {},
|
||||||
|
submitError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(ExecutionEnvironmentForm);
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
|
import ExecutionEnvironmentForm from './ExecutionEnvironmentForm';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const executionEnvironment = {
|
||||||
|
id: 16,
|
||||||
|
type: 'execution_environment',
|
||||||
|
url: '/api/v2/execution_environments/16/',
|
||||||
|
related: {
|
||||||
|
created_by: '/api/v2/users/1/',
|
||||||
|
modified_by: '/api/v2/users/1/',
|
||||||
|
activity_stream: '/api/v2/execution_environments/16/activity_stream/',
|
||||||
|
unified_job_templates:
|
||||||
|
'/api/v2/execution_environments/16/unified_job_templates/',
|
||||||
|
credential: '/api/v2/credentials/4/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
credential: {
|
||||||
|
id: 4,
|
||||||
|
name: 'Container Registry',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-09-17T16:06:57.346128Z',
|
||||||
|
modified: '2020-09-17T16:06:57.346147Z',
|
||||||
|
description: 'A simple EE',
|
||||||
|
organization: null,
|
||||||
|
image: 'https://registry.com/image/container',
|
||||||
|
managed_by_tower: false,
|
||||||
|
credential: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<ExecutionEnvironmentForm/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
let onCancel;
|
||||||
|
let onSubmit;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
onCancel = jest.fn();
|
||||||
|
onSubmit = jest.fn();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ExecutionEnvironmentForm
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
executionEnvironment={executionEnvironment}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Initially renders successfully', () => {
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display form fields properly', () => {
|
||||||
|
expect(wrapper.find('FormGroup[label="Image"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('CredentialLookup').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call onSubmit when form submitted', async () => {
|
||||||
|
expect(onSubmit).not.toHaveBeenCalled();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||||
|
});
|
||||||
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update form values', () => {
|
||||||
|
act(() => {
|
||||||
|
wrapper.find('input#execution-environment-image').simulate('change', {
|
||||||
|
target: {
|
||||||
|
value: 'https://registry.com/image/container2',
|
||||||
|
name: 'image',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wrapper
|
||||||
|
.find('input#execution-environment-description')
|
||||||
|
.simulate('change', {
|
||||||
|
target: { value: 'New description', name: 'description' },
|
||||||
|
});
|
||||||
|
wrapper.find('CredentialLookup').invoke('onBlur')();
|
||||||
|
wrapper.find('CredentialLookup').invoke('onChange')({
|
||||||
|
id: 99,
|
||||||
|
name: 'credential',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#execution-environment-image').prop('value')
|
||||||
|
).toEqual('https://registry.com/image/container2');
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#execution-environment-description').prop('value')
|
||||||
|
).toEqual('New description');
|
||||||
|
expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
|
||||||
|
id: 99,
|
||||||
|
name: 'credential',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call handleCancel when Cancel button is clicked', async () => {
|
||||||
|
expect(onCancel).not.toHaveBeenCalled();
|
||||||
|
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||||
|
expect(onCancel).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user