1import { isArray, reduce } from 'lodash';
2import { QueryPartDef, QueryPart } from 'app/angular/components/query_part';
3
4const alertQueryDef = new QueryPartDef({
5  type: 'query',
6  params: [
7    { name: 'queryRefId', type: 'string', dynamicLookup: true },
8    {
9      name: 'from',
10      type: 'string',
11      options: ['10s', '1m', '5m', '10m', '15m', '1h', '2h', '6h', '12h', '24h', '48h'],
12    },
13    { name: 'to', type: 'string', options: ['now', 'now-1m', 'now-5m', 'now-10m', 'now-1h'] },
14  ],
15  defaultParams: ['#A', '15m', 'now', 'avg'],
16});
17
18const conditionTypes = [{ text: 'Query', value: 'query' }];
19
20const alertStateSortScore = {
21  alerting: 1,
22  firing: 1,
23  no_data: 2,
24  pending: 3,
25  ok: 4,
26  paused: 5,
27  inactive: 5,
28};
29
30export enum EvalFunction {
31  'IsAbove' = 'gt',
32  'IsBelow' = 'lt',
33  'IsOutsideRange' = 'outside_range',
34  'IsWithinRange' = 'within_range',
35  'HasNoValue' = 'no_value',
36}
37
38const evalFunctions = [
39  { value: EvalFunction.IsAbove, text: 'IS ABOVE' },
40  { value: EvalFunction.IsBelow, text: 'IS BELOW' },
41  { value: EvalFunction.IsOutsideRange, text: 'IS OUTSIDE RANGE' },
42  { value: EvalFunction.IsWithinRange, text: 'IS WITHIN RANGE' },
43  { value: EvalFunction.HasNoValue, text: 'HAS NO VALUE' },
44];
45
46const evalOperators = [
47  { text: 'OR', value: 'or' },
48  { text: 'AND', value: 'and' },
49];
50
51const reducerTypes = [
52  { text: 'avg()', value: 'avg' },
53  { text: 'min()', value: 'min' },
54  { text: 'max()', value: 'max' },
55  { text: 'sum()', value: 'sum' },
56  { text: 'count()', value: 'count' },
57  { text: 'last()', value: 'last' },
58  { text: 'median()', value: 'median' },
59  { text: 'diff()', value: 'diff' },
60  { text: 'diff_abs()', value: 'diff_abs' },
61  { text: 'percent_diff()', value: 'percent_diff' },
62  { text: 'percent_diff_abs()', value: 'percent_diff_abs' },
63  { text: 'count_non_null()', value: 'count_non_null' },
64];
65
66const noDataModes = [
67  { text: 'Alerting', value: 'alerting' },
68  { text: 'No Data', value: 'no_data' },
69  { text: 'Keep Last State', value: 'keep_state' },
70  { text: 'Ok', value: 'ok' },
71];
72
73const executionErrorModes = [
74  { text: 'Alerting', value: 'alerting' },
75  { text: 'Keep Last State', value: 'keep_state' },
76];
77
78function createReducerPart(model: any) {
79  const def = new QueryPartDef({ type: model.type, defaultParams: [] });
80  return new QueryPart(model, def);
81}
82
83function getStateDisplayModel(state: string) {
84  const normalizedState = state.toLowerCase().replace(/_/g, '');
85
86  switch (normalizedState) {
87    case 'normal':
88    case 'ok': {
89      return {
90        text: 'OK',
91        iconClass: 'heart',
92        stateClass: 'alert-state-ok',
93      };
94    }
95    case 'alerting': {
96      return {
97        text: 'ALERTING',
98        iconClass: 'heart-break',
99        stateClass: 'alert-state-critical',
100      };
101    }
102    case 'nodata': {
103      return {
104        text: 'NO DATA',
105        iconClass: 'question-circle',
106        stateClass: 'alert-state-warning',
107      };
108    }
109    case 'paused': {
110      return {
111        text: 'PAUSED',
112        iconClass: 'pause',
113        stateClass: 'alert-state-paused',
114      };
115    }
116    case 'pending': {
117      return {
118        text: 'PENDING',
119        iconClass: 'hourglass',
120        stateClass: 'alert-state-warning',
121      };
122    }
123    case 'unknown': {
124      return {
125        text: 'UNKNOWN',
126        iconClass: 'question-circle',
127        stateClass: '.alert-state-paused',
128      };
129    }
130
131    case 'firing': {
132      return {
133        text: 'FIRING',
134        iconClass: 'fire',
135        stateClass: '',
136      };
137    }
138
139    case 'inactive': {
140      return {
141        text: 'INACTIVE',
142        iconClass: 'check',
143        stateClass: '',
144      };
145    }
146
147    case 'error': {
148      return {
149        text: 'ERROR',
150        iconClass: 'heart-break',
151        stateClass: 'alert-state-critical',
152      };
153    }
154  }
155
156  throw { message: 'Unknown alert state' };
157}
158
159function joinEvalMatches(matches: any, separator: string) {
160  return reduce(
161    matches,
162    (res, ev) => {
163      if (ev.metric !== undefined && ev.value !== undefined) {
164        res.push(ev.metric + '=' + ev.value);
165      }
166
167      // For backwards compatibility . Should be be able to remove this after ~2017-06-01
168      if (ev.Metric !== undefined && ev.Value !== undefined) {
169        res.push(ev.Metric + '=' + ev.Value);
170      }
171
172      return res;
173    },
174    [] as string[]
175  ).join(separator);
176}
177
178function getAlertAnnotationInfo(ah: any) {
179  // backward compatibility, can be removed in grafana 5.x
180  // old way stored evalMatches in data property directly,
181  // new way stores it in evalMatches property on new data object
182
183  if (isArray(ah.data)) {
184    return joinEvalMatches(ah.data, ', ');
185  } else if (isArray(ah.data.evalMatches)) {
186    return joinEvalMatches(ah.data.evalMatches, ', ');
187  }
188
189  if (ah.data.error) {
190    return 'Error: ' + ah.data.error;
191  }
192
193  return '';
194}
195
196export default {
197  alertQueryDef: alertQueryDef,
198  getStateDisplayModel: getStateDisplayModel,
199  conditionTypes: conditionTypes,
200  evalFunctions: evalFunctions,
201  evalOperators: evalOperators,
202  noDataModes: noDataModes,
203  executionErrorModes: executionErrorModes,
204  reducerTypes: reducerTypes,
205  createReducerPart: createReducerPart,
206  getAlertAnnotationInfo: getAlertAnnotationInfo,
207  alertStateSortScore: alertStateSortScore,
208};
209