Explorar o código

Merge pull request #10274 from bergquist/alerting_without_rows

Alerting without dashboard rows
Carl Bergquist %!s(int64=8) %!d(string=hai) anos
pai
achega
0d9378d18e

+ 91 - 66
pkg/services/alerting/extractor.go

@@ -69,95 +69,120 @@ func copyJson(in *simplejson.Json) (*simplejson.Json, error) {
 	return simplejson.NewJson(rawJson)
 }
 
-func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
-	e.log.Debug("GetAlerts")
+func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) ([]*m.Alert, error) {
+	alerts := make([]*m.Alert, 0)
 
-	dashboardJson, err := copyJson(e.Dash.Data)
-	if err != nil {
-		return nil, err
-	}
+	for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
+		panel := simplejson.NewFromAny(panelObj)
+		jsonAlert, hasAlert := panel.CheckGet("alert")
 
-	alerts := make([]*m.Alert, 0)
-	for _, rowObj := range dashboardJson.Get("rows").MustArray() {
-		row := simplejson.NewFromAny(rowObj)
+		if !hasAlert {
+			continue
+		}
 
-		for _, panelObj := range row.Get("panels").MustArray() {
-			panel := simplejson.NewFromAny(panelObj)
-			jsonAlert, hasAlert := panel.CheckGet("alert")
+		panelId, err := panel.Get("id").Int64()
+		if err != nil {
+			return nil, fmt.Errorf("panel id is required. err %v", err)
+		}
 
-			if !hasAlert {
-				continue
-			}
+		// backward compatibility check, can be removed later
+		enabled, hasEnabled := jsonAlert.CheckGet("enabled")
+		if hasEnabled && enabled.MustBool() == false {
+			continue
+		}
 
-			panelId, err := panel.Get("id").Int64()
-			if err != nil {
-				return nil, fmt.Errorf("panel id is required. err %v", err)
-			}
+		frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
+		if err != nil {
+			return nil, ValidationError{Reason: "Could not parse frequency"}
+		}
+
+		alert := &m.Alert{
+			DashboardId: e.Dash.Id,
+			OrgId:       e.OrgId,
+			PanelId:     panelId,
+			Id:          jsonAlert.Get("id").MustInt64(),
+			Name:        jsonAlert.Get("name").MustString(),
+			Handler:     jsonAlert.Get("handler").MustInt64(),
+			Message:     jsonAlert.Get("message").MustString(),
+			Frequency:   frequency,
+		}
+
+		for _, condition := range jsonAlert.Get("conditions").MustArray() {
+			jsonCondition := simplejson.NewFromAny(condition)
+
+			jsonQuery := jsonCondition.Get("query")
+			queryRefId := jsonQuery.Get("params").MustArray()[0].(string)
+			panelQuery := findPanelQueryByRefId(panel, queryRefId)
 
-			// backward compatibility check, can be removed later
-			enabled, hasEnabled := jsonAlert.CheckGet("enabled")
-			if hasEnabled && enabled.MustBool() == false {
-				continue
+			if panelQuery == nil {
+				reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
+				return nil, ValidationError{Reason: reason}
 			}
 
-			frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
-			if err != nil {
-				return nil, ValidationError{Reason: "Could not parse frequency"}
+			dsName := ""
+			if panelQuery.Get("datasource").MustString() != "" {
+				dsName = panelQuery.Get("datasource").MustString()
+			} else if panel.Get("datasource").MustString() != "" {
+				dsName = panel.Get("datasource").MustString()
 			}
 
-			alert := &m.Alert{
-				DashboardId: e.Dash.Id,
-				OrgId:       e.OrgId,
-				PanelId:     panelId,
-				Id:          jsonAlert.Get("id").MustInt64(),
-				Name:        jsonAlert.Get("name").MustString(),
-				Handler:     jsonAlert.Get("handler").MustInt64(),
-				Message:     jsonAlert.Get("message").MustString(),
-				Frequency:   frequency,
+			if datasource, err := e.lookupDatasourceId(dsName); err != nil {
+				return nil, err
+			} else {
+				jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
 			}
 
-			for _, condition := range jsonAlert.Get("conditions").MustArray() {
-				jsonCondition := simplejson.NewFromAny(condition)
+			if interval, err := panel.Get("interval").String(); err == nil {
+				panelQuery.Set("interval", interval)
+			}
 
-				jsonQuery := jsonCondition.Get("query")
-				queryRefId := jsonQuery.Get("params").MustArray()[0].(string)
-				panelQuery := findPanelQueryByRefId(panel, queryRefId)
+			jsonQuery.Set("model", panelQuery.Interface())
+		}
 
-				if panelQuery == nil {
-					reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
-					return nil, ValidationError{Reason: reason}
-				}
+		alert.Settings = jsonAlert
 
-				dsName := ""
-				if panelQuery.Get("datasource").MustString() != "" {
-					dsName = panelQuery.Get("datasource").MustString()
-				} else if panel.Get("datasource").MustString() != "" {
-					dsName = panel.Get("datasource").MustString()
-				}
+		// validate
+		_, err = NewRuleFromDBAlert(alert)
+		if err == nil && alert.ValidToSave() {
+			alerts = append(alerts, alert)
+		} else {
+			return nil, err
+		}
+	}
 
-				if datasource, err := e.lookupDatasourceId(dsName); err != nil {
-					return nil, err
-				} else {
-					jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
-				}
+	return alerts, nil
+}
 
-				if interval, err := panel.Get("interval").String(); err == nil {
-					panelQuery.Set("interval", interval)
-				}
+func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
+	e.log.Debug("GetAlerts")
 
-				jsonQuery.Set("model", panelQuery.Interface())
-			}
+	dashboardJson, err := copyJson(e.Dash.Data)
+	if err != nil {
+		return nil, err
+	}
 
-			alert.Settings = jsonAlert
+	alerts := make([]*m.Alert, 0)
 
-			// validate
-			_, err = NewRuleFromDBAlert(alert)
-			if err == nil && alert.ValidToSave() {
-				alerts = append(alerts, alert)
-			} else {
+	// We extract alerts from rows to be backwards compatible
+	// with the old dashboard json model.
+	rows := dashboardJson.Get("rows").MustArray()
+	if len(rows) > 0 {
+		for _, rowObj := range rows {
+			row := simplejson.NewFromAny(rowObj)
+			a, err := e.GetAlertFromPanels(row)
+			if err != nil {
 				return nil, err
 			}
+
+			alerts = append(alerts, a...)
+		}
+	} else {
+		a, err := e.GetAlertFromPanels(dashboardJson)
+		if err != nil {
+			return nil, err
 		}
+
+		alerts = append(alerts, a...)
 	}
 
 	e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts))

+ 28 - 416
pkg/services/alerting/extractor_test.go

@@ -1,12 +1,12 @@
 package alerting
 
 import (
+	"io/ioutil"
 	"testing"
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/setting"
 	. "github.com/smartystreets/goconvey/convey"
 )
 
@@ -18,10 +18,6 @@ func TestAlertRuleExtraction(t *testing.T) {
 			return &FakeCondition{}, nil
 		})
 
-		setting.NewConfigContext(&setting.CommandLineArgs{
-			HomePath: "../../../",
-		})
-
 		// mock data
 		defaultDs := &m.DataSource{Id: 12, OrgId: 1, Name: "I am default", IsDefault: true}
 		graphite2Ds := &m.DataSource{Id: 15, OrgId: 1, Name: "graphite2"}
@@ -45,70 +41,8 @@ func TestAlertRuleExtraction(t *testing.T) {
 			return nil
 		})
 
-		json := `
-      {
-        "id": 57,
-        "title": "Graphite 4",
-        "originalTitle": "Graphite 4",
-        "tags": ["graphite"],
-        "rows": [
-        {
-          "panels": [
-          {
-            "title": "Active desktop users",
-            "editable": true,
-            "type": "graph",
-            "id": 3,
-            "targets": [
-            {
-              "refId": "A",
-              "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
-            }
-            ],
-            "datasource": null,
-            "alert": {
-              "name": "name1",
-              "message": "desc1",
-              "handler": 1,
-              "frequency": "60s",
-              "conditions": [
-              {
-                "type": "query",
-                "query": {"params": ["A", "5m", "now"]},
-                "reducer": {"type": "avg", "params": []},
-                "evaluator": {"type": ">", "params": [100]}
-              }
-              ]
-            }
-          },
-          {
-            "title": "Active mobile users",
-            "id": 4,
-            "targets": [
-              {"refId": "A", "target": ""},
-              {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
-            ],
-            "datasource": "graphite2",
-            "alert": {
-              "name": "name2",
-              "message": "desc2",
-              "handler": 0,
-              "frequency": "60s",
-              "severity": "warning",
-              "conditions": [
-              {
-                "type": "query",
-                "query":  {"params": ["B", "5m", "now"]},
-                "reducer": {"type": "avg", "params": []},
-                "evaluator": {"type": ">", "params": [100]}
-              }
-              ]
-            }
-          }
-          ]
-        }
-      ]
-      }`
+		json, err := ioutil.ReadFile("./test-data/graphite-alert.json")
+		So(err, ShouldBeNil)
 
 		Convey("Extractor should not modify the original json", func() {
 			dashJson, err := simplejson.NewJson([]byte(json))
@@ -201,69 +135,8 @@ func TestAlertRuleExtraction(t *testing.T) {
 		})
 
 		Convey("Panels missing id should return error", func() {
-			panelWithoutId := `
-      {
-        "id": 57,
-        "title": "Graphite 4",
-        "originalTitle": "Graphite 4",
-        "tags": ["graphite"],
-        "rows": [
-        {
-          "panels": [
-          {
-            "title": "Active desktop users",
-            "editable": true,
-            "type": "graph",
-            "targets": [
-            {
-              "refId": "A",
-              "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
-            }
-            ],
-            "datasource": null,
-            "alert": {
-              "name": "name1",
-              "message": "desc1",
-              "handler": 1,
-              "frequency": "60s",
-              "conditions": [
-              {
-                "type": "query",
-                "query": {"params": ["A", "5m", "now"]},
-                "reducer": {"type": "avg", "params": []},
-                "evaluator": {"type": ">", "params": [100]}
-              }
-              ]
-            }
-          },
-          {
-            "title": "Active mobile users",
-            "id": 4,
-            "targets": [
-              {"refId": "A", "target": ""},
-              {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
-            ],
-            "datasource": "graphite2",
-            "alert": {
-              "name": "name2",
-              "message": "desc2",
-              "handler": 0,
-              "frequency": "60s",
-              "severity": "warning",
-              "conditions": [
-              {
-                "type": "query",
-                "query":  {"params": ["B", "5m", "now"]},
-                "reducer": {"type": "avg", "params": []},
-                "evaluator": {"type": ">", "params": [100]}
-              }
-              ]
-            }
-          }
-          ]
-        }
-      ]
-			}`
+			panelWithoutId, err := ioutil.ReadFile("./test-data/panels-missing-id.json")
+			So(err, ShouldBeNil)
 
 			dashJson, err := simplejson.NewJson([]byte(panelWithoutId))
 			So(err, ShouldBeNil)
@@ -277,292 +150,31 @@ func TestAlertRuleExtraction(t *testing.T) {
 			})
 		})
 
+		Convey("Parse alerts from dashboard without rows", func() {
+			json, err := ioutil.ReadFile("./test-data/v5-dashboard.json")
+			So(err, ShouldBeNil)
+
+			dashJson, err := simplejson.NewJson(json)
+			So(err, ShouldBeNil)
+			dash := m.NewDashboardFromJson(dashJson)
+			extractor := NewDashAlertExtractor(dash, 1)
+
+			alerts, err := extractor.GetAlerts()
+
+			Convey("Get rules without error", func() {
+				So(err, ShouldBeNil)
+			})
+
+			Convey("Should have 2 alert rule", func() {
+				So(len(alerts), ShouldEqual, 2)
+			})
+		})
+
 		Convey("Parse and validate dashboard containing influxdb alert", func() {
+			json, err := ioutil.ReadFile("./test-data/influxdb-alert.json")
+			So(err, ShouldBeNil)
 
-			json2 := `{
-				  "id": 4,
-				  "title": "Influxdb",
-				  "tags": [
-				    "apa"
-				  ],
-				  "style": "dark",
-				  "timezone": "browser",
-				  "editable": true,
-				  "hideControls": false,
-				  "sharedCrosshair": false,
-				  "rows": [
-				    {
-				      "collapse": false,
-				      "editable": true,
-				      "height": "450px",
-				      "panels": [
-				        {
-				          "alert": {
-				            "conditions": [
-				              {
-				                "evaluator": {
-				                  "params": [
-				                    10
-				                  ],
-				                  "type": "gt"
-				                },
-				                "query": {
-				                  "params": [
-				                    "B",
-				                    "5m",
-				                    "now"
-				                  ]
-				                },
-				                "reducer": {
-				                  "params": [],
-				                  "type": "avg"
-				                },
-				                "type": "query"
-				              }
-				            ],
-				            "frequency": "3s",
-				            "handler": 1,
-				            "name": "Influxdb",
-				            "noDataState": "no_data",
-				            "notifications": [
-				              {
-				                "id": 6
-				              }
-				            ]
-				          },
-				          "alerting": {},
-				          "aliasColors": {
-				            "logins.count.count": "#890F02"
-				          },
-				          "bars": false,
-				          "datasource": "InfluxDB",
-				          "editable": true,
-				          "error": false,
-				          "fill": 1,
-				          "grid": {},
-				          "id": 1,
-				          "interval": ">10s",
-				          "isNew": true,
-				          "legend": {
-				            "avg": false,
-				            "current": false,
-				            "max": false,
-				            "min": false,
-				            "show": true,
-				            "total": false,
-				            "values": false
-				          },
-				          "lines": true,
-				          "linewidth": 2,
-				          "links": [],
-				          "nullPointMode": "connected",
-				          "percentage": false,
-				          "pointradius": 5,
-				          "points": false,
-				          "renderer": "flot",
-				          "seriesOverrides": [],
-				          "span": 10,
-				          "stack": false,
-				          "steppedLine": false,
-				          "targets": [
-				            {
-				              "groupBy": [
-				                {
-				                  "params": [
-				                    "$interval"
-				                  ],
-				                  "type": "time"
-				                },
-				                {
-				                  "params": [
-				                    "datacenter"
-				                  ],
-				                  "type": "tag"
-				                },
-				                {
-				                  "params": [
-				                    "none"
-				                  ],
-				                  "type": "fill"
-				                }
-				              ],
-				              "hide": false,
-				              "measurement": "logins.count",
-				              "policy": "default",
-				              "query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
-				              "rawQuery": true,
-				              "refId": "B",
-				              "resultFormat": "time_series",
-				              "select": [
-				                [
-				                  {
-				                    "params": [
-				                      "value"
-				                    ],
-				                    "type": "field"
-				                  },
-				                  {
-				                    "params": [],
-				                    "type": "count"
-				                  }
-				                ]
-				              ],
-				              "tags": []
-				            },
-				            {
-				              "groupBy": [
-				                {
-				                  "params": [
-				                    "$interval"
-				                  ],
-				                  "type": "time"
-				                },
-				                {
-				                  "params": [
-				                    "null"
-				                  ],
-				                  "type": "fill"
-				                }
-				              ],
-				              "hide": true,
-				              "measurement": "cpu",
-				              "policy": "default",
-				              "refId": "A",
-				              "resultFormat": "time_series",
-				              "select": [
-				                [
-				                  {
-				                    "params": [
-				                      "value"
-				                    ],
-				                    "type": "field"
-				                  },
-				                  {
-				                    "params": [],
-				                    "type": "mean"
-				                  }
-				                ],
-				                [
-				                  {
-				                    "params": [
-				                      "value"
-				                    ],
-				                    "type": "field"
-				                  },
-				                  {
-				                    "params": [],
-				                    "type": "sum"
-				                  }
-				                ]
-				              ],
-				              "tags": []
-				            }
-				          ],
-				          "thresholds": [
-				            {
-				              "colorMode": "critical",
-				              "fill": true,
-				              "line": true,
-				              "op": "gt",
-				              "value": 10
-				            }
-				          ],
-				          "timeFrom": null,
-				          "timeShift": null,
-				          "title": "Panel Title",
-				          "tooltip": {
-				            "msResolution": false,
-				            "ordering": "alphabetical",
-				            "shared": true,
-				            "sort": 0,
-				            "value_type": "cumulative"
-				          },
-				          "type": "graph",
-				          "xaxis": {
-				            "mode": "time",
-				            "name": null,
-				            "show": true,
-				            "values": []
-				          },
-				          "yaxes": [
-				            {
-				              "format": "short",
-				              "logBase": 1,
-				              "max": null,
-				              "min": null,
-				              "show": true
-				            },
-				            {
-				              "format": "short",
-				              "logBase": 1,
-				              "max": null,
-				              "min": null,
-				              "show": true
-				            }
-				          ]
-				        },
-				        {
-				          "editable": true,
-				          "error": false,
-				          "id": 2,
-				          "isNew": true,
-				          "limit": 10,
-				          "links": [],
-				          "show": "current",
-				          "span": 2,
-				          "stateFilter": [
-				            "alerting"
-				          ],
-				          "title": "Alert status",
-				          "type": "alertlist"
-				        }
-				      ],
-				      "title": "Row"
-				    }
-				  ],
-				  "time": {
-				    "from": "now-5m",
-				    "to": "now"
-				  },
-				  "timepicker": {
-				    "now": true,
-				    "refresh_intervals": [
-				      "5s",
-				      "10s",
-				      "30s",
-				      "1m",
-				      "5m",
-				      "15m",
-				      "30m",
-				      "1h",
-				      "2h",
-				      "1d"
-				    ],
-				    "time_options": [
-				      "5m",
-				      "15m",
-				      "1h",
-				      "6h",
-				      "12h",
-				      "24h",
-				      "2d",
-				      "7d",
-				      "30d"
-				    ]
-				  },
-				  "templating": {
-				    "list": []
-				  },
-				  "annotations": {
-				    "list": []
-				  },
-				  "schemaVersion": 13,
-				  "version": 120,
-				  "links": [],
-				  "gnetId": null
-				}`
-
-			dashJson, err := simplejson.NewJson([]byte(json2))
+			dashJson, err := simplejson.NewJson(json)
 			So(err, ShouldBeNil)
 			dash := m.NewDashboardFromJson(dashJson)
 			extractor := NewDashAlertExtractor(dash, 1)

+ 63 - 0
pkg/services/alerting/test-data/graphite-alert.json

@@ -0,0 +1,63 @@
+{
+    "id": 57,
+    "title": "Graphite 4",
+    "originalTitle": "Graphite 4",
+    "tags": ["graphite"],
+    "rows": [
+    {
+      "panels": [
+      {
+        "title": "Active desktop users",
+        "editable": true,
+        "type": "graph",
+        "id": 3,
+        "targets": [
+        {
+          "refId": "A",
+          "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
+        }
+        ],
+        "datasource": null,
+        "alert": {
+          "name": "name1",
+          "message": "desc1",
+          "handler": 1,
+          "frequency": "60s",
+          "conditions": [
+          {
+            "type": "query",
+            "query": {"params": ["A", "5m", "now"]},
+            "reducer": {"type": "avg", "params": []},
+            "evaluator": {"type": ">", "params": [100]}
+          }
+          ]
+        }
+      },
+      {
+        "title": "Active mobile users",
+        "id": 4,
+        "targets": [
+          {"refId": "A", "target": ""},
+          {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
+        ],
+        "datasource": "graphite2",
+        "alert": {
+          "name": "name2",
+          "message": "desc2",
+          "handler": 0,
+          "frequency": "60s",
+          "severity": "warning",
+          "conditions": [
+          {
+            "type": "query",
+            "query":  {"params": ["B", "5m", "now"]},
+            "reducer": {"type": "avg", "params": []},
+            "evaluator": {"type": ">", "params": [100]}
+          }
+          ]
+        }
+      }
+      ]
+    }
+  ]
+  }

+ 282 - 0
pkg/services/alerting/test-data/influxdb-alert.json

@@ -0,0 +1,282 @@
+{
+    "id": 4,
+    "title": "Influxdb",
+    "tags": [
+      "apa"
+    ],
+    "style": "dark",
+    "timezone": "browser",
+    "editable": true,
+    "hideControls": false,
+    "sharedCrosshair": false,
+    "rows": [
+      {
+        "collapse": false,
+        "editable": true,
+        "height": "450px",
+        "panels": [
+          {
+            "alert": {
+              "conditions": [
+                {
+                  "evaluator": {
+                    "params": [
+                      10
+                    ],
+                    "type": "gt"
+                  },
+                  "query": {
+                    "params": [
+                      "B",
+                      "5m",
+                      "now"
+                    ]
+                  },
+                  "reducer": {
+                    "params": [],
+                    "type": "avg"
+                  },
+                  "type": "query"
+                }
+              ],
+              "frequency": "3s",
+              "handler": 1,
+              "name": "Influxdb",
+              "noDataState": "no_data",
+              "notifications": [
+                {
+                  "id": 6
+                }
+              ]
+            },
+            "alerting": {},
+            "aliasColors": {
+              "logins.count.count": "#890F02"
+            },
+            "bars": false,
+            "datasource": "InfluxDB",
+            "editable": true,
+            "error": false,
+            "fill": 1,
+            "grid": {},
+            "id": 1,
+            "interval": ">10s",
+            "isNew": true,
+            "legend": {
+              "avg": false,
+              "current": false,
+              "max": false,
+              "min": false,
+              "show": true,
+              "total": false,
+              "values": false
+            },
+            "lines": true,
+            "linewidth": 2,
+            "links": [],
+            "nullPointMode": "connected",
+            "percentage": false,
+            "pointradius": 5,
+            "points": false,
+            "renderer": "flot",
+            "seriesOverrides": [],
+            "span": 10,
+            "stack": false,
+            "steppedLine": false,
+            "targets": [
+              {
+                "groupBy": [
+                  {
+                    "params": [
+                      "$interval"
+                    ],
+                    "type": "time"
+                  },
+                  {
+                    "params": [
+                      "datacenter"
+                    ],
+                    "type": "tag"
+                  },
+                  {
+                    "params": [
+                      "none"
+                    ],
+                    "type": "fill"
+                  }
+                ],
+                "hide": false,
+                "measurement": "logins.count",
+                "policy": "default",
+                "query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
+                "rawQuery": true,
+                "refId": "B",
+                "resultFormat": "time_series",
+                "select": [
+                  [
+                    {
+                      "params": [
+                        "value"
+                      ],
+                      "type": "field"
+                    },
+                    {
+                      "params": [],
+                      "type": "count"
+                    }
+                  ]
+                ],
+                "tags": []
+              },
+              {
+                "groupBy": [
+                  {
+                    "params": [
+                      "$interval"
+                    ],
+                    "type": "time"
+                  },
+                  {
+                    "params": [
+                      "null"
+                    ],
+                    "type": "fill"
+                  }
+                ],
+                "hide": true,
+                "measurement": "cpu",
+                "policy": "default",
+                "refId": "A",
+                "resultFormat": "time_series",
+                "select": [
+                  [
+                    {
+                      "params": [
+                        "value"
+                      ],
+                      "type": "field"
+                    },
+                    {
+                      "params": [],
+                      "type": "mean"
+                    }
+                  ],
+                  [
+                    {
+                      "params": [
+                        "value"
+                      ],
+                      "type": "field"
+                    },
+                    {
+                      "params": [],
+                      "type": "sum"
+                    }
+                  ]
+                ],
+                "tags": []
+              }
+            ],
+            "thresholds": [
+              {
+                "colorMode": "critical",
+                "fill": true,
+                "line": true,
+                "op": "gt",
+                "value": 10
+              }
+            ],
+            "timeFrom": null,
+            "timeShift": null,
+            "title": "Panel Title",
+            "tooltip": {
+              "msResolution": false,
+              "ordering": "alphabetical",
+              "shared": true,
+              "sort": 0,
+              "value_type": "cumulative"
+            },
+            "type": "graph",
+            "xaxis": {
+              "mode": "time",
+              "name": null,
+              "show": true,
+              "values": []
+            },
+            "yaxes": [
+              {
+                "format": "short",
+                "logBase": 1,
+                "max": null,
+                "min": null,
+                "show": true
+              },
+              {
+                "format": "short",
+                "logBase": 1,
+                "max": null,
+                "min": null,
+                "show": true
+              }
+            ]
+          },
+          {
+            "editable": true,
+            "error": false,
+            "id": 2,
+            "isNew": true,
+            "limit": 10,
+            "links": [],
+            "show": "current",
+            "span": 2,
+            "stateFilter": [
+              "alerting"
+            ],
+            "title": "Alert status",
+            "type": "alertlist"
+          }
+        ],
+        "title": "Row"
+      }
+    ],
+    "time": {
+      "from": "now-5m",
+      "to": "now"
+    },
+    "timepicker": {
+      "now": true,
+      "refresh_intervals": [
+        "5s",
+        "10s",
+        "30s",
+        "1m",
+        "5m",
+        "15m",
+        "30m",
+        "1h",
+        "2h",
+        "1d"
+      ],
+      "time_options": [
+        "5m",
+        "15m",
+        "1h",
+        "6h",
+        "12h",
+        "24h",
+        "2d",
+        "7d",
+        "30d"
+      ]
+    },
+    "templating": {
+      "list": []
+    },
+    "annotations": {
+      "list": []
+    },
+    "schemaVersion": 13,
+    "version": 120,
+    "links": [],
+    "gnetId": null
+  }

+ 62 - 0
pkg/services/alerting/test-data/panels-missing-id.json

@@ -0,0 +1,62 @@
+{
+    "id": 57,
+    "title": "Graphite 4",
+    "originalTitle": "Graphite 4",
+    "tags": ["graphite"],
+    "rows": [
+    {
+      "panels": [
+      {
+        "title": "Active desktop users",
+        "editable": true,
+        "type": "graph",
+        "targets": [
+        {
+          "refId": "A",
+          "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
+        }
+        ],
+        "datasource": null,
+        "alert": {
+          "name": "name1",
+          "message": "desc1",
+          "handler": 1,
+          "frequency": "60s",
+          "conditions": [
+          {
+            "type": "query",
+            "query": {"params": ["A", "5m", "now"]},
+            "reducer": {"type": "avg", "params": []},
+            "evaluator": {"type": ">", "params": [100]}
+          }
+          ]
+        }
+      },
+      {
+        "title": "Active mobile users",
+        "id": 4,
+        "targets": [
+          {"refId": "A", "target": ""},
+          {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
+        ],
+        "datasource": "graphite2",
+        "alert": {
+          "name": "name2",
+          "message": "desc2",
+          "handler": 0,
+          "frequency": "60s",
+          "severity": "warning",
+          "conditions": [
+          {
+            "type": "query",
+            "query":  {"params": ["B", "5m", "now"]},
+            "reducer": {"type": "avg", "params": []},
+            "evaluator": {"type": ">", "params": [100]}
+          }
+          ]
+        }
+      }
+      ]
+    }
+  ]
+        }

+ 60 - 0
pkg/services/alerting/test-data/v5-dashboard.json

@@ -0,0 +1,60 @@
+{
+    "id": 57,
+    "title": "Graphite 4",
+    "originalTitle": "Graphite 4",
+    "tags": ["graphite"],
+      "panels": [
+      {
+        "title": "Active desktop users",
+        "editable": true,
+        "type": "graph",
+        "id": 3,
+        "targets": [
+        {
+          "refId": "A",
+          "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
+        }
+        ],
+        "datasource": null,
+        "alert": {
+          "name": "name1",
+          "message": "desc1",
+          "handler": 1,
+          "frequency": "60s",
+          "conditions": [
+          {
+            "type": "query",
+            "query": {"params": ["A", "5m", "now"]},
+            "reducer": {"type": "avg", "params": []},
+            "evaluator": {"type": ">", "params": [100]}
+          }
+          ]
+        }
+      },
+      {
+        "title": "Active mobile users",
+        "id": 4,
+        "targets": [
+          {"refId": "A", "target": ""},
+          {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
+        ],
+        "datasource": "graphite2",
+        "alert": {
+          "name": "name2",
+          "message": "desc2",
+          "handler": 0,
+          "frequency": "60s",
+          "severity": "warning",
+          "conditions": [
+          {
+            "type": "query",
+            "query":  {"params": ["B", "5m", "now"]},
+            "reducer": {"type": "avg", "params": []},
+            "evaluator": {"type": ">", "params": [100]}
+          }
+          ]
+        }
+
+    }
+  ]
+  }