Alerting: Support for `condition` field in /api/v1/eval (#79032)

Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
pull/79158/head
Yuri Tseretyan 1 year ago committed by GitHub
parent 7a38a2e48b
commit 7e331c8507
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      pkg/services/ngalert/api/api_testing.go
  2. 5
      pkg/services/ngalert/api/tooling/definitions/testing.go
  3. 60
      pkg/tests/api/alerting/api_alertmanager_test.go
  4. 23
      public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx
  5. 4
      public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAlertQueryRunner.tsx
  6. 9
      public/app/features/alerting/unified/components/rule-viewer/RuleViewer.v1.tsx
  7. 37
      public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts
  8. 16
      public/app/features/alerting/unified/state/AlertingQueryRunner.ts

@ -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)

@ -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 {

@ -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 {

@ -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
</Button>
)}
{!isPreviewLoading && (
<Button icon="sync" type="button" onClick={runQueriesPreview} disabled={emptyQueries}>
<Button icon="sync" type="button" onClick={() => runQueriesPreview()} disabled={emptyQueries}>
Preview
</Button>
)}

@ -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(() => {

@ -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);

@ -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;

@ -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<Record<string, PanelData>> => {
const runRequest = (
backendSrv: BackendSrv,
queries: AlertQuery[],
condition: string
): Observable<Record<string, PanelData>> => {
const initial = initialState(queries, LoadingState.Loading);
const request = {
data: { data: queries },
data: { data: queries, condition },
url: '/api/v1/eval',
method: 'POST',
requestId: uuidv4(),

Loading…
Cancel
Save