mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
ed9e26122a
commit
3e93a0991f
@ -11,7 +11,9 @@ import {
|
|||||||
ExternalAlertmanagerConfig,
|
ExternalAlertmanagerConfig,
|
||||||
ExternalAlertmanagers,
|
ExternalAlertmanagers,
|
||||||
ExternalAlertmanagersResponse,
|
ExternalAlertmanagersResponse,
|
||||||
|
GrafanaManagedContactPoint,
|
||||||
Matcher,
|
Matcher,
|
||||||
|
MuteTimeInterval,
|
||||||
} from '../../../../plugins/datasource/alertmanager/types';
|
} from '../../../../plugins/datasource/alertmanager/types';
|
||||||
import { NotifierDTO } from '../../../../types';
|
import { NotifierDTO } from '../../../../types';
|
||||||
import { withPerformanceLogging } from '../Analytics';
|
import { withPerformanceLogging } from '../Analytics';
|
||||||
@ -257,5 +259,12 @@ export const alertmanagerApi = alertingApi.injectEndpoints({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
// Grafana Managed Alertmanager only
|
||||||
|
getContactPointsList: build.query<GrafanaManagedContactPoint[], void>({
|
||||||
|
query: () => ({ url: '/api/v1/notifications/receivers' }),
|
||||||
|
}),
|
||||||
|
getMuteTimingList: build.query<MuteTimeInterval[], void>({
|
||||||
|
query: () => ({ url: '/api/v1/notifications/time-intervals' }),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -27,7 +27,13 @@ const RECEIVER_STATUS_POLLING_INTERVAL = 10 * 1000; // 10 seconds
|
|||||||
* 3. (if available) additional metadata about Grafana Managed contact points
|
* 3. (if available) additional metadata about Grafana Managed contact points
|
||||||
* 4. (if available) the OnCall plugin metadata
|
* 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 { selectedAlertmanager, isGrafanaAlertmanager } = useAlertmanager();
|
||||||
const { installed: onCallPluginInstalled, loading: onCallPluginStatusLoading } = usePluginBridge(
|
const { installed: onCallPluginInstalled, loading: onCallPluginStatusLoading } = usePluginBridge(
|
||||||
SupportedPlugin.OnCall
|
SupportedPlugin.OnCall
|
||||||
@ -64,6 +70,7 @@ export function useContactPointsWithStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetch the latest config from the Alertmanager
|
// 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(
|
const fetchAlertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery(
|
||||||
selectedAlertmanager!,
|
selectedAlertmanager!,
|
||||||
{
|
{
|
||||||
@ -73,31 +80,56 @@ export function useContactPointsWithStatus() {
|
|||||||
...result,
|
...result,
|
||||||
contactPoints: result.data
|
contactPoints: result.data
|
||||||
? enhanceContactPointsWithMetadata(
|
? enhanceContactPointsWithMetadata(
|
||||||
result.data,
|
|
||||||
fetchContactPointsStatus.data,
|
fetchContactPointsStatus.data,
|
||||||
fetchReceiverMetadata.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
|
// 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 =
|
const isLoading =
|
||||||
fetchAlertmanagerConfiguration.isLoading ||
|
fetchAlertmanagerConfiguration.isLoading ||
|
||||||
|
fetchGrafanaContactPoints.isLoading ||
|
||||||
fetchContactPointsStatus.isLoading ||
|
fetchContactPointsStatus.isLoading ||
|
||||||
onCallPluginStatusLoading ||
|
onCallPluginStatusLoading ||
|
||||||
onCallPluginIntegrationsLoading;
|
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 {
|
return {
|
||||||
error,
|
error,
|
||||||
isLoading,
|
isLoading,
|
||||||
contactPoints,
|
contactPoints,
|
||||||
refetchReceivers: fetchAlertmanagerConfiguration.refetch,
|
refetchReceivers: fetchGrafanaContactPoints.refetch,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
GrafanaManagedContactPoint,
|
GrafanaManagedContactPoint,
|
||||||
GrafanaManagedReceiverConfig,
|
GrafanaManagedReceiverConfig,
|
||||||
MatcherOperator,
|
MatcherOperator,
|
||||||
|
Receiver,
|
||||||
Route,
|
Route,
|
||||||
} from 'app/plugins/datasource/alertmanager/types';
|
} from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { NotifierDTO, NotifierStatus, ReceiversStateDTO } from 'app/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...
|
// TODO we should really add some type information to these receiver settings...
|
||||||
export function getReceiverDescription(receiver: ReceiverConfigWithMetadata): ReactNode | undefined {
|
export function getReceiverDescription(receiver: ReceiverConfigWithMetadata): ReactNode | undefined {
|
||||||
|
if (!receiver.settings) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
switch (receiver.type) {
|
switch (receiver.type) {
|
||||||
case 'email': {
|
case 'email': {
|
||||||
const hasEmailAddresses = 'addresses' in receiver.settings; // when dealing with alertmanager email_configs we don't normalize the settings
|
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 {
|
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[];
|
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
|
* 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
|
* 1. we iterate over all contact points
|
||||||
* 2. for each contact point we "enhance" it with the status or "undefined" for vanilla Alertmanager
|
* 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(
|
export function enhanceContactPointsWithMetadata(
|
||||||
result: AlertManagerCortexConfig,
|
|
||||||
status: ReceiversStateDTO[] = [],
|
status: ReceiversStateDTO[] = [],
|
||||||
notifiers: NotifierDTO[] = [],
|
notifiers: NotifierDTO[] = [],
|
||||||
onCallIntegrations: OnCallIntegrationDTO[] | undefined | null
|
onCallIntegrations: OnCallIntegrationDTO[] | undefined | null,
|
||||||
|
contactPoints: Receiver[],
|
||||||
|
alertmanagerConfiguration?: AlertManagerCortexConfig
|
||||||
): ContactPointWithMetadata[] {
|
): ContactPointWithMetadata[] {
|
||||||
const contactPoints = result.alertmanager_config.receivers ?? [];
|
|
||||||
|
|
||||||
// compute the entire inherited tree before finding what notification policies are using a particular contact point
|
// 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 usedContactPoints = getUsedContactPoints(fullyInheritedTree);
|
||||||
const usedContactPointsByName = countBy(usedContactPoints);
|
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 receivers = extractReceivers(contactPoint);
|
||||||
const statusForReceiver = status.find((status) => status.name === contactPoint.name);
|
const statusForReceiver = status.find((status) => status.name === contactPoint.name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...contactPoint,
|
...contactPoint,
|
||||||
numberOfPolicies: usedContactPointsByName[contactPoint.name] ?? 0,
|
numberOfPolicies:
|
||||||
|
alertmanagerConfiguration && usedContactPointsByName && (usedContactPointsByName[contactPoint.name] ?? 0),
|
||||||
grafana_managed_receiver_configs: receivers.map((receiver, index) => {
|
grafana_managed_receiver_configs: receivers.map((receiver, index) => {
|
||||||
const isOnCallReceiver = receiver.type === ReceiverTypes.OnCall;
|
const isOnCallReceiver = receiver.type === ReceiverTypes.OnCall;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...receiver,
|
...receiver,
|
||||||
[RECEIVER_STATUS_KEY]: statusForReceiver?.integrations[index],
|
[RECEIVER_STATUS_KEY]: statusForReceiver?.integrations[index],
|
||||||
@ -130,6 +140,7 @@ export function enhanceContactPointsWithMetadata(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAutoGeneratedPolicy(route: Route) {
|
export function isAutoGeneratedPolicy(route: Route) {
|
||||||
const simplifiedRoutingToggleEnabled = config.featureToggles.alertingSimplifiedRouting ?? false;
|
const simplifiedRoutingToggleEnabled = config.featureToggles.alertingSimplifiedRouting ?? false;
|
||||||
if (!simplifiedRoutingToggleEnabled) {
|
if (!simplifiedRoutingToggleEnabled) {
|
||||||
|
@ -22,7 +22,12 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
|
|||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const alertManagerName = alertManager.name;
|
const alertManagerName = alertManager.name;
|
||||||
const { isLoading, error: errorInContactPointStatus, contactPoints, refetchReceivers } = useContactPointsWithStatus();
|
const {
|
||||||
|
isLoading,
|
||||||
|
error: errorInContactPointStatus,
|
||||||
|
contactPoints,
|
||||||
|
refetchReceivers,
|
||||||
|
} = useContactPointsWithStatus({ includePoliciesCount: false });
|
||||||
const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState<
|
const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState<
|
||||||
ContactPointWithMetadata | undefined
|
ContactPointWithMetadata | undefined
|
||||||
>();
|
>();
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { Field, InputControl, MultiSelect, useStyles2 } from '@grafana/ui';
|
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 { 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 { mapMultiSelectValueToStrings } from 'app/features/alerting/unified/utils/amroutes';
|
||||||
|
|
||||||
import { getFormStyles } from '../../../../notification-policies/formStyles';
|
import { getFormStyles } from '../../../../notification-policies/formStyles';
|
||||||
@ -19,7 +21,7 @@ export function MuteTimingFields({ alertManager }: MuteTimingFieldsProps) {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext<RuleFormValues>();
|
} = useFormContext<RuleFormValues>();
|
||||||
|
|
||||||
const muteTimingOptions = useMuteTimingOptions();
|
const muteTimingOptions = useSelectableMuteTimings();
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
label="Mute timings"
|
label="Mute timings"
|
||||||
@ -44,3 +46,21 @@ export function MuteTimingFields({ alertManager }: MuteTimingFieldsProps) {
|
|||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useSelectableMuteTimings(): Array<SelectableValue<string>> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
@ -14,8 +14,6 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
useStyles2,
|
useStyles2,
|
||||||
} from '@grafana/ui';
|
} 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 { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
|
||||||
import {
|
import {
|
||||||
commonGroupByOptions,
|
commonGroupByOptions,
|
||||||
@ -42,7 +40,7 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext<RuleFormValues>();
|
} = useFormContext<RuleFormValues>();
|
||||||
const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues([]));
|
const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues([]));
|
||||||
const { groupBy, groupIntervalValue, groupWaitValue, repeatIntervalValue } = useGetDefaultsForRoutingSettings();
|
const { groupIntervalValue, groupWaitValue, repeatIntervalValue } = getDefaultsForRoutingSettings();
|
||||||
const overrideGrouping = watch(`contactPoints.${alertManager}.overrideGrouping`);
|
const overrideGrouping = watch(`contactPoints.${alertManager}.overrideGrouping`);
|
||||||
const overrideTimings = watch(`contactPoints.${alertManager}.overrideTimings`);
|
const overrideTimings = watch(`contactPoints.${alertManager}.overrideTimings`);
|
||||||
const requiredFieldsInGroupBy = ['grafana_folder', 'alertname'];
|
const requiredFieldsInGroupBy = ['grafana_folder', 'alertname'];
|
||||||
@ -56,7 +54,7 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
</InlineField>
|
</InlineField>
|
||||||
{!overrideGrouping && (
|
{!overrideGrouping && (
|
||||||
<Text variant="body" color="secondary">
|
<Text variant="body" color="secondary">
|
||||||
Grouping: <strong>{groupBy.join(', ')}</strong>
|
Grouping: <strong>{requiredFieldsInGroupBy.join(', ')}</strong>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -131,20 +129,13 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function useGetDefaultsForRoutingSettings() {
|
function getDefaultsForRoutingSettings() {
|
||||||
const { selectedAlertmanager } = useAlertmanager();
|
return {
|
||||||
const { currentData } = useAlertmanagerConfig(selectedAlertmanager);
|
groupWaitValue: TIMING_OPTIONS_DEFAULTS.group_wait,
|
||||||
const config = currentData?.alertmanager_config;
|
groupIntervalValue: TIMING_OPTIONS_DEFAULTS.group_interval,
|
||||||
return React.useMemo(() => {
|
repeatIntervalValue: TIMING_OPTIONS_DEFAULTS.repeat_interval,
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
switchElement: css({
|
switchElement: css({
|
||||||
flexFlow: 'row-reverse',
|
flexFlow: 'row-reverse',
|
||||||
|
Loading…
Reference in New Issue
Block a user