diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx
index 5a468bcccb..3c72e56084 100644
--- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx
+++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx
@@ -5,9 +5,8 @@ import { t } from '@lingui/macro';
import { useField } from 'formik';
import styled from 'styled-components';
import { Split, SplitItem } from '@patternfly/react-core';
-import { CheckboxField } from '@components/FormField';
+import { CheckboxField, FieldTooltip } from '@components/FormField';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
-import { FieldTooltip } from '@components/FormField';
import CodeMirrorInput from './CodeMirrorInput';
import YamlJsonToggle from './YamlJsonToggle';
import { JSON_MODE, YAML_MODE } from './constants';
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx
index 0cfead83c8..05d68a1b6e 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.jsx
@@ -17,33 +17,28 @@ function WorkflowJobTemplateAdd() {
const {
data: { id },
} = await WorkflowJobTemplatesAPI.create(remainingValues);
- await Promise.all([submitLabels(id, labels, organizationId, values)]);
+ await submitLabels(id, labels, organizationId);
history.push(`/templates/workflow_job_template/${id}/details`);
} catch (err) {
setFormSubmitError(err);
}
};
- const submitLabels = async (
- templateId,
- labels = [],
- organizationId,
- values
- ) => {
- if (!organizationId && !values.organization) {
+ const submitLabels = async (templateId, labels = [], organizationId) => {
+ if (!organizationId) {
try {
const {
data: { results },
} = await OrganizationsAPI.read();
organizationId = results[0].id;
} catch (err) {
- setFormSubmitError(err);
+ throw err;
}
}
const associatePromises = labels.map(label =>
WorkflowJobTemplatesAPI.associateLabel(templateId, label, organizationId)
);
- return Promise.all([...associatePromises]);
+ return [...associatePromises];
};
const handleCancel = () => {
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx
index 7b620044ed..6c9356e70d 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx
@@ -13,46 +13,38 @@ function WorkflowJobTemplateEdit({ template, webhook_key }) {
const handleSubmit = async values => {
const { labels, ...remainingValues } = values;
try {
+ await submitLabels(labels, values.organization, template.organization);
await WorkflowJobTemplatesAPI.update(template.id, remainingValues);
- await Promise.all([submitLabels(labels, values.organization)]);
history.push(`/templates/workflow_job_template/${template.id}/details`);
} catch (err) {
setFormSubmitError(err);
}
};
- const submitLabels = async (labels = [], orgId) => {
+ const submitLabels = async (labels = [], formOrgId, templateOrgId) => {
const { added, removed } = getAddedAndRemoved(
template.summary_fields.labels.results,
labels
);
- if (!orgId && !template.organization) {
+ let orgId = formOrgId || templateOrgId;
+ if (!orgId) {
try {
const {
data: { results },
} = await OrganizationsAPI.read();
orgId = results[0].id;
} catch (err) {
- setFormSubmitError(err);
+ throw err;
}
}
- const disassociationPromises = removed.map(label =>
+ const disassociationPromises = await removed.map(label =>
WorkflowJobTemplatesAPI.disassociateLabel(template.id, label)
);
-
- const associationPromises = added.map(label => {
- return WorkflowJobTemplatesAPI.associateLabel(
- template.id,
- label,
- orgId || template.organization
- );
- });
-
- const results = await Promise.all([
- ...disassociationPromises,
- ...associationPromises,
- ]);
+ const associationPromises = await added.map(label =>
+ WorkflowJobTemplatesAPI.associateLabel(template.id, label, orgId)
+ );
+ const results = [...disassociationPromises, ...associationPromises];
return results;
};
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 0cd3979e35..f38fe2134b 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.jsx
@@ -1,12 +1,14 @@
import React from 'react';
import { Route } from 'react-router-dom';
import { act } from 'react-dom/test-utils';
-import { WorkflowJobTemplatesAPI } from '@api';
+import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '@api';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import { createMemoryHistory } from 'history';
import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit';
-jest.mock('@api');
+jest.mock('@api/models/WorkflowJobTemplates');
+jest.mock('@api/models/Organizations');
+
const mockTemplate = {
id: 6,
name: 'Foo',
@@ -53,6 +55,7 @@ describe('', () => {
afterEach(async () => {
wrapper.unmount();
+ jest.clearAllMocks();
});
test('renders successfully', () => {
@@ -65,8 +68,8 @@ describe('', () => {
id: 6,
name: 'Alex',
description: 'Apollo and Athena',
- inventory: { id: 1, name: 'Inventory 1' },
- organization: 2,
+ inventory: 1,
+ organization: 1,
labels: [{ name: 'Label 2', id: 2 }, { name: 'Generated Label' }],
scm_branch: 'master',
limit: '5000',
@@ -78,8 +81,8 @@ describe('', () => {
id: 6,
name: 'Alex',
description: 'Apollo and Athena',
- inventory: { id: 1, name: 'Inventory 1' },
- organization: 2,
+ inventory: 1,
+ organization: 1,
scm_branch: 'master',
limit: '5000',
variables: '---',
@@ -94,9 +97,9 @@ 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')();
+ test('handleCancel navigates the user to the /templates', () => {
+ act(() => {
+ wrapper.find('WorkflowJobTemplateForm').invoke('handleCancel')();
});
expect(history.location.pathname).toBe(
'/templates/workflow_job_template/6/details'
@@ -105,25 +108,41 @@ describe('', () => {
test('throwing error renders FormSubmitError component', async () => {
const error = new Error('oops');
- WorkflowJobTemplatesAPI.update.mockImplementation(() =>
- Promise.reject(error)
- );
+ OrganizationsAPI.read.mockResolvedValue({ results: [{ id: 1 }] });
+ WorkflowJobTemplatesAPI.update.mockRejectedValue(error);
await act(async () => {
- wrapper.find('WorkflowJobTemplateForm').prop('handleSubmit')({
- id: 6,
- name: 'Alex',
- description: 'Apollo and Athena',
- inventory: { id: 1, name: 'Inventory 1' },
- organization: 2,
- scm_branch: 'master',
- limit: '5000',
- variables: '---',
- });
+ wrapper.find('Button[aria-label="Save"]').simulate('click');
});
- wrapper.update();
expect(WorkflowJobTemplatesAPI.update).toHaveBeenCalled();
+ wrapper.update();
expect(wrapper.find('WorkflowJobTemplateForm').prop('submitError')).toEqual(
error
);
});
+
+ test('throwing error prevents form submission', () => {
+ OrganizationsAPI.read.mockRejectedValue(new Error('An error occurred'));
+ WorkflowJobTemplatesAPI.update.mockResolvedValue();
+
+ act(() => {
+ wrapper = mountWithContexts(
+ ,
+ {
+ context: {
+ router: {
+ history,
+ },
+ },
+ }
+ );
+ });
+ wrapper.find('Button[aria-label="Save"]').simulate('click');
+
+ expect(wrapper.find('WorkflowJobTemplateForm').length).toBe(1);
+ expect(OrganizationsAPI.read).toBeCalled();
+ expect(WorkflowJobTemplatesAPI.update).not.toBeCalled();
+ expect(history.location.pathname).toBe(
+ '/templates/workflow_job_template/6/edit'
+ );
+ });
});
diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx
index 19a8391f8c..7ebea06f9a 100644
--- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx
+++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx
@@ -97,11 +97,14 @@ function WorkflowJobTemplateForm({
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)
+ let results;
+ if (webhookService) {
+ 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])
);
@@ -109,10 +112,10 @@ function WorkflowJobTemplateForm({
useEffect(() => {
loadCredentialType();
}, [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;
+
+ // TODO: Convert this function below to useRequest. Might want to 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
const changeWebhookKey = async () => {
try {
const {
@@ -124,7 +127,9 @@ function WorkflowJobTemplateForm({
}
};
+ let initialWebhookKey = webhook_key;
const initialWebhookCredential = template?.summary_fields?.webhook_credential;
+
const storeWebhookValues = (form, webhookServiceValue) => {
if (
webhookServiceValue === form.initialValues.webhook_service ||
@@ -135,12 +140,17 @@ function WorkflowJobTemplateForm({
form.initialValues.webhook_credential
);
setWebhookCredential(initialWebhookCredential);
+
form.setFieldValue('webhook_url', form.initialValues.webhook_url);
+
form.setFieldValue('webhook_service', form.initialValues.webhook_service);
+ setWebHookService(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}/`
@@ -232,36 +242,37 @@ function WorkflowJobTemplateForm({
{({ form }) => (
form.setFieldTouched('organization')}
onChange={value => {
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}
+ isValid={!form.errors.organization}
/>
)}
{({ form }) => (
- {
- form.setFieldValue('inventory', value?.id || null);
- setInventory(value);
- form.setFieldValue('organizationId', value?.organization);
- }}
- error={form.errors.inventory}
- />
+
+
+ {
+ form.setFieldValue('inventory', value?.id || null);
+ setInventory(value);
+ form.setFieldValue('organizationId', value?.organization);
+ }}
+ />
+
)}
@@ -473,7 +479,7 @@ function WorkflowJobTemplateForm({
)}
)}
-
+ {submitError && }