From 7ba340f4a22299979481c662a698c6bbd29de3d0 Mon Sep 17 00:00:00 2001 From: tonypowa Date: Mon, 3 Nov 2025 16:48:16 +0100 Subject: [PATCH] Alerting: Add frontend validation for placeholder email addresses - Disable test button when placeholder email is detected - Show info alert guiding users to configure valid email address - Add tooltip explaining why test is disabled - Pass channel values to TestContactPointModal for validation - Proactively prevent testing with placeholder addresses - Non-email contact points remain unaffected --- .../receivers/form/ChannelSubForm.tsx | 42 +++++++++++++- .../receivers/form/GrafanaReceiverForm.tsx | 1 + .../receivers/form/TestContactPointModal.tsx | 58 +++++++++++++++++-- 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx index 0202e7c2e52..0c440a09253 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx @@ -70,6 +70,17 @@ export function ChannelSubForm({ const onCallIntegrationType = watch(`${settingsFieldPath}.integration_type`); 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']; + const addresses = typeof emailAddresses === 'string' ? [emailAddresses] : emailAddresses; + return addresses.some((addr: string) => placeholders.includes(addr?.trim())); + }, [selectedType, emailAddresses]); + useEffect(() => { register(`${channelFieldPath}.__id`); /* Need to manually register secureFields or else they'll @@ -230,7 +241,22 @@ export function ChannelSubForm({
{isTestable && onTest && isTestAvailable && ( - )} @@ -257,6 +283,20 @@ export function ChannelSubForm({
{notifier && (
+ {hasPlaceholderEmail && ( + + + This contact point is using a placeholder email address (example@email.com + ). Please update it with a valid email address to receive alerts and enable testing. + + + )} {showTelegramWarning && ( )} diff --git a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx index 78cdb95fa89..4107fae4cbc 100644 --- a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx @@ -1,14 +1,15 @@ import { css } from '@emotion/css'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Button, Label, Modal, RadioButtonGroup, useStyles2 } from '@grafana/ui'; +import { Alert, Button, Label, Modal, RadioButtonGroup, Text, useStyles2 } from '@grafana/ui'; import { Receiver, TestReceiversAlert } from 'app/plugins/datasource/alertmanager/types'; import { Annotations, Labels } from 'app/types/unified-alerting-dto'; import { useTestIntegrationMutation } from '../../../api/receiversApi'; +import { GrafanaChannelValues } from '../../../types/receiver-form'; import { defaultAnnotations } from '../../../utils/constants'; import { stringifyErrorLike } from '../../../utils/misc'; import AnnotationsStep from '../../rule-editor/AnnotationsStep'; @@ -19,6 +20,7 @@ interface Props { onDismiss: () => void; alertManagerSourceName: string; receivers: Receiver[]; + channelValues?: GrafanaChannelValues; } type AnnoField = { @@ -43,15 +45,34 @@ const defaultValues: FormFields = { labels: [{ key: '', value: '' }], }; -export const TestContactPointModal = ({ isOpen, onDismiss, alertManagerSourceName, receivers }: Props) => { +export const TestContactPointModal = ({ + isOpen, + onDismiss, + alertManagerSourceName, + receivers, + channelValues, +}: Props) => { const [notificationType, setNotificationType] = useState(NotificationType.predefined); const styles = useStyles2(getStyles); const formMethods = useForm({ defaultValues, mode: 'onBlur' }); 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']; + const addressList = typeof addresses === 'string' ? [addresses] : addresses; + return addressList.some((addr: string) => placeholders.includes(addr?.trim())); + }, [channelValues]); + const onSubmit = async (data: FormFields) => { let alert: TestReceiversAlert | undefined; - if (notificationType === NotificationType.custom) { alert = { annotations: data.annotations @@ -93,6 +114,22 @@ export const TestContactPointModal = ({ isOpen, onDismiss, alertManagerSourceNam /> )} + {hasPlaceholderEmail && ( +
+ + + This contact point is using a placeholder email address (example@email.com). + Please update it with a valid email address before testing. + + +
+ )}