Fixes bug where email notification ssl and tls were a dropdown instead of checkboxes

This commit is contained in:
mabashian 2021-06-16 16:50:02 -04:00 committed by Shane McDonald
parent 2ce09d0dcc
commit c993c8b3b9
No known key found for this signature in database
GPG Key ID: 6F374AF6E9EB9374
9 changed files with 518 additions and 87 deletions

View File

@ -15,7 +15,6 @@ import { FormSelect, FormSelectOption } from '@patternfly/react-core';
function AnsibleSelect({
id,
data,
isValid,
onBlur,
value,

View File

@ -25,7 +25,7 @@ function ArrayDetail({ label, value, dataCy }) {
</DetailName>
<Value component={TextListItemVariants.dd} data-cy={valueCy}>
{vals.map(v => (
<div>{v}</div>
<div key={v}>{v}</div>
))}
</Value>
</div>

View File

@ -1,7 +1,12 @@
import React, { useCallback } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { Button } from '@patternfly/react-core';
import {
Button,
TextList,
TextListItem,
TextListItemVariants,
TextListVariants,
} from '@patternfly/react-core';
import { t } from '@lingui/macro';
import AlertModal from '../../../components/AlertModal';
import { CardBody, CardActionsRow } from '../../../components/Card';
@ -31,6 +36,23 @@ function NotificationTemplateDetail({ template, defaultMessages }) {
messages,
} = template;
const renderOptionsField = configuration.use_ssl || configuration.use_tls;
const renderOptions = (
<TextList component={TextListVariants.ul}>
{configuration.use_ssl && (
<TextListItem component={TextListItemVariants.li}>
{t`Use SSL`}
</TextListItem>
)}
{configuration.use_tls && (
<TextListItem component={TextListItemVariants.li}>
{t`Use TLS`}
</TextListItem>
)}
</TextList>
);
const { request: deleteTemplate, isLoading, error: deleteError } = useRequest(
useCallback(async () => {
await NotificationTemplatesAPI.destroy(template.id);
@ -104,11 +126,9 @@ function NotificationTemplateDetail({ template, defaultMessages }) {
value={configuration.timeout}
dataCy="nt-detail-timeout"
/>
<Detail
label={t`Email Options`}
value={configuration.use_ssl ? t`Use SSL` : t`Use TLS`}
dataCy="nt-detail-email-options"
/>
{renderOptionsField && (
<Detail label={t`Email Options`} value={renderOptions} />
)}
</>
)}
{template.notification_type === 'grafana' && (

View File

@ -0,0 +1,102 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import {
mountWithContexts,
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import NotificationTemplateDetail from './NotificationTemplateDetail';
import defaultMessages from '../shared/notification-template-default-messages.json';
jest.mock('../../../api');
const mockTemplate = {
id: 1,
type: 'notification_template',
url: '/api/v2/notification_templates/1/',
related: {
named_url: '/api/v2/notification_templates/abc++Default/',
created_by: '/api/v2/users/2/',
modified_by: '/api/v2/users/2/',
test: '/api/v2/notification_templates/1/test/',
notifications: '/api/v2/notification_templates/1/notifications/',
copy: '/api/v2/notification_templates/1/copy/',
organization: '/api/v2/organizations/1/',
},
summary_fields: {
organization: {
id: 1,
name: 'Default',
description: '',
},
created_by: {
id: 2,
username: 'test',
first_name: '',
last_name: '',
},
modified_by: {
id: 2,
username: 'test',
first_name: '',
last_name: '',
},
user_capabilities: {
edit: true,
delete: true,
copy: true,
},
recent_notifications: [],
},
created: '2021-06-16T18:52:23.811374Z',
modified: '2021-06-16T18:53:37.631371Z',
name: 'abc',
description: 'foo description',
organization: 1,
notification_type: 'email',
notification_configuration: {
username: '',
password: '',
host: 'https://localhost',
recipients: ['foo@ansible.com'],
sender: 'bar@ansible.com',
port: 324,
timeout: 11,
use_ssl: true,
use_tls: true,
},
messages: null,
};
describe('<NotificationTemplateDetail />', () => {
let wrapper;
beforeEach(async () => {
await act(async () => {
wrapper = mountWithContexts(
<NotificationTemplateDetail
template={mockTemplate}
defaultMessages={defaultMessages}
/>
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
});
afterEach(() => {
jest.clearAllMocks();
});
test('should render Details', () => {
function assertDetail(label, value) {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
}
assertDetail('Name', mockTemplate.name);
assertDetail('Description', mockTemplate.description);
expect(
wrapper
.find('Detail[label="Email Options"]')
.containsAllMatchingElements([<li>Use SSL</li>, <li>Use TLS</li>])
).toEqual(true);
});
});

View File

@ -118,10 +118,6 @@ function NotificationTemplateForm({
);
};
let emailOptions = '';
if (template.notification_type === 'email') {
emailOptions = template.notification_configuration?.use_ssl ? 'ssl' : 'tls';
}
const messages = template.messages || { workflow_approval: {} };
const defs = defaultMessages[template.notification_type || 'email'];
const mergeDefaultMessages = (templ = {}, def) => {
@ -144,7 +140,6 @@ function NotificationTemplateForm({
...template.notification_configuration,
headers: headers ? JSON.stringify(headers, null, 2) : null,
},
emailOptions,
organization: template.summary_fields?.organization,
messages: {
started: { ...mergeDefaultMessages(messages.started, defs.started) },
@ -235,10 +230,6 @@ function normalizeTypeFields(values) {
stripped[fieldName] = values.notification_configuration[fieldName];
}
});
if (values.notification_type === 'email') {
stripped.use_ssl = values.emailOptions === 'ssl';
stripped.use_tls = !stripped.use_ssl;
}
if (values.notification_type === 'webhook') {
stripped.headers = stripped.headers ? JSON.parse(stripped.headers) : {};
}

View File

@ -4,6 +4,7 @@ import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import NotificationTemplateForm from './NotificationTemplateForm';
jest.mock('../../../api/models/NotificationTemplates');
jest.mock('../../../api/models/Organizations');
const template = {
id: 3,
@ -50,16 +51,19 @@ const defaultMessages = {
};
describe('<NotificationTemplateForm />', () => {
test('should render form fields', () => {
const wrapper = mountWithContexts(
<NotificationTemplateForm
template={template}
defaultMessages={defaultMessages}
detailUrl="/notification_templates/3/detail"
onSubmit={jest.fn()}
onCancel={jest.fn()}
/>
);
let wrapper;
test('should render form fields', async () => {
await act(async () => {
wrapper = mountWithContexts(
<NotificationTemplateForm
template={template}
defaultMessages={defaultMessages}
detailUrl="/notification_templates/3/detail"
onSubmit={jest.fn()}
onCancel={jest.fn()}
/>
);
});
expect(wrapper.find('input#notification-name').prop('value')).toEqual(
'Test Notification'
@ -77,26 +81,48 @@ describe('<NotificationTemplateForm />', () => {
expect(
wrapper.find('CustomMessagesSubForm').prop('defaultMessages')
).toEqual(defaultMessages);
expect(wrapper.find('input#option-use-ssl').length).toBe(0);
expect(wrapper.find('input#option-use-tls').length).toBe(0);
await act(async () => {
wrapper.find('AnsibleSelect#notification-type').invoke('onChange')(
{
target: {
name: 'notification_type',
value: 'email',
},
},
'email'
);
});
wrapper.update();
expect(wrapper.find('input#option-use-ssl').length).toBe(1);
expect(wrapper.find('input#option-use-tls').length).toBe(1);
});
test('should render custom messages fields', () => {
const wrapper = mountWithContexts(
<NotificationTemplateForm
template={{
...template,
messages: {
started: {
message: 'Started',
body: null,
test('should render custom messages fields', async () => {
await act(async () => {
wrapper = mountWithContexts(
<NotificationTemplateForm
template={{
...template,
messages: {
started: {
message: 'Started',
body: null,
},
},
},
}}
defaultMessages={defaultMessages}
detailUrl="/notification_templates/3/detail"
onSubmit={jest.fn()}
onCancel={jest.fn()}
/>
);
}}
defaultMessages={defaultMessages}
detailUrl="/notification_templates/3/detail"
onSubmit={jest.fn()}
onCancel={jest.fn()}
/>
);
});
expect(
wrapper
@ -108,21 +134,23 @@ describe('<NotificationTemplateForm />', () => {
test('should submit', async () => {
const handleSubmit = jest.fn();
const wrapper = mountWithContexts(
<NotificationTemplateForm
template={{
...template,
notification_configuration: {
channels: ['#foo'],
token: 'abc123',
},
}}
defaultMessages={defaultMessages}
detailUrl="/notification_templates/3/detail"
onSubmit={handleSubmit}
onCancel={jest.fn()}
/>
);
await act(async () => {
wrapper = mountWithContexts(
<NotificationTemplateForm
template={{
...template,
notification_configuration: {
channels: ['#foo'],
token: 'abc123',
},
}}
defaultMessages={defaultMessages}
detailUrl="/notification_templates/3/detail"
onSubmit={handleSubmit}
onCancel={jest.fn()}
/>
);
});
await act(async () => {
wrapper.find('FormActionGroup').invoke('onSubmit')();

View File

@ -4,6 +4,7 @@ import { t } from '@lingui/macro';
import { useField } from 'formik';
import { FormGroup, Title } from '@patternfly/react-core';
import {
FormCheckboxLayout,
FormColumnLayout,
FormFullWidthLayout,
SubFormLayout,
@ -56,10 +57,6 @@ TypeInputsSubForm.propTypes = {
export default TypeInputsSubForm;
function EmailFields() {
const [optionsField, optionsMeta] = useField({
name: 'emailOptions',
validate: required(t`Select a value for this field`),
});
return (
<>
<FormField
@ -123,29 +120,19 @@ function EmailFields() {
notification stops trying to reach the host and times out. Ranges
from 1 to 120 seconds.`}
/>
<FormGroup
fieldId="email-options"
helperTextInvalid={optionsMeta.error}
isRequired
validated={
!optionsMeta.touched || !optionsMeta.error ? 'default' : 'error'
}
label={t`E-mail options`}
>
<AnsibleSelect
{...optionsField}
id="email-options"
data={[
{
value: '',
key: '',
label: t`Choose an email option`,
isDisabled: true,
},
{ value: 'tls', key: 'tls', label: t`Use TLS` },
{ value: 'ssl', key: 'ssl', label: t`Use SSL` },
]}
/>
<FormGroup fieldId="email-options" label={t`E-mail options`}>
<FormCheckboxLayout>
<CheckboxField
id="option-use-ssl"
name="notification_configuration.use_ssl"
label={t`Use SSL`}
/>
<CheckboxField
id="option-use-tls"
name="notification_configuration.use_tls"
label={t`Use TLS`}
/>
</FormCheckboxLayout>
</FormGroup>
</>
);

View File

@ -0,0 +1,302 @@
{
"type": "json",
"required": false,
"label": "Messages",
"help_text": "Optional custom messages for notification template.",
"filterable": true,
"default": {
"started": null,
"success": null,
"error": null,
"workflow_approval": null
},
"email": {
"started": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": "{{ job_friendly_name }} #{{ job.id }} had status {{ job.status }}, view details at {{ url }}\n\n{{ job_metadata }}"
},
"success": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": "{{ job_friendly_name }} #{{ job.id }} had status {{ job.status }}, view details at {{ url }}\n\n{{ job_metadata }}"
},
"error": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": "{{ job_friendly_name }} #{{ job.id }} had status {{ job.status }}, view details at {{ url }}\n\n{{ job_metadata }}"
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" needs review. This approval node can be viewed at: {{ workflow_url }}\n\n{{ job_metadata }}"
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}\n\n{{ job_metadata }}"
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}\n\n{{ job_metadata }}"
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}\n\n{{ job_metadata }}"
}
}
},
"slack": {
"started": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"success": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"error": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": null
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": null
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": null
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": null
}
}
},
"twilio": {
"started": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"success": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"error": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": null
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": null
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": null
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": null
}
}
},
"pagerduty": {
"started": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": "{{ job_metadata }}"
},
"success": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": "{{ job_metadata }}"
},
"error": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": "{{ job_metadata }}"
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" needs review. This approval node can be viewed at: {{ workflow_url }}\n\n{{ job_metadata }}"
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}\n\n{{ job_metadata }}"
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}\n\n{{ job_metadata }}"
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}\n\n{{ job_metadata }}"
}
}
},
"grafana": {
"started": {
"body": "{{ job_metadata }}",
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}"
},
"success": {
"body": "{{ job_metadata }}",
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}"
},
"error": {
"body": "{{ job_metadata }}",
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}"
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" needs review. This approval node can be viewed at: {{ workflow_url }}\n\n{{ job_metadata }}"
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}\n\n{{ job_metadata }}"
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}\n\n{{ job_metadata }}"
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}\n\n{{ job_metadata }}"
}
}
},
"webhook": {
"started": {
"body": "{{ job_metadata }}"
},
"success": {
"body": "{{ job_metadata }}"
},
"error": {
"body": "{{ job_metadata }}"
},
"workflow_approval": {
"running": {
"body": {
"body": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}"
}
},
"approved": {
"body": {
"body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}"
}
},
"timed_out": {
"body": {
"body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}"
}
},
"denied": {
"body": {
"body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}"
}
}
}
},
"mattermost": {
"started": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"success": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"error": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": null
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": null
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": null
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": null
}
}
},
"rocketchat": {
"started": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"success": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"error": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": null
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": null
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": null
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": null
}
}
},
"irc": {
"started": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"success": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"error": {
"message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
"body": null
},
"workflow_approval": {
"running": {
"message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
"body": null
},
"approved": {
"message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
"body": null
},
"timed_out": {
"message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
"body": null
},
"denied": {
"message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
"body": null
}
}
}
}

View File

@ -7,6 +7,8 @@ const typeFieldNames = {
'sender',
'port',
'timeout',
'use_ssl',
'use_tls',
],
grafana: [
'grafana_url',