mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: added possibility to preview grafana managed alert rules. (#34600)
* starting to add eval logic. * wip * first version of test rule. * reverted file. * add info colum to result to show error or (with CC evalmatches) * fix labels in evalmatch * fix be test * refactored using observables. * moved widht/height div to outside panel rendere. * adding docs api level. * adding container styles to error div. * increasing size of preview. Co-authored-by: kyle <kyle@grafana.com>
This commit is contained in:
parent
42f33630c7
commit
e19b3df1a9
@ -20,3 +20,4 @@ export { DataLinkBuiltInVars, mapInternalLinkToExplore } from './dataLinks';
|
|||||||
export { DocsId } from './docs';
|
export { DocsId } from './docs';
|
||||||
export { makeClassES5Compatible } from './makeClassES5Compatible';
|
export { makeClassES5Compatible } from './makeClassES5Compatible';
|
||||||
export { anyToNumber } from './anyToNumber';
|
export { anyToNumber } from './anyToNumber';
|
||||||
|
export { withLoadingIndicator, WithLoadingIndicatorOptions } from './withLoadingIndicator';
|
||||||
|
17
packages/grafana-data/src/utils/withLoadingIndicator.ts
Normal file
17
packages/grafana-data/src/utils/withLoadingIndicator.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { merge, Observable, timer } from 'rxjs';
|
||||||
|
import { mapTo, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export type WithLoadingIndicatorOptions<T> = {
|
||||||
|
whileLoading: T;
|
||||||
|
source: Observable<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function withLoadingIndicator<T>({ whileLoading, source }: WithLoadingIndicatorOptions<T>): Observable<T> {
|
||||||
|
return merge(timer(200).pipe(mapTo(whileLoading), takeUntil(source)), source);
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
"github.com/grafana/grafana/pkg/expr/mathexp"
|
"github.com/grafana/grafana/pkg/expr/mathexp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
ptr "github.com/xorcare/pointer"
|
ptr "github.com/xorcare/pointer"
|
||||||
@ -202,7 +203,7 @@ func TestConditionsCmdExecute(t *testing.T) {
|
|||||||
vars: mathexp.Vars{
|
vars: mathexp.Vars{
|
||||||
"A": mathexp.Results{
|
"A": mathexp.Results{
|
||||||
Values: []mathexp.Value{
|
Values: []mathexp.Value{
|
||||||
valBasedSeries(ptr.Float64(30), ptr.Float64(40)),
|
valBasedSeriesWithLabels(data.Labels{"h": "1"}, ptr.Float64(30), ptr.Float64(40)),
|
||||||
valBasedSeries(ptr.Float64(0), ptr.Float64(10)),
|
valBasedSeries(ptr.Float64(0), ptr.Float64(10)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -218,7 +219,7 @@ func TestConditionsCmdExecute(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
resultNumber: func() mathexp.Number {
|
resultNumber: func() mathexp.Number {
|
||||||
v := valBasedNumber(ptr.Float64(1))
|
v := valBasedNumber(ptr.Float64(1))
|
||||||
v.SetMeta([]EvalMatch{{Value: ptr.Float64(35)}})
|
v.SetMeta([]EvalMatch{{Value: ptr.Float64(35), Labels: data.Labels{"h": "1"}}})
|
||||||
return v
|
return v
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,11 @@ func (cr classicReducer) ValidReduceFunc() bool {
|
|||||||
//nolint: gocyclo
|
//nolint: gocyclo
|
||||||
func (cr classicReducer) Reduce(series mathexp.Series) mathexp.Number {
|
func (cr classicReducer) Reduce(series mathexp.Series) mathexp.Number {
|
||||||
num := mathexp.NewNumber("", nil)
|
num := mathexp.NewNumber("", nil)
|
||||||
|
|
||||||
|
if series.GetLabels() != nil {
|
||||||
|
num.SetLabels(series.GetLabels().Copy())
|
||||||
|
}
|
||||||
|
|
||||||
num.SetValue(nil)
|
num.SetValue(nil)
|
||||||
|
|
||||||
if series.Len() == 0 {
|
if series.Len() == 0 {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
"github.com/grafana/grafana/pkg/expr/mathexp"
|
"github.com/grafana/grafana/pkg/expr/mathexp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
ptr "github.com/xorcare/pointer"
|
ptr "github.com/xorcare/pointer"
|
||||||
@ -408,6 +409,17 @@ func valBasedSeries(vals ...*float64) mathexp.Series {
|
|||||||
return newSeries
|
return newSeries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func valBasedSeriesWithLabels(l data.Labels, vals ...*float64) mathexp.Series {
|
||||||
|
newSeries := mathexp.NewSeries("", l, 0, false, 1, true, len(vals))
|
||||||
|
for idx, f := range vals {
|
||||||
|
err := newSeries.SetPoint(idx, unixTimePointer(int64(idx)), f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newSeries
|
||||||
|
}
|
||||||
|
|
||||||
func unixTimePointer(sec int64) *time.Time {
|
func unixTimePointer(sec int64) *time.Time {
|
||||||
t := time.Unix(sec, 0)
|
t := time.Unix(sec, 0)
|
||||||
return &t
|
return &t
|
||||||
|
@ -331,12 +331,21 @@ func (evalResults Results) AsDataFrame() data.Frame {
|
|||||||
frame.Fields = append(frame.Fields, data.NewField(lKey, nil, make([]string, fieldLen)))
|
frame.Fields = append(frame.Fields, data.NewField(lKey, nil, make([]string, fieldLen)))
|
||||||
}
|
}
|
||||||
frame.Fields = append(frame.Fields, data.NewField("State", nil, make([]string, fieldLen)))
|
frame.Fields = append(frame.Fields, data.NewField("State", nil, make([]string, fieldLen)))
|
||||||
|
frame.Fields = append(frame.Fields, data.NewField("Info", nil, make([]string, fieldLen)))
|
||||||
|
|
||||||
for evalIdx, evalResult := range evalResults {
|
for evalIdx, evalResult := range evalResults {
|
||||||
for lIdx, v := range labelColumns {
|
for lIdx, v := range labelColumns {
|
||||||
frame.Set(lIdx, evalIdx, evalResult.Instance[v])
|
frame.Set(lIdx, evalIdx, evalResult.Instance[v])
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.Set(len(labelColumns), evalIdx, evalResult.State.String())
|
frame.Set(len(labelColumns), evalIdx, evalResult.State.String())
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case evalResult.Error != nil:
|
||||||
|
frame.Set(len(labelColumns)+1, evalIdx, evalResult.Error.Error())
|
||||||
|
case evalResult.EvaluationString != "":
|
||||||
|
frame.Set(len(labelColumns)+1, evalIdx, evalResult.EvaluationString)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return *frame
|
return *frame
|
||||||
}
|
}
|
||||||
|
@ -1597,6 +1597,13 @@ func TestEval(t *testing.T) {
|
|||||||
"typeInfo": {
|
"typeInfo": {
|
||||||
"frame": "string"
|
"frame": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Info",
|
||||||
|
"type": "string",
|
||||||
|
"typeInfo": {
|
||||||
|
"frame": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1604,6 +1611,9 @@ func TestEval(t *testing.T) {
|
|||||||
"values": [
|
"values": [
|
||||||
[
|
[
|
||||||
"Alerting"
|
"Alerting"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1648,6 +1658,13 @@ func TestEval(t *testing.T) {
|
|||||||
"typeInfo": {
|
"typeInfo": {
|
||||||
"frame": "string"
|
"frame": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Info",
|
||||||
|
"type": "string",
|
||||||
|
"typeInfo": {
|
||||||
|
"frame": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1655,6 +1672,9 @@ func TestEval(t *testing.T) {
|
|||||||
"values": [
|
"values": [
|
||||||
[
|
[
|
||||||
"Normal"
|
"Normal"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
83
public/app/features/alerting/unified/api/preview.ts
Normal file
83
public/app/features/alerting/unified/api/preview.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
dataFrameFromJSON,
|
||||||
|
DataFrameJSON,
|
||||||
|
getDefaultTimeRange,
|
||||||
|
LoadingState,
|
||||||
|
PanelData,
|
||||||
|
withLoadingIndicator,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { getBackendSrv, toDataQueryError } from '@grafana/runtime';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { catchError, map, share } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
CloudPreviewRuleRequest,
|
||||||
|
GrafanaPreviewRuleRequest,
|
||||||
|
isCloudPreviewRequest,
|
||||||
|
isGrafanaPreviewRequest,
|
||||||
|
PreviewRuleRequest,
|
||||||
|
PreviewRuleResponse,
|
||||||
|
} from '../types/preview';
|
||||||
|
import { RuleFormType } from '../types/rule-form';
|
||||||
|
|
||||||
|
export function previewAlertRule(request: PreviewRuleRequest): Observable<PreviewRuleResponse> {
|
||||||
|
if (isCloudPreviewRequest(request)) {
|
||||||
|
return previewCloudAlertRule(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGrafanaPreviewRequest(request)) {
|
||||||
|
return previewGrafanaAlertRule(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('unsupported preview rule request');
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrafanaPreviewRuleResponse = {
|
||||||
|
instances: DataFrameJSON[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function previewGrafanaAlertRule(request: GrafanaPreviewRuleRequest): Observable<PreviewRuleResponse> {
|
||||||
|
const type = RuleFormType.grafana;
|
||||||
|
|
||||||
|
return withLoadingIndicator({
|
||||||
|
whileLoading: createResponse(type),
|
||||||
|
source: getBackendSrv()
|
||||||
|
.fetch<GrafanaPreviewRuleResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/v1/rule/test/grafana`,
|
||||||
|
data: request,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
map(({ data }) => {
|
||||||
|
return createResponse(type, {
|
||||||
|
state: LoadingState.Done,
|
||||||
|
series: data.instances.map(dataFrameFromJSON),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
catchError((error: Error) => {
|
||||||
|
return of(
|
||||||
|
createResponse(type, {
|
||||||
|
state: LoadingState.Error,
|
||||||
|
error: toDataQueryError(error),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
share()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createResponse(ruleType: RuleFormType, data: Partial<PanelData> = {}): PreviewRuleResponse {
|
||||||
|
return {
|
||||||
|
ruleType,
|
||||||
|
data: {
|
||||||
|
state: LoadingState.Loading,
|
||||||
|
series: [],
|
||||||
|
timeRange: getDefaultTimeRange(),
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function previewCloudAlertRule(request: CloudPreviewRuleRequest): Observable<PreviewRuleResponse> {
|
||||||
|
throw new Error('preview for cloud alerting rules is not implemented');
|
||||||
|
}
|
@ -8,6 +8,7 @@ import { timeOptions, timeValidationPattern } from '../../utils/time';
|
|||||||
import { ConditionField } from './ConditionField';
|
import { ConditionField } from './ConditionField';
|
||||||
import { GrafanaAlertStatePicker } from './GrafanaAlertStatePicker';
|
import { GrafanaAlertStatePicker } from './GrafanaAlertStatePicker';
|
||||||
import { RuleEditorSection } from './RuleEditorSection';
|
import { RuleEditorSection } from './RuleEditorSection';
|
||||||
|
import { PreviewRule } from './PreviewRule';
|
||||||
|
|
||||||
const MIN_TIME_RANGE_STEP_S = 10; // 10 seconds
|
const MIN_TIME_RANGE_STEP_S = 10; // 10 seconds
|
||||||
|
|
||||||
@ -142,6 +143,7 @@ export const ConditionsStep: FC = () => {
|
|||||||
</Field>
|
</Field>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<PreviewRule />
|
||||||
</RuleEditorSection>
|
</RuleEditorSection>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { takeWhile } from 'rxjs/operators';
|
||||||
|
import { useMountedState } from 'react-use';
|
||||||
|
import { Button, HorizontalGroup, useStyles2 } from '@grafana/ui';
|
||||||
|
import { dateTimeFormatISO, GrafanaTheme2, LoadingState } from '@grafana/data';
|
||||||
|
import { RuleFormType } from '../../types/rule-form';
|
||||||
|
import { PreviewRuleRequest, PreviewRuleResponse } from '../../types/preview';
|
||||||
|
import { previewAlertRule } from '../../api/preview';
|
||||||
|
import { PreviewRuleResult } from './PreviewRuleResult';
|
||||||
|
|
||||||
|
const fields: string[] = ['type', 'dataSourceName', 'condition', 'queries', 'expression'];
|
||||||
|
|
||||||
|
export function PreviewRule(): React.ReactElement | null {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const [preview, onPreview] = usePreview();
|
||||||
|
const { getValues } = useFormContext();
|
||||||
|
const [type] = getValues(fields);
|
||||||
|
|
||||||
|
if (type === RuleFormType.cloud) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<Button type="button" variant="primary" onClick={onPreview}>
|
||||||
|
Preview your alert
|
||||||
|
</Button>
|
||||||
|
</HorizontalGroup>
|
||||||
|
<PreviewRuleResult preview={preview} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function usePreview(): [PreviewRuleResponse | undefined, () => void] {
|
||||||
|
const [preview, setPreview] = useState<PreviewRuleResponse | undefined>();
|
||||||
|
const { getValues } = useFormContext();
|
||||||
|
const isMounted = useMountedState();
|
||||||
|
|
||||||
|
const onPreview = useCallback(() => {
|
||||||
|
const values = getValues(fields);
|
||||||
|
const request = createPreviewRequest(values);
|
||||||
|
|
||||||
|
previewAlertRule(request)
|
||||||
|
.pipe(takeWhile((response) => !isCompleted(response), true))
|
||||||
|
.subscribe((response) => {
|
||||||
|
if (!isMounted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPreview(response);
|
||||||
|
});
|
||||||
|
}, [getValues, isMounted]);
|
||||||
|
|
||||||
|
return [preview, onPreview];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPreviewRequest(values: any[]): PreviewRuleRequest {
|
||||||
|
const [type, dataSourceName, condition, queries, expression] = values;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case RuleFormType.cloud:
|
||||||
|
return {
|
||||||
|
dataSourceName,
|
||||||
|
expr: expression,
|
||||||
|
};
|
||||||
|
|
||||||
|
case RuleFormType.grafana:
|
||||||
|
return {
|
||||||
|
grafana_condition: {
|
||||||
|
condition,
|
||||||
|
data: queries,
|
||||||
|
now: dateTimeFormatISO(Date.now()),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Alert type ${type} not supported by preview.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCompleted(response: PreviewRuleResponse): boolean {
|
||||||
|
switch (response.data.state) {
|
||||||
|
case LoadingState.Done:
|
||||||
|
case LoadingState.Error:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyles(theme: GrafanaTheme2) {
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
margin-top: ${theme.spacing(2)};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
import { PanelRenderer } from '@grafana/runtime';
|
||||||
|
import { GrafanaTheme2, LoadingState } from '@grafana/data';
|
||||||
|
import { PreviewRuleResponse } from '../../types/preview';
|
||||||
|
import { RuleFormType } from '../../types/rule-form';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
preview: PreviewRuleResponse | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PreviewRuleResult(props: Props): React.ReactElement | null {
|
||||||
|
const { preview } = props;
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
if (!preview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, ruleType } = preview;
|
||||||
|
|
||||||
|
if (data.state === LoadingState.Loading) {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<span>Loading preview...</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.state === LoadingState.Error) {
|
||||||
|
return <div className={styles.container}>{data.error ?? 'Failed to preview alert rule'}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<span>
|
||||||
|
Preview based on the result of running the query, for this moment.{' '}
|
||||||
|
{ruleType === RuleFormType.grafana ? 'Configuration for `no data` and `error handling` is not applied.' : null}
|
||||||
|
</span>
|
||||||
|
<div className={styles.table}>
|
||||||
|
<AutoSizer>
|
||||||
|
{({ width, height }) => (
|
||||||
|
<div style={{ width: `${width}px`, height: `${height}px` }}>
|
||||||
|
<PanelRenderer title="" width={width} height={height} pluginId="table" data={data} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyles(theme: GrafanaTheme2) {
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
margin: ${theme.spacing(2)} 0;
|
||||||
|
`,
|
||||||
|
table: css`
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 135px;
|
||||||
|
margin-top: ${theme.spacing(2)};
|
||||||
|
border: 1px solid ${theme.colors.border.medium};
|
||||||
|
border-radius: ${theme.shape.borderRadius(1)};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { merge, Observable, of, OperatorFunction, ReplaySubject, timer, Unsubscribable } from 'rxjs';
|
import { Observable, of, OperatorFunction, ReplaySubject, Unsubscribable } from 'rxjs';
|
||||||
import { catchError, map, mapTo, share, takeUntil } from 'rxjs/operators';
|
import { catchError, map, share } from 'rxjs/operators';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import {
|
import {
|
||||||
dataFrameFromJSON,
|
dataFrameFromJSON,
|
||||||
@ -9,6 +9,7 @@ import {
|
|||||||
PanelData,
|
PanelData,
|
||||||
rangeUtil,
|
rangeUtil,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
|
withLoadingIndicator,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { FetchResponse, toDataQueryError } from '@grafana/runtime';
|
import { FetchResponse, toDataQueryError } from '@grafana/runtime';
|
||||||
import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
@ -107,14 +108,15 @@ const runRequest = (backendSrv: BackendSrv, queries: GrafanaQuery[]): Observable
|
|||||||
requestId: uuidv4(),
|
requestId: uuidv4(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const runningRequest = backendSrv.fetch<AlertingQueryResponse>(request).pipe(
|
return withLoadingIndicator({
|
||||||
mapToPanelData(initial),
|
whileLoading: initial,
|
||||||
catchError((error) => of(mapErrorToPanelData(initial, error))),
|
source: backendSrv.fetch<AlertingQueryResponse>(request).pipe(
|
||||||
cancelNetworkRequestsOnUnsubscribe(backendSrv, request.requestId),
|
mapToPanelData(initial),
|
||||||
share()
|
catchError((error) => of(mapErrorToPanelData(initial, error))),
|
||||||
);
|
cancelNetworkRequestsOnUnsubscribe(backendSrv, request.requestId),
|
||||||
|
share()
|
||||||
return merge(timer(200).pipe(mapTo(initial), takeUntil(runningRequest)), runningRequest);
|
),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState = (queries: GrafanaQuery[], state: LoadingState): Record<string, PanelData> => {
|
const initialState = (queries: GrafanaQuery[], state: LoadingState): Record<string, PanelData> => {
|
||||||
|
31
public/app/features/alerting/unified/types/preview.ts
Normal file
31
public/app/features/alerting/unified/types/preview.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { PanelData } from '@grafana/data';
|
||||||
|
import { GrafanaQuery } from 'app/types/unified-alerting-dto';
|
||||||
|
import { RuleFormType } from './rule-form';
|
||||||
|
|
||||||
|
export type PreviewRuleRequest = GrafanaPreviewRuleRequest | CloudPreviewRuleRequest;
|
||||||
|
|
||||||
|
export type GrafanaPreviewRuleRequest = {
|
||||||
|
grafana_condition: {
|
||||||
|
condition: string;
|
||||||
|
data: GrafanaQuery[];
|
||||||
|
now: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CloudPreviewRuleRequest = {
|
||||||
|
dataSourceName: string;
|
||||||
|
expr: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PreviewRuleResponse = {
|
||||||
|
ruleType: RuleFormType;
|
||||||
|
data: PanelData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isCloudPreviewRequest(request: PreviewRuleRequest): request is CloudPreviewRuleRequest {
|
||||||
|
return 'expr' in request;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGrafanaPreviewRequest(request: PreviewRuleRequest): request is GrafanaPreviewRuleRequest {
|
||||||
|
return 'grafana_condition' in request;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user