Alerting: Add AI buttons in some alerting workflows (#107754)
* Add alerting ai buttons for cloud * add tracking for ai buttons usage * Empty commit to trigger GitHub Actions * add FrontendOnly in ff * update analytics folder * prettier * revert ff being frontend only * review comments * address some review comments * refactor * revert change and remove comment * prettier * update betterer * remove unused property * prettier * address review comments * prettier * update test * fix linter errors * add translations * prettier * update workspace --------- Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
co-authored by
Gilles De Mey
parent
974103c6fa
commit
9c15662cf6
@@ -1280,9 +1280,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/alerting/unified/components/rule-editor/AnnotationKeyInput.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/CloudEvaluationBehavior.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
|
||||
|
||||
@@ -230,7 +230,7 @@ require (
|
||||
require (
|
||||
github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b // @grafana/alerting-backend
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-app-platform-squad @grafana/dashboards-squad
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250716152245-2202c99d7030 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad
|
||||
github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-search-and-storage
|
||||
github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b // @grafana/identity-access-team
|
||||
github.com/grafana/grafana/apps/investigations v0.0.0-20250627191313-2f1a6ae1712b // @fcjack @matryer
|
||||
|
||||
@@ -1611,8 +1611,8 @@ github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b h1:8o
|
||||
github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:q+h3HbmqU/PposW6lq8cMle1v8vuyX1LCMrGzbabHxc=
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b h1:jr+C3epmjhd5Yyob4P1Z/dPaW4LRTkU5UJLXsI4eaeM=
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:WpI7TCck4P2wKTO2WJLBRcfOWvUGvTdxYu3QqS3z7jM=
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250627191313-2f1a6ae1712b h1:FJHoqhrVR/PMS9xv7yYGVi73iTB1VXyV/oIXCUssvMQ=
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:eR8wca74ADgxBrvX0uNpdB1qnPaGx/KhCm4Xj8oqHfQ=
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250716152245-2202c99d7030 h1:ravSwl2XJacO/o2u3IVaMacd0NpL1Msf9ENLcLbLwO0=
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0-20250716152245-2202c99d7030/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4=
|
||||
github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b h1:31MwoIKKT9Ay0ZjbT4lkfcPijiWogUWzXs2EjrCgodI=
|
||||
github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:dLtYBp1pza5HYalezNvzlP8JDeKrZ5BKTonDgEOE0NY=
|
||||
github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b h1:NV8v9xdM/pzjjy+1cLqUseia3bYcvQGh88vZdMW/jA0=
|
||||
|
||||
@@ -749,6 +749,26 @@ export interface FeatureToggles {
|
||||
*/
|
||||
azureMonitorEnableUserAuth?: boolean;
|
||||
/**
|
||||
* Enable AI-generated alert rules.
|
||||
* @default false
|
||||
*/
|
||||
alertingAIGenAlertRules?: boolean;
|
||||
/**
|
||||
* Enable AI-improve alert rules labels and annotations.
|
||||
* @default false
|
||||
*/
|
||||
alertingAIImproveAlertRules?: boolean;
|
||||
/**
|
||||
* Enable AI-generated alerting templates.
|
||||
* @default false
|
||||
*/
|
||||
alertingAIGenTemplates?: boolean;
|
||||
/**
|
||||
* Enable AI-analyze central state history.
|
||||
* @default false
|
||||
*/
|
||||
alertingAIAnalyzeCentralStateHistory?: boolean;
|
||||
/**
|
||||
* Enables simplified step mode in the notifications section
|
||||
* @default true
|
||||
*/
|
||||
|
||||
@@ -1277,6 +1277,42 @@ var (
|
||||
Owner: grafanaPartnerPluginsSquad,
|
||||
Expression: "true", // Enabled by default for now
|
||||
},
|
||||
{
|
||||
Name: "alertingAIGenAlertRules",
|
||||
Description: "Enable AI-generated alert rules.",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
HideFromAdminPage: true,
|
||||
HideFromDocs: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "alertingAIImproveAlertRules",
|
||||
Description: "Enable AI-improve alert rules labels and annotations.",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
HideFromAdminPage: true,
|
||||
HideFromDocs: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "alertingAIGenTemplates",
|
||||
Description: "Enable AI-generated alerting templates.",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
HideFromAdminPage: true,
|
||||
HideFromDocs: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "alertingAIAnalyzeCentralStateHistory",
|
||||
Description: "Enable AI-analyze central state history.",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
HideFromAdminPage: true,
|
||||
HideFromDocs: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "alertingNotificationsStepMode",
|
||||
Description: "Enables simplified step mode in the notifications section",
|
||||
|
||||
@@ -167,6 +167,10 @@ enableSCIM,preview,@grafana/identity-access-team,false,false,false
|
||||
crashDetection,experimental,@grafana/observability-traces-and-profiling,false,false,true
|
||||
alertingUIOptimizeReducer,GA,@grafana/alerting-squad,false,false,true
|
||||
azureMonitorEnableUserAuth,GA,@grafana/partner-datasources,false,false,false
|
||||
alertingAIGenAlertRules,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
|
||||
alertingNotificationsStepMode,GA,@grafana/alerting-squad,false,false,true
|
||||
feedbackButton,experimental,@grafana/grafana-operator-experience-squad,false,false,false
|
||||
unifiedStorageSearchUI,experimental,@grafana/search-and-storage,false,false,false
|
||||
|
||||
|
@@ -679,6 +679,22 @@ const (
|
||||
// Enables user auth for Azure Monitor datasource only
|
||||
FlagAzureMonitorEnableUserAuth = "azureMonitorEnableUserAuth"
|
||||
|
||||
// FlagAlertingAIGenAlertRules
|
||||
// Enable AI-generated alert rules.
|
||||
FlagAlertingAIGenAlertRules = "alertingAIGenAlertRules"
|
||||
|
||||
// FlagAlertingAIImproveAlertRules
|
||||
// Enable AI-improve alert rules labels and annotations.
|
||||
FlagAlertingAIImproveAlertRules = "alertingAIImproveAlertRules"
|
||||
|
||||
// FlagAlertingAIGenTemplates
|
||||
// Enable AI-generated alerting templates.
|
||||
FlagAlertingAIGenTemplates = "alertingAIGenTemplates"
|
||||
|
||||
// FlagAlertingAIAnalyzeCentralStateHistory
|
||||
// Enable AI-analyze central state history.
|
||||
FlagAlertingAIAnalyzeCentralStateHistory = "alertingAIAnalyzeCentralStateHistory"
|
||||
|
||||
// FlagAlertingNotificationsStepMode
|
||||
// Enables simplified step mode in the notifications section
|
||||
FlagAlertingNotificationsStepMode = "alertingNotificationsStepMode"
|
||||
|
||||
@@ -71,6 +71,78 @@
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingAIAnalyzeCentralStateHistory",
|
||||
"resourceVersion": "1752215431801",
|
||||
"creationTimestamp": "2025-07-07T14:40:14Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable AI-analyze central state history.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"hideFromAdminPage": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingAIGenAlertRules",
|
||||
"resourceVersion": "1752215431801",
|
||||
"creationTimestamp": "2025-07-07T14:40:14Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable AI-generated alert rules.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"hideFromAdminPage": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingAIGenTemplates",
|
||||
"resourceVersion": "1752215431801",
|
||||
"creationTimestamp": "2025-07-07T14:40:14Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable AI-generated alerting templates.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"hideFromAdminPage": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingAIImproveAlertRules",
|
||||
"resourceVersion": "1752215431801",
|
||||
"creationTimestamp": "2025-07-07T14:40:14Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable AI-improve alert rules labels and annotations.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"hideFromAdminPage": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingBacktesting",
|
||||
@@ -963,6 +1035,23 @@
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "enableAppChromeExtensions",
|
||||
"resourceVersion": "1751899214406",
|
||||
"creationTimestamp": "2025-07-07T14:40:14Z",
|
||||
"deletionTimestamp": "2025-07-07T15:15:21Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Set this to true to enable all app chrome extensions registered by plugins.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/plugins-platform-backend",
|
||||
"frontend": true,
|
||||
"hideFromAdminPage": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "enableDatagridEditing",
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { ActiveTab as ContactPointsActiveTabs } from 'app/features/alerting/unified/components/contact-points/ContactPoints';
|
||||
import { TestTemplateAlert } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { AITemplateButtonComponent } from '../../enterprise-components/AI/AIGenTemplateButton/addAITemplateButton';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||
import { makeAMLink, stringifyErrorLike } from '../../utils/misc';
|
||||
import { ProvisionedResource, ProvisioningAlert } from '../Provisioning';
|
||||
@@ -164,6 +165,10 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
|
||||
setValue('content', newValue);
|
||||
};
|
||||
|
||||
const handleTemplateGenerated = (template: string) => {
|
||||
setValue('content', template);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormProvider {...formApi}>
|
||||
@@ -275,6 +280,13 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)}
|
||||
{/* GenAI button – only available for Grafana Alertmanager and enterprise */}
|
||||
{isGrafanaAlertManager && (
|
||||
<AITemplateButtonComponent
|
||||
onTemplateGenerated={handleTemplateGenerated}
|
||||
disabled={isProvisioned}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
icon="question-circle"
|
||||
size="sm"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Field, Input, Stack, Text, TextArea, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { DashboardModel } from '../../../../dashboard/state/DashboardModel';
|
||||
import { AIImproveAnnotationsButtonComponent } from '../../enterprise-components/AI/AIGenImproveAnnotationsButton/addAIImproveAnnotationsButton';
|
||||
import { RuleFormValues } from '../../types/rule-form';
|
||||
import { Annotation, annotationLabels } from '../../utils/constants';
|
||||
import { isGrafanaManagedRuleByType } from '../../utils/rules';
|
||||
@@ -138,6 +139,7 @@ const AnnotationsStep = () => {
|
||||
fullWidth
|
||||
>
|
||||
<Stack direction="column" gap={1}>
|
||||
{isGrafanaManagedRuleByType(type) && <AIImproveAnnotationsButtonComponent />}
|
||||
{fields.map((annotationField, index: number) => {
|
||||
const isUrl = annotations[index]?.key?.toLocaleLowerCase().endsWith('url');
|
||||
const ValueInputComponent = isUrl ? Input : TextArea;
|
||||
@@ -173,6 +175,7 @@ const AnnotationsStep = () => {
|
||||
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}`}
|
||||
|
||||
+18
-11
@@ -3,8 +3,9 @@ import { useFormContext } from 'react-hook-form';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Stack, Text } from '@grafana/ui';
|
||||
|
||||
import { AIImproveLabelsButtonComponent } from '../../../enterprise-components/AI/AIGenImproveLabelsButton/addAIImproveLabelsButton';
|
||||
import { RuleFormValues } from '../../../types/rule-form';
|
||||
import { isRecordingRuleByType } from '../../../utils/rules';
|
||||
import { isGrafanaManagedRuleByType, isRecordingRuleByType } from '../../../utils/rules';
|
||||
import { NeedHelpInfo } from '../NeedHelpInfo';
|
||||
|
||||
import { LabelsInRule } from './LabelsField';
|
||||
@@ -19,6 +20,7 @@ export function LabelsFieldInForm({ onEditClick }: LabelsFieldInFormProps) {
|
||||
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.')
|
||||
@@ -35,17 +37,22 @@ export function LabelsFieldInForm({ onEditClick }: LabelsFieldInFormProps) {
|
||||
<Text element="h5">
|
||||
<Trans i18nKey="alerting.labels-field-in-form.labels">Labels</Trans>
|
||||
</Text>
|
||||
<Stack direction={'row'} gap={1}>
|
||||
<Text variant="bodySmall" color="secondary">
|
||||
{text}
|
||||
</Text>
|
||||
<NeedHelpInfo
|
||||
externalLink={'https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/annotation-label/'}
|
||||
linkText={`Read about labels`}
|
||||
contentText="The dropdown only displays labels that you have previously used for alerts.
|
||||
<Stack direction={'column'} gap={1}>
|
||||
<Stack direction={'row'} gap={1}>
|
||||
<Text variant="bodySmall" color="secondary">
|
||||
{text}
|
||||
</Text>
|
||||
<NeedHelpInfo
|
||||
externalLink={
|
||||
'https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/annotation-label/'
|
||||
}
|
||||
linkText={`Read about labels`}
|
||||
contentText="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."
|
||||
title={t('alerting.labels-field-in-form.title-labels', 'Labels')}
|
||||
/>
|
||||
title={t('alerting.labels-field-in-form.title-labels', 'Labels')}
|
||||
/>
|
||||
</Stack>
|
||||
{isGrafanaManaged && <AIImproveLabelsButtonComponent />}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
|
||||
+41
-28
@@ -25,6 +25,7 @@ import {
|
||||
|
||||
import { trackUseCentralHistoryFilterByClicking, trackUseCentralHistoryMaxEventsReached } from '../../../Analytics';
|
||||
import { stateHistoryApi } from '../../../api/stateHistoryApi';
|
||||
import { AITriageButtonComponent } from '../../../enterprise-components/AI/AIGenTriageButton/addAITriageButton';
|
||||
import { usePagination } from '../../../hooks/usePagination';
|
||||
import { combineMatcherStrings } from '../../../utils/alertmanager';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource';
|
||||
@@ -93,7 +94,7 @@ export const HistoryEventsList = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack direction="column" gap={0.5}>
|
||||
{maximumEventsReached && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
@@ -107,7 +108,7 @@ export const HistoryEventsList = ({
|
||||
)}
|
||||
<LoadingIndicator visible={isLoading} />
|
||||
<HistoryLogEvents logRecords={historyRecords} addFilter={addFilter} timeRange={timeRange} />
|
||||
</>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -124,9 +125,17 @@ interface HistoryLogEventsProps {
|
||||
}
|
||||
function HistoryLogEvents({ logRecords, addFilter, timeRange }: HistoryLogEventsProps) {
|
||||
const { page, pageItems, numberOfPages, onPageChange } = usePagination(logRecords, 1, PAGE_SIZE);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Stack direction="column" gap={0}>
|
||||
<ListHeader />
|
||||
<div className={styles.headerContainer}>
|
||||
<ListHeader />
|
||||
|
||||
<div className={styles.triageButtonContainer}>
|
||||
<AITriageButtonComponent logRecords={logRecords} timeRange={timeRange} />
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
{pageItems.map((record) => {
|
||||
return (
|
||||
@@ -148,28 +157,26 @@ function HistoryLogEvents({ logRecords, addFilter, timeRange }: HistoryLogEvents
|
||||
function ListHeader() {
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
<div className={styles.headerWrapper}>
|
||||
<div className={styles.mainHeader}>
|
||||
<div className={styles.timeCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.timestamp">Timestamp</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.transitionCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.state">State</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.alertNameCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.alert-rule">Alert rule</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.labelsCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.instance">Instance</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.mainHeader}>
|
||||
<div className={styles.timeCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.timestamp">Timestamp</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.transitionCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.state">State</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.alertNameCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.alert-rule">Alert rule</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.labelsCol}>
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.central-alert-history.details.header.instance">Instance</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -482,9 +489,6 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}),
|
||||
headerWrapper: css({
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
}),
|
||||
mainHeader: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
@@ -494,6 +498,15 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
padding: `${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(1)} 0`,
|
||||
gap: theme.spacing(0.5),
|
||||
}),
|
||||
headerContainer: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
}),
|
||||
triageButtonContainer: css({
|
||||
padding: `${theme.spacing(1)} ${theme.spacing(2)}`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { AIAlertRuleButtonComponent, GenAIAlertRuleButtonProps, addAIAlertRuleButton } from './addAIAlertRuleButton';
|
||||
|
||||
// Component that throws an error for testing
|
||||
const ThrowingComponent: ComponentType<GenAIAlertRuleButtonProps> = () => {
|
||||
throw new Error('Test error from AI component');
|
||||
};
|
||||
|
||||
// Component that renders normally
|
||||
const WorkingComponent: ComponentType<GenAIAlertRuleButtonProps> = () => {
|
||||
return <div>AI Alert Rule Button</div>;
|
||||
};
|
||||
|
||||
describe('AIAlertRuleButtonComponent Error Boundary', () => {
|
||||
beforeEach(() => {
|
||||
addAIAlertRuleButton(null);
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should render null when no component is registered', () => {
|
||||
const { container } = render(<AIAlertRuleButtonComponent />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render the registered component when it works correctly', () => {
|
||||
addAIAlertRuleButton(WorkingComponent);
|
||||
render(<AIAlertRuleButtonComponent />);
|
||||
expect(screen.getByText('AI Alert Rule Button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should gracefully handle errors from AI components with error boundary', () => {
|
||||
addAIAlertRuleButton(ThrowingComponent);
|
||||
|
||||
// Render the component, it should not crash the page
|
||||
render(<AIAlertRuleButtonComponent />);
|
||||
|
||||
expect(screen.getByText('AI Alert Rule Button failed to load')).toBeInTheDocument();
|
||||
// Check for error alert role instead of direct DOM access
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { ComponentType, createElement } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { withErrorBoundary } from '@grafana/ui';
|
||||
|
||||
import { logError } from '../../../Analytics';
|
||||
|
||||
export interface GenAIAlertRuleButtonProps {}
|
||||
|
||||
// Internal variable to store the actual component
|
||||
let InternalAIAlertRuleButtonComponent: ComponentType<GenAIAlertRuleButtonProps> | null = null;
|
||||
|
||||
export const AIAlertRuleButtonComponent: ComponentType<GenAIAlertRuleButtonProps> = (props) => {
|
||||
if (!InternalAIAlertRuleButtonComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wrap the component with error boundary
|
||||
const WrappedComponent = withErrorBoundary(InternalAIAlertRuleButtonComponent, {
|
||||
title: t('alerting.ai.error-boundary.alert-rule-button', 'AI Alert Rule Button failed to load'),
|
||||
style: 'alertbox',
|
||||
errorLogger: logError,
|
||||
});
|
||||
|
||||
return createElement(WrappedComponent, props);
|
||||
};
|
||||
|
||||
export function addAIAlertRuleButton(component: ComponentType<GenAIAlertRuleButtonProps> | null) {
|
||||
InternalAIAlertRuleButtonComponent = component;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import {
|
||||
AIImproveAnnotationsButtonComponent,
|
||||
GenAIImproveAnnotationsButtonProps,
|
||||
addAIImproveAnnotationsButton,
|
||||
} from './addAIImproveAnnotationsButton';
|
||||
|
||||
// Component that throws an error for testing
|
||||
const ThrowingComponent: ComponentType<GenAIImproveAnnotationsButtonProps> = () => {
|
||||
throw new Error('Test error from AI component');
|
||||
};
|
||||
|
||||
// Component that renders normally
|
||||
const WorkingComponent: ComponentType<GenAIImproveAnnotationsButtonProps> = () => {
|
||||
return <div>AI Improve Annotations Button</div>;
|
||||
};
|
||||
|
||||
describe('AIImproveAnnotationsButtonComponent Error Boundary', () => {
|
||||
beforeEach(() => {
|
||||
addAIImproveAnnotationsButton(null);
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should render null when no component is registered', () => {
|
||||
const { container } = render(<AIImproveAnnotationsButtonComponent />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render the registered component when it works correctly', () => {
|
||||
addAIImproveAnnotationsButton(WorkingComponent);
|
||||
render(<AIImproveAnnotationsButtonComponent />);
|
||||
expect(screen.getByText('AI Improve Annotations Button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should gracefully handle errors from AI components with error boundary', () => {
|
||||
addAIImproveAnnotationsButton(ThrowingComponent);
|
||||
|
||||
// Render the component, it should not crash the page
|
||||
render(<AIImproveAnnotationsButtonComponent />);
|
||||
|
||||
expect(screen.getByText('AI Improve Annotations Button failed to load')).toBeInTheDocument();
|
||||
// Check for error alert role instead of direct DOM access
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { ComponentType, createElement } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { withErrorBoundary } from '@grafana/ui';
|
||||
|
||||
import { logError } from '../../../Analytics';
|
||||
|
||||
export interface GenAIImproveAnnotationsButtonProps {}
|
||||
|
||||
// Internal variable to store the actual component
|
||||
let InternalAIImproveAnnotationsButtonComponent: ComponentType<GenAIImproveAnnotationsButtonProps> | null = null;
|
||||
|
||||
export const AIImproveAnnotationsButtonComponent: ComponentType<GenAIImproveAnnotationsButtonProps> = (props) => {
|
||||
if (!InternalAIImproveAnnotationsButtonComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wrap the component with error boundary
|
||||
const WrappedComponent = withErrorBoundary(InternalAIImproveAnnotationsButtonComponent, {
|
||||
title: t('alerting.ai.error-boundary.improve-annotations-button', 'AI Improve Annotations Button failed to load'),
|
||||
style: 'alertbox',
|
||||
errorLogger: logError,
|
||||
});
|
||||
|
||||
return createElement(WrappedComponent, props);
|
||||
};
|
||||
|
||||
export function addAIImproveAnnotationsButton(component: ComponentType<GenAIImproveAnnotationsButtonProps> | null) {
|
||||
InternalAIImproveAnnotationsButtonComponent = component;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import {
|
||||
AIImproveLabelsButtonComponent,
|
||||
GenAIImproveLabelsButtonProps,
|
||||
addAIImproveLabelsButton,
|
||||
} from './addAIImproveLabelsButton';
|
||||
|
||||
// Component that throws an error for testing
|
||||
const ThrowingComponent: ComponentType<GenAIImproveLabelsButtonProps> = () => {
|
||||
throw new Error('Test error from AI component');
|
||||
};
|
||||
|
||||
// Component that renders normally
|
||||
const WorkingComponent: ComponentType<GenAIImproveLabelsButtonProps> = () => {
|
||||
return <div>AI Improve Labels Button</div>;
|
||||
};
|
||||
|
||||
describe('AIImproveLabelsButtonComponent Error Boundary', () => {
|
||||
beforeEach(() => {
|
||||
addAIImproveLabelsButton(null);
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should render null when no component is registered', () => {
|
||||
const { container } = render(<AIImproveLabelsButtonComponent />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render the registered component when it works correctly', () => {
|
||||
addAIImproveLabelsButton(WorkingComponent);
|
||||
render(<AIImproveLabelsButtonComponent />);
|
||||
expect(screen.getByText('AI Improve Labels Button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should gracefully handle errors from AI components with error boundary', () => {
|
||||
addAIImproveLabelsButton(ThrowingComponent);
|
||||
|
||||
// Render the component, it should not crash the page
|
||||
render(<AIImproveLabelsButtonComponent />);
|
||||
|
||||
expect(screen.getByText('AI Improve Labels Button failed to load')).toBeInTheDocument();
|
||||
// Check for error alert role instead of direct DOM access
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { ComponentType, createElement } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { withErrorBoundary } from '@grafana/ui';
|
||||
|
||||
import { logError } from '../../../Analytics';
|
||||
|
||||
export interface GenAIImproveLabelsButtonProps {}
|
||||
|
||||
// Internal variable to store the actual component
|
||||
let InternalAIImproveLabelsButtonComponent: ComponentType<GenAIImproveLabelsButtonProps> | null = null;
|
||||
|
||||
export const AIImproveLabelsButtonComponent: ComponentType<GenAIImproveLabelsButtonProps> = (props) => {
|
||||
if (!InternalAIImproveLabelsButtonComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wrap the component with error boundary
|
||||
const WrappedComponent = withErrorBoundary(InternalAIImproveLabelsButtonComponent, {
|
||||
title: t('alerting.ai.error-boundary.improve-labels-button', 'AI Improve Labels Button failed to load'),
|
||||
style: 'alertbox',
|
||||
errorLogger: logError,
|
||||
});
|
||||
|
||||
return createElement(WrappedComponent, props);
|
||||
};
|
||||
|
||||
export function addAIImproveLabelsButton(component: ComponentType<GenAIImproveLabelsButtonProps> | null) {
|
||||
InternalAIImproveLabelsButtonComponent = component;
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { AITemplateButtonComponent, GenAITemplateButtonProps, addAITemplateButton } from './addAITemplateButton';
|
||||
|
||||
// Component that throws an error for testing
|
||||
const ThrowingComponent: ComponentType<GenAITemplateButtonProps> = () => {
|
||||
throw new Error('Test error from AI component');
|
||||
};
|
||||
|
||||
// Component that renders normally
|
||||
const WorkingComponent: ComponentType<GenAITemplateButtonProps> = () => {
|
||||
return <div>AI Template Button</div>;
|
||||
};
|
||||
|
||||
const mockProps: GenAITemplateButtonProps = {
|
||||
onTemplateGenerated: jest.fn(),
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
describe('AITemplateButtonComponent Error Boundary', () => {
|
||||
beforeEach(() => {
|
||||
addAITemplateButton(null);
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should render null when no component is registered', () => {
|
||||
const { container } = render(<AITemplateButtonComponent {...mockProps} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render the registered component when it works correctly', () => {
|
||||
addAITemplateButton(WorkingComponent);
|
||||
render(<AITemplateButtonComponent {...mockProps} />);
|
||||
expect(screen.getByText('AI Template Button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should gracefully handle errors from AI components with error boundary', () => {
|
||||
addAITemplateButton(ThrowingComponent);
|
||||
|
||||
// Render the component, it should not crash the page
|
||||
render(<AITemplateButtonComponent {...mockProps} />);
|
||||
|
||||
expect(screen.getByText('AI Template Button failed to load')).toBeInTheDocument();
|
||||
// Check for error alert role instead of direct DOM access
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { ComponentType, createElement } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { withErrorBoundary } from '@grafana/ui';
|
||||
|
||||
import { logError } from '../../../Analytics';
|
||||
|
||||
export interface GenAITemplateButtonProps {
|
||||
onTemplateGenerated: (template: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
let InternalAITemplateButtonComponent: ComponentType<GenAITemplateButtonProps> | null = null;
|
||||
|
||||
// this is the component that is used by the consumer in the grafana repo
|
||||
export const AITemplateButtonComponent: ComponentType<GenAITemplateButtonProps> = (props) => {
|
||||
if (!InternalAITemplateButtonComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wrap the component with error boundary
|
||||
const WrappedComponent = withErrorBoundary(InternalAITemplateButtonComponent, {
|
||||
title: t('alerting.ai.error-boundary.template-button', 'AI Template Button failed to load'),
|
||||
style: 'alertbox',
|
||||
errorLogger: logError,
|
||||
});
|
||||
|
||||
return createElement(WrappedComponent, props);
|
||||
};
|
||||
|
||||
export function addAITemplateButton(component: ComponentType<GenAITemplateButtonProps> | null) {
|
||||
InternalAITemplateButtonComponent = component;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { dateTime } from '@grafana/data';
|
||||
|
||||
import { AITriageButtonComponent, GenAITriageButtonProps, addAITriageButton } from './addAITriageButton';
|
||||
|
||||
// Component that throws an error for testing
|
||||
const ThrowingComponent: ComponentType<GenAITriageButtonProps> = () => {
|
||||
throw new Error('Test error from AI component');
|
||||
};
|
||||
|
||||
// Component that renders normally
|
||||
const WorkingComponent: ComponentType<GenAITriageButtonProps> = () => {
|
||||
return <div>AI Triage Button</div>;
|
||||
};
|
||||
|
||||
const mockProps: GenAITriageButtonProps = {
|
||||
logRecords: [],
|
||||
timeRange: {
|
||||
from: dateTime(1681300292392),
|
||||
to: dateTime(1681300293392),
|
||||
raw: {
|
||||
from: 'now-1s',
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('AITriageButtonComponent Error Boundary', () => {
|
||||
beforeEach(() => {
|
||||
addAITriageButton(null);
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should render null when no component is registered', () => {
|
||||
const { container } = render(<AITriageButtonComponent {...mockProps} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render the registered component when it works correctly', () => {
|
||||
addAITriageButton(WorkingComponent);
|
||||
render(<AITriageButtonComponent {...mockProps} />);
|
||||
expect(screen.getByText('AI Triage Button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should gracefully handle errors from AI components with error boundary', () => {
|
||||
addAITriageButton(ThrowingComponent);
|
||||
|
||||
// Render the component, it should not crash the page
|
||||
render(<AITriageButtonComponent {...mockProps} />);
|
||||
|
||||
expect(screen.getByText('AI Triage Button failed to load')).toBeInTheDocument();
|
||||
// Check for error alert role instead of direct DOM access
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { ComponentType, createElement } from 'react';
|
||||
|
||||
import { TimeRange } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { withErrorBoundary } from '@grafana/ui';
|
||||
|
||||
import { logError } from '../../../Analytics';
|
||||
import { LogRecord } from '../../../components/rules/state-history/common';
|
||||
|
||||
export interface GenAITriageButtonProps {
|
||||
logRecords: LogRecord[];
|
||||
timeRange: TimeRange;
|
||||
}
|
||||
|
||||
let InternalAITriageButtonComponent: ComponentType<GenAITriageButtonProps> | null = null;
|
||||
|
||||
export const AITriageButtonComponent: ComponentType<GenAITriageButtonProps> = (props) => {
|
||||
if (!InternalAITriageButtonComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wrap the component with error boundary
|
||||
const WrappedComponent = withErrorBoundary(InternalAITriageButtonComponent, {
|
||||
title: t('alerting.ai.error-boundary.triage-button', 'AI Triage Button failed to load'),
|
||||
style: 'alertbox',
|
||||
errorLogger: logError,
|
||||
});
|
||||
|
||||
return createElement(WrappedComponent, props);
|
||||
};
|
||||
|
||||
export function addAITriageButton(component: ComponentType<GenAITriageButtonProps> | null) {
|
||||
InternalAITriageButtonComponent = component;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { RuleListErrors } from '../components/rules/RuleListErrors';
|
||||
import { RuleListGroupView } from '../components/rules/RuleListGroupView';
|
||||
import { RuleListStateView } from '../components/rules/RuleListStateView';
|
||||
import { RuleStats } from '../components/rules/RuleStats';
|
||||
import { AIAlertRuleButtonComponent } from '../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton';
|
||||
import { shouldUsePrometheusRulesPrimary } from '../featureToggles';
|
||||
import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities';
|
||||
import { useCombinedRuleNamespaces } from '../hooks/useCombinedRuleNamespaces';
|
||||
@@ -174,13 +175,16 @@ export function CreateAlertButton() {
|
||||
|
||||
if (canCreateGrafanaRules || canCreateCloudRules) {
|
||||
return (
|
||||
<LinkButton
|
||||
href={urlUtil.renderUrl('alerting/new/alerting', { returnTo: location.pathname + location.search })}
|
||||
icon="plus"
|
||||
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
|
||||
>
|
||||
<Trans i18nKey="alerting.rule-list.new-alert-rule">New alert rule</Trans>
|
||||
</LinkButton>
|
||||
<Stack direction="row" gap={1}>
|
||||
<LinkButton
|
||||
href={urlUtil.renderUrl('alerting/new/alerting', { returnTo: location.pathname + location.search })}
|
||||
icon="plus"
|
||||
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
|
||||
>
|
||||
<Trans i18nKey="alerting.rule-list.new-alert-rule">New alert rule</Trans>
|
||||
</LinkButton>
|
||||
{canCreateGrafanaRules && AIAlertRuleButtonComponent && <AIAlertRuleButtonComponent />}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button, Dropdown, Icon, LinkButton, Menu, Stack } from '@grafana/ui';
|
||||
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
|
||||
import RulesFilter from '../components/rules/Filter/RulesFilter';
|
||||
import { useListViewMode } from '../components/rules/Filter/RulesViewModeSelector';
|
||||
import { AIAlertRuleButtonComponent } from '../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton';
|
||||
import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities';
|
||||
import { useRulesFilter } from '../hooks/useFilteredRules';
|
||||
import { isAdmin } from '../utils/misc';
|
||||
@@ -86,6 +87,7 @@ export function RuleListActions() {
|
||||
<Trans i18nKey="alerting.rule-list.new-alert-rule">New alert rule</Trans>
|
||||
</LinkButton>
|
||||
)}
|
||||
{canCreateGrafanaRules && <AIAlertRuleButtonComponent />}
|
||||
<Dropdown overlay={moreActionsMenu}>
|
||||
<Button variant="secondary">
|
||||
<Trans i18nKey="alerting.rule-list.more">More</Trans> <Icon name="angle-down" />
|
||||
|
||||
@@ -6,7 +6,7 @@ import { llm } from '@grafana/llm';
|
||||
import { createMonitoringLogger } from '@grafana/runtime';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
|
||||
import { isLLMPluginEnabled, DEFAULT_LLM_MODEL } from './utils';
|
||||
import { DEFAULT_LLM_MODEL, isLLMPluginEnabled } from './utils';
|
||||
|
||||
// Declared instead of imported from utils to make this hook modular
|
||||
// Ideally we will want to move the hook itself to a different scope later.
|
||||
|
||||
@@ -380,6 +380,15 @@
|
||||
"add-button": {
|
||||
"add-more": "Add more"
|
||||
},
|
||||
"ai": {
|
||||
"error-boundary": {
|
||||
"alert-rule-button": "AI Alert Rule 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",
|
||||
"triage-button": "AI Triage Button failed to load"
|
||||
}
|
||||
},
|
||||
"alert": {
|
||||
"alert-state": "Alert state",
|
||||
"annotations": "Annotations",
|
||||
|
||||
Reference in New Issue
Block a user