add custom notification messages subform

This commit is contained in:
Keith Grant 2020-08-19 16:11:58 -07:00
parent bb12e0a3a9
commit ba95775ded
6 changed files with 371 additions and 9 deletions

View File

@ -0,0 +1,73 @@
import React from 'react';
import {
string,
oneOfType,
object,
func,
bool,
node,
oneOf,
number,
} from 'prop-types';
import { useField } from 'formik';
import { FormGroup } from '@patternfly/react-core';
import CodeMirrorInput from './CodeMirrorInput';
import { FieldTooltip } from '../FormField';
function CodeMirrorField({
id,
name,
label,
tooltip,
helperText,
validate,
isRequired,
mode,
...rest
}) {
const [field, meta, helpers] = useField({ name, validate });
const isValid = !(meta.touched && meta.error);
return (
<FormGroup
fieldId={id}
helperText={helperText}
helperTextInvalid={meta.error}
isRequired={isRequired}
validated={isValid ? 'default' : 'error'}
label={label}
labelIcon={<FieldTooltip content={tooltip} />}
>
<CodeMirrorInput
id="webhook-headers"
{...rest}
{...field}
onChange={value => {
helpers.setValue(value);
}}
mode={mode}
/>
</FormGroup>
);
}
CodeMirrorField.propTypes = {
helperText: string,
id: string.isRequired,
name: string.isRequired,
label: oneOfType([object, string]).isRequired,
validate: func,
isRequired: bool,
tooltip: node,
mode: oneOf(['javascript', 'yaml', 'jinja2']).isRequired,
rows: number,
};
CodeMirrorField.defaultProps = {
helperText: '',
validate: () => {},
isRequired: false,
tooltip: null,
rows: 5,
};
export default CodeMirrorField;

View File

@ -1,6 +1,7 @@
import CodeMirrorInput from './CodeMirrorInput';
export default CodeMirrorInput;
export { default as CodeMirrorField } from './CodeMirrorField';
export { default as VariablesDetail } from './VariablesDetail';
export { default as VariablesInput } from './VariablesInput';
export { default as VariablesField } from './VariablesField';

View File

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import { CardBody } from '../../../components/Card';
import { OrganizationsAPI } from '../../../api';
import { Config } from '../../../contexts/Config';
import NotificationTemplateForm from '../shared/NotificationTemplateForm';

View File

@ -0,0 +1,213 @@
import 'styled-components/macro';
import React from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { useField } from 'formik';
import {
FormGroup,
Title,
Switch,
Text,
TextVariants,
} from '@patternfly/react-core';
import {
FormColumnLayout,
FormFullWidthLayout,
SubFormLayout,
} from '../../../components/FormLayout';
import FormField, {
PasswordField,
CheckboxField,
FieldTooltip,
} from '../../../components/FormField';
import AnsibleSelect from '../../../components/AnsibleSelect';
import { CodeMirrorField } from '../../../components/CodeMirrorInput';
import {
combine,
required,
requiredEmail,
url,
} from '../../../util/validators';
import { NotificationType } from '../../../types';
function CustomMessagesSubForm({ defaultMessages, type, i18n }) {
const [useCustomField, , useCustomHelpers] = useField('useCustomMessages');
const showMessages = type !== 'webhook';
const showBodies = ['email', 'pagerduty', 'webhook'].includes(type);
return (
<>
<Switch
id="toggle-custom-messages"
label={i18n._(t`Customize messages…`)}
isChecked={!!useCustomField.value}
onChange={() => useCustomHelpers.setValue(!useCustomField.value)}
/>
{useCustomField.value && (
<SubFormLayout>
<Text
className="pf-c-content"
css="margin-bottom: var(--pf-c-content--MarginBottom)"
>
<small>
Use custom messages to change the content of notifications sent
when a job starts, succeeds, or fails. Use curly braces to access
information about the job:{' '}
<code>
{'{{'} job_friendly_name {'}}'}
</code>
,{' '}
<code>
{'{{'} url {'}}'}
</code>
, or attributes of the job such as{' '}
<code>
{'{{'} job.status {'}}'}
</code>
. You may apply a number of possible variables in the message.
Refer to the{' '}
<a
href="https://docs.ansible.com/ansible-tower/latest/html/userguide/notifications.html#create-custom-notifications"
target="_blank"
rel="noopener noreferrer"
>
Ansible Tower documentation
</a>{' '}
for more details.
</small>
</Text>
<FormFullWidthLayout>
{showMessages && (
<CodeMirrorField
id="start-message"
name="messages.started.message"
label={i18n._(t`Start message`)}
mode="jinja2"
rows={2}
/>
)}
{showBodies && (
<CodeMirrorField
id="start-body"
name="messages.started.body"
label={i18n._(t`Start message body`)}
mode="jinja2"
rows={6}
/>
)}
{showMessages && (
<CodeMirrorField
id="success-message"
name="messages.success.message"
label={i18n._(t`Success message`)}
mode="jinja2"
rows={2}
/>
)}
{showBodies && (
<CodeMirrorField
id="success-body"
name="messages.success.body"
label={i18n._(t`Success message body`)}
mode="jinja2"
rows={6}
/>
)}
{showMessages && (
<CodeMirrorField
id="error-message"
name="messages.error.message"
label={i18n._(t`Error message`)}
mode="jinja2"
rows={2}
/>
)}
{showBodies && (
<CodeMirrorField
id="error-body"
name="messages.error.body"
label={i18n._(t`Error message body`)}
mode="jinja2"
rows={6}
/>
)}
{showMessages && (
<CodeMirrorField
id="wf-approved-message"
name="messages.workflow_approval.approved.message"
label={i18n._(t`Workflow approved message`)}
mode="jinja2"
rows={2}
/>
)}
{showBodies && (
<CodeMirrorField
id="wf-approved-body"
name="messages.workflow_approval.approved.body"
label={i18n._(t`Workflow approved message body`)}
mode="jinja2"
rows={6}
/>
)}
{showMessages && (
<CodeMirrorField
id="wf-denied-message"
name="messages.workflow_approval.denied.message"
label={i18n._(t`Workflow denied message`)}
mode="jinja2"
rows={2}
/>
)}
{showBodies && (
<CodeMirrorField
id="wf-denied-body"
name="messages.workflow_approval.denied.body"
label={i18n._(t`Workflow denied message body`)}
mode="jinja2"
rows={6}
/>
)}
{showMessages && (
<CodeMirrorField
id="wf-running-message"
name="messages.workflow_approval.running.message"
label={i18n._(t`Workflow pending message`)}
mode="jinja2"
rows={2}
/>
)}
{showBodies && (
<CodeMirrorField
id="wf-running-body"
name="messages.workflow_approval.running.body"
label={i18n._(t`Workflow pending message body`)}
mode="jinja2"
rows={6}
/>
)}
{showMessages && (
<CodeMirrorField
id="wf-timed-out-message"
name="messages.workflow_approval.timed_out.message"
label={i18n._(t`Workflow timed out message`)}
mode="jinja2"
rows={2}
/>
)}
{showBodies && (
<CodeMirrorField
id="wf-timed-out-body"
name="messages.workflow_approval.timed_out.body"
label={i18n._(t`Workflow timed out message body`)}
mode="jinja2"
rows={6}
/>
)}
</FormFullWidthLayout>
</SubFormLayout>
)}
</>
);
}
export default withI18n()(CustomMessagesSubForm);

View File

@ -1,22 +1,18 @@
import React, { useContext, useEffect, useState } from 'react';
import React from 'react';
import { shape, func } from 'prop-types';
import { Formik, useField } from 'formik';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Form, FormGroup } from '@patternfly/react-core';
import { OrganizationsAPI } from '../../../api';
import { ConfigContext } from '../../../contexts/Config';
import AnsibleSelect from '../../../components/AnsibleSelect';
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
import FormField, { FormSubmitError } from '../../../components/FormField';
import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup';
import { OrganizationLookup } from '../../../components/Lookup';
import { getAddedAndRemoved } from '../../../util/lists';
import { required, minMaxValue } from '../../../util/validators';
import { required } from '../../../util/validators';
import { FormColumnLayout } from '../../../components/FormLayout';
import TypeInputsSubForm from './TypeInputsSubForm';
import CustomMessagesSubForm from './CustomMessagesSubForm';
import typeFieldNames, { initialConfigValues } from './typeFieldNames';
import { NotificationTemplate } from '../../../types';
@ -85,6 +81,10 @@ function NotificationTemplateFormFields({ i18n, defaultMessages }) {
/>
</FormGroup>
{typeField.value && <TypeInputsSubForm type={typeField.value} />}
<CustomMessagesSubForm
defaultMessages={defaultMessages}
type={typeField.value}
/>
</>
);
}
@ -105,6 +105,14 @@ function NotificationTemplateForm({
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) => {
return {
message: templ.message || def.message || '',
body: templ.body || def.body || '',
};
};
return (
<Formik
@ -117,6 +125,38 @@ function NotificationTemplateForm({
...template.notification_configuration,
},
emailOptions,
messages: {
started: { ...mergeDefaultMessages(messages.started, defs.started) },
success: { ...mergeDefaultMessages(messages.success, defs.success) },
error: { ...mergeDefaultMessages(messages.error, defs.error) },
workflow_approval: {
approved: {
...mergeDefaultMessages(
messages.workflow_approval.approved,
defs.workflow_approval.approved
),
},
denied: {
...mergeDefaultMessages(
messages.workflow_approval.denied,
defs.workflow_approval.denied
),
},
running: {
...mergeDefaultMessages(
messages.workflow_approval.running,
defs.workflow_approval.running
),
},
timed_out: {
...mergeDefaultMessages(
messages.workflow_approval.timed_out,
defs.workflow_approval.timed_out
),
},
},
},
useCustomMessages: hasCustomMessages(messages, defs),
}}
onSubmit={handleSubmit}
>
@ -155,6 +195,42 @@ NotificationTemplateForm.defaultProps = {
export default withI18n()(NotificationTemplateForm);
function hasCustomMessages(messages, defaults) {
return (
isCustomized(messages.started, defaults.started) ||
isCustomized(messages.success, defaults.success) ||
isCustomized(messages.error, defaults.error) ||
isCustomized(
messages.workflow_approval.approved,
defaults.workflow_approval.approved
) ||
isCustomized(
messages.workflow_approval.denied,
defaults.workflow_approval.denied
) ||
isCustomized(
messages.workflow_approval.running,
defaults.workflow_approval.running
) ||
isCustomized(
messages.workflow_approval.timed_out,
defaults.workflow_approval.timed_out
)
);
}
function isCustomized(message, defaultMessage) {
if (!message) {
return false;
}
if (!message.message || message.message !== defaultMessage.message) {
return true;
}
if (!message.body || message.body !== defaultMessage.body) {
return true;
}
return false;
}
/* If the user filled in some of the Type Details fields, then switched
* to a different notification type, unecessary fields may be set in the
* notification_configuration this function strips them off */

View File

@ -1,7 +1,7 @@
import React from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { useField, useFormikContext } from 'formik';
import { useField } from 'formik';
import { FormGroup, Title } from '@patternfly/react-core';
import {
FormColumnLayout,