diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx index 69a55550f1..5863abe8ea 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { Card } from '@patternfly/react-core'; +import { Card, PageSection } from '@patternfly/react-core'; import { CardBody } from '@components/Card'; import JobTemplateForm from '../shared/JobTemplateForm'; import { JobTemplatesAPI } from '@api'; @@ -61,15 +61,17 @@ function JobTemplateAdd() { } return ( - - - - - + + + + + + + ); } diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx index 40c874328c..921aa7486b 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx @@ -8,8 +8,9 @@ import { WorkflowJobTemplatesAPI } from '@api'; import WorkflowJobTemplateForm from '../shared/WorkflowJobTemplateForm'; function WorkflowJobTemplateAdd() { - const [formSubmitError, setFormSubmitError] = useState(); const history = useHistory(); + const [formSubmitError, setFormSubmitError] = useState(); + const handleSubmit = async values => { const { labels, organizationId, ...remainingValues } = values; try { diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx index 1424c689e0..fd063ac73b 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx @@ -39,9 +39,11 @@ describe('', () => { afterEach(async () => { wrapper.unmount(); }); + test('initially renders successfully', async () => { expect(wrapper.length).toBe(1); }); + test('calls workflowJobTemplatesAPI with correct information on submit', async () => { await act(async () => { await wrapper.find('WorkflowJobTemplateForm').invoke('handleSubmit')({ @@ -55,12 +57,14 @@ describe('', () => { }); expect(WorkflowJobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(2); }); + test('handleCancel navigates the user to the /templates', async () => { await act(async () => { await wrapper.find('WorkflowJobTemplateForm').invoke('handleCancel')(); }); expect(history.location.pathname).toBe('/templates'); }); + test('throwing error renders FormSubmitError component', async () => { const error = new Error('oops'); WorkflowJobTemplatesAPI.create.mockImplementation(() => diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx index fcfea46b82..f39035286a 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.jsx @@ -39,12 +39,15 @@ function WorkflowJobTemplateDetail({ template, i18n, webhook_key }) { related, webhook_credential, } = template; + const urlOrigin = window.location.origin; const history = useHistory(); + const [deletionError, setDeletionError] = useState(null); const [hasContentLoading, setHasContentLoading] = useState(false); + const renderOptionsField = - template.allow_simultaneous || template.webhook_servicee; + template.allow_simultaneous || template.webhook_service; const renderOptions = ( @@ -75,6 +78,7 @@ function WorkflowJobTemplateDetail({ template, i18n, webhook_key }) { } setHasContentLoading(false); }; + const inventoryValue = (kind, inventoryId) => { const inventorykind = kind === 'smart' ? 'smart_inventory' : 'inventory'; @@ -91,6 +95,7 @@ function WorkflowJobTemplateDetail({ template, i18n, webhook_key }) { ); }; + const canLaunch = summary_fields?.user_capabilities?.start; const recentPlaybookJobs = summary_fields.recent_jobs.map(job => ({ ...job, diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.test.jsx index d979a2713a..90f1ab08e1 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.test.jsx @@ -40,6 +40,7 @@ describe('', () => { }, webhook_service: 'Github', }; + beforeEach(async () => { history = createMemoryHistory({ initialEntries: ['/templates/workflow_job_template/1/details'], @@ -75,12 +76,15 @@ describe('', () => { ); }); }); + afterEach(() => { wrapper.unmount(); }); + test('renders successfully', () => { expect(wrapper.find(WorkflowJobTemplateDetail).length).toBe(1); }); + test('expect detail fields to render properly', () => { const renderedValues = [ { @@ -147,6 +151,7 @@ describe('', () => { renderedValues.map(value => assertValue(value)); }); + test('link out resource have the correct url', () => { const inventory = wrapper.find('Detail[label="Inventory"]').find('Link'); const organization = wrapper diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx index 2a83c18033..53dd2ba442 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx @@ -7,8 +7,8 @@ import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '@api'; import { WorkflowJobTemplateForm } from '../shared'; function WorkflowJobTemplateEdit({ template, webhook_key }) { - const [formSubmitError, setFormSubmitError] = useState(); const history = useHistory(); + const [formSubmitError, setFormSubmitError] = useState(); const handleSubmit = async values => { const { labels, ...remainingValues } = values; diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx index e73b29ae4e..ebb3155abc 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx @@ -25,6 +25,7 @@ const mockTemplate = { describe('', () => { let wrapper; let history; + beforeEach(async () => { await act(async () => { history = createMemoryHistory({ @@ -49,12 +50,15 @@ describe('', () => { ); }); }); + afterEach(async () => { wrapper.unmount(); }); + test('renders successfully', () => { expect(wrapper.find('WorkflowJobTemplateEdit').length).toBe(1); }); + test('api is called to properly to save the updated template.', async () => { await act(async () => { await wrapper.find('WorkflowJobTemplateForm').invoke('handleSubmit')({ @@ -69,6 +73,7 @@ describe('', () => { variables: '---', }); }); + expect(WorkflowJobTemplatesAPI.update).toHaveBeenCalledWith(6, { id: 6, name: 'Alex', @@ -88,12 +93,14 @@ describe('', () => { await expect(WorkflowJobTemplatesAPI.associateLabel).toBeCalledTimes(1); }); + test('handleCancel navigates the user to the /templates', async () => { await act(async () => { await wrapper.find('WorkflowJobTemplateForm').invoke('handleCancel')(); }); expect(history.location.pathname).toBe('/templates'); }); + test('throwing error renders FormSubmitError component', async () => { const error = new Error('oops'); WorkflowJobTemplatesAPI.update.mockImplementation(() => diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx index afa68ea97a..db36f925f3 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx @@ -46,14 +46,24 @@ function WorkflowJobTemplateForm({ webhook_key, submitError, }) { + const urlOrigin = window.location.origin; + const { id } = useParams(); const wfjtAddMatch = useRouteMatch('/templates/workflow_job_template/add'); - const urlOrigin = window.location.origin; - const [contentError, setContentError] = useState(null); - const [webhookKey, setWebHookKey] = useState(webhook_key); const [credTypeId, setCredentialTypeId] = useState(); + + const [inventory, setInventory] = useState( + template?.summary_fields?.inventory || null + ); + const [organization, setOrganization] = useState( + template?.summary_fields?.organization || null + ); + const [webhookCredential, setWebhookCredential] = useState( + template?.summary_fields?.webhook_credential || null + ); + const [webhookKey, setWebHookKey] = useState(webhook_key); const [webhookService, setWebHookService] = useState( template.webhook_service || '' ); @@ -159,16 +169,16 @@ function WorkflowJobTemplateForm({ return ( { - values.webhook_credential = values?.webhook_credential?.id; - values.organization = values?.organization?.id; - values.inventory = values?.inventory?.id; + if (values.webhook_service === '') { + values.webhook_credential = ''; + } return handleSubmit(values); }} initialValues={{ name: template.name || '', description: template.description || '', - inventory: template?.summary_fields?.inventory || null, - organization: template?.summary_fields?.organization || null, + inventory: template?.summary_fields?.inventory?.id || null, + organization: template?.summary_fields?.organization?.id || null, labels: template.summary_fields?.labels?.results || [], extra_vars: template.variables || '---', limit: template.limit || '', @@ -178,7 +188,7 @@ function WorkflowJobTemplateForm({ template?.related?.webhook_receiver && `${urlOrigin}${template?.related?.webhook_receiver}`, webhook_credential: - template?.summary_fields?.webhook_credential || null, + template?.summary_fields?.webhook_credential?.id || null, webhook_service: template.webhook_service || '', ask_limit_on_launch: template.ask_limit_on_launch || false, ask_inventory_on_launch: template.ask_inventory_on_launch || false, @@ -208,7 +218,7 @@ function WorkflowJobTemplateForm({ label={i18n._(t`Organization`)} name="organization" > - {({ form, field }) => ( + {({ form }) => ( form.setFieldTouched('organization')} onChange={value => { - form.setFieldValue('organization', value); + form.setFieldValue('organization', value.id); + setOrganization(value); }} - value={field.value} + value={organization} touched={form.touched.organization} error={form.errors.organization} /> )} - {({ form, field }) => ( + {({ form }) => ( { - form.setFieldValue('inventory', value); + form.setFieldValue('inventory', value.id); + setInventory(value); form.setFieldValue('organizationId', value.organization); }} error={form.errors.inventory} @@ -426,7 +438,7 @@ function WorkflowJobTemplateForm({ )} {credTypeId && ( - {({ form, field }) => ( + {({ form }) => ( { - form.setFieldValue('webhook_credential', value); + form.setFieldValue('webhook_credential', value.id); + setWebhookCredential(value); }} - value={field.value} + value={webhookCredential} /> )} diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx index c7fa586c06..328ba5de85 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx @@ -9,9 +9,11 @@ import WorkflowJobTemplateForm from './WorkflowJobTemplateForm'; import { WorkflowJobTemplatesAPI } from '../../../api'; jest.mock('@api/models/WorkflowJobTemplates'); + WorkflowJobTemplatesAPI.updateWebhookKey.mockResolvedValue({ data: { webhook_key: 'sdafdghjkl2345678ionbvcxz' }, }); + describe('', () => { let wrapper; let history; @@ -35,6 +37,7 @@ describe('', () => { webhook_receiver: '/api/v2/workflow_job_templates/57/gitlab/', }, }; + beforeEach(() => { history = createMemoryHistory({ initialEntries: ['/templates/workflow_job_template/6/edit'], @@ -66,13 +69,16 @@ describe('', () => { ); }); }); + afterEach(() => { wrapper.unmount(); jest.clearAllMocks(); }); + test('renders successfully', () => { expect(wrapper.length).toBe(1); }); + test('all the fields render successfully', () => { const fields = [ 'FormField[name="name"]', @@ -89,6 +95,7 @@ describe('', () => { }; fields.map((field, index) => assertField(field, index)); }); + test('changing inputs should update values', async () => { const inputsToChange = [ { @@ -193,6 +200,7 @@ describe('', () => { expect(wrapper.find('AnsibleSelect').prop('value')).toBe('gitlab'); }); + test('handleSubmit is called on submit button click', async () => { act(() => { wrapper.find('Formik').prop('onSubmit')({}); @@ -201,6 +209,7 @@ describe('', () => { sleep(0); expect(handleSubmit).toBeCalled(); }); + test('handleCancel is called on cancel button click', async () => { act(() => { wrapper.find('button[aria-label="Cancel"]').simulate('click');