diff --git a/awx/ui_next/src/screens/Template/Templates.jsx b/awx/ui_next/src/screens/Template/Templates.jsx index 456f8b3ca4..97b86ebb84 100644 --- a/awx/ui_next/src/screens/Template/Templates.jsx +++ b/awx/ui_next/src/screens/Template/Templates.jsx @@ -90,7 +90,6 @@ class Templates extends Component { {({ me }) => ( { const { labels, organizationId, ...remainingValues } = values; @@ -17,14 +17,29 @@ function WorkflowJobTemplateAdd() { const { data: { id }, } = await WorkflowJobTemplatesAPI.create(remainingValues); - await Promise.all([submitLabels(id, labels, organizationId)]); + await Promise.all([submitLabels(id, labels, organizationId, values)]); history.push(`/templates/workflow_job_template/${id}/details`); } catch (err) { setFormSubmitError(err); } }; - const submitLabels = (templateId, labels = [], organizationId) => { + const submitLabels = async ( + templateId, + labels = [], + organizationId, + values + ) => { + if (!organizationId && !values.organization) { + try { + const { + data: { results }, + } = await OrganizationsAPI.read(); + organizationId = results[0].id; + } catch (err) { + setFormSubmitError(err); + } + } const associatePromises = labels.map(label => WorkflowJobTemplatesAPI.associateLabel(templateId, label, organizationId) ); 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 fd063ac73b..65161efb0f 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx @@ -82,8 +82,11 @@ describe('', () => { variables: '---', }); }); + expect(wrapper.find('ContentError').length).toBe(0); + expect(wrapper.length).toBe(1); wrapper.update(); expect(WorkflowJobTemplatesAPI.create).toBeCalled(); + expect(wrapper.find('ContentError').length).toBe(1); expect(wrapper.find('WorkflowJobTemplateForm').prop('submitError')).toEqual( error ); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx index 53dd2ba442..7b620044ed 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx @@ -8,7 +8,7 @@ import { WorkflowJobTemplateForm } from '../shared'; function WorkflowJobTemplateEdit({ template, webhook_key }) { const history = useHistory(); - const [formSubmitError, setFormSubmitError] = useState(); + const [formSubmitError, setFormSubmitError] = useState(null); const handleSubmit = async values => { const { labels, ...remainingValues } = values; @@ -57,21 +57,19 @@ function WorkflowJobTemplateEdit({ template, webhook_key }) { }; const handleCancel = () => { - history.push(`/templates`); + history.push(`/templates/workflow_job_template/${template.id}/details`); }; return ( - <> - - - - + + + ); } export default WorkflowJobTemplateEdit; 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 ebb3155abc..0cd3979e35 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx @@ -98,7 +98,9 @@ describe('', () => { await act(async () => { await wrapper.find('WorkflowJobTemplateForm').invoke('handleCancel')(); }); - expect(history.location.pathname).toBe('/templates'); + expect(history.location.pathname).toBe( + '/templates/workflow_job_template/6/details' + ); }); test('throwing error renders FormSubmitError component', async () => { diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx index db36f925f3..19a8391f8c 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { t } from '@lingui/macro'; import { useRouteMatch, useParams } from 'react-router-dom'; @@ -20,6 +20,7 @@ import { SyncAltIcon } from '@patternfly/react-icons'; import AnsibleSelect from '@components/AnsibleSelect'; import { WorkflowJobTemplatesAPI, CredentialTypesAPI } from '@api'; +import useRequest from '@util/useRequest'; import FormField, { FieldTooltip, FormSubmitError, @@ -29,14 +30,15 @@ import { FormFullWidthLayout, FormCheckboxLayout, } from '@components/FormLayout'; +import ContentLoading from '@components/ContentLoading'; import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import CredentialLookup from '@components/Lookup/CredentialLookup'; import { InventoryLookup } from '@components/Lookup'; import { VariablesField } from '@components/CodeMirrorInput'; import FormActionGroup from '@components/FormActionGroup'; import ContentError from '@components/ContentError'; +import CheckboxField from '@components/FormField/CheckboxField'; import LabelSelect from './LabelSelect'; -import CheckboxField from '../../../components/FormField/CheckboxField'; function WorkflowJobTemplateForm({ handleSubmit, @@ -51,8 +53,7 @@ function WorkflowJobTemplateForm({ const { id } = useParams(); const wfjtAddMatch = useRouteMatch('/templates/workflow_job_template/add'); - const [contentError, setContentError] = useState(null); - const [credTypeId, setCredentialTypeId] = useState(); + const [hasContentError, setContentError] = useState(null); const [inventory, setInventory] = useState( template?.summary_fields?.inventory || null @@ -85,30 +86,33 @@ function WorkflowJobTemplateForm({ { value: 'gitlab', key: 'gitlab', - label: i18n._(t`Git Lab`), + label: i18n._(t`GitLab`), isDisabled: false, }, ]; + const { + request: loadCredentialType, + error: contentError, + contentLoading, + result: credTypeId, + } = useRequest( + useCallback(async () => { + const results = await CredentialTypesAPI.read({ + namespace: `${webhookService}_token`, + }); + // TODO: Consider how to handle the situation where the results returns + // and empty array, or any of the other values is undefined or null (data, results, id) + return results?.data?.results[0]?.id; + }, [webhookService]) + ); useEffect(() => { - if (!webhookService) { - return; - } - const loadCredentialType = async () => { - try { - const { - data: { results }, - } = await CredentialTypesAPI.read({ - namespace: `${webhookService}_token`, - }); - setCredentialTypeId(results[0].id); - } catch (err) { - setContentError(err); - } - }; loadCredentialType(); - }, [webhookService]); - + }, [loadCredentialType]); + // TODO: Convert this function below to useRequest. Create a new webhookkey component + // that handles all of that api calls. Will also need to move this api call out of + // WorkflowJobTemplate.jsx and add it to workflowJobTemplateDetai.jsx + let initialWebhookKey = webhook_key; const changeWebhookKey = async () => { try { const { @@ -120,9 +124,8 @@ function WorkflowJobTemplateForm({ } }; - let initialWebhookKey = webhook_key; - - const setWebhookValues = (form, webhookServiceValue) => { + const initialWebhookCredential = template?.summary_fields?.webhook_credential; + const storeWebhookValues = (form, webhookServiceValue) => { if ( webhookServiceValue === form.initialValues.webhook_service || webhookServiceValue === '' @@ -131,18 +134,21 @@ function WorkflowJobTemplateForm({ 'webhook_credential', form.initialValues.webhook_credential ); + setWebhookCredential(initialWebhookCredential); form.setFieldValue('webhook_url', form.initialValues.webhook_url); form.setFieldValue('webhook_service', form.initialValues.webhook_service); - setWebHookKey(initialWebhookKey); } else { form.setFieldValue('webhook_credential', null); + setWebhookCredential(null); form.setFieldValue( 'webhook_url', `${urlOrigin}/api/v2/workflow_job_templates/${template.id}/${webhookServiceValue}/` ); - setWebHookKey('A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE'); + setWebHookKey( + i18n._(t`a new webhook key will be generated on save.`).toUpperCase() + ); } }; @@ -159,13 +165,18 @@ function WorkflowJobTemplateForm({ setWebHookService(''); setWebHookKey(''); } else { - setWebhookValues(form, webhookServiceValue); + storeWebhookValues(form, webhookServiceValue); } }; - if (contentError) { - return ; + if (hasContentError || contentError) { + return ; } + + if (contentLoading) { + return ; + } + return ( { @@ -221,15 +232,15 @@ function WorkflowJobTemplateForm({ {({ form }) => ( form.setFieldTouched('organization')} onChange={value => { - form.setFieldValue('organization', value.id); + form.setFieldValue('organization', value?.id || null); setOrganization(value); }} value={organization} + isValid={ + !(form.touched.organization || form.errors.organization) + } touched={form.touched.organization} error={form.errors.organization} /> @@ -242,53 +253,43 @@ function WorkflowJobTemplateForm({ tooltip={i18n._( t`Select an inventory for the workflow. This inventory is applied to all job template nodes that prompt for an inventory.` )} - isValid={!form.touched.inventory || !form.errors.inventory} + isValid={!(form.touched.inventory || form.errors.inventory)} helperTextInvalid={form.errors.inventory} onChange={value => { - form.setFieldValue('inventory', value.id); + form.setFieldValue('inventory', value?.id || null); setInventory(value); - form.setFieldValue('organizationId', value.organization); + form.setFieldValue('organizationId', value?.organization); }} error={form.errors.inventory} /> )} - - - - - + - - - + /> {({ form, field }) => ( @@ -300,7 +301,7 @@ function WorkflowJobTemplateForm({ form.setFieldValue('labels', labels)} - onError={() => setContentError()} + onError={err => setContentError(err)} /> )} @@ -320,7 +321,6 @@ function WorkflowJobTemplateForm({ fieldId="options" isInline label={i18n._(t`Options`)} - css="margin-top: 20px" > { setWebHookService(val); - setWebhookValues(form, val); + storeWebhookValues(form, val); form.setFieldValue('webhook_service', val); }} @@ -391,72 +397,77 @@ function WorkflowJobTemplateForm({ {!wfjtAddMatch && ( <> - - - - - - - - - - - + tooltip={i18n._( + t`Webhook services can launch jobs with this job template by making a POST request to this URL.` + )} + isReadOnly + /> + + {({ form }) => ( + + + + + + + + )} + )} {credTypeId && ( + // TODO: Consider how to handle the situation where the results returns + // an empty array, or any of the other values is undefined or null + // (data, results, id) {({ form }) => ( - - { - form.setFieldValue('webhook_credential', value.id); - setWebhookCredential(value); - }} - value={webhookCredential} - /> - + { + form.setFieldValue( + 'webhook_credential', + value?.id || null + ); + setWebhookCredential(value); + }} + isValid={ + !( + form.touched.webhook_credential || + form.errors.webhook_credential + ) + } + helperTextInvalid={form.errors.webhook_credential} + 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 328ba5de85..02aad9ef20 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx @@ -6,7 +6,7 @@ import { sleep } from '@testUtils/testUtils'; import { mountWithContexts } from '@testUtils/enzymeHelpers'; import WorkflowJobTemplateForm from './WorkflowJobTemplateForm'; -import { WorkflowJobTemplatesAPI } from '../../../api'; +import { WorkflowJobTemplatesAPI } from '@api'; jest.mock('@api/models/WorkflowJobTemplates'); @@ -137,8 +137,6 @@ describe('', () => { }); wrapper.update(); - expect(wrapper.find('input#wfjt-name').prop('value')).toEqual('new foo'); - const assertChanges = ({ element, value }) => { expect(wrapper.find(`input#${element}`).prop('value')).toEqual( `${value.value}` @@ -146,20 +144,6 @@ describe('', () => { }; inputsToChange.map(input => assertChanges(input)); - expect(wrapper.find('input#wfjt-name').prop('value')).toEqual('new foo'); - expect(wrapper.find('InventoryLookup').prop('value')).toEqual({ - id: 3, - name: 'inventory', - }); - expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({ - id: 3, - name: 'organization', - }); - expect(wrapper.find('LabelSelect').prop('value')).toEqual([ - { name: 'new label', id: 5 }, - { name: 'Label 1', id: 1 }, - { name: 'Label 2', id: 2 }, - ]); }); test('webhooks and enable concurrent jobs functions properly', async () => { @@ -185,11 +169,10 @@ describe('', () => { .find('Button[variant="tertiary"]') .prop('onClick')() ); - expect(WorkflowJobTemplatesAPI.updateWebhookKey).toBeCalledWith('6'); expect( wrapper.find('TextInputBase[name="webhook_url"]').prop('value') - ).toBe('http://127.0.0.1:3001/api/v2/workflow_job_templates/57/gitlab/'); + ).toContain('/api/v2/workflow_job_templates/57/gitlab/'); wrapper.update();