diff --git a/awx/ui_next/src/screens/Template/Template.jsx b/awx/ui_next/src/screens/Template/Template.jsx
index b95cf64b88..80e4df8539 100644
--- a/awx/ui_next/src/screens/Template/Template.jsx
+++ b/awx/ui_next/src/screens/Template/Template.jsx
@@ -45,7 +45,7 @@ function Template({ i18n, me, setBreadcrumb }) {
role_level: 'notification_admin_role',
}),
]);
- if (data?.related?.webhook_key) {
+ if (data.webhook_service && data?.related?.webhook_key) {
const {
data: { webhook_key },
} = await JobTemplatesAPI.readWebhookKey(templateId);
diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
index 621b5d9e64..0e96b8995d 100644
--- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
+++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
@@ -40,7 +40,7 @@ import {
import { JobTemplatesAPI, ProjectsAPI } from '@api';
import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect';
-import WebhookSubForm from './WebhooksSubForm';
+import WebhookSubForm from './WebhookSubForm';
const { origin } = document.location;
@@ -94,10 +94,6 @@ function JobTemplateForm({
);
const [jobTagsField, , jobTagsHelpers] = useField('job_tags');
const [skipTagsField, , skipTagsHelpers] = useField('skip_tags');
- const webhookService = useField('webhook_service');
- const webhookUrl = useField('webhook_url');
- const webhookKey = useField('webhook_key');
- const webhookCredential = useField('webhook_credential');
const {
request: fetchProject,
@@ -189,19 +185,11 @@ function JobTemplateForm({
callbackUrl = `${origin}${path}`;
}
- if (
- instanceGroupLoading ||
- hasProjectLoading
- // credentialContentLoading
- ) {
+ if (instanceGroupLoading || hasProjectLoading) {
return ;
}
- if (
- instanceGroupError ||
- projectContentError
- // credentialContentError
- ) {
+ if (instanceGroupError || projectContentError) {
return ;
}
@@ -530,23 +518,9 @@ function JobTemplateForm({
}
id="wfjt-enabled-webhooks"
- isChecked={
- Boolean(webhookService[0].value) || enableWebhooks
- }
+ isChecked={enableWebhooks}
onChange={checked => {
setEnableWebhooks(checked);
- webhookService[2].setValue(
- !checked ? '' : webhookService[1].initialValue
- );
- webhookUrl[2].setValue(
- !checked ? '' : webhookUrl[1].initialValue
- );
- webhookKey[2].setValue(
- !checked ? '' : webhookKey[1].initialValue
- );
- webhookCredential[2].setValue(
- !checked ? null : webhookCredential[1].initialValue
- );
}}
/>
', () => {
);
});
+ test('webhooks should render properly, without data', async () => {
+ let wrapper;
+ const history = createMemoryHistory({
+ initialEntries: ['/templates/job_template/1/edit'],
+ });
+ await act(async () => {
+ wrapper = mountWithContexts(
+ (
+
+ )}
+ />,
+ {
+ context: {
+ router: {
+ history,
+ route: {
+ location: history.location,
+ match: { params: { id: 1 } },
+ },
+ },
+ },
+ }
+ );
+ });
+ expect(
+ wrapper.find('TextInputBase#template-webhook_key').prop('value')
+ ).toBe('A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.');
+ expect(
+ wrapper.find('Button[aria-label="Update webhook key"]').prop('isDisabled')
+ ).toBe(true);
+ });
test('should call handleSubmit when Submit button is clicked', async () => {
const handleSubmit = jest.fn();
let wrapper;
diff --git a/awx/ui_next/src/screens/Template/shared/WebhooksSubForm.jsx b/awx/ui_next/src/screens/Template/shared/WebhookSubForm.jsx
similarity index 60%
rename from awx/ui_next/src/screens/Template/shared/WebhooksSubForm.jsx
rename to awx/ui_next/src/screens/Template/shared/WebhookSubForm.jsx
index 6430fc1f6a..7211990c90 100644
--- a/awx/ui_next/src/screens/Template/shared/WebhooksSubForm.jsx
+++ b/awx/ui_next/src/screens/Template/shared/WebhookSubForm.jsx
@@ -1,6 +1,6 @@
-import React, { useEffect, useCallback, useState } from 'react';
+import React, { useEffect, useCallback } from 'react';
import { SyncAltIcon } from '@patternfly/react-icons';
-import { useParams, useRouteMatch } from 'react-router-dom';
+import { useParams, useLocation } from 'react-router-dom';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import {
@@ -20,9 +20,8 @@ import { FieldTooltip } from '@components/FormField';
import { JobTemplatesAPI, CredentialTypesAPI } from '@api';
function WebhookSubForm({ i18n, enableWebhooks }) {
- const [contentError, setContentError] = useState(null);
- const jtAddMatch = useRouteMatch('/templates/job_template/add');
- const { id } = useParams();
+ const { id, templateType } = useParams();
+ const { pathname } = useLocation();
const { origin } = document.location;
@@ -32,7 +31,9 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
webhookServiceHelpers,
] = useField('webhook_service');
- const [webhookUrlField, , webhookUrlHelpers] = useField('webhook_url');
+ const [webhookUrlField, webhookUrlMeta, webhookUrlHelpers] = useField(
+ 'webhook_url'
+ );
const [webhookKeyField, webhookKeyMeta, webhookKeyHelpers] = useField(
'webhook_key'
);
@@ -65,17 +66,37 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
loadCredentialType();
}, [loadCredentialType]);
- const changeWebhookKey = async () => {
- try {
+ useEffect(() => {
+ if (enableWebhooks) {
+ webhookServiceHelpers.setValue(webhookServiceMeta.initialValue);
+ webhookUrlHelpers.setValue(webhookUrlMeta.initialValue);
+ webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
+ webhookCredentialHelpers.setValue(webhookCredentialMeta.initialValue);
+ } else {
+ webhookServiceHelpers.setValue('');
+ webhookUrlHelpers.setValue('');
+ webhookKeyHelpers.setValue('');
+ webhookCredentialHelpers.setValue(null);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [enableWebhooks]);
+
+ const { request: fetchWebhookKey, error: webhookKeyError } = useRequest(
+ useCallback(async () => {
const {
data: { webhook_key: key },
} = await JobTemplatesAPI.updateWebhookKey(id);
webhookKeyHelpers.setValue(key);
- } catch (err) {
- setContentError(err);
- }
- };
+ }, [webhookKeyHelpers, id])
+ );
+ const changeWebhookKey = async () => {
+ await fetchWebhookKey();
+ };
+ const isUpdateKeyDisabled =
+ pathname.endsWith('/add') ||
+ webhookKeyMeta.initialValue ===
+ 'A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.';
const webhookServiceOptions = [
{
value: '',
@@ -97,7 +118,7 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
},
];
- if (error || contentError) {
+ if (error || webhookKeyError) {
return ;
}
if (isLoading) {
@@ -120,7 +141,11 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
onChange={(event, val) => {
webhookServiceHelpers.setValue(val);
webhookUrlHelpers.setValue(
- `${origin}/api/v2/job_templates/${id}/${val}/`
+ pathname.endsWith('/add')
+ ? i18n
+ ._(t`a new webhook url will be generated on save.`)
+ .toUpperCase()
+ : `${origin}/api/v2/${templateType}s/${id}/${val}/`
);
if (val === webhookServiceMeta.initialValue || val === '') {
webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
@@ -138,53 +163,53 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
}}
/>
- {!jtAddMatch && (
- <>
-
-
+ <>
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- >
- )}
+
+
+
+ >
+
{credTypeId && (
', () => {
+ let wrapper;
+ let history;
+ const initialValues = {
+ webhook_url: '/api/v2/job_templates/51/github/',
+ webhook_credential: { id: 1, name: 'Github credential' },
+ webhook_service: 'github',
+ webhook_key: 'webhook key',
+ };
+ beforeEach(async () => {
+ history = createMemoryHistory({
+ initialEntries: ['templates/job_template/51/edit'],
+ });
+ CredentialsAPI.read.mockResolvedValue({
+ data: { results: [{ id: 12, name: 'Github credential' }] },
+ });
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+
+ ,
+ {
+ context: {
+ router: {
+ history,
+ route: {
+ location: { pathname: 'templates/job_template/51/edit' },
+ match: { params: { id: 51, templateType: 'job_template' } },
+ },
+ },
+ },
+ }
+ );
+ });
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+ test('mounts properly', () => {
+ expect(wrapper.length).toBe(1);
+ });
+ test('should render initial values properly', () => {
+ waitForElement(wrapper, 'Lookup__ChipHolder', el => el.lenth > 0);
+ expect(wrapper.find('AnsibleSelect').prop('value')).toBe('github');
+ expect(
+ wrapper.find('TextInputBase[aria-label="Webhook URL"]').prop('value')
+ ).toContain('/api/v2/job_templates/51/github/');
+ expect(
+ wrapper.find('TextInputBase[aria-label="wfjt-webhook-key"]').prop('value')
+ ).toBe('webhook key');
+ expect(
+ wrapper
+ .find('Chip')
+ .find('span')
+ .text()
+ ).toBe('Github credential');
+ });
+ test('should make other credential type available', async () => {
+ CredentialsAPI.read.mockResolvedValue({
+ data: { results: [{ id: 13, name: 'GitLab credential' }] },
+ });
+ await act(async () =>
+ wrapper.find('AnsibleSelect').prop('onChange')({}, 'gitlab')
+ );
+ expect(CredentialsAPI.read).toHaveBeenCalledWith({
+ namespace: 'gitlab_token',
+ });
+ wrapper.update();
+ expect(
+ wrapper.find('TextInputBase[aria-label="Webhook URL"]').prop('value')
+ ).toContain('/api/v2/job_templates/51/gitlab/');
+ expect(
+ wrapper.find('TextInputBase[aria-label="wfjt-webhook-key"]').prop('value')
+ ).toBe('A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.');
+ });
+ test('should have disabled button to update webhook key', async () => {
+ let newWrapper;
+ await act(async () => {
+ newWrapper = mountWithContexts(
+
+
+
+
+ ,
+ {
+ context: {
+ router: {
+ history,
+ route: {
+ location: { pathname: 'templates/job_template/51/edit' },
+ match: { params: { id: 51, templateType: 'job_template' } },
+ },
+ },
+ },
+ }
+ );
+ });
+ expect(
+ newWrapper
+ .find("Button[aria-label='Update webhook key']")
+ .prop('isDisabled')
+ ).toBe(true);
+ });
+});