diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx
index ad8151d4ebe..4ec14cd359a 100644
--- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx
@@ -1,11 +1,11 @@
-import { css, cx } from '@emotion/css';
+import { css } from '@emotion/css';
import React, { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
-import { Alert, CollapsableSection, IconButton, LoadingPlaceholder, Stack, TextLink, useStyles2 } from '@grafana/ui';
+import { Alert, CollapsableSection, LoadingPlaceholder, Stack, useStyles2 } from '@grafana/ui';
import { AlertManagerDataSource } from 'app/features/alerting/unified/utils/datasource';
-import { createUrl } from 'app/features/alerting/unified/utils/url';
+import { ContactPointReceiverSummary } from '../../../contact-points/ContactPoints';
import { useContactPointsWithStatus } from '../../../contact-points/useContactPoints';
import { ContactPointWithMetadata } from '../../../contact-points/utils';
@@ -18,8 +18,6 @@ interface AlertManagerManualRoutingProps {
alertManager: AlertManagerDataSource;
}
-const LOADING_SPINNER_DURATION = 1000;
-
export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRoutingProps) {
const styles = useStyles2(getStyles);
@@ -29,21 +27,17 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
ContactPointWithMetadata | undefined
>();
- // We need to provide a fake loading state for the contact points, because it might be that the response is so fast that the loading spinner is not shown,
- // and the user might think that the contact points are not fetched.
- // We will show the loading spinner for 1 second, and if the fetching takes more than 1 second, we will show the loading spinner until the fetching is done.
-
- const [loadingContactPoints, setLoadingContactPoints] = useState(false);
- // we need to keep track if the fetching takes more than 1 second, so we can show the loading spinner until the fetching is done
- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
-
- const onClickRefresh = () => {
- setLoadingContactPoints(true);
- Promise.all([refetchReceivers(), sleep(LOADING_SPINNER_DURATION)]).finally(() => {
- setLoadingContactPoints(false);
- });
+ const onSelectContactPoint = (contactPoint?: ContactPointWithMetadata) => {
+ setSelectedContactPointWithMetadata(contactPoint);
};
+ const options = contactPoints.map((receiver) => {
+ const integrations = receiver?.grafana_managed_receiver_configs;
+ const description = ;
+
+ return { label: receiver.name, value: receiver, description };
+ });
+
if (errorInContactPointStatus) {
return ;
}
@@ -64,21 +58,10 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
-
-
-
-
{selectedContactPointWithMetadata?.grafana_managed_receiver_configs && (
@@ -98,14 +81,6 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
);
}
-function LinkToContactPoints() {
- const hrefToContactPoints = '/alerting/notifications';
- return (
-
- View or create contact points
-
- );
-}
const getStyles = (theme: GrafanaTheme2) => ({
firstAlertManagerLine: css({
@@ -141,30 +116,4 @@ const getStyles = (theme: GrafanaTheme2) => ({
padding: `${theme.spacing(1)} ${theme.spacing(2)}`,
marginTop: theme.spacing(2),
}),
- contactPointsInfo: css({
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- gap: theme.spacing(1),
- marginTop: theme.spacing(1),
- }),
- refreshButton: css({
- color: theme.colors.text.secondary,
- cursor: 'pointer',
- borderRadius: theme.shape.radius.circle,
- overflow: 'hidden',
- }),
- loading: css({
- pointerEvents: 'none',
- animation: 'rotation 2s infinite linear',
- '@keyframes rotation': {
- from: {
- transform: 'rotate(720deg)',
- },
- to: {
- transform: 'rotate(0deg)',
- },
- },
- }),
});
diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx
index d78a2087c4c..5788b9dd44a 100644
--- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx
@@ -1,75 +1,186 @@
-import { css } from '@emotion/css';
-import React from 'react';
+import { css, cx } from '@emotion/css';
+import React, { useCallback, useEffect, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
-import { ActionMeta, Field, FieldValidationMessage, InputControl, Select, Stack, useStyles2 } from '@grafana/ui';
+import {
+ ActionMeta,
+ Field,
+ FieldValidationMessage,
+ IconButton,
+ InputControl,
+ Select,
+ Stack,
+ TextLink,
+ useStyles2,
+} from '@grafana/ui';
import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
+import { createUrl } from 'app/features/alerting/unified/utils/url';
-import { ContactPointReceiverSummary } from '../../../../contact-points/ContactPoints';
import { ContactPointWithMetadata } from '../../../../contact-points/utils';
export interface ContactPointSelectorProps {
alertManager: string;
- contactPoints: ContactPointWithMetadata[];
+ options: Array<{
+ label: string;
+ value: ContactPointWithMetadata;
+ description: React.JSX.Element;
+ }>;
onSelectContactPoint: (contactPoint?: ContactPointWithMetadata) => void;
+ refetchReceivers: () => Promise;
}
-export function ContactPointSelector({ alertManager, contactPoints, onSelectContactPoint }: ContactPointSelectorProps) {
+
+export function ContactPointSelector({
+ alertManager,
+ options,
+ onSelectContactPoint,
+ refetchReceivers,
+}: ContactPointSelectorProps) {
const styles = useStyles2(getStyles);
- const { control, watch } = useFormContext();
+ const { control, watch, trigger } = useFormContext();
- const options = contactPoints.map((receiver) => {
- const integrations = receiver?.grafana_managed_receiver_configs;
- const description = ;
+ const contactPointInForm = watch(`contactPoints.${alertManager}.selectedContactPoint`);
- return { label: receiver.name, value: receiver, description };
- });
-
- const selectedContactPointWithMetadata = options.find(
- (option) => option.value.name === watch(`contactPoints.${alertManager}.selectedContactPoint`)
- )?.value;
+ const selectedContactPointWithMetadata = options.find((option) => option.value.name === contactPointInForm)?.value;
const selectedContactPointSelectableValue = selectedContactPointWithMetadata
? { value: selectedContactPointWithMetadata, label: selectedContactPointWithMetadata.name }
: undefined;
+ const LOADING_SPINNER_DURATION = 1000;
+
+ const [loadingContactPoints, setLoadingContactPoints] = useState(false);
+ // we need to keep track if the fetching takes more than 1 second, so we can show the loading spinner until the fetching is done
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+ // if we have a contact point selected, check if it still exists in the event that someone has deleted it
+ const validateContactPoint = useCallback(() => {
+ if (contactPointInForm) {
+ trigger(`contactPoints.${alertManager}.selectedContactPoint`, { shouldFocus: true });
+ }
+ }, [alertManager, contactPointInForm, trigger]);
+
+ const onClickRefresh = () => {
+ setLoadingContactPoints(true);
+ Promise.all([refetchReceivers(), sleep(LOADING_SPINNER_DURATION)]).finally(() => {
+ setLoadingContactPoints(false);
+ validateContactPoint();
+ });
+ };
+
+ // validate the contact point and check if it still exists when mounting the component
+ useEffect(() => {
+ validateContactPoint();
+ }, [validateContactPoint]);
+
return (
-
- (
- <>
-