Alerting: Refactor placeholder email detection into shared utility

- Extract duplicate placeholder email detection logic into reusable functions
- Add hasPlaceholderEmail() for single channel checks
- Add receiverHasPlaceholderEmail() for receiver list checks
- Centralize PLACEHOLDER_EMAILS constant in receiver-form.ts
- Update ChannelSubForm, TestContactPointModal, and actions to use utilities
- Reduces code duplication by 44 lines
This commit is contained in:
tonypowa
2025-11-04 10:22:09 +01:00
parent 7ba340f4a2
commit e51fb87cc2
4 changed files with 53 additions and 46 deletions
@@ -16,6 +16,7 @@ import {
GrafanaChannelValues,
ReceiverFormValues,
} from '../../../types/receiver-form';
import { hasPlaceholderEmail } from '../../../utils/receiver-form';
import { OnCallIntegrationType } from '../grafanaAppReceivers/onCall/useOnCallIntegration';
import { ChannelOptions } from './ChannelOptions';
@@ -71,15 +72,8 @@ export function ChannelSubForm<R extends ChannelValues>({
const isTestAvailable = onCallIntegrationType !== OnCallIntegrationType.NewIntegration;
// Check if email integration has placeholder addresses
const emailAddresses = watch(`${settingsFieldPath}.addresses`);
const hasPlaceholderEmail = useMemo(() => {
if (selectedType !== 'email' || !emailAddresses) {
return false;
}
const placeholders = ['<example@email.com>', 'example@email.com'];
const addresses = typeof emailAddresses === 'string' ? [emailAddresses] : emailAddresses;
return addresses.some((addr: string) => placeholders.includes(addr?.trim()));
}, [selectedType, emailAddresses]);
const channelValues = getValues(channelFieldPath) as GrafanaChannelValues | undefined;
const isPlaceholderEmail = hasPlaceholderEmail(channelValues);
useEffect(() => {
register(`${channelFieldPath}.__id`);
@@ -242,14 +236,14 @@ export function ChannelSubForm<R extends ChannelValues>({
<div className={styles.buttons}>
{isTestable && onTest && isTestAvailable && (
<Button
disabled={hasPlaceholderEmail}
disabled={isPlaceholderEmail}
size="xs"
variant="secondary"
type="button"
onClick={() => handleTest()}
icon="message"
tooltip={
hasPlaceholderEmail
isPlaceholderEmail
? t(
'alerting.channel-sub-form.test-disabled-placeholder',
'Please configure a valid email address before testing'
@@ -283,7 +277,7 @@ export function ChannelSubForm<R extends ChannelValues>({
</div>
{notifier && (
<div className={styles.innerContent}>
{hasPlaceholderEmail && (
{isPlaceholderEmail && (
<Alert
title={t(
'alerting.contact-points.email.placeholder-warning-title',
@@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
@@ -12,6 +12,7 @@ import { useTestIntegrationMutation } from '../../../api/receiversApi';
import { GrafanaChannelValues } from '../../../types/receiver-form';
import { defaultAnnotations } from '../../../utils/constants';
import { stringifyErrorLike } from '../../../utils/misc';
import { hasPlaceholderEmail } from '../../../utils/receiver-form';
import AnnotationsStep from '../../rule-editor/AnnotationsStep';
import LabelsField from '../../rule-editor/labels/LabelsField';
@@ -58,18 +59,7 @@ export const TestContactPointModal = ({
const [testIntegration, { isLoading, error, isSuccess }] = useTestIntegrationMutation();
// Check if email integration has placeholder addresses
const hasPlaceholderEmail = useMemo(() => {
if (!channelValues || channelValues.type !== 'email') {
return false;
}
const addresses = channelValues.settings?.addresses;
if (!addresses) {
return false;
}
const placeholders = ['<example@email.com>', 'example@email.com'];
const addressList = typeof addresses === 'string' ? [addresses] : addresses;
return addressList.some((addr: string) => placeholders.includes(addr?.trim()));
}, [channelValues]);
const isPlaceholderEmail = hasPlaceholderEmail(channelValues);
const onSubmit = async (data: FormFields) => {
let alert: TestReceiversAlert | undefined;
@@ -114,7 +104,7 @@ export const TestContactPointModal = ({
/>
)}
{hasPlaceholderEmail && (
{isPlaceholderEmail && (
<div className={styles.section}>
<Alert
title={t(
@@ -171,9 +161,9 @@ export const TestContactPointModal = ({
<Modal.ButtonRow>
<Button
type="submit"
disabled={isLoading || hasPlaceholderEmail}
disabled={isLoading || isPlaceholderEmail}
tooltip={
hasPlaceholderEmail
isPlaceholderEmail
? t(
'alerting.test-contact-point-modal.test-disabled-placeholder',
'Please configure a valid email address before testing'
@@ -30,6 +30,7 @@ import { FetchRulerRulesFilter, fetchRulerRules } from '../api/ruler';
import { addDefaultsToAlertmanagerConfig } from '../utils/alertmanager';
import { getAllRulesSourceNames } from '../utils/datasource';
import { makeAMLink } from '../utils/misc';
import { receiverHasPlaceholderEmail } from '../utils/receiver-form';
import { withAppEvents, withSerializedError } from '../utils/redux';
import { getAlertInfo } from '../utils/rules';
import { safeParsePrometheusDuration } from '../utils/time';
@@ -272,27 +273,10 @@ interface TestReceiversOptions {
alert?: TestReceiversAlert;
}
// Check if a receiver uses placeholder email addresses
function hasPlaceholderEmail(receivers: Receiver[]): boolean {
const placeholderEmails = ['<example@email.com>', 'example@email.com'];
return receivers.some((receiver) =>
receiver.grafana_managed_receiver_configs?.some((config) => {
if (config.type === 'email' && config.settings?.addresses) {
const addresses = config.settings.addresses;
// addresses can be a string or array
const addressList = typeof addresses === 'string' ? [addresses] : addresses;
return addressList.some((addr: string) => placeholderEmails.includes(addr.trim()));
}
return false;
})
);
}
export const testReceiversAction = createAsyncThunk(
'unifiedalerting/testReceivers',
async ({ alertManagerSourceName, receivers, alert }: TestReceiversOptions): Promise<void> => {
const usesPlaceholder = hasPlaceholderEmail(receivers);
const usesPlaceholder = receiverHasPlaceholderEmail(receivers);
if (usesPlaceholder) {
// Handle placeholder email case with custom warning message
@@ -304,3 +304,42 @@ export function omitTemporaryIdentifiers<T>(object: Readonly<T>): T {
return objectCopy;
}
/**
* Placeholder emails that ship with the default grafana-default-email contact point.
* These should not trigger actual email sends or throw errors.
*/
const PLACEHOLDER_EMAILS = ['<example@email.com>', 'example@email.com'];
/**
* Check if a single channel/integration has placeholder email addresses.
* Used in UI components to disable test buttons and show warnings.
*/
export function hasPlaceholderEmail(channelValues?: GrafanaChannelValues): boolean {
if (!channelValues || channelValues.type !== 'email') {
return false;
}
const addresses = channelValues.settings?.addresses;
if (!addresses) {
return false;
}
const addressList = typeof addresses === 'string' ? [addresses] : addresses;
return addressList.some((addr: string) => PLACEHOLDER_EMAILS.includes(addr?.trim()));
}
/**
* Check if any receiver in a list has placeholder email addresses.
* Used in actions to determine if warning messages should be shown.
*/
export function receiverHasPlaceholderEmail(receivers: Receiver[]): boolean {
return receivers.some((receiver) =>
receiver.grafana_managed_receiver_configs?.some((config) => {
if (config.type === 'email' && config.settings?.addresses) {
const addresses = config.settings.addresses;
const addressList = typeof addresses === 'string' ? [addresses] : addresses;
return addressList.some((addr: string) => PLACEHOLDER_EMAILS.includes(addr.trim()));
}
return false;
})
);
}