mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 18:40:01 -03:30
Adds Webhook form fields and tooltip to VariablesField component
This commit is contained in:
parent
d97f516c3a
commit
d4ba32d0c5
@ -69,6 +69,22 @@ describe('VariablesField', () => {
|
||||
expect(field.prop('hasErrors')).toEqual(true);
|
||||
expect(wrapper.find('.pf-m-error')).toHaveLength(1);
|
||||
});
|
||||
it('should render tooltip', () => {
|
||||
const value = '---\n';
|
||||
const wrapper = mount(
|
||||
<Formik initialValues={{ variables: value }}>
|
||||
{() => (
|
||||
<VariablesField
|
||||
id="the-field"
|
||||
name="variables"
|
||||
label="Variables"
|
||||
tooltip="This is a tooltip"
|
||||
/>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
expect(wrapper.find('Tooltip').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should submit value through Formik', async () => {
|
||||
const value = '---\nfoo: bar\n';
|
||||
|
||||
@ -22,15 +22,18 @@ function WorkflowJobTemplateAdd() {
|
||||
setFormSubmitError(err);
|
||||
}
|
||||
};
|
||||
|
||||
const submitLabels = (templateId, labels = [], organizationId) => {
|
||||
const associatePromises = labels.map(label =>
|
||||
WorkflowJobTemplatesAPI.associateLabel(templateId, label, organizationId)
|
||||
);
|
||||
return Promise.all([...associatePromises]);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
history.push(`/templates`);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>
|
||||
@ -38,9 +41,9 @@ function WorkflowJobTemplateAdd() {
|
||||
<WorkflowJobTemplateForm
|
||||
handleCancel={handleCancel}
|
||||
handleSubmit={handleSubmit}
|
||||
submitError={formSubmitError}
|
||||
/>
|
||||
</CardBody>
|
||||
{formSubmitError ? <div>formSubmitError</div> : ''}
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
|
||||
@ -61,4 +61,27 @@ describe('<WorkflowJobTemplateAdd/>', () => {
|
||||
});
|
||||
expect(history.location.pathname).toBe('/templates');
|
||||
});
|
||||
test('throwing error renders FormSubmitError component', async () => {
|
||||
const error = new Error('oops');
|
||||
WorkflowJobTemplatesAPI.create.mockImplementation(() =>
|
||||
Promise.reject(error)
|
||||
);
|
||||
await act(async () => {
|
||||
await 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.update();
|
||||
expect(WorkflowJobTemplatesAPI.create).toBeCalled();
|
||||
expect(wrapper.find('WorkflowJobTemplateForm').prop('submitError')).toEqual(
|
||||
error
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -25,6 +25,7 @@ function WorkflowJobTemplateEdit({
|
||||
setFormSubmitError(err);
|
||||
}
|
||||
};
|
||||
|
||||
const submitLabels = async (labels = [], orgId) => {
|
||||
const { added, removed } = getAddedAndRemoved(
|
||||
template.summary_fields.labels.results,
|
||||
@ -40,9 +41,11 @@ function WorkflowJobTemplateEdit({
|
||||
setFormSubmitError(err);
|
||||
}
|
||||
}
|
||||
|
||||
const disassociationPromises = removed.map(label =>
|
||||
WorkflowJobTemplatesAPI.disassociateLabel(template.id, label)
|
||||
);
|
||||
|
||||
const associationPromises = added.map(label => {
|
||||
return WorkflowJobTemplatesAPI.associateLabel(
|
||||
template.id,
|
||||
@ -61,6 +64,7 @@ function WorkflowJobTemplateEdit({
|
||||
const handleCancel = () => {
|
||||
history.push(`/templates`);
|
||||
};
|
||||
|
||||
if (hasTemplateLoading) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
@ -72,9 +76,9 @@ function WorkflowJobTemplateEdit({
|
||||
handleCancel={handleCancel}
|
||||
template={template}
|
||||
webhook_key={webhook_key}
|
||||
submitError={formSubmitError}
|
||||
/>
|
||||
</CardBody>
|
||||
{formSubmitError ? <div>formSubmitError</div> : ''}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -94,4 +94,27 @@ describe('<WorkflowJobTemplateEdit/>', () => {
|
||||
});
|
||||
expect(history.location.pathname).toBe('/templates');
|
||||
});
|
||||
test('throwing error renders FormSubmitError component', async () => {
|
||||
const error = new Error('oops');
|
||||
WorkflowJobTemplatesAPI.update.mockImplementation(() =>
|
||||
Promise.reject(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.update();
|
||||
expect(WorkflowJobTemplatesAPI.update).toHaveBeenCalled();
|
||||
expect(wrapper.find('WorkflowJobTemplateForm').prop('submitError')).toEqual(
|
||||
error
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { func, shape } from 'prop-types';
|
||||
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { Formik, Field } from 'formik';
|
||||
import {
|
||||
@ -11,14 +13,17 @@ import {
|
||||
TextInput,
|
||||
} from '@patternfly/react-core';
|
||||
import { required } from '@util/validators';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SyncAltIcon } from '@patternfly/react-icons';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AnsibleSelect from '@components/AnsibleSelect';
|
||||
import { WorkflowJobTemplatesAPI, CredentialTypesAPI } from '@api';
|
||||
import FormRow from '@components/FormRow';
|
||||
import FormField, { FieldTooltip, CheckboxField } from '@components/FormField';
|
||||
|
||||
import FormField, {
|
||||
FieldTooltip,
|
||||
FormSubmitError,
|
||||
} from '@components/FormField';
|
||||
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
||||
import CredentialLookup from '@components/Lookup/CredentialLookup';
|
||||
import { InventoryLookup } from '@components/Lookup';
|
||||
@ -44,11 +49,11 @@ function WorkflowJobTemplateForm({
|
||||
handleCancel,
|
||||
i18n,
|
||||
template = {},
|
||||
className,
|
||||
webhook_key,
|
||||
submitError,
|
||||
}) {
|
||||
const urlOrigin = window.location.origin;
|
||||
const { id } = useParams();
|
||||
const urlOrigin = window.location.origin;
|
||||
const [contentError, setContentError] = useState(null);
|
||||
const [inventory, setInventory] = useState(
|
||||
template?.summary_fields?.inventory || null
|
||||
@ -139,9 +144,10 @@ function WorkflowJobTemplateForm({
|
||||
variables: template.variables || '---',
|
||||
limit: template.limit || '',
|
||||
scmBranch: template.scm_branch || '',
|
||||
allow_simultaneous: template?.allow_simultaneous || false,
|
||||
webhook_url: `${urlOrigin}${template?.related?.webhook_receiver}` || '',
|
||||
webhook_key: webHookKey,
|
||||
allow_simultaneous: template.allow_simultaneous || false,
|
||||
webhook_url:
|
||||
template?.related?.webhook_receiver &&
|
||||
`${urlOrigin}${template?.related?.webhook_receiver}`,
|
||||
webhook_credential:
|
||||
template?.summary_fields?.webhook_credential?.id || null,
|
||||
webhook_service: webhookService,
|
||||
@ -269,16 +275,12 @@ function WorkflowJobTemplateForm({
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<VariablesField
|
||||
id="host-variables"
|
||||
id="wfjt-variables"
|
||||
name="variables"
|
||||
label={i18n._(t`Variables`)}
|
||||
tooltip={i18n._(
|
||||
t`Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON. Refer to the Ansible Tower documentation for example syntax.`
|
||||
)}
|
||||
/>
|
||||
</FormRow>
|
||||
<GridFormGroup
|
||||
className={className}
|
||||
fieldId="options"
|
||||
isInline
|
||||
label={i18n._(t`Options`)}
|
||||
@ -302,21 +304,34 @@ function WorkflowJobTemplateForm({
|
||||
setHasWebhooks(checked);
|
||||
}}
|
||||
/>
|
||||
<CheckboxField
|
||||
id="wfjt-allow_simultaneous"
|
||||
name="allow_simultaneous"
|
||||
label={i18n._(t`Enable Concurrent Jobs`)}
|
||||
tooltip={i18n._(
|
||||
t`If enabled, simultaneous runs of this workflow job template will be allowed.`
|
||||
<Field name="allow_simultaneous">
|
||||
{({ field, form }) => (
|
||||
<Checkbox
|
||||
name="allow_simultaneous"
|
||||
id="wfjt-allow_simultaneous"
|
||||
label={
|
||||
<span>
|
||||
{i18n._(t`Enable Concurrent Jobs`)}
|
||||
<FieldTooltip
|
||||
content={i18n._(
|
||||
t`If enabled, simultaneous runs of this workflow job template will be allowed.`
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
isChecked={field.value}
|
||||
onChange={value => {
|
||||
form.setFieldValue('allow_simultaneous', value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Field>
|
||||
</GridFormGroup>
|
||||
{hasWebhooks && (
|
||||
<>
|
||||
<FormRow>
|
||||
{template.related && (
|
||||
<FormGroup
|
||||
className={className}
|
||||
fieldId="wfjt-webhook-url"
|
||||
id="wfjt-webhook-url"
|
||||
name="webhook_url"
|
||||
@ -362,69 +377,60 @@ function WorkflowJobTemplateForm({
|
||||
</FormGroup>
|
||||
)}
|
||||
<Field name="webhook_service">
|
||||
{({ form }) => {
|
||||
const isValid =
|
||||
!form.errors.webhook_service;
|
||||
return (
|
||||
<FormGroup
|
||||
name="webhook_service"
|
||||
fieldId="webhook_service"
|
||||
helperTextInvalid={form.errors.webhook_service}
|
||||
isValid={isValid}
|
||||
label={i18n._(t`Webhook Service`)}
|
||||
>
|
||||
<FieldTooltip
|
||||
content={i18n._(t`Select a webhook service`)}
|
||||
/>
|
||||
<AnsibleSelect
|
||||
isValid={isValid}
|
||||
id="webhook_service"
|
||||
data={webhookServiceOptions}
|
||||
value={webhookService}
|
||||
onChange={(event, val) => {
|
||||
setWebHookService(val);
|
||||
setWebHookCredential(null);
|
||||
form.setFieldValue('webhook_credential', null);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}}
|
||||
{({ form }) => (
|
||||
<FormGroup
|
||||
name="webhook_service"
|
||||
fieldId="webhook_service"
|
||||
helperTextInvalid={form.errors.webhook_service}
|
||||
label={i18n._(t`Webhook Service`)}
|
||||
>
|
||||
<FieldTooltip
|
||||
content={i18n._(t`Select a webhook service`)}
|
||||
/>
|
||||
<AnsibleSelect
|
||||
id="webhook_service"
|
||||
data={webhookServiceOptions}
|
||||
value={webhookService}
|
||||
onChange={(event, val) => {
|
||||
setWebHookService(val);
|
||||
setWebHookCredential(null);
|
||||
form.setFieldValue('webhook_service', val);
|
||||
form.setFieldValue('webhook_credential', null);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</Field>
|
||||
</FormRow>
|
||||
{credTypeId && (
|
||||
<FormRow>
|
||||
<Field name="webhook_credential">
|
||||
{({ form }) => {
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="webhook_credential"
|
||||
id="webhook_credential"
|
||||
name="webhook_credential"
|
||||
>
|
||||
<CredentialLookup
|
||||
label={i18n._(t`Webhook Credential`)}
|
||||
tooltip={i18n._(
|
||||
t`Optionally select the credential to use to send status updates back to the webhook service.`
|
||||
)}
|
||||
credentialTypeId={credTypeId || null}
|
||||
onChange={value => {
|
||||
setWebHookCredential(value);
|
||||
form.setFieldValue(
|
||||
'webhook_credential',
|
||||
value.id
|
||||
);
|
||||
}}
|
||||
value={webhookCredential}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}}
|
||||
{({ form }) => (
|
||||
<FormGroup
|
||||
fieldId="webhook_credential"
|
||||
id="webhook_credential"
|
||||
name="webhook_credential"
|
||||
>
|
||||
<CredentialLookup
|
||||
label={i18n._(t`Webhook Credential`)}
|
||||
tooltip={i18n._(
|
||||
t`Optionally select the credential to use to send status updates back to the webhook service.`
|
||||
)}
|
||||
credentialTypeId={credTypeId || null}
|
||||
onChange={value => {
|
||||
setWebHookCredential(value);
|
||||
form.setFieldValue('webhook_credential', value.id);
|
||||
}}
|
||||
value={webhookCredential}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</Field>
|
||||
</FormRow>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<FormSubmitError error={submitError} />
|
||||
<FormActionGroup
|
||||
onCancel={handleCancel}
|
||||
onSubmit={formik.handleSubmit}
|
||||
@ -436,7 +442,13 @@ function WorkflowJobTemplateForm({
|
||||
}
|
||||
|
||||
WorkflowJobTemplateForm.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
handleCancel: PropTypes.func.isRequired,
|
||||
handleSubmit: func.isRequired,
|
||||
handleCancel: func.isRequired,
|
||||
submitError: shape({}),
|
||||
};
|
||||
|
||||
WorkflowJobTemplateForm.defaultProps = {
|
||||
submitError: null,
|
||||
};
|
||||
|
||||
export default withI18n()(WorkflowJobTemplateForm);
|
||||
|
||||
@ -1,10 +1,20 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { sleep } from '@testUtils/testUtils';
|
||||
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import WorkflowJobTemplateForm from './WorkflowJobTemplateForm';
|
||||
import { WorkflowJobTemplatesAPI } from '../../../api';
|
||||
|
||||
jest.mock('@api/models/WorkflowJobTemplates');
|
||||
WorkflowJobTemplatesAPI.updateWebhookKey.mockResolvedValue({
|
||||
data: { webhook_key: 'sdafdghjkl2345678ionbvcxz' },
|
||||
});
|
||||
describe('<WorkflowJobTemplateForm/>', () => {
|
||||
let wrapper;
|
||||
let history;
|
||||
const handleSubmit = jest.fn();
|
||||
const handleCancel = jest.fn();
|
||||
const mockTemplate = {
|
||||
@ -21,15 +31,38 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
||||
scm_branch: 'devel',
|
||||
limit: '5000',
|
||||
variables: '---',
|
||||
related: {
|
||||
webhook_receiver: '/api/v2/workflow_job_templates/57/gitlab/',
|
||||
},
|
||||
};
|
||||
beforeEach(() => {
|
||||
history = createMemoryHistory({
|
||||
initialEntries: ['/templates/workflow_job_template/6/edit'],
|
||||
});
|
||||
act(() => {
|
||||
wrapper = mountWithContexts(
|
||||
<WorkflowJobTemplateForm
|
||||
template={mockTemplate}
|
||||
handleCancel={handleCancel}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
<Route
|
||||
path="/templates/workflow_job_template/:id/edit"
|
||||
component={() => (
|
||||
<WorkflowJobTemplateForm
|
||||
template={mockTemplate}
|
||||
handleCancel={handleCancel}
|
||||
handleSubmit={handleSubmit}
|
||||
webhook_key="sdfghjklmnbvcdsew435678iokjhgfd"
|
||||
/>
|
||||
)}
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
route: {
|
||||
location: history.location,
|
||||
match: { params: { id: 6 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -63,7 +96,10 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
||||
});
|
||||
test('changing inputs should update values', async () => {
|
||||
const inputsToChange = [
|
||||
{ element: 'wfjt-name', value: { value: 'new foo', name: 'name' } },
|
||||
{
|
||||
element: 'wfjt-name',
|
||||
value: { value: 'new foo', name: 'name' },
|
||||
},
|
||||
{
|
||||
element: 'wfjt-description',
|
||||
value: { value: 'new bar', name: 'description' },
|
||||
@ -76,12 +112,13 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
||||
];
|
||||
const changeInputs = async ({ element, value }) => {
|
||||
wrapper.find(`input#${element}`).simulate('change', {
|
||||
target: value,
|
||||
target: { value: `${value.value}`, name: `${value.name}` },
|
||||
});
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
inputsToChange.map(input => changeInputs(input));
|
||||
|
||||
wrapper.find('LabelSelect').invoke('onChange')([
|
||||
{ name: 'new label', id: 5 },
|
||||
{ name: 'Label 1', id: 1 },
|
||||
@ -97,13 +134,17 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
||||
});
|
||||
});
|
||||
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(
|
||||
typeof value.value === 'string' ? `${value.value}` : value.value
|
||||
`${value.value}`
|
||||
);
|
||||
};
|
||||
|
||||
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',
|
||||
@ -118,16 +159,57 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
||||
{ name: 'Label 2', id: 2 },
|
||||
]);
|
||||
});
|
||||
test('handleSubmit is called on submit button click', async () => {
|
||||
await act(async () => {
|
||||
await wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||
});
|
||||
|
||||
test('webhooks and enable concurrent jobs functions properly', async () => {
|
||||
act(() => {
|
||||
expect(handleSubmit).toBeCalled();
|
||||
wrapper.find('Checkbox[name="has_webhooks"]').invoke('onChange')(true);
|
||||
wrapper.find('Checkbox[name="allow_simultaneous"]').invoke('onChange')(
|
||||
true
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(
|
||||
wrapper.find('input[aria-label="wfjt-webhook-key"]').prop('readOnly')
|
||||
).toBe(true);
|
||||
expect(
|
||||
wrapper.find('input[aria-label="wfjt-webhook-key"]').prop('value')
|
||||
).toBe('sdfghjklmnbvcdsew435678iokjhgfd');
|
||||
await act(() =>
|
||||
wrapper
|
||||
.find('FormGroup[name="webhook_key"]')
|
||||
.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/');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper.find('Checkbox[name="has_webhooks"]').prop('isChecked')
|
||||
).toBe(true);
|
||||
expect(
|
||||
wrapper.find('Checkbox[name="allow_simultaneous"]').prop('isChecked')
|
||||
).toBe(true);
|
||||
expect(wrapper.find('Field[name="webhook_service"]').length).toBe(1);
|
||||
|
||||
act(() => wrapper.find('AnsibleSelect').prop('onChange')({}, 'gitlab'));
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('AnsibleSelect').prop('value')).toBe('gitlab');
|
||||
});
|
||||
test('handleCancel is called on cancel button click', () => {
|
||||
test('handleSubmit is called on submit button click', async () => {
|
||||
act(() => {
|
||||
wrapper.find('Formik').prop('onSubmit')();
|
||||
});
|
||||
wrapper.update();
|
||||
sleep(0);
|
||||
expect(handleSubmit).toBeCalled();
|
||||
});
|
||||
test('handleCancel is called on cancel button click', async () => {
|
||||
act(() => {
|
||||
wrapper.find('button[aria-label="Cancel"]').simulate('click');
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user