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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 108 additions and 16 deletions

View File

@ -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

View File

@ -1106,4 +1106,9 @@ export interface FeatureToggles {
* @default false
*/
teamFolders?: boolean;
/**
* Enables the alerting triage feature
* @default false
*/
alertingTriage?: boolean;
}

View File

@ -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;

View File

@ -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

View File

@ -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
}

View File

@ -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",
},
}
)

View File

@ -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

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

View File

@ -998,4 +998,8 @@ const (
// FlagTeamFolders
// Enables team folders functionality
FlagTeamFolders = "teamFolders"
// FlagAlertingTriage
// Enables the alerting triage feature
FlagAlertingTriage = "alertingTriage"
)

View File

@ -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",

View File

@ -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",

View File

@ -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: () => <AlertingPageWrapper />,
});
}
return routes;
}

View File

@ -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(

View File

@ -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(