diff --git a/awx/ui_next/src/components/FormField/FormSubmitError.jsx b/awx/ui_next/src/components/FormField/FormSubmitError.jsx
index 7b6edc6cb3..714ca4119d 100644
--- a/awx/ui_next/src/components/FormField/FormSubmitError.jsx
+++ b/awx/ui_next/src/components/FormField/FormSubmitError.jsx
@@ -22,6 +22,8 @@ function FormSubmitError({ error }) {
Object.values(error.response.data).forEach(value => {
if (Array.isArray(value)) {
messages = messages.concat(value);
+ } else {
+ messages.push(value);
}
});
setErrorMessage(messages.length > 0 ? messages : null);
diff --git a/awx/ui_next/src/screens/CredentialType/CredentialType.jsx b/awx/ui_next/src/screens/CredentialType/CredentialType.jsx
index 121020b569..17dc115a44 100644
--- a/awx/ui_next/src/screens/CredentialType/CredentialType.jsx
+++ b/awx/ui_next/src/screens/CredentialType/CredentialType.jsx
@@ -65,11 +65,6 @@ function CredentialType({ i18n, setBreadcrumb }) {
},
];
- let cardHeader = ;
- if (pathname.endsWith('edit')) {
- cardHeader = null;
- }
-
if (!isLoading && contentError) {
return (
@@ -89,6 +84,11 @@ function CredentialType({ i18n, setBreadcrumb }) {
);
}
+ let cardHeader = ;
+ if (pathname.endsWith('edit')) {
+ cardHeader = null;
+ }
+
return (
@@ -104,7 +104,7 @@ function CredentialType({ i18n, setBreadcrumb }) {
{credentialType && (
<>
-
+
diff --git a/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx b/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx
index d622c9d6f1..1b99c16867 100644
--- a/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx
+++ b/awx/ui_next/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.jsx
@@ -70,7 +70,7 @@ function CredentialTypeDetails({ credentialType, i18n }) {
diff --git a/awx/ui_next/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.jsx b/awx/ui_next/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.jsx
index 9ccbf329d0..c188e97e97 100644
--- a/awx/ui_next/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.jsx
+++ b/awx/ui_next/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.jsx
@@ -1,11 +1,41 @@
-import React from 'react';
-import { Card, PageSection } from '@patternfly/react-core';
+import React, { useState } from 'react';
+import { useHistory } from 'react-router-dom';
-function CredentialTypeEdit() {
+import { CardBody } from '../../../components/Card';
+import { CredentialTypesAPI } from '../../../api';
+import CredentialTypeForm from '../shared/CredentialTypeForm';
+import { parseVariableField } from '../../../util/yaml';
+
+function CredentialTypeEdit({ credentialType }) {
+ const history = useHistory();
+ const [submitError, setSubmitError] = useState(null);
+ const detailsUrl = `/credential_types/${credentialType.id}/details`;
+
+ const handleSubmit = async values => {
+ try {
+ await CredentialTypesAPI.update(credentialType.id, {
+ ...values,
+ injectors: parseVariableField(values.injectors),
+ inputs: parseVariableField(values.inputs),
+ });
+ history.push(detailsUrl);
+ } catch (error) {
+ setSubmitError(error);
+ }
+ };
+
+ const handleCancel = () => {
+ history.push(detailsUrl);
+ };
return (
-
- Credential Type Edit
-
+
+
+
);
}
diff --git a/awx/ui_next/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.test.jsx b/awx/ui_next/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.test.jsx
new file mode 100644
index 0000000000..f4800e81b4
--- /dev/null
+++ b/awx/ui_next/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.test.jsx
@@ -0,0 +1,140 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { createMemoryHistory } from 'history';
+
+import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
+import { CredentialTypesAPI } from '../../../api';
+
+import CredentialTypeEdit from './CredentialTypeEdit';
+
+jest.mock('../../../api');
+
+const credentialTypeData = {
+ id: 42,
+ name: 'Foo',
+ description: 'New credential',
+ kind: 'cloud',
+ inputs: JSON.stringify({
+ fields: [
+ {
+ id: 'username',
+ type: 'string',
+ label: 'Jenkins username',
+ },
+ {
+ id: 'password',
+ type: 'string',
+ label: 'Jenkins password',
+ secret: true,
+ },
+ ],
+ required: ['username', 'password'],
+ }),
+ injectors: JSON.stringify({
+ extra_vars: {
+ Jenkins_password: '{{ password }}',
+ Jenkins_username: '{{ username }}',
+ },
+ }),
+ summary_fields: {
+ created_by: {
+ id: 1,
+ username: 'admin',
+ first_name: '',
+ last_name: '',
+ },
+ modified_by: {
+ id: 1,
+ username: 'admin',
+ first_name: '',
+ last_name: '',
+ },
+ user_capabilities: {
+ edit: true,
+ delete: true,
+ },
+ },
+ created: '2020-06-25T16:52:36.127008Z',
+ modified: '2020-06-25T16:52:36.127022Z',
+};
+
+const updateCredentialTypeData = {
+ name: 'Bar',
+ description: 'Updated new Credential Type',
+ injectors: credentialTypeData.injectors,
+ inputs: credentialTypeData.inputs,
+};
+
+describe('', () => {
+ let wrapper;
+ let history;
+
+ beforeAll(async () => {
+ history = createMemoryHistory();
+ await act(async () => {
+ wrapper = mountWithContexts(
+ ,
+ {
+ 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('CredentialTypeForm').invoke('onSubmit')(
+ updateCredentialTypeData
+ );
+ wrapper.update();
+ expect(CredentialTypesAPI.update).toHaveBeenCalledWith(42, {
+ ...updateCredentialTypeData,
+ injectors: JSON.parse(credentialTypeData.injectors),
+ inputs: JSON.parse(credentialTypeData.inputs),
+ });
+ });
+ });
+
+ test('should navigate to credential types detail when cancel is clicked', async () => {
+ await act(async () => {
+ wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
+ });
+ expect(history.location.pathname).toEqual('/credential_types/42/details');
+ });
+
+ test('should navigate to credential type detail after successful submission', async () => {
+ await act(async () => {
+ wrapper.find('CredentialTypeForm').invoke('onSubmit')({
+ ...updateCredentialTypeData,
+ injectors: JSON.parse(credentialTypeData.injectors),
+ inputs: JSON.parse(credentialTypeData.inputs),
+ });
+ });
+ wrapper.update();
+ expect(wrapper.find('FormSubmitError').length).toBe(0);
+ expect(history.location.pathname).toEqual('/credential_types/42/details');
+ });
+
+ test('failed form submission should show an error message', async () => {
+ const error = {
+ response: {
+ data: { detail: 'An error occurred' },
+ },
+ };
+ CredentialTypesAPI.update.mockImplementationOnce(() =>
+ Promise.reject(error)
+ );
+ await act(async () => {
+ wrapper.find('CredentialTypeForm').invoke('onSubmit')(
+ updateCredentialTypeData
+ );
+ });
+ wrapper.update();
+ expect(wrapper.find('FormSubmitError').length).toBe(1);
+ });
+});
diff --git a/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx b/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx
index 7375a61b63..c19391f0c0 100644
--- a/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx
+++ b/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx
@@ -131,7 +131,7 @@ function CredentialTypeList({ i18n }) {
)}
renderItem={credentialType => (
@@ -65,8 +67,12 @@ function CredentialTypeForm({
const initialValues = {
name: credentialType.name || '',
description: credentialType.description || '',
- inputs: credentialType.inputs || '---',
- injectors: credentialType.injectors || '---',
+ inputs: credentialType.inputs
+ ? jsonToYaml(JSON.stringify(credentialType.inputs))
+ : '---',
+ injectors: credentialType.injectors
+ ? jsonToYaml(JSON.stringify(credentialType.injectors))
+ : '---',
};
return (
onSubmit(values)}>
@@ -74,7 +80,7 @@ function CredentialTypeForm({