Alerting: Add MissingSeriesEvalsToResolve to the APIs (#102150)
What is this feature? A follow-up for #101184, adds AlertRule.MissingSeriesEvalsToResolve to the APIs. missing_series_evals_to_resolve must be specified too and it must be > 0. POST /api/ruler/grafana/api/v1/rules/{folderUID} works in the following way: If missing_series_evals_to_resolve is not sent or null, the rule keeps its existing value If missing_series_evals_to_resolve > 0: updates to that value If missing_series_evals_to_resolve = 0: resets to default (nil). AlertRule.MissingSeriesEvalsToResolve can't be 0, so I used it to reset In the Provisioning API, the value is just set if present and > 0. Otherwise it's reset: PUT to /api/v1/provisioning/alert-rules/{UID}: If missing_series_evals_to_resolve is nil, it's reset to the default value If missing_series_evals_to_resolve > 0, it's updated
This commit is contained in:
@@ -408,6 +408,42 @@ func TestProvisioningApi(t *testing.T) {
|
||||
updated := deserializeRule(t, response.Body())
|
||||
require.Equal(t, rule.UID, updated.UID)
|
||||
})
|
||||
|
||||
t.Run("PUT without MissingSeriesEvalsToResolve clears the field", func(t *testing.T) {
|
||||
oldValue := util.Pointer(5)
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
rule := createTestAlertRule("rule", 1)
|
||||
rule.MissingSeriesEvalsToResolve = oldValue
|
||||
insertRule(t, sut, rule)
|
||||
rule.MissingSeriesEvalsToResolve = nil
|
||||
|
||||
response := sut.RoutePutAlertRule(&rc, rule, rule.UID)
|
||||
require.Equal(t, 200, response.Status(), string(response.Body()))
|
||||
require.NotEmpty(t, response.Body())
|
||||
updated := deserializeRule(t, response.Body())
|
||||
require.Equal(t, rule.UID, updated.UID)
|
||||
require.Nil(t, updated.MissingSeriesEvalsToResolve)
|
||||
})
|
||||
|
||||
t.Run("PUT with MissingSeriesEvalsToResolve updates the value", func(t *testing.T) {
|
||||
oldValue := util.Pointer(5)
|
||||
newValue := util.Pointer(10)
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
rule := createTestAlertRule("rule", 1)
|
||||
rule.MissingSeriesEvalsToResolve = oldValue
|
||||
insertRule(t, sut, rule)
|
||||
rule.MissingSeriesEvalsToResolve = newValue
|
||||
|
||||
response := sut.RoutePutAlertRule(&rc, rule, rule.UID)
|
||||
require.Equal(t, 200, response.Status(), string(response.Body()))
|
||||
require.NotEmpty(t, response.Body())
|
||||
updated := deserializeRule(t, response.Body())
|
||||
require.Equal(t, rule.UID, updated.UID)
|
||||
require.NotNil(t, updated.MissingSeriesEvalsToResolve)
|
||||
require.Equal(t, *newValue, *updated.MissingSeriesEvalsToResolve)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("exist in non-default orgs", func(t *testing.T) {
|
||||
@@ -657,6 +693,47 @@ func TestProvisioningApi(t *testing.T) {
|
||||
require.Contains(t, string(response.Body()), "invalid alert rule")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("are valid", func(t *testing.T) {
|
||||
t.Run("PUT returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
group := createTestAlertRuleGroup(1)
|
||||
|
||||
response := sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
|
||||
updated := deserializeRuleGroup(t, response.Body())
|
||||
require.Equal(t, group.Title, updated.Title)
|
||||
})
|
||||
|
||||
t.Run("PUT with MissingSeriesEvalsToResolve updates the value", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
group := createTestAlertRuleGroup(1)
|
||||
|
||||
response := sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
|
||||
updated := deserializeRuleGroup(t, response.Body())
|
||||
require.Nil(t, updated.Rules[0].MissingSeriesEvalsToResolve)
|
||||
|
||||
// Put the same group with a new value
|
||||
group.Rules[0].MissingSeriesEvalsToResolve = util.Pointer(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)
|
||||
|
||||
// Reset the value again
|
||||
group.Rules[0].MissingSeriesEvalsToResolve = nil
|
||||
response = sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
|
||||
require.Equal(t, 200, response.Status())
|
||||
updated = deserializeRuleGroup(t, response.Body())
|
||||
require.Nil(t, updated.Rules[0].MissingSeriesEvalsToResolve)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("exports", func(t *testing.T) {
|
||||
@@ -2178,6 +2255,18 @@ func createInvalidAlertRuleGroup() definitions.AlertRuleGroup {
|
||||
}
|
||||
}
|
||||
|
||||
func createTestAlertRuleGroup(orgID int64) definitions.AlertRuleGroup {
|
||||
return definitions.AlertRuleGroup{
|
||||
Title: "test rule group",
|
||||
Interval: 60,
|
||||
Rules: []definitions.ProvisionedAlertRule{
|
||||
createTestAlertRule("test-alert-rule", orgID),
|
||||
createTestAlertRule("test-alert-rule-2", orgID),
|
||||
createTestRecordingRule("test-recording-rule", orgID),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createTestAlertRuleWithFolderAndGroup(title string, orgID int64, folderUid string, group string) definitions.ProvisionedAlertRule {
|
||||
rule := createTestAlertRule(title, orgID)
|
||||
rule.FolderUID = folderUid
|
||||
@@ -2256,6 +2345,15 @@ func insertRuleInOrg(t *testing.T, srv ProvisioningSrv, rule definitions.Provisi
|
||||
require.Equal(t, 201, resp.Status())
|
||||
}
|
||||
|
||||
func deserializeRuleGroup(t *testing.T, data []byte) definitions.AlertRuleGroup {
|
||||
t.Helper()
|
||||
|
||||
var rule definitions.AlertRuleGroup
|
||||
err := json.Unmarshal(data, &rule)
|
||||
require.NoError(t, err)
|
||||
return rule
|
||||
}
|
||||
|
||||
func deserializeRule(t *testing.T, data []byte) definitions.ProvisionedAlertRule {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -641,24 +641,25 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, provenanceRecords map[stri
|
||||
|
||||
gettableExtendedRuleNode := apimodels.GettableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &apimodels.GettableGrafanaRule{
|
||||
Title: r.Title,
|
||||
Condition: r.Condition,
|
||||
Data: ApiAlertQueriesFromAlertQueries(r.Data),
|
||||
Updated: r.Updated,
|
||||
UpdatedBy: userIdToName(r.UpdatedBy),
|
||||
IntervalSeconds: r.IntervalSeconds,
|
||||
Version: r.Version,
|
||||
UID: r.UID,
|
||||
NamespaceUID: r.NamespaceUID,
|
||||
RuleGroup: r.RuleGroup,
|
||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||
Provenance: apimodels.Provenance(provenance),
|
||||
IsPaused: r.IsPaused,
|
||||
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(r.NotificationSettings),
|
||||
Record: ApiRecordFromModelRecord(r.Record),
|
||||
Metadata: AlertRuleMetadataFromModelMetadata(r.Metadata),
|
||||
GUID: r.GUID,
|
||||
Title: r.Title,
|
||||
Condition: r.Condition,
|
||||
Data: ApiAlertQueriesFromAlertQueries(r.Data),
|
||||
Updated: r.Updated,
|
||||
UpdatedBy: userIdToName(r.UpdatedBy),
|
||||
IntervalSeconds: r.IntervalSeconds,
|
||||
Version: r.Version,
|
||||
UID: r.UID,
|
||||
NamespaceUID: r.NamespaceUID,
|
||||
RuleGroup: r.RuleGroup,
|
||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||
Provenance: apimodels.Provenance(provenance),
|
||||
IsPaused: r.IsPaused,
|
||||
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(r.NotificationSettings),
|
||||
Record: ApiRecordFromModelRecord(r.Record),
|
||||
Metadata: AlertRuleMetadataFromModelMetadata(r.Metadata),
|
||||
GUID: r.GUID,
|
||||
MissingSeriesEvalsToResolve: r.MissingSeriesEvalsToResolve,
|
||||
},
|
||||
}
|
||||
forDuration := model.Duration(r.For)
|
||||
|
||||
@@ -17,24 +17,25 @@ import (
|
||||
// AlertRuleFromProvisionedAlertRule converts definitions.ProvisionedAlertRule to models.AlertRule
|
||||
func AlertRuleFromProvisionedAlertRule(a definitions.ProvisionedAlertRule) (models.AlertRule, error) {
|
||||
rule := models.AlertRule{
|
||||
ID: a.ID,
|
||||
UID: a.UID,
|
||||
OrgID: a.OrgID,
|
||||
NamespaceUID: a.FolderUID,
|
||||
RuleGroup: a.RuleGroup,
|
||||
Title: a.Title,
|
||||
Condition: a.Condition,
|
||||
Data: AlertQueriesFromApiAlertQueries(a.Data),
|
||||
Updated: a.Updated,
|
||||
NoDataState: models.NoDataState(a.NoDataState), // TODO there must be a validation
|
||||
ExecErrState: models.ExecutionErrorState(a.ExecErrState), // TODO there must be a validation
|
||||
For: time.Duration(a.For),
|
||||
KeepFiringFor: time.Duration(a.KeepFiringFor),
|
||||
Annotations: a.Annotations,
|
||||
Labels: a.Labels,
|
||||
IsPaused: a.IsPaused,
|
||||
NotificationSettings: NotificationSettingsFromAlertRuleNotificationSettings(a.NotificationSettings),
|
||||
Record: ModelRecordFromApiRecord(a.Record),
|
||||
ID: a.ID,
|
||||
UID: a.UID,
|
||||
OrgID: a.OrgID,
|
||||
NamespaceUID: a.FolderUID,
|
||||
RuleGroup: a.RuleGroup,
|
||||
Title: a.Title,
|
||||
Condition: a.Condition,
|
||||
Data: AlertQueriesFromApiAlertQueries(a.Data),
|
||||
Updated: a.Updated,
|
||||
NoDataState: models.NoDataState(a.NoDataState), // TODO there must be a validation
|
||||
ExecErrState: models.ExecutionErrorState(a.ExecErrState), // TODO there must be a validation
|
||||
For: time.Duration(a.For),
|
||||
KeepFiringFor: time.Duration(a.KeepFiringFor),
|
||||
Annotations: a.Annotations,
|
||||
Labels: a.Labels,
|
||||
IsPaused: a.IsPaused,
|
||||
NotificationSettings: NotificationSettingsFromAlertRuleNotificationSettings(a.NotificationSettings),
|
||||
Record: ModelRecordFromApiRecord(a.Record),
|
||||
MissingSeriesEvalsToResolve: a.MissingSeriesEvalsToResolve,
|
||||
}
|
||||
|
||||
if rule.Type() == models.RuleTypeRecording {
|
||||
@@ -47,25 +48,26 @@ func AlertRuleFromProvisionedAlertRule(a definitions.ProvisionedAlertRule) (mode
|
||||
// ProvisionedAlertRuleFromAlertRule converts models.AlertRule to definitions.ProvisionedAlertRule and sets provided provenance status
|
||||
func ProvisionedAlertRuleFromAlertRule(rule models.AlertRule, provenance models.Provenance) definitions.ProvisionedAlertRule {
|
||||
return definitions.ProvisionedAlertRule{
|
||||
ID: rule.ID,
|
||||
UID: rule.UID,
|
||||
OrgID: rule.OrgID,
|
||||
FolderUID: rule.NamespaceUID,
|
||||
RuleGroup: rule.RuleGroup,
|
||||
Title: rule.Title,
|
||||
For: model.Duration(rule.For),
|
||||
KeepFiringFor: model.Duration(rule.KeepFiringFor),
|
||||
Condition: rule.Condition,
|
||||
Data: ApiAlertQueriesFromAlertQueries(rule.Data),
|
||||
Updated: rule.Updated,
|
||||
NoDataState: definitions.NoDataState(rule.NoDataState), // TODO there may be a validation
|
||||
ExecErrState: definitions.ExecutionErrorState(rule.ExecErrState), // TODO there may be a validation
|
||||
Annotations: rule.Annotations,
|
||||
Labels: rule.Labels,
|
||||
Provenance: definitions.Provenance(provenance), // TODO validate enum conversion?
|
||||
IsPaused: rule.IsPaused,
|
||||
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
|
||||
Record: ApiRecordFromModelRecord(rule.Record),
|
||||
ID: rule.ID,
|
||||
UID: rule.UID,
|
||||
OrgID: rule.OrgID,
|
||||
FolderUID: rule.NamespaceUID,
|
||||
RuleGroup: rule.RuleGroup,
|
||||
Title: rule.Title,
|
||||
For: model.Duration(rule.For),
|
||||
KeepFiringFor: model.Duration(rule.KeepFiringFor),
|
||||
Condition: rule.Condition,
|
||||
Data: ApiAlertQueriesFromAlertQueries(rule.Data),
|
||||
Updated: rule.Updated,
|
||||
NoDataState: definitions.NoDataState(rule.NoDataState), // TODO there may be a validation
|
||||
ExecErrState: definitions.ExecutionErrorState(rule.ExecErrState), // TODO there may be a validation
|
||||
Annotations: rule.Annotations,
|
||||
Labels: rule.Labels,
|
||||
Provenance: definitions.Provenance(provenance), // TODO validate enum conversion?
|
||||
IsPaused: rule.IsPaused,
|
||||
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
|
||||
Record: ApiRecordFromModelRecord(rule.Record),
|
||||
MissingSeriesEvalsToResolve: rule.MissingSeriesEvalsToResolve,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,19 +207,20 @@ func AlertRuleExportFromAlertRule(rule models.AlertRule) (definitions.AlertRuleE
|
||||
}
|
||||
|
||||
result := definitions.AlertRuleExport{
|
||||
UID: rule.UID,
|
||||
Title: rule.Title,
|
||||
For: model.Duration(rule.For),
|
||||
KeepFiringFor: model.Duration(rule.KeepFiringFor),
|
||||
Condition: cPtr,
|
||||
Data: data,
|
||||
DashboardUID: rule.DashboardUID,
|
||||
PanelID: rule.PanelID,
|
||||
NoDataState: ndsPtr,
|
||||
ExecErrState: eesPtr,
|
||||
IsPaused: rule.IsPaused,
|
||||
NotificationSettings: AlertRuleNotificationSettingsExportFromNotificationSettings(rule.NotificationSettings),
|
||||
Record: AlertRuleRecordExportFromRecord(rule.Record),
|
||||
UID: rule.UID,
|
||||
Title: rule.Title,
|
||||
For: model.Duration(rule.For),
|
||||
KeepFiringFor: model.Duration(rule.KeepFiringFor),
|
||||
Condition: cPtr,
|
||||
Data: data,
|
||||
DashboardUID: rule.DashboardUID,
|
||||
PanelID: rule.PanelID,
|
||||
NoDataState: ndsPtr,
|
||||
ExecErrState: eesPtr,
|
||||
IsPaused: rule.IsPaused,
|
||||
NotificationSettings: AlertRuleNotificationSettingsExportFromNotificationSettings(rule.NotificationSettings),
|
||||
Record: AlertRuleRecordExportFromRecord(rule.Record),
|
||||
MissingSeriesEvalsToResolve: rule.MissingSeriesEvalsToResolve,
|
||||
}
|
||||
if rule.For.Seconds() > 0 {
|
||||
result.ForString = util.Pointer(model.Duration(rule.For).String())
|
||||
@@ -231,6 +234,7 @@ func AlertRuleExportFromAlertRule(rule models.AlertRule) (definitions.AlertRuleE
|
||||
if rule.Labels != nil {
|
||||
result.Labels = &rule.Labels
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -1630,6 +1634,10 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"namespace_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2864,6 +2872,12 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"description": "Number of consecutive evaluation intervals with no data for a dimension must pass\nbefore the alert state is considered stale and automatically resolved.\nIf set to 0, the value is reset to the default.",
|
||||
"example": 3,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"no_data_state": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -3150,6 +3164,11 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"missingSeriesEvalsToResolve": {
|
||||
"example": 2,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -4976,6 +4995,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert",
|
||||
"type": "object"
|
||||
|
||||
@@ -569,28 +569,35 @@ type PostableGrafanaRule struct {
|
||||
NotificationSettings *AlertRuleNotificationSettings `json:"notification_settings" yaml:"notification_settings"`
|
||||
Record *Record `json:"record" yaml:"record"`
|
||||
Metadata *AlertRuleMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
// Number of consecutive evaluation intervals with no data for a dimension must pass
|
||||
// before the alert state is considered stale and automatically resolved.
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type GettableGrafanaRule struct {
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Condition string `json:"condition" yaml:"condition"`
|
||||
Data []AlertQuery `json:"data" yaml:"data"`
|
||||
Updated time.Time `json:"updated" yaml:"updated"`
|
||||
UpdatedBy *UserInfo `json:"updated_by" yaml:"updated_by"`
|
||||
IntervalSeconds int64 `json:"intervalSeconds" yaml:"intervalSeconds"`
|
||||
Version int64 `json:"version" yaml:"version"`
|
||||
UID string `json:"uid" yaml:"uid"`
|
||||
NamespaceUID string `json:"namespace_uid" yaml:"namespace_uid"`
|
||||
RuleGroup string `json:"rule_group" yaml:"rule_group"`
|
||||
NoDataState NoDataState `json:"no_data_state" yaml:"no_data_state"`
|
||||
ExecErrState ExecutionErrorState `json:"exec_err_state" yaml:"exec_err_state"`
|
||||
Provenance Provenance `json:"provenance,omitempty" yaml:"provenance,omitempty"`
|
||||
IsPaused bool `json:"is_paused" yaml:"is_paused"`
|
||||
NotificationSettings *AlertRuleNotificationSettings `json:"notification_settings,omitempty" yaml:"notification_settings,omitempty"`
|
||||
Record *Record `json:"record,omitempty" yaml:"record,omitempty"`
|
||||
Metadata *AlertRuleMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
GUID string `json:"guid" yaml:"guid"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Condition string `json:"condition" yaml:"condition"`
|
||||
Data []AlertQuery `json:"data" yaml:"data"`
|
||||
Updated time.Time `json:"updated" yaml:"updated"`
|
||||
UpdatedBy *UserInfo `json:"updated_by" yaml:"updated_by"`
|
||||
IntervalSeconds int64 `json:"intervalSeconds" yaml:"intervalSeconds"`
|
||||
Version int64 `json:"version" yaml:"version"`
|
||||
UID string `json:"uid" yaml:"uid"`
|
||||
NamespaceUID string `json:"namespace_uid" yaml:"namespace_uid"`
|
||||
RuleGroup string `json:"rule_group" yaml:"rule_group"`
|
||||
NoDataState NoDataState `json:"no_data_state" yaml:"no_data_state"`
|
||||
ExecErrState ExecutionErrorState `json:"exec_err_state" yaml:"exec_err_state"`
|
||||
Provenance Provenance `json:"provenance,omitempty" yaml:"provenance,omitempty"`
|
||||
IsPaused bool `json:"is_paused" yaml:"is_paused"`
|
||||
NotificationSettings *AlertRuleNotificationSettings `json:"notification_settings,omitempty" yaml:"notification_settings,omitempty"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// UserInfo represents user-related information, including a unique identifier and a name.
|
||||
|
||||
@@ -173,6 +173,8 @@ type ProvisionedAlertRule struct {
|
||||
NotificationSettings *AlertRuleNotificationSettings `json:"notification_settings"`
|
||||
//example: {"metric":"grafana_alerts_ratio", "from":"A"}
|
||||
Record *Record `json:"record"`
|
||||
// example: 2
|
||||
MissingSeriesEvalsToResolve *int `json:"missingSeriesEvalsToResolve,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:route GET /v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteGetAlertRuleGroup
|
||||
@@ -275,13 +277,14 @@ type AlertRuleExport struct {
|
||||
// ForString and KeepFiringForString are used to:
|
||||
// - Only export the for field for HCL if it is non-zero.
|
||||
// - Format the Prometheus model.Duration type properly for HCL.
|
||||
ForString *string `json:"-" yaml:"-" hcl:"for"`
|
||||
KeepFiringForString *string `json:"-" yaml:"-" hcl:"keep_firing_for"`
|
||||
Annotations *map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty" hcl:"annotations"`
|
||||
Labels *map[string]string `json:"labels,omitempty" yaml:"labels,omitempty" hcl:"labels"`
|
||||
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"`
|
||||
ForString *string `json:"-" yaml:"-" hcl:"for"`
|
||||
KeepFiringForString *string `json:"-" yaml:"-" hcl:"keep_firing_for"`
|
||||
Annotations *map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty" hcl:"annotations"`
|
||||
Labels *map[string]string `json:"labels,omitempty" yaml:"labels,omitempty" hcl:"labels"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// AlertQueryExport is the provisioned export of models.AlertQuery.
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -1630,6 +1634,10 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"namespace_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2864,6 +2872,12 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"description": "Number of consecutive evaluation intervals with no data for a dimension must pass\nbefore the alert state is considered stale and automatically resolved.\nIf set to 0, the value is reset to the default.",
|
||||
"example": 3,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"no_data_state": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -3150,6 +3164,11 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"missingSeriesEvalsToResolve": {
|
||||
"example": 2,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -4814,7 +4833,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup",
|
||||
"type": "object"
|
||||
@@ -4976,7 +4994,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert",
|
||||
"type": "object"
|
||||
|
||||
@@ -4442,6 +4442,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"noDataState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -5855,6 +5859,10 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"namespace_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -7090,6 +7098,12 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"description": "Number of consecutive evaluation intervals with no data for a dimension must pass\nbefore the alert state is considered stale and automatically resolved.\nIf set to 0, the value is reset to the default.",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 3
|
||||
},
|
||||
"no_data_state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -7387,6 +7401,11 @@
|
||||
"team": "sre-team-1"
|
||||
}
|
||||
},
|
||||
"missingSeriesEvalsToResolve": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 2
|
||||
},
|
||||
"noDataState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -9039,7 +9058,6 @@
|
||||
}
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
@@ -9201,7 +9219,6 @@
|
||||
}
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
prommodels "github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
@@ -65,14 +66,15 @@ func ValidateRuleNode(
|
||||
queries := AlertQueriesFromApiAlertQueries(ruleNode.GrafanaManagedAlert.Data)
|
||||
|
||||
newAlertRule := ngmodels.AlertRule{
|
||||
OrgID: orgId,
|
||||
Title: ruleNode.GrafanaManagedAlert.Title,
|
||||
Condition: ruleNode.GrafanaManagedAlert.Condition,
|
||||
Data: queries,
|
||||
UID: ruleNode.GrafanaManagedAlert.UID,
|
||||
IntervalSeconds: intervalSeconds,
|
||||
NamespaceUID: namespaceUID,
|
||||
RuleGroup: groupName,
|
||||
OrgID: orgId,
|
||||
Title: ruleNode.GrafanaManagedAlert.Title,
|
||||
Condition: ruleNode.GrafanaManagedAlert.Condition,
|
||||
Data: queries,
|
||||
UID: ruleNode.GrafanaManagedAlert.UID,
|
||||
IntervalSeconds: intervalSeconds,
|
||||
NamespaceUID: namespaceUID,
|
||||
RuleGroup: groupName,
|
||||
MissingSeriesEvalsToResolve: ruleNode.GrafanaManagedAlert.MissingSeriesEvalsToResolve,
|
||||
}
|
||||
|
||||
if isRecordingRule {
|
||||
@@ -152,6 +154,11 @@ func validateAlertingRuleFields(in *apimodels.PostableExtendedRuleNode, newRule
|
||||
}
|
||||
}
|
||||
|
||||
newRule.MissingSeriesEvalsToResolve, err = validateMissingSeriesEvalsToResolve(in)
|
||||
if err != nil {
|
||||
return ngmodels.AlertRule{}, err
|
||||
}
|
||||
|
||||
newRule.For, err = validateForInterval(in)
|
||||
if err != nil {
|
||||
return ngmodels.AlertRule{}, err
|
||||
@@ -293,6 +300,29 @@ func validateKeepFiringForInterval(ruleNode *apimodels.PostableExtendedRuleNode)
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
// validateMissingSeriesEvalsToResolve validates MissingSeriesEvalsToResolve and converts it to *int.
|
||||
// If the ruleNode.GrafanaManagedAlert.MissingSeriesEvalsToResolve is:
|
||||
// - == 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) {
|
||||
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 nil, nil // if it's a new rule, use nil as the default
|
||||
}
|
||||
|
||||
v := ruleNode.GrafanaManagedAlert.MissingSeriesEvalsToResolve
|
||||
if *v == 0 {
|
||||
return nil, nil // allow to reset the value to default when 0 is sent
|
||||
} else if *v < 0 {
|
||||
return nil, fmt.Errorf("field `missing_series_evals_to_resolve` cannot be negative [%v]. 0 or any positive number are allowed", *v)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ValidateRuleGroup validates API model (definitions.PostableRuleGroupConfig) and converts it to a collection of models.AlertRule.
|
||||
// Returns a slice that contains all rules described by API model or error if either group specification or an alert definition is not valid.
|
||||
// It also returns a map containing current existing alerts that don't contain the is_paused field in the body of the call.
|
||||
|
||||
@@ -974,6 +974,9 @@ func PatchPartialAlertRule(existingRule *AlertRule, ruleToPatch *AlertRuleWithOp
|
||||
if !ruleToPatch.HasEditorSettings {
|
||||
ruleToPatch.Metadata.EditorSettings = existingRule.Metadata.EditorSettings
|
||||
}
|
||||
if ruleToPatch.MissingSeriesEvalsToResolve != nil && *ruleToPatch.MissingSeriesEvalsToResolve == -1 {
|
||||
ruleToPatch.MissingSeriesEvalsToResolve = existingRule.MissingSeriesEvalsToResolve
|
||||
}
|
||||
|
||||
if ruleToPatch.GUID == "" {
|
||||
ruleToPatch.GUID = existingRule.GUID
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func createRuleWithNotificationSettings(t *testing.T, client apiClient, folder string, nfSettings *definitions.AlertRuleNotificationSettings) (definitions.PostableRuleGroupConfig, string) {
|
||||
@@ -664,14 +665,24 @@ func TestIntegrationProvisioningRules(t *testing.T) {
|
||||
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
|
||||
},
|
||||
},
|
||||
MissingSeriesEvalsToResolve: util.Pointer(3),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, status, raw := apiClient.CreateOrUpdateRuleGroupProvisioning(t, originalRuleGroup)
|
||||
|
||||
t.Run("should create a new rule group with UIDs specified", func(t *testing.T) {
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
require.Equal(t, originalRuleGroup, result)
|
||||
|
||||
require.Len(t, result.Rules, 3)
|
||||
for _, rule := range result.Rules {
|
||||
require.NotEmpty(t, rule.UID)
|
||||
if rule.UID == "rule3" {
|
||||
require.Equal(t, 3, *rule.MissingSeriesEvalsToResolve)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should remove a rule when updating group with a rule removed", func(t *testing.T) {
|
||||
|
||||
@@ -1753,6 +1753,94 @@ func TestIntegrationRuleUpdate(t *testing.T) {
|
||||
require.Equal(t, newKeepFiringFor, *getGroup.Rules[0].ApiRuleNode.KeepFiringFor)
|
||||
})
|
||||
|
||||
t.Run("missing_series_evals_to_resolve", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
initialValue *int
|
||||
updatedValue *int
|
||||
expectedValue *int
|
||||
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),
|
||||
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),
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "should preserve missing_series_evals_to_resolve when it's set nil",
|
||||
initialValue: util.Pointer(5),
|
||||
updatedValue: nil,
|
||||
expectedValue: util.Pointer(5),
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "should reject missing_series_evals_to_resolve < 0",
|
||||
initialValue: util.Pointer(1),
|
||||
updatedValue: util.Pointer(-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),
|
||||
expectedValue: nil,
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create a new rule
|
||||
group := generateAlertRuleGroup(1, alertRuleGen())
|
||||
group.Rules[0].GrafanaManagedAlert.MissingSeriesEvalsToResolve = tc.initialValue
|
||||
|
||||
// Post the rule group with our alert rule
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group, false)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
|
||||
// and the value of the missing_series_evals_to_resolve
|
||||
getGroup, status := client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
if tc.initialValue == nil {
|
||||
require.Nil(t, getGroup.Rules[0].GrafanaManagedAlert.MissingSeriesEvalsToResolve)
|
||||
} else {
|
||||
require.Equal(t, *tc.initialValue, *getGroup.Rules[0].GrafanaManagedAlert.MissingSeriesEvalsToResolve)
|
||||
}
|
||||
|
||||
// Now let's update the initial value with the updated value
|
||||
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
group.Rules[0].GrafanaManagedAlert.MissingSeriesEvalsToResolve = tc.updatedValue
|
||||
_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group, false)
|
||||
require.Equalf(t, tc.expectedStatus, status, "failed to post rule group. Response: %s", body)
|
||||
// Check the response status
|
||||
require.Equal(t, tc.expectedStatus, status)
|
||||
if tc.expectedStatus != http.StatusAccepted {
|
||||
// If the status is not accepted, we don't need to check the response body
|
||||
return
|
||||
}
|
||||
|
||||
// Get the group again and check that the value is updated to updatedValue
|
||||
getGroup, status = client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
if tc.expectedValue == nil {
|
||||
require.Nil(t, getGroup.Rules[0].GrafanaManagedAlert.MissingSeriesEvalsToResolve)
|
||||
} else {
|
||||
require.Equal(t, *tc.expectedValue, *getGroup.Rules[0].GrafanaManagedAlert.MissingSeriesEvalsToResolve)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("when data source missing", func(t *testing.T) {
|
||||
var groupName string
|
||||
{
|
||||
@@ -3362,16 +3450,17 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
}`),
|
||||
},
|
||||
},
|
||||
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
|
||||
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
|
||||
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
|
||||
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
|
||||
MissingSeriesEvalsToResolve: util.Pointer(2), // If UID is specified, this field is required
|
||||
},
|
||||
},
|
||||
},
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
response, status, _ := apiClient.PostRulesGroupWithStatus(t, "default", &rules, false)
|
||||
assert.Equal(t, http.StatusAccepted, status)
|
||||
response, status, body := apiClient.PostRulesGroupWithStatus(t, "default", &rules, false)
|
||||
assert.Equal(t, http.StatusAccepted, status, body)
|
||||
|
||||
require.Len(t, response.Created, 1)
|
||||
require.Len(t, response.Updated, 2)
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"isPaused": false
|
||||
"isPaused": false,
|
||||
"missing_series_evals_to_resolve": 1
|
||||
},
|
||||
{
|
||||
"uid": "<dynamic>",
|
||||
@@ -67,9 +68,10 @@
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"isPaused": false
|
||||
"isPaused": false,
|
||||
"missing_series_evals_to_resolve": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
"simplified_query_and_expressions_section": false,
|
||||
"simplified_notifications_section": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"missing_series_evals_to_resolve": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -93,7 +94,8 @@
|
||||
"simplified_query_and_expressions_section": false,
|
||||
"simplified_notifications_section": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"missing_series_evals_to_resolve": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
"exec_err_state": "Alerting",
|
||||
"missing_series_evals_to_resolve": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -49,7 +50,8 @@
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
"exec_err_state": "Alerting",
|
||||
"missing_series_evals_to_resolve": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -12663,6 +12663,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"noDataState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -16112,6 +16116,10 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"namespace_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -18494,6 +18502,12 @@
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"description": "Number of consecutive evaluation intervals with no data for a dimension must pass\nbefore the alert state is considered stale and automatically resolved.\nIf set to 0, the value is reset to the default.",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 3
|
||||
},
|
||||
"no_data_state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -18840,6 +18854,11 @@
|
||||
"team": "sre-team-1"
|
||||
}
|
||||
},
|
||||
"missingSeriesEvalsToResolve": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 2
|
||||
},
|
||||
"noDataState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -22905,6 +22924,7 @@
|
||||
}
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
|
||||
@@ -2724,6 +2724,10 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -6174,6 +6178,10 @@
|
||||
"metadata": {
|
||||
"$ref": "#/components/schemas/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"namespace_uid": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8556,6 +8564,12 @@
|
||||
"metadata": {
|
||||
"$ref": "#/components/schemas/AlertRuleMetadata"
|
||||
},
|
||||
"missing_series_evals_to_resolve": {
|
||||
"description": "Number of consecutive evaluation intervals with no data for a dimension must pass\nbefore the alert state is considered stale and automatically resolved.\nIf set to 0, the value is reset to the default.",
|
||||
"example": 3,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"no_data_state": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -8891,6 +8905,11 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"missingSeriesEvalsToResolve": {
|
||||
"example": 2,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
@@ -12966,6 +12985,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/gettableAlert"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user