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/matchers.ts

89 lines
2.9 KiB

import { uniqBy } from 'lodash';
import { Labels } from '@grafana/data';
import { Matcher, MatcherOperator } from 'app/plugins/datasource/alertmanager/types';
import { Alert } from 'app/types/unified-alerting';
import { MatcherFieldValue } from '../types/silence-form';
import { parseMatcher } from './alertmanager';
// Parses a list of entries like like "['foo=bar', 'baz=~bad*']" into SilenceMatcher[]
export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] {
const parsedMatchers = matcherPairs.filter((x) => !!x.trim()).map((x) => parseMatcher(x.trim()));
// Due to migration, old alert rules might have a duplicated alertname label
// To handle that case want to filter out duplicates and make sure there are only unique labels
return uniqBy(parsedMatchers, (matcher) => matcher.name);
}
export const getMatcherQueryParams = (labels: Labels) => {
const validMatcherLabels = Object.entries(labels).filter(
([labelKey]) => !(labelKey.startsWith('__') && labelKey.endsWith('__'))
);
const matcherUrlParams = new URLSearchParams();
validMatcherLabels.forEach(([labelKey, labelValue]) =>
matcherUrlParams.append('matcher', `${labelKey}=${labelValue}`)
);
return matcherUrlParams;
};
interface MatchedInstance {
id: string;
data: {
matchedInstance: Alert;
};
}
export const findAlertInstancesWithMatchers = (
instances: Alert[],
matchers: MatcherFieldValue[]
): MatchedInstance[] => {
const anchorRegex = (regexpString: string): RegExp => {
// Silence matchers are always fully anchored in the Alertmanager: https://github.com/prometheus/alertmanager/pull/748
if (!regexpString.startsWith('^')) {
regexpString = '^' + regexpString;
}
if (!regexpString.endsWith('$')) {
regexpString = regexpString + '$';
}
return new RegExp(regexpString);
};
const matchesInstance = (instance: Alert, matcher: MatcherFieldValue) => {
return Object.entries(instance.labels).some(([key, value]) => {
if (!matcher.name || !matcher.value) {
return false;
}
if (matcher.name !== key) {
return false;
}
switch (matcher.operator) {
case MatcherOperator.equal:
return matcher.value === value;
case MatcherOperator.notEqual:
return matcher.value !== value;
case MatcherOperator.regex:
const regex = anchorRegex(matcher.value);
return regex.test(value);
case MatcherOperator.notRegex:
const negregex = anchorRegex(matcher.value);
return !negregex.test(value);
default:
return false;
}
});
};
const filteredInstances = instances.filter((instance) => {
return matchers.every((matcher) => matchesInstance(instance, matcher));
});
const mappedInstances = filteredInstances.map((instance) => ({
id: `${instance.activeAt}-${instance.value}`,
data: { matchedInstance: instance },
}));
return mappedInstances;
};