Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating Preview Message functionality while setting notifications in detector alerts #1241

Merged
merged 4 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions public/components/Notifications/NotificationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
EuiCompressedSwitch,
EuiText,
EuiCompressedTextArea,
EuiCompressedCheckbox,
} from '@elastic/eui';
import React, { useState } from 'react';
import { NOTIFICATIONS_HREF } from '../../utils/constants';
Expand All @@ -24,8 +25,10 @@ import {
NotificationChannelOption,
NotificationChannelTypeOptions,
TriggerAction,
TriggerContext,
} from '../../../types';
import { getIsNotificationPluginInstalled } from '../../utils/helpers';
import Mustache from 'mustache';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use named imports here and other places too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


export interface NotificationFormProps {
allNotificationChannels: NotificationChannelTypeOptions[];
Expand All @@ -37,10 +40,12 @@ export interface NotificationFormProps {
onMessageBodyChange: (message: string) => void;
onMessageSubjectChange: (subject: string) => void;
onNotificationToggle?: (enabled: boolean) => void;
context: TriggerContext;
}

export const NotificationForm: React.FC<NotificationFormProps> = ({
action,
context,
allNotificationChannels,
loadingNotifications,
prepareMessage,
Expand All @@ -53,6 +58,8 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
const hasNotificationPlugin = getIsNotificationPluginInstalled();
const [shouldSendNotification, setShouldSendNotification] = useState(!!action?.destination_id);
const selectedNotificationChannelOption: NotificationChannelOption[] = [];
const onDisplayPreviewChange = (e) => setDisplayPreview(e.target.checked);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memoize this using useCallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const [displayPreview, setDisplayPreview] = useState(false);
if (shouldSendNotification && action?.destination_id) {
allNotificationChannels.forEach((typeOption) => {
const matchingChannel = typeOption.options.find(
Expand All @@ -61,7 +68,17 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
if (matchingChannel) selectedNotificationChannelOption.push(matchingChannel);
});
}

let preview = '';
try {
preview = `${Mustache.render(action?.subject_template.source, context)}\n\n${Mustache.render(
action?.message_template.source,
context
)}`;
} catch (err) {
preview = `There was an error rendering message preview: ${err.message}`;
console.error('There was an error rendering mustache template', err);
amsiglan marked this conversation as resolved.
Show resolved Hide resolved
}
``;
return (
<>
<EuiCompressedSwitch
Expand Down Expand Up @@ -168,18 +185,27 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
/>
</EuiCompressedFormRow>
</EuiFlexItem>
{prepareMessage && (

<EuiFlexItem>
<EuiCompressedCheckbox
id="notification-message-preview-checkbox"
label="Preview message"
checked={displayPreview}
onChange={onDisplayPreviewChange}
/>
</EuiFlexItem>
{displayPreview ? (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We can also do `displayPreview && () instead of the ternary to keep it short

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

<EuiFlexItem>
<EuiCompressedFormRow>
<EuiSmallButton
fullWidth={false}
onClick={() => prepareMessage(true /* updateMessage */)}
>
Generate message
</EuiSmallButton>
<EuiCompressedFormRow label="Message preview" fullWidth>
<EuiCompressedTextArea
placeholder="Preview of mustache template"
fullWidth
value={preview}
readOnly
/>
</EuiCompressedFormRow>
</EuiFlexItem>
)}
) : null}
</EuiFlexGroup>
</EuiAccordion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
NotificationChannelTypeOptions,
} from '../../../../../../../types';
import { NotificationForm } from '../../../../../../components/Notifications/NotificationForm';
import { ALERT_SEVERITY_OPTIONS } from '../../../../../../utils/constants';
import { ALERT_SEVERITY_OPTIONS, DEFAULT_MESSAGE_SOURCE } from '../../../../../../utils/constants';

interface AlertConditionPanelProps extends RouteComponentProps {
alertCondition: AlertCondition;
Expand Down Expand Up @@ -99,6 +99,27 @@ export default class AlertConditionPanel extends Component<
});
}

getTriggerContext = () => {
const lineBreakAndTab = '\n\t';
const { alertCondition, detector } = this.props;
const detectorInput = detector.inputs[0].detector_input;
const detectorIndices = `${lineBreakAndTab}${detectorInput.indices.join(
`,${lineBreakAndTab}`
)}`;
return {
trigger: {
name: alertCondition.name,
severity:
parseAlertSeverityToOption(alertCondition.severity)?.label || alertCondition.severity,
},
detector: {
name: detector.name,
description: detectorInput.description,
datasources: detectorIndices,
},
};
};

// When component mounts, we prepare message but at this point we don't want to emit the
// trigger changed metric since it is not user initiated. So we use the onMount flag to determine that
// and pass it downstream accordingly.
Expand All @@ -113,7 +134,7 @@ export default class AlertConditionPanel extends Component<
parseAlertSeverityToOption(alertCondition.severity)?.label || alertCondition.severity
}`;
const detectorName = `Threat detector: ${detector.name}`;
const defaultSubject = [alertConditionName, alertConditionSeverity, detectorName].join(' - ');
const defaultSubject = DEFAULT_MESSAGE_SOURCE.MESSAGE_SUBJECT;

if (updateMessage || !alertCondition.actions[0]?.subject_template.source)
this.onMessageSubjectChange(defaultSubject, !onMount);
Expand Down Expand Up @@ -157,7 +178,7 @@ export default class AlertConditionPanel extends Component<
if (alertConditionSelections.length)
defaultMessageBody =
defaultMessageBody + lineBreak + lineBreak + alertConditionSelections.join(lineBreak);
this.onMessageBodyChange(defaultMessageBody, !onMount);
this.onMessageBodyChange(DEFAULT_MESSAGE_SOURCE.MESSAGE_BODY, !onMount);
}
};

Expand Down Expand Up @@ -286,6 +307,7 @@ export default class AlertConditionPanel extends Component<
};

render() {
const context = this.getTriggerContext();
const {
alertCondition = getEmptyAlertCondition(),
allNotificationChannels,
Expand Down Expand Up @@ -537,6 +559,9 @@ export default class AlertConditionPanel extends Component<

<NotificationForm
action={alertCondition.actions[0]}
context={{
ctx: context,
}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to match the expected type TriggerContext. It doesn't have ctx as the expected field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

allNotificationChannels={allNotificationChannels}
loadingNotifications={loadingNotifications}
prepareMessage={this.prepareMessage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,28 +838,22 @@ Object {
class="euiFlexItem"
>
<div
class="euiFormRow euiFormRow--compressed"
id="some_html_id-row"
class="euiCheckbox euiCheckbox--compressed"
>
<input
class="euiCheckbox__input"
id="notification-message-preview-checkbox"
type="checkbox"
/>
<div
class="euiFormRow__fieldWrapper"
class="euiCheckbox__square"
/>
<label
class="euiCheckbox__label"
for="notification-message-preview-checkbox"
>
<button
class="euiButton euiButton--primary euiButton--small"
id="some_html_id"
type="button"
>
<span
class="euiButtonContent euiButton__content"
>
<span
class="euiButton__text"
>
Generate message
</span>
</span>
</button>
</div>
Preview message
</label>
</div>
</div>
</div>
Expand Down Expand Up @@ -1707,28 +1701,22 @@ Object {
class="euiFlexItem"
>
<div
class="euiFormRow euiFormRow--compressed"
id="some_html_id-row"
class="euiCheckbox euiCheckbox--compressed"
>
<input
class="euiCheckbox__input"
id="notification-message-preview-checkbox"
type="checkbox"
/>
<div
class="euiFormRow__fieldWrapper"
class="euiCheckbox__square"
/>
<label
class="euiCheckbox__label"
for="notification-message-preview-checkbox"
>
<button
class="euiButton euiButton--primary euiButton--small"
id="some_html_id"
type="button"
>
<span
class="euiButtonContent euiButton__content"
>
<span
class="euiButton__text"
>
Generate message
</span>
</span>
</button>
</div>
Preview message
</label>
</div>
</div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions public/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,16 @@ const LocalCluster: DataSourceOption = {
export const dataSourceObservable = new BehaviorSubject<DataSourceOption>({});

export const DATA_SOURCE_NOT_SET_ERROR = 'Data source is not set';

/*
nishtham-amazon marked this conversation as resolved.
Show resolved Hide resolved
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
export const DEFAULT_MESSAGE_SOURCE = {
MESSAGE_BODY: `- Triggered alert condition: {{ctx.trigger.name}}
- Severity: {{ctx.trigger.severity}}
- Threat detector: {{ctx.detector.name}}
- Description: {{ctx.detector.description}}
- Detector data sources: {{ctx.detector.datasources}}`,
MESSAGE_SUBJECT: `Triggered alert condition: {{ctx.trigger.name}} - Severity: {{ctx.trigger.severity}} - Threat detector: {{ctx.detector.name}}`,
};
12 changes: 12 additions & 0 deletions types/Alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ export interface TriggerAction {
};
}

export interface TriggerContext {
trigger: {
name: string;
severity: string;
};
detector: {
name: string;
description: string;
datasources: string;
};
}

/**
* API interfaces
*/
Expand Down
Loading