Files
Sonia Aguilar 9905d3447c Alerting: Fix RefIds not being shown when creating or editing Grafana-managed recording rule (#106840)
* fix refids when creating grafana-managed recording rule

* fix refids when editing grafana-managed recording rule

* review comments
2025-06-18 06:48:13 +00:00

903 lines
28 KiB
TypeScript

import {
DataSourceInstanceSettings,
IntervalValues,
RelativeTimeRange,
ScopedVars,
TimeRange,
getDefaultRelativeTimeRange,
getNextRefId,
rangeUtil,
} from '@grafana/data';
import { PromQuery } from '@grafana/prometheus';
import { config, getDataSourceSrv } from '@grafana/runtime';
import { ExpressionDatasourceRef } from '@grafana/runtime/internal';
import { VizPanel, sceneGraph } from '@grafana/scenes';
import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import {
getDashboardSceneFor,
getPanelIdForVizPanel,
getQueryRunnerFor,
} from 'app/features/dashboard-scene/utils/utils';
import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
import { LokiQuery } from 'app/plugins/datasource/loki/types';
import { RuleWithLocation } from 'app/types/unified-alerting';
import {
AlertDataQuery,
AlertQuery,
Annotations,
GrafanaNotificationSettings,
GrafanaRuleDefinition,
Labels,
PostableRuleGrafanaRuleDTO,
RulerAlertingRuleDTO,
RulerGrafanaRuleDTO,
RulerRecordingRuleDTO,
RulerRuleDTO,
} from 'app/types/unified-alerting-dto';
import { EvalFunction } from '../../state/alertDef';
import { getDefaultFormValues } from '../rule-editor/formDefaults';
import { normalizeDefaultAnnotations } from '../rule-editor/formProcessing';
import {
AlertManagerManualRouting,
ContactPoint,
KVObject,
RuleFormType,
RuleFormValues,
SimplifiedEditor,
} from '../types/rule-form';
import { Annotation } from './constants';
import {
GRAFANA_RULES_SOURCE_NAME,
getDefaultOrFirstCompatibleDataSource,
isGrafanaRulesSource,
isSupportedExternalPrometheusFlavoredRulesSourceType,
isSupportedExternalRulesSourceType,
} from './datasource';
import { arrayToRecord, recordToArray } from './misc';
import { isGrafanaAlertingRuleByType, isGrafanaRecordingRuleByType, rulerRuleType } from './rules';
import { parseInterval } from './time';
export type PromOrLokiQuery = PromQuery | LokiQuery;
export const MANUAL_ROUTING_KEY = 'grafana.alerting.manualRouting';
export const SIMPLIFIED_QUERY_EDITOR_KEY = 'grafana.alerting.simplifiedQueryEditor';
export function formValuesToRulerRuleDTO(values: RuleFormValues): RulerRuleDTO {
const { name, expression, forTime, forTimeUnit, keepFiringForTime, keepFiringForTimeUnit, type } = values;
const annotations = arrayToRecord(cleanAnnotations(values.annotations));
const labels = arrayToRecord(cleanLabels(values.labels));
if (type === RuleFormType.cloudAlerting) {
let keepFiringFor: string | undefined;
if (keepFiringForTime && keepFiringForTimeUnit) {
keepFiringFor = `${keepFiringForTime}${keepFiringForTimeUnit}`;
}
return {
alert: name,
for: `${forTime}${forTimeUnit}`,
keep_firing_for: keepFiringFor,
annotations,
labels,
expr: expression,
};
} else if (type === RuleFormType.cloudRecording) {
return {
record: name,
labels,
expr: expression,
};
}
throw new Error(`unexpected rule type: ${type}`);
}
export function listifyLabelsOrAnnotations(item: Labels | Annotations | undefined, addEmpty: boolean): KVObject[] {
const list = [...recordToArray(item || {})];
if (addEmpty) {
list.push({ key: '', value: '' });
}
return list;
}
export function getNotificationSettingsForDTO(
manualRouting: boolean,
contactPoints?: AlertManagerManualRouting
): GrafanaNotificationSettings | undefined {
if (contactPoints?.grafana?.selectedContactPoint && manualRouting) {
return {
receiver: contactPoints?.grafana?.selectedContactPoint,
mute_time_intervals: contactPoints?.grafana?.muteTimeIntervals,
active_time_intervals: contactPoints?.grafana?.activeTimeIntervals,
group_by: contactPoints?.grafana?.overrideGrouping ? contactPoints?.grafana?.groupBy : undefined,
group_wait:
contactPoints?.grafana?.overrideTimings && contactPoints?.grafana?.groupWaitValue
? contactPoints?.grafana?.groupWaitValue
: undefined,
group_interval:
contactPoints?.grafana?.overrideTimings && contactPoints?.grafana?.groupIntervalValue
? contactPoints?.grafana?.groupIntervalValue
: undefined,
repeat_interval:
contactPoints?.grafana?.overrideTimings && contactPoints?.grafana?.repeatIntervalValue
? contactPoints?.grafana?.repeatIntervalValue
: undefined,
};
}
return undefined;
}
function getEditorSettingsForDTO(simplifiedEditor: SimplifiedEditor) {
return {
simplified_query_and_expressions_section: simplifiedEditor.simplifiedQueryEditor,
simplified_notifications_section: simplifiedEditor.simplifiedNotificationEditor,
};
}
export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): PostableRuleGrafanaRuleDTO {
const {
name,
condition,
noDataState,
execErrState,
evaluateFor,
keepFiringFor,
queries,
isPaused,
contactPoints,
manualRouting,
type,
metric,
targetDatasourceUid,
missingSeriesEvalsToResolve,
} = values;
if (!condition) {
throw new Error('You cannot create an alert rule without specifying the alert condition');
}
const notificationSettings = getNotificationSettingsForDTO(manualRouting, contactPoints);
const metadata = values.editorSettings
? { editor_settings: getEditorSettingsForDTO(values.editorSettings) }
: undefined;
const annotations = arrayToRecord(cleanAnnotations(values.annotations));
const labels = arrayToRecord(cleanLabels(values.labels));
const wantsAlertingRule = isGrafanaAlertingRuleByType(type);
const wantsRecordingRule = isGrafanaRecordingRuleByType(type!);
if (wantsAlertingRule) {
return {
grafana_alert: {
title: name,
condition,
data: queries.map(fixBothInstantAndRangeQuery),
is_paused: Boolean(isPaused),
// Alerting rule specific
no_data_state: noDataState,
exec_err_state: execErrState,
notification_settings: notificationSettings,
metadata,
missing_series_evals_to_resolve: missingSeriesEvalsToResolve
? Number(missingSeriesEvalsToResolve)
: // API uses 0 value to reset, as `missing_series_evals_to_resolve` cannot be 0
0,
},
annotations,
labels,
// Alerting rule specific
for: evaluateFor,
keep_firing_for: keepFiringFor,
};
} else if (wantsRecordingRule) {
return {
grafana_alert: {
title: name,
condition,
data: queries.map(fixBothInstantAndRangeQuery),
is_paused: Boolean(isPaused),
// Recording rule specific
record: {
metric: metric ?? name,
from: condition,
target_datasource_uid: targetDatasourceUid,
},
},
annotations,
labels,
};
}
throw new Error(`Failed to convert form values to Grafana rule: unknown type ${type}`);
}
export const cleanAnnotations = (kvs: KVObject[]) =>
kvs.map(trimKeyAndValue).filter(({ key, value }: KVObject): Boolean => Boolean(key) && Boolean(value));
export const cleanLabels = (kvs: KVObject[]) =>
kvs.map(trimKeyAndValue).filter(({ key }: KVObject): Boolean => Boolean(key));
const trimKeyAndValue = ({ key, value }: KVObject): KVObject => ({
key: key.trim(),
value: value.trim(),
});
export function getContactPointsFromDTO(ga: GrafanaRuleDefinition): AlertManagerManualRouting | undefined {
const contactPoint: ContactPoint | undefined = ga.notification_settings
? {
selectedContactPoint: ga.notification_settings.receiver,
muteTimeIntervals: ga.notification_settings.mute_time_intervals ?? [],
activeTimeIntervals: ga.notification_settings.active_time_intervals ?? [],
overrideGrouping:
Array.isArray(ga.notification_settings.group_by) && ga.notification_settings.group_by.length > 0,
overrideTimings: [
ga.notification_settings.group_wait,
ga.notification_settings.group_interval,
ga.notification_settings.repeat_interval,
].some(Boolean),
groupBy: ga.notification_settings.group_by || [],
groupWaitValue: ga.notification_settings.group_wait || '',
groupIntervalValue: ga.notification_settings.group_interval || '',
repeatIntervalValue: ga.notification_settings.repeat_interval || '',
}
: undefined;
const routingSettings: AlertManagerManualRouting | undefined = contactPoint
? {
[GRAFANA_RULES_SOURCE_NAME]: contactPoint,
}
: undefined;
return routingSettings;
}
function getEditorSettingsFromDTO(ga: GrafanaRuleDefinition) {
// we need to check if the feature toggle is enabled as it might be disabled after the rule was created with the feature enabled
if (!config.featureToggles.alertingQueryAndExpressionsStepMode) {
return undefined;
}
if (ga.metadata?.editor_settings) {
return {
simplifiedQueryEditor: ga.metadata.editor_settings.simplified_query_and_expressions_section,
simplifiedNotificationEditor: ga.metadata.editor_settings.simplified_notifications_section,
};
}
return {
simplifiedQueryEditor: false,
simplifiedNotificationEditor: Boolean(ga.notification_settings), // in case this rule was created before the new field was added, we'll default to current routing settings
};
}
export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleFormValues {
const { ruleSourceName, namespace, group, rule } = ruleWithLocation;
const isGrafanaRecordingRule = rulerRuleType.grafana.recordingRule(rule);
const defaultFormValues = getDefaultFormValues(isGrafanaRecordingRule ? RuleFormType.grafanaRecording : undefined);
if (isGrafanaRulesSource(ruleSourceName)) {
// GRAFANA-MANAGED RULES
if (isGrafanaRecordingRule) {
// grafana recording rule
const ga = rule.grafana_alert;
return {
...defaultFormValues,
name: ga.title,
type: RuleFormType.grafanaRecording,
group: group.name,
evaluateEvery: group.interval || defaultFormValues.evaluateEvery,
queries: ga.data,
condition: ga.condition,
annotations: normalizeDefaultAnnotations(listifyLabelsOrAnnotations(rule.annotations, false)),
labels: listifyLabelsOrAnnotations(rule.labels, true),
folder: { title: namespace, uid: ga.namespace_uid },
isPaused: ga.is_paused,
metric: ga.record?.metric,
targetDatasourceUid: ga.record?.target_datasource_uid || defaultFormValues.targetDatasourceUid,
};
} else if (rulerRuleType.grafana.rule(rule)) {
// grafana alerting rule
const ga = rule.grafana_alert;
const routingSettings: AlertManagerManualRouting | undefined = getContactPointsFromDTO(ga);
if (ga.no_data_state !== undefined && ga.exec_err_state !== undefined) {
return {
...defaultFormValues,
name: ga.title,
type: RuleFormType.grafana,
group: group.name,
evaluateEvery: group.interval || defaultFormValues.evaluateEvery,
evaluateFor: rule.for || '0',
keepFiringFor: rule.keep_firing_for || '0',
noDataState: ga.no_data_state,
execErrState: ga.exec_err_state,
queries: ga.data,
condition: ga.condition,
annotations: normalizeDefaultAnnotations(listifyLabelsOrAnnotations(rule.annotations, false)),
labels: listifyLabelsOrAnnotations(rule.labels, true),
folder: { title: namespace, uid: ga.namespace_uid },
isPaused: ga.is_paused,
contactPoints: routingSettings,
manualRouting: Boolean(routingSettings),
editorSettings: getEditorSettingsFromDTO(ga),
missingSeriesEvalsToResolve: ga.missing_series_evals_to_resolve,
};
} else {
throw new Error('Unexpected type of rule for grafana rules source');
}
} else {
throw new Error('Unexpected type of rule for grafana rules source');
}
} else {
// DATASOURCE-MANAGED RULES
if (rulerRuleType.dataSource.alertingRule(rule)) {
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,
},
};
const alertingRuleValues = alertingRulerRuleToRuleForm(rule);
return {
...defaultFormValues,
...alertingRuleValues,
queries: [defaultQuery],
annotations: normalizeDefaultAnnotations(listifyLabelsOrAnnotations(rule.annotations, false)),
type: RuleFormType.cloudAlerting,
dataSourceName: ruleSourceName,
namespace,
group: group.name,
};
} else if (rulerRuleType.dataSource.recordingRule(rule)) {
const recordingRuleValues = recordingRulerRuleToRuleForm(rule);
return {
...defaultFormValues,
...recordingRuleValues,
type: RuleFormType.cloudRecording,
dataSourceName: ruleSourceName,
namespace,
group: group.name,
};
} else {
throw new Error('Unexpected type of rule for cloud rules source');
}
}
}
export function grafanaRuleDtoToFormValues(rule: RulerGrafanaRuleDTO, namespace: string): RuleFormValues {
const isGrafanaRecordingRule = rulerRuleType.grafana.recordingRule(rule);
const defaultFormValues = getDefaultFormValues(isGrafanaRecordingRule ? RuleFormType.grafanaRecording : undefined);
const ga = rule.grafana_alert;
const duration = rule.for;
const keepFiringFor = rule.keep_firing_for;
const annotations = rule.annotations;
const labels = rule.labels;
const commonProperties = {
...defaultFormValues,
name: ga.title,
queries: ga.data,
condition: ga.condition,
annotations: normalizeDefaultAnnotations(listifyLabelsOrAnnotations(annotations, false)),
labels: listifyLabelsOrAnnotations(labels, true),
folder: { title: namespace, uid: ga.namespace_uid },
isPaused: ga.is_paused,
};
if (rulerRuleType.grafana.recordingRule(rule)) {
// grafana recording rule
return {
...commonProperties,
type: RuleFormType.grafanaRecording,
group: ga.rule_group,
metric: ga.record?.metric,
};
}
// grafana alerting rule
const routingSettings: AlertManagerManualRouting | undefined = getContactPointsFromDTO(ga);
if (ga.no_data_state !== undefined && ga.exec_err_state !== undefined) {
return {
...commonProperties,
type: RuleFormType.grafana,
group: ga.rule_group,
evaluateFor: duration || '0',
keepFiringFor: keepFiringFor || '0',
noDataState: ga.no_data_state,
execErrState: ga.exec_err_state,
contactPoints: routingSettings,
manualRouting: Boolean(routingSettings),
editorSettings: getEditorSettingsFromDTO(ga),
};
} else {
throw new Error('Unexpected type of rule for grafana rules source');
}
}
export function alertingRulerRuleToRuleForm(
rule: RulerAlertingRuleDTO
): Pick<
RuleFormValues,
| 'name'
| 'forTime'
| 'forTimeUnit'
| 'keepFiringForTime'
| 'keepFiringForTimeUnit'
| 'expression'
| 'annotations'
| 'labels'
> {
const defaultFormValues = getDefaultFormValues();
const [forTime, forTimeUnit] = rule.for ? parseInterval(rule.for) : [0, 's'];
const [keepFiringForTime, keepFiringForTimeUnit] = rule.keep_firing_for
? parseInterval(rule.keep_firing_for)
: [defaultFormValues.keepFiringForTime, defaultFormValues.keepFiringForTimeUnit];
return {
name: rule.alert,
expression: rule.expr,
forTime,
forTimeUnit,
keepFiringForTime,
keepFiringForTimeUnit,
annotations: listifyLabelsOrAnnotations(rule.annotations, false),
labels: listifyLabelsOrAnnotations(rule.labels, true),
};
}
export function recordingRulerRuleToRuleForm(
rule: RulerRecordingRuleDTO
): Pick<RuleFormValues, 'name' | 'expression' | 'labels'> {
return {
name: rule.record,
expression: rule.expr,
labels: listifyLabelsOrAnnotations(rule.labels, true),
};
}
export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => {
const dataSource = getDefaultOrFirstCompatibleDataSource();
if (!dataSource) {
const expressions = isRecordingRule ? getDefaultExpressionsForRecording('A') : getDefaultExpressions('A', 'B');
return [...expressions];
}
const relativeTimeRange = getDefaultRelativeTimeRange();
const expressions = isRecordingRule ? getDefaultExpressionsForRecording('B') : getDefaultExpressions('B', 'C');
const isLokiOrPrometheus = dataSource ? isSupportedExternalRulesSourceType(dataSource.type) : false;
return [
{
refId: 'A',
datasourceUid: dataSource.uid,
queryType: '',
relativeTimeRange,
model: {
refId: 'A',
instant: isLokiOrPrometheus ? true : undefined,
},
},
...expressions,
];
};
export const getDefaultRecordingRulesQueries = (
rulesSourcesWithRuler: Array<DataSourceInstanceSettings<DataSourceJsonData>>
): AlertQuery[] => {
const relativeTimeRange = getDefaultRelativeTimeRange();
return [
{
refId: 'A',
datasourceUid: rulesSourcesWithRuler[0]?.uid || '',
queryType: '',
relativeTimeRange,
model: {
refId: 'A',
},
},
];
};
const getDefaultExpressions = (...refIds: [string, string]): AlertQuery[] => {
const refOne = refIds[0];
const refTwo = refIds[1];
const reduceExpression: ExpressionQuery = {
refId: refIds[0],
type: ExpressionQueryType.reduce,
datasource: {
uid: ExpressionDatasourceUID,
type: ExpressionDatasourceRef.type,
},
conditions: [
{
type: 'query',
evaluator: {
params: [],
type: EvalFunction.IsAbove,
},
operator: {
type: 'and',
},
query: {
params: [refOne],
},
reducer: {
params: [],
type: 'last',
},
},
],
reducer: 'last',
expression: 'A',
};
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,
};
return [
{
refId: refOne,
datasourceUid: ExpressionDatasourceUID,
queryType: '',
model: reduceExpression,
},
{
refId: refTwo,
datasourceUid: ExpressionDatasourceUID,
queryType: '',
model: thresholdExpression,
},
];
};
const getDefaultExpressionsForRecording = (refOne: string): AlertQuery[] => {
const reduceExpression: ExpressionQuery = {
refId: refOne,
type: ExpressionQueryType.reduce,
datasource: {
uid: ExpressionDatasourceUID,
type: ExpressionDatasourceRef.type,
},
conditions: [
{
type: 'query',
evaluator: {
params: [],
type: EvalFunction.IsAbove,
},
operator: {
type: 'and',
},
query: {
params: [refOne],
},
reducer: {
params: [],
type: 'last',
},
},
],
reducer: 'last',
expression: 'A',
};
return [
{
refId: refOne,
datasourceUid: ExpressionDatasourceUID,
queryType: '',
model: reduceExpression,
},
];
};
const dataQueriesToGrafanaQueries = async (
queries: DataQuery[],
relativeTimeRange: RelativeTimeRange,
scopedVars: ScopedVars | {},
panelDataSourceRef?: DataSourceRef,
maxDataPoints?: number,
minInterval?: string
): Promise<AlertQuery[]> => {
const result: AlertQuery[] = [];
for (const target of queries) {
const datasource = await getDataSourceSrv().get(target.datasource?.uid ? target.datasource : panelDataSourceRef);
const dsRef = { uid: datasource.uid, type: datasource.type };
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,
};
const interpolatedTarget = datasource.interpolateVariablesInQueries
? datasource.interpolateVariablesInQueries([target], queryVariables)[0]
: target;
// 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) {
const newQuery: AlertQuery = {
refId: interpolatedTarget.refId,
queryType: interpolatedTarget.queryType ?? '',
relativeTimeRange,
datasourceUid: datasourceSettings.uid,
model: {
...interpolatedTarget,
maxDataPoints,
intervalMs,
},
};
result.push(newQuery);
}
}
}
return result;
};
export const panelToRuleFormValues = async (
panel: PanelModel,
dashboard: DashboardModel
): Promise<Partial<RuleFormValues> | undefined> => {
const { targets } = panel;
if (!panel.id || !dashboard.uid) {
return undefined;
}
const relativeTimeRange = rangeUtil.timeRangeToRelative(rangeUtil.convertRawToRange(dashboard.time));
const queries = await dataQueriesToGrafanaQueries(
targets,
relativeTimeRange,
panel.scopedVars || {},
panel.datasource ?? undefined,
panel.maxDataPoints ?? undefined,
panel.interval ?? undefined
);
// 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)) {
const [reduceExpression, _thresholdExpression] = getDefaultExpressions(getNextRefId(queries), '-');
queries.push(reduceExpression);
const [_reduceExpression, thresholdExpression] = getDefaultExpressions(
reduceExpression.refId,
getNextRefId(queries)
);
queries.push(thresholdExpression);
}
const { folderTitle, folderUid } = dashboard.meta;
const folder =
folderUid && folderTitle
? {
kind: 'folder',
uid: folderUid,
title: folderTitle,
}
: undefined;
const formValues = {
type: RuleFormType.grafana,
folder,
queries,
name: panel.title,
condition: queries[queries.length - 1].refId,
annotations: [
{
key: Annotation.dashboardUID,
value: dashboard.uid,
},
{
key: Annotation.panelID,
value: String(panel.id),
},
],
};
return formValues;
};
export const scenesPanelToRuleFormValues = async (vizPanel: VizPanel): Promise<Partial<RuleFormValues> | undefined> => {
if (!vizPanel.state.key) {
return undefined;
}
const timeRange = sceneGraph.getTimeRange(vizPanel);
const queryRunner = getQueryRunnerFor(vizPanel);
if (!queryRunner) {
return undefined;
}
const { queries, datasource, maxDataPoints, minInterval } = queryRunner.state;
const dashboard = getDashboardSceneFor(vizPanel);
if (!dashboard || !dashboard.state.uid) {
return undefined;
}
const grafanaQueries = await dataQueriesToGrafanaQueries(
queries,
rangeUtil.timeRangeToRelative(rangeUtil.convertRawToRange(timeRange.state.value.raw)),
{ __sceneObject: { value: vizPanel } },
datasource,
maxDataPoints,
minInterval
);
// if no alerting capable queries are found, can't create a rule
if (!grafanaQueries.length || !grafanaQueries.find((query) => query.datasourceUid !== ExpressionDatasourceUID)) {
return undefined;
}
if (!grafanaQueries.find((query) => query.datasourceUid === ExpressionDatasourceUID)) {
const [reduceExpression, _thresholdExpression] = getDefaultExpressions(getNextRefId(grafanaQueries), '-');
grafanaQueries.push(reduceExpression);
const [_reduceExpression, thresholdExpression] = getDefaultExpressions(
reduceExpression.refId,
getNextRefId(grafanaQueries)
);
grafanaQueries.push(thresholdExpression);
}
const { folderTitle, folderUid } = dashboard.state.meta;
const folder =
folderUid && folderTitle
? {
kind: 'folder',
uid: folderUid,
title: folderTitle,
}
: undefined;
const formValues = {
type: RuleFormType.grafana,
folder,
queries: grafanaQueries,
name: vizPanel.state.title,
condition: grafanaQueries[grafanaQueries.length - 1].refId,
annotations: [
{
key: Annotation.dashboardUID,
value: dashboard.state.uid,
},
{
key: Annotation.panelID,
value: String(getPanelIdForVizPanel(vizPanel)),
},
],
};
return formValues;
};
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);
}
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;
}
export function isPromOrLokiQuery(model: AlertDataQuery): model is PromOrLokiQuery {
return 'expr' in model;
}
export function getInstantFromDataQuery(query: AlertQuery<AlertDataQuery>): boolean | undefined {
const dataSourceUID = query.datasourceUid ?? query.model.datasource?.uid;
if (!dataSourceUID) {
return undefined;
}
// find the datasource type from the UID
const type = getDataSourceSrv().getInstanceSettings(dataSourceUID)?.type;
if (!type) {
return undefined;
}
// if the datasource is not a supported prometheus flavor or loki, return "undefined"
if (!isSupportedExternalRulesSourceType(type)) {
return undefined;
}
const { model } = query;
// if the datasource is prometheus or loki, instant is defined in the model, or defaults to true
const isInstantForPrometheus = 'instant' in model && model.instant !== undefined ? model.instant : true;
const isInstantForLoki = 'queryType' in model && model.queryType !== undefined ? model.queryType === 'instant' : true;
const isPrometheusFlavoredDataSourceType = isSupportedExternalPrometheusFlavoredRulesSourceType(type);
const isInstant = isPrometheusFlavoredDataSourceType ? isInstantForPrometheus : isInstantForLoki;
return isInstant;
}