Alerting: Add feedback buttons for the new AI helpers (#108207)

* Add alerting ai buttons for cloud

* add tracking for ai buttons usage

* Empty commit to trigger GitHub Actions

* wip

* add FrontendOnly in ff

* update analytics folder

* prettier

* revert ff being frontend only

* add feedback button in templates

* review comments

* address some review comments

* refactor

* revert change and remove comment

* prettier

* update betterer

* remove unused property

* prettier

* update AIFeedbackComponent

* add feedback buttons for alert rule generation with ai

* remove tracking from oss

* fix analytics imports

* udpdate changes in go.mod go.sum

* prettier

* update translations

* update workspace

* fix betterer

* address pr comments

* move most of the ai feedback buttons code to enterprise

* prettier

* fix

* address pr feedback
This commit is contained in:
Sonia Aguilar
2025-07-23 14:38:09 +02:00
committed by GitHub
parent c43a92c1be
commit 7c872f0e8a
10 changed files with 126 additions and 2 deletions
@@ -748,6 +748,11 @@ export interface FeatureToggles {
*/
alertingAIGenAlertRules?: boolean;
/**
* Enable AI-generated feedback from the Grafana UI.
* @default false
*/
alertingAIFeedback?: boolean;
/**
* Enable AI-improve alert rules labels and annotations.
* @default false
*/
+9
View File
@@ -1278,6 +1278,15 @@ var (
HideFromDocs: true,
Expression: "false",
},
{
Name: "alertingAIFeedback",
Description: "Enable AI-generated feedback from the Grafana UI.",
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
HideFromAdminPage: true,
HideFromDocs: true,
Expression: "false",
},
{
Name: "alertingAIImproveAlertRules",
Description: "Enable AI-improve alert rules labels and annotations.",
+1
View File
@@ -167,6 +167,7 @@ crashDetection,experimental,@grafana/observability-traces-and-profiling,false,fa
alertingUIOptimizeReducer,GA,@grafana/alerting-squad,false,false,true
azureMonitorEnableUserAuth,GA,@grafana/partner-datasources,false,false,false
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
alertingAIAnalyzeCentralStateHistory,experimental,@grafana/alerting-squad,false,false,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
167 alertingUIOptimizeReducer GA @grafana/alerting-squad false false true
168 azureMonitorEnableUserAuth GA @grafana/partner-datasources false false false
169 alertingAIGenAlertRules experimental @grafana/alerting-squad false false false
170 alertingAIFeedback experimental @grafana/alerting-squad false false false
171 alertingAIImproveAlertRules experimental @grafana/alerting-squad false false false
172 alertingAIGenTemplates experimental @grafana/alerting-squad false false false
173 alertingAIAnalyzeCentralStateHistory experimental @grafana/alerting-squad false false false
+4
View File
@@ -679,6 +679,10 @@ const (
// Enable AI-generated alert rules.
FlagAlertingAIGenAlertRules = "alertingAIGenAlertRules"
// FlagAlertingAIFeedback
// Enable AI-generated feedback from the Grafana UI.
FlagAlertingAIFeedback = "alertingAIFeedback"
// FlagAlertingAIImproveAlertRules
// Enable AI-improve alert rules labels and annotations.
FlagAlertingAIImproveAlertRules = "alertingAIImproveAlertRules"
+15
View File
@@ -89,6 +89,21 @@
"expression": "false"
}
},
{
"metadata": {
"name": "alertingAIFeedback",
"resourceVersion": "1752820131286",
"creationTimestamp": "2025-07-18T06:28:51Z"
},
"spec": {
"description": "Enable AI-generated feedback from the Grafana UI.",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad",
"hideFromAdminPage": true,
"hideFromDocs": true,
"expression": "false"
}
},
{
"metadata": {
"name": "alertingAIGenAlertRules",
@@ -107,6 +107,9 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
const [payload, setPayload] = useState(defaultPayloadString);
const [payloadFormatError, setPayloadFormatError] = useState<string | null>(null);
// AI feedback state
const [aiGeneratedTemplate, setAiGeneratedTemplate] = useState(false);
const { isProvisioned } = useNotificationTemplateMetadata(originalTemplate);
const originalTemplatePrefill: TemplateFormValues | undefined = originalTemplate
? { title: originalTemplate.title, content: originalTemplate.content }
@@ -167,6 +170,7 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
const handleTemplateGenerated = (template: string) => {
setValue('content', template);
setAiGeneratedTemplate(true);
};
return (
@@ -352,6 +356,8 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
setPayloadFormatError={setPayloadFormatError}
payloadFormatError={payloadFormatError}
className={cx(styles.templatePreview, styles.minEditorSize)}
aiGeneratedTemplate={aiGeneratedTemplate}
setAiGeneratedTemplate={setAiGeneratedTemplate}
/>
</div>
)}
@@ -8,6 +8,7 @@ import { Trans, t } from '@grafana/i18n';
import { Alert, Box, Button, CodeEditor, useStyles2 } from '@grafana/ui';
import { TemplatePreviewErrors, TemplatePreviewResponse, TemplatePreviewResult } from '../../api/templateApi';
import { AIFeedbackButtonComponent } from '../../enterprise-components/AI/addAIFeedbackButton';
import { stringifyErrorLike } from '../../utils/misc';
import { EditorColumnHeader } from '../contact-points/templates/EditorColumnHeader';
@@ -20,6 +21,8 @@ export function TemplatePreview({
payloadFormatError,
setPayloadFormatError,
className,
aiGeneratedTemplate,
setAiGeneratedTemplate,
}: {
payload: string;
templateName: string;
@@ -27,6 +30,8 @@ export function TemplatePreview({
payloadFormatError: string | null;
setPayloadFormatError: (value: React.SetStateAction<string | null>) => void;
className?: string;
aiGeneratedTemplate?: boolean;
setAiGeneratedTemplate?: (aiGeneratedTemplate: boolean) => void;
}) {
const styles = useStyles2(getStyles);
@@ -48,7 +53,10 @@ export function TemplatePreview({
disabled={isLoading}
icon="sync"
aria-label={t('alerting.template-preview.aria-label-refresh-preview', 'Refresh preview')}
onClick={onPreview}
onClick={() => {
onPreview();
setAiGeneratedTemplate?.(false);
}}
size="sm"
variant="secondary"
>
@@ -56,6 +64,9 @@ export function TemplatePreview({
</Button>
}
/>
<div className={styles.viewer.feedbackContainer}>
<AIFeedbackButtonComponent origin="template" shouldShowFeedbackButton={Boolean(aiGeneratedTemplate)} />
</div>
<Box flex={1}>
<AutoSizer disableWidth>
{({ height }) => <div className={styles.viewerContainer({ height })}>{previewToRender}</div>}
@@ -163,6 +174,22 @@ const getStyles = (theme: GrafanaTheme2) => ({
errorText: css({
color: theme.colors.error.text,
}),
feedbackContainer: css({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderTop: `1px solid ${theme.colors.border.medium}`,
backgroundColor: theme.colors.background.secondary,
minHeight: 'auto',
}),
emptyState: css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: theme.colors.text.secondary,
fontSize: theme.typography.bodySmall.fontSize,
}),
},
});
@@ -174,10 +201,12 @@ export function getPreviewResults(
// ERRORS IN JSON OR IN REQUEST (endpoint not available, for example)
const previewErrorRequest = previewError ? stringifyErrorLike(previewError) : undefined;
const errorToRender = payloadFormatError || previewErrorRequest;
const styles = useStyles2(getStyles);
//PREVIEW : RESULTS AND ERRORS
const previewResponseResults = data?.results ?? [];
const previewResponseErrors = data?.errors;
const hasContent = previewResponseResults.length > 0 || previewResponseErrors || errorToRender;
return (
<>
@@ -187,7 +216,12 @@ export function getPreviewResults(
</Alert>
)}
{previewResponseErrors && <PreviewErrorViewer errors={previewResponseErrors} />}
{previewResponseResults && <PreviewResultViewer previews={previewResponseResults} />}
{previewResponseResults.length > 0 && <PreviewResultViewer previews={previewResponseResults} />}
{!hasContent && (
<div className={styles.viewer.emptyState}>
<Trans i18nKey="alerting.template-preview.empty-state">Add template content to see preview</Trans>
</div>
)}
</>
);
}
@@ -40,6 +40,7 @@ import {
RulerGroupUpdatedResponse,
isGrafanaGroupUpdatedResponse,
} from '../../../api/alertRuleModel';
import { AIFeedbackButtonComponent } from '../../../enterprise-components/AI/addAIFeedbackButton';
import { useAddRuleToRuleGroup, useUpdateRuleInRuleGroup } from '../../../hooks/ruleGroup/useUpsertRuleFromRuleGroup';
import {
defaultFormValuesForRuleType,
@@ -244,6 +245,10 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) =>
<Stack direction="column" gap={3}>
{/* Step 1 */}
<AlertRuleNameAndMetric />
{/* AI Feedback: automatically shown for rules generated by AI */}
<AIFeedbackButtonComponent origin="alert-rule" shouldShowFeedbackButton={true} useRouteDetection={true} />
{/* Step 2 */}
<QueryAndExpressionsStep editingExistingRule={!!existing} onDataChange={checkAlertCondition} mode="edit" />
{/* Step 3-4-5 */}
@@ -0,0 +1,43 @@
import { ComponentType, createElement } from 'react';
import { t } from '@grafana/i18n';
import { withErrorBoundary } from '@grafana/ui';
import { AIFeedbackOrigin } from 'app/extensions/alerting/AI/feedback/AIFeedbackComponent';
import { logError } from '../../Analytics';
export interface GenAIFeedbackButtonProps {
origin: AIFeedbackOrigin;
shouldShowFeedbackButton?: boolean;
// If true, the component will use the route detection to determine if the feedback should be shown
// this is necessary for example in alerting rule form, where the feedback button is shown in the same page as the rule form
useRouteDetection?: boolean;
}
let InternalAIFeedbackButtonComponent: ComponentType<GenAIFeedbackButtonProps> | null = null;
export const AIFeedbackButtonComponent: ComponentType<GenAIFeedbackButtonProps> = (props) => {
if (!InternalAIFeedbackButtonComponent) {
return null;
}
// Wrap the component with error boundary
const WrappedComponent = withErrorBoundary(InternalAIFeedbackButtonComponent, {
title: t('alerting.ai.error-boundary.feedback-button', 'AI Feedback Button failed to load'),
style: 'alertbox',
errorLogger: logError,
});
// Provide default value for shouldShowFeedbackButton
const propsWithDefaults = {
shouldShowFeedbackButton: true,
useRouteDetection: false,
...props,
};
return createElement(WrappedComponent, propsWithDefaults);
};
export function addAIFeedbackButton(component: ComponentType<GenAIFeedbackButtonProps> | null) {
InternalAIFeedbackButtonComponent = component;
}
+2
View File
@@ -383,6 +383,7 @@
"ai": {
"error-boundary": {
"alert-rule-button": "AI Alert Rule Button failed to load",
"feedback-button": "AI Feedback Button failed to load",
"improve-annotations-button": "AI Improve Annotations Button failed to load",
"improve-labels-button": "AI Improve Labels Button failed to load",
"template-button": "AI Template Button failed to load",
@@ -2803,6 +2804,7 @@
},
"template-preview": {
"aria-label-refresh-preview": "Refresh preview",
"empty-state": "Add template content to see preview",
"label-preview": "Preview",
"refresh": "Refresh"
},