Alerting: Support for condition field in /api/v1/eval (#79032)
Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
This commit is contained in:
co-authored by
Sonia Aguilar
parent
7a38a2e48b
commit
7e331c8507
@@ -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 {
|
||||
|
||||
+13
-10
@@ -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>
|
||||
)}
|
||||
|
||||
+2
-2
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user