diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx new file mode 100644 index 0000000000..451d6ea832 --- /dev/null +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { withRouter } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + Card, + CardBody, + CardHeader, + PageSection, + Tooltip, +} from '@patternfly/react-core'; +import CardCloseButton from '@components/CardCloseButton'; +import JobTemplateForm from '../shared/JobTemplateForm'; +import { JobTemplatesAPI } from '@api'; + +function JobTemplateAdd({ history, i18n }) { + const [error, setError] = useState(''); + + const handleSubmit = async values => { + try { + const data = await JobTemplatesAPI.create(values); + const { response } = data; + history.push(`/templates/${response.type}/${response.id}/details`); + } catch (err) { + setError(err); + } + }; + + const handleCancel = () => { + history.push(`/templates`); + }; + + return ( + + + + + + + + + + + {error ?
error
: ''} +
+
+ ); +} + +export default withI18n()(withRouter(JobTemplateAdd)); diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx new file mode 100644 index 0000000000..e4b794a2f0 --- /dev/null +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { mountWithContexts } from '@testUtils/enzymeHelpers'; +import JobTemplateAdd from './JobTemplateAdd'; + +jest.mock('@api'); + +describe('', () => { + const defaultProps = { + description: '', + inventory: 0, + job_type: 'run', + name: '', + playbook: '', + project: 0, + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should render Job Template Form', () => { + const wrapper = mountWithContexts(); + expect(wrapper.find('JobTemplateForm').length).toBe(1); + }); + + test('should render Job Template Form with default values', () => { + const wrapper = mountWithContexts(); + expect(wrapper.find('input#template-description').props().value).toBe( + defaultProps.description + ); + expect(wrapper.find('input#template-inventory').props().value).toBe( + defaultProps.inventory + ); + expect(wrapper.find('AnsibleSelect[name="job_type"]').props().value).toBe( + defaultProps.job_type + ); + expect(wrapper.find('input#template-name').props().value).toBe( + defaultProps.name + ); + expect(wrapper.find('input#template-playbook').props().value).toBe( + defaultProps.playbook + ); + expect(wrapper.find('input#template-project').props().value).toBe( + defaultProps.project + ); + }); + + test('should navigate to templates list when cancel is clicked', () => { + const history = { + push: jest.fn(), + }; + const wrapper = mountWithContexts(, { + context: { router: { history } }, + }); + + wrapper.find('button[aria-label="Cancel"]').prop('onClick')(); + expect(history.push).toHaveBeenCalledWith('/templates'); + }); +}); diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/index.js b/awx/ui_next/src/screens/Template/JobTemplateAdd/index.js new file mode 100644 index 0000000000..28af0b3a54 --- /dev/null +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/index.js @@ -0,0 +1 @@ +export { default } from './JobTemplateAdd'; diff --git a/awx/ui_next/src/screens/Template/Templates.jsx b/awx/ui_next/src/screens/Template/Templates.jsx index 2e815471d6..55643616cd 100644 --- a/awx/ui_next/src/screens/Template/Templates.jsx +++ b/awx/ui_next/src/screens/Template/Templates.jsx @@ -8,6 +8,7 @@ import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs'; import { TemplateList } from './TemplateList'; import Template from './Template'; +import JobTemplateAdd from './JobTemplateAdd'; class Templates extends Component { constructor(props) { @@ -17,6 +18,7 @@ class Templates extends Component { this.state = { breadcrumbConfig: { '/templates': i18n._(t`Templates`), + '/templates/job_template/add': i18n._(t`Create New Job Template`), }, }; } @@ -28,6 +30,7 @@ class Templates extends Component { } const breadcrumbConfig = { '/templates': i18n._(t`Templates`), + '/templates/job_template/add': i18n._(t`Create New Job Template`), [`/templates/${template.type}/${template.id}`]: `${template.name}`, [`/templates/${template.type}/${template.id}/details`]: i18n._( t`Details` @@ -46,6 +49,10 @@ class Templates extends Component { + } + /> ( diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx index 536cf486e5..deac1ee9b7 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx @@ -20,11 +20,22 @@ const QuestionCircleIcon = styled(PFQuestionCircleIcon)` class JobTemplateForm extends Component { static propTypes = { - template: JobTemplate.isRequired, + template: JobTemplate, handleCancel: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired, }; + static defaultProps = { + template: { + name: '', + description: '', + inventory: 0, + job_type: 'run', + project: 0, + playbook: '', + }, + }; + render() { const { handleCancel, handleSubmit, i18n, template } = this.props;