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

Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
This commit is contained in:
Yuri Tseretyan
2023-12-06 11:28:43 -05:00
committed by GitHub
co-authored by Sonia Aguilar
parent 7a38a2e48b
commit 7e331c8507
8 changed files with 117 additions and 43 deletions
+3 -3
View File
@@ -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(),