Skip to content

Commit af6f441

Browse files
nixociojbradberry
authored andcommitted
Add feature to Add/Edit Execution Environments (ansible#8165)
* Add feature to Add/Edit Execution Environments Add feature to Add/Edit Execution Environments. Also, add key for `ExecutionEnvironmentsList`. See: ansible#7887 * Update registry credential label
1 parent f3b3172 commit af6f441

11 files changed

+570
-29
lines changed

awx/ui_next/src/routeConfig.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function getRouteConfig(i18n) {
128128
screen: Applications,
129129
},
130130
{
131-
title: i18n._(t`Execution environments`),
131+
title: i18n._(t`Execution Environments`),
132132
path: '/execution_environments',
133133
screen: ExecutionEnvironments,
134134
},
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,124 @@
1-
import React from 'react';
2-
import { Route, Redirect, Switch } from 'react-router-dom';
1+
import React, { useEffect, useCallback } from 'react';
2+
import {
3+
Link,
4+
Redirect,
5+
Route,
6+
Switch,
7+
useLocation,
8+
useParams,
9+
} from 'react-router-dom';
10+
import { withI18n } from '@lingui/react';
11+
import { t } from '@lingui/macro';
12+
import { Card, PageSection } from '@patternfly/react-core';
13+
import { CaretLeftIcon } from '@patternfly/react-icons';
14+
15+
import useRequest from '../../util/useRequest';
16+
import { ExecutionEnvironmentsAPI } from '../../api';
17+
import RoutedTabs from '../../components/RoutedTabs';
18+
import ContentError from '../../components/ContentError';
19+
import ContentLoading from '../../components/ContentLoading';
320

421
import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails';
522
import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
623

7-
function ExecutionEnvironment() {
24+
function ExecutionEnvironment({ i18n, setBreadcrumb }) {
25+
const { id } = useParams();
26+
const { pathname } = useLocation();
27+
28+
const {
29+
isLoading,
30+
error: contentError,
31+
request: fetchExecutionEnvironments,
32+
result: executionEnvironment,
33+
} = useRequest(
34+
useCallback(async () => {
35+
const { data } = await ExecutionEnvironmentsAPI.readDetail(id);
36+
return data;
37+
}, [id]),
38+
null
39+
);
40+
41+
useEffect(() => {
42+
fetchExecutionEnvironments();
43+
}, [fetchExecutionEnvironments, pathname]);
44+
45+
useEffect(() => {
46+
if (executionEnvironment) {
47+
setBreadcrumb(executionEnvironment);
48+
}
49+
}, [executionEnvironment, setBreadcrumb]);
50+
51+
const tabsArray = [
52+
{
53+
name: (
54+
<>
55+
<CaretLeftIcon />
56+
{i18n._(t`Back to execution environments`)}
57+
</>
58+
),
59+
link: '/execution_environments',
60+
id: 99,
61+
},
62+
{
63+
name: i18n._(t`Details`),
64+
link: `/execution_environments/${id}/details`,
65+
id: 0,
66+
},
67+
];
68+
69+
if (!isLoading && contentError) {
70+
return (
71+
<PageSection>
72+
<Card>
73+
<ContentError error={contentError}>
74+
{contentError.response?.status === 404 && (
75+
<span>
76+
{i18n._(t`Execution environment not found.`)}{' '}
77+
<Link to="/execution_environments">
78+
{i18n._(t`View all execution environments`)}
79+
</Link>
80+
</span>
81+
)}
82+
</ContentError>
83+
</Card>
84+
</PageSection>
85+
);
86+
}
87+
88+
let cardHeader = <RoutedTabs tabsArray={tabsArray} />;
89+
if (pathname.endsWith('edit')) {
90+
cardHeader = null;
91+
}
92+
893
return (
9-
<Switch>
10-
<Redirect
11-
from="/execution_environments/:id"
12-
to="/execution_environments/:id/details"
13-
exact
14-
/>
15-
<Route path="/execution_environments/:id/edit">
16-
<ExecutionEnvironmentEdit />
17-
</Route>
18-
<Route path="/execution_environments/:id/details">
19-
<ExecutionEnvironmentDetails />
20-
</Route>
21-
</Switch>
94+
<PageSection>
95+
<Card>
96+
{cardHeader}
97+
{isLoading && <ContentLoading />}
98+
{!isLoading && executionEnvironment && (
99+
<Switch>
100+
<Redirect
101+
from="/execution_environments/:id"
102+
to="/execution_environments/:id/details"
103+
exact
104+
/>
105+
{executionEnvironment && (
106+
<>
107+
<Route path="/execution_environments/:id/edit">
108+
<ExecutionEnvironmentEdit
109+
executionEnvironment={executionEnvironment}
110+
/>
111+
</Route>
112+
<Route path="/execution_environments/:id/details">
113+
<ExecutionEnvironmentDetails />
114+
</Route>
115+
</>
116+
)}
117+
</Switch>
118+
)}
119+
</Card>
120+
</PageSection>
22121
);
23122
}
24123

25-
export default ExecutionEnvironment;
124+
export default withI18n()(ExecutionEnvironment);

awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.jsx

+31-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,40 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import { Card, PageSection } from '@patternfly/react-core';
3+
import { useHistory } from 'react-router-dom';
4+
5+
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
6+
import { CardBody } from '../../../components/Card';
7+
import { ExecutionEnvironmentsAPI } from '../../../api';
38

49
function ExecutionEnvironmentAdd() {
10+
const history = useHistory();
11+
const [submitError, setSubmitError] = useState(null);
12+
13+
const handleSubmit = async values => {
14+
try {
15+
const { data: response } = await ExecutionEnvironmentsAPI.create({
16+
...values,
17+
credential: values?.credential?.id,
18+
});
19+
history.push(`/execution_environments/${response.id}/details`);
20+
} catch (error) {
21+
setSubmitError(error);
22+
}
23+
};
24+
25+
const handleCancel = () => {
26+
history.push(`/execution_environments`);
27+
};
528
return (
629
<PageSection>
730
<Card>
8-
<div>Add Execution Environments</div>
31+
<CardBody>
32+
<ExecutionEnvironmentForm
33+
onSubmit={handleSubmit}
34+
submitError={submitError}
35+
onCancel={handleCancel}
36+
/>
37+
</CardBody>
938
</Card>
1039
</PageSection>
1140
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import { act } from 'react-dom/test-utils';
3+
import { createMemoryHistory } from 'history';
4+
5+
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
6+
import { ExecutionEnvironmentsAPI } from '../../../api';
7+
import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
8+
9+
jest.mock('../../../api');
10+
11+
const executionEnvironmentData = {
12+
credential: 4,
13+
description: 'A simple EE',
14+
image: 'https://registry.com/image/container',
15+
};
16+
17+
ExecutionEnvironmentsAPI.create.mockResolvedValue({
18+
data: {
19+
id: 42,
20+
},
21+
});
22+
23+
describe('<ExecutionEnvironmentAdd/>', () => {
24+
let wrapper;
25+
let history;
26+
27+
beforeEach(async () => {
28+
history = createMemoryHistory({
29+
initialEntries: ['/execution_environments'],
30+
});
31+
await act(async () => {
32+
wrapper = mountWithContexts(<ExecutionEnvironmentAdd />, {
33+
context: { router: { history } },
34+
});
35+
});
36+
});
37+
38+
afterEach(() => {
39+
jest.clearAllMocks();
40+
wrapper.unmount();
41+
});
42+
43+
test('handleSubmit should call the api and redirect to details page', async () => {
44+
await act(async () => {
45+
wrapper.find('ExecutionEnvironmentForm').prop('onSubmit')({
46+
executionEnvironmentData,
47+
});
48+
});
49+
wrapper.update();
50+
expect(ExecutionEnvironmentsAPI.create).toHaveBeenCalledWith({
51+
executionEnvironmentData,
52+
});
53+
expect(history.location.pathname).toBe(
54+
'/execution_environments/42/details'
55+
);
56+
});
57+
58+
test('handleCancel should return the user back to the execution environments list', async () => {
59+
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
60+
expect(history.location.pathname).toEqual('/execution_environments');
61+
});
62+
63+
test('failed form submission should show an error message', async () => {
64+
const error = {
65+
response: {
66+
data: { detail: 'An error occurred' },
67+
},
68+
};
69+
ExecutionEnvironmentsAPI.create.mockImplementationOnce(() =>
70+
Promise.reject(error)
71+
);
72+
await act(async () => {
73+
wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')(
74+
executionEnvironmentData
75+
);
76+
});
77+
wrapper.update();
78+
expect(wrapper.find('FormSubmitError').length).toBe(1);
79+
});
80+
});

awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.jsx

+34-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,39 @@
1-
import React from 'react';
2-
import { Card, PageSection } from '@patternfly/react-core';
1+
import React, { useState } from 'react';
2+
import { useHistory } from 'react-router-dom';
33

4-
function ExecutionEnvironmentEdit() {
4+
import { CardBody } from '../../../components/Card';
5+
import { ExecutionEnvironmentsAPI } from '../../../api';
6+
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
7+
8+
function ExecutionEnvironmentEdit({ executionEnvironment }) {
9+
const history = useHistory();
10+
const [submitError, setSubmitError] = useState(null);
11+
const detailsUrl = `/execution_environments/${executionEnvironment.id}/details`;
12+
13+
const handleSubmit = async values => {
14+
try {
15+
await ExecutionEnvironmentsAPI.update(executionEnvironment.id, {
16+
...values,
17+
credential: values.credential ? values.credential.id : null,
18+
});
19+
history.push(detailsUrl);
20+
} catch (error) {
21+
setSubmitError(error);
22+
}
23+
};
24+
25+
const handleCancel = () => {
26+
history.push(detailsUrl);
27+
};
528
return (
6-
<PageSection>
7-
<Card>
8-
<div>Edit Execution environments</div>
9-
</Card>
10-
</PageSection>
29+
<CardBody>
30+
<ExecutionEnvironmentForm
31+
executionEnvironment={executionEnvironment}
32+
onSubmit={handleSubmit}
33+
submitError={submitError}
34+
onCancel={handleCancel}
35+
/>
36+
</CardBody>
1137
);
1238
}
1339

0 commit comments

Comments
 (0)