mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 01:57:35 -03:30
Merge pull request #8105 from keithjgrant/7877-notification-custom-messages
Notification Detail: show custom messages Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
432e167930
35
awx/ui_next/src/components/DetailList/ArrayDetail.jsx
Normal file
35
awx/ui_next/src/components/DetailList/ArrayDetail.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import 'styled-components/macro';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { TextListItemVariants } from '@patternfly/react-core';
|
||||
import { DetailName, DetailValue } from './Detail';
|
||||
|
||||
const Value = styled(DetailValue)`
|
||||
margin-top: var(--pf-global--spacer--xs);
|
||||
padding: var(--pf-global--spacer--xs);
|
||||
border: 1px solid var(--pf-global--BorderColor--100);
|
||||
max-height: 5.5em;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
function ArrayDetail({ label, value, dataCy }) {
|
||||
const labelCy = dataCy ? `${dataCy}-label` : null;
|
||||
const valueCy = dataCy ? `${dataCy}-value` : null;
|
||||
|
||||
const vals = Array.isArray(value) ? value : [value];
|
||||
|
||||
return (
|
||||
<div css="grid-column: span 2">
|
||||
<DetailName component={TextListItemVariants.dt} data-cy={labelCy}>
|
||||
{label}
|
||||
</DetailName>
|
||||
<Value component={TextListItemVariants.dd} data-cy={valueCy}>
|
||||
{vals.map(v => (
|
||||
<div>{v}</div>
|
||||
))}
|
||||
</Value>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArrayDetail;
|
||||
@ -1,11 +1,11 @@
|
||||
import 'styled-components/macro';
|
||||
import React from 'react';
|
||||
import { shape, node, number } from 'prop-types';
|
||||
import { shape, node, number, oneOf } from 'prop-types';
|
||||
import { TextListItemVariants } from '@patternfly/react-core';
|
||||
import { DetailName, DetailValue } from './Detail';
|
||||
import CodeMirrorInput from '../CodeMirrorInput';
|
||||
|
||||
function ObjectDetail({ value, label, rows, fullHeight }) {
|
||||
function CodeDetail({ value, label, mode, rows, fullHeight }) {
|
||||
return (
|
||||
<>
|
||||
<DetailName
|
||||
@ -28,8 +28,8 @@ function ObjectDetail({ value, label, rows, fullHeight }) {
|
||||
css="grid-column: 1 / -1; margin-top: -20px"
|
||||
>
|
||||
<CodeMirrorInput
|
||||
mode="json"
|
||||
value={JSON.stringify(value)}
|
||||
mode={mode}
|
||||
value={value}
|
||||
readOnly
|
||||
rows={rows}
|
||||
fullHeight={fullHeight}
|
||||
@ -39,13 +39,14 @@ function ObjectDetail({ value, label, rows, fullHeight }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
ObjectDetail.propTypes = {
|
||||
CodeDetail.propTypes = {
|
||||
value: shape.isRequired,
|
||||
label: node.isRequired,
|
||||
rows: number,
|
||||
mode: oneOf(['json', 'yaml', 'jinja2']).isRequired,
|
||||
};
|
||||
ObjectDetail.defaultProps = {
|
||||
CodeDetail.defaultProps = {
|
||||
rows: null,
|
||||
};
|
||||
|
||||
export default ObjectDetail;
|
||||
export default CodeDetail;
|
||||
@ -11,7 +11,7 @@ const DetailList = ({ children, stacked, ...props }) => (
|
||||
export default styled(DetailList)`
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
${props =>
|
||||
props.stacked
|
||||
? `
|
||||
|
||||
@ -3,8 +3,9 @@ export { default as Detail, DetailName, DetailValue } from './Detail';
|
||||
export { default as DeletedDetail } from './DeletedDetail';
|
||||
export { default as UserDateDetail } from './UserDateDetail';
|
||||
export { default as DetailBadge } from './DetailBadge';
|
||||
export { default as ArrayDetail } from './ArrayDetail';
|
||||
/*
|
||||
NOTE: ObjectDetail cannot be imported here, as it causes circular
|
||||
NOTE: CodeDetail cannot be imported here, as it causes circular
|
||||
dependencies in testing environment. Import it directly from
|
||||
DetailList/ObjectDetail
|
||||
*/
|
||||
|
||||
@ -407,12 +407,12 @@ exports[`<ResourceAccessListItem /> initially renders succesfully 1`] = `
|
||||
"componentStyle": ComponentStyle {
|
||||
"componentId": "sc-bwzfXH",
|
||||
"isStatic": false,
|
||||
"lastClassName": "kVCDmm",
|
||||
"lastClassName": "gAzXep",
|
||||
"rules": Array [
|
||||
"
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
",
|
||||
[Function],
|
||||
"
|
||||
@ -433,15 +433,15 @@ exports[`<ResourceAccessListItem /> initially renders succesfully 1`] = `
|
||||
stacked={true}
|
||||
>
|
||||
<DetailList
|
||||
className="sc-bwzfXH kVCDmm"
|
||||
className="sc-bwzfXH gAzXep"
|
||||
stacked={true}
|
||||
>
|
||||
<TextList
|
||||
className="sc-bwzfXH kVCDmm"
|
||||
className="sc-bwzfXH gAzXep"
|
||||
component="dl"
|
||||
>
|
||||
<dl
|
||||
className="sc-bwzfXH kVCDmm"
|
||||
className="sc-bwzfXH gAzXep"
|
||||
data-pf-content={true}
|
||||
>
|
||||
<Detail
|
||||
@ -630,12 +630,12 @@ exports[`<ResourceAccessListItem /> initially renders succesfully 1`] = `
|
||||
"componentStyle": ComponentStyle {
|
||||
"componentId": "sc-bwzfXH",
|
||||
"isStatic": false,
|
||||
"lastClassName": "kVCDmm",
|
||||
"lastClassName": "gAzXep",
|
||||
"rules": Array [
|
||||
"
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
",
|
||||
[Function],
|
||||
"
|
||||
@ -656,15 +656,15 @@ exports[`<ResourceAccessListItem /> initially renders succesfully 1`] = `
|
||||
stacked={true}
|
||||
>
|
||||
<DetailList
|
||||
className="sc-bwzfXH kVCDmm"
|
||||
className="sc-bwzfXH gAzXep"
|
||||
stacked={true}
|
||||
>
|
||||
<TextList
|
||||
className="sc-bwzfXH kVCDmm"
|
||||
className="sc-bwzfXH gAzXep"
|
||||
component="dl"
|
||||
>
|
||||
<dl
|
||||
className="sc-bwzfXH kVCDmm"
|
||||
className="sc-bwzfXH gAzXep"
|
||||
data-pf-content={true}
|
||||
>
|
||||
<Detail
|
||||
|
||||
@ -106,6 +106,7 @@ function NotificationTemplate({ setBreadcrumb, i18n }) {
|
||||
<Route path="/notification_templates/:id/details">
|
||||
<NotificationTemplateDetail
|
||||
template={template}
|
||||
defaultMessages={defaultMessages}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
@ -7,22 +7,25 @@ import AlertModal from '../../../components/AlertModal';
|
||||
import { CardBody, CardActionsRow } from '../../../components/Card';
|
||||
import {
|
||||
Detail,
|
||||
ArrayDetail,
|
||||
DetailList,
|
||||
DeletedDetail,
|
||||
} from '../../../components/DetailList';
|
||||
import ObjectDetail from '../../../components/DetailList/ObjectDetail';
|
||||
import CodeDetail from '../../../components/DetailList/CodeDetail';
|
||||
import DeleteButton from '../../../components/DeleteButton';
|
||||
import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import { NotificationTemplatesAPI } from '../../../api';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import hasCustomMessages from '../shared/hasCustomMessages';
|
||||
import { NOTIFICATION_TYPES } from '../constants';
|
||||
|
||||
function NotificationTemplateDetail({ i18n, template }) {
|
||||
function NotificationTemplateDetail({ i18n, template, defaultMessages }) {
|
||||
const history = useHistory();
|
||||
|
||||
const {
|
||||
notification_configuration: configuration,
|
||||
summary_fields,
|
||||
messages,
|
||||
} = template;
|
||||
|
||||
const { request: deleteTemplate, isLoading, error: deleteError } = useRequest(
|
||||
@ -33,6 +36,7 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
);
|
||||
|
||||
const { error, dismissError } = useDismissableError(deleteError);
|
||||
const typeMessageDefaults = defaultMessages[template.notification_type];
|
||||
|
||||
return (
|
||||
<CardBody>
|
||||
@ -81,9 +85,9 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
value={configuration.host}
|
||||
dataCy="nt-detail-host"
|
||||
/>
|
||||
<Detail
|
||||
<ArrayDetail
|
||||
label={i18n._(t`Recipient List`)}
|
||||
value={configuration.recipients} // array
|
||||
value={configuration.recipients}
|
||||
dataCy="nt-detail-recipients"
|
||||
/>
|
||||
<Detail
|
||||
@ -127,9 +131,9 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
value={configuration.panelId}
|
||||
dataCy="nt-detail-panel-id"
|
||||
/>
|
||||
<Detail
|
||||
<ArrayDetail
|
||||
label={i18n._(t`Tags for the Annotation`)}
|
||||
value={configuration.annotation_tags} // array
|
||||
value={configuration.annotation_tags}
|
||||
dataCy="nt-detail-"
|
||||
/>
|
||||
<Detail
|
||||
@ -160,9 +164,9 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
value={configuration.nickname}
|
||||
dataCy="nt-detail-irc-nickname"
|
||||
/>
|
||||
<Detail
|
||||
<ArrayDetail
|
||||
label={i18n._(t`Destination Channels or Users`)}
|
||||
value={configuration.targets} // array
|
||||
value={configuration.targets}
|
||||
dataCy="nt-detail-channels"
|
||||
/>
|
||||
<Detail
|
||||
@ -254,9 +258,9 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
)}
|
||||
{template.notification_type === 'slack' && (
|
||||
<>
|
||||
<Detail
|
||||
<ArrayDetail
|
||||
label={i18n._(t`Destination Channels`)}
|
||||
value={configuration.channels} // array
|
||||
value={configuration.channels}
|
||||
dataCy="nt-detail-slack-channels"
|
||||
/>
|
||||
<Detail
|
||||
@ -273,9 +277,9 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
value={configuration.from_number}
|
||||
dataCy="nt-detail-twilio-source-phone"
|
||||
/>
|
||||
<Detail
|
||||
label={i18n._(t`Destination SMS Number`)}
|
||||
value={configuration.to_numbers} // array
|
||||
<ArrayDetail
|
||||
label={i18n._(t`Destination SMS Number(s)`)}
|
||||
value={configuration.to_numbers}
|
||||
dataCy="nt-detail-twilio-destination-numbers"
|
||||
/>
|
||||
<Detail
|
||||
@ -311,14 +315,23 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
value={configuration.http_method}
|
||||
dataCy="nt-detail-webhook-http-method"
|
||||
/>
|
||||
<ObjectDetail
|
||||
<CodeDetail
|
||||
label={i18n._(t`HTTP Headers`)}
|
||||
value={configuration.headers}
|
||||
value={JSON.stringify(configuration.headers)}
|
||||
mode="json"
|
||||
rows="6"
|
||||
dataCy="nt-detail-webhook-headers"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{hasCustomMessages(messages, typeMessageDefaults) && (
|
||||
<CustomMessageDetails
|
||||
messages={messages}
|
||||
defaults={typeMessageDefaults}
|
||||
type={template.notification_type}
|
||||
i18n={i18n}
|
||||
/>
|
||||
)}
|
||||
</DetailList>
|
||||
<CardActionsRow>
|
||||
{summary_fields.user_capabilities &&
|
||||
@ -358,4 +371,164 @@ function NotificationTemplateDetail({ i18n, template }) {
|
||||
);
|
||||
}
|
||||
|
||||
function CustomMessageDetails({ messages, defaults, type, i18n }) {
|
||||
const showMessages = type !== 'webhook';
|
||||
const showBodies = ['email', 'pagerduty', 'webhook'].includes(type);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMessages && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Start message`)}
|
||||
value={messages.started.message || defaults.started.message}
|
||||
mode="jinja2"
|
||||
rows="2"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showBodies && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Start message body`)}
|
||||
value={messages.started.body || defaults.started.body}
|
||||
mode="jinja2"
|
||||
rows="6"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showMessages && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Success message`)}
|
||||
value={messages.success.message || defaults.success.message}
|
||||
mode="jinja2"
|
||||
rows="2"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showBodies && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Success message body`)}
|
||||
value={messages.success.body || defaults.success.body}
|
||||
mode="jinja2"
|
||||
rows="6"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showMessages && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Error message`)}
|
||||
value={messages.error.message || defaults.error.message}
|
||||
mode="jinja2"
|
||||
rows="2"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showBodies && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Error message body`)}
|
||||
value={messages.error.body || defaults.error.body}
|
||||
mode="jinja2"
|
||||
rows="6"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showMessages && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow approved message`)}
|
||||
value={
|
||||
messages.workflow_approval?.approved?.message ||
|
||||
defaults.workflow_approval.approved.message
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="2"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showBodies && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow approved message body`)}
|
||||
value={
|
||||
messages.workflow_approval?.approved?.body ||
|
||||
defaults.workflow_approval.approved.body
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="6"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showMessages && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow denied message`)}
|
||||
value={
|
||||
messages.workflow_approval?.denied?.message ||
|
||||
defaults.workflow_approval.denied.message
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="2"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showBodies && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow denied message body`)}
|
||||
value={
|
||||
messages.workflow_approval?.denied?.body ||
|
||||
defaults.workflow_approval.denied.body
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="6"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showMessages && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow pending message`)}
|
||||
value={
|
||||
messages.workflow_approval?.running?.message ||
|
||||
defaults.workflow_approval.running.message
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="2"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showBodies && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow pending message body`)}
|
||||
value={
|
||||
messages.workflow_approval?.running?.body ||
|
||||
defaults.workflow_approval.running.body
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="6"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showMessages && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow timed out message`)}
|
||||
value={
|
||||
messages.workflow_approval?.timed_out?.message ||
|
||||
defaults.workflow_approval.timed_out.message
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="2"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
{showBodies && (
|
||||
<CodeDetail
|
||||
label={i18n._(t`Workflow timed out message body`)}
|
||||
value={
|
||||
messages.workflow_approval?.timed_out?.body ||
|
||||
defaults.workflow_approval.timed_out.body
|
||||
}
|
||||
mode="jinja2"
|
||||
rows="6"
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n()(NotificationTemplateDetail);
|
||||
|
||||
@ -124,7 +124,7 @@ function NotificationTemplatesList({ i18n }) {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={() => setSelected([...templates])}
|
||||
onSelectAll={set => setSelected(set ? [...templates] : [])}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -13,6 +13,7 @@ import { required } from '../../../util/validators';
|
||||
import { FormColumnLayout } from '../../../components/FormLayout';
|
||||
import TypeInputsSubForm from './TypeInputsSubForm';
|
||||
import CustomMessagesSubForm from './CustomMessagesSubForm';
|
||||
import hasCustomMessages from './hasCustomMessages';
|
||||
import typeFieldNames, { initialConfigValues } from './typeFieldNames';
|
||||
|
||||
function NotificationTemplateFormFields({ i18n, defaultMessages }) {
|
||||
@ -117,8 +118,8 @@ function NotificationTemplateForm({
|
||||
const defs = defaultMessages[template.notification_type || 'email'];
|
||||
const mergeDefaultMessages = (templ = {}, def) => {
|
||||
return {
|
||||
message: templ.message || def.message || '',
|
||||
body: templ.body || def.body || '',
|
||||
message: templ?.message || def.message || '',
|
||||
body: templ?.body || def.body || '',
|
||||
};
|
||||
};
|
||||
|
||||
@ -144,25 +145,25 @@ function NotificationTemplateForm({
|
||||
workflow_approval: {
|
||||
approved: {
|
||||
...mergeDefaultMessages(
|
||||
messages.workflow_approval.approved,
|
||||
messages.workflow_approval?.approved,
|
||||
defs.workflow_approval.approved
|
||||
),
|
||||
},
|
||||
denied: {
|
||||
...mergeDefaultMessages(
|
||||
messages.workflow_approval.denied,
|
||||
messages.workflow_approval?.denied,
|
||||
defs.workflow_approval.denied
|
||||
),
|
||||
},
|
||||
running: {
|
||||
...mergeDefaultMessages(
|
||||
messages.workflow_approval.running,
|
||||
messages.workflow_approval?.running,
|
||||
defs.workflow_approval.running
|
||||
),
|
||||
},
|
||||
timed_out: {
|
||||
...mergeDefaultMessages(
|
||||
messages.workflow_approval.timed_out,
|
||||
messages.workflow_approval?.timed_out,
|
||||
defs.workflow_approval.timed_out
|
||||
),
|
||||
},
|
||||
@ -210,42 +211,6 @@ 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;
|
||||
}
|
||||
|
||||
function normalizeFields(values, defaultMessages) {
|
||||
return normalizeTypeFields(normalizeMessageFields(values, defaultMessages));
|
||||
}
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
export default function hasCustomMessages(messages, defaults) {
|
||||
if (!messages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user