From 3e93a0991f6ae1e77a7dc72fd6b7e4429dff28a2 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:13:37 +0100 Subject: [PATCH] Alerting: Use new readonly permission endpoints for getting contact points and mute timings (#82132) * use new read only contact points list endpoint in simplified routing section * Dont use alertmanager endpoint to get groupby defaults * Use the new read only endpoint for mute timings in route settings * review suggestions * Rename hook * Use options in params for useContactPointsWithStatus hook * Refactor useContactPointsWithStatus * second part of the enhanceContactPointsWithMetadata refactor --- .../alerting/unified/api/alertmanagerApi.ts | 9 ++++ .../contact-points/useContactPoints.tsx | 46 ++++++++++++++++--- .../components/contact-points/utils.ts | 29 ++++++++---- .../simplifiedRouting/AlertManagerRouting.tsx | 7 ++- .../route-settings/MuteTimingFields.tsx | 24 +++++++++- .../route-settings/RouteSettings.tsx | 25 ++++------ 6 files changed, 104 insertions(+), 36 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertmanagerApi.ts b/public/app/features/alerting/unified/api/alertmanagerApi.ts index e97588813de..c0c71092210 100644 --- a/public/app/features/alerting/unified/api/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/api/alertmanagerApi.ts @@ -11,7 +11,9 @@ import { ExternalAlertmanagerConfig, ExternalAlertmanagers, ExternalAlertmanagersResponse, + GrafanaManagedContactPoint, Matcher, + MuteTimeInterval, } from '../../../../plugins/datasource/alertmanager/types'; import { NotifierDTO } from '../../../../types'; import { withPerformanceLogging } from '../Analytics'; @@ -257,5 +259,12 @@ export const alertmanagerApi = alertingApi.injectEndpoints({ })); }, }), + // Grafana Managed Alertmanager only + getContactPointsList: build.query({ + query: () => ({ url: '/api/v1/notifications/receivers' }), + }), + getMuteTimingList: build.query({ + query: () => ({ url: '/api/v1/notifications/time-intervals' }), + }), }), }); diff --git a/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx index d5b3a841ed6..5ab23e150e3 100644 --- a/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx +++ b/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx @@ -27,7 +27,13 @@ const RECEIVER_STATUS_POLLING_INTERVAL = 10 * 1000; // 10 seconds * 3. (if available) additional metadata about Grafana Managed contact points * 4. (if available) the OnCall plugin metadata */ -export function useContactPointsWithStatus() { +interface UseContactPointsWithStatusOptions { + includePoliciesCount: boolean; +} + +export function useContactPointsWithStatus( + { includePoliciesCount }: UseContactPointsWithStatusOptions = { includePoliciesCount: true } +) { const { selectedAlertmanager, isGrafanaAlertmanager } = useAlertmanager(); const { installed: onCallPluginInstalled, loading: onCallPluginStatusLoading } = usePluginBridge( SupportedPlugin.OnCall @@ -64,6 +70,7 @@ export function useContactPointsWithStatus() { } // fetch the latest config from the Alertmanager + // we use this endpoint only when we need to get the number of policies const fetchAlertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery( selectedAlertmanager!, { @@ -73,31 +80,56 @@ export function useContactPointsWithStatus() { ...result, contactPoints: result.data ? enhanceContactPointsWithMetadata( - result.data, fetchContactPointsStatus.data, fetchReceiverMetadata.data, - onCallMetadata + onCallMetadata, + result.data.alertmanager_config.receivers ?? [], + result.data ) : [], }), + skip: !includePoliciesCount, } ); + // for Grafana Managed Alertmanager, we use the new read-only endpoint for getting the list of contact points + const fetchGrafanaContactPoints = alertmanagerApi.endpoints.getContactPointsList.useQuery(undefined, { + refetchOnFocus: true, + refetchOnReconnect: true, + selectFromResult: (result) => ({ + ...result, + contactPoints: result.data + ? enhanceContactPointsWithMetadata( + fetchContactPointsStatus.data, + fetchReceiverMetadata.data, + onCallMetadata, + result.data, // contact points from the new readonly endpoint + undefined //no config data + ) + : [], + }), + skip: includePoliciesCount || !isGrafanaAlertmanager, + }); + // we will fail silently for fetching OnCall plugin status and integrations - const error = fetchAlertmanagerConfiguration.error ?? fetchContactPointsStatus.error; + const error = + fetchAlertmanagerConfiguration.error || fetchGrafanaContactPoints.error || fetchContactPointsStatus.error; const isLoading = fetchAlertmanagerConfiguration.isLoading || + fetchGrafanaContactPoints.isLoading || fetchContactPointsStatus.isLoading || onCallPluginStatusLoading || onCallPluginIntegrationsLoading; - const contactPoints = fetchAlertmanagerConfiguration.contactPoints.sort((a, b) => a.name.localeCompare(b.name)); - + const unsortedContactPoints = includePoliciesCount + ? fetchAlertmanagerConfiguration.contactPoints + : fetchGrafanaContactPoints.contactPoints; + const contactPoints = unsortedContactPoints.sort((a, b) => a.name.localeCompare(b.name)); return { error, isLoading, contactPoints, - refetchReceivers: fetchAlertmanagerConfiguration.refetch, + refetchReceivers: fetchGrafanaContactPoints.refetch, }; } diff --git a/public/app/features/alerting/unified/components/contact-points/utils.ts b/public/app/features/alerting/unified/components/contact-points/utils.ts index 38a69fd3b72..201351e1451 100644 --- a/public/app/features/alerting/unified/components/contact-points/utils.ts +++ b/public/app/features/alerting/unified/components/contact-points/utils.ts @@ -7,6 +7,7 @@ import { GrafanaManagedContactPoint, GrafanaManagedReceiverConfig, MatcherOperator, + Receiver, Route, } from 'app/plugins/datasource/alertmanager/types'; import { NotifierDTO, NotifierStatus, ReceiversStateDTO } from 'app/types'; @@ -30,6 +31,9 @@ export function isProvisioned(contactPoint: GrafanaManagedContactPoint) { // TODO we should really add some type information to these receiver settings... export function getReceiverDescription(receiver: ReceiverConfigWithMetadata): ReactNode | undefined { + if (!receiver.settings) { + return undefined; + } switch (receiver.type) { case 'email': { const hasEmailAddresses = 'addresses' in receiver.settings; // when dealing with alertmanager email_configs we don't normalize the settings @@ -87,7 +91,7 @@ export interface ReceiverConfigWithMetadata extends GrafanaManagedReceiverConfig } export interface ContactPointWithMetadata extends GrafanaManagedContactPoint { - numberOfPolicies: number; + numberOfPolicies?: number; // now is optional as we don't have the data from the read-only endpoint grafana_managed_receiver_configs: ReceiverConfigWithMetadata[]; } @@ -95,30 +99,36 @@ export interface ContactPointWithMetadata extends GrafanaManagedContactPoint { * This function adds the status information for each of the integrations (contact point types) in a contact point * 1. we iterate over all contact points * 2. for each contact point we "enhance" it with the status or "undefined" for vanilla Alertmanager + * contactPoints: list of contact points + * alertmanagerConfiguration: optional as is passed when we need to get number of policies for each contact point + * and we prefer using the data from the read-only endpoint. */ export function enhanceContactPointsWithMetadata( - result: AlertManagerCortexConfig, status: ReceiversStateDTO[] = [], notifiers: NotifierDTO[] = [], - onCallIntegrations: OnCallIntegrationDTO[] | undefined | null + onCallIntegrations: OnCallIntegrationDTO[] | undefined | null, + contactPoints: Receiver[], + alertmanagerConfiguration?: AlertManagerCortexConfig ): ContactPointWithMetadata[] { - const contactPoints = result.alertmanager_config.receivers ?? []; - // compute the entire inherited tree before finding what notification policies are using a particular contact point - const fullyInheritedTree = computeInheritedTree(result?.alertmanager_config?.route ?? {}); + const fullyInheritedTree = computeInheritedTree(alertmanagerConfiguration?.alertmanager_config?.route ?? {}); const usedContactPoints = getUsedContactPoints(fullyInheritedTree); const usedContactPointsByName = countBy(usedContactPoints); - return contactPoints.map((contactPoint) => { + const contactPointsList = alertmanagerConfiguration + ? alertmanagerConfiguration?.alertmanager_config.receivers ?? [] + : contactPoints ?? []; + + return contactPointsList.map((contactPoint) => { const receivers = extractReceivers(contactPoint); const statusForReceiver = status.find((status) => status.name === contactPoint.name); return { ...contactPoint, - numberOfPolicies: usedContactPointsByName[contactPoint.name] ?? 0, + numberOfPolicies: + alertmanagerConfiguration && usedContactPointsByName && (usedContactPointsByName[contactPoint.name] ?? 0), grafana_managed_receiver_configs: receivers.map((receiver, index) => { const isOnCallReceiver = receiver.type === ReceiverTypes.OnCall; - return { ...receiver, [RECEIVER_STATUS_KEY]: statusForReceiver?.integrations[index], @@ -130,6 +140,7 @@ export function enhanceContactPointsWithMetadata( }; }); } + export function isAutoGeneratedPolicy(route: Route) { const simplifiedRoutingToggleEnabled = config.featureToggles.alertingSimplifiedRouting ?? false; if (!simplifiedRoutingToggleEnabled) { 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 4ec14cd359a..06ab125cba9 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 @@ -22,7 +22,12 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo const styles = useStyles2(getStyles); const alertManagerName = alertManager.name; - const { isLoading, error: errorInContactPointStatus, contactPoints, refetchReceivers } = useContactPointsWithStatus(); + const { + isLoading, + error: errorInContactPointStatus, + contactPoints, + refetchReceivers, + } = useContactPointsWithStatus({ includePoliciesCount: false }); const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState< ContactPointWithMetadata | undefined >(); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx index 70728a041d7..ddb93051902 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { useFormContext } from 'react-hook-form'; +import { SelectableValue } from '@grafana/data'; import { Field, InputControl, MultiSelect, useStyles2 } from '@grafana/ui'; -import { useMuteTimingOptions } from 'app/features/alerting/unified/hooks/useMuteTimingOptions'; +import { alertmanagerApi } from 'app/features/alerting/unified/api/alertmanagerApi'; import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form'; +import { timeIntervalToString } from 'app/features/alerting/unified/utils/alertmanager'; import { mapMultiSelectValueToStrings } from 'app/features/alerting/unified/utils/amroutes'; import { getFormStyles } from '../../../../notification-policies/formStyles'; @@ -19,7 +21,7 @@ export function MuteTimingFields({ alertManager }: MuteTimingFieldsProps) { formState: { errors }, } = useFormContext(); - const muteTimingOptions = useMuteTimingOptions(); + const muteTimingOptions = useSelectableMuteTimings(); return ( ); } + +function useSelectableMuteTimings(): Array> { + const fetchGrafanaMuteTimings = alertmanagerApi.endpoints.getMuteTimingList.useQuery(undefined, { + refetchOnFocus: true, + refetchOnReconnect: true, + selectFromResult: (result) => ({ + ...result, + mutetimings: result.data + ? result.data.map((value) => ({ + value: value.name, + label: value.name, + description: value.time_intervals.map((interval) => timeIntervalToString(interval)).join(', AND '), + })) + : [], + }), + }); + return fetchGrafanaMuteTimings.mutetimings; +} diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx index 298239dc15d..d676664f2f1 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx @@ -14,8 +14,6 @@ import { Text, useStyles2, } from '@grafana/ui'; -import { useAlertmanagerConfig } from 'app/features/alerting/unified/hooks/useAlertmanagerConfig'; -import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext'; import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form'; import { commonGroupByOptions, @@ -42,7 +40,7 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => { formState: { errors }, } = useFormContext(); const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues([])); - const { groupBy, groupIntervalValue, groupWaitValue, repeatIntervalValue } = useGetDefaultsForRoutingSettings(); + const { groupIntervalValue, groupWaitValue, repeatIntervalValue } = getDefaultsForRoutingSettings(); const overrideGrouping = watch(`contactPoints.${alertManager}.overrideGrouping`); const overrideTimings = watch(`contactPoints.${alertManager}.overrideTimings`); const requiredFieldsInGroupBy = ['grafana_folder', 'alertname']; @@ -56,7 +54,7 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => { {!overrideGrouping && ( - Grouping: {groupBy.join(', ')} + Grouping: {requiredFieldsInGroupBy.join(', ')} )} @@ -131,20 +129,13 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => { ); }; -function useGetDefaultsForRoutingSettings() { - const { selectedAlertmanager } = useAlertmanager(); - const { currentData } = useAlertmanagerConfig(selectedAlertmanager); - const config = currentData?.alertmanager_config; - return React.useMemo(() => { - return { - groupWaitValue: TIMING_OPTIONS_DEFAULTS.group_wait, - groupIntervalValue: TIMING_OPTIONS_DEFAULTS.group_interval, - repeatIntervalValue: TIMING_OPTIONS_DEFAULTS.repeat_interval, - groupBy: config?.route?.group_by ?? [], - }; - }, [config]); +function getDefaultsForRoutingSettings() { + return { + groupWaitValue: TIMING_OPTIONS_DEFAULTS.group_wait, + groupIntervalValue: TIMING_OPTIONS_DEFAULTS.group_interval, + repeatIntervalValue: TIMING_OPTIONS_DEFAULTS.repeat_interval, + }; } - const getStyles = (theme: GrafanaTheme2) => ({ switchElement: css({ flexFlow: 'row-reverse',