1import React, { useCallback, useState } from 'react';
2import { css } from '@emotion/css';
3import { useFormContext } from 'react-hook-form';
4import { takeWhile } from 'rxjs/operators';
5import { useMountedState } from 'react-use';
6import { Button, HorizontalGroup, useStyles2 } from '@grafana/ui';
7import { dateTimeFormatISO, GrafanaTheme2, LoadingState } from '@grafana/data';
8import { RuleFormType } from '../../types/rule-form';
9import { PreviewRuleRequest, PreviewRuleResponse } from '../../types/preview';
10import { previewAlertRule } from '../../api/preview';
11import { PreviewRuleResult } from './PreviewRuleResult';
12
13const fields: string[] = ['type', 'dataSourceName', 'condition', 'queries', 'expression'];
14
15export function PreviewRule(): React.ReactElement | null {
16  const styles = useStyles2(getStyles);
17  const [preview, onPreview] = usePreview();
18  const { watch } = useFormContext();
19  const [type, condition] = watch(['type', 'condition']);
20
21  if (type === RuleFormType.cloudRecording || type === RuleFormType.cloudAlerting) {
22    return null;
23  }
24
25  return (
26    <div className={styles.container}>
27      <HorizontalGroup>
28        <Button disabled={!condition} type="button" variant="primary" onClick={onPreview}>
29          Preview alerts
30        </Button>
31      </HorizontalGroup>
32      <PreviewRuleResult preview={preview} />
33    </div>
34  );
35}
36
37function usePreview(): [PreviewRuleResponse | undefined, () => void] {
38  const [preview, setPreview] = useState<PreviewRuleResponse | undefined>();
39  const { getValues } = useFormContext();
40  const isMounted = useMountedState();
41
42  const onPreview = useCallback(() => {
43    const values = getValues(fields);
44    const request = createPreviewRequest(values);
45
46    previewAlertRule(request)
47      .pipe(takeWhile((response) => !isCompleted(response), true))
48      .subscribe((response) => {
49        if (!isMounted()) {
50          return;
51        }
52        setPreview(response);
53      });
54  }, [getValues, isMounted]);
55
56  return [preview, onPreview];
57}
58
59function createPreviewRequest(values: any[]): PreviewRuleRequest {
60  const [type, dataSourceName, condition, queries, expression] = values;
61
62  switch (type) {
63    case RuleFormType.cloudAlerting:
64      return {
65        dataSourceName,
66        expr: expression,
67      };
68
69    case RuleFormType.grafana:
70      return {
71        grafana_condition: {
72          condition,
73          data: queries,
74          now: dateTimeFormatISO(Date.now()),
75        },
76      };
77
78    default:
79      throw new Error(`Alert type ${type} not supported by preview.`);
80  }
81}
82
83function isCompleted(response: PreviewRuleResponse): boolean {
84  switch (response.data.state) {
85    case LoadingState.Done:
86    case LoadingState.Error:
87      return true;
88    default:
89      return false;
90  }
91}
92
93function getStyles(theme: GrafanaTheme2) {
94  return {
95    container: css`
96      margin-top: ${theme.spacing(2)};
97      max-width: ${theme.breakpoints.values.xxl}px;
98    `,
99  };
100}
101