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
This commit is contained in:
Konrad Lalik
2025-09-01 11:33:33 +02:00
committed by GitHub
parent f09f77ced4
commit 31114fb47c
13 changed files with 108 additions and 16 deletions
+14 -5
View File
@@ -100,15 +100,24 @@ export interface GrafanaJavascriptAgentConfig {
apiKey: string; apiKey: string;
} }
export interface UnifiedAlertingStateHistoryConfig {
backend?: string;
primary?: string;
prometheusTargetDatasourceUID?: string;
prometheusMetricName?: string;
}
export interface UnifiedAlertingConfig { export interface UnifiedAlertingConfig {
minInterval: string; minInterval: string;
// will be undefined if alerStateHistory is not enabled stateHistory?: UnifiedAlertingStateHistoryConfig;
alertStateHistoryBackend?: string;
// will be undefined if implementation is not "multiple"
alertStateHistoryPrimary?: string;
recordingRulesEnabled?: boolean; recordingRulesEnabled?: boolean;
// will be undefined if no default datasource is configured
defaultRecordingRulesTargetDatasourceUID?: string; defaultRecordingRulesTargetDatasourceUID?: string;
// Backward compatibility aliases - deprecated
/** @deprecated Use stateHistory.backend instead */
alertStateHistoryBackend?: string;
/** @deprecated Use stateHistory.primary instead */
alertStateHistoryPrimary?: string;
} }
/** Supported OAuth services /** Supported OAuth services
@@ -1106,4 +1106,9 @@ export interface FeatureToggles {
* @default false * @default false
*/ */
teamFolders?: boolean; teamFolders?: boolean;
/**
* Enables the alerting triage feature
* @default false
*/
alertingTriage?: boolean;
} }
+10 -2
View File
@@ -186,10 +186,18 @@ export class GrafanaBootConfig {
unifiedAlertingEnabled = false; unifiedAlertingEnabled = false;
unifiedAlerting: UnifiedAlertingConfig = { unifiedAlerting: UnifiedAlertingConfig = {
minInterval: '', minInterval: '',
alertStateHistoryBackend: undefined, stateHistory: {
alertStateHistoryPrimary: undefined, backend: undefined,
primary: undefined,
prometheusTargetDatasourceUID: undefined,
prometheusMetricName: undefined,
},
recordingRulesEnabled: false, recordingRulesEnabled: false,
defaultRecordingRulesTargetDatasourceUID: undefined, defaultRecordingRulesTargetDatasourceUID: undefined,
// Backward compatibility fields - populated by backend
alertStateHistoryBackend: undefined,
alertStateHistoryPrimary: undefined,
}; };
applicationInsightsConnectionString?: string; applicationInsightsConnectionString?: string;
applicationInsightsEndpointUrl?: string; applicationInsightsEndpointUrl?: string;
+15 -5
View File
@@ -93,12 +93,22 @@ type FrontendSettingsAnalyticsDTO struct {
Enabled bool `json:"enabled"` 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 { type FrontendSettingsUnifiedAlertingDTO struct {
MinInterval string `json:"minInterval"` MinInterval string `json:"minInterval"`
AlertStateHistoryBackend string `json:"alertStateHistoryBackend,omitempty"` StateHistory *FrontendSettingsUnifiedAlertingStateHistoryDTO `json:"stateHistory,omitempty"`
AlertStateHistoryPrimary string `json:"alertStateHistoryPrimary,omitempty"` RecordingRulesEnabled bool `json:"recordingRulesEnabled"`
RecordingRulesEnabled bool `json:"recordingRulesEnabled"` DefaultRecordingRulesTargetDatasourceUID string `json:"defaultRecordingRulesTargetDatasourceUID,omitempty"`
DefaultRecordingRulesTargetDatasourceUID string `json:"defaultRecordingRulesTargetDatasourceUID,omitempty"`
// Backward compatibility fields - deprecated
AlertStateHistoryBackend string `json:"alertStateHistoryBackend,omitempty"`
AlertStateHistoryPrimary string `json:"alertStateHistoryPrimary,omitempty"`
} }
// Enterprise-only // Enterprise-only
+12
View File
@@ -348,6 +348,18 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
} }
if hs.Cfg.UnifiedAlerting.StateHistory.Enabled { 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.AlertStateHistoryBackend = hs.Cfg.UnifiedAlerting.StateHistory.Backend
frontendSettings.UnifiedAlerting.AlertStateHistoryPrimary = hs.Cfg.UnifiedAlerting.StateHistory.MultiPrimary frontendSettings.UnifiedAlerting.AlertStateHistoryPrimary = hs.Cfg.UnifiedAlerting.StateHistory.MultiPrimary
} }
+10
View File
@@ -1918,6 +1918,16 @@ var (
Owner: grafanaFrontendSearchNavOrganise, Owner: grafanaFrontendSearchNavOrganise,
Expression: "false", Expression: "false",
}, },
{
Name: "alertingTriage",
Description: "Enables the alerting triage feature",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaAlertingSquad,
HideFromDocs: true,
HideFromAdminPage: true,
Expression: "false",
},
} }
) )
+1
View File
@@ -247,3 +247,4 @@ newLogContext,experimental,@grafana/observability-logs,false,false,true
newClickhouseConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false newClickhouseConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false
unifiedStorageSearchAfterWriteExperimentalAPI,experimental,@grafana/search-and-storage,false,true,false unifiedStorageSearchAfterWriteExperimentalAPI,experimental,@grafana/search-and-storage,false,true,false
teamFolders,experimental,@grafana/grafana-search-navigate-organise,false,false,false teamFolders,experimental,@grafana/grafana-search-navigate-organise,false,false,false
alertingTriage,experimental,@grafana/alerting-squad,false,false,true
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
247 newClickhouseConfigPageDesign privatePreview @grafana/partner-datasources false false false
248 unifiedStorageSearchAfterWriteExperimentalAPI experimental @grafana/search-and-storage false true false
249 teamFolders experimental @grafana/grafana-search-navigate-organise false false false
250 alertingTriage experimental @grafana/alerting-squad false false true
+4
View File
@@ -998,4 +998,8 @@ const (
// FlagTeamFolders // FlagTeamFolders
// Enables team folders functionality // Enables team folders functionality
FlagTeamFolders = "teamFolders" FlagTeamFolders = "teamFolders"
// FlagAlertingTriage
// Enables the alerting triage feature
FlagAlertingTriage = "alertingTriage"
) )
+16
View File
@@ -515,6 +515,22 @@
"codeowner": "@grafana/alerting-squad" "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": { "metadata": {
"name": "alertingUIOptimizeReducer", "name": "alertingUIOptimizeReducer",
@@ -428,6 +428,14 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
hasAccess := ac.HasAccess(s.accessControl, c) hasAccess := ac.HasAccess(s.accessControl, c)
var alertChildNavs []*navtree.NavLink 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))) { if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{ 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", Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
+9
View File
@@ -3,6 +3,7 @@ import { Navigate } from 'react-router-dom-v5-compat';
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { GrafanaRouteComponent, RouteDescriptor } from 'app/core/navigation/types'; import { GrafanaRouteComponent, RouteDescriptor } from 'app/core/navigation/types';
import { AlertingPageWrapper } from 'app/features/alerting/unified/components/AlertingPageWrapper';
import { AccessControlAction } from 'app/types/accessControl'; import { AccessControlAction } from 'app/types/accessControl';
import { PERMISSIONS_CONTACT_POINTS } from './unified/components/contact-points/permissions'; 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: () => <AlertingPageWrapper />,
});
}
return routes; return routes;
} }
@@ -14,9 +14,9 @@ interface HistoryProps {
const History = ({ rule }: HistoryProps) => { const History = ({ rule }: HistoryProps) => {
// can be "loki", "multiple" or "annotations" // can be "loki", "multiple" or "annotations"
const stateHistoryBackend = config.unifiedAlerting.alertStateHistoryBackend; const stateHistoryBackend = config.unifiedAlerting.stateHistory?.backend;
// can be "loki" or "annotations" // 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 // if "loki" is either the backend or the primary, show the new state history implementation
const usingNewAlertStateHistory = [stateHistoryBackend, stateHistoryPrimary].some( const usingNewAlertStateHistory = [stateHistoryBackend, stateHistoryPrimary].some(
@@ -22,9 +22,9 @@ function useStateHistoryModal() {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
// can be "loki", "multiple" or "annotations" // can be "loki", "multiple" or "annotations"
const stateHistoryBackend = config.unifiedAlerting.alertStateHistoryBackend; const stateHistoryBackend = config.unifiedAlerting.stateHistory?.backend;
// can be "loki" or "annotations" // 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 // if "loki" is either the backend or the primary, show the new state history implementation
const usingNewAlertStateHistory = [stateHistoryBackend, stateHistoryPrimary].some( const usingNewAlertStateHistory = [stateHistoryBackend, stateHistoryPrimary].some(