Alerting: improve error on failure to remove query referenced by legacy alerting (#42169)

* Improve error and add test

* praise the linter!

* pr feedback

* guard against nil

* getters and setters

* Use IsEnabled instead of accessing struct
This commit is contained in:
David Parrott 2021-12-02 07:41:24 -08:00 committed by GitHub
parent e061cbd25b
commit a53b78df0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 223 additions and 2 deletions

View File

@ -345,7 +345,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
}
dashSvc := dashboards.NewService(hs.SQLStore)
dashboard, err := dashSvc.SaveDashboard(ctx, dashItem, allowUiUpdate)
dashboard, err := dashSvc.SaveDashboard(alerting.WithUAEnabled(ctx, hs.Cfg.UnifiedAlerting.IsEnabled()), dashItem, allowUiUpdate)
if hs.Live != nil {
// Tell everyone listening that the dashboard changed

View File

@ -82,6 +82,25 @@ func copyJSON(in json.Marshaler) (*simplejson.Json, error) {
return simplejson.NewJson(rawJSON)
}
// UAEnabled takes a context and returns true if Unified Alerting is enabled
// and false if it is disabled or the setting is not present in the context
type uaEnabledKeyType string
const uaEnabledKey uaEnabledKeyType = "unified_alerting_enabled"
func WithUAEnabled(ctx context.Context, enabled bool) context.Context {
retCtx := context.WithValue(ctx, uaEnabledKey, enabled)
return retCtx
}
func UAEnabled(ctx context.Context) bool {
enabled, ok := ctx.Value(uaEnabledKey).(bool)
if !ok {
return false
}
return enabled
}
func (e *DashAlertExtractor) getAlertFromPanels(ctx context.Context, jsonWithPanels *simplejson.Json, validateAlertFunc func(*models.Alert) bool, logTranslationFailures bool) ([]*models.Alert, error) {
alerts := make([]*models.Alert, 0)
@ -170,7 +189,12 @@ func (e *DashAlertExtractor) getAlertFromPanels(ctx context.Context, jsonWithPan
panelQuery := findPanelQueryByRefID(panel, queryRefID)
if panelQuery == nil {
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefID)
var reason string
if UAEnabled(ctx) {
reason = fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found. Legacy alerting queries are not able to be removed at this time in order to preserve the ability to rollback to previous versions of Grafana", alert.PanelId, queryRefID)
} else {
reason = fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefID)
}
return nil, ValidationError{Reason: reason}
}

View File

@ -149,6 +149,18 @@ func TestAlertRuleExtraction(t *testing.T) {
require.NotNil(t, err)
})
t.Run("Cannot save panel with query that is referenced by legacy alerting", func(t *testing.T) {
panelWithQuery, err := ioutil.ReadFile("./testdata/panel-with-bad-query-id.json")
require.Nil(t, err)
dashJSON, err := simplejson.NewJson(panelWithQuery)
require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
_, err = extractor.GetAlerts(WithUAEnabled(context.Background(), true))
require.Equal(t, "alert validation error: Alert on PanelId: 2 refers to query(B) that cannot be found. Legacy alerting queries are not able to be removed at this time in order to preserve the ability to rollback to previous versions of Grafana", err.Error())
})
t.Run("Panel does not have datasource configured, use the default datasource", func(t *testing.T) {
panelWithoutSpecifiedDatasource, err := ioutil.ReadFile("./testdata/panel-without-specified-datasource.json")
require.Nil(t, err)

View File

@ -0,0 +1,185 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 436,
"links": [],
"panels": [
{
"alert": {
"alertRuleTags": {},
"conditions": [
{
"evaluator": {
"params": [
3
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"B",
"15s",
"now"
]
},
"reducer": {
"params": [],
"type": "last"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "0m",
"frequency": "10s",
"handler": 1,
"message": "A message",
"name": "Ok / Alerting Cycle test default datasource",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fieldConfig": {
"defaults": {
"custom": {},
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 6,
"w": 12,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.4.0-pre",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"pulseWave": {
"offCount": 6,
"offValue": "2",
"onCount": 6,
"onValue": "4",
"timeStep": 10
},
"refId": "A",
"scenarioId": "predictable_pulse",
"stringInput": ""
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 3
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Alerting / Ok",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:536",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:537",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "extractor test default datasource",
"uid": "bqQguKaMz",
"version": 7
}