From 45b5b81db6bab24e0a1dcba5c187bc7706a86371 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Mon, 24 Jul 2023 11:35:26 -0300 Subject: [PATCH] Alerting: Add View YAML button for Grafana/provisioned rules (#71983) * Add View YAML button for grafana/provisioned rules * Address PR comments --- .../features/alerting/unified/RuleViewer.tsx | 47 +++++++++--- .../alerting/unified/api/alertRuleApi.ts | 8 ++ .../components/rule-editor/AlertRuleForm.tsx | 30 +++++--- .../rule-editor/GrafanaRuleInspector.tsx | 74 +++++++++++++++++++ .../components/rule-editor/RuleInspector.tsx | 6 +- 5 files changed, 138 insertions(+), 27 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rule-editor/GrafanaRuleInspector.tsx diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index b27e204afbfc3..ae093f35b482a 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -1,12 +1,16 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Disable, Enable } from 'react-enable'; +import { useParams } from 'react-router-dom'; -import { withErrorBoundary } from '@grafana/ui'; +import { Button, HorizontalGroup, withErrorBoundary } from '@grafana/ui'; +import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; +import { GrafanaRuleInspector } from './components/rule-editor/GrafanaRuleInspector'; import { AlertingFeature } from './features'; +import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; const DetailViewV1 = SafeDynamicImport(() => import('./components/rule-viewer/RuleViewer.v1')); const DetailViewV2 = SafeDynamicImport(() => import('./components/rule-viewer/v2/RuleViewer.v2')); @@ -16,15 +20,34 @@ type RuleViewerProps = GrafanaRouteComponentProps<{ sourceName: string; }>; -const RuleViewer = (props: RuleViewerProps): JSX.Element => ( - - - - - - - - -); +const RuleViewer = (props: RuleViewerProps): JSX.Element => { + const routeParams = useParams<{ type: string; id: string }>(); + const uidFromParams = routeParams.id; + + const sourceName = props.match.params.sourceName; + + const [showYaml, setShowYaml] = useState(false); + const actionButtons = + sourceName === GRAFANA_RULES_SOURCE_NAME ? ( + + + + ) : null; + + return ( + + + {showYaml && setShowYaml(false)} />} + + + + + + + + ); +}; export default withErrorBoundary(RuleViewer, { style: 'page' }); diff --git a/public/app/features/alerting/unified/api/alertRuleApi.ts b/public/app/features/alerting/unified/api/alertRuleApi.ts index 0caa567431b4e..ed5dc6eb376a8 100644 --- a/public/app/features/alerting/unified/api/alertRuleApi.ts +++ b/public/app/features/alerting/unified/api/alertRuleApi.ts @@ -35,6 +35,10 @@ export interface Datasource { export const PREVIEW_URL = '/api/v1/rule/test/grafana'; export const PROM_RULES_URL = 'api/prometheus/grafana/api/v1/rules'; +function getProvisioningUrl(ruleUid: string, format: 'yaml' | 'json' = 'yaml') { + return `/api/v1/provisioning/alert-rules/${ruleUid}/export?format=${format}`; +} + export interface Data { refId: string; relativeTimeRange: RelativeTimeRange; @@ -127,5 +131,9 @@ export const alertRuleApi = alertingApi.injectEndpoints({ return groupRulesByFileName(response.data.groups, GRAFANA_RULES_SOURCE_NAME); }, }), + + exportRule: build.query({ + query: ({ uid, format }) => ({ url: getProvisioningUrl(uid, format) }), + }), }), }); diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx index b4c1f24c57f70..b56373e3a300a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx @@ -27,6 +27,7 @@ import * as ruleId from '../../utils/rule-id'; import { CloudEvaluationBehavior } from './CloudEvaluationBehavior'; import { DetailsStep } from './DetailsStep'; import { GrafanaEvaluationBehavior } from './GrafanaEvaluationBehavior'; +import { GrafanaRuleInspector } from './GrafanaRuleInspector'; import { NotificationsStep } from './NotificationsStep'; import { RuleEditorSection } from './RuleEditorSection'; import { RuleInspector } from './RuleInspector'; @@ -225,17 +226,16 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { Delete ) : null} - {isCortexLokiOrRecordingRule(watch) && ( - - )} + + ); @@ -278,7 +278,13 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { onDismiss={() => setShowDeleteModal(false)} /> ) : null} - {showEditYaml ? setShowEditYaml(false)} /> : null} + {showEditYaml ? ( + type === RuleFormType.grafana ? ( + setShowEditYaml(false)} /> + ) : ( + setShowEditYaml(false)} /> + ) + ) : null} ); }; diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaRuleInspector.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaRuleInspector.tsx new file mode 100644 index 0000000000000..8f16b083e8f2c --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaRuleInspector.tsx @@ -0,0 +1,74 @@ +import { dump } from 'js-yaml'; +import React, { useMemo, useState } from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; + +import { CodeEditor, Drawer, useStyles2 } from '@grafana/ui'; + +import { alertRuleApi } from '../../api/alertRuleApi'; + +import { drawerStyles, RuleInspectorSubtitle, yamlTabStyle } from './RuleInspector'; + +interface Props { + onClose: () => void; + alertUid: string; +} + +export const GrafanaRuleInspector = ({ onClose, alertUid }: Props) => { + const [activeTab, setActiveTab] = useState('yaml'); + + const styles = useStyles2(drawerStyles); + + return ( + + + + } + onClose={onClose} + > + {activeTab === 'yaml' && } + + ); +}; + +const { useExportRuleQuery } = alertRuleApi; + +interface YamlTabProps { + alertUid: string; +} + +const GrafanaInspectorYamlTab = ({ alertUid }: YamlTabProps) => { + const styles = useStyles2(yamlTabStyle); + + const { currentData: ruleYamlConfig, isLoading } = useExportRuleQuery({ uid: alertUid, format: 'yaml' }); + + const yamlRule = useMemo(() => dump(ruleYamlConfig), [ruleYamlConfig]); + + if (isLoading) { + return
Loading...
; + } + + return ( + <> +
+ + {({ height }) => ( + + )} + +
+ + ); +}; diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx index 48ea6c18f359c..1d56df6111f63 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx @@ -57,7 +57,7 @@ interface SubtitleProps { setActiveTab: (tab: string) => void; } -const RuleInspectorSubtitle = ({ activeTab, setActiveTab }: SubtitleProps) => { +export const RuleInspectorSubtitle = ({ activeTab, setActiveTab }: SubtitleProps) => { return ( {tabs.map((tab, index) => { @@ -153,7 +153,7 @@ function rulerRuleToRuleFormValues(rulerRule: RulerRuleDTO): Partial ({ +export const yamlTabStyle = (theme: GrafanaTheme2) => ({ content: css` flex-grow: 1; height: 100%; @@ -170,7 +170,7 @@ const yamlTabStyle = (theme: GrafanaTheme2) => ({ `, }); -const drawerStyles = () => ({ +export const drawerStyles = () => ({ subtitle: css` display: flex; align-items: center;