Alerting: Simplified alert rule toggle bug fixes (#102119)

This commit is contained in:
Gilles De Mey
2025-03-20 17:46:13 +02:00
committed by GitHub
parent 7970f0c79f
commit 9ad7fef4f4
15 changed files with 339 additions and 170 deletions
+3 -2
View File
@@ -1870,11 +1870,12 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"] [0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
], ],
"public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx:5381": [ "public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"], [0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"], [0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"], [0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"], [0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"] [0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
], ],
"public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx:5381": [ "public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
@@ -14,6 +14,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema'; import { DataQuery } from '@grafana/schema';
import { Button, Card, Icon, Stack } from '@grafana/ui'; import { Button, Card, Icon, Stack } from '@grafana/ui';
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
import { isExpressionQuery } from 'app/features/expressions/guards';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
@@ -236,8 +237,10 @@ function copyModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit
} }
function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit<AlertQuery, 'datasource'> { function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit<AlertQuery, 'datasource'> {
const isInstant = getInstantFromDataQuery(item.model, settings.type); const isExpression = isExpressionQuery(item);
return { const isInstant = isExpression ? false : getInstantFromDataQuery(item);
const newQuery: Omit<AlertQuery, 'datasource'> = {
refId: item.refId, refId: item.refId,
relativeTimeRange: item.relativeTimeRange, relativeTimeRange: item.relativeTimeRange,
queryType: '', queryType: '',
@@ -246,9 +249,14 @@ function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit<
refId: item.refId, refId: item.refId,
hide: false, hide: false,
datasource: getDataSourceRef(settings), datasource: getDataSourceRef(settings),
instant: isInstant,
}, },
}; };
if (isInstant && !isExpressionQuery(item)) {
(newQuery as AlertQuery<AlertDataQuery>).model.instant = isInstant;
}
return newQuery;
} }
interface DatasourceNotFoundProps { interface DatasourceNotFoundProps {
@@ -23,7 +23,12 @@ import {
} from '@grafana/ui'; } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization'; import { Trans, t } from 'app/core/internationalization';
import { isExpressionQuery } from 'app/features/expressions/guards'; import { isExpressionQuery } from 'app/features/expressions/guards';
import { ExpressionDatasourceUID, ExpressionQueryType, expressionTypes } from 'app/features/expressions/types'; import {
ExpressionDatasourceUID,
ExpressionQuery,
ExpressionQueryType,
expressionTypes,
} from 'app/features/expressions/types';
import { AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertQuery } from 'app/types/unified-alerting-dto';
import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler'; import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler';
@@ -50,7 +55,7 @@ import { RuleEditorSection } from '../RuleEditorSection';
import { errorFromCurrentCondition, errorFromPreviewData, findRenamedDataQueryReferences, refIdExists } from '../util'; import { errorFromCurrentCondition, errorFromPreviewData, findRenamedDataQueryReferences, refIdExists } from '../util';
import { CloudDataSourceSelector } from './CloudDataSourceSelector'; import { CloudDataSourceSelector } from './CloudDataSourceSelector';
import { SimpleConditionEditor, SimpleConditionIdentifier, getSimpleConditionFromExpressions } from './SimpleCondition'; import { SimpleConditionEditor, getSimpleConditionFromExpressions } from './SimpleCondition';
import { SmartAlertTypeDetector } from './SmartAlertTypeDetector'; import { SmartAlertTypeDetector } from './SmartAlertTypeDetector';
import { DESCRIPTIONS } from './descriptions'; import { DESCRIPTIONS } from './descriptions';
import { import {
@@ -146,7 +151,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod
); );
const simplifiedQueryStep = const simplifiedQueryStep =
isSwitchModeEnabled && isGrafanaAlertingType ? getValues('editorSettings.simplifiedQueryEditor') : false; isSwitchModeEnabled && isGrafanaAlertingType ? editorSettings?.simplifiedQueryEditor : false;
// If we switch to simple mode we need to update the simple condition with the data in the queries reducer // If we switch to simple mode we need to update the simple condition with the data in the queries reducer
useEffect(() => { useEffect(() => {
@@ -164,15 +169,22 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod
// Grafana Managed rules and recording rules do // Grafana Managed rules and recording rules do
return; return;
} }
// we need to be sure the condition is set once we switch to simple mode
if (simplifiedQueryStep) { if (simplifiedQueryStep) {
setValue('condition', SimpleConditionIdentifier.thresholdId); const lastExpression = expressionQueries.at(-1);
runQueries(getValues('queries'), SimpleConditionIdentifier.thresholdId); if (!lastExpression) {
return;
}
const condition = lastExpression.refId;
// we need to be sure the condition is set once we switch to simple mode
setValue('condition', condition);
runQueries(getValues('queries'), condition);
} else { } else {
runQueries(getValues('queries'), condition || (getValues('condition') ?? '')); runQueries(getValues('queries'), condition || (getValues('condition') ?? ''));
} }
}, },
[isCloudAlertRuleType, runQueries, getValues, simplifiedQueryStep, setValue] [isCloudAlertRuleType, expressionQueries, simplifiedQueryStep, setValue, runQueries, getValues]
); );
// whenever we update the queries we have to update the form too // whenever we update the queries we have to update the form too
@@ -247,7 +259,9 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod
// As a workaround we update form values as soon as possible to avoid stale state // As a workaround we update form values as soon as possible to avoid stale state
// This way we can access up to date queries in runQueriesPreview without waiting for re-render // This way we can access up to date queries in runQueriesPreview without waiting for re-render
const previousQueries = getValues('queries'); const previousQueries = getValues('queries');
const expressionQueries = previousQueries.filter((query) => isExpressionQuery(query.model));
const expressionQueries = previousQueries.filter<AlertQuery<ExpressionQuery>>(isExpressionQueryInAlert);
setValue('queries', [...updatedQueries, ...expressionQueries], { shouldValidate: false }); setValue('queries', [...updatedQueries, ...expressionQueries], { shouldValidate: false });
updateExpressionAndDatasource(updatedQueries); updateExpressionAndDatasource(updatedQueries);
@@ -17,11 +17,6 @@ import { ExpressionResult } from '../../expressions/Expression';
import { updateExpression } from './reducer'; import { updateExpression } from './reducer';
export const SimpleConditionIdentifier = {
queryId: 'A',
reducerId: 'B',
thresholdId: 'C',
} as const;
export interface SimpleCondition { export interface SimpleCondition {
whenField?: string; whenField?: string;
evaluator: { evaluator: {
@@ -158,10 +153,8 @@ function updateReduceExpression(
expressionQueriesList: Array<AlertQuery<ExpressionQuery>>, expressionQueriesList: Array<AlertQuery<ExpressionQuery>>,
dispatch: Dispatch<UnknownAction> dispatch: Dispatch<UnknownAction>
) { ) {
const reduceExpression = expressionQueriesList.find( // 1. make sure have have a reduce expression and that it is pointing to the data query
(query) => const reduceExpression = expressionQueriesList.find((query) => query.model.type === ExpressionQueryType.reduce);
query.model.type === ExpressionQueryType.reduce && query.model.refId === SimpleConditionIdentifier.reducerId
);
const newReduceExpression = reduceExpression const newReduceExpression = reduceExpression
? produce(reduceExpression?.model, (draft) => { ? produce(reduceExpression?.model, (draft) => {
@@ -179,10 +172,7 @@ function updateThresholdFunction(
expressionQueriesList: Array<AlertQuery<ExpressionQuery>>, expressionQueriesList: Array<AlertQuery<ExpressionQuery>>,
dispatch: Dispatch<UnknownAction> dispatch: Dispatch<UnknownAction>
) { ) {
const thresholdExpression = expressionQueriesList.find( const thresholdExpression = expressionQueriesList.find((query) => query.model.type === ExpressionQueryType.threshold);
(query) =>
query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId
);
const newThresholdExpression = produce(thresholdExpression, (draft) => { const newThresholdExpression = produce(thresholdExpression, (draft) => {
if (draft && draft.model.conditions) { if (draft && draft.model.conditions) {
@@ -198,10 +188,7 @@ function updateThresholdValue(
expressionQueriesList: Array<AlertQuery<ExpressionQuery>>, expressionQueriesList: Array<AlertQuery<ExpressionQuery>>,
dispatch: Dispatch<UnknownAction> dispatch: Dispatch<UnknownAction>
) { ) {
const thresholdExpression = expressionQueriesList.find( const thresholdExpression = expressionQueriesList.find((query) => query.model.type === ExpressionQueryType.threshold);
(query) =>
query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId
);
const newThresholdExpression = produce(thresholdExpression, (draft) => { const newThresholdExpression = produce(thresholdExpression, (draft) => {
if (draft && draft.model.conditions) { if (draft && draft.model.conditions) {
@@ -212,13 +199,8 @@ function updateThresholdValue(
} }
export function getSimpleConditionFromExpressions(expressions: Array<AlertQuery<ExpressionQuery>>): SimpleCondition { export function getSimpleConditionFromExpressions(expressions: Array<AlertQuery<ExpressionQuery>>): SimpleCondition {
const reduceExpression = expressions.find( const reduceExpression = expressions.find((query) => query.model.type === ExpressionQueryType.reduce);
(query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId const thresholdExpression = expressions.find((query) => query.model.type === ExpressionQueryType.threshold);
);
const thresholdExpression = expressions.find(
(query) =>
query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId
);
const conditionsFromThreshold = thresholdExpression?.model.conditions ?? []; const conditionsFromThreshold = thresholdExpression?.model.conditions ?? [];
const whenField = reduceExpression?.model.reducer; const whenField = reduceExpression?.model.reducer;
const params = conditionsFromThreshold[0]?.evaluator?.params const params = conditionsFromThreshold[0]?.evaluator?.params
@@ -1,35 +1,50 @@
import { produce } from 'immer'; import { produce } from 'immer';
import { EvalFunction } from 'app/features/alerting/state/alertDef'; import { EvalFunction } from 'app/features/alerting/state/alertDef';
import { dataQuery, reduceExpression, thresholdExpression } from 'app/features/alerting/unified/mocks'; import {
mockDataQuery,
mockDataSource,
mockReduceExpression,
mockThresholdExpression,
} from 'app/features/alerting/unified/mocks';
import { areQueriesTransformableToSimpleCondition } from 'app/features/alerting/unified/rule-editor/formProcessing'; import { areQueriesTransformableToSimpleCondition } from 'app/features/alerting/unified/rule-editor/formProcessing';
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
import { DataSourceType } from 'app/features/alerting/unified/utils/datasource';
import { ExpressionQuery, ReducerMode } from 'app/features/expressions/types'; import { ExpressionQuery, ReducerMode } from 'app/features/expressions/types';
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
const expressionQueries: Array<AlertQuery<ExpressionQuery>> = [reduceExpression, thresholdExpression]; const reduceExpression = mockReduceExpression({ expression: 'A', settings: { mode: ReducerMode.Strict } });
const thresholdExpression = mockThresholdExpression({ expression: 'B' });
const expressionQueries: Array<AlertQuery<ExpressionQuery>> = [reduceExpression, thresholdExpression];
const ds = mockDataSource({ type: DataSourceType.Prometheus, name: 'Mimir-cloud', uid: 'abc123' });
describe('areQueriesTransformableToSimpleCondition', () => { describe('areQueriesTransformableToSimpleCondition', () => {
beforeEach(() => {
setupDataSources(ds);
});
it('should return false if dataQueries length is not 1', () => { it('should return false if dataQueries length is not 1', () => {
// zero dataQueries // zero dataQueries
expect(areQueriesTransformableToSimpleCondition([], expressionQueries)).toBe(false); expect(areQueriesTransformableToSimpleCondition([], expressionQueries)).toBe(false);
// more than one dataQueries // more than one dataQueries
expect(areQueriesTransformableToSimpleCondition([dataQuery, dataQuery], expressionQueries)).toBe(false); expect(areQueriesTransformableToSimpleCondition([mockDataQuery(), mockDataQuery()], expressionQueries)).toBe(false);
}); });
it('should return false if expressionQueries length is not 2', () => { it('should return false if expressionQueries length is not 2', () => {
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [dataQuery]; const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [mockDataQuery()];
const result = areQueriesTransformableToSimpleCondition(dataQueries, []); const result = areQueriesTransformableToSimpleCondition(dataQueries, []);
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false if the dataQuery refId does not match SimpleConditionIdentifier.queryId', () => { // notSimpleCondition
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [ // reducer:
{ refId: 'notSimpleCondition', datasourceUid: 'abc123', queryType: '', model: { refId: 'notSimpleCondition' } }, it('should return false if the mockDataQuery() refId does not match SimpleConditionIdentifier.queryId', () => {
]; const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [mockDataQuery({ refId: 'foo' })];
const result = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries); const result = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries);
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false if no reduce expression is found with correct type and refId', () => { it('should return false if no reduce expression is found with correct type and refId', () => {
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [dataQuery]; const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [mockDataQuery()];
const result = areQueriesTransformableToSimpleCondition(dataQueries, [ const result = areQueriesTransformableToSimpleCondition(dataQueries, [
{ ...reduceExpression, refId: 'hello' }, { ...reduceExpression, refId: 'hello' },
thresholdExpression, thresholdExpression,
@@ -37,17 +52,25 @@ describe('areQueriesTransformableToSimpleCondition', () => {
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false if no threshold expression is found with correct type and refId', () => { it('should return false if no threshold expression is found that points to reducer', () => {
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [dataQuery]; const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [mockDataQuery()];
const result = areQueriesTransformableToSimpleCondition(dataQueries, [ const result = areQueriesTransformableToSimpleCondition(dataQueries, [
reduceExpression, reduceExpression,
{ ...thresholdExpression, refId: 'hello' }, mockThresholdExpression({ expression: 'hello' }),
]);
expect(result).toBe(false);
});
it('should return false if no threshold expression is found that points to instant data query', () => {
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [mockDataQuery({ instant: true })];
const result = areQueriesTransformableToSimpleCondition(dataQueries, [
mockThresholdExpression({ expression: 'hello' }),
]); ]);
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false if reduceExpression settings mode is not ReducerMode.Strict', () => { it('should return false if reduceExpression settings mode is not ReducerMode.Strict', () => {
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [dataQuery]; const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [mockDataQuery()];
const transformedReduceExpression = produce(reduceExpression, (draft) => { const transformedReduceExpression = produce(reduceExpression, (draft) => {
draft.model.settings = { mode: ReducerMode.DropNonNumbers }; draft.model.settings = { mode: ReducerMode.DropNonNumbers };
}); });
@@ -60,7 +83,7 @@ describe('areQueriesTransformableToSimpleCondition', () => {
}); });
it('should return false if thresholdExpression unloadEvaluator has a value', () => { it('should return false if thresholdExpression unloadEvaluator has a value', () => {
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [dataQuery]; const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [mockDataQuery()];
const transformedThresholdExpression = produce(thresholdExpression, (draft) => { const transformedThresholdExpression = produce(thresholdExpression, (draft) => {
draft.model.conditions = [ draft.model.conditions = [
@@ -79,9 +102,17 @@ describe('areQueriesTransformableToSimpleCondition', () => {
]); ]);
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return true when all conditions are met', () => {
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [dataQuery]; it('should return true when data query is connected to valid reducer and threshold', () => {
const result = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries); const result = areQueriesTransformableToSimpleCondition([mockDataQuery({ refId: 'A' })], expressionQueries);
expect(result).toBe(true);
});
it('should return true when all conditions are met for instant data query with threshold', () => {
const result = areQueriesTransformableToSimpleCondition(
[mockDataQuery({ instant: true })],
[mockThresholdExpression({ expression: 'A' })]
);
expect(result).toBe(true); expect(result).toBe(true);
}); });
}); });
@@ -130,16 +130,16 @@ exports[`Query and expressions reducer should add reduce expression if there is
}, },
"expression": "A", "expression": "A",
"reducer": "last", "reducer": "last",
"refId": "B", "refId": "reducer",
"type": "reduce", "type": "reduce",
}, },
"queryType": "expression", "queryType": "expression",
"refId": "B", "refId": "reducer",
}, },
{ {
"datasourceUid": "__expr__", "datasourceUid": "__expr__",
"model": { "model": {
"expression": "B", "expression": "reducer",
"refId": "C", "refId": "C",
"type": "threshold", "type": "threshold",
}, },
@@ -218,6 +218,31 @@ exports[`Query and expressions reducer should remove first reducer 1`] = `
} }
`; `;
exports[`Query and expressions reducer should remove reducer even if reducer is not the first expression 1`] = `
{
"queries": [
{
"datasourceUid": "abc123",
"model": {
"refId": "A",
},
"queryType": "query",
"refId": "A",
},
{
"datasourceUid": "__expr__",
"model": {
"expression": "A",
"refId": "C",
"type": "threshold",
},
"queryType": "expression",
"refId": "C",
},
],
}
`;
exports[`Query and expressions reducer should rewire expressions 1`] = ` exports[`Query and expressions reducer should rewire expressions 1`] = `
{ {
"queries": [ "queries": [
@@ -10,7 +10,6 @@ import {
import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; import { defaultCondition } from 'app/features/expressions/utils/expressionTypes';
import { AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertQuery } from 'app/types/unified-alerting-dto';
import { SimpleConditionIdentifier } from './SimpleCondition';
import { import {
QueriesAndExpressionsState, QueriesAndExpressionsState,
addNewDataQuery, addNewDataQuery,
@@ -28,22 +27,23 @@ import {
} from './reducer'; } from './reducer';
const reduceExpression: AlertQuery<ExpressionQuery> = { const reduceExpression: AlertQuery<ExpressionQuery> = {
refId: SimpleConditionIdentifier.reducerId, refId: 'B',
queryType: 'expression', queryType: 'expression',
datasourceUid: '__expr__', datasourceUid: '__expr__',
model: { model: {
type: ExpressionQueryType.reduce, type: ExpressionQueryType.reduce,
refId: SimpleConditionIdentifier.reducerId, refId: 'B',
settings: { mode: ReducerMode.Strict }, settings: { mode: ReducerMode.Strict },
expression: 'A',
}, },
}; };
const thresholdExpression: AlertQuery<ExpressionQuery> = { const thresholdExpression: AlertQuery<ExpressionQuery> = {
refId: SimpleConditionIdentifier.thresholdId, refId: 'C',
queryType: 'expression', queryType: 'expression',
datasourceUid: '__expr__', datasourceUid: '__expr__',
model: { model: {
type: ExpressionQueryType.threshold, type: ExpressionQueryType.threshold,
refId: SimpleConditionIdentifier.thresholdId, refId: 'C',
}, },
}; };
@@ -400,7 +400,7 @@ describe('Query and expressions reducer', () => {
expect(newState).toMatchSnapshot(); expect(newState).toMatchSnapshot();
}); });
it('should not remove first reducer if reducer is not the first expression', () => { it('should remove reducer even if reducer is not the first expression', () => {
const initialState: QueriesAndExpressionsState = { const initialState: QueriesAndExpressionsState = {
queries: [alertQuery, thresholdExpression, reduceExpression], queries: [alertQuery, thresholdExpression, reduceExpression],
}; };
@@ -412,7 +412,7 @@ describe('Query and expressions reducer', () => {
expressionQueries: [thresholdExpression, reduceExpression], expressionQueries: [thresholdExpression, reduceExpression],
}) })
); );
expect(newState).toEqual(initialState); expect(newState).toMatchSnapshot();
}); });
it('should not remove first reducer if reducer is not the second query', () => { it('should not remove first reducer if reducer is not the second query', () => {
@@ -8,21 +8,25 @@ import {
getNextRefId, getNextRefId,
rangeUtil, rangeUtil,
} from '@grafana/data'; } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema'; import { DataQuery } from '@grafana/schema';
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
import { isExpressionQuery } from 'app/features/expressions/guards'; import { isExpressionQuery } from 'app/features/expressions/guards';
import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; import {
defaultCondition,
isReducerExpression,
isThresholdExpression,
} from 'app/features/expressions/utils/expressionTypes';
import { AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertQuery } from 'app/types/unified-alerting-dto';
import { logError } from '../../../Analytics'; import { logError } from '../../../Analytics';
import { DataSourceType, getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource';
import { getDefaultQueries, getInstantFromDataQuery } from '../../../utils/rule-form'; import { getDefaultQueries, getInstantFromDataQuery } from '../../../utils/rule-form';
import { createDagFromQueries, getOriginOfRefId } from '../dag'; import { createDagFromQueries, getOriginOfRefId } from '../dag';
import { queriesWithUpdatedReferences, refIdExists } from '../util'; import { queriesWithUpdatedReferences, refIdExists } from '../util';
import { SimpleConditionIdentifier } from './SimpleCondition'; // this one will be used as the refID when we create a new reducer for the threshold expression
export const NEW_REDUCER_REF = 'reducer';
export interface QueriesAndExpressionsState { export interface QueriesAndExpressionsState {
queries: AlertQuery[]; queries: AlertQuery[];
@@ -64,9 +68,10 @@ export const updateMaxDataPoints = createAction<{ refId: string; maxDataPoints:
export const updateMinInterval = createAction<{ refId: string; minInterval: string }>('updateMinInterval'); export const updateMinInterval = createAction<{ refId: string; minInterval: string }>('updateMinInterval');
export const resetToSimpleCondition = createAction('resetToSimpleCondition'); export const resetToSimpleCondition = createAction('resetToSimpleCondition');
export const optimizeReduceExpression = createAction<{ updatedQueries: AlertQuery[]; expressionQueries: AlertQuery[] }>( export const optimizeReduceExpression = createAction<{
'optimizeReduceExpression' updatedQueries: AlertQuery[];
); expressionQueries: Array<AlertQuery<ExpressionQuery>>;
}>('optimizeReduceExpression');
export const setRecordingRulesQueries = createAction<{ recordingRuleQueries: AlertQuery[]; expression: string }>( export const setRecordingRulesQueries = createAction<{ recordingRuleQueries: AlertQuery[]; expression: string }>(
'setRecordingRulesQueries' 'setRecordingRulesQueries'
); );
@@ -231,6 +236,7 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder
.addCase(rewireExpressions, (state, { payload }) => { .addCase(rewireExpressions, (state, { payload }) => {
state.queries = queriesWithUpdatedReferences(state.queries, payload.oldRefId, payload.newRefId); state.queries = queriesWithUpdatedReferences(state.queries, payload.oldRefId, payload.newRefId);
}) })
// removes the reduce expression when we have a instant data query
.addCase(optimizeReduceExpression, (state, { payload }) => { .addCase(optimizeReduceExpression, (state, { payload }) => {
const { updatedQueries, expressionQueries } = payload; const { updatedQueries, expressionQueries } = payload;
@@ -239,48 +245,29 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder
return; return;
} }
//sometimes we dont have data source in the model yet const dataQuery = updatedQueries.at(0);
const getDataSourceSettingsForFirstQuery = getDataSourceSrv().getInstanceSettings( const isInstantDataQuery = dataQuery ? getInstantFromDataQuery(dataQuery) : false;
updatedQueries[0].datasourceUid
);
if (!getDataSourceSettingsForFirstQuery) {
return;
}
const type = getDataSourceSettingsForFirstQuery?.type;
const firstQueryIsPromOrLoki = type === DataSourceType.Prometheus || type === DataSourceType.Loki;
const isInstant = getInstantFromDataQuery(updatedQueries[0].model, type);
const shouldRemoveReducer =
firstQueryIsPromOrLoki && updatedQueries.length === 1 && isInstant && expressionQueries.length === 2;
const onlyOneExpressionNotReducer =
expressionQueries.length === 1 &&
'type' in expressionQueries[0].model &&
expressionQueries[0].model.type !== ExpressionQueryType.reduce;
// we only add the reduce expression if we have one data query and one expression query. For other cases we don't do anything,
// and let the user add the reducer manually.
const shouldAddReduceExpression =
firstQueryIsPromOrLoki && updatedQueries.length === 1 && !isInstant && onlyOneExpressionNotReducer;
const shouldRemoveReducer = isInstantDataQuery && expressionQueries.length === 2;
if (shouldRemoveReducer) { if (shouldRemoveReducer) {
const reduceExpressionIndex = state.queries.findIndex( const reduceExpressionIndex = state.queries.findIndex(
(query) => isExpressionQuery(query.model) && query.model.type === ExpressionQueryType.reduce (query) =>
isExpressionQuery(query.model) &&
isReducerExpression(query.model) &&
query.model.expression === dataQuery?.refId
); );
if (reduceExpressionIndex === 1) { state.queries.splice(reduceExpressionIndex, 1);
// means the reduce expression is the second query state.queries[1].model.expression = dataQuery?.refId;
state.queries.splice(reduceExpressionIndex, 1);
state.queries[1].model.expression = SimpleConditionIdentifier.queryId;
}
} }
const shouldAddReduceExpression =
!isInstantDataQuery && expressionQueries.length === 1 && isThresholdExpression(expressionQueries[0].model);
if (shouldAddReduceExpression) { if (shouldAddReduceExpression) {
// add reducer to the second position // add reducer to the second position
// we only update the refid and the model to point to the reducer expression // we only update the refid and the model to point to the reducer expression
state.queries[1].model.expression = SimpleConditionIdentifier.reducerId; state.queries[1].model.expression = NEW_REDUCER_REF;
// insert in second position the reducer expression // insert in second position the reducer expression
state.queries.splice(1, 0, { state.queries.splice(1, 0, {
datasourceUid: ExpressionDatasourceUID, datasourceUid: ExpressionDatasourceUID,
@@ -288,10 +275,10 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder
type: ExpressionQueryType.reduce, type: ExpressionQueryType.reduce,
reducer: ReducerID.last, reducer: ReducerID.last,
conditions: [{ ...defaultCondition, query: { params: [] } }], conditions: [{ ...defaultCondition, query: { params: [] } }],
expression: SimpleConditionIdentifier.queryId, expression: dataQuery?.refId,
refId: SimpleConditionIdentifier.reducerId, refId: NEW_REDUCER_REF,
}), }),
refId: SimpleConditionIdentifier.reducerId, refId: NEW_REDUCER_REF,
queryType: 'expression', queryType: 'expression',
}); });
} }
@@ -2,6 +2,7 @@ import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWi
import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/types'; import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/types';
import { AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertQuery } from 'app/types/unified-alerting-dto';
import { NEW_REDUCER_REF } from './query-and-alert-condition/reducer';
import { import {
containsPathSeparator, containsPathSeparator,
findRenamedDataQueryReferences, findRenamedDataQueryReferences,
@@ -163,10 +164,10 @@ describe('rule-editor', () => {
it('should rewire threshold expressions', () => { it('should rewire threshold expressions', () => {
const queries: AlertQuery[] = [dataSource, reduceExpression, thresholdExpression]; const queries: AlertQuery[] = [dataSource, reduceExpression, thresholdExpression];
const rewiredQueries = queriesWithUpdatedReferences(queries, 'B', 'REDUCER'); const rewiredQueries = queriesWithUpdatedReferences(queries, 'B', NEW_REDUCER_REF);
const queryModel = rewiredQueries[2].model as ExpressionQuery; const queryModel = rewiredQueries[2].model as ExpressionQuery;
expect(queryModel.expression).toBe('REDUCER'); expect(queryModel.expression).toBe(NEW_REDUCER_REF);
}); });
it('should rewire multiple expressions', () => { it('should rewire multiple expressions', () => {
+15 -13
View File
@@ -56,7 +56,6 @@ import {
import { DashboardSearchItem, DashboardSearchItemType } from '../../search/types'; import { DashboardSearchItem, DashboardSearchItemType } from '../../search/types';
import { SimpleConditionIdentifier } from './components/rule-editor/query-and-alert-condition/SimpleCondition';
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
import { parsePromQLStyleMatcherLooseSafe } from './utils/matchers'; import { parsePromQLStyleMatcherLooseSafe } from './utils/matchers';
@@ -772,30 +771,33 @@ export function mockDashboardDto(
}; };
} }
export const dataQuery: AlertQuery<AlertDataQuery | ExpressionQuery> = { export const mockDataQuery = (partial: Partial<AlertDataQuery> = {}): AlertQuery<AlertDataQuery> => ({
refId: SimpleConditionIdentifier.queryId, refId: partial?.refId ?? 'A',
datasourceUid: 'abc123', datasourceUid: 'abc123',
queryType: '', queryType: '',
model: { refId: SimpleConditionIdentifier.queryId }, model: { refId: 'A', ...partial },
}; });
export const reduceExpression: AlertQuery<ExpressionQuery> = { export const mockReduceExpression = (partial: Partial<ExpressionQuery> = {}): AlertQuery<ExpressionQuery> => ({
refId: SimpleConditionIdentifier.reducerId, refId: 'B',
queryType: 'expression', queryType: 'expression',
datasourceUid: '__expr__', datasourceUid: '__expr__',
model: { model: {
type: ExpressionQueryType.reduce, type: ExpressionQueryType.reduce,
refId: SimpleConditionIdentifier.reducerId, refId: 'B',
settings: { mode: ReducerMode.Strict }, settings: { mode: ReducerMode.Strict },
reducer: ReducerID.last, reducer: ReducerID.last,
...partial,
}, },
}; });
export const thresholdExpression: AlertQuery<ExpressionQuery> = {
refId: SimpleConditionIdentifier.thresholdId, export const mockThresholdExpression = (partial: Partial<ExpressionQuery> = {}): AlertQuery<ExpressionQuery> => ({
refId: 'C',
queryType: 'expression', queryType: 'expression',
datasourceUid: '__expr__', datasourceUid: '__expr__',
model: { model: {
type: ExpressionQueryType.threshold, type: ExpressionQueryType.threshold,
refId: SimpleConditionIdentifier.thresholdId, refId: 'C',
...partial,
}, },
}; });
@@ -1,6 +1,6 @@
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { mockAlertQuery, mockDataSource, reduceExpression, thresholdExpression } from '../mocks'; import { mockAlertQuery, mockDataSource, mockReduceExpression, mockThresholdExpression } from '../mocks';
import { testWithFeatureToggles } from '../test/test-utils'; import { testWithFeatureToggles } from '../test/test-utils';
import { RuleFormType } from '../types/rule-form'; import { RuleFormType } from '../types/rule-form';
import { Annotation } from '../utils/constants'; import { Annotation } from '../utils/constants';
@@ -73,7 +73,11 @@ describe('formValuesFromQueryParams', () => {
it('should enable simplified query editor if queries are transformable to simple condition', () => { it('should enable simplified query editor if queries are transformable to simple condition', () => {
const result = formValuesFromQueryParams( const result = formValuesFromQueryParams(
JSON.stringify({ JSON.stringify({
queries: [mockAlertQuery(), reduceExpression, thresholdExpression], queries: [
mockAlertQuery(),
mockReduceExpression({ expression: 'A' }),
mockThresholdExpression({ expression: 'B' }),
],
}), }),
RuleFormType.grafana RuleFormType.grafana
); );
@@ -85,7 +89,7 @@ describe('formValuesFromQueryParams', () => {
it('should disable simplified query editor if queries are not transformable to simple condition', () => { it('should disable simplified query editor if queries are not transformable to simple condition', () => {
const result = formValuesFromQueryParams( const result = formValuesFromQueryParams(
JSON.stringify({ JSON.stringify({
queries: [mockAlertQuery(), mockAlertQuery(), thresholdExpression], queries: [mockAlertQuery(), mockAlertQuery(), mockThresholdExpression({ expression: 'B' })],
}), }),
RuleFormType.grafana RuleFormType.grafana
); );
@@ -1,14 +1,15 @@
import { omit } from 'lodash'; import { isEmpty, omit } from 'lodash';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { isExpressionQuery } from 'app/features/expressions/guards'; import { isExpressionQuery } from 'app/features/expressions/guards';
import { ExpressionQuery, ExpressionQueryType, ReducerMode } from 'app/features/expressions/types'; import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
import { isStrictReducer } from 'app/features/expressions/utils/expressionTypes';
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
import { SimpleConditionIdentifier } from '../components/rule-editor/query-and-alert-condition/SimpleCondition';
import { KVObject, RuleFormValues } from '../types/rule-form'; import { KVObject, RuleFormValues } from '../types/rule-form';
import { defaultAnnotations } from '../utils/constants'; import { defaultAnnotations } from '../utils/constants';
import { DataSourceType } from '../utils/datasource'; import { DataSourceType } from '../utils/datasource';
import { getInstantFromDataQuery } from '../utils/rule-form';
export function setQueryEditorSettings(values: RuleFormValues): RuleFormValues { export function setQueryEditorSettings(values: RuleFormValues): RuleFormValues {
const isQuerySwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false; const isQuerySwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false;
@@ -64,47 +65,63 @@ export function setInstantOrRange(values: RuleFormValues): RuleFormValues {
}; };
} }
/**
* A alert rule is "transformable" to a simple condition editor if
* 1. we have a single data query
* 2. we have _either_
* 2.1 a reduce expression (pointing to the data query) _and_ a threshold expression pointing to the reducer
* 2.2 a threshold expression pointing to a (instant) data query
* ⚠️ do not assert on refIds or indexes of the queries
*/
export function areQueriesTransformableToSimpleCondition( export function areQueriesTransformableToSimpleCondition(
dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>>, dataQueries: Array<AlertQuery<AlertDataQuery>>,
expressionQueries: Array<AlertQuery<ExpressionQuery>> expressionQueries: Array<AlertQuery<ExpressionQuery>>
) { ) {
// 1. check if we only have a _single_ data query
if (dataQueries.length !== 1) { if (dataQueries.length !== 1) {
return false; return false;
} }
const singleReduceExpressionInInstantQuery =
'instant' in dataQueries[0].model && dataQueries[0].model.instant && expressionQueries.length === 1;
if (expressionQueries.length !== 2 && !singleReduceExpressionInInstantQuery) { // short-circuit when we have more than 2 expressions, we don't know what to do with that
if (expressionQueries.length > 2) {
return false; return false;
} }
const query = dataQueries[0]; const dataQuery = dataQueries.at(0);
if (query.refId !== SimpleConditionIdentifier.queryId) { // find the reduce or threshold expressions
return false; const reduceExpression = expressionQueries.find((query) => query.model.type === ExpressionQueryType.reduce);
const thresholdExpression = expressionQueries.find((query) => query.model.type === ExpressionQueryType.threshold);
// reducer should be set to "strict" mode
const reducerIsStrict = reduceExpression ? isStrictReducer(reduceExpression.model) : false;
// threshold expression shouldn't have an unload evaluator (custom recovery threshold)
const thresholdExpressionIsClean =
thresholdExpression?.model.conditions?.every((condition) => {
return isEmpty(condition.unloadEvaluator);
}) ?? true;
const validReducerExpression = reduceExpression && reducerIsStrict;
const validThresholdExpression = thresholdExpression && thresholdExpressionIsClean;
const thresholdPointingToReducer = thresholdExpression?.model.expression === reduceExpression?.refId;
const reducerPointingToDataQuery = reduceExpression?.model.expression === dataQuery?.refId;
// 2.1 check for a reduce + threshold expression and their targets
if (validReducerExpression && reducerPointingToDataQuery && validThresholdExpression && thresholdPointingToReducer) {
return true;
} }
const reduceExpressionIndex = expressionQueries.findIndex( // 2.2 check for a single threshold expression pointing to an "instant" data query
(query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId const isInstantDataQuery = dataQuery ? getInstantFromDataQuery(dataQuery) : false;
); const hasSingleThresholdExpression = expressionQueries.length === 1 && thresholdExpression;
const reduceExpression = expressionQueries.at(reduceExpressionIndex); const thresholdPointingToDataQuery = thresholdExpression?.model.expression === dataQuery?.refId;
const reduceOk =
reduceExpression &&
reduceExpressionIndex === 0 &&
(reduceExpression.model.settings?.mode === ReducerMode.Strict ||
reduceExpression.model.settings?.mode === undefined);
const thresholdExpressionIndex = expressionQueries.findIndex( if (isInstantDataQuery && hasSingleThresholdExpression && validThresholdExpression && thresholdPointingToDataQuery) {
(query) => return true;
query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId }
);
const thresholdExpression = expressionQueries.at(thresholdExpressionIndex); return false;
const conditions = thresholdExpression?.model.conditions ?? [];
const thresholdIndexOk = singleReduceExpressionInInstantQuery
? thresholdExpressionIndex === 0
: thresholdExpressionIndex === 1;
const thresholdOk = thresholdExpression && thresholdIndexOk && conditions[0]?.unloadEvaluator === undefined;
return (Boolean(reduceOk) || Boolean(singleReduceExpressionInInstantQuery)) && Boolean(thresholdOk);
} }
export function isExpressionQueryInAlert( export function isExpressionQueryInAlert(
@@ -1,10 +1,18 @@
import { PromQuery } from '@grafana/prometheus'; import { PromQuery } from '@grafana/prometheus';
import { GrafanaAlertStateDecision, GrafanaRuleDefinition, RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto'; import {
AlertDataQuery,
AlertQuery,
GrafanaAlertStateDecision,
GrafanaRuleDefinition,
RulerAlertingRuleDTO,
} from 'app/types/unified-alerting-dto';
import { mockDataSource } from '../mocks';
import { getDefaultFormValues } from '../rule-editor/formDefaults'; import { getDefaultFormValues } from '../rule-editor/formDefaults';
import { setupDataSources } from '../testSetup/datasources';
import { AlertManagerManualRouting, RuleFormType, RuleFormValues } from '../types/rule-form'; import { AlertManagerManualRouting, RuleFormType, RuleFormValues } from '../types/rule-form';
import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './datasource';
import { import {
alertingRulerRuleToRuleForm, alertingRulerRuleToRuleForm,
cleanAnnotations, cleanAnnotations,
@@ -12,6 +20,7 @@ import {
formValuesToRulerGrafanaRuleDTO, formValuesToRulerGrafanaRuleDTO,
formValuesToRulerRuleDTO, formValuesToRulerRuleDTO,
getContactPointsFromDTO, getContactPointsFromDTO,
getInstantFromDataQuery,
getNotificationSettingsForDTO, getNotificationSettingsForDTO,
} from './rule-form'; } from './rule-form';
@@ -254,3 +263,71 @@ describe('cleanLabels', () => {
expect(output).toStrictEqual([{ key: 'key', value: '' }]); expect(output).toStrictEqual([{ key: 'key', value: '' }]);
}); });
}); });
describe('getInstantFromDataQuery', () => {
const query: AlertQuery<AlertDataQuery> = {
refId: 'Q',
datasourceUid: 'abc123',
queryType: '',
relativeTimeRange: {
from: 600,
to: 0,
},
model: {
refId: 'Q',
},
};
it('should return undefined if datasource UID is undefined', () => {
setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'Mimir-cloud', uid: 'mimir-1' }));
const result = getInstantFromDataQuery({ ...query });
expect(result).toBeUndefined();
});
it('should return undefined if datasource type is not Prometheus or Loki', () => {
setupDataSources(mockDataSource({ type: DataSourceType.Alertmanager, name: 'aa', uid: 'aa-1' }));
const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa' });
expect(result).toBeUndefined();
});
it('should return true if datasource is Prometheus and instant is not defined', () => {
setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'aa', uid: 'aa-1' }));
const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa' });
expect(result).toBe(true);
});
it('should return the value of instant if datasource is Prometheus and instant is defined', () => {
setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'aa', uid: 'aa-1' }));
const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa', model: { refId: 'f', instant: false } });
expect(result).toBe(false);
});
it('should return true if datasource is Loki and queryType is not defined', () => {
setupDataSources(mockDataSource({ type: DataSourceType.Loki, name: 'aa', uid: 'aa-1' }));
const result = getInstantFromDataQuery({ ...query, datasourceUid: 'aa' });
expect(result).toBe(true);
});
it('should return true if datasource is Loki and queryType is instant', () => {
setupDataSources(mockDataSource({ type: DataSourceType.Loki, name: 'aa', uid: 'aa-1' }));
const result = getInstantFromDataQuery({
...query,
datasourceUid: 'aa',
model: { refId: 'f', queryType: 'instant' },
});
expect(result).toBe(true);
});
it('should return false if datasource is Loki and queryType is not instant', () => {
setupDataSources(mockDataSource({ type: DataSourceType.Loki, name: 'aa', uid: 'aa-1' }));
const result = getInstantFromDataQuery({
...query,
datasourceUid: 'aa',
model: { refId: 'f', queryType: 'range' },
});
expect(result).toBe(false);
});
});
@@ -803,19 +803,22 @@ export function isPromOrLokiQuery(model: AlertDataQuery): model is PromOrLokiQue
return 'expr' in model; return 'expr' in model;
} }
export function getInstantFromDataQuery(model: AlertDataQuery, type: string): boolean | undefined { export function getInstantFromDataQuery(query: AlertQuery<AlertDataQuery>): boolean | undefined {
// if the datasource is not prometheus or loki, instant is defined in the model or defaults to undefined const dataSourceUID = query.datasourceUid ?? query.model.datasource?.uid;
if (type !== DataSourceType.Prometheus && type !== DataSourceType.Loki) { if (!dataSourceUID) {
if ('instant' in model) { return undefined;
return model.instant;
} else {
if ('queryType' in model) {
return model.queryType === 'instant';
} else {
return undefined;
}
}
} }
// find the datasource type from the UID
const type = getDataSourceSrv().getInstanceSettings(dataSourceUID)?.type;
// if the datasource is not prometheus or loki, return "undefined"
if (type !== DataSourceType.Prometheus && type !== DataSourceType.Loki) {
return undefined;
}
const { model } = query;
// if the datasource is prometheus or loki, instant is defined in the model, or defaults to true // 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 isInstantForPrometheus = 'instant' in model && model.instant !== undefined ? model.instant : true;
const isInstantForLoki = 'queryType' in model && model.queryType !== undefined ? model.queryType === 'instant' : true; const isInstantForLoki = 'queryType' in model && model.queryType !== undefined ? model.queryType === 'instant' : true;
@@ -2,7 +2,7 @@ import { ReducerID } from '@grafana/data';
import { EvalFunction } from '../../alerting/state/alertDef'; import { EvalFunction } from '../../alerting/state/alertDef';
import { isReducerType } from '../guards'; import { isReducerType } from '../guards';
import { ClassicCondition, ExpressionQuery, ExpressionQueryType, ReducerType } from '../types'; import { ClassicCondition, ExpressionQuery, ExpressionQueryType, ReducerMode, ReducerType } from '../types';
export const getDefaults = (query: ExpressionQuery) => { export const getDefaults = (query: ExpressionQuery) => {
switch (query.type) { switch (query.type) {
@@ -69,3 +69,20 @@ export function getReducerType(value: string): ReducerType | undefined {
} }
return undefined; return undefined;
} }
export function isStrictReducer(expressionModel: ExpressionQuery): boolean {
if (!isReducerExpression(expressionModel)) {
return false;
}
const mode = expressionModel.settings?.mode;
return mode === ReducerMode.Strict || mode === undefined;
}
export function isReducerExpression(expressionModel: ExpressionQuery) {
return expressionModel.type === ExpressionQueryType.reduce;
}
export function isThresholdExpression(expressionModel: ExpressionQuery) {
return expressionModel.type === ExpressionQueryType.threshold;
}