update rule definition section UI

This commit is contained in:
laurenashleigh
2026-01-12 11:41:25 +00:00
parent de4ff128fd
commit 179753c608
6 changed files with 169 additions and 61 deletions
@@ -13,7 +13,7 @@ import { isCloudRecordingRuleByType, isGrafanaManagedRuleByType, isRecordingRule
import { FolderSelectorV2 } from './rule-editor/FolderSelectorV2';
import { LabelsEditorModal } from './rule-editor/labels/LabelsEditorModal';
import { LabelsFieldInForm } from './rule-editor/labels/LabelsFieldInForm';
import { LabelsFieldInFormV2 } from './rule-editor/labels/LabelsFieldInFormV2';
export function RuleDefinitionSection({ type }: { type: RuleFormType }) {
const styles = useStyles2(getStyles);
@@ -43,7 +43,11 @@ export function RuleDefinitionSection({ type }: { type: RuleFormType }) {
<Stack direction="column" gap={2}>
<Field
noMargin
label={<Trans i18nKey="alerting.alert-rule-name-and-metric.label-name">Name</Trans>}
label={
<span className={styles.nameLabel}>
<Trans i18nKey="alerting.alert-rule-name-and-metric.label-name">Name</Trans>
</span>
}
error={errors?.name?.message}
invalid={!!errors.name?.message}
>
@@ -78,7 +82,7 @@ export function RuleDefinitionSection({ type }: { type: RuleFormType }) {
{isGrafanaManagedRuleByType(type) && (
<>
<FolderSelectorV2 />
<LabelsFieldInForm showHelpTooltip onEditClick={() => setShowLabelsEditor(true)} labelVariant="small" />
<LabelsFieldInFormV2 onEditClick={() => setShowLabelsEditor(true)} />
<LabelsEditorModal
isOpen={showLabelsEditor}
onClose={(labelsToUpdate) => {
@@ -128,5 +132,9 @@ function getStyles(theme: GrafanaTheme2) {
fontWeight: 600,
}),
contentIndented: css({ marginLeft: `calc(20px + ${theme.spacing(1)})` }),
nameLabel: css({
fontSize: theme.typography.bodySmall.fontSize,
fontWeight: 500,
}),
};
}
@@ -4,9 +4,7 @@ import { useMemo, useState } from 'react';
import { notificationsAPIv0alpha1 } from '@grafana/alerting/unstable';
import type { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { Button, Combobox, ComboboxOption, Field, Input, Stack, TextArea, useStyles2 } from '@grafana/ui';
import { LinkToContactPoints } from './rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector';
import { Button, Combobox, ComboboxOption, Field, Input, Stack, TextArea, TextLink, useStyles2 } from '@grafana/ui';
export function RuleNotificationSection() {
const styles = useStyles2(getStyles);
@@ -37,7 +35,7 @@ export function RuleNotificationSection() {
<div className={styles.contentIndented}>
<Stack direction="column" gap={2}>
<Field
label={t('alerting.simplified.notification.contact-point', 'Contact point')}
label={t('alerting.simplified.notification.contact-point.label', 'Contact point')}
description={t(
'alerting.simplified.notification.contact-point.description',
'Select who should receive a notification when the alert rule fires'
@@ -67,11 +65,22 @@ export function RuleNotificationSection() {
}
}}
/>
<LinkToContactPoints />
<TextLink
external
href={'/alerting/notifications'}
aria-label={t(
'alerting.link-to-contact-points.aria-label-view-or-create-contact-points',
'View or create contact points'
)}
>
<Trans i18nKey="alerting.link-to-contact-points.view-or-create-contact-points">
View or create contact points
</Trans>
</TextLink>
</Stack>
</Field>
<Field label={t('alerting.simplified.notification.summary', 'Summary (optional)')} noMargin>
<Field label={t('alerting.simplified.notification.summary.label', 'Summary (optional)')} noMargin>
<TextArea
placeholder={t(
'alerting.simplified.notification.summary.placeholder',
@@ -80,7 +89,7 @@ export function RuleNotificationSection() {
/>
</Field>
<Field label={t('alerting.simplified.notification.description', 'Description (optional)')} noMargin>
<Field label={t('alerting.simplified.notification.description.label', 'Description (optional)')} noMargin>
<TextArea
placeholder={t(
'alerting.simplified.notification.description.placeholder',
@@ -89,7 +98,7 @@ export function RuleNotificationSection() {
/>
</Field>
<Field label={t('alerting.simplified.notification.runbook-url', 'Runbook URL (optional)')} noMargin>
<Field label={t('alerting.simplified.notification.runbook-url.label', 'Runbook URL (optional)')} noMargin>
<Input
placeholder={t(
'alerting.simplified.notification.runbook-url.placeholder',
@@ -55,7 +55,7 @@ export function FolderSelectorV2() {
error={errors.folder?.message}
data-testid="folder-picker"
>
<Stack direction="column" alignItems="flex-start" gap={0.5}>
<Stack direction="column" alignItems="flex-start" gap={1}>
<Controller
render={({ field: { ref, ...field } }) => (
<div style={{ width: 420 }}>
@@ -1,7 +1,7 @@
import { useFormContext, useWatch } from 'react-hook-form';
import { useFormContext } from 'react-hook-form';
import { Trans, t } from '@grafana/i18n';
import { Button, Icon, Stack, Text, Tooltip } from '@grafana/ui';
import { Button, Stack, Text } from '@grafana/ui';
import { AIImproveLabelsButtonComponent } from '../../../enterprise-components/AI/AIGenImproveLabelsButton/addAIImproveLabelsButton';
import { RuleFormValues } from '../../../types/rule-form';
@@ -12,18 +12,11 @@ import { LabelsInRule } from './LabelsField';
interface LabelsFieldInFormProps {
onEditClick: () => void;
labelVariant?: 'small' | 'default';
showHelpTooltip?: boolean;
}
export function LabelsFieldInForm({
onEditClick,
labelVariant = 'default',
showHelpTooltip = false,
}: LabelsFieldInFormProps) {
const { control, watch } = useFormContext<RuleFormValues>();
export function LabelsFieldInForm({ onEditClick }: LabelsFieldInFormProps) {
const { watch } = useFormContext<RuleFormValues>();
// Subscribe to label changes so UI updates when modal saves
const labels = useWatch({ control, name: 'labels' }) ?? [];
const labels = watch('labels');
const type = watch('type');
const isRecordingRule = type ? isRecordingRuleByType(type) : false;
@@ -36,48 +29,13 @@ export function LabelsFieldInForm({
'Add labels to your rule for searching, silencing, or routing to a notification policy.'
);
const hasLabels = Array.isArray(labels) && labels.length > 0 && labels.some((label) => label?.key || label?.value);
const hasLabels = Object.keys(labels).length > 0 && labels.some((label) => label.key || label.value);
return (
<Stack direction="column" gap={2}>
<Stack direction="column" gap={1}>
<Text element="h5">
<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>
<Trans i18nKey="alerting.labels-field-in-form.labels">Labels</Trans>
</Text>
<Stack direction={'column'} gap={1}>
<Stack direction={'row'} gap={1}>
@@ -0,0 +1,86 @@
import { useFormContext, useWatch } from 'react-hook-form';
import { Trans, t } from '@grafana/i18n';
import { Button, Field, Stack, Text } from '@grafana/ui';
import { AIImproveLabelsButtonComponent } from '../../../enterprise-components/AI/AIGenImproveLabelsButton/addAIImproveLabelsButton';
import { RuleFormValues } from '../../../types/rule-form';
import { isGrafanaManagedRuleByType, isRecordingRuleByType } from '../../../utils/rules';
import { LabelsInRule } from './LabelsField';
interface LabelsFieldInFormProps {
onEditClick: () => void;
}
export function LabelsFieldInFormV2({ onEditClick }: LabelsFieldInFormProps) {
const { control, watch } = useFormContext<RuleFormValues>();
// Subscribe to label changes so UI updates when modal saves
const labels = useWatch({ control, name: 'labels' }) ?? [];
const type = watch('type');
const isRecordingRule = type ? isRecordingRuleByType(type) : false;
const isGrafanaManaged = type ? isGrafanaManagedRuleByType(type) : false;
const text = isRecordingRule
? t('alerting.alertform.labels.recording', 'Add labels to your rule.')
: t(
'alerting.alertform.labels.alerting',
'Add labels to your rule for searching, silencing, or routing to a notification policy.'
);
const hasLabels = Array.isArray(labels) && labels.length > 0 && labels.some((label) => label?.key || label?.value);
return (
<Field
noMargin
label={
<Stack direction="row" alignItems="center" gap={0.5}>
<Text variant="bodySmall">
<Trans i18nKey="alerting.labels-field-in-form.labels">Labels</Trans>
</Text>
<Text variant="bodySmall" color="secondary">
{t('alerting.common.optional', '(optional)')}
</Text>
</Stack>
}
>
<Stack direction={'column'} gap={2}>
<Stack direction={'column'} gap={1}>
<Stack direction={'row'} gap={1}>
<Text variant="bodySmall" color="secondary">
{text}
</Text>
</Stack>
{isGrafanaManaged && <AIImproveLabelsButtonComponent />}
</Stack>
<Stack>
{hasLabels ? (
<Stack direction="row" gap={1} alignItems="center">
<LabelsInRule labels={labels} />
<Button variant="secondary" type="button" onClick={onEditClick} size="sm">
<Trans i18nKey="alerting.labels-field-in-form.edit-labels">Edit labels</Trans>
</Button>
</Stack>
) : (
<Stack direction="column" gap={0.5} alignItems="start">
<Text color="secondary">
<Trans i18nKey="alerting.labels-field-in-form.no-labels-selected">No labels selected</Trans>
</Text>
<Button
icon="plus"
type="button"
variant="secondary"
onClick={onEditClick}
size="sm"
data-testid="add-labels-button"
>
<Trans i18nKey="alerting.labels-field-in-form.add-labels">Add labels</Trans>
</Button>
</Stack>
)}
</Stack>
</Stack>
</Field>
);
}
+47
View File
@@ -523,6 +523,7 @@
"metric-aria-label-metric": "metric",
"metric-placeholder-recorded-metric": "Give the name of the new recorded metric",
"placeholder-name": "Give your {{namePlaceholder}} a name",
"recording-rule-pattern": "Recording rule name must be valid metric name. It may only contain letters, numbers, and colons. It may not contain whitespace.",
"title-section": "Enter {{entityName}} name"
},
"alert-rules": {
@@ -863,6 +864,8 @@
"export-all": "Export all",
"learn-more": "Learn more",
"loading": "Loading...",
"optional": "(optional)",
"refresh": "Refresh",
"search-by-matchers": "Search by matchers",
"titles": {
"notification-templates": "Notification Templates"
@@ -1076,6 +1079,9 @@
"text-loading-notification-template": "Loading notification template",
"title-error-loading-notification-template": "Error loading notification template"
},
"duration": {
"immediately": "immediately"
},
"duration-quick-pick": {
"none": "None"
},
@@ -2811,6 +2817,47 @@
"simpleCondition": {
"alertCondition": "Alert condition"
},
"simplified": {
"condition": {
"title": "Condition"
},
"continue-in-alerting": "Continue in Alerting",
"create": "Create",
"evaluation": {
"after-breached": "after the condition is initially breached.",
"custom-delay": "with custom delay of",
"custom-delay-invalid": "The delay must be a multiple of the evaluation frequency.",
"every": "Evaluate the rule every",
"fire": "Fire the alert rule",
"immediate-warning": "Immediate firing might lead to unnecessary alerts being sent for temporary issues",
"wait-prefix": "Wait",
"wait-suffix": "after the condition is breached before firing."
},
"notification": {
"contact-point": {
"description": "Select who should receive a notification when the alert rule fires",
"label": "Contact point"
},
"description": {
"label": "Description (optional)",
"placeholder": "Enter a description of what the alert rule does…"
},
"runbook-url": {
"label": "Runbook URL (optional)",
"placeholder": "Enter the webpage where you keep your runbook for the alert…"
},
"select-contact-point": "Select a contact point...",
"summary": {
"label": "Summary (optional)",
"placeholder": "Enter a summary of what happened and why…"
},
"title": "Notification"
},
"rule-definition": "Rule Definition",
"step-number-one": "1",
"step-number-three": "3",
"step-number-two": "2"
},
"smart-alert-type-detector": {
"data-source-managed": "Data source-managed",
"data-sourcemanaged-alert-rules": "Data source-managed alert rules",