Alerting: Update Alert Rule to use int64 for MissingSeriesEvalsToResolve (#109306)

This commit is contained in:
Moustafa Baiou 2025-08-06 21:45:48 -04:00 committed by GitHub
parent e36402a121
commit 16f8359d35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 50 additions and 50 deletions

View File

@ -403,7 +403,7 @@ func TestIntegrationProvisioningApi(t *testing.T) {
})
t.Run("PUT without MissingSeriesEvalsToResolve clears the field", func(t *testing.T) {
oldValue := util.Pointer(5)
oldValue := util.Pointer[int64](5)
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
rule := createTestAlertRule("rule", 1)
@ -420,8 +420,8 @@ func TestIntegrationProvisioningApi(t *testing.T) {
})
t.Run("PUT with MissingSeriesEvalsToResolve updates the value", func(t *testing.T) {
oldValue := util.Pointer(5)
newValue := util.Pointer(10)
oldValue := util.Pointer[int64](5)
newValue := util.Pointer[int64](10)
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
rule := createTestAlertRule("rule", 1)
@ -657,12 +657,12 @@ func TestIntegrationProvisioningApi(t *testing.T) {
require.Nil(t, updated.Rules[0].MissingSeriesEvalsToResolve)
// Put the same group with a new value
group.Rules[0].MissingSeriesEvalsToResolve = util.Pointer(5)
group.Rules[0].MissingSeriesEvalsToResolve = util.Pointer[int64](5)
response = sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
require.Equal(t, 200, response.Status())
updated = deserializeRuleGroup(t, response.Body())
require.NotNil(t, updated.Rules[0].MissingSeriesEvalsToResolve)
require.Equal(t, 5, *updated.Rules[0].MissingSeriesEvalsToResolve)
require.Equal(t, int64(5), *updated.Rules[0].MissingSeriesEvalsToResolve)
// Reset the value again
group.Rules[0].MissingSeriesEvalsToResolve = nil

View File

@ -593,7 +593,7 @@ type PostableGrafanaRule struct {
// If set to 0, the value is reset to the default.
// required: false
// example: 3
MissingSeriesEvalsToResolve *int `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"`
MissingSeriesEvalsToResolve *int64 `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"`
}
// swagger:model
@ -616,7 +616,7 @@ type GettableGrafanaRule struct {
Record *Record `json:"record,omitempty" yaml:"record,omitempty"`
Metadata *AlertRuleMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"`
GUID string `json:"guid" yaml:"guid"`
MissingSeriesEvalsToResolve *int `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"`
MissingSeriesEvalsToResolve *int64 `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"`
}
// UserInfo represents user-related information, including a unique identifier and a name.

View File

@ -174,7 +174,7 @@ type ProvisionedAlertRule struct {
// example: {"metric":"grafana_alerts_ratio", "from":"A"}
Record *Record `json:"record"`
// example: 2
MissingSeriesEvalsToResolve *int `json:"missingSeriesEvalsToResolve,omitempty"`
MissingSeriesEvalsToResolve *int64 `json:"missingSeriesEvalsToResolve,omitempty"`
}
// swagger:route GET /v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteGetAlertRuleGroup
@ -284,7 +284,7 @@ type AlertRuleExport struct {
IsPaused bool `json:"isPaused" yaml:"isPaused" hcl:"is_paused"`
NotificationSettings *AlertRuleNotificationSettingsExport `json:"notification_settings,omitempty" yaml:"notification_settings,omitempty" hcl:"notification_settings,block"`
Record *AlertRuleRecordExport `json:"record,omitempty" yaml:"record,omitempty" hcl:"record,block"`
MissingSeriesEvalsToResolve *int `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty" hcl:"missing_series_evals_to_resolve"`
MissingSeriesEvalsToResolve *int64 `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty" hcl:"missing_series_evals_to_resolve"`
}
// AlertQueryExport is the provisioned export of models.AlertQuery.

View File

@ -299,10 +299,10 @@ func validateKeepFiringForInterval(ruleNode *apimodels.PostableExtendedRuleNode)
// - == 0, returns nil (reset to default)
// - == nil && UID == "", returns nil (new rule)
// - == nil && UID != "", returns -1 (existing rule)
func validateMissingSeriesEvalsToResolve(ruleNode *apimodels.PostableExtendedRuleNode) (*int, error) {
func validateMissingSeriesEvalsToResolve(ruleNode *apimodels.PostableExtendedRuleNode) (*int64, error) {
if ruleNode.GrafanaManagedAlert.MissingSeriesEvalsToResolve == nil {
if ruleNode.GrafanaManagedAlert.UID != "" {
return util.Pointer(-1), nil // will be patched later with the real value of the current version of the rule
return util.Pointer[int64](-1), nil // will be patched later with the real value of the current version of the rule
}
return nil, nil // if it's a new rule, use nil as the default
}

View File

@ -304,7 +304,7 @@ type AlertRule struct {
// required before resolving an alert state (a dimension) when data is missing.
// If nil, alerts resolve after 2 missing evaluation intervals
// (i.e., resolution occurs during the second evaluation where data is absent).
MissingSeriesEvalsToResolve *int
MissingSeriesEvalsToResolve *int64
}
type AlertRuleMetadata struct {
@ -598,7 +598,7 @@ func (alertRule *AlertRule) GetGroupKey() AlertRuleGroupKey {
// to wait before resolving an alert rule instance when its data is missing.
// If not configured, it returns the default value (2), which means the alert
// resolves after missing for two evaluation intervals.
func (alertRule *AlertRule) GetMissingSeriesEvalsToResolve() int {
func (alertRule *AlertRule) GetMissingSeriesEvalsToResolve() int64 {
if alertRule.MissingSeriesEvalsToResolve == nil {
return 2 // default value
}

View File

@ -554,8 +554,8 @@ func TestDiff(t *testing.T) {
if rule1.MissingSeriesEvalsToResolve != rule2.MissingSeriesEvalsToResolve {
diff := diffs.GetDiffsForField("MissingSeriesEvalsToResolve")
assert.Len(t, diff, 1)
assert.Equal(t, *rule1.MissingSeriesEvalsToResolve, int(diff[0].Left.Int()))
assert.Equal(t, *rule2.MissingSeriesEvalsToResolve, int(diff[0].Right.Int()))
assert.Equal(t, *rule1.MissingSeriesEvalsToResolve, diff[0].Left.Int())
assert.Equal(t, *rule2.MissingSeriesEvalsToResolve, diff[0].Right.Int())
difCnt++
}
@ -1001,14 +1001,14 @@ func TestAlertRuleGetMissingSeriesEvalsToResolve(t *testing.T) {
t.Run("should return the default 2 if MissingSeriesEvalsToResolve is nil", func(t *testing.T) {
rule := RuleGen.GenerateRef()
rule.MissingSeriesEvalsToResolve = nil
require.Equal(t, 2, rule.GetMissingSeriesEvalsToResolve())
require.Equal(t, int64(2), rule.GetMissingSeriesEvalsToResolve())
})
t.Run("should return the correct value", func(t *testing.T) {
rule := RuleGen.With(
RuleMuts.WithMissingSeriesEvalsToResolve(3),
).GenerateRef()
require.Equal(t, 3, rule.GetMissingSeriesEvalsToResolve())
require.Equal(t, int64(3), rule.GetMissingSeriesEvalsToResolve())
})
}
@ -1113,7 +1113,7 @@ func TestValidateAlertRule(t *testing.T) {
t.Run("missingSeriesEvalsToResolve", func(t *testing.T) {
testCases := []struct {
name string
missingSeriesEvalsToResolve *int
missingSeriesEvalsToResolve *int64
expectedErrorContains string
}{
{
@ -1122,17 +1122,17 @@ func TestValidateAlertRule(t *testing.T) {
},
{
name: "should reject negative value",
missingSeriesEvalsToResolve: util.Pointer(-1),
missingSeriesEvalsToResolve: util.Pointer[int64](-1),
expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0",
},
{
name: "should reject 0",
missingSeriesEvalsToResolve: util.Pointer(0),
missingSeriesEvalsToResolve: util.Pointer[int64](0),
expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0",
},
{
name: "should accept positive value",
missingSeriesEvalsToResolve: util.Pointer(2),
missingSeriesEvalsToResolve: util.Pointer[int64](2),
},
}

View File

@ -128,7 +128,7 @@ func (g *AlertRuleGenerator) Generate() AlertRule {
Labels: labels,
NotificationSettings: ns,
Metadata: GenerateMetadata(),
MissingSeriesEvalsToResolve: util.Pointer(2),
MissingSeriesEvalsToResolve: util.Pointer[int64](2),
}
for _, mutator := range g.mutators {
@ -514,12 +514,12 @@ func (a *AlertRuleMutators) WithSameGroup() AlertRuleMutator {
}
}
func (a *AlertRuleMutators) WithMissingSeriesEvalsToResolve(timesOfInterval int) AlertRuleMutator {
func (a *AlertRuleMutators) WithMissingSeriesEvalsToResolve(timesOfInterval int64) AlertRuleMutator {
return func(rule *AlertRule) {
if timesOfInterval <= 0 {
panic("timesOfInterval must be greater than 0")
}
rule.MissingSeriesEvalsToResolve = util.Pointer(timesOfInterval)
rule.MissingSeriesEvalsToResolve = util.Pointer[int64](timesOfInterval)
}
}

View File

@ -275,7 +275,7 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom
// Prometheus resolves alerts as soon as the series disappears.
// By setting this value to 1 we ensure that the alert is resolved on the first evaluation
// that doesn't have the series.
MissingSeriesEvalsToResolve: util.Pointer(1),
MissingSeriesEvalsToResolve: util.Pointer[int64](1),
}
if !isRecordingRule {

View File

@ -358,7 +358,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
require.Equal(t, models.Duration(evalOffset), grafanaRule.Data[0].RelativeTimeRange.To)
require.Equal(t, models.Duration(10*time.Minute+evalOffset), grafanaRule.Data[0].RelativeTimeRange.From)
require.Equal(t, util.Pointer(1), grafanaRule.MissingSeriesEvalsToResolve)
require.Equal(t, util.Pointer(int64(1)), grafanaRule.MissingSeriesEvalsToResolve)
require.Equal(t, models.OkErrState, grafanaRule.ExecErrState)
require.Equal(t, models.OK, grafanaRule.NoDataState)

View File

@ -214,7 +214,7 @@ func TestRuleWithFolderFingerprint(t *testing.T) {
SimplifiedNotificationsSection: false,
},
},
MissingSeriesEvalsToResolve: util.Pointer(2),
MissingSeriesEvalsToResolve: util.Pointer[int64](2),
}
r2 := &models.AlertRule{
ID: 2,
@ -260,7 +260,7 @@ func TestRuleWithFolderFingerprint(t *testing.T) {
SimplifiedQueryAndExpressionsSection: true,
},
},
MissingSeriesEvalsToResolve: util.Pointer(1),
MissingSeriesEvalsToResolve: util.Pointer[int64](1),
}
excludedFields := map[string]struct{}{

View File

@ -587,13 +587,13 @@ func (st *Manager) processMissingSeriesStates(logger log.Logger, evaluatedAt tim
// stateIsStale determines whether the evaluation state is considered stale.
// A state is considered stale if the data has been missing for at least missingSeriesEvalsToResolve evaluation intervals.
func stateIsStale(evaluatedAt time.Time, lastEval time.Time, intervalSeconds int64, missingSeriesEvalsToResolve int) bool {
func stateIsStale(evaluatedAt time.Time, lastEval time.Time, intervalSeconds int64, missingSeriesEvalsToResolve int64) bool {
// If the last evaluation time equals the current evaluation time, the state is not stale.
if evaluatedAt.Equal(lastEval) {
return false
}
resolveIfMissingDuration := time.Duration(int64(missingSeriesEvalsToResolve)*intervalSeconds) * time.Second
resolveIfMissingDuration := time.Duration(missingSeriesEvalsToResolve*intervalSeconds) * time.Second
// timeSinceLastEval >= resolveIfMissingDuration
return evaluatedAt.Sub(lastEval) >= resolveIfMissingDuration

View File

@ -39,7 +39,7 @@ func TestStateIsStale(t *testing.T) {
name string
lastEvaluation time.Time
expectedResult bool
missingSeriesEvalsToResolve int
missingSeriesEvalsToResolve int64
}{
{
name: "false if last evaluation is now",

View File

@ -30,7 +30,7 @@ type alertRule struct {
IsPaused bool
NotificationSettings string `xorm:"notification_settings"`
Metadata string `xorm:"metadata"`
MissingSeriesEvalsToResolve *int `xorm:"missing_series_evals_to_resolve"`
MissingSeriesEvalsToResolve *int64 `xorm:"missing_series_evals_to_resolve"`
}
func (a alertRule) TableName() string {
@ -68,7 +68,7 @@ type alertRuleVersion struct {
IsPaused bool
NotificationSettings string `xorm:"notification_settings"`
Metadata string `xorm:"metadata"`
MissingSeriesEvalsToResolve *int `xorm:"missing_series_evals_to_resolve"`
MissingSeriesEvalsToResolve *int64 `xorm:"missing_series_evals_to_resolve"`
}
// EqualSpec compares two alertRuleVersion objects for equality based on their specifications and returns true if they match.

View File

@ -661,7 +661,7 @@ func TestIntegrationProvisioningRules(t *testing.T) {
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
},
},
MissingSeriesEvalsToResolve: util.Pointer(3),
MissingSeriesEvalsToResolve: util.Pointer[int64](3),
},
},
}
@ -676,7 +676,7 @@ func TestIntegrationProvisioningRules(t *testing.T) {
for _, rule := range result.Rules {
require.NotEmpty(t, rule.UID)
if rule.UID == "rule3" {
require.Equal(t, 3, *rule.MissingSeriesEvalsToResolve)
require.Equal(t, int64(3), *rule.MissingSeriesEvalsToResolve)
}
}
})

View File

@ -1761,42 +1761,42 @@ func TestIntegrationRuleUpdate(t *testing.T) {
t.Run("missing_series_evals_to_resolve", func(t *testing.T) {
testCases := []struct {
name string
initialValue *int
updatedValue *int
expectedValue *int
initialValue *int64
updatedValue *int64
expectedValue *int64
expectedStatus int
}{
{
name: "should be able to set missing_series_evals_to_resolve to 5",
initialValue: nil,
updatedValue: util.Pointer(5),
expectedValue: util.Pointer(5),
updatedValue: util.Pointer[int64](5),
expectedValue: util.Pointer[int64](5),
expectedStatus: http.StatusAccepted,
},
{
name: "should be able to update missing_series_evals_to_resolve",
initialValue: util.Pointer(1),
updatedValue: util.Pointer(2),
expectedValue: util.Pointer(2),
initialValue: util.Pointer[int64](1),
updatedValue: util.Pointer[int64](2),
expectedValue: util.Pointer[int64](2),
expectedStatus: http.StatusAccepted,
},
{
name: "should preserve missing_series_evals_to_resolve when it's set nil",
initialValue: util.Pointer(5),
initialValue: util.Pointer[int64](5),
updatedValue: nil,
expectedValue: util.Pointer(5),
expectedValue: util.Pointer[int64](5),
expectedStatus: http.StatusAccepted,
},
{
name: "should reject missing_series_evals_to_resolve < 0",
initialValue: util.Pointer(1),
updatedValue: util.Pointer(-1),
initialValue: util.Pointer[int64](1),
updatedValue: util.Pointer[int64](-1),
expectedStatus: http.StatusBadRequest,
},
{
name: "should be able to reset missing_series_evals_to_resolve by setting it to 0",
initialValue: util.Pointer(1),
updatedValue: util.Pointer(0),
initialValue: util.Pointer[int64](1),
updatedValue: util.Pointer[int64](0),
expectedValue: nil,
expectedStatus: http.StatusAccepted,
},
@ -3474,7 +3474,7 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
},
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
MissingSeriesEvalsToResolve: util.Pointer(2), // If UID is specified, this field is required
MissingSeriesEvalsToResolve: util.Pointer[int64](2), // If UID is specified, this field is required
},
},
},