diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx
index b27e204afbf..ae093f35b48 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 0caa567431b..ed5dc6eb376 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 b4c1f24c57f..b56373e3a30 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 00000000000..8f16b083e8f
--- /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 48ea6c18f35..1d56df6111f 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;