mirror of https://github.com/grafana/grafana.git
227 lines
8.2 KiB
TypeScript
227 lines
8.2 KiB
TypeScript
import { difference, groupBy, take, trim, upperFirst } from 'lodash';
|
|
import { ReactNode } from 'react';
|
|
|
|
import { computeInheritedTree } from '@grafana/alerting/unstable';
|
|
import { t } from '@grafana/i18n';
|
|
import { NotifierDTO, NotifierStatus, ReceiversStateDTO } from 'app/features/alerting/unified/types/alerting';
|
|
import { canAdminEntity, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils';
|
|
import {
|
|
AlertManagerCortexConfig,
|
|
GrafanaManagedContactPoint,
|
|
GrafanaManagedReceiverConfig,
|
|
MatcherOperator,
|
|
Receiver,
|
|
Route,
|
|
} from 'app/plugins/datasource/alertmanager/types';
|
|
|
|
import { OnCallIntegrationDTO } from '../../api/onCallApi';
|
|
import { extractReceivers } from '../../utils/receivers';
|
|
import { routeAdapter } from '../../utils/routeAdapter';
|
|
import { ReceiverTypes } from '../receivers/grafanaAppReceivers/onCall/onCall';
|
|
import { ReceiverPluginMetadata, getOnCallMetadata } from '../receivers/grafanaAppReceivers/useReceiversMetadata';
|
|
|
|
import { RECEIVER_META_KEY, RECEIVER_PLUGIN_META_KEY, RECEIVER_STATUS_KEY } from './constants';
|
|
|
|
const AUTOGENERATED_RECEIVER_POLICY_MATCHER_KEY = '__grafana_receiver__';
|
|
|
|
// TODO we should really add some type information to these receiver settings...
|
|
export function getReceiverDescription(receiver: ReceiverConfigWithMetadata): ReactNode | undefined {
|
|
if (!receiver.settings) {
|
|
return undefined;
|
|
}
|
|
const { settings } = receiver;
|
|
switch (receiver.type) {
|
|
case 'email': {
|
|
const addresses = settings.addresses || settings.to; // when dealing with alertmanager email_configs we don't normalize the settings
|
|
return addresses ? summarizeEmailAddresses(addresses) : undefined;
|
|
}
|
|
case 'slack': {
|
|
const recipient = settings.recipient || settings.channel;
|
|
if (!recipient) {
|
|
return;
|
|
}
|
|
|
|
// Slack channel name might have a "#" in the recipient already
|
|
const channelName = recipient.replace(/^#/, '');
|
|
return `#${channelName}`;
|
|
}
|
|
case 'kafka': {
|
|
return settings.kafkaTopic;
|
|
}
|
|
case 'webhook': {
|
|
return settings.url;
|
|
}
|
|
case 'jira': {
|
|
return t(
|
|
'alerting.contact-points.receiver-summary.jira',
|
|
`Creates a "{{issueType}}" issue in the "{{project}}" project`,
|
|
{
|
|
issueType: settings.issue_type,
|
|
project: settings.project,
|
|
url: settings.api_url,
|
|
}
|
|
);
|
|
}
|
|
case ReceiverTypes.OnCall: {
|
|
return receiver[RECEIVER_PLUGIN_META_KEY]?.description;
|
|
}
|
|
default:
|
|
return receiver[RECEIVER_META_KEY]?.description;
|
|
}
|
|
}
|
|
|
|
// input: foo+1@bar.com, foo+2@bar.com, foo+3@bar.com, foo+4@bar.com
|
|
// output: foo+1@bar.com, foo+2@bar.com, +2 more
|
|
export function summarizeEmailAddresses(addresses: string): string {
|
|
const MAX_ADDRESSES_SHOWN = 3;
|
|
const SUPPORTED_SEPARATORS = /,|;|\n+/g;
|
|
|
|
// split all email addresses
|
|
const emails = addresses.trim().split(SUPPORTED_SEPARATORS).map(trim);
|
|
|
|
// grab the first 3 and the rest
|
|
const summary = take(emails, MAX_ADDRESSES_SHOWN);
|
|
const rest = difference(emails, summary);
|
|
|
|
if (rest.length) {
|
|
summary.push(`+${rest.length} more`);
|
|
}
|
|
|
|
return summary.join(', ');
|
|
}
|
|
|
|
// Grafana Managed contact points have receivers with additional diagnostics
|
|
export interface ReceiverConfigWithMetadata extends GrafanaManagedReceiverConfig {
|
|
// we're using a symbol here so we'll never have a conflict on keys for a receiver
|
|
// we also specify that the diagnostics might be "undefined" for vanilla Alertmanager
|
|
[RECEIVER_STATUS_KEY]?: NotifierStatus | undefined;
|
|
[RECEIVER_META_KEY]: {
|
|
name: string;
|
|
description?: string;
|
|
};
|
|
// optional metadata that comes from a particular plugin (like Grafana OnCall)
|
|
[RECEIVER_PLUGIN_META_KEY]?: ReceiverPluginMetadata;
|
|
}
|
|
|
|
export interface ContactPointWithMetadata extends GrafanaManagedContactPoint {
|
|
id: string;
|
|
policies?: RouteReference[]; // now is optional as we don't have the data from the read-only endpoint
|
|
grafana_managed_receiver_configs: ReceiverConfigWithMetadata[];
|
|
}
|
|
|
|
type EnhanceContactPointsArgs = {
|
|
status?: ReceiversStateDTO[];
|
|
notifiers?: NotifierDTO[];
|
|
onCallIntegrations?: OnCallIntegrationDTO[] | undefined | null;
|
|
contactPoints: Receiver[];
|
|
alertmanagerConfiguration?: AlertManagerCortexConfig;
|
|
};
|
|
|
|
/**
|
|
* 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({
|
|
status = [],
|
|
notifiers = [],
|
|
onCallIntegrations,
|
|
contactPoints,
|
|
alertmanagerConfiguration,
|
|
}: EnhanceContactPointsArgs): ContactPointWithMetadata[] {
|
|
// compute the entire inherited tree before finding what notification policies are using a particular contact point
|
|
const fullyInheritedTree = computeInheritedTree(
|
|
routeAdapter.toPackage(alertmanagerConfiguration?.alertmanager_config?.route ?? {})
|
|
);
|
|
const usedContactPoints = getUsedContactPoints(routeAdapter.fromPackage(fullyInheritedTree));
|
|
const usedContactPointsByName = groupBy(usedContactPoints, 'receiver');
|
|
|
|
const enhanced = contactPoints.map((contactPoint) => {
|
|
const receivers = extractReceivers(contactPoint);
|
|
const statusForReceiver = status.find((status) => status.name === contactPoint.name);
|
|
|
|
const id = getContactPointIdentifier(contactPoint);
|
|
|
|
return {
|
|
...contactPoint,
|
|
id,
|
|
policies:
|
|
alertmanagerConfiguration && usedContactPointsByName && (usedContactPointsByName[contactPoint.name] ?? []),
|
|
grafana_managed_receiver_configs: receivers.map((receiver, index) => {
|
|
const isOnCallReceiver = receiver.type === ReceiverTypes.OnCall;
|
|
// if we don't have alertmanagerConfiguration we can't get the metadata for oncall receivers,
|
|
// because we don't have the url, as we are not using the alertmanager configuration
|
|
// but the contact points returned by the read only permissions contact points endpoint (/api/v1/notifications/receivers)
|
|
return {
|
|
...receiver,
|
|
[RECEIVER_STATUS_KEY]: statusForReceiver?.integrations[index],
|
|
[RECEIVER_META_KEY]: getNotifierMetadata(notifiers, receiver),
|
|
// if OnCall plugin is installed, we'll add it to the receiver's plugin metadata
|
|
[RECEIVER_PLUGIN_META_KEY]: isOnCallReceiver
|
|
? getOnCallMetadata(onCallIntegrations, receiver, Boolean(alertmanagerConfiguration))
|
|
: undefined,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
|
|
return enhanced.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
function getContactPointIdentifier(contactPoint: Receiver): string {
|
|
return 'id' in contactPoint && contactPoint.id ? contactPoint.id : contactPoint.name;
|
|
}
|
|
|
|
export function isAutoGeneratedPolicy(route: Route) {
|
|
if (!route.object_matchers) {
|
|
return false;
|
|
}
|
|
return (
|
|
route.object_matchers.some((objectMatcher) => {
|
|
return (
|
|
objectMatcher[0] === AUTOGENERATED_RECEIVER_POLICY_MATCHER_KEY && objectMatcher[1] === MatcherOperator.equal
|
|
);
|
|
}) ?? false
|
|
);
|
|
}
|
|
|
|
export interface RouteReference {
|
|
receiver: string;
|
|
route: {
|
|
type: 'auto-generated' | 'normal';
|
|
};
|
|
}
|
|
|
|
export function getUsedContactPoints(route: Route): RouteReference[] {
|
|
const childrenContactPoints = route.routes?.flatMap((route) => getUsedContactPoints(route)) ?? [];
|
|
|
|
if (route.receiver) {
|
|
return [
|
|
{
|
|
receiver: route.receiver,
|
|
route: {
|
|
type: isAutoGeneratedPolicy(route) ? 'auto-generated' : 'normal',
|
|
},
|
|
},
|
|
...childrenContactPoints,
|
|
];
|
|
}
|
|
|
|
return childrenContactPoints;
|
|
}
|
|
|
|
function getNotifierMetadata(notifiers: NotifierDTO[], receiver: GrafanaManagedReceiverConfig) {
|
|
const match = notifiers.find((notifier) => notifier.type === receiver.type);
|
|
|
|
return {
|
|
name: match?.name ?? upperFirst(receiver.type),
|
|
description: match?.description,
|
|
};
|
|
}
|
|
|
|
export const showManageContactPointPermissions = (alertmanager: string, contactPoint: GrafanaManagedContactPoint) =>
|
|
shouldUseK8sApi(alertmanager) && canAdminEntity(contactPoint);
|