Alerting: Add feature toggle and extension point (#110141)
* Add feature toggle and extension point * Update ff name for enrichment per rule * update translations * rename to addRuleFormEnrichmentSection * pr feedback * prettier * update translations * revert wrong change in tsconfig
This commit is contained in:
@@ -748,6 +748,11 @@ export interface FeatureToggles {
|
||||
*/
|
||||
alertingAIGenTemplates?: boolean;
|
||||
/**
|
||||
* Enable enrichment per rule in the alerting UI.
|
||||
* @default false
|
||||
*/
|
||||
alertingEnrichmentPerRule?: boolean;
|
||||
/**
|
||||
* Enable AI-analyze central state history.
|
||||
* @default false
|
||||
*/
|
||||
|
||||
@@ -1284,6 +1284,15 @@ var (
|
||||
HideFromDocs: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "alertingEnrichmentPerRule",
|
||||
Description: "Enable enrichment per rule in the alerting UI.",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
HideFromAdminPage: true,
|
||||
HideFromDocs: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "alertingAIAnalyzeCentralStateHistory",
|
||||
Description: "Enable AI-analyze central state history.",
|
||||
|
||||
@@ -167,6 +167,7 @@ alertingAIGenAlertRules,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingAIFeedback,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingAIImproveAlertRules,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingAIGenTemplates,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingEnrichmentPerRule,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingAIAnalyzeCentralStateHistory,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingNotificationsStepMode,GA,@grafana/alerting-squad,false,false,true
|
||||
feedbackButton,experimental,@grafana/grafana-operator-experience-squad,false,false,false
|
||||
|
||||
|
@@ -679,6 +679,10 @@ const (
|
||||
// Enable AI-generated alerting templates.
|
||||
FlagAlertingAIGenTemplates = "alertingAIGenTemplates"
|
||||
|
||||
// FlagAlertingEnrichmentPerRule
|
||||
// Enable enrichment per rule in the alerting UI.
|
||||
FlagAlertingEnrichmentPerRule = "alertingEnrichmentPerRule"
|
||||
|
||||
// FlagAlertingAIAnalyzeCentralStateHistory
|
||||
// Enable AI-analyze central state history.
|
||||
FlagAlertingAIAnalyzeCentralStateHistory = "alertingAIAnalyzeCentralStateHistory"
|
||||
|
||||
@@ -214,6 +214,21 @@
|
||||
"hideFromDocs": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingEnrichmentPerRule",
|
||||
"resourceVersion": "1756206837948",
|
||||
"creationTimestamp": "2025-08-26T11:13:57Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable enrichment per rule in the alerting UI.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"hideFromAdminPage": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingFilterV2",
|
||||
@@ -408,6 +423,22 @@
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingRuleNotificationMessageSectionExtension",
|
||||
"resourceVersion": "1756193222535",
|
||||
"creationTimestamp": "2025-08-26T07:27:02Z",
|
||||
"deletionTimestamp": "2025-08-26T11:13:57Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable rule notification message section extension.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"hideFromAdminPage": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingRulePermanentlyDelete",
|
||||
|
||||
@@ -19,6 +19,7 @@ import DashboardAnnotationField from './DashboardAnnotationField';
|
||||
import { DashboardPicker, PanelDTO, getVisualPanels } from './DashboardPicker';
|
||||
import { NeedHelpInfo } from './NeedHelpInfo';
|
||||
import { RuleEditorSection } from './RuleEditorSection';
|
||||
import { NotificationMessageSectionExtension } from './alert-rule-form/extensions/NotificationMessageSectionExtension';
|
||||
import { useDashboardQuery } from './useDashboardQuery';
|
||||
|
||||
const AnnotationsStep = () => {
|
||||
@@ -165,51 +166,48 @@ const AnnotationsStep = () => {
|
||||
onDeleteClick={handleDeleteDashboardAnnotation}
|
||||
/>
|
||||
)}
|
||||
|
||||
{
|
||||
<div className={styles.annotationValueContainer}>
|
||||
<Field
|
||||
hidden={
|
||||
annotationField.key === Annotation.dashboardUID || annotationField.key === Annotation.panelID
|
||||
<div className={styles.annotationValueContainer}>
|
||||
<Field
|
||||
hidden={
|
||||
annotationField.key === Annotation.dashboardUID || annotationField.key === Annotation.panelID
|
||||
}
|
||||
className={cx(styles.flexRowItemMargin, styles.field)}
|
||||
invalid={!!errors.annotations?.[index]?.value?.message}
|
||||
error={errors.annotations?.[index]?.value?.message}
|
||||
noMargin
|
||||
>
|
||||
<ValueInputComponent
|
||||
data-testid={`annotation-value-${index}`}
|
||||
id={`annotation-${index}`}
|
||||
className={cx(styles.annotationValueInput, { [styles.textarea]: !isUrl })}
|
||||
{...register(`annotations.${index}.value`)}
|
||||
placeholder={
|
||||
isUrl
|
||||
? // eslint-disable-next-line @grafana/i18n/no-untranslated-strings
|
||||
'https://'
|
||||
: (annotationField.key &&
|
||||
t('alerting.annotations-step.placeholder-value-input', 'Enter a {{key}}...', {
|
||||
key: annotationField.key,
|
||||
})) ||
|
||||
t(
|
||||
'alerting.annotations-step.placeholder-value-input-default',
|
||||
'Enter custom annotation content...'
|
||||
)
|
||||
}
|
||||
className={cx(styles.flexRowItemMargin, styles.field)}
|
||||
invalid={!!errors.annotations?.[index]?.value?.message}
|
||||
error={errors.annotations?.[index]?.value?.message}
|
||||
noMargin
|
||||
>
|
||||
<ValueInputComponent
|
||||
data-testid={`annotation-value-${index}`}
|
||||
id={`annotation-${index}`}
|
||||
className={cx(styles.annotationValueInput, { [styles.textarea]: !isUrl })}
|
||||
{...register(`annotations.${index}.value`)}
|
||||
placeholder={
|
||||
isUrl
|
||||
? // eslint-disable-next-line @grafana/i18n/no-untranslated-strings
|
||||
'https://'
|
||||
: (annotationField.key &&
|
||||
t('alerting.annotations-step.placeholder-value-input', 'Enter a {{key}}...', {
|
||||
key: annotationField.key,
|
||||
})) ||
|
||||
t(
|
||||
'alerting.annotations-step.placeholder-value-input-default',
|
||||
'Enter custom annotation content...'
|
||||
)
|
||||
}
|
||||
defaultValue={annotationField.value}
|
||||
/>
|
||||
</Field>
|
||||
{!annotationLabels[annotation] && (
|
||||
<Button
|
||||
type="button"
|
||||
className={styles.deleteAnnotationButton}
|
||||
aria-label={t('alerting.annotations-step.aria-label-delete-annotation', 'delete annotation')}
|
||||
icon="trash-alt"
|
||||
variant="secondary"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
defaultValue={annotationField.value}
|
||||
/>
|
||||
</Field>
|
||||
{!annotationLabels[annotation] && (
|
||||
<Button
|
||||
type="button"
|
||||
className={styles.deleteAnnotationButton}
|
||||
aria-label={t('alerting.annotations-step.aria-label-delete-annotation', 'delete annotation')}
|
||||
icon="trash-alt"
|
||||
variant="secondary"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -242,6 +240,7 @@ const AnnotationsStep = () => {
|
||||
onDismiss={() => setShowPanelSelector(false)}
|
||||
/>
|
||||
)}
|
||||
<NotificationMessageSectionExtension />
|
||||
</Stack>
|
||||
</RuleEditorSection>
|
||||
);
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { withErrorBoundary } from '@grafana/ui';
|
||||
|
||||
import { logError } from '../../../../Analytics';
|
||||
|
||||
export interface NotificationMessageSectionExtensionProps {}
|
||||
|
||||
// Internal variable to store the extension component, for now only one component is supported
|
||||
let InternalNotificationMessageSectionExtension: ComponentType<NotificationMessageSectionExtensionProps> | null = null;
|
||||
|
||||
// This component is used to render the notification message section extension.
|
||||
export const NotificationMessageSectionExtension: ComponentType<NotificationMessageSectionExtensionProps> = (props) => {
|
||||
if (!InternalNotificationMessageSectionExtension) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wrap the component with error boundary
|
||||
const WrappedComponent = withErrorBoundary(InternalNotificationMessageSectionExtension, {
|
||||
title: t(
|
||||
'alerting.enrichment.error-boundary.notification-message-section-extension',
|
||||
'Notification Message Section Extension failed to load'
|
||||
),
|
||||
style: 'alertbox',
|
||||
errorLogger: logError,
|
||||
});
|
||||
|
||||
return <WrappedComponent {...props} />;
|
||||
};
|
||||
|
||||
export function addRuleFormEnrichmentSection(component: ComponentType<NotificationMessageSectionExtensionProps>) {
|
||||
InternalNotificationMessageSectionExtension = component;
|
||||
}
|
||||
@@ -1118,6 +1118,11 @@
|
||||
"new-alert-rule": "New alert rule",
|
||||
"new-recording-rule": "New recording rule"
|
||||
},
|
||||
"enrichment": {
|
||||
"error-boundary": {
|
||||
"notification-message-section-extension": "Notification Message Section Extension failed to load"
|
||||
}
|
||||
},
|
||||
"error-modal": {
|
||||
"failed-to-update-your-configuration": "Failed to update your configuration:",
|
||||
"title-something-went-wrong": "Something went wrong"
|
||||
|
||||
Reference in New Issue
Block a user