diff --git a/pkg/services/ngalert/api/api_testing.go b/pkg/services/ngalert/api/api_testing.go index fa9b945dd46..0207a16c2f9 100644 --- a/pkg/services/ngalert/api/api_testing.go +++ b/pkg/services/ngalert/api/api_testing.go @@ -156,11 +156,11 @@ func (srv TestingApiSrv) RouteEvalQueries(c *contextmodel.ReqContext, cmd apimod } cond := ngmodels.Condition{ - Condition: "", + Condition: cmd.Condition, Data: queries, } - if len(cmd.Data) > 0 { - cond.Condition = cmd.Data[0].RefID + if cond.Condition == "" && len(cond.Data) > 0 { + cond.Condition = cond.Data[len(cond.Data)-1].RefID } _, err := store.OptimizeAlertQueries(cond.Data) diff --git a/pkg/services/ngalert/api/tooling/definitions/testing.go b/pkg/services/ngalert/api/tooling/definitions/testing.go index e5c3265dbab..90c304434e1 100644 --- a/pkg/services/ngalert/api/tooling/definitions/testing.go +++ b/pkg/services/ngalert/api/tooling/definitions/testing.go @@ -128,8 +128,9 @@ type EvalQueriesRequest struct { // swagger:model type EvalQueriesPayload struct { - Data []AlertQuery `json:"data"` - Now time.Time `json:"now"` + Condition string `json:"condition"` + Data []AlertQuery `json:"data"` + Now time.Time `json:"now"` } func (p *TestRulePayload) UnmarshalJSON(b []byte) error { diff --git a/pkg/tests/api/alerting/api_alertmanager_test.go b/pkg/tests/api/alerting/api_alertmanager_test.go index ae803302e5e..878362a118b 100644 --- a/pkg/tests/api/alerting/api_alertmanager_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_test.go @@ -2164,6 +2164,7 @@ func TestIntegrationEval(t *testing.T) { } } ], + "condition": "A", "now": "2021-04-11T14:38:14Z" } `, @@ -2221,6 +2222,7 @@ func TestIntegrationEval(t *testing.T) { } } ], + "condition": "A", "now": "2021-04-11T14:38:14Z" } `, @@ -2276,6 +2278,7 @@ func TestIntegrationEval(t *testing.T) { } } ], + "condition": "A", "now": "2021-04-11T14:38:14Z" } `, @@ -2293,6 +2296,63 @@ func TestIntegrationEval(t *testing.T) { return "Failed to build evaluator for queries and expressions: failed to build query 'A': data source not found" }, }, + { + desc: "condition is empty", + payload: ` + { + "data": [ + { + "refId": "A", + "relativeTimeRange": { + "from": 18000, + "to": 10800 + }, + "datasourceUid": "__expr__", + "model": { + "type":"math", + "expression":"1 > 2" + } + } + ], + "now": "2021-04-11T14:38:14Z" + } + `, + expectedStatusCode: func() int { return http.StatusOK }, + expectedMessage: func() string { return "" }, + expectedResponse: func() string { + return `{ + "results": { + "A": { + "status": 200, + "frames": [ + { + "schema": { + "refId": "A", + "fields": [ + { + "name": "A", + "type": "number", + "typeInfo": { + "frame": "float64", + "nullable": true + } + } + ] + }, + "data": { + "values": [ + [ + 0 + ] + ] + } + } + ] + } + } + }` + }, + }, } for _, tc := range testCases { 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 a77726cda35..27fb0a39cf5 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 @@ -92,15 +92,18 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P const rulesSourcesWithRuler = useRulesSourcesWithRuler(); - const runQueriesPreview = useCallback(() => { - if (isCloudAlertRuleType) { - // we will skip preview for cloud rules, these do not have any time series preview - // Grafana Managed rules and recording rules do - return; - } + const runQueriesPreview = useCallback( + (condition?: string) => { + if (isCloudAlertRuleType) { + // we will skip preview for cloud rules, these do not have any time series preview + // Grafana Managed rules and recording rules do + return; + } - runQueries(getValues('queries')); - }, [isCloudAlertRuleType, runQueries, getValues]); + runQueries(getValues('queries'), condition || (getValues('condition') ?? '')); + }, + [isCloudAlertRuleType, runQueries, getValues] + ); // whenever we update the queries we have to update the form too useEffect(() => { @@ -149,7 +152,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P return; } - runQueriesPreview(); //we need to run the queries to know if the condition is valid + runQueriesPreview(refId); //we need to run the queries to know if the condition is valid setValue('condition', refId); }, @@ -504,7 +507,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P )} {!isPreviewLoading && ( - )} diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAlertQueryRunner.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAlertQueryRunner.tsx index de0dbbc19ee..1b5f0f4da9b 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAlertQueryRunner.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAlertQueryRunner.tsx @@ -30,8 +30,8 @@ export function useAlertQueryRunner() { runner.current.cancel(); }, []); - const runQueries = useCallback((queriesToPreview: AlertQuery[]) => { - runner.current.run(queriesToPreview); + const runQueries = useCallback((queriesToPreview: AlertQuery[], condition: string) => { + runner.current.run(queriesToPreview, condition); }, []); const isPreviewLoading = useMemo(() => { diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.v1.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.v1.tsx index 4861a87a1f5..2073cbdd334 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.v1.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.v1.tsx @@ -78,10 +78,13 @@ export function RuleViewer({ match }: RuleViewerProps) { ...q, relativeTimeRange: evaluationTimeRanges[q.refId] ?? q.relativeTimeRange, })); - - runner.run(evalCustomizedQueries); + let condition; + if (rule && isGrafanaRulerRule(rule.rulerRule)) { + condition = rule.rulerRule.grafana_alert.condition; + } + runner.run(evalCustomizedQueries, condition ?? 'A'); } - }, [queries, evaluationTimeRanges, runner, allDataSourcesAvailable]); + }, [queries, evaluationTimeRanges, runner, allDataSourcesAvailable, rule]); useEffect(() => { const alertQueries = alertRuleToQueries(rule); diff --git a/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts b/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts index 5177063eaf0..a4dc6e5f576 100644 --- a/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts +++ b/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts @@ -6,14 +6,14 @@ import { createFetchResponse } from 'test/helpers/createFetchResponse'; import { DataFrame, DataFrameJSON, + DataSourceInstanceSettings, Field, FieldType, - getDefaultRelativeTimeRange, LoadingState, + getDefaultRelativeTimeRange, rangeUtil, - DataSourceInstanceSettings, } from '@grafana/data'; -import { DataSourceSrv, FetchResponse, DataSourceWithBackend } from '@grafana/runtime'; +import { DataSourceSrv, DataSourceWithBackend, FetchResponse } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { BackendSrv } from 'app/core/services/backend_srv'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; @@ -37,7 +37,7 @@ describe('AlertingQueryRunner', () => { ); const data = runner.get(); - runner.run([createQuery('A'), createQuery('B')]); + runner.run([createQuery('A'), createQuery('B')], 'B'); await expect(data.pipe(take(1))).toEmitValuesWith((values) => { const [data] = values; @@ -94,7 +94,7 @@ describe('AlertingQueryRunner', () => { ); const data = runner.get(); - runner.run([createQuery('A'), createQuery('B')]); + runner.run([createQuery('A'), createQuery('B')], 'B'); await expect(data.pipe(take(1))).toEmitValuesWith((values) => { const [data] = values; @@ -126,7 +126,7 @@ describe('AlertingQueryRunner', () => { ); const data = runner.get(); - runner.run([createQuery('A'), createQuery('B')]); + runner.run([createQuery('A'), createQuery('B')], 'B'); await expect(data.pipe(take(2))).toEmitValuesWith((values) => { const [loading, data] = values; @@ -181,7 +181,7 @@ describe('AlertingQueryRunner', () => { ); const data = runner.get(); - runner.run([createQuery('A'), createQuery('B')]); + runner.run([createQuery('A'), createQuery('B')], 'B'); await expect(data.pipe(take(1))).toEmitValuesWith((values) => { const [data] = values; @@ -203,7 +203,7 @@ describe('AlertingQueryRunner', () => { ); const data = runner.get(); - runner.run([createQuery('A'), createQuery('B')]); + runner.run([createQuery('A'), createQuery('B')], 'B'); await expect(data.pipe(take(1))).toEmitValuesWith((values) => { const [data] = values; @@ -231,15 +231,18 @@ describe('AlertingQueryRunner', () => { ); const data = runner.get(); - runner.run([ - createQuery('A', { - model: { - refId: 'A', - hide: true, - }, - }), - createQuery('B'), - ]); + runner.run( + [ + createQuery('A', { + model: { + refId: 'A', + hide: true, + }, + }), + createQuery('B'), + ], + 'B' + ); await expect(data.pipe(take(1))).toEmitValuesWith((values) => { const [loading, _data] = values; diff --git a/public/app/features/alerting/unified/state/AlertingQueryRunner.ts b/public/app/features/alerting/unified/state/AlertingQueryRunner.ts index 45950f25202..c92feb7c4a3 100644 --- a/public/app/features/alerting/unified/state/AlertingQueryRunner.ts +++ b/public/app/features/alerting/unified/state/AlertingQueryRunner.ts @@ -9,12 +9,12 @@ import { getDefaultTimeRange, LoadingState, PanelData, + preProcessPanelData, rangeUtil, TimeRange, withLoadingIndicator, - preProcessPanelData, } from '@grafana/data'; -import { FetchResponse, getDataSourceSrv, toDataQueryError, DataSourceWithBackend } from '@grafana/runtime'; +import { DataSourceWithBackend, FetchResponse, getDataSourceSrv, toDataQueryError } from '@grafana/runtime'; import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; import { isExpressionQuery } from 'app/features/expressions/guards'; import { cancelNetworkRequestsOnUnsubscribe } from 'app/features/query/state/processing/canceler'; @@ -49,7 +49,7 @@ export class AlertingQueryRunner { return this.subject.asObservable(); } - async run(queries: AlertQuery[]) { + async run(queries: AlertQuery[], condition: string) { const empty = initialState(queries, LoadingState.Done); const queriesToExclude: string[] = []; @@ -79,7 +79,7 @@ export class AlertingQueryRunner { return this.subject.next(empty); } - this.subscription = runRequest(this.backendSrv, queriesToRun).subscribe({ + this.subscription = runRequest(this.backendSrv, queriesToRun, condition).subscribe({ next: (dataPerQuery) => { const nextResult = applyChange(dataPerQuery, (refId, data) => { const previous = this.lastResult[refId]; @@ -131,10 +131,14 @@ export class AlertingQueryRunner { } } -const runRequest = (backendSrv: BackendSrv, queries: AlertQuery[]): Observable> => { +const runRequest = ( + backendSrv: BackendSrv, + queries: AlertQuery[], + condition: string +): Observable> => { const initial = initialState(queries, LoadingState.Loading); const request = { - data: { data: queries }, + data: { data: queries, condition }, url: '/api/v1/eval', method: 'POST', requestId: uuidv4(),