2023-10-12 15:04:59 +08:00
|
|
|
import { omit } from 'lodash';
|
|
|
|
|
|
2021-08-10 15:59:48 +08:00
|
|
|
import {
|
|
|
|
|
DataQuery,
|
2023-06-19 19:32:17 +08:00
|
|
|
DataSourceInstanceSettings,
|
2022-04-20 00:43:33 +08:00
|
|
|
DataSourceRef,
|
|
|
|
|
getDefaultRelativeTimeRange,
|
|
|
|
|
IntervalValues,
|
2021-08-10 15:59:48 +08:00
|
|
|
rangeUtil,
|
|
|
|
|
RelativeTimeRange,
|
|
|
|
|
ScopedVars,
|
|
|
|
|
TimeRange,
|
|
|
|
|
} from '@grafana/data';
|
2021-05-04 22:31:25 +08:00
|
|
|
import { getDataSourceSrv } from '@grafana/runtime';
|
2022-04-20 00:43:33 +08:00
|
|
|
import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend';
|
2023-03-22 20:21:15 +08:00
|
|
|
import { DataSourceJsonData } from '@grafana/schema';
|
2021-05-17 15:39:42 +08:00
|
|
|
import { getNextRefIdChar } from 'app/core/utils/query';
|
|
|
|
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
2023-06-19 19:32:17 +08:00
|
|
|
import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
|
2023-03-30 02:42:47 +08:00
|
|
|
import { LokiQuery } from 'app/plugins/datasource/loki/types';
|
2022-10-27 23:22:23 +08:00
|
|
|
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
|
2021-04-16 16:08:26 +08:00
|
|
|
import { RuleWithLocation } from 'app/types/unified-alerting';
|
|
|
|
|
import {
|
2022-10-27 23:22:23 +08:00
|
|
|
AlertDataQuery,
|
2022-04-20 00:43:33 +08:00
|
|
|
AlertQuery,
|
2021-04-16 16:08:26 +08:00
|
|
|
Annotations,
|
2021-05-06 16:21:58 +08:00
|
|
|
GrafanaAlertStateDecision,
|
2021-04-16 16:08:26 +08:00
|
|
|
Labels,
|
|
|
|
|
PostableRuleGrafanaRuleDTO,
|
2022-09-08 18:52:36 +08:00
|
|
|
RulerAlertingRuleDTO,
|
|
|
|
|
RulerRecordingRuleDTO,
|
2021-08-24 16:31:56 +08:00
|
|
|
RulerRuleDTO,
|
2021-04-16 16:08:26 +08:00
|
|
|
} from 'app/types/unified-alerting-dto';
|
2022-04-22 21:33:13 +08:00
|
|
|
|
2021-05-04 22:31:25 +08:00
|
|
|
import { EvalFunction } from '../../state/alertDef';
|
2021-04-16 16:08:26 +08:00
|
|
|
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
2022-04-22 21:33:13 +08:00
|
|
|
|
2022-04-20 00:43:33 +08:00
|
|
|
import { getRulesAccess } from './access-control';
|
2023-06-21 22:15:12 +08:00
|
|
|
import { Annotation, defaultAnnotations } from './constants';
|
2022-06-28 16:05:53 +08:00
|
|
|
import { getDefaultOrFirstCompatibleDataSource, isGrafanaRulesSource } from './datasource';
|
2021-04-16 16:08:26 +08:00
|
|
|
import { arrayToRecord, recordToArray } from './misc';
|
2021-08-24 16:31:56 +08:00
|
|
|
import { isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from './rules';
|
2021-05-04 21:57:11 +08:00
|
|
|
import { parseInterval } from './time';
|
2021-04-16 16:08:26 +08:00
|
|
|
|
2023-03-30 02:42:47 +08:00
|
|
|
export type PromOrLokiQuery = PromQuery | LokiQuery;
|
|
|
|
|
|
2023-06-19 19:32:17 +08:00
|
|
|
export const MINUTE = '1m';
|
|
|
|
|
|
2022-04-20 00:43:33 +08:00
|
|
|
export const getDefaultFormValues = (): RuleFormValues => {
|
|
|
|
|
const { canCreateGrafanaRules, canCreateCloudRules } = getRulesAccess();
|
|
|
|
|
|
|
|
|
|
return Object.freeze({
|
2021-05-17 16:15:17 +08:00
|
|
|
name: '',
|
2023-06-19 19:32:17 +08:00
|
|
|
uid: '',
|
2021-05-17 16:15:17 +08:00
|
|
|
labels: [{ key: '', value: '' }],
|
2023-06-21 22:15:12 +08:00
|
|
|
annotations: defaultAnnotations,
|
2021-05-17 16:15:17 +08:00
|
|
|
dataSourceName: null,
|
2022-04-20 00:43:33 +08:00
|
|
|
type: canCreateGrafanaRules ? RuleFormType.grafana : canCreateCloudRules ? RuleFormType.cloudAlerting : undefined, // viewers can't create prom alerts
|
2022-04-20 17:41:33 +08:00
|
|
|
group: '',
|
2021-04-16 16:08:26 +08:00
|
|
|
|
2021-05-19 00:14:57 +08:00
|
|
|
// grafana
|
2021-05-17 16:15:17 +08:00
|
|
|
folder: null,
|
|
|
|
|
queries: [],
|
2023-03-22 20:21:15 +08:00
|
|
|
recordingRulesQueries: [],
|
2021-05-17 16:15:17 +08:00
|
|
|
condition: '',
|
|
|
|
|
noDataState: GrafanaAlertStateDecision.NoData,
|
2022-09-19 17:42:04 +08:00
|
|
|
execErrState: GrafanaAlertStateDecision.Error,
|
2021-05-17 16:15:17 +08:00
|
|
|
evaluateFor: '5m',
|
2022-12-15 15:28:47 +08:00
|
|
|
evaluateEvery: MINUTE,
|
2021-04-16 16:08:26 +08:00
|
|
|
|
2021-05-19 00:14:57 +08:00
|
|
|
// cortex / loki
|
2021-05-17 16:15:17 +08:00
|
|
|
namespace: '',
|
|
|
|
|
expression: '',
|
|
|
|
|
forTime: 1,
|
|
|
|
|
forTimeUnit: 'm',
|
|
|
|
|
});
|
2022-04-20 00:43:33 +08:00
|
|
|
};
|
2021-04-14 20:57:36 +08:00
|
|
|
|
2021-08-24 16:31:56 +08:00
|
|
|
export function formValuesToRulerRuleDTO(values: RuleFormValues): RulerRuleDTO {
|
2023-09-22 04:02:53 +08:00
|
|
|
const { name, expression, forTime, forTimeUnit, keepFiringForTime, keepFiringForTimeUnit, type } = values;
|
2021-08-24 16:31:56 +08:00
|
|
|
if (type === RuleFormType.cloudAlerting) {
|
2023-09-22 04:02:53 +08:00
|
|
|
let keepFiringFor: string | undefined;
|
|
|
|
|
if (keepFiringForTime && keepFiringForTimeUnit) {
|
|
|
|
|
keepFiringFor = `${keepFiringForTime}${keepFiringForTimeUnit}`;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-24 16:31:56 +08:00
|
|
|
return {
|
|
|
|
|
alert: name,
|
|
|
|
|
for: `${forTime}${forTimeUnit}`,
|
2023-09-22 04:02:53 +08:00
|
|
|
keep_firing_for: keepFiringFor,
|
2021-08-24 16:31:56 +08:00
|
|
|
annotations: arrayToRecord(values.annotations || []),
|
|
|
|
|
labels: arrayToRecord(values.labels || []),
|
|
|
|
|
expr: expression,
|
|
|
|
|
};
|
|
|
|
|
} else if (type === RuleFormType.cloudRecording) {
|
|
|
|
|
return {
|
|
|
|
|
record: name,
|
|
|
|
|
labels: arrayToRecord(values.labels || []),
|
|
|
|
|
expr: expression,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`unexpected rule type: ${type}`);
|
2021-04-14 20:57:36 +08:00
|
|
|
}
|
|
|
|
|
|
2023-07-26 04:34:14 +08:00
|
|
|
export function listifyLabelsOrAnnotations(
|
2023-06-21 22:15:12 +08:00
|
|
|
item: Labels | Annotations | undefined,
|
|
|
|
|
addEmpty: boolean
|
|
|
|
|
): Array<{ key: string; value: string }> {
|
|
|
|
|
const list = [...recordToArray(item || {})];
|
|
|
|
|
if (addEmpty) {
|
|
|
|
|
list.push({ key: '', value: '' });
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//make sure default annotations are always shown in order even if empty
|
2023-07-26 04:34:14 +08:00
|
|
|
export function normalizeDefaultAnnotations(annotations: Array<{ key: string; value: string }>) {
|
2023-06-21 22:15:12 +08:00
|
|
|
const orderedAnnotations = [...annotations];
|
|
|
|
|
const defaultAnnotationKeys = defaultAnnotations.map((annotation) => annotation.key);
|
|
|
|
|
|
|
|
|
|
defaultAnnotationKeys.forEach((defaultAnnotationKey, index) => {
|
|
|
|
|
const fieldIndex = orderedAnnotations.findIndex((field) => field.key === defaultAnnotationKey);
|
|
|
|
|
|
|
|
|
|
if (fieldIndex === -1) {
|
|
|
|
|
//add the default annotation if abstent
|
|
|
|
|
const emptyValue = { key: defaultAnnotationKey, value: '' };
|
|
|
|
|
orderedAnnotations.splice(index, 0, emptyValue);
|
|
|
|
|
} else if (fieldIndex !== index) {
|
|
|
|
|
//move it to the correct position if present
|
|
|
|
|
orderedAnnotations.splice(index, 0, orderedAnnotations.splice(fieldIndex, 1)[0]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return orderedAnnotations;
|
2021-04-16 16:08:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): PostableRuleGrafanaRuleDTO {
|
2023-02-01 20:15:03 +08:00
|
|
|
const { name, condition, noDataState, execErrState, evaluateFor, queries, isPaused } = values;
|
2021-04-14 20:57:36 +08:00
|
|
|
if (condition) {
|
|
|
|
|
return {
|
|
|
|
|
grafana_alert: {
|
|
|
|
|
title: name,
|
|
|
|
|
condition,
|
|
|
|
|
no_data_state: noDataState,
|
|
|
|
|
exec_err_state: execErrState,
|
2022-10-27 23:22:23 +08:00
|
|
|
data: queries.map(fixBothInstantAndRangeQuery),
|
2023-02-01 20:15:03 +08:00
|
|
|
is_paused: Boolean(isPaused),
|
2021-04-14 20:57:36 +08:00
|
|
|
},
|
2021-04-19 17:53:02 +08:00
|
|
|
for: evaluateFor,
|
|
|
|
|
annotations: arrayToRecord(values.annotations || []),
|
|
|
|
|
labels: arrayToRecord(values.labels || []),
|
2021-04-14 20:57:36 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
throw new Error('Cannot create rule without specifying alert condition');
|
|
|
|
|
}
|
2021-04-16 16:08:26 +08:00
|
|
|
|
|
|
|
|
export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleFormValues {
|
|
|
|
|
const { ruleSourceName, namespace, group, rule } = ruleWithLocation;
|
2021-05-17 16:15:17 +08:00
|
|
|
|
|
|
|
|
const defaultFormValues = getDefaultFormValues();
|
2021-04-16 16:08:26 +08:00
|
|
|
if (isGrafanaRulesSource(ruleSourceName)) {
|
|
|
|
|
if (isGrafanaRulerRule(rule)) {
|
|
|
|
|
const ga = rule.grafana_alert;
|
2023-06-16 19:14:46 +08:00
|
|
|
|
2021-04-16 16:08:26 +08:00
|
|
|
return {
|
|
|
|
|
...defaultFormValues,
|
|
|
|
|
name: ga.title,
|
2021-05-19 00:14:57 +08:00
|
|
|
type: RuleFormType.grafana,
|
2022-04-20 17:41:33 +08:00
|
|
|
group: group.name,
|
2022-12-15 15:28:47 +08:00
|
|
|
evaluateEvery: group.interval || defaultFormValues.evaluateEvery,
|
2021-06-21 20:54:42 +08:00
|
|
|
evaluateFor: rule.for || '0',
|
2021-04-16 16:08:26 +08:00
|
|
|
noDataState: ga.no_data_state,
|
|
|
|
|
execErrState: ga.exec_err_state,
|
|
|
|
|
queries: ga.data,
|
|
|
|
|
condition: ga.condition,
|
2023-06-21 22:15:12 +08:00
|
|
|
annotations: normalizeDefaultAnnotations(listifyLabelsOrAnnotations(rule.annotations, false)),
|
|
|
|
|
labels: listifyLabelsOrAnnotations(rule.labels, true),
|
2023-06-19 19:32:17 +08:00
|
|
|
folder: { title: namespace, uid: ga.namespace_uid },
|
2023-02-01 20:15:03 +08:00
|
|
|
isPaused: ga.is_paused,
|
2021-04-16 16:08:26 +08:00
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error('Unexpected type of rule for grafana rules source');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (isAlertingRulerRule(rule)) {
|
2023-08-04 22:46:58 +08:00
|
|
|
const datasourceUid = getDataSourceSrv().getInstanceSettings(ruleSourceName)?.uid ?? '';
|
|
|
|
|
|
|
|
|
|
const defaultQuery = {
|
|
|
|
|
refId: 'A',
|
|
|
|
|
datasourceUid,
|
|
|
|
|
queryType: '',
|
|
|
|
|
relativeTimeRange: getDefaultRelativeTimeRange(),
|
|
|
|
|
expr: rule.expr,
|
|
|
|
|
model: {
|
|
|
|
|
refId: 'A',
|
|
|
|
|
hide: false,
|
|
|
|
|
expr: rule.expr,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-08 18:52:36 +08:00
|
|
|
const alertingRuleValues = alertingRulerRuleToRuleForm(rule);
|
|
|
|
|
|
2021-04-16 16:08:26 +08:00
|
|
|
return {
|
|
|
|
|
...defaultFormValues,
|
2022-09-08 18:52:36 +08:00
|
|
|
...alertingRuleValues,
|
2023-08-04 22:46:58 +08:00
|
|
|
queries: [defaultQuery],
|
2023-07-26 04:34:14 +08:00
|
|
|
annotations: normalizeDefaultAnnotations(listifyLabelsOrAnnotations(rule.annotations, false)),
|
2021-08-24 16:31:56 +08:00
|
|
|
type: RuleFormType.cloudAlerting,
|
2021-04-16 16:08:26 +08:00
|
|
|
dataSourceName: ruleSourceName,
|
2021-04-16 19:57:33 +08:00
|
|
|
namespace,
|
|
|
|
|
group: group.name,
|
2021-04-16 16:08:26 +08:00
|
|
|
};
|
2021-08-24 16:31:56 +08:00
|
|
|
} else if (isRecordingRulerRule(rule)) {
|
2022-09-08 18:52:36 +08:00
|
|
|
const recordingRuleValues = recordingRulerRuleToRuleForm(rule);
|
|
|
|
|
|
2021-08-24 16:31:56 +08:00
|
|
|
return {
|
|
|
|
|
...defaultFormValues,
|
2022-09-08 18:52:36 +08:00
|
|
|
...recordingRuleValues,
|
2021-08-24 16:31:56 +08:00
|
|
|
type: RuleFormType.cloudRecording,
|
|
|
|
|
dataSourceName: ruleSourceName,
|
|
|
|
|
namespace,
|
|
|
|
|
group: group.name,
|
|
|
|
|
};
|
2021-04-16 16:08:26 +08:00
|
|
|
} else {
|
2021-08-24 16:31:56 +08:00
|
|
|
throw new Error('Unexpected type of rule for cloud rules source');
|
2021-04-16 16:08:26 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-04 22:31:25 +08:00
|
|
|
|
2022-09-08 18:52:36 +08:00
|
|
|
export function alertingRulerRuleToRuleForm(
|
|
|
|
|
rule: RulerAlertingRuleDTO
|
2023-09-22 04:02:53 +08:00
|
|
|
): Pick<
|
|
|
|
|
RuleFormValues,
|
|
|
|
|
| 'name'
|
|
|
|
|
| 'forTime'
|
|
|
|
|
| 'forTimeUnit'
|
|
|
|
|
| 'keepFiringForTime'
|
|
|
|
|
| 'keepFiringForTimeUnit'
|
|
|
|
|
| 'expression'
|
|
|
|
|
| 'annotations'
|
|
|
|
|
| 'labels'
|
|
|
|
|
> {
|
2022-09-08 18:52:36 +08:00
|
|
|
const defaultFormValues = getDefaultFormValues();
|
|
|
|
|
|
|
|
|
|
const [forTime, forTimeUnit] = rule.for
|
|
|
|
|
? parseInterval(rule.for)
|
|
|
|
|
: [defaultFormValues.forTime, defaultFormValues.forTimeUnit];
|
|
|
|
|
|
2023-09-22 04:02:53 +08:00
|
|
|
const [keepFiringForTime, keepFiringForTimeUnit] = rule.keep_firing_for
|
|
|
|
|
? parseInterval(rule.keep_firing_for)
|
|
|
|
|
: [defaultFormValues.keepFiringForTime, defaultFormValues.keepFiringForTimeUnit];
|
|
|
|
|
|
2022-09-08 18:52:36 +08:00
|
|
|
return {
|
|
|
|
|
name: rule.alert,
|
|
|
|
|
expression: rule.expr,
|
|
|
|
|
forTime,
|
|
|
|
|
forTimeUnit,
|
2023-09-22 04:02:53 +08:00
|
|
|
keepFiringForTime,
|
|
|
|
|
keepFiringForTimeUnit,
|
2023-06-21 22:15:12 +08:00
|
|
|
annotations: listifyLabelsOrAnnotations(rule.annotations, false),
|
|
|
|
|
labels: listifyLabelsOrAnnotations(rule.labels, true),
|
2022-09-08 18:52:36 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function recordingRulerRuleToRuleForm(
|
|
|
|
|
rule: RulerRecordingRuleDTO
|
|
|
|
|
): Pick<RuleFormValues, 'name' | 'expression' | 'labels'> {
|
|
|
|
|
return {
|
|
|
|
|
name: rule.record,
|
|
|
|
|
expression: rule.expr,
|
2023-06-21 22:15:12 +08:00
|
|
|
labels: listifyLabelsOrAnnotations(rule.labels, true),
|
2022-09-08 18:52:36 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-06 23:31:03 +08:00
|
|
|
export const getDefaultQueries = (): AlertQuery[] => {
|
2022-06-28 16:05:53 +08:00
|
|
|
const dataSource = getDefaultOrFirstCompatibleDataSource();
|
2021-05-04 22:31:25 +08:00
|
|
|
|
|
|
|
|
if (!dataSource) {
|
2022-10-05 20:35:15 +08:00
|
|
|
return [...getDefaultExpressions('A', 'B')];
|
2021-05-04 22:31:25 +08:00
|
|
|
}
|
2021-05-19 15:42:40 +08:00
|
|
|
const relativeTimeRange = getDefaultRelativeTimeRange();
|
2021-05-04 22:31:25 +08:00
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
refId: 'A',
|
|
|
|
|
datasourceUid: dataSource.uid,
|
|
|
|
|
queryType: '',
|
|
|
|
|
relativeTimeRange,
|
|
|
|
|
model: {
|
|
|
|
|
refId: 'A',
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-06 23:31:03 +08:00
|
|
|
...getDefaultExpressions('B', 'C'),
|
2021-05-04 22:31:25 +08:00
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-22 20:21:15 +08:00
|
|
|
export const getDefaultRecordingRulesQueries = (
|
|
|
|
|
rulesSourcesWithRuler: Array<DataSourceInstanceSettings<DataSourceJsonData>>
|
|
|
|
|
): AlertQuery[] => {
|
|
|
|
|
const relativeTimeRange = getDefaultRelativeTimeRange();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
refId: 'A',
|
|
|
|
|
datasourceUid: rulesSourcesWithRuler[0]?.uid || '',
|
|
|
|
|
queryType: '',
|
|
|
|
|
relativeTimeRange,
|
|
|
|
|
model: {
|
|
|
|
|
refId: 'A',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-05 20:35:15 +08:00
|
|
|
const getDefaultExpressions = (...refIds: [string, string]): AlertQuery[] => {
|
|
|
|
|
const refOne = refIds[0];
|
|
|
|
|
const refTwo = refIds[1];
|
|
|
|
|
|
|
|
|
|
const reduceExpression: ExpressionQuery = {
|
|
|
|
|
refId: refIds[0],
|
|
|
|
|
type: ExpressionQueryType.reduce,
|
2021-10-30 01:57:24 +08:00
|
|
|
datasource: {
|
|
|
|
|
uid: ExpressionDatasourceUID,
|
2021-12-21 22:11:11 +08:00
|
|
|
type: ExpressionDatasourceRef.type,
|
2021-10-30 01:57:24 +08:00
|
|
|
},
|
2021-05-04 22:31:25 +08:00
|
|
|
conditions: [
|
|
|
|
|
{
|
|
|
|
|
type: 'query',
|
|
|
|
|
evaluator: {
|
2022-10-05 20:35:15 +08:00
|
|
|
params: [],
|
2021-05-04 22:31:25 +08:00
|
|
|
type: EvalFunction.IsAbove,
|
|
|
|
|
},
|
|
|
|
|
operator: {
|
|
|
|
|
type: 'and',
|
|
|
|
|
},
|
|
|
|
|
query: {
|
2022-10-05 20:35:15 +08:00
|
|
|
params: [refOne],
|
2021-05-04 22:31:25 +08:00
|
|
|
},
|
|
|
|
|
reducer: {
|
|
|
|
|
params: [],
|
|
|
|
|
type: 'last',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
2022-10-05 20:35:15 +08:00
|
|
|
reducer: 'last',
|
2022-07-12 17:50:58 +08:00
|
|
|
expression: 'A',
|
2021-05-04 22:31:25 +08:00
|
|
|
};
|
|
|
|
|
|
2022-10-05 20:35:15 +08:00
|
|
|
const thresholdExpression: ExpressionQuery = {
|
|
|
|
|
refId: refTwo,
|
|
|
|
|
type: ExpressionQueryType.threshold,
|
|
|
|
|
datasource: {
|
|
|
|
|
uid: ExpressionDatasourceUID,
|
|
|
|
|
type: ExpressionDatasourceRef.type,
|
|
|
|
|
},
|
|
|
|
|
conditions: [
|
|
|
|
|
{
|
|
|
|
|
type: 'query',
|
|
|
|
|
evaluator: {
|
|
|
|
|
params: [0],
|
|
|
|
|
type: EvalFunction.IsAbove,
|
|
|
|
|
},
|
|
|
|
|
operator: {
|
|
|
|
|
type: 'and',
|
|
|
|
|
},
|
|
|
|
|
query: {
|
|
|
|
|
params: [refTwo],
|
|
|
|
|
},
|
|
|
|
|
reducer: {
|
|
|
|
|
params: [],
|
|
|
|
|
type: 'last',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
expression: refOne,
|
2021-05-04 22:31:25 +08:00
|
|
|
};
|
2022-10-05 20:35:15 +08:00
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
refId: refOne,
|
|
|
|
|
datasourceUid: ExpressionDatasourceUID,
|
|
|
|
|
queryType: '',
|
|
|
|
|
model: reduceExpression,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
refId: refTwo,
|
|
|
|
|
datasourceUid: ExpressionDatasourceUID,
|
|
|
|
|
queryType: '',
|
|
|
|
|
model: thresholdExpression,
|
|
|
|
|
},
|
|
|
|
|
];
|
2021-05-04 22:31:25 +08:00
|
|
|
};
|
2021-05-17 15:39:42 +08:00
|
|
|
|
2021-08-10 15:59:48 +08:00
|
|
|
const dataQueriesToGrafanaQueries = async (
|
2021-05-17 15:39:42 +08:00
|
|
|
queries: DataQuery[],
|
|
|
|
|
relativeTimeRange: RelativeTimeRange,
|
2021-08-10 15:59:48 +08:00
|
|
|
scopedVars: ScopedVars | {},
|
2021-10-30 01:57:24 +08:00
|
|
|
panelDataSourceRef?: DataSourceRef,
|
2021-08-10 15:59:48 +08:00
|
|
|
maxDataPoints?: number,
|
|
|
|
|
minInterval?: string
|
|
|
|
|
): Promise<AlertQuery[]> => {
|
|
|
|
|
const result: AlertQuery[] = [];
|
2021-10-30 01:57:24 +08:00
|
|
|
|
2021-08-10 15:59:48 +08:00
|
|
|
for (const target of queries) {
|
2021-10-30 01:57:24 +08:00
|
|
|
const datasource = await getDataSourceSrv().get(target.datasource?.uid ? target.datasource : panelDataSourceRef);
|
|
|
|
|
const dsRef = { uid: datasource.uid, type: datasource.type };
|
2021-08-10 15:59:48 +08:00
|
|
|
|
|
|
|
|
const range = rangeUtil.relativeToTimeRange(relativeTimeRange);
|
|
|
|
|
const { interval, intervalMs } = getIntervals(range, minInterval ?? datasource.interval, maxDataPoints);
|
|
|
|
|
const queryVariables = {
|
|
|
|
|
__interval: { text: interval, value: interval },
|
|
|
|
|
__interval_ms: { text: intervalMs, value: intervalMs },
|
|
|
|
|
...scopedVars,
|
|
|
|
|
};
|
2021-10-30 01:57:24 +08:00
|
|
|
|
2021-08-10 15:59:48 +08:00
|
|
|
const interpolatedTarget = datasource.interpolateVariablesInQueries
|
|
|
|
|
? await datasource.interpolateVariablesInQueries([target], queryVariables)[0]
|
|
|
|
|
: target;
|
2021-10-30 01:57:24 +08:00
|
|
|
|
|
|
|
|
// expressions
|
|
|
|
|
if (dsRef.uid === ExpressionDatasourceUID) {
|
|
|
|
|
const newQuery: AlertQuery = {
|
|
|
|
|
refId: interpolatedTarget.refId,
|
|
|
|
|
queryType: '',
|
|
|
|
|
relativeTimeRange,
|
|
|
|
|
datasourceUid: ExpressionDatasourceUID,
|
|
|
|
|
model: interpolatedTarget,
|
|
|
|
|
};
|
|
|
|
|
result.push(newQuery);
|
|
|
|
|
// queries
|
|
|
|
|
} else {
|
|
|
|
|
const datasourceSettings = getDataSourceSrv().getInstanceSettings(dsRef);
|
|
|
|
|
if (datasourceSettings && datasourceSettings.meta.alerting) {
|
2021-05-27 18:29:10 +08:00
|
|
|
const newQuery: AlertQuery = {
|
2021-08-10 15:59:48 +08:00
|
|
|
refId: interpolatedTarget.refId,
|
2021-10-30 01:57:24 +08:00
|
|
|
queryType: interpolatedTarget.queryType ?? '',
|
2021-05-17 15:39:42 +08:00
|
|
|
relativeTimeRange,
|
2021-10-30 01:57:24 +08:00
|
|
|
datasourceUid: datasourceSettings.uid,
|
|
|
|
|
model: {
|
|
|
|
|
...interpolatedTarget,
|
|
|
|
|
maxDataPoints,
|
|
|
|
|
intervalMs,
|
|
|
|
|
},
|
2021-05-17 15:39:42 +08:00
|
|
|
};
|
2021-08-10 15:59:48 +08:00
|
|
|
result.push(newQuery);
|
2021-05-17 15:39:42 +08:00
|
|
|
}
|
|
|
|
|
}
|
2021-08-10 15:59:48 +08:00
|
|
|
}
|
|
|
|
|
return result;
|
2021-05-17 15:39:42 +08:00
|
|
|
};
|
|
|
|
|
|
2021-08-10 15:59:48 +08:00
|
|
|
export const panelToRuleFormValues = async (
|
2021-05-17 15:39:42 +08:00
|
|
|
panel: PanelModel,
|
|
|
|
|
dashboard: DashboardModel
|
2021-08-10 15:59:48 +08:00
|
|
|
): Promise<Partial<RuleFormValues> | undefined> => {
|
2021-05-17 15:39:42 +08:00
|
|
|
const { targets } = panel;
|
2021-10-13 14:53:36 +08:00
|
|
|
if (!panel.id || !dashboard.uid) {
|
2021-05-17 15:39:42 +08:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const relativeTimeRange = rangeUtil.timeRangeToRelative(rangeUtil.convertRawToRange(dashboard.time));
|
2021-08-10 15:59:48 +08:00
|
|
|
const queries = await dataQueriesToGrafanaQueries(
|
|
|
|
|
targets,
|
|
|
|
|
relativeTimeRange,
|
|
|
|
|
panel.scopedVars || {},
|
|
|
|
|
panel.datasource ?? undefined,
|
|
|
|
|
panel.maxDataPoints ?? undefined,
|
|
|
|
|
panel.interval ?? undefined
|
|
|
|
|
);
|
2021-05-17 15:39:42 +08:00
|
|
|
// if no alerting capable queries are found, can't create a rule
|
|
|
|
|
if (!queries.length || !queries.find((query) => query.datasourceUid !== ExpressionDatasourceUID)) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!queries.find((query) => query.datasourceUid === ExpressionDatasourceUID)) {
|
2022-10-05 20:35:15 +08:00
|
|
|
const [reduceExpression, _thresholdExpression] = getDefaultExpressions(getNextRefIdChar(queries), '-');
|
|
|
|
|
queries.push(reduceExpression);
|
|
|
|
|
|
|
|
|
|
const [_reduceExpression, thresholdExpression] = getDefaultExpressions(
|
|
|
|
|
reduceExpression.refId,
|
|
|
|
|
getNextRefIdChar(queries)
|
|
|
|
|
);
|
|
|
|
|
queries.push(thresholdExpression);
|
2021-05-17 15:39:42 +08:00
|
|
|
}
|
|
|
|
|
|
2023-06-19 19:32:17 +08:00
|
|
|
const { folderTitle, folderUid } = dashboard.meta;
|
2021-05-17 15:39:42 +08:00
|
|
|
|
|
|
|
|
const formValues = {
|
2021-05-19 00:14:57 +08:00
|
|
|
type: RuleFormType.grafana,
|
2021-05-17 15:39:42 +08:00
|
|
|
folder:
|
2023-06-19 19:32:17 +08:00
|
|
|
folderUid && folderTitle
|
2021-05-17 15:39:42 +08:00
|
|
|
? {
|
2023-06-19 19:32:17 +08:00
|
|
|
uid: folderUid,
|
2021-05-17 15:39:42 +08:00
|
|
|
title: folderTitle,
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
queries,
|
|
|
|
|
name: panel.title,
|
2021-08-10 15:59:48 +08:00
|
|
|
condition: queries[queries.length - 1].refId,
|
2021-05-17 15:39:42 +08:00
|
|
|
annotations: [
|
|
|
|
|
{
|
|
|
|
|
key: Annotation.dashboardUID,
|
|
|
|
|
value: dashboard.uid,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: Annotation.panelID,
|
2021-10-13 14:53:36 +08:00
|
|
|
value: String(panel.id),
|
2021-05-17 15:39:42 +08:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
return formValues;
|
|
|
|
|
};
|
2021-08-10 15:59:48 +08:00
|
|
|
|
|
|
|
|
export function getIntervals(range: TimeRange, lowLimit?: string, resolution?: number): IntervalValues {
|
|
|
|
|
if (!resolution) {
|
|
|
|
|
if (lowLimit && rangeUtil.intervalToMs(lowLimit) > 1000) {
|
|
|
|
|
return {
|
|
|
|
|
interval: lowLimit,
|
|
|
|
|
intervalMs: rangeUtil.intervalToMs(lowLimit),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { interval: '1s', intervalMs: 1000 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rangeUtil.calculateInterval(range, resolution, lowLimit);
|
|
|
|
|
}
|
2022-10-27 23:22:23 +08:00
|
|
|
|
|
|
|
|
export function fixBothInstantAndRangeQuery(query: AlertQuery) {
|
|
|
|
|
const model = query.model;
|
|
|
|
|
|
|
|
|
|
if (!isPromQuery(model)) {
|
|
|
|
|
return query;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isBothInstantAndRange = model.instant && model.range;
|
|
|
|
|
if (isBothInstantAndRange) {
|
|
|
|
|
return { ...query, model: { ...model, range: true, instant: false } };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return query;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isPromQuery(model: AlertDataQuery): model is PromQuery {
|
|
|
|
|
return 'expr' in model && 'instant' in model && 'range' in model;
|
|
|
|
|
}
|
2023-03-30 02:42:47 +08:00
|
|
|
|
|
|
|
|
export function isPromOrLokiQuery(model: AlertDataQuery): model is PromOrLokiQuery {
|
|
|
|
|
return 'expr' in model;
|
|
|
|
|
}
|
2023-10-12 15:04:59 +08:00
|
|
|
|
|
|
|
|
// the backend will always execute "hidden" queries, so we have no choice but to remove the property in the front-end
|
|
|
|
|
// to avoid confusion. The query editor shows them as "disabled" and that's a different semantic meaning.
|
|
|
|
|
// furthermore the "AlertingQueryRunner" calls `filterQuery` on each data source and those will skip running queries that are "hidden"."
|
|
|
|
|
// It seems like we have no choice but to act like "hidden" queries don't exist in alerting.
|
|
|
|
|
export const ignoreHiddenQueries = (ruleDefinition: RuleFormValues): RuleFormValues => {
|
|
|
|
|
return {
|
|
|
|
|
...ruleDefinition,
|
|
|
|
|
queries: ruleDefinition.queries?.map((query) => omit(query, 'model.hide')),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function formValuesFromExistingRule(rule: RuleWithLocation<RulerRuleDTO>) {
|
|
|
|
|
return ignoreHiddenQueries(rulerRuleToFormValues(rule));
|
|
|
|
|
}
|