Skip to content

Commit

Permalink
Alerting: Add View YAML button for Grafana/provisioned rules (grafana…
Browse files Browse the repository at this point in the history
…#71983)

* Add View YAML button for grafana/provisioned rules

* Address PR comments
  • Loading branch information
VikaCep authored Jul 24, 2023
1 parent eee8e52 commit 45b5b81
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 27 deletions.
47 changes: 35 additions & 12 deletions public/app/features/alerting/unified/RuleViewer.tsx
Original file line number Diff line number Diff line change
@@ -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'));
Expand All @@ -16,15 +20,34 @@ type RuleViewerProps = GrafanaRouteComponentProps<{
sourceName: string;
}>;

const RuleViewer = (props: RuleViewerProps): JSX.Element => (
<AlertingPageWrapper>
<Enable feature={AlertingFeature.DetailsViewV2}>
<DetailViewV2 {...props} />
</Enable>
<Disable feature={AlertingFeature.DetailsViewV2}>
<DetailViewV1 {...props} />
</Disable>
</AlertingPageWrapper>
);
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 ? (
<HorizontalGroup height="auto" justify="flex-end">
<Button variant="secondary" type="button" onClick={() => setShowYaml(true)} size="sm">
View YAML
</Button>
</HorizontalGroup>
) : null;

return (
<AlertingPageWrapper>
<AppChromeUpdate actions={actionButtons} />
{showYaml && <GrafanaRuleInspector alertUid={uidFromParams} onClose={() => setShowYaml(false)} />}
<Enable feature={AlertingFeature.DetailsViewV2}>
<DetailViewV2 {...props} />
</Enable>
<Disable feature={AlertingFeature.DetailsViewV2}>
<DetailViewV1 {...props} />
</Disable>
</AlertingPageWrapper>
);
};

export default withErrorBoundary(RuleViewer, { style: 'page' });
8 changes: 8 additions & 0 deletions public/app/features/alerting/unified/api/alertRuleApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -127,5 +131,9 @@ export const alertRuleApi = alertingApi.injectEndpoints({
return groupRulesByFileName(response.data.groups, GRAFANA_RULES_SOURCE_NAME);
},
}),

exportRule: build.query<string, { uid: string; format: 'yaml' | 'json' }>({
query: ({ uid, format }) => ({ url: getProvisioningUrl(uid, format) }),
}),
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -225,17 +226,16 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
Delete
</Button>
) : null}
{isCortexLokiOrRecordingRule(watch) && (
<Button
variant="secondary"
type="button"
onClick={() => setShowEditYaml(true)}
disabled={submitState.loading}
size="sm"
>
Edit YAML
</Button>
)}

<Button
variant="secondary"
type="button"
onClick={() => setShowEditYaml(true)}
disabled={submitState.loading}
size="sm"
>
{isCortexLokiOrRecordingRule(watch) ? 'Edit YAML' : 'View YAML'}
</Button>
</HorizontalGroup>
);

Expand Down Expand Up @@ -278,7 +278,13 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
onDismiss={() => setShowDeleteModal(false)}
/>
) : null}
{showEditYaml ? <RuleInspector onClose={() => setShowEditYaml(false)} /> : null}
{showEditYaml ? (
type === RuleFormType.grafana ? (
<GrafanaRuleInspector alertUid={uidFromParams} onClose={() => setShowEditYaml(false)} />
) : (
<RuleInspector onClose={() => setShowEditYaml(false)} />
)
) : null}
</FormProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<Drawer
title="Inspect Alert rule"
subtitle={
<div className={styles.subtitle}>
<RuleInspectorSubtitle setActiveTab={setActiveTab} activeTab={activeTab} />
</div>
}
onClose={onClose}
>
{activeTab === 'yaml' && <GrafanaInspectorYamlTab alertUid={alertUid} />}
</Drawer>
);
};

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 <div>Loading...</div>;
}

return (
<>
<div className={styles.content}>
<AutoSizer disableWidth>
{({ height }) => (
<CodeEditor
width="100%"
height={height}
language="yaml"
value={yamlRule}
monacoOptions={{
minimap: {
enabled: false,
},
}}
/>
)}
</AutoSizer>
</div>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ interface SubtitleProps {
setActiveTab: (tab: string) => void;
}

const RuleInspectorSubtitle = ({ activeTab, setActiveTab }: SubtitleProps) => {
export const RuleInspectorSubtitle = ({ activeTab, setActiveTab }: SubtitleProps) => {
return (
<TabsBar>
{tabs.map((tab, index) => {
Expand Down Expand Up @@ -153,7 +153,7 @@ function rulerRuleToRuleFormValues(rulerRule: RulerRuleDTO): Partial<RuleFormVal
return {};
}

const yamlTabStyle = (theme: GrafanaTheme2) => ({
export const yamlTabStyle = (theme: GrafanaTheme2) => ({
content: css`
flex-grow: 1;
height: 100%;
Expand All @@ -170,7 +170,7 @@ const yamlTabStyle = (theme: GrafanaTheme2) => ({
`,
});

const drawerStyles = () => ({
export const drawerStyles = () => ({
subtitle: css`
display: flex;
align-items: center;
Expand Down

0 comments on commit 45b5b81

Please sign in to comment.