matchers.ts 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { uniqBy } from 'lodash';
  2. import { Labels } from '@grafana/data';
  3. import { Matcher, MatcherOperator } from 'app/plugins/datasource/alertmanager/types';
  4. import { Alert } from 'app/types/unified-alerting';
  5. import { MatcherFieldValue } from '../types/silence-form';
  6. import { parseMatcher } from './alertmanager';
  7. // Parses a list of entries like like "['foo=bar', 'baz=~bad*']" into SilenceMatcher[]
  8. export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] {
  9. const parsedMatchers = matcherPairs.filter((x) => !!x.trim()).map((x) => parseMatcher(x.trim()));
  10. // Due to migration, old alert rules might have a duplicated alertname label
  11. // To handle that case want to filter out duplicates and make sure there are only unique labels
  12. return uniqBy(parsedMatchers, (matcher) => matcher.name);
  13. }
  14. export const getMatcherQueryParams = (labels: Labels) => {
  15. const validMatcherLabels = Object.entries(labels).filter(
  16. ([labelKey]) => !(labelKey.startsWith('__') && labelKey.endsWith('__'))
  17. );
  18. const matcherUrlParams = new URLSearchParams();
  19. validMatcherLabels.forEach(([labelKey, labelValue]) =>
  20. matcherUrlParams.append('matcher', `${labelKey}=${labelValue}`)
  21. );
  22. return matcherUrlParams;
  23. };
  24. interface MatchedInstance {
  25. id: string;
  26. data: {
  27. matchedInstance: Alert;
  28. };
  29. }
  30. export const findAlertInstancesWithMatchers = (
  31. instances: Alert[],
  32. matchers: MatcherFieldValue[]
  33. ): MatchedInstance[] => {
  34. const anchorRegex = (regexpString: string): RegExp => {
  35. // Silence matchers are always fully anchored in the Alertmanager: https://github.com/prometheus/alertmanager/pull/748
  36. if (!regexpString.startsWith('^')) {
  37. regexpString = '^' + regexpString;
  38. }
  39. if (!regexpString.endsWith('$')) {
  40. regexpString = regexpString + '$';
  41. }
  42. return new RegExp(regexpString);
  43. };
  44. const matchesInstance = (instance: Alert, matcher: MatcherFieldValue) => {
  45. return Object.entries(instance.labels).some(([key, value]) => {
  46. if (!matcher.name || !matcher.value) {
  47. return false;
  48. }
  49. if (matcher.name !== key) {
  50. return false;
  51. }
  52. switch (matcher.operator) {
  53. case MatcherOperator.equal:
  54. return matcher.value === value;
  55. case MatcherOperator.notEqual:
  56. return matcher.value !== value;
  57. case MatcherOperator.regex:
  58. const regex = anchorRegex(matcher.value);
  59. return regex.test(value);
  60. case MatcherOperator.notRegex:
  61. const negregex = anchorRegex(matcher.value);
  62. return !negregex.test(value);
  63. default:
  64. return false;
  65. }
  66. });
  67. };
  68. const filteredInstances = instances.filter((instance) => {
  69. return matchers.every((matcher) => matchesInstance(instance, matcher));
  70. });
  71. const mappedInstances = filteredInstances.map((instance) => ({
  72. id: `${instance.activeAt}-${instance.value}`,
  73. data: { matchedInstance: instance },
  74. }));
  75. return mappedInstances;
  76. };