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:
Marcus Andersson
2021-05-26 10:06:28 +02:00
committed by GitHub
parent 42f33630c7
commit e19b3df1a9
13 changed files with 362 additions and 12 deletions

View File

@@ -8,6 +8,7 @@ import { timeOptions, timeValidationPattern } from '../../utils/time';
import { ConditionField } from './ConditionField';
import { GrafanaAlertStatePicker } from './GrafanaAlertStatePicker';
import { RuleEditorSection } from './RuleEditorSection';
import { PreviewRule } from './PreviewRule';
const MIN_TIME_RANGE_STEP_S = 10; // 10 seconds
@@ -142,6 +143,7 @@ export const ConditionsStep: FC = () => {
</Field>
</>
)}
<PreviewRule />
</RuleEditorSection>
);
};

View File

@@ -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)};
`,
};
}

View File

@@ -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)};
`,
};
}