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({
- + {submitError && }