Add rule definition section to panel
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { Drawer } from '@grafana/ui';
|
||||
import { RuleDefinitionSection } from 'app/features/alerting/unified/components/RuleDefinitionSection';
|
||||
|
||||
import { getDefaultFormValues } from '../rule-editor/formDefaults';
|
||||
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
||||
|
||||
export interface AlertRuleDrawerFormProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function AlertRuleDrawerForm({ isOpen, onClose, title }: AlertRuleDrawerFormProps) {
|
||||
const methods = useForm<RuleFormValues>({ defaultValues: getDefaultFormValues(RuleFormType.grafana) });
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={title ?? t('alerting.new-rule-from-panel-button.new-alert-rule', 'New alert rule')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<RuleDefinitionSection type={RuleFormType.grafana} />
|
||||
</FormProvider>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Field, Input, Stack, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
import { isCloudRecordingRuleByType, isGrafanaManagedRuleByType, isRecordingRuleByType } from '../utils/rules';
|
||||
|
||||
import { FolderSelectorV2 } from './rule-editor/FolderSelectorV2';
|
||||
import { LabelsEditorModal } from './rule-editor/labels/LabelsEditorModal';
|
||||
import { LabelsFieldInForm } from './rule-editor/labels/LabelsFieldInForm';
|
||||
|
||||
export function RuleDefinitionSection({ type }: { type: RuleFormType }) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
getValues,
|
||||
} = useFormContext<RuleFormValues>();
|
||||
const [showLabelsEditor, setShowLabelsEditor] = useState(false);
|
||||
|
||||
const isRecording = isRecordingRuleByType(type);
|
||||
const isCloudRecordingRule = isCloudRecordingRuleByType(type);
|
||||
const namePlaceholder = isRecording ? 'recording rule' : 'alert rule';
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<div className={styles.sectionHeaderRow}>
|
||||
<span className={styles.stepBadge}>
|
||||
<Trans i18nKey="alerting.simplified.step-number-one">1</Trans>
|
||||
</span>
|
||||
<div className={styles.sectionHeader}>
|
||||
<Trans i18nKey="alerting.simplified.rule-definition">Rule Definition</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.contentIndented}>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Field
|
||||
noMargin
|
||||
label={<Trans i18nKey="alerting.alert-rule-name-and-metric.label-name">Name</Trans>}
|
||||
error={errors?.name?.message}
|
||||
invalid={!!errors.name?.message}
|
||||
>
|
||||
<Input
|
||||
data-testid={selectors.components.AlertRules.ruleNameField}
|
||||
id="name"
|
||||
width={38}
|
||||
{...register('name', {
|
||||
required: {
|
||||
value: true,
|
||||
message: t('alerting.alert-rule-name-and-metric.message.must-enter-a-name', 'Must enter a name'),
|
||||
},
|
||||
pattern: isCloudRecordingRule
|
||||
? {
|
||||
value: /^[a-zA-Z_:][a-zA-Z0-9_:]*$/,
|
||||
message: t(
|
||||
'alerting.alert-rule-name-and-metric.recording-rule-pattern',
|
||||
'Recording rule name must be valid metric name. It may only contain letters, numbers, and colons. It may not contain whitespace.'
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
})}
|
||||
aria-label={t('alerting.alert-rule-name-and-metric.aria-label-name', 'name')}
|
||||
placeholder={t(
|
||||
'alerting.alert-rule-name-and-metric.placeholder-name',
|
||||
'Give your {{namePlaceholder}} a name',
|
||||
{ namePlaceholder }
|
||||
)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{isGrafanaManagedRuleByType(type) && (
|
||||
<>
|
||||
<FolderSelectorV2 />
|
||||
<LabelsFieldInForm showHelpTooltip onEditClick={() => setShowLabelsEditor(true)} labelVariant="small" />
|
||||
<LabelsEditorModal
|
||||
isOpen={showLabelsEditor}
|
||||
onClose={(labelsToUpdate) => {
|
||||
if (labelsToUpdate) {
|
||||
setValue('labels', labelsToUpdate);
|
||||
}
|
||||
setShowLabelsEditor(false);
|
||||
}}
|
||||
dataSourceName={GRAFANA_RULES_SOURCE_NAME}
|
||||
initialLabels={getValues('labels')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
section: css({ width: '100%' }),
|
||||
sectionHeaderRow: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
}),
|
||||
sectionHeader: css({
|
||||
fontWeight: 600,
|
||||
fontSize: theme.typography.h4.fontSize,
|
||||
lineHeight: theme.typography.h4.lineHeight,
|
||||
}),
|
||||
stepBadge: css({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 20,
|
||||
width: 20,
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
background: theme.colors.primary.main,
|
||||
color: theme.colors.text.maxContrast,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
fontWeight: 600,
|
||||
}),
|
||||
contentIndented: css({ marginLeft: `calc(20px + ${theme.spacing(1)})` }),
|
||||
};
|
||||
}
|
||||
@@ -27,8 +27,8 @@ export const CreateNewFolder = ({ onCreate }: { onCreate: (folder: Folder) => vo
|
||||
onClick={() => setIsCreatingFolder(true)}
|
||||
type="button"
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={!contextSrv.hasPermission(AccessControlAction.FoldersCreate)}
|
||||
>
|
||||
<Trans i18nKey="alerting.create-new-folder.new-folder">New folder</Trans>
|
||||
|
||||
+4
-10
@@ -5,12 +5,13 @@ import { useAsync } from 'react-use';
|
||||
import { urlUtil } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Alert, Button, Drawer, LinkButton } from '@grafana/ui';
|
||||
import { Alert, Button, LinkButton } from '@grafana/ui';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import { useSelector } from 'app/types/store';
|
||||
|
||||
import { LogMessages, logInfo } from '../../Analytics';
|
||||
import { AlertRuleDrawerForm } from '../../components/AlertRuleDrawerForm';
|
||||
import { panelToRuleFormValues } from '../../utils/rule-form';
|
||||
|
||||
interface Props {
|
||||
@@ -72,18 +73,11 @@ export const NewRuleFromPanelButton = ({ dashboard, panel, className }: Props) =
|
||||
icon="bell"
|
||||
className={className}
|
||||
data-testid="create-alert-rule-button-drawer"
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
}}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<Trans i18nKey="alerting.new-rule-from-panel-button.new-alert-rule">New alert rule</Trans>
|
||||
</Button>
|
||||
<Drawer
|
||||
title={t('alerting.new-rule-from-panel-button.new-alert-rule', 'New alert rule')}
|
||||
onClose={() => setIsOpen(false)}
|
||||
>
|
||||
<Trans i18nKey="alerting.new-rule-from-panel-button.content-coming-soon">Content coming soon...</Trans>
|
||||
</Drawer>
|
||||
<AlertRuleDrawerForm isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Field, Icon, Label, Stack, Tooltip } from '@grafana/ui';
|
||||
import { NestedFolderPicker } from 'app/core/components/NestedFolderPicker/NestedFolderPicker';
|
||||
|
||||
import { Folder, RuleFormValues } from '../../types/rule-form';
|
||||
import { CreateNewFolder } from '../create-folder/CreateNewFolder';
|
||||
|
||||
export function FolderSelectorV2() {
|
||||
const {
|
||||
formState: { errors },
|
||||
setValue,
|
||||
watch,
|
||||
} = useFormContext<RuleFormValues>();
|
||||
|
||||
const resetGroup = useCallback(() => {
|
||||
setValue('group', '');
|
||||
}, [setValue]);
|
||||
|
||||
const folder = watch('folder');
|
||||
|
||||
const handleFolderCreation = (folder: Folder) => {
|
||||
resetGroup();
|
||||
setValue('folder', folder);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack alignItems="center">
|
||||
{
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
<Label
|
||||
htmlFor="folder"
|
||||
description={t(
|
||||
'alerting.folder-selector.description-select-folder',
|
||||
'Select a folder to store your rule in.'
|
||||
)}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" gap={0.5}>
|
||||
<Trans i18nKey="alerting.rule-form.folder.label">Folder</Trans>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'alerting.rule-form.folders.help-info',
|
||||
'Folders are used for storing alert rules. You can extend the access provided by a role to alert rules and assign permissions to individual folders.'
|
||||
)}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Label>
|
||||
}
|
||||
error={errors.folder?.message}
|
||||
data-testid="folder-picker"
|
||||
>
|
||||
<Stack direction="column" alignItems="flex-start" gap={0.5}>
|
||||
<Controller
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<div style={{ width: 420 }}>
|
||||
<NestedFolderPicker
|
||||
permission="view"
|
||||
showRootFolder={false}
|
||||
invalid={!!errors.folder?.message}
|
||||
{...field}
|
||||
value={folder?.uid}
|
||||
onChange={(uid, title) => {
|
||||
if (uid && title) {
|
||||
setValue('folder', { title, uid });
|
||||
} else {
|
||||
setValue('folder', undefined);
|
||||
}
|
||||
|
||||
resetGroup();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
name="folder"
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: t('alerting.folder-selector.message.select-a-folder', 'Select a folder'),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<CreateNewFolder onCreate={handleFolderCreation} />
|
||||
</Stack>
|
||||
</Field>
|
||||
}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+44
-3
@@ -1,7 +1,7 @@
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Stack, Text } from '@grafana/ui';
|
||||
import { Button, Icon, Stack, Text, Tooltip } from '@grafana/ui';
|
||||
|
||||
import { AIImproveLabelsButtonComponent } from '../../../enterprise-components/AI/AIGenImproveLabelsButton/addAIImproveLabelsButton';
|
||||
import { RuleFormValues } from '../../../types/rule-form';
|
||||
@@ -12,8 +12,14 @@ import { LabelsInRule } from './LabelsField';
|
||||
|
||||
interface LabelsFieldInFormProps {
|
||||
onEditClick: () => void;
|
||||
labelVariant?: 'small' | 'default';
|
||||
showHelpTooltip?: boolean;
|
||||
}
|
||||
export function LabelsFieldInForm({ onEditClick }: LabelsFieldInFormProps) {
|
||||
export function LabelsFieldInForm({
|
||||
onEditClick,
|
||||
labelVariant = 'default',
|
||||
showHelpTooltip = false,
|
||||
}: LabelsFieldInFormProps) {
|
||||
const { watch } = useFormContext<RuleFormValues>();
|
||||
|
||||
const labels = watch('labels');
|
||||
@@ -35,7 +41,42 @@ export function LabelsFieldInForm({ onEditClick }: LabelsFieldInFormProps) {
|
||||
<Stack direction="column" gap={2}>
|
||||
<Stack direction="column" gap={1}>
|
||||
<Text element="h5">
|
||||
<Trans i18nKey="alerting.labels-field-in-form.labels">Labels</Trans>
|
||||
<Stack direction="row" alignItems="center" gap={0.5}>
|
||||
{labelVariant === 'small' ? (
|
||||
<Text variant="bodySmall">
|
||||
<Trans i18nKey="alerting.labels-field-in-form.labels">Labels</Trans>
|
||||
</Text>
|
||||
) : (
|
||||
<Text element="h5">
|
||||
<Trans i18nKey="alerting.labels-field-in-form.labels">Labels</Trans>
|
||||
</Text>
|
||||
)}
|
||||
<Text variant="bodySmall" color="secondary">
|
||||
{t('alerting.common.optional', '(optional)')}
|
||||
</Text>
|
||||
{showHelpTooltip && (
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
{t(
|
||||
'alerting.labels-field-in-form.tooltip-text',
|
||||
'Labels are used to differentiate an alert from all other alerts.You can use them for searching, silencing, and routing notifications.'
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{t(
|
||||
'alerting.labels-field-in-form.tooltip-text-2',
|
||||
'The dropdown only displays labels that you have previously used for alerts. Select a label from the options below or type in a new one.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Stack>
|
||||
</Text>
|
||||
<Stack direction={'column'} gap={1}>
|
||||
<Stack direction={'row'} gap={1}>
|
||||
|
||||
+3
-10
@@ -6,8 +6,9 @@ import { urlUtil } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config, locationService, logInfo } from '@grafana/runtime';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { Alert, Button, Drawer } from '@grafana/ui';
|
||||
import { Alert, Button } from '@grafana/ui';
|
||||
import { LogMessages } from 'app/features/alerting/unified/Analytics';
|
||||
import { AlertRuleDrawerForm } from 'app/features/alerting/unified/components/AlertRuleDrawerForm';
|
||||
import { scenesPanelToRuleFormValues } from 'app/features/alerting/unified/utils/rule-form';
|
||||
|
||||
interface ScenesNewRuleFromPanelButtonProps {
|
||||
@@ -67,20 +68,12 @@ export const ScenesNewRuleFromPanelButton = ({ panel, className }: ScenesNewRule
|
||||
className={className}
|
||||
data-testid="create-alert-rule-button-drawer"
|
||||
onClick={() => {
|
||||
// logInfo(LogMessages.alertRuleFromPanel);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="alerting.new-rule-from-panel-button.new-alert-rule">New alert rule</Trans>
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<Drawer
|
||||
title={t('alerting.new-rule-from-panel-button.new-alert-rule', 'New alert rule')}
|
||||
onClose={() => setIsOpen(false)}
|
||||
>
|
||||
<Trans i18nKey="alerting.new-rule-from-panel-button.content-coming-soon">Content coming soon...</Trans>
|
||||
</Drawer>
|
||||
)}
|
||||
<AlertRuleDrawerForm isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@@ -39,9 +40,9 @@ export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
|
||||
new PanelDataTransformationsTab({ panelRef }),
|
||||
];
|
||||
|
||||
if (shouldShowAlertingTab(panel.state.pluginId)) {
|
||||
tabs.push(new PanelDataAlertingTab({ panelRef }));
|
||||
}
|
||||
// if (shouldShowAlertingTab(panel.state.pluginId)) {
|
||||
tabs.push(new PanelDataAlertingTab({ panelRef }));
|
||||
// }
|
||||
|
||||
return new PanelDataPane({
|
||||
panelRef,
|
||||
|
||||
Reference in New Issue
Block a user