mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add yaml editor to cloud rules (#46533)
* ruleinspector component * Adding yaml component * setvalues * update yarn.lock * bump types * chore: update lockfile * move apply button position * move button back to tab Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
This commit is contained in:
parent
7b30fae36f
commit
cb03b05ced
0
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file → Normal file
0
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file → Normal file
@ -128,6 +128,7 @@
|
||||
"@types/hoist-non-react-statics": "3.3.1",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/jquery": "3.5.14",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsurl": "^1.2.28",
|
||||
"@types/lingui__macro": "^3",
|
||||
"@types/lodash": "4.14.181",
|
||||
@ -311,6 +312,7 @@
|
||||
"immer": "9.0.12",
|
||||
"immutable": "4.0.0",
|
||||
"jquery": "3.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-source-map": "0.6.1",
|
||||
"jsurl": "^0.1.5",
|
||||
"lezer-promql": "0.22.0",
|
||||
|
@ -6,7 +6,7 @@ import { css } from '@emotion/css';
|
||||
import { AlertTypeStep } from './AlertTypeStep';
|
||||
import { DetailsStep } from './DetailsStep';
|
||||
import { QueryStep } from './QueryStep';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { useForm, FormProvider, UseFormWatch } from 'react-hook-form';
|
||||
|
||||
import { RuleFormType, RuleFormValues } from '../../types/rule-form';
|
||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||
@ -23,6 +23,7 @@ import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { CloudConditionsStep } from './CloudConditionsStep';
|
||||
import { GrafanaConditionsStep } from './GrafanaConditionsStep';
|
||||
import * as ruleId from '../../utils/rule-id';
|
||||
import { RuleInspector } from './RuleInspector';
|
||||
|
||||
type Props = {
|
||||
existing?: RuleWithLocation;
|
||||
@ -33,6 +34,7 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
|
||||
const dispatch = useDispatch();
|
||||
const notifyApp = useAppNotification();
|
||||
const [queryParams] = useQueryParams();
|
||||
const [showEditYaml, setShowEditYaml] = useState(false);
|
||||
|
||||
const returnTo: string = (queryParams['returnTo'] as string | undefined) ?? '/alerting/list';
|
||||
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
|
||||
@ -106,7 +108,7 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
|
||||
return (
|
||||
<FormProvider {...formAPI}>
|
||||
<form onSubmit={(e) => e.preventDefault()} className={styles.form}>
|
||||
<PageToolbar title="Create alert rule" pageIcon="bell">
|
||||
<PageToolbar title={`${existing ? 'Edit' : 'Create'} alert rule`} pageIcon="bell">
|
||||
<Link to={returnTo}>
|
||||
<Button variant="secondary" disabled={submitState.loading} type="button" fill="outline">
|
||||
Cancel
|
||||
@ -117,6 +119,16 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
|
||||
Delete
|
||||
</Button>
|
||||
) : null}
|
||||
{isCortexLokiOrRecordingRule(watch) && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={() => setShowEditYaml(true)}
|
||||
disabled={submitState.loading}
|
||||
>
|
||||
Edit yaml
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
type="button"
|
||||
@ -162,10 +174,17 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
|
||||
onDismiss={() => setShowDeleteModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{showEditYaml ? <RuleInspector onClose={() => setShowEditYaml(false)} /> : null}
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const isCortexLokiOrRecordingRule = (watch: UseFormWatch<RuleFormValues>) => {
|
||||
const [ruleType, dataSourceName] = watch(['type', 'dataSourceName']);
|
||||
|
||||
return (ruleType === RuleFormType.cloudAlerting || ruleType === RuleFormType.cloudRecording) && dataSourceName !== '';
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
buttonSpinner: css`
|
||||
|
@ -0,0 +1,131 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { dump, load } from 'js-yaml';
|
||||
import { css } from '@emotion/css';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, CodeEditor, Drawer, Tab, TabsBar, useStyles2 } from '@grafana/ui';
|
||||
import { RuleFormValues } from '../../types/rule-form';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const tabs = [{ label: 'Yaml', value: 'yaml' }];
|
||||
|
||||
export const RuleInspector: FC<Props> = ({ onClose }) => {
|
||||
const [activeTab, setActiveTab] = useState('yaml');
|
||||
const { setValue } = useFormContext<RuleFormValues>();
|
||||
const styles = useStyles2(drawerStyles);
|
||||
|
||||
const onApply = (formValues: RuleFormValues) => {
|
||||
// Need to loop through all values and set them individually
|
||||
// TODO this is not type-safe :(
|
||||
for (const key in formValues) {
|
||||
// @ts-ignore
|
||||
setValue(key, formValues[key]);
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title="Inspect Alert rule"
|
||||
subtitle={
|
||||
<div className={styles.subtitle}>
|
||||
<RuleInspectorSubtitle setActiveTab={setActiveTab} activeTab={activeTab} />
|
||||
</div>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
{activeTab === 'yaml' && <InspectorYamlTab onSubmit={onApply} />}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
interface SubtitleProps {
|
||||
activeTab: string;
|
||||
setActiveTab: (tab: string) => void;
|
||||
}
|
||||
|
||||
const RuleInspectorSubtitle: FC<SubtitleProps> = ({ activeTab, setActiveTab }) => {
|
||||
return (
|
||||
<TabsBar>
|
||||
{tabs.map((tab, index) => {
|
||||
return (
|
||||
<Tab
|
||||
key={`${tab.value}-${index}`}
|
||||
label={tab.label}
|
||||
value={tab.value}
|
||||
onChangeTab={() => setActiveTab(tab.value)}
|
||||
active={activeTab === tab.value}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TabsBar>
|
||||
);
|
||||
};
|
||||
|
||||
interface YamlTabProps {
|
||||
onSubmit: (newModel: RuleFormValues) => void;
|
||||
}
|
||||
|
||||
const InspectorYamlTab: FC<YamlTabProps> = ({ onSubmit }) => {
|
||||
const styles = useStyles2(yamlTabStyle);
|
||||
const { getValues } = useFormContext<RuleFormValues>();
|
||||
const [alertRuleAsYaml, setAlertRuleAsYaml] = useState(dump(getValues()));
|
||||
|
||||
const onApply = () => {
|
||||
onSubmit(load(alertRuleAsYaml) as RuleFormValues);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.applyButton}>
|
||||
<Button type="button" onClick={onApply}>
|
||||
Apply
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CodeEditor
|
||||
width="100%"
|
||||
height={height}
|
||||
language="yaml"
|
||||
value={alertRuleAsYaml}
|
||||
onBlur={setAlertRuleAsYaml}
|
||||
monacoOptions={{
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const yamlTabStyle = (theme: GrafanaTheme2) => ({
|
||||
content: css`
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
`,
|
||||
applyButton: css`
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
`,
|
||||
});
|
||||
|
||||
const drawerStyles = () => ({
|
||||
subtitle: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`,
|
||||
});
|
@ -10112,6 +10112,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/js-yaml@npm:^4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@types/js-yaml@npm:4.0.5"
|
||||
checksum: 7dcac8c50fec31643cc9d6444b5503239a861414cdfaa7ae9a38bc22597c4d850c4b8cec3d82d73b3fbca408348ce223b0408d598b32e094470dfffc6d486b4d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9":
|
||||
version: 7.0.9
|
||||
resolution: "@types/json-schema@npm:7.0.9"
|
||||
@ -20496,6 +20503,7 @@ __metadata:
|
||||
"@types/hoist-non-react-statics": 3.3.1
|
||||
"@types/jest": 27.4.1
|
||||
"@types/jquery": 3.5.14
|
||||
"@types/js-yaml": ^4.0.5
|
||||
"@types/jsurl": ^1.2.28
|
||||
"@types/lingui__macro": ^3
|
||||
"@types/lodash": 4.14.181
|
||||
@ -20604,6 +20612,7 @@ __metadata:
|
||||
jest-matcher-utils: 27.5.1
|
||||
jest-mock-console: 1.2.3
|
||||
jquery: 3.6.0
|
||||
js-yaml: ^4.1.0
|
||||
json-source-map: 0.6.1
|
||||
jsurl: ^0.1.5
|
||||
lerna: ^4.0.0
|
||||
|
Loading…
Reference in New Issue
Block a user