From 31114fb47ced7afbd559afe00a49f40914cd7acb Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Mon, 1 Sep 2025 11:33:33 +0200 Subject: [PATCH] Alerting: Add Triage feature toggle (#110326) * Add state history config to frontend config object * Add alertingTriage feature toggle * Add Triage menu entry * Add old state history config props for backward compatibility --- packages/grafana-data/src/types/config.ts | 19 +++++++++++++----- .../src/types/featureToggles.gen.ts | 5 +++++ packages/grafana-runtime/src/config.ts | 12 +++++++++-- pkg/api/dtos/frontend_settings.go | 20 ++++++++++++++----- pkg/api/frontendsettings.go | 12 +++++++++++ pkg/services/featuremgmt/registry.go | 10 ++++++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ pkg/services/featuremgmt/toggles_gen.json | 16 +++++++++++++++ pkg/services/navtree/navtreeimpl/navtree.go | 8 ++++++++ public/app/features/alerting/routes.tsx | 9 +++++++++ .../components/rule-viewer/tabs/History.tsx | 4 ++-- .../unified/hooks/useStateHistoryModal.tsx | 4 ++-- 13 files changed, 108 insertions(+), 16 deletions(-) diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 2ef0d54b2de..87136be6e1c 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -100,15 +100,24 @@ export interface GrafanaJavascriptAgentConfig { apiKey: string; } +export interface UnifiedAlertingStateHistoryConfig { + backend?: string; + primary?: string; + prometheusTargetDatasourceUID?: string; + prometheusMetricName?: string; +} + export interface UnifiedAlertingConfig { minInterval: string; - // will be undefined if alerStateHistory is not enabled - alertStateHistoryBackend?: string; - // will be undefined if implementation is not "multiple" - alertStateHistoryPrimary?: string; + stateHistory?: UnifiedAlertingStateHistoryConfig; recordingRulesEnabled?: boolean; - // will be undefined if no default datasource is configured defaultRecordingRulesTargetDatasourceUID?: string; + + // Backward compatibility aliases - deprecated + /** @deprecated Use stateHistory.backend instead */ + alertStateHistoryBackend?: string; + /** @deprecated Use stateHistory.primary instead */ + alertStateHistoryPrimary?: string; } /** Supported OAuth services diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 2523471d4a4..85e9c6c1255 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1106,4 +1106,9 @@ export interface FeatureToggles { * @default false */ teamFolders?: boolean; + /** + * Enables the alerting triage feature + * @default false + */ + alertingTriage?: boolean; } diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index ba898a0860e..ec6e967cb7a 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -186,10 +186,18 @@ export class GrafanaBootConfig { unifiedAlertingEnabled = false; unifiedAlerting: UnifiedAlertingConfig = { minInterval: '', - alertStateHistoryBackend: undefined, - alertStateHistoryPrimary: undefined, + stateHistory: { + backend: undefined, + primary: undefined, + prometheusTargetDatasourceUID: undefined, + prometheusMetricName: undefined, + }, recordingRulesEnabled: false, defaultRecordingRulesTargetDatasourceUID: undefined, + + // Backward compatibility fields - populated by backend + alertStateHistoryBackend: undefined, + alertStateHistoryPrimary: undefined, }; applicationInsightsConnectionString?: string; applicationInsightsEndpointUrl?: string; diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index 2efeb995191..601654dc566 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -93,12 +93,22 @@ type FrontendSettingsAnalyticsDTO struct { Enabled bool `json:"enabled"` } +type FrontendSettingsUnifiedAlertingStateHistoryDTO struct { + Backend string `json:"backend,omitempty"` + Primary string `json:"primary,omitempty"` + PrometheusTargetDatasourceUID string `json:"prometheusTargetDatasourceUID,omitempty"` + PrometheusMetricName string `json:"prometheusMetricName,omitempty"` +} + type FrontendSettingsUnifiedAlertingDTO struct { - MinInterval string `json:"minInterval"` - AlertStateHistoryBackend string `json:"alertStateHistoryBackend,omitempty"` - AlertStateHistoryPrimary string `json:"alertStateHistoryPrimary,omitempty"` - RecordingRulesEnabled bool `json:"recordingRulesEnabled"` - DefaultRecordingRulesTargetDatasourceUID string `json:"defaultRecordingRulesTargetDatasourceUID,omitempty"` + MinInterval string `json:"minInterval"` + StateHistory *FrontendSettingsUnifiedAlertingStateHistoryDTO `json:"stateHistory,omitempty"` + RecordingRulesEnabled bool `json:"recordingRulesEnabled"` + DefaultRecordingRulesTargetDatasourceUID string `json:"defaultRecordingRulesTargetDatasourceUID,omitempty"` + + // Backward compatibility fields - deprecated + AlertStateHistoryBackend string `json:"alertStateHistoryBackend,omitempty"` + AlertStateHistoryPrimary string `json:"alertStateHistoryPrimary,omitempty"` } // Enterprise-only diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 7e65ffb38ce..b9fff7d46fd 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -348,6 +348,18 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro } if hs.Cfg.UnifiedAlerting.StateHistory.Enabled { + frontendSettings.UnifiedAlerting.StateHistory = &dtos.FrontendSettingsUnifiedAlertingStateHistoryDTO{ + Backend: hs.Cfg.UnifiedAlerting.StateHistory.Backend, + Primary: hs.Cfg.UnifiedAlerting.StateHistory.MultiPrimary, + } + if hs.Cfg.UnifiedAlerting.StateHistory.PrometheusTargetDatasourceUID != "" { + frontendSettings.UnifiedAlerting.StateHistory.PrometheusTargetDatasourceUID = hs.Cfg.UnifiedAlerting.StateHistory.PrometheusTargetDatasourceUID + } + if hs.Cfg.UnifiedAlerting.StateHistory.PrometheusMetricName != "" { + frontendSettings.UnifiedAlerting.StateHistory.PrometheusMetricName = hs.Cfg.UnifiedAlerting.StateHistory.PrometheusMetricName + } + + // Populate deprecated fields for backward compatibility frontendSettings.UnifiedAlerting.AlertStateHistoryBackend = hs.Cfg.UnifiedAlerting.StateHistory.Backend frontendSettings.UnifiedAlerting.AlertStateHistoryPrimary = hs.Cfg.UnifiedAlerting.StateHistory.MultiPrimary } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 07029af9b92..bbad4d1c9ba 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1918,6 +1918,16 @@ var ( Owner: grafanaFrontendSearchNavOrganise, Expression: "false", }, + { + Name: "alertingTriage", + Description: "Enables the alerting triage feature", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaAlertingSquad, + HideFromDocs: true, + HideFromAdminPage: true, + Expression: "false", + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index fb581e030b4..c770f0fcc36 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -247,3 +247,4 @@ newLogContext,experimental,@grafana/observability-logs,false,false,true newClickhouseConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false unifiedStorageSearchAfterWriteExperimentalAPI,experimental,@grafana/search-and-storage,false,true,false teamFolders,experimental,@grafana/grafana-search-navigate-organise,false,false,false +alertingTriage,experimental,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 22383ebb4f9..e1c9df82f22 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -998,4 +998,8 @@ const ( // FlagTeamFolders // Enables team folders functionality FlagTeamFolders = "teamFolders" + + // FlagAlertingTriage + // Enables the alerting triage feature + FlagAlertingTriage = "alertingTriage" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 8a3c0b60263..709841d5f36 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -515,6 +515,22 @@ "codeowner": "@grafana/alerting-squad" } }, + { + "metadata": { + "name": "alertingTriage", + "resourceVersion": "1756386724059", + "creationTimestamp": "2025-08-28T13:12:04Z" + }, + "spec": { + "description": "Enables the alerting triage feature", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "frontend": true, + "hideFromAdminPage": true, + "hideFromDocs": true, + "expression": "false" + } + }, { "metadata": { "name": "alertingUIOptimizeReducer", diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 7ae65815da3..e826a748cc1 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -428,6 +428,14 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na hasAccess := ac.HasAccess(s.accessControl, c) var alertChildNavs []*navtree.NavLink + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingTriage) { + if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) { + alertChildNavs = append(alertChildNavs, &navtree.NavLink{ + Text: "Triage", SubTitle: "Triage alerts", Id: "alert-triage", Url: s.cfg.AppSubURL + "/alerting/triage", Icon: "medkit", + }) + } + } + if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) { alertChildNavs = append(alertChildNavs, &navtree.NavLink{ Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul", diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx index 96bf0a4b421..b7c025868a3 100644 --- a/public/app/features/alerting/routes.tsx +++ b/public/app/features/alerting/routes.tsx @@ -3,6 +3,7 @@ import { Navigate } from 'react-router-dom-v5-compat'; import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; import { config } from 'app/core/config'; import { GrafanaRouteComponent, RouteDescriptor } from 'app/core/navigation/types'; +import { AlertingPageWrapper } from 'app/features/alerting/unified/components/AlertingPageWrapper'; import { AccessControlAction } from 'app/types/accessControl'; import { PERMISSIONS_CONTACT_POINTS } from './unified/components/contact-points/permissions'; @@ -333,6 +334,14 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] { }, ]; + if (cfg.featureToggles.alertingTriage) { + routes.push({ + path: '/alerting/triage', + roles: evaluateAccess([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleExternalRead]), + component: () => , + }); + } + return routes; } diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/History.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/History.tsx index b946c0a65f7..5de1df03bd1 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/tabs/History.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/History.tsx @@ -14,9 +14,9 @@ interface HistoryProps { const History = ({ rule }: HistoryProps) => { // can be "loki", "multiple" or "annotations" - const stateHistoryBackend = config.unifiedAlerting.alertStateHistoryBackend; + const stateHistoryBackend = config.unifiedAlerting.stateHistory?.backend; // can be "loki" or "annotations" - const stateHistoryPrimary = config.unifiedAlerting.alertStateHistoryPrimary; + const stateHistoryPrimary = config.unifiedAlerting.stateHistory?.primary; // if "loki" is either the backend or the primary, show the new state history implementation const usingNewAlertStateHistory = [stateHistoryBackend, stateHistoryPrimary].some( diff --git a/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx b/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx index a6e4f8f125c..e0d77d7bdae 100644 --- a/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx +++ b/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx @@ -22,9 +22,9 @@ function useStateHistoryModal() { const styles = useStyles2(getStyles); // can be "loki", "multiple" or "annotations" - const stateHistoryBackend = config.unifiedAlerting.alertStateHistoryBackend; + const stateHistoryBackend = config.unifiedAlerting.stateHistory?.backend; // can be "loki" or "annotations" - const stateHistoryPrimary = config.unifiedAlerting.alertStateHistoryPrimary; + const stateHistoryPrimary = config.unifiedAlerting.stateHistory?.primary; // if "loki" is either the backend or the primary, show the new state history implementation const usingNewAlertStateHistory = [stateHistoryBackend, stateHistoryPrimary].some(