Alerting: Add support for alpha rules apis in legacy storage

Rules created in the new api makes the rule have no group in the database, but the rule is returned in the old group api with a sentinel group name formatted with the rule uid for compatiblity with the old api.
This makes the UI continue to work with the rules without a group, and the ruler will continue to work with the rules without a group.

Rules are not allowed to be created in the provisioning api with a NoGroup sentinel mask, but NoGroup rules can be manipulated through both the new and old apis.

Co-authored-by: William Wernert <william.wernert@grafana.com>
This commit is contained in:
Moustafa Baiou
2025-09-10 09:30:56 -04:00
committed by Moustafa Baiou
co-authored by William Wernert
parent 0a85a30642
commit ca8324e62a
50 changed files with 8729 additions and 110 deletions
@@ -7,6 +7,8 @@ ExecErrState: *"Error" | "Ok" | "Alerting" | "KeepLast"
#TimeIntervalRef: string // TODO(@moustafab): validate regex for time interval ref
// FIXME: the For and KeepFiringFor types should be using the AlertRulePromDuration type, but there seems to be an issue with the generator
AlertRuleSpec: #RuleSpec & {
noDataState: NoDataState
execErrState: ExecErrState
@@ -0,0 +1,51 @@
package v0alpha1
import (
"fmt"
"slices"
)
func (o *AlertRule) GetProvenanceStatus() string {
if o == nil || o.Annotations == nil {
return ProvenanceStatusNone
}
s, ok := o.Annotations[ProvenanceStatusAnnotationKey]
if !ok {
return ProvenanceStatusNone
}
return s
}
func (o *AlertRule) SetProvenanceStatus(status string) (err error) {
if o.Annotations == nil {
o.Annotations = make(map[string]string, 1)
}
if !slices.Contains(AcceptedProvenanceStatuses, status) {
return fmt.Errorf("invalid provenance status: %s", status)
}
o.Annotations[ProvenanceStatusAnnotationKey] = status
return
}
// HACK: these should be unnecessary based on the openapi schema generation
const (
DefaultNoDataState = "NoData"
DefaultExecErrState = "Error"
)
func (s *AlertRuleSpec) NoDataStateOrDefault() string {
if s.NoDataState == "" {
return DefaultNoDataState
}
return s.NoDataState
}
func (s *AlertRuleSpec) ExecErrStateOrDefault() string {
if s.ExecErrState == "" {
return DefaultExecErrState
}
return s.ExecErrState
}
// TODO: add duration clamping for the field types AlertRulePromDuration, AlertRulePromDurationWMillis, and the For and KeepFiringFor string pointers
@@ -0,0 +1,17 @@
package v0alpha1
const (
InternalPrefix = "grafana.com/"
GroupLabelKey = InternalPrefix + "group"
GroupIndexLabelKey = GroupLabelKey + "-index"
ProvenanceStatusAnnotationKey = InternalPrefix + "provenance"
)
const (
ProvenanceStatusNone = ""
ProvenanceStatusAPI = "api"
)
var (
AcceptedProvenanceStatuses = []string{ProvenanceStatusNone, ProvenanceStatusAPI}
)
@@ -0,0 +1,30 @@
package v0alpha1
import (
"fmt"
"slices"
)
func (o *RecordingRule) GetProvenanceStatus() string {
if o == nil || o.Annotations == nil {
return ProvenanceStatusNone
}
s, ok := o.Annotations[ProvenanceStatusAnnotationKey]
if !ok {
return ProvenanceStatusNone
}
return s
}
func (o *RecordingRule) SetProvenanceStatus(status string) (err error) {
if o.Annotations == nil {
o.Annotations = make(map[string]string, 1)
}
if !slices.Contains(AcceptedProvenanceStatuses, status) {
return fmt.Errorf("invalid provenance status: %s", status)
}
o.Annotations[ProvenanceStatusAnnotationKey] = status
return
}
// TODO: add duration clamping for the field types RecordingRulePromDurationWMillis and RecordingRulePromDuration