diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index ad8d984e43f..0cb0cfacb7e 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -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 diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index e8d61888dfa..c5ebabe3344 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -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} } diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index c9f6d51f2e0..71416f2c6a2 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -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) diff --git a/pkg/services/alerting/testdata/panel-with-bad-query-id.json b/pkg/services/alerting/testdata/panel-with-bad-query-id.json new file mode 100644 index 00000000000..8fd18ce0625 --- /dev/null +++ b/pkg/services/alerting/testdata/panel-with-bad-query-id.json @@ -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 +}