amroutes.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { isUndefined, omitBy } from 'lodash';
  2. import { Validate } from 'react-hook-form';
  3. import { SelectableValue } from '@grafana/data';
  4. import { MatcherOperator, Route } from 'app/plugins/datasource/alertmanager/types';
  5. import { FormAmRoute } from '../types/amroutes';
  6. import { MatcherFieldValue } from '../types/silence-form';
  7. import { matcherToMatcherField, parseMatcher } from './alertmanager';
  8. import { GRAFANA_RULES_SOURCE_NAME } from './datasource';
  9. import { parseInterval, timeOptions } from './time';
  10. const defaultValueAndType: [string, string] = ['', ''];
  11. const matchersToArrayFieldMatchers = (
  12. matchers: Record<string, string> | undefined,
  13. isRegex: boolean
  14. ): MatcherFieldValue[] =>
  15. Object.entries(matchers ?? {}).reduce<MatcherFieldValue[]>(
  16. (acc, [name, value]) => [
  17. ...acc,
  18. {
  19. name,
  20. value,
  21. operator: isRegex ? MatcherOperator.regex : MatcherOperator.equal,
  22. },
  23. ],
  24. [] as MatcherFieldValue[]
  25. );
  26. const intervalToValueAndType = (
  27. strValue: string | undefined,
  28. defaultValue?: typeof defaultValueAndType
  29. ): [string, string] => {
  30. if (!strValue) {
  31. return defaultValue ?? defaultValueAndType;
  32. }
  33. const [value, valueType] = strValue ? parseInterval(strValue) : [undefined, undefined];
  34. const timeOption = timeOptions.find((opt) => opt.value === valueType);
  35. if (!value || !timeOption) {
  36. return defaultValueAndType;
  37. }
  38. return [String(value), timeOption.value];
  39. };
  40. const selectableValueToString = (selectableValue: SelectableValue<string>): string => selectableValue.value!;
  41. const selectableValuesToStrings = (arr: Array<SelectableValue<string>> | undefined): string[] =>
  42. (arr ?? []).map(selectableValueToString);
  43. export const emptyArrayFieldMatcher: MatcherFieldValue = {
  44. name: '',
  45. value: '',
  46. operator: MatcherOperator.equal,
  47. };
  48. export const emptyRoute: FormAmRoute = {
  49. id: '',
  50. overrideGrouping: false,
  51. groupBy: [],
  52. object_matchers: [],
  53. routes: [],
  54. continue: false,
  55. receiver: '',
  56. overrideTimings: false,
  57. groupWaitValue: '',
  58. groupWaitValueType: timeOptions[0].value,
  59. groupIntervalValue: '',
  60. groupIntervalValueType: timeOptions[0].value,
  61. repeatIntervalValue: '',
  62. repeatIntervalValueType: timeOptions[0].value,
  63. muteTimeIntervals: [],
  64. };
  65. //returns route, and a record mapping id to existing route
  66. export const amRouteToFormAmRoute = (route: Route | undefined): [FormAmRoute, Record<string, Route>] => {
  67. if (!route) {
  68. return [emptyRoute, {}];
  69. }
  70. const id = String(Math.random());
  71. const id2route = {
  72. [id]: route,
  73. };
  74. if (Object.keys(route).length === 0) {
  75. const formAmRoute = { ...emptyRoute, id };
  76. return [formAmRoute, id2route];
  77. }
  78. const formRoutes: FormAmRoute[] = [];
  79. route.routes?.forEach((subRoute) => {
  80. const [subFormRoute, subId2Route] = amRouteToFormAmRoute(subRoute);
  81. formRoutes.push(subFormRoute);
  82. Object.assign(id2route, subId2Route);
  83. });
  84. // Frontend migration to use object_matchers instead of matchers
  85. const matchers = route.matchers
  86. ? route.matchers?.map((matcher) => matcherToMatcherField(parseMatcher(matcher))) ?? []
  87. : route.object_matchers?.map(
  88. (matcher) => ({ name: matcher[0], operator: matcher[1], value: matcher[2] } as MatcherFieldValue)
  89. ) ?? [];
  90. const [groupWaitValue, groupWaitValueType] = intervalToValueAndType(route.group_wait, ['', 's']);
  91. const [groupIntervalValue, groupIntervalValueType] = intervalToValueAndType(route.group_interval, ['', 'm']);
  92. const [repeatIntervalValue, repeatIntervalValueType] = intervalToValueAndType(route.repeat_interval, ['', 'h']);
  93. return [
  94. {
  95. id,
  96. object_matchers: [
  97. ...matchers,
  98. ...matchersToArrayFieldMatchers(route.match, false),
  99. ...matchersToArrayFieldMatchers(route.match_re, true),
  100. ],
  101. continue: route.continue ?? false,
  102. receiver: route.receiver ?? '',
  103. overrideGrouping: Array.isArray(route.group_by) && route.group_by.length !== 0,
  104. groupBy: route.group_by ?? [],
  105. overrideTimings: [groupWaitValue, groupIntervalValue, repeatIntervalValue].some(Boolean),
  106. groupWaitValue,
  107. groupWaitValueType,
  108. groupIntervalValue,
  109. groupIntervalValueType,
  110. repeatIntervalValue,
  111. repeatIntervalValueType,
  112. routes: formRoutes,
  113. muteTimeIntervals: route.mute_time_intervals ?? [],
  114. },
  115. id2route,
  116. ];
  117. };
  118. export const formAmRouteToAmRoute = (
  119. alertManagerSourceName: string | undefined,
  120. formAmRoute: FormAmRoute,
  121. id2ExistingRoute: Record<string, Route>
  122. ): Route => {
  123. const existing: Route | undefined = id2ExistingRoute[formAmRoute.id];
  124. const {
  125. overrideGrouping,
  126. groupBy,
  127. overrideTimings,
  128. groupWaitValue,
  129. groupWaitValueType,
  130. groupIntervalValue,
  131. groupIntervalValueType,
  132. repeatIntervalValue,
  133. repeatIntervalValueType,
  134. } = formAmRoute;
  135. const group_by = overrideGrouping && groupBy ? groupBy : [];
  136. const overrideGroupWait = overrideTimings && groupWaitValue;
  137. const group_wait = overrideGroupWait ? `${groupWaitValue}${groupWaitValueType}` : undefined;
  138. const overrideGroupInterval = overrideTimings && groupIntervalValue;
  139. const group_interval = overrideGroupInterval ? `${groupIntervalValue}${groupIntervalValueType}` : undefined;
  140. const overrideRepeatInterval = overrideTimings && repeatIntervalValue;
  141. const repeat_interval = overrideRepeatInterval ? `${repeatIntervalValue}${repeatIntervalValueType}` : undefined;
  142. const amRoute: Route = {
  143. ...(existing ?? {}),
  144. continue: formAmRoute.continue,
  145. group_by: group_by,
  146. object_matchers: formAmRoute.object_matchers.length
  147. ? formAmRoute.object_matchers.map((matcher) => [matcher.name, matcher.operator, matcher.value])
  148. : undefined,
  149. match: undefined, // DEPRECATED: Use matchers
  150. match_re: undefined, // DEPRECATED: Use matchers
  151. group_wait,
  152. group_interval,
  153. repeat_interval,
  154. routes: formAmRoute.routes.map((subRoute) =>
  155. formAmRouteToAmRoute(alertManagerSourceName, subRoute, id2ExistingRoute)
  156. ),
  157. mute_time_intervals: formAmRoute.muteTimeIntervals,
  158. };
  159. if (alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME) {
  160. amRoute.matchers = formAmRoute.object_matchers.map(({ name, operator, value }) => `${name}${operator}${value}`);
  161. amRoute.object_matchers = undefined;
  162. } else {
  163. amRoute.matchers = undefined;
  164. }
  165. if (formAmRoute.receiver) {
  166. amRoute.receiver = formAmRoute.receiver;
  167. }
  168. return omitBy(amRoute, isUndefined);
  169. };
  170. export const stringToSelectableValue = (str: string): SelectableValue<string> => ({
  171. label: str,
  172. value: str,
  173. });
  174. export const stringsToSelectableValues = (arr: string[] | undefined): Array<SelectableValue<string>> =>
  175. (arr ?? []).map(stringToSelectableValue);
  176. export const mapSelectValueToString = (selectableValue: SelectableValue<string>): string => {
  177. if (!selectableValue) {
  178. return '';
  179. }
  180. return selectableValueToString(selectableValue) ?? '';
  181. };
  182. export const mapMultiSelectValueToStrings = (
  183. selectableValues: Array<SelectableValue<string>> | undefined
  184. ): string[] => {
  185. if (!selectableValues) {
  186. return [];
  187. }
  188. return selectableValuesToStrings(selectableValues);
  189. };
  190. export const optionalPositiveInteger: Validate<string> = (value) => {
  191. if (!value) {
  192. return undefined;
  193. }
  194. return !/^\d+$/.test(value) ? 'Must be a positive integer.' : undefined;
  195. };