diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorField.jsx b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorField.jsx new file mode 100644 index 0000000000..60d7896a38 --- /dev/null +++ b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorField.jsx @@ -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 ( + } + > + { + helpers.setValue(value); + }} + mode={mode} + /> + + ); +} +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; diff --git a/awx/ui_next/src/components/CodeMirrorInput/index.js b/awx/ui_next/src/components/CodeMirrorInput/index.js index 9cad016228..2c60b806f5 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/index.js +++ b/awx/ui_next/src/components/CodeMirrorInput/index.js @@ -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'; diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateEdit/NotificationTemplateEdit.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateEdit/NotificationTemplateEdit.jsx index e57a01fd4e..9445f9c7be 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateEdit/NotificationTemplateEdit.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateEdit/NotificationTemplateEdit.jsx @@ -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'; diff --git a/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx new file mode 100644 index 0000000000..8bca667b82 --- /dev/null +++ b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx @@ -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 ( + <> + useCustomHelpers.setValue(!useCustomField.value)} + /> + {useCustomField.value && ( + + + + 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:{' '} + + {'{{'} job_friendly_name {'}}'} + + ,{' '} + + {'{{'} url {'}}'} + + , or attributes of the job such as{' '} + + {'{{'} job.status {'}}'} + + . You may apply a number of possible variables in the message. + Refer to the{' '} + + Ansible Tower documentation + {' '} + for more details. + + + + {showMessages && ( + + )} + {showBodies && ( + + )} + {showMessages && ( + + )} + {showBodies && ( + + )} + {showMessages && ( + + )} + {showBodies && ( + + )} + {showMessages && ( + + )} + {showBodies && ( + + )} + {showMessages && ( + + )} + {showBodies && ( + + )} + {showMessages && ( + + )} + {showBodies && ( + + )} + {showMessages && ( + + )} + {showBodies && ( + + )} + + + )} + + ); +} + +export default withI18n()(CustomMessagesSubForm); diff --git a/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.jsx b/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.jsx index 300fea5a59..0efe8b764b 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/shared/NotificationTemplateForm.jsx @@ -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 }) { /> {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 ( @@ -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 */ diff --git a/awx/ui_next/src/screens/NotificationTemplate/shared/TypeInputsSubForm.jsx b/awx/ui_next/src/screens/NotificationTemplate/shared/TypeInputsSubForm.jsx index 253430da8d..23d21a5dcc 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/shared/TypeInputsSubForm.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/shared/TypeInputsSubForm.jsx @@ -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,