The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/features/alerting/unified/utils/amroutes.ts

274 lines
8.9 KiB

import { uniqueId } from 'lodash';
import { SelectableValue } from '@grafana/data';
import { MatcherOperator, ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
import { FormAmRoute } from '../types/amroutes';
import { MatcherFieldValue } from '../types/silence-form';
import { matcherToMatcherField } from './alertmanager';
import { GRAFANA_RULES_SOURCE_NAME } from './datasource';
import { normalizeMatchers, parseMatcher, quoteWithEscape, unquoteWithUnescape } from './matchers';
import { findExistingRoute } from './routeTree';
Alerting: Add notification policies preview in alert creation (#68839) * Add notification policies preview in alert rule form Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com> * Refactor using new useGetPotentialInstances hook and apply some style changes * Add notification policy detail modal * Use backtesting api for simulating potential alert instances * Fix logic to travserse all the children from the root route * Split notification preview by alert manager * Add instance count to matching policy header and fix some styles * Move some logic to a new hook useGetAlertManagersSourceNames to make the code more clean * Fix some tests * Add initial test for NotificationPreview * Use button to preview potential instances * Add link to contact point details * Add route matching result details * Show AlertManager image in the routing preview list * Add tests setup, add single AM preview test * Handle no matchers and no labels use case * Update some style in collapse component and fix policy path in modal * Update modal styles * Update styles * Update collapse header styling * Normalize tree nodes should happen before findMatchingRoutes call * Fix findMatchingRoutes and findMatchingAlertGroups methods after reabasing * Move instances matching to the web worker code * Fix config fetching for vanilla prometheus AMs * Add tests * Add tests mocks * Fix tests after adding web worker * Display matching labels for each matching alert instance * Add minor css improvements * Revert changes added in Collapse component as we don't use it anymore * Move the route details modal to a separate file * Move NotificationRoute and preview hook into separate files * Fix Alertmanager preview tests * Fix tests * Move matcher code to a separate file, improve matcher mock * Add permissions control for contact point edit view link * Fix from and to for the temporal use of backtesting api * Fix tests, add lazy loading of the preview component Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com> * Fix preview test * Add onclick on the header div so it collapse and expands when clicking on it, and update styles to be consistent with the rest of tables * Adapt the code to the new rule testing endpoint definition * Fix tests * small changes after reviewing the final code * compute entire inherited tree before computing the routes map * Throw error in case of not having receiver in routesByIdMap and add test for the use case of inheriting receiver from parent to check UI throws no errors * Add list of labels in the policy route path that produces the policy matchers to match potential instances * Use color determined by the key, in label tags when hovering matchers in the policy tree * Remove labels in modal and handle empty string as receiver to inherit from parent as we do with undefined * Revert "Add list of labels in the policy route path that produces the policy matchers to match potential instances" This reverts commit ee73ae9cf9caf10c33bcd58973279f23437a9146. * fix inheritance for computeInheritedTree * Fix message shown when preview has not been executed yet * First round for adressing PR review comments * Adress the rest of PR review commments * Update texts and rename id prop in NotificaitonStep to alertUid --------- Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com> Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2 years ago
import { isValidPrometheusDuration, safeParseDurationstr } from './time';
const matchersToArrayFieldMatchers = (
matchers: Record<string, string> | undefined,
isRegex: boolean
): MatcherFieldValue[] =>
Object.entries(matchers ?? {}).reduce<MatcherFieldValue[]>(
(acc, [name, value]) => [
...acc,
{
name,
value,
operator: isRegex ? MatcherOperator.regex : MatcherOperator.equal,
},
],
[]
);
const selectableValueToString = (selectableValue: SelectableValue<string>): string => selectableValue.value!;
const selectableValuesToStrings = (arr: Array<SelectableValue<string>> | undefined): string[] =>
(arr ?? []).map(selectableValueToString);
export const emptyArrayFieldMatcher: MatcherFieldValue = {
name: '',
value: '',
operator: MatcherOperator.equal,
};
// Default route group_by labels for newly created routes.
export const defaultGroupBy = ['grafana_folder', 'alertname'];
// Common route group_by options for multiselect drop-down
export const commonGroupByOptions = [
{ label: 'grafana_folder', value: 'grafana_folder', isFixed: true },
{ label: 'alertname', value: 'alertname', isFixed: true },
{ label: 'Disable (...)', value: '...' },
];
export const emptyRoute: FormAmRoute = {
id: '',
overrideGrouping: false,
groupBy: defaultGroupBy,
object_matchers: [],
routes: [],
continue: false,
receiver: '',
overrideTimings: false,
groupWaitValue: '',
groupIntervalValue: '',
repeatIntervalValue: '',
muteTimeIntervals: [],
};
// add unique identifiers to each route in the route tree, that way we can figure out what route we've edited / deleted
export function addUniqueIdentifierToRoute(route: Route): RouteWithID {
return {
id: uniqueId('route-'),
...route,
routes: (route.routes ?? []).map(addUniqueIdentifierToRoute),
};
}
//returns route, and a record mapping id to existing route
export const amRouteToFormAmRoute = (route: RouteWithID | Route | undefined): FormAmRoute => {
if (!route) {
return emptyRoute;
}
const id = 'id' in route ? route.id : uniqueId('route-');
if (Object.keys(route).length === 0) {
const formAmRoute = { ...emptyRoute, id };
return formAmRoute;
}
const formRoutes: FormAmRoute[] = [];
route.routes?.forEach((subRoute) => {
const subFormRoute = amRouteToFormAmRoute(subRoute);
formRoutes.push(subFormRoute);
});
const objectMatchers =
route.object_matchers?.map((matcher) => ({ name: matcher[0], operator: matcher[1], value: matcher[2] })) ?? [];
const matchers =
route.matchers
?.map((matcher) => matcherToMatcherField(parseMatcher(matcher)))
.map(({ name, operator, value }) => ({
name,
operator,
value: unquoteWithUnescape(value),
})) ?? [];
return {
id,
// Frontend migration to use object_matchers instead of matchers, match, and match_re
object_matchers: [
...matchers,
...objectMatchers,
...matchersToArrayFieldMatchers(route.match, false),
...matchersToArrayFieldMatchers(route.match_re, true),
],
continue: route.continue ?? false,
receiver: route.receiver ?? '',
overrideGrouping: Array.isArray(route.group_by) && route.group_by.length > 0,
groupBy: route.group_by ?? undefined,
overrideTimings: [route.group_wait, route.group_interval, route.repeat_interval].some(Boolean),
groupWaitValue: route.group_wait ?? '',
groupIntervalValue: route.group_interval ?? '',
repeatIntervalValue: route.repeat_interval ?? '',
routes: formRoutes,
muteTimeIntervals: route.mute_time_intervals ?? [],
};
};
// convert a FormAmRoute to a Route
export const formAmRouteToAmRoute = (
alertManagerSourceName: string,
formAmRoute: Partial<FormAmRoute>,
routeTree: RouteWithID
): Route => {
const existing = findExistingRoute(formAmRoute.id ?? '', routeTree);
const {
overrideGrouping,
groupBy,
overrideTimings,
groupWaitValue,
groupIntervalValue,
repeatIntervalValue,
receiver,
} = formAmRoute;
// "undefined" means "inherit from the parent policy", currently supported by group_by, group_wait, group_interval, and repeat_interval
const INHERIT_FROM_PARENT = undefined;
const group_by = overrideGrouping ? groupBy : INHERIT_FROM_PARENT;
const overrideGroupWait = overrideTimings && groupWaitValue;
const group_wait = overrideGroupWait ? groupWaitValue : INHERIT_FROM_PARENT;
const overrideGroupInterval = overrideTimings && groupIntervalValue;
const group_interval = overrideGroupInterval ? groupIntervalValue : INHERIT_FROM_PARENT;
const overrideRepeatInterval = overrideTimings && repeatIntervalValue;
const repeat_interval = overrideRepeatInterval ? repeatIntervalValue : INHERIT_FROM_PARENT;
// Empty matcher values are valid. Such matchers require specified label to not exists
const object_matchers: ObjectMatcher[] | undefined = formAmRoute.object_matchers
?.filter((route) => route.name && route.operator && route.value !== null && route.value !== undefined)
.map(({ name, operator, value }) => [name, operator, value]);
const routes = formAmRoute.routes?.map((subRoute) =>
formAmRouteToAmRoute(alertManagerSourceName, subRoute, routeTree)
);
const amRoute: Route = {
...(existing ?? {}),
continue: formAmRoute.continue,
group_by: group_by,
object_matchers: object_matchers,
match: undefined, // DEPRECATED: Use matchers
match_re: undefined, // DEPRECATED: Use matchers
group_wait,
group_interval,
repeat_interval,
routes: routes,
mute_time_intervals: formAmRoute.muteTimeIntervals,
receiver: receiver,
};
// non-Grafana managed rules should use "matchers", Grafana-managed rules should use "object_matchers"
// Grafana maintains a fork of AM to support all utf-8 characters in the "object_matchers" property values but this
// does not exist in upstream AlertManager
if (alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME) {
amRoute.matchers = formAmRoute.object_matchers?.map(
({ name, operator, value }) => `${name}${operator}${quoteWithEscape(value)}`
);
amRoute.object_matchers = undefined;
} else {
amRoute.object_matchers = normalizeMatchers(amRoute);
amRoute.matchers = undefined;
}
if (formAmRoute.receiver) {
amRoute.receiver = formAmRoute.receiver;
}
return amRoute;
};
export const stringToSelectableValue = (str: string): SelectableValue<string> => ({
label: str,
value: str,
});
export const stringsToSelectableValues = (arr: string[] | undefined): Array<SelectableValue<string>> =>
(arr ?? []).map(stringToSelectableValue);
export const mapSelectValueToString = (selectableValue: SelectableValue<string>): string | null => {
// this allows us to deal with cleared values
if (selectableValue === null) {
return null;
}
if (!selectableValue) {
return '';
}
return selectableValueToString(selectableValue) ?? '';
};
export const mapMultiSelectValueToStrings = (
selectableValues: Array<SelectableValue<string>> | undefined
): string[] => {
if (!selectableValues) {
return [];
}
return selectableValuesToStrings(selectableValues);
};
export function promDurationValidator(duration?: string) {
if (!duration || duration.length === 0) {
return true;
}
return isValidPrometheusDuration(duration) || 'Invalid duration format. Must be {number}{time_unit}';
}
Alerting: Add notification policies preview in alert creation (#68839) * Add notification policies preview in alert rule form Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com> * Refactor using new useGetPotentialInstances hook and apply some style changes * Add notification policy detail modal * Use backtesting api for simulating potential alert instances * Fix logic to travserse all the children from the root route * Split notification preview by alert manager * Add instance count to matching policy header and fix some styles * Move some logic to a new hook useGetAlertManagersSourceNames to make the code more clean * Fix some tests * Add initial test for NotificationPreview * Use button to preview potential instances * Add link to contact point details * Add route matching result details * Show AlertManager image in the routing preview list * Add tests setup, add single AM preview test * Handle no matchers and no labels use case * Update some style in collapse component and fix policy path in modal * Update modal styles * Update styles * Update collapse header styling * Normalize tree nodes should happen before findMatchingRoutes call * Fix findMatchingRoutes and findMatchingAlertGroups methods after reabasing * Move instances matching to the web worker code * Fix config fetching for vanilla prometheus AMs * Add tests * Add tests mocks * Fix tests after adding web worker * Display matching labels for each matching alert instance * Add minor css improvements * Revert changes added in Collapse component as we don't use it anymore * Move the route details modal to a separate file * Move NotificationRoute and preview hook into separate files * Fix Alertmanager preview tests * Fix tests * Move matcher code to a separate file, improve matcher mock * Add permissions control for contact point edit view link * Fix from and to for the temporal use of backtesting api * Fix tests, add lazy loading of the preview component Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com> * Fix preview test * Add onclick on the header div so it collapse and expands when clicking on it, and update styles to be consistent with the rest of tables * Adapt the code to the new rule testing endpoint definition * Fix tests * small changes after reviewing the final code * compute entire inherited tree before computing the routes map * Throw error in case of not having receiver in routesByIdMap and add test for the use case of inheriting receiver from parent to check UI throws no errors * Add list of labels in the policy route path that produces the policy matchers to match potential instances * Use color determined by the key, in label tags when hovering matchers in the policy tree * Remove labels in modal and handle empty string as receiver to inherit from parent as we do with undefined * Revert "Add list of labels in the policy route path that produces the policy matchers to match potential instances" This reverts commit ee73ae9cf9caf10c33bcd58973279f23437a9146. * fix inheritance for computeInheritedTree * Fix message shown when preview has not been executed yet * First round for adressing PR review comments * Adress the rest of PR review commments * Update texts and rename id prop in NotificaitonStep to alertUid --------- Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com> Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2 years ago
// function to convert ObjectMatchers to a array of strings
export const objectMatchersToString = (matchers: ObjectMatcher[]): string[] => {
return matchers.map((matcher) => {
const [name, operator, value] = matcher;
return `${name}${operator}${value}`;
});
};
export const repeatIntervalValidator = (repeatInterval: string, groupInterval = '') => {
if (repeatInterval.length === 0) {
return true;
}
const validRepeatInterval = promDurationValidator(repeatInterval);
const validGroupInterval = promDurationValidator(groupInterval);
if (validRepeatInterval !== true) {
return validRepeatInterval;
}
if (validGroupInterval !== true) {
return validGroupInterval;
}
const repeatDuration = safeParseDurationstr(repeatInterval);
const groupDuration = safeParseDurationstr(groupInterval);
const isRepeatLowerThanGroupDuration = groupDuration !== 0 && repeatDuration < groupDuration;
return isRepeatLowerThanGroupDuration ? 'Repeat interval should be higher or equal to Group interval' : true;
};