diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx index b3cdcafcc3b..22dc6e98528 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx @@ -17,6 +17,8 @@ import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOp import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; +import { getInstantFromDataQuery } from '../../utils/rule-form'; + import { AlertQueryOptions, EmptyQueryWrapper, QueryWrapper } from './QueryWrapper'; import { errorFromCurrentCondition, errorFromPreviewData, getThresholdsForQueries } from './util'; @@ -234,6 +236,7 @@ function copyModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit } function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit { + const isInstant = getInstantFromDataQuery(item.model, settings.type); return { refId: item.refId, relativeTimeRange: item.relativeTimeRange, @@ -243,6 +246,7 @@ function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit< refId: item.refId, hide: false, datasource: getDataSourceRef(settings), + instant: isInstant, }, }; } diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index 1e66bba204d..768d0152928 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -54,13 +54,7 @@ import { RuleEditorSection } from '../RuleEditorSection'; import { errorFromCurrentCondition, errorFromPreviewData, findRenamedDataQueryReferences, refIdExists } from '../util'; import { CloudDataSourceSelector } from './CloudDataSourceSelector'; -import { - getSimpleConditionFromExpressions, - SIMPLE_CONDITION_QUERY_ID, - SIMPLE_CONDITION_REDUCER_ID, - SIMPLE_CONDITION_THRESHOLD_ID, - SimpleConditionEditor, -} from './SimpleCondition'; +import { getSimpleConditionFromExpressions, SimpleConditionEditor, SimpleConditionIdentifier } from './SimpleCondition'; import { SmartAlertTypeDetector } from './SmartAlertTypeDetector'; import { DESCRIPTIONS } from './descriptions'; import { @@ -68,6 +62,7 @@ import { addNewDataQuery, addNewExpression, duplicateQuery, + optimizeReduceExpression, queriesAndExpressionsReducer, removeExpression, removeExpressions, @@ -90,19 +85,21 @@ export function areQueriesTransformableToSimpleCondition( if (dataQueries.length !== 1) { return false; } + const singleReduceExpressionInInstantQuery = + 'instant' in dataQueries[0].model && dataQueries[0].model.instant && expressionQueries.length === 1; - if (expressionQueries.length !== 2) { + if (expressionQueries.length !== 2 && !singleReduceExpressionInInstantQuery) { return false; } const query = dataQueries[0]; - if (query.refId !== SIMPLE_CONDITION_QUERY_ID) { + if (query.refId !== SimpleConditionIdentifier.queryId) { return false; } const reduceExpressionIndex = expressionQueries.findIndex( - (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SIMPLE_CONDITION_REDUCER_ID + (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId ); const reduceExpression = expressionQueries.at(reduceExpressionIndex); const reduceOk = @@ -112,13 +109,16 @@ export function areQueriesTransformableToSimpleCondition( reduceExpression.model.settings?.mode === undefined); const thresholdExpressionIndex = expressionQueries.findIndex( - (query) => query.model.type === ExpressionQueryType.threshold && query.refId === SIMPLE_CONDITION_THRESHOLD_ID + (query) => + query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId ); const thresholdExpression = expressionQueries.at(thresholdExpressionIndex); const conditions = thresholdExpression?.model.conditions ?? []; - const thresholdOk = - thresholdExpression && thresholdExpressionIndex === 1 && conditions[0]?.unloadEvaluator === undefined; - return Boolean(reduceOk) && Boolean(thresholdOk); + const thresholdIndexOk = singleReduceExpressionInInstantQuery + ? thresholdExpressionIndex === 0 + : thresholdExpressionIndex === 1; + const thresholdOk = thresholdExpression && thresholdIndexOk && conditions[0]?.unloadEvaluator === undefined; + return (Boolean(reduceOk) || Boolean(singleReduceExpressionInInstantQuery)) && Boolean(thresholdOk); } interface Props { @@ -200,8 +200,8 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P } // we need to be sure the condition is set once we switch to simple mode if (!isAdvancedMode) { - setValue('condition', SIMPLE_CONDITION_THRESHOLD_ID); - runQueries(getValues('queries'), SIMPLE_CONDITION_THRESHOLD_ID); + setValue('condition', SimpleConditionIdentifier.thresholdId); + runQueries(getValues('queries'), SimpleConditionIdentifier.thresholdId); } else { runQueries(getValues('queries'), condition || (getValues('condition') ?? '')); } @@ -285,6 +285,12 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P setValue('queries', [...updatedQueries, ...expressionQueries], { shouldValidate: false }); updateExpressionAndDatasource(updatedQueries); + // we only remove or add the reducer(optimize reducer) expression when creating a new alert. + // When editing an alert, we assume the user wants to manually adjust expressions and queries for more control and customization. + if (!editingExistingRule) { + dispatch(optimizeReduceExpression({ updatedQueries, expressionQueries })); + } + dispatch(setDataQueries(updatedQueries)); dispatch(updateExpressionTimeRange()); @@ -294,7 +300,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P dispatch(rewireExpressions({ oldRefId, newRefId })); } }, - [queries, updateExpressionAndDatasource, getValues, setValue] + [queries, updateExpressionAndDatasource, getValues, setValue, editingExistingRule] ); const onChangeRecordingRulesQueries = useCallback( diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx index 8d092e149f6..acd8402694d 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx @@ -17,12 +17,13 @@ import { ExpressionResult } from '../../expressions/Expression'; import { updateExpression } from './reducer'; -export const SIMPLE_CONDITION_QUERY_ID = 'A'; -export const SIMPLE_CONDITION_REDUCER_ID = 'B'; -export const SIMPLE_CONDITION_THRESHOLD_ID = 'C'; - +export const SimpleConditionIdentifier = { + queryId: 'A', + reducerId: 'B', + thresholdId: 'C', +} as const; export interface SimpleCondition { - whenField: string; + whenField?: string; evaluator: { params: number[]; type: EvalFunction; @@ -106,15 +107,17 @@ export const SimpleConditionEditor = ({ - - o.value === simpleCondition.whenField)} + onChange={onReducerTypeChange} + width={20} + /> + + )} + {isRange ? ( @@ -156,7 +159,8 @@ function updateReduceExpression( dispatch: Dispatch ) { const reduceExpression = expressionQueriesList.find( - (query) => query.model.type === ExpressionQueryType.reduce && query.model.refId === SIMPLE_CONDITION_REDUCER_ID + (query) => + query.model.type === ExpressionQueryType.reduce && query.model.refId === SimpleConditionIdentifier.reducerId ); const newReduceExpression = reduceExpression @@ -176,7 +180,8 @@ function updateThresholdFunction( dispatch: Dispatch ) { const thresholdExpression = expressionQueriesList.find( - (query) => query.model.type === ExpressionQueryType.threshold && query.model.refId === SIMPLE_CONDITION_THRESHOLD_ID + (query) => + query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId ); const newThresholdExpression = produce(thresholdExpression, (draft) => { @@ -194,7 +199,8 @@ function updateThresholdValue( dispatch: Dispatch ) { const thresholdExpression = expressionQueriesList.find( - (query) => query.model.type === ExpressionQueryType.threshold && query.model.refId === SIMPLE_CONDITION_THRESHOLD_ID + (query) => + query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId ); const newThresholdExpression = produce(thresholdExpression, (draft) => { @@ -207,13 +213,14 @@ function updateThresholdValue( export function getSimpleConditionFromExpressions(expressions: Array>): SimpleCondition { const reduceExpression = expressions.find( - (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SIMPLE_CONDITION_REDUCER_ID + (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId ); const thresholdExpression = expressions.find( - (query) => query.model.type === ExpressionQueryType.threshold && query.refId === SIMPLE_CONDITION_THRESHOLD_ID + (query) => + query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId ); const conditionsFromThreshold = thresholdExpression?.model.conditions ?? []; - const whenField = reduceExpression?.model.reducer ?? ReducerID.last; + const whenField = reduceExpression?.model.reducer; const params = conditionsFromThreshold[0]?.evaluator?.params ? [...conditionsFromThreshold[0]?.evaluator?.params] : [0]; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts index 363c88c6eab..208342735e6 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts @@ -1,5 +1,3 @@ -// QueryAndExpressionsStep.test.tsx - import { produce } from 'immer'; import { EvalFunction } from 'app/features/alerting/state/alertDef'; @@ -24,7 +22,7 @@ describe('areQueriesTransformableToSimpleCondition', () => { expect(result).toBe(false); }); - it('should return false if the dataQuery refId does not match SIMPLE_CONDITION_QUERY_ID', () => { + it('should return false if the dataQuery refId does not match SimpleConditionIdentifier.queryId', () => { const dataQueries: Array> = [ { refId: 'notSimpleCondition', datasourceUid: 'abc123', queryType: '', model: { refId: 'notSimpleCondition' } }, ]; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap index 13caf2d9ca4..a316611bf55 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap @@ -86,6 +86,70 @@ exports[`Query and expressions reducer should add query 1`] = ` } `; +exports[`Query and expressions reducer should add reduce expression if there is no reduce expression and the query is not instant 1`] = ` +{ + "queries": [ + { + "datasourceUid": "abc123", + "model": { + "instant": false, + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0, + ], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [], + }, + "reducer": { + "params": [], + "type": "avg", + }, + "type": "query", + }, + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "B", + "type": "reduce", + }, + "queryType": "expression", + "refId": "B", + }, + { + "datasourceUid": "__expr__", + "model": { + "expression": "B", + "refId": "C", + "type": "threshold", + }, + "queryType": "expression", + "refId": "C", + }, + ], +} +`; + exports[`Query and expressions reducer should duplicate query 1`] = ` { "queries": [ @@ -129,6 +193,31 @@ exports[`Query and expressions reducer should remove an expression or alert quer } `; +exports[`Query and expressions reducer should remove first reducer 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`] = ` { "queries": [ diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx index ba5f3dcfd7a..a3694e582fc 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx @@ -1,14 +1,21 @@ import { getDefaultRelativeTimeRange, RelativeTimeRange } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime/src/services/__mocks__/dataSourceSrv'; import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; -import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; +import { + ExpressionDatasourceUID, + ExpressionQuery, + ExpressionQueryType, + ReducerMode, +} from 'app/features/expressions/types'; import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; import { AlertQuery } from 'app/types/unified-alerting-dto'; +import { SimpleConditionIdentifier } from './SimpleCondition'; import { addNewDataQuery, addNewExpression, duplicateQuery, + optimizeReduceExpression, queriesAndExpressionsReducer, QueriesAndExpressionsState, removeExpression, @@ -20,6 +27,26 @@ import { updateExpressionType, } from './reducer'; +const reduceExpression: AlertQuery = { + refId: SimpleConditionIdentifier.reducerId, + queryType: 'expression', + datasourceUid: '__expr__', + model: { + type: ExpressionQueryType.reduce, + refId: SimpleConditionIdentifier.reducerId, + settings: { mode: ReducerMode.Strict }, + }, +}; +const thresholdExpression: AlertQuery = { + refId: SimpleConditionIdentifier.thresholdId, + queryType: 'expression', + datasourceUid: '__expr__', + model: { + type: ExpressionQueryType.threshold, + refId: SimpleConditionIdentifier.thresholdId, + }, +}; + jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getDataSourceSrv: getDataSourceSrv, @@ -358,4 +385,70 @@ describe('Query and expressions reducer', () => { expect(newState).toMatchSnapshot(); }); + it('should remove first reducer', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, reduceExpression, thresholdExpression], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + optimizeReduceExpression({ + updatedQueries: [alertQuery], + expressionQueries: [reduceExpression, thresholdExpression], + }) + ); + expect(newState).toMatchSnapshot(); + }); + + it('should not remove first reducer if reducer is not the first expression', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, thresholdExpression, reduceExpression], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + optimizeReduceExpression({ + updatedQueries: [alertQuery], + expressionQueries: [thresholdExpression, reduceExpression], + }) + ); + expect(newState).toEqual(initialState); + }); + + it('should not remove first reducer if reducer is not the second query', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, alertQuery, reduceExpression, thresholdExpression], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + optimizeReduceExpression({ + updatedQueries: [alertQuery, alertQuery], + expressionQueries: [reduceExpression, thresholdExpression], + }) + ); + expect(newState).toEqual(initialState); + }); + + it('should add reduce expression if there is no reduce expression and the query is not instant', () => { + const alertQuery: AlertQuery = { + refId: 'A', + queryType: 'query', + datasourceUid: 'abc123', + model: { + refId: 'A', + instant: false, + }, + }; + + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, thresholdExpression], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + optimizeReduceExpression({ updatedQueries: [alertQuery], expressionQueries: [thresholdExpression] }) + ); + expect(newState).toMatchSnapshot(); + }); }); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts index e1e7f66092b..00142d111f8 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts @@ -6,8 +6,10 @@ import { getDefaultRelativeTimeRange, getNextRefId, rangeUtil, + ReducerID, RelativeTimeRange, } from '@grafana/data'; +import { getDataSourceSrv } from '@grafana/runtime'; import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; import { isExpressionQuery } from 'app/features/expressions/guards'; import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; @@ -15,11 +17,13 @@ import { defaultCondition } from 'app/features/expressions/utils/expressionTypes import { AlertQuery } from 'app/types/unified-alerting-dto'; import { logError } from '../../../Analytics'; -import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; -import { getDefaultQueries } from '../../../utils/rule-form'; +import { DataSourceType, getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; +import { getDefaultQueries, getInstantFromDataQuery } from '../../../utils/rule-form'; import { createDagFromQueries, getOriginOfRefId } from '../dag'; import { queriesWithUpdatedReferences, refIdExists } from '../util'; +import { SimpleConditionIdentifier } from './SimpleCondition'; + export interface QueriesAndExpressionsState { queries: AlertQuery[]; } @@ -60,7 +64,9 @@ export const updateMaxDataPoints = createAction<{ refId: string; maxDataPoints: export const updateMinInterval = createAction<{ refId: string; minInterval: string }>('updateMinInterval'); export const resetToSimpleCondition = createAction('resetToSimpleCondition'); - +export const optimizeReduceExpression = createAction<{ updatedQueries: AlertQuery[]; expressionQueries: AlertQuery[] }>( + 'optimizeReduceExpression' +); export const setRecordingRulesQueries = createAction<{ recordingRuleQueries: AlertQuery[]; expression: string }>( 'setRecordingRulesQueries' ); @@ -225,6 +231,71 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder .addCase(rewireExpressions, (state, { payload }) => { state.queries = queriesWithUpdatedReferences(state.queries, payload.oldRefId, payload.newRefId); }) + .addCase(optimizeReduceExpression, (state, { payload }) => { + const { updatedQueries, expressionQueries } = payload; + + if (updatedQueries.length !== 1) { + // we only optimize when we have one data query + return; + } + + //sometimes we dont have data source in the model yet + const getDataSourceSettingsForFirstQuery = getDataSourceSrv().getInstanceSettings( + 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; + + if (shouldRemoveReducer) { + const reduceExpressionIndex = state.queries.findIndex( + (query) => isExpressionQuery(query.model) && query.model.type === ExpressionQueryType.reduce + ); + + if (reduceExpressionIndex === 1) { + // means the reduce expression is the second query + state.queries.splice(reduceExpressionIndex, 1); + state.queries[1].model.expression = SimpleConditionIdentifier.queryId; + } + } + if (shouldAddReduceExpression) { + // add reducer to the second position + // we only update the refid and the model to point to the reducer expression + state.queries[1].model.expression = SimpleConditionIdentifier.reducerId; + // insert in second position the reducer expression + state.queries.splice(1, 0, { + datasourceUid: ExpressionDatasourceUID, + model: expressionDatasource.newQuery({ + type: ExpressionQueryType.reduce, + reducer: ReducerID.last, + conditions: [{ ...defaultCondition, query: { params: [] } }], + expression: SimpleConditionIdentifier.queryId, + refId: SimpleConditionIdentifier.reducerId, + }), + refId: SimpleConditionIdentifier.reducerId, + queryType: 'expression', + }); + } + }) .addCase(updateExpressionType, (state, action) => { state.queries = state.queries.map((query) => { return query.refId === action.payload.refId diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 0564b60507a..806e13b9a01 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -64,11 +64,7 @@ import { import { DashboardSearchItem, DashboardSearchItemType } from '../../search/types'; -import { - SIMPLE_CONDITION_QUERY_ID, - SIMPLE_CONDITION_REDUCER_ID, - SIMPLE_CONDITION_THRESHOLD_ID, -} from './components/rule-editor/query-and-alert-condition/SimpleCondition'; +import { SimpleConditionIdentifier } from './components/rule-editor/query-and-alert-condition/SimpleCondition'; import { parsePromQLStyleMatcherLooseSafe } from './utils/matchers'; let nextDataSourceId = 1; @@ -855,29 +851,29 @@ export function mockDashboardDto( } export const dataQuery: AlertQuery = { - refId: SIMPLE_CONDITION_QUERY_ID, + refId: SimpleConditionIdentifier.queryId, datasourceUid: 'abc123', queryType: '', - model: { refId: SIMPLE_CONDITION_QUERY_ID }, + model: { refId: SimpleConditionIdentifier.queryId }, }; export const reduceExpression: AlertQuery = { - refId: SIMPLE_CONDITION_REDUCER_ID, + refId: SimpleConditionIdentifier.reducerId, queryType: 'expression', datasourceUid: '__expr__', model: { type: ExpressionQueryType.reduce, - refId: SIMPLE_CONDITION_REDUCER_ID, + refId: SimpleConditionIdentifier.reducerId, settings: { mode: ReducerMode.Strict }, reducer: ReducerID.last, }, }; export const thresholdExpression: AlertQuery = { - refId: SIMPLE_CONDITION_THRESHOLD_ID, + refId: SimpleConditionIdentifier.thresholdId, queryType: 'expression', datasourceUid: '__expr__', model: { type: ExpressionQueryType.threshold, - refId: SIMPLE_CONDITION_THRESHOLD_ID, + refId: SimpleConditionIdentifier.thresholdId, }, }; diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index 8fb355423c5..2879b2f239c 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -53,7 +53,12 @@ import { import { getRulesAccess } from './access-control'; import { Annotation, defaultAnnotations } from './constants'; -import { getDefaultOrFirstCompatibleDataSource, GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } from './datasource'; +import { + DataSourceType, + getDefaultOrFirstCompatibleDataSource, + GRAFANA_RULES_SOURCE_NAME, + isGrafanaRulesSource, +} from './datasource'; import { arrayToRecord, recordToArray } from './misc'; import { isAlertingRulerRule, @@ -499,7 +504,6 @@ export function recordingRulerRuleToRuleForm( export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => { const dataSource = getDefaultOrFirstCompatibleDataSource(); - if (!dataSource) { const expressions = isRecordingRule ? getDefaultExpressionsForRecording('A') : getDefaultExpressions('A', 'B'); return [...expressions]; @@ -507,6 +511,7 @@ export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => { const relativeTimeRange = getDefaultRelativeTimeRange(); const expressions = isRecordingRule ? getDefaultExpressionsForRecording('B') : getDefaultExpressions('B', 'C'); + const isLokiOrPrometheus = dataSource?.type === DataSourceType.Prometheus || dataSource?.type === DataSourceType.Loki; return [ { refId: 'A', @@ -515,6 +520,7 @@ export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => { relativeTimeRange, model: { refId: 'A', + instant: isLokiOrPrometheus ? true : undefined, }, }, ...expressions, @@ -899,3 +905,24 @@ export const ignoreHiddenQueries = (ruleDefinition: RuleFormValues): RuleFormVal export function formValuesFromExistingRule(rule: RuleWithLocation) { return ignoreHiddenQueries(rulerRuleToFormValues(rule)); } + +export function getInstantFromDataQuery(model: AlertDataQuery, type: string): boolean | undefined { + // if the datasource is not prometheus or loki, instant is defined in the model or defaults to undefined + if (type !== DataSourceType.Prometheus && type !== DataSourceType.Loki) { + if ('instant' in model) { + return model.instant; + } else { + if ('queryType' in model) { + return model.queryType === 'instant'; + } else { + return undefined; + } + } + } + // 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 isInstant = type === DataSourceType.Prometheus ? isInstantForPrometheus : isInstantForLoki; + return isInstant; +}