浏览代码

Merge branch 'master' into develop

Torkel Ödegaard 8 年之前
父节点
当前提交
18337f610d
共有 100 个文件被更改,包括 837 次插入1848 次删除
  1. 1 0
      .gitignore
  2. 8 0
      CHANGELOG.md
  3. 17 0
      conf/defaults.ini
  4. 17 0
      conf/sample.ini
  5. 6 0
      docker/blocks/jaeger/fig
  6. 1 0
      docker/blocks/prometheus/Dockerfile
  7. 10 0
      docker/blocks/prometheus/alert.rules
  8. 5 0
      docker/blocks/prometheus/fig
  9. 9 0
      docker/blocks/prometheus/prometheus.yml
  10. 3 3
      docs/sources/installation/configuration.md
  11. 10 3
      package.json
  12. 1 1
      pkg/api/api.go
  13. 1 1
      pkg/api/cloudwatch/metrics.go
  14. 2 2
      pkg/api/metrics.go
  15. 16 0
      pkg/api/pluginproxy/ds_proxy.go
  16. 9 1
      pkg/api/route_register.go
  17. 11 0
      pkg/api/route_register_test.go
  18. 1 1
      pkg/cmd/grafana-server/main.go
  19. 11 2
      pkg/cmd/grafana-server/server.go
  20. 23 7
      pkg/metrics/graphitebridge/graphite.go
  21. 68 3
      pkg/metrics/graphitebridge/graphite_test.go
  22. 5 5
      pkg/metrics/metrics.go
  23. 36 0
      pkg/middleware/request_tracing.go
  24. 2 2
      pkg/services/alerting/conditions/query.go
  25. 1 1
      pkg/services/alerting/conditions/query_test.go
  26. 28 1
      pkg/services/alerting/engine.go
  27. 6 1
      pkg/services/alerting/extractor.go
  28. 77 0
      pkg/services/alerting/extractor_test.go
  29. 0 1
      pkg/services/sqlstore/user.go
  30. 116 0
      pkg/tracing/tracing.go
  31. 36 0
      pkg/tracing/tracing_test.go
  32. 0 90
      pkg/tsdb/batch.go
  33. 0 36
      pkg/tsdb/executor.go
  34. 3 3
      pkg/tsdb/fake_test.go
  35. 39 25
      pkg/tsdb/graphite/graphite.go
  36. 23 26
      pkg/tsdb/influxdb/influxdb.go
  37. 5 5
      pkg/tsdb/influxdb/query.go
  38. 7 7
      pkg/tsdb/influxdb/query_part.go
  39. 1 1
      pkg/tsdb/influxdb/query_part_test.go
  40. 3 3
      pkg/tsdb/influxdb/query_test.go
  41. 5 8
      pkg/tsdb/models.go
  42. 0 129
      pkg/tsdb/mqe/httpClient.go
  43. 0 60
      pkg/tsdb/mqe/model_parser.go
  44. 0 127
      pkg/tsdb/mqe/model_parser_test.go
  45. 0 85
      pkg/tsdb/mqe/mqe.go
  46. 0 177
      pkg/tsdb/mqe/response_parser.go
  47. 0 187
      pkg/tsdb/mqe/response_parser_test.go
  48. 0 101
      pkg/tsdb/mqe/token_client.go
  49. 0 27
      pkg/tsdb/mqe/token_client_test.go
  50. 0 137
      pkg/tsdb/mqe/types.go
  51. 0 95
      pkg/tsdb/mqe/types_test.go
  52. 20 18
      pkg/tsdb/mysql/mysql.go
  53. 26 18
      pkg/tsdb/opentsdb/opentsdb.go
  54. 20 16
      pkg/tsdb/prometheus/prometheus.go
  55. 0 21
      pkg/tsdb/query_context.go
  56. 36 0
      pkg/tsdb/query_endpoint.go
  57. 12 49
      pkg/tsdb/request.go
  58. 7 7
      pkg/tsdb/testdata/scenarios.go
  59. 5 5
      pkg/tsdb/testdata/testdata.go
  60. 7 108
      pkg/tsdb/tsdb_test.go
  61. 2 0
      public/app/app.ts
  62. 39 0
      public/app/core/components/PasswordStrength.tsx
  63. 0 58
      public/app/core/components/collapse_box.ts
  64. 1 4
      public/app/core/components/colorpicker.ts
  65. 0 3
      public/app/core/components/dashboard_selector.ts
  66. 0 1
      public/app/core/components/form_dropdown/form_dropdown.ts
  67. 0 2
      public/app/core/components/grafana_app.ts
  68. 1 1
      public/app/core/components/help/help.ts
  69. 0 1
      public/app/core/components/info_popover.ts
  70. 1 1
      public/app/core/components/json_explorer/helpers.ts
  71. 1 2
      public/app/core/components/json_explorer/json_explorer.ts
  72. 0 3
      public/app/core/components/layout_selector/layout_selector.ts
  73. 2 5
      public/app/core/components/navbar/navbar.ts
  74. 0 1
      public/app/core/components/org_switcher.ts
  75. 1 1
      public/app/core/components/row_ctrl.ts
  76. 0 1
      public/app/core/components/scroll/scroll.ts
  77. 1 5
      public/app/core/components/search/search.ts
  78. 1 1
      public/app/core/components/sidemenu/sidemenu.ts
  79. 0 4
      public/app/core/components/switch.ts
  80. 1 2
      public/app/core/components/user_group_picker.ts
  81. 1 2
      public/app/core/components/user_picker.ts
  82. 0 32
      public/app/core/components/wizard/wizard.html
  83. 0 73
      public/app/core/components/wizard/wizard.ts
  84. 3 3
      public/app/core/controllers/signup_ctrl.ts
  85. 2 4
      public/app/core/core.ts
  86. 1 1
      public/app/core/directives/diff-view.ts
  87. 1 1
      public/app/core/directives/password_strength.js
  88. 0 2
      public/app/core/directives/rebuild_on_change.ts
  89. 0 1
      public/app/core/live/live_srv.ts
  90. 0 1
      public/app/core/profiler.ts
  91. 0 1
      public/app/core/routes/routes.ts
  92. 1 2
      public/app/core/services/alert_srv.ts
  93. 1 3
      public/app/core/services/backend_srv.ts
  94. 1 1
      public/app/core/services/dynamic_directive_srv.ts
  95. 1 4
      public/app/core/services/keybindingSrv.ts
  96. 0 2
      public/app/core/services/popover_srv.ts
  97. 0 4
      public/app/core/services/util_srv.ts
  98. 14 0
      public/app/core/specs/PasswordStrength_specs.tsx
  99. 4 3
      public/app/core/time_series2.ts
  100. 0 6
      public/app/core/utils/emitter.ts

+ 1 - 0
.gitignore

@@ -25,6 +25,7 @@ public/css/*.min.css
 .idea/
 *.iml
 *.tmp
+.DS_Store
 .vscode/
 
 /data/*

+ 8 - 0
CHANGELOG.md

@@ -13,10 +13,17 @@
 * **GCS**: Adds support for Google Cloud Storage [#8370](https://github.com/grafana/grafana/issues/8370) thx [@chuhlomin](https://github.com/chuhlomin)
 * **Prometheus**: Adds /metrics endpoint for exposing Grafana metrics. [#9187](https://github.com/grafana/grafana/pull/9187)
 * **Graph**: Add support for local formating in axis. [#1395](https://github.com/grafana/grafana/issues/1395), thx [@m0nhawk](https://github.com/m0nhawk)
+* **Jaeger**: Add support for open tracing using jaeger in Grafana. [#9213](https://github.com/grafana/grafana/pull/9213)
+* **Unit types**: New date & time unit types added, useful in singlestat to show dates & times. [#3678](https://github.com/grafana/grafana/issues/3678), [#6710](https://github.com/grafana/grafana/issues/6710), [#2764](https://github.com/grafana/grafana/issues/6710)
 
 ## Breaking changes
 * **Metrics**: The metric structure for internal metrics about Grafana published to graphite has changed. This might break dashboards for internal metrics. 
 
+# 4.5.2 (unreleased)
+
+## Fixes 
+* **Metrics**: dont write NaN values to graphite [#9279](https://github.com/grafana/grafana/issues/9279)
+
 # 4.5.1 (2017-09-15)
 
 ## Fixes
@@ -49,6 +56,7 @@
 ### Breaking change
 
 * **InfluxDB/Elasticsearch**: The panel & data source option named "Group by time interval" is now named "Min time interval" and does now always define a lower limit for the auto group by time. Without having to use `>` prefix (that prefix still works). This should in theory have close to zero actual impact on existing dashboards. It does mean that if you used this setting to define a hard group by time interval of, say "1d", if you zoomed to a time range wide enough the time range could increase above the "1d" range as the setting is now always considered a lower limit.
+* **Elasticsearch**: Elasticsearch metric queries without date histogram now return table formated data making table panel much easier to use for this use case. Should not break/change existing dashboards with stock panels but external panel plugins can be affected. 
 
 ## Changes
 

+ 17 - 0
conf/defaults.ini

@@ -452,6 +452,23 @@ url = https://grafana.com
 [grafana_com]
 url = https://grafana.com
 
+#################################### Distributed tracing ############
+[tracing.jaeger]
+# jaeger destination (ex localhost:6831)
+address =
+# tag that will always be included in when creating new spans. ex (tag1:value1,tag2:value2)
+always_included_tag =
+# Type specifies the type of the sampler: const, probabilistic, rateLimiting, or remote
+sampler_type = const
+# jaeger samplerconfig param
+# for "const" sampler, 0 or 1 for always false/true respectively
+# for "probabilistic" sampler, a probability between 0 and 1
+# for "rateLimiting" sampler, the number of spans per second
+# for "remote" sampler, param is the same as for "probabilistic"
+# and indicates the initial sampling rate before the actual one
+# is received from the mothership
+sampler_param = 1
+
 #################################### External Image Storage ##############
 [external_image_storage]
 # You can choose between (s3, webdav, gcs)

+ 17 - 0
conf/sample.ini

@@ -391,6 +391,23 @@
 ;address =
 ;prefix = prod.grafana.%(instance_name)s.
 
+#################################### Distributed tracing ############
+[tracing.jaeger]
+# Enable by setting the address sending traces to jaeger (ex localhost:6831)
+;address = localhost:6831
+# Tag that will always be included in when creating new spans. ex (tag1:value1,tag2:value2)
+;always_included_tag = tag1:value1
+# Type specifies the type of the sampler: const, probabilistic, rateLimiting, or remote
+;sampler_type = const
+# jaeger samplerconfig param
+# for "const" sampler, 0 or 1 for always false/true respectively
+# for "probabilistic" sampler, a probability between 0 and 1
+# for "rateLimiting" sampler, the number of spans per second
+# for "remote" sampler, param is the same as for "probabilistic"
+# and indicates the initial sampling rate before the actual one
+# is received from the mothership
+;sampler_param = 1
+
 #################################### Grafana.com integration  ##########################
 # Url used to to import dashboards directly from Grafana.com
 [grafana_com]

+ 6 - 0
docker/blocks/jaeger/fig

@@ -0,0 +1,6 @@
+jaeger:
+  image: jaegertracing/all-in-one:latest
+  ports:
+    - "localhost:6831:6831/udp"
+    - "16686:16686"
+  

+ 1 - 0
docker/blocks/prometheus/Dockerfile

@@ -1,2 +1,3 @@
 FROM prom/prometheus
 ADD prometheus.yml /etc/prometheus/
+ADD alert.rules /etc/prometheus/

+ 10 - 0
docker/blocks/prometheus/alert.rules

@@ -0,0 +1,10 @@
+# Alert Rules
+
+ALERT AppCrash
+  IF process_open_fds > 0
+  FOR 15s
+  LABELS { severity="critical" }
+  ANNOTATIONS {
+   summary = "Number of open fds > 0",
+   description = "Just testing"
+  }

+ 5 - 0
docker/blocks/prometheus/fig

@@ -18,3 +18,8 @@ fake-prometheus-data:
   environment:
     FD_DATASOURCE: prom
 
+alertmanager:
+  image: quay.io/prometheus/alertmanager
+  net: host
+  ports:
+    - "9093:9093"

+ 9 - 0
docker/blocks/prometheus/prometheus.yml

@@ -6,9 +6,18 @@ global:
 
 # Load and evaluate rules in this file every 'evaluation_interval' seconds.
 rule_files:
+  - "alert.rules"
   # - "first.rules"
   # - "second.rules"
 
+alerting:
+  alertmanagers:
+  - scheme: http
+    static_configs:
+    - targets:
+      - "127.0.0.1:9093"
+
+
 # A scrape configuration containing exactly one endpoint to scrape:
 # Here it's Prometheus itself.
 scrape_configs:

+ 3 - 3
docs/sources/installation/configuration.md

@@ -308,15 +308,15 @@ options are `Editor` and `Admin`.
 
 ## [auth.github]
 
-You need to create a GitHub application (you find this under the GitHub
-profile page). When you create the application you will need to specify
+You need to create a GitHub OAuth application (you find this under the GitHub
+settings page). When you create the application you will need to specify
 a callback URL. Specify this as callback:
 
     http://<my_grafana_server_name_or_ip>:<grafana_server_port>/login/github
 
 This callback URL must match the full HTTP address that you use in your
 browser to access Grafana, but with the prefix path of `/login/github`.
-When the GitHub application is created you will get a Client ID and a
+When the GitHub OAuth application is created you will get a Client ID and a
 Client Secret. Specify these in the Grafana configuration file. For
 example:
 

+ 10 - 3
package.json

@@ -10,6 +10,8 @@
     "url": "http://github.com/grafana/grafana.git"
   },
   "devDependencies": {
+    "@types/react": "^16.0.5",
+    "@types/react-dom": "^15.5.4",
     "autoprefixer": "^6.4.0",
     "es6-promise": "^3.0.2",
     "es6-shim": "^0.35.1",
@@ -48,7 +50,7 @@
     "mocha": "3.2.0",
     "phantomjs-prebuilt": "^2.1.14",
     "reflect-metadata": "0.1.8",
-    "rxjs": "^5.0.0-rc.5",
+    "rxjs": "^5.4.3",
     "sass-lint": "^1.10.2",
     "systemjs": "0.19.41",
     "zone.js": "^0.7.2"
@@ -60,6 +62,7 @@
   },
   "license": "Apache-2.0",
   "dependencies": {
+    "@types/enzyme": "^2.8.8",
     "ace-builds": "^1.2.8",
     "eventemitter3": "^2.0.2",
     "gaze": "^1.1.2",
@@ -73,13 +76,17 @@
     "karma-sinon": "^1.0.5",
     "lodash": "^4.17.4",
     "mousetrap": "^1.6.0",
+    "ngreact": "^0.4.1",
+    "react": "^15.6.1",
+    "react-dom": "^15.6.1",
+    "react-test-renderer": "^15.6.1",
     "remarkable": "^1.7.1",
     "sinon": "1.17.6",
     "systemjs-builder": "^0.15.34",
     "tether": "^1.4.0",
     "tether-drop": "https://github.com/torkelo/drop",
-    "tslint": "^5.1.0",
-    "typescript": "^2.2.2",
+    "tslint": "^5.7.0",
+    "typescript": "^2.5.2",
     "virtual-scroll": "^1.1.1"
   }
 }

+ 1 - 1
pkg/api/api.go

@@ -21,7 +21,7 @@ func (hs *HttpServer) registerRoutes() {
 	// automatically set HEAD for every GET
 	macaronR.SetAutoHead(true)
 
-	r := newRouteRegister(middleware.RequestMetrics)
+	r := newRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)
 
 	// not logged in views
 	r.Get("/", reqSignedIn, Index)

+ 1 - 1
pkg/api/cloudwatch/metrics.go

@@ -126,7 +126,7 @@ func init() {
 		"AWS/NATGateway":       {"NatGatewayId"},
 		"AWS/OpsWorks":         {"StackId", "LayerId", "InstanceId"},
 		"AWS/Redshift":         {"NodeID", "ClusterIdentifier"},
-		"AWS/RDS":              {"DBInstanceIdentifier", "DBClusterIdentifier", "DatabaseClass", "EngineName", "Role"},
+		"AWS/RDS":              {"DBInstanceIdentifier", "DBClusterIdentifier", "DbClusterIdentifier", "DatabaseClass", "EngineName", "Role"},
 		"AWS/Route53":          {"HealthCheckId", "Region"},
 		"AWS/S3":               {"BucketName", "StorageType", "FilterId"},
 		"AWS/SES":              {},

+ 2 - 2
pkg/api/metrics.go

@@ -31,7 +31,7 @@ func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response {
 		return ApiError(500, "failed to fetch data source", err)
 	}
 
-	request := &tsdb.Request{TimeRange: timeRange}
+	request := &tsdb.TsdbQuery{TimeRange: timeRange}
 
 	for _, query := range reqDto.Queries {
 		request.Queries = append(request.Queries, &tsdb.Query{
@@ -98,7 +98,7 @@ func GetTestDataRandomWalk(c *middleware.Context) Response {
 	intervalMs := c.QueryInt64("intervalMs")
 
 	timeRange := tsdb.NewTimeRange(from, to)
-	request := &tsdb.Request{TimeRange: timeRange}
+	request := &tsdb.TsdbQuery{TimeRange: timeRange}
 
 	request.Queries = append(request.Queries, &tsdb.Query{
 		RefId:      "A",

+ 16 - 0
pkg/api/pluginproxy/ds_proxy.go

@@ -15,6 +15,8 @@ import (
 	"text/template"
 	"time"
 
+	"github.com/opentracing/opentracing-go"
+
 	"github.com/grafana/grafana/pkg/api/cloudwatch"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/middleware"
@@ -85,6 +87,20 @@ func (proxy *DataSourceProxy) HandleRequest() {
 
 	proxy.logRequest()
 
+	span, ctx := opentracing.StartSpanFromContext(proxy.ctx.Req.Context(), "datasource reverse proxy")
+	proxy.ctx.Req.Request = proxy.ctx.Req.WithContext(ctx)
+
+	defer span.Finish()
+	span.SetTag("datasource_id", proxy.ds.Id)
+	span.SetTag("datasource_type", proxy.ds.Type)
+	span.SetTag("user_id", proxy.ctx.SignedInUser.UserId)
+	span.SetTag("org_id", proxy.ctx.SignedInUser.OrgId)
+
+	opentracing.GlobalTracer().Inject(
+		span.Context(),
+		opentracing.HTTPHeaders,
+		opentracing.HTTPHeadersCarrier(proxy.ctx.Req.Request.Header))
+
 	reverseProxy.ServeHTTP(proxy.ctx.Resp, proxy.ctx.Req.Request)
 	proxy.ctx.Resp.Header().Del("Set-Cookie")
 }

+ 9 - 1
pkg/api/route_register.go

@@ -8,6 +8,7 @@ import (
 
 type Router interface {
 	Handle(method, pattern string, handlers []macaron.Handler) *macaron.Route
+	Get(pattern string, handlers ...macaron.Handler) *macaron.Route
 }
 
 type RouteRegister interface {
@@ -62,7 +63,14 @@ func (rr *routeRegister) Group(pattern string, fn func(rr RouteRegister), handle
 
 func (rr *routeRegister) Register(router Router) *macaron.Router {
 	for _, r := range rr.routes {
-		router.Handle(r.method, r.pattern, r.handlers)
+		// GET requests have to be added to macaron routing using Get()
+		// Otherwise HEAD requests will not be allowed.
+		// https://github.com/go-macaron/macaron/blob/a325110f8b392bce3e5cdeb8c44bf98078ada3be/router.go#L198
+		if r.method == http.MethodGet {
+			router.Get(r.pattern, r.handlers...)
+		} else {
+			router.Handle(r.method, r.pattern, r.handlers)
+		}
 	}
 
 	for _, g := range rr.groups {

+ 11 - 0
pkg/api/route_register_test.go

@@ -1,6 +1,7 @@
 package api
 
 import (
+	"net/http"
 	"strconv"
 	"testing"
 
@@ -21,6 +22,16 @@ func (fr *fakeRouter) Handle(method, pattern string, handlers []macaron.Handler)
 	return &macaron.Route{}
 }
 
+func (fr *fakeRouter) Get(pattern string, handlers ...macaron.Handler) *macaron.Route {
+	fr.route = append(fr.route, route{
+		pattern:  pattern,
+		method:   http.MethodGet,
+		handlers: handlers,
+	})
+
+	return &macaron.Route{}
+}
+
 func emptyHandlers(n int) []macaron.Handler {
 	res := []macaron.Handler{}
 	for i := 1; n >= i; i++ {

+ 1 - 1
pkg/cmd/grafana-server/main.go

@@ -22,9 +22,9 @@ import (
 	_ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
 	_ "github.com/grafana/grafana/pkg/tsdb/graphite"
 	_ "github.com/grafana/grafana/pkg/tsdb/influxdb"
-	_ "github.com/grafana/grafana/pkg/tsdb/mqe"
 	_ "github.com/grafana/grafana/pkg/tsdb/mysql"
 	_ "github.com/grafana/grafana/pkg/tsdb/opentsdb"
+
 	_ "github.com/grafana/grafana/pkg/tsdb/prometheus"
 	_ "github.com/grafana/grafana/pkg/tsdb/testdata"
 )

+ 11 - 2
pkg/cmd/grafana-server/server.go

@@ -24,6 +24,7 @@ import (
 	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/social"
+	"github.com/grafana/grafana/pkg/tracing"
 )
 
 func NewGrafanaServer() models.GrafanaServer {
@@ -61,6 +62,14 @@ func (g *GrafanaServerImpl) Start() {
 	eventpublisher.Init()
 	plugins.Init()
 
+	closer, err := tracing.Init(setting.Cfg)
+	if err != nil {
+		g.log.Error("Tracing settings is not valid", "error", err)
+		g.Shutdown(1, "Startup failed")
+		return
+	}
+	defer closer.Close()
+
 	// init alerting
 	if setting.AlertingEnabled && setting.ExecuteAlerts {
 		engine := alerting.NewEngine()
@@ -71,8 +80,8 @@ func (g *GrafanaServerImpl) Start() {
 	cleanUpService := cleanup.NewCleanUpService()
 	g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) })
 
-	if err := notifications.Init(); err != nil {
-		g.log.Error("Notification service failed to initialize", "erro", err)
+	if err = notifications.Init(); err != nil {
+		g.log.Error("Notification service failed to initialize", "error", err)
 		g.Shutdown(1, "Startup failed")
 		return
 	}

+ 23 - 7
pkg/metrics/graphitebridge/graphite.go

@@ -20,6 +20,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"net"
 	"sort"
 	"strings"
@@ -53,7 +54,17 @@ const (
 	AbortOnError
 )
 
-var metricCategoryPrefix []string = []string{"proxy_", "api_", "page_", "alerting_", "aws_", "db_", "stat_", "go_", "process_"}
+var metricCategoryPrefix []string = []string{
+	"proxy_",
+	"api_",
+	"page_",
+	"alerting_",
+	"aws_",
+	"db_",
+	"stat_",
+	"go_",
+	"process_"}
+
 var trimMetricPrefix []string = []string{"grafana_"}
 
 // Config defines the Graphite bridge config.
@@ -208,6 +219,10 @@ func (b *Bridge) writeMetrics(w io.Writer, mfs []*dto.MetricFamily, prefix strin
 
 		buf := bufio.NewWriter(w)
 		for _, s := range vec {
+			if math.IsNaN(float64(s.Value)) {
+				continue
+			}
+
 			if err := writePrefix(buf, prefix); err != nil {
 				return err
 			}
@@ -235,16 +250,17 @@ func writeMetric(buf *bufio.Writer, m model.Metric, mf *dto.MetricFamily) error
 	if !hasName {
 		numLabels = len(m)
 	}
-	for _, v := range metricCategoryPrefix {
+
+	for _, v := range trimMetricPrefix {
 		if strings.HasPrefix(string(metricName), v) {
-			group := strings.Replace(v, "_", " ", 1)
-			metricName = model.LabelValue(strings.Replace(string(metricName), v, group, 1))
+			metricName = model.LabelValue(strings.Replace(string(metricName), v, "", 1))
 		}
 	}
 
-	for _, v := range trimMetricPrefix {
+	for _, v := range metricCategoryPrefix {
 		if strings.HasPrefix(string(metricName), v) {
-			metricName = model.LabelValue(strings.Replace(string(metricName), v, "", 1))
+			group := strings.Replace(v, "_", " ", 1)
+			metricName = model.LabelValue(strings.Replace(string(metricName), v, group, 1))
 		}
 	}
 
@@ -357,7 +373,7 @@ func replaceInvalidRune(c rune) rune {
 	if c == ' ' {
 		return '.'
 	}
-	if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':' || (c >= '0' && c <= '9')) {
+	if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' || c == ':' || (c >= '0' && c <= '9')) {
 		return '_'
 	}
 	return c

+ 68 - 3
pkg/metrics/graphitebridge/graphite_test.go

@@ -128,6 +128,7 @@ func TestWriteSummary(t *testing.T) {
 		prometheus.SummaryOpts{
 			Name:        "name",
 			Help:        "docstring",
+			Namespace:   "grafana",
 			ConstLabels: prometheus.Labels{"constname": "constvalue"},
 			Objectives:  map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
 		},
@@ -187,6 +188,7 @@ func TestWriteHistogram(t *testing.T) {
 		prometheus.HistogramOpts{
 			Name:        "name",
 			Help:        "docstring",
+			Namespace:   "grafana",
 			ConstLabels: prometheus.Labels{"constname": "constvalue"},
 			Buckets:     []float64{0.01, 0.02, 0.05, 0.1},
 		},
@@ -248,6 +250,17 @@ func TestCounterVec(t *testing.T) {
 	cntVec := prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Name:        "page_response",
+			Namespace:   "grafana",
+			Help:        "docstring",
+			ConstLabels: prometheus.Labels{"constname": "constvalue"},
+		},
+		[]string{"labelname"},
+	)
+
+	apicntVec := prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name:        "api_response",
+			Namespace:   "grafana",
 			Help:        "docstring",
 			ConstLabels: prometheus.Labels{"constname": "constvalue"},
 		},
@@ -256,9 +269,12 @@ func TestCounterVec(t *testing.T) {
 
 	reg := prometheus.NewRegistry()
 	reg.MustRegister(cntVec)
+	reg.MustRegister(apicntVec)
 
 	cntVec.WithLabelValues("val1").Inc()
 	cntVec.WithLabelValues("val2").Inc()
+	apicntVec.WithLabelValues("val1").Inc()
+	apicntVec.WithLabelValues("val2").Inc()
 
 	b, err := NewBridge(&Config{
 		URL:             "localhost:8080",
@@ -281,7 +297,9 @@ func TestCounterVec(t *testing.T) {
 		t.Fatalf("error: %v", err)
 	}
 
-	want := `prefix.page.response.constname.constvalue.labelname.val1.count 1 1477043
+	want := `prefix.api.response.constname.constvalue.labelname.val1.count 1 1477043
+prefix.api.response.constname.constvalue.labelname.val2.count 1 1477043
+prefix.page.response.constname.constvalue.labelname.val1.count 1 1477043
 prefix.page.response.constname.constvalue.labelname.val2.count 1 1477043
 `
 	if got := buf.String(); want != got {
@@ -291,6 +309,8 @@ prefix.page.response.constname.constvalue.labelname.val2.count 1 1477043
 	//next collect
 	cntVec.WithLabelValues("val1").Inc()
 	cntVec.WithLabelValues("val2").Inc()
+	apicntVec.WithLabelValues("val1").Inc()
+	apicntVec.WithLabelValues("val2").Inc()
 
 	mfs, err = reg.Gather()
 	if err != nil {
@@ -303,7 +323,9 @@ prefix.page.response.constname.constvalue.labelname.val2.count 1 1477043
 		t.Fatalf("error: %v", err)
 	}
 
-	want2 := `prefix.page.response.constname.constvalue.labelname.val1.count 1 1477053
+	want2 := `prefix.api.response.constname.constvalue.labelname.val1.count 1 1477053
+prefix.api.response.constname.constvalue.labelname.val2.count 1 1477053
+prefix.page.response.constname.constvalue.labelname.val1.count 1 1477053
 prefix.page.response.constname.constvalue.labelname.val2.count 1 1477053
 `
 	if got := buf.String(); want2 != got {
@@ -316,6 +338,7 @@ func TestCounter(t *testing.T) {
 		prometheus.CounterOpts{
 			Name:        "page_response",
 			Help:        "docstring",
+			Namespace:   "grafana",
 			ConstLabels: prometheus.Labels{"constname": "constvalue"},
 		})
 
@@ -373,7 +396,7 @@ func TestCounter(t *testing.T) {
 func TestTrimGrafanaNamespace(t *testing.T) {
 	cntVec := prometheus.NewCounter(
 		prometheus.CounterOpts{
-			Name:        "grafana_http_request_total",
+			Name:        "http_request_total",
 			Help:        "docstring",
 			ConstLabels: prometheus.Labels{"constname": "constvalue"},
 		})
@@ -410,12 +433,54 @@ func TestTrimGrafanaNamespace(t *testing.T) {
 	}
 }
 
+func TestSkipNanValues(t *testing.T) {
+	cntVec := prometheus.NewSummary(
+		prometheus.SummaryOpts{
+			Name:        "http_request_total",
+			Help:        "docstring",
+			ConstLabels: prometheus.Labels{"constname": "constvalue"},
+		})
+
+	reg := prometheus.NewRegistry()
+	reg.MustRegister(cntVec)
+
+	b, err := NewBridge(&Config{
+		URL:             "localhost:8080",
+		Gatherer:        reg,
+		CountersAsDelta: true,
+	})
+	if err != nil {
+		t.Fatalf("error creating bridge: %v", err)
+	}
+
+	// first collect
+	mfs, err := reg.Gather()
+	if err != nil {
+		t.Fatalf("error: %v", err)
+	}
+
+	var buf bytes.Buffer
+	err = b.writeMetrics(&buf, mfs, "prefix.", model.Time(1477043083))
+	if err != nil {
+		t.Fatalf("error: %v", err)
+	}
+
+	want := `prefix.http_request_total_sum.constname.constvalue 0 1477043
+prefix.http_request_total_count.constname.constvalue.count 0 1477043
+`
+
+	if got := buf.String(); want != got {
+		t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
+	}
+}
+
 func TestPush(t *testing.T) {
 	reg := prometheus.NewRegistry()
 	cntVec := prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Name:        "name",
 			Help:        "docstring",
+			Namespace:   "grafana",
 			ConstLabels: prometheus.Labels{"constname": "constvalue"},
 		},
 		[]string{"labelname"},

+ 5 - 5
pkg/metrics/metrics.go

@@ -102,7 +102,7 @@ func init() {
 
 	M_Http_Request_Summary = prometheus.NewSummaryVec(
 		prometheus.SummaryOpts{
-			Name: "http_request_duration_milleseconds",
+			Name: "http_request_duration_milliseconds",
 			Help: "http request summary",
 		},
 		[]string{"handler", "statuscode", "method"},
@@ -127,19 +127,19 @@ func init() {
 	})
 
 	M_Api_Dashboard_Save = prometheus.NewSummary(prometheus.SummaryOpts{
-		Name:      "api_dashboard_save_milleseconds",
+		Name:      "api_dashboard_save_milliseconds",
 		Help:      "summary for dashboard save duration",
 		Namespace: exporterName,
 	})
 
 	M_Api_Dashboard_Get = prometheus.NewSummary(prometheus.SummaryOpts{
-		Name:      "api_dashboard_get_milleseconds",
+		Name:      "api_dashboard_get_milliseconds",
 		Help:      "summary for dashboard get duration",
 		Namespace: exporterName,
 	})
 
 	M_Api_Dashboard_Search = prometheus.NewSummary(prometheus.SummaryOpts{
-		Name:      "api_dashboard_search_milleseconds",
+		Name:      "api_dashboard_search_milliseconds",
 		Help:      "summary for dashboard search duration",
 		Namespace: exporterName,
 	})
@@ -223,7 +223,7 @@ func init() {
 	})
 
 	M_DataSource_ProxyReq_Timer = prometheus.NewSummary(prometheus.SummaryOpts{
-		Name:      "api_dataproxy_request_all_milleseconds",
+		Name:      "api_dataproxy_request_all_milliseconds",
 		Help:      "summary for dashboard search duration",
 		Namespace: exporterName,
 	})

+ 36 - 0
pkg/middleware/request_tracing.go

@@ -0,0 +1,36 @@
+package middleware
+
+import (
+	"fmt"
+	"net/http"
+
+	opentracing "github.com/opentracing/opentracing-go"
+	"github.com/opentracing/opentracing-go/ext"
+
+	"gopkg.in/macaron.v1"
+)
+
+func RequestTracing(handler string) macaron.Handler {
+	return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
+		rw := res.(macaron.ResponseWriter)
+
+		tracer := opentracing.GlobalTracer()
+		wireContext, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
+		span := tracer.StartSpan(fmt.Sprintf("HTTP %s", handler), ext.RPCServerOption(wireContext))
+		defer span.Finish()
+
+		ctx := opentracing.ContextWithSpan(req.Context(), span)
+		c.Req.Request = req.WithContext(ctx)
+
+		c.Next()
+
+		status := rw.Status()
+
+		ext.HTTPStatusCode.Set(span, uint16(status))
+		ext.HTTPUrl.Set(span, req.RequestURI)
+		ext.HTTPMethod.Set(span, req.Method)
+		if status >= 400 {
+			ext.Error.Set(span, true)
+		}
+	}
+}

+ 2 - 2
pkg/services/alerting/conditions/query.go

@@ -139,8 +139,8 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
 	return result, nil
 }
 
-func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource, timeRange *tsdb.TimeRange) *tsdb.Request {
-	req := &tsdb.Request{
+func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource, timeRange *tsdb.TimeRange) *tsdb.TsdbQuery {
+	req := &tsdb.TsdbQuery{
 		TimeRange: timeRange,
 		Queries: []*tsdb.Query{
 			{

+ 1 - 1
pkg/services/alerting/conditions/query_test.go

@@ -168,7 +168,7 @@ func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error)
 
 	ctx.condition = condition
 
-	condition.HandleRequest = func(context context.Context, req *tsdb.Request) (*tsdb.Response, error) {
+	condition.HandleRequest = func(context context.Context, req *tsdb.TsdbQuery) (*tsdb.Response, error) {
 		return &tsdb.Response{
 			Results: map[string]*tsdb.QueryResult{
 				"A": {Series: ctx.series},

+ 28 - 1
pkg/services/alerting/engine.go

@@ -2,8 +2,13 @@ package alerting
 
 import (
 	"context"
+	"fmt"
 	"time"
 
+	"github.com/opentracing/opentracing-go"
+	"github.com/opentracing/opentracing-go/ext"
+	tlog "github.com/opentracing/opentracing-go/log"
+
 	"github.com/benbjohnson/clock"
 	"github.com/grafana/grafana/pkg/log"
 	"golang.org/x/sync/errgroup"
@@ -99,22 +104,44 @@ func (e *Engine) processJob(grafanaCtx context.Context, job *Job) error {
 	}()
 
 	alertCtx, cancelFn := context.WithTimeout(context.Background(), alertTimeout)
+	span := opentracing.StartSpan("alert execution")
+	alertCtx = opentracing.ContextWithSpan(alertCtx, span)
 
 	job.Running = true
 	evalContext := NewEvalContext(alertCtx, job.Rule)
+	evalContext.Ctx = alertCtx
 
 	done := make(chan struct{})
-
 	go func() {
 		defer func() {
 			if err := recover(); err != nil {
 				e.log.Error("Alert Panic", "error", err, "stack", log.Stack(1))
+				ext.Error.Set(span, true)
+				span.LogFields(
+					tlog.Error(fmt.Errorf("%v", err)),
+					tlog.String("message", "failed to execute alert rule. panic was recovered."),
+				)
+				span.Finish()
 				close(done)
 			}
 		}()
 
 		e.evalHandler.Eval(evalContext)
 		e.resultHandler.Handle(evalContext)
+
+		span.SetTag("alertId", evalContext.Rule.Id)
+		span.SetTag("dashboardId", evalContext.Rule.DashboardId)
+		span.SetTag("firing", evalContext.Firing)
+		span.SetTag("nodatapoints", evalContext.NoDataFound)
+		if evalContext.Error != nil {
+			ext.Error.Set(span, true)
+			span.LogFields(
+				tlog.Error(evalContext.Error),
+				tlog.String("message", "alerting execution failed"),
+			)
+		}
+
+		span.Finish()
 		close(done)
 	}()
 

+ 6 - 1
pkg/services/alerting/extractor.go

@@ -89,6 +89,11 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
 				continue
 			}
 
+			panelId, err := panel.Get("id").Int64()
+			if err != nil {
+				return nil, fmt.Errorf("panel id is required. err %v", err)
+			}
+
 			// backward compatibility check, can be removed later
 			enabled, hasEnabled := jsonAlert.CheckGet("enabled")
 			if hasEnabled && enabled.MustBool() == false {
@@ -103,7 +108,7 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
 			alert := &m.Alert{
 				DashboardId: e.Dash.Id,
 				OrgId:       e.OrgId,
-				PanelId:     panel.Get("id").MustInt64(),
+				PanelId:     panelId,
 				Id:          jsonAlert.Get("id").MustInt64(),
 				Name:        jsonAlert.Get("name").MustString(),
 				Handler:     jsonAlert.Get("handler").MustInt64(),

+ 77 - 0
pkg/services/alerting/extractor_test.go

@@ -200,6 +200,83 @@ 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]}
+              }
+              ]
+            }
+          }
+          ]
+        }
+      ]
+			}`
+
+			dashJson, err := simplejson.NewJson([]byte(panelWithoutId))
+			So(err, ShouldBeNil)
+			dash := m.NewDashboardFromJson(dashJson)
+			extractor := NewDashAlertExtractor(dash, 1)
+
+			_, err = extractor.GetAlerts()
+
+			Convey("panels without Id should return error", func() {
+				So(err, ShouldNotBeNil)
+			})
+		})
+
 		Convey("Parse and validate dashboard containing influxdb alert", func() {
 
 			json2 := `{

+ 0 - 1
pkg/services/sqlstore/user.go

@@ -28,7 +28,6 @@ func init() {
 	bus.AddHandler("sql", SearchUsers)
 	bus.AddHandler("sql", GetUserOrgList)
 	bus.AddHandler("sql", DeleteUser)
-	bus.AddHandler("sql", SetUsingOrg)
 	bus.AddHandler("sql", UpdateUserPermissions)
 	bus.AddHandler("sql", SetUserHelpFlag)
 }

+ 116 - 0
pkg/tracing/tracing.go

@@ -0,0 +1,116 @@
+package tracing
+
+import (
+	"io"
+	"strings"
+
+	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/setting"
+
+	opentracing "github.com/opentracing/opentracing-go"
+	jaegercfg "github.com/uber/jaeger-client-go/config"
+	ini "gopkg.in/ini.v1"
+)
+
+var (
+	logger log.Logger = log.New("tracing")
+)
+
+type TracingSettings struct {
+	Enabled      bool
+	Address      string
+	CustomTags   map[string]string
+	SamplerType  string
+	SamplerParam float64
+}
+
+func Init(file *ini.File) (io.Closer, error) {
+	settings := parseSettings(file)
+	return internalInit(settings)
+}
+
+func parseSettings(file *ini.File) *TracingSettings {
+	settings := &TracingSettings{}
+
+	var section, err = setting.Cfg.GetSection("tracing.jaeger")
+	if err != nil {
+		return settings
+	}
+
+	settings.Address = section.Key("address").MustString("")
+	if settings.Address != "" {
+		settings.Enabled = true
+	}
+
+	settings.CustomTags = splitTagSettings(section.Key("always_included_tag").MustString(""))
+	settings.SamplerType = section.Key("sampler_type").MustString("")
+	settings.SamplerParam = section.Key("sampler_param").MustFloat64(1)
+
+	return settings
+}
+
+func internalInit(settings *TracingSettings) (io.Closer, error) {
+	if !settings.Enabled {
+		return &nullCloser{}, nil
+	}
+
+	cfg := jaegercfg.Configuration{
+		Disabled: !settings.Enabled,
+		Sampler: &jaegercfg.SamplerConfig{
+			Type:  settings.SamplerType,
+			Param: settings.SamplerParam,
+		},
+		Reporter: &jaegercfg.ReporterConfig{
+			LogSpans:           false,
+			LocalAgentHostPort: settings.Address,
+		},
+	}
+
+	jLogger := &jaegerLogWrapper{logger: log.New("jaeger")}
+
+	options := []jaegercfg.Option{}
+	options = append(options, jaegercfg.Logger(jLogger))
+
+	for tag, value := range settings.CustomTags {
+		options = append(options, jaegercfg.Tag(tag, value))
+	}
+
+	tracer, closer, err := cfg.New("grafana", options...)
+	if err != nil {
+		return nil, err
+	}
+
+	logger.Info("Initialized jaeger tracer", "address", settings.Address)
+	opentracing.InitGlobalTracer(tracer)
+	return closer, nil
+}
+
+func splitTagSettings(input string) map[string]string {
+	res := map[string]string{}
+
+	tags := strings.Split(input, ",")
+	for _, v := range tags {
+		kv := strings.Split(v, ":")
+		if len(kv) > 1 {
+			res[kv[0]] = kv[1]
+		}
+	}
+
+	return res
+}
+
+type jaegerLogWrapper struct {
+	logger log.Logger
+}
+
+func (jlw *jaegerLogWrapper) Error(msg string) {
+	jlw.logger.Error(msg)
+}
+
+func (jlw *jaegerLogWrapper) Infof(msg string, args ...interface{}) {
+	jlw.logger.Info(msg, args)
+}
+
+type nullCloser struct{}
+
+func (*nullCloser) Close() error { return nil }

+ 36 - 0
pkg/tracing/tracing_test.go

@@ -0,0 +1,36 @@
+package tracing
+
+import "testing"
+
+func TestGroupSplit(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected map[string]string
+	}{
+		{
+			input: "tag1:value1,tag2:value2",
+			expected: map[string]string{
+				"tag1": "value1",
+				"tag2": "value2",
+			},
+		},
+		{
+			input:    "",
+			expected: map[string]string{},
+		},
+		{
+			input:    "tag1",
+			expected: map[string]string{},
+		},
+	}
+
+	for _, test := range tests {
+		tags := splitTagSettings(test.input)
+		for k, v := range test.expected {
+			value, exists := tags[k]
+			if !exists || value != v {
+				t.Errorf("tags does not match %v ", test)
+			}
+		}
+	}
+}

+ 0 - 90
pkg/tsdb/batch.go

@@ -1,90 +0,0 @@
-package tsdb
-
-import "context"
-
-type Batch struct {
-	DataSourceId int64
-	Queries      QuerySlice
-	Depends      map[string]bool
-	Done         bool
-	Started      bool
-}
-
-type BatchSlice []*Batch
-
-func newBatch(dsId int64, queries QuerySlice) *Batch {
-	return &Batch{
-		DataSourceId: dsId,
-		Queries:      queries,
-		Depends:      make(map[string]bool),
-	}
-}
-
-func (bg *Batch) process(ctx context.Context, queryContext *QueryContext) {
-	executor, err := getExecutorFor(bg.Queries[0].DataSource)
-
-	if err != nil {
-		bg.Done = true
-		result := &BatchResult{
-			Error:        err,
-			QueryResults: make(map[string]*QueryResult),
-		}
-		for _, query := range bg.Queries {
-			result.QueryResults[query.RefId] = &QueryResult{Error: result.Error}
-		}
-		queryContext.ResultsChan <- result
-		return
-	}
-
-	res := executor.Execute(ctx, bg.Queries, queryContext)
-	bg.Done = true
-	queryContext.ResultsChan <- res
-}
-
-func (bg *Batch) addQuery(query *Query) {
-	bg.Queries = append(bg.Queries, query)
-}
-
-func (bg *Batch) allDependenciesAreIn(context *QueryContext) bool {
-	for key := range bg.Depends {
-		if _, exists := context.Results[key]; !exists {
-			return false
-		}
-	}
-
-	return true
-}
-
-func getBatches(req *Request) (BatchSlice, error) {
-	batches := make(BatchSlice, 0)
-
-	for _, query := range req.Queries {
-		if foundBatch := findMatchingBatchGroup(query, batches); foundBatch != nil {
-			foundBatch.addQuery(query)
-		} else {
-			newBatch := newBatch(query.DataSource.Id, QuerySlice{query})
-			batches = append(batches, newBatch)
-
-			for _, refId := range query.Depends {
-				for _, batch := range batches {
-					for _, batchQuery := range batch.Queries {
-						if batchQuery.RefId == refId {
-							newBatch.Depends[refId] = true
-						}
-					}
-				}
-			}
-		}
-	}
-
-	return batches, nil
-}
-
-func findMatchingBatchGroup(query *Query, batches BatchSlice) *Batch {
-	for _, batch := range batches {
-		if batch.DataSourceId == query.DataSource.Id {
-			return batch
-		}
-	}
-	return nil
-}

+ 0 - 36
pkg/tsdb/executor.go

@@ -1,36 +0,0 @@
-package tsdb
-
-import (
-	"context"
-	"fmt"
-
-	"github.com/grafana/grafana/pkg/models"
-)
-
-type Executor interface {
-	Execute(ctx context.Context, queries QuerySlice, query *QueryContext) *BatchResult
-}
-
-var registry map[string]GetExecutorFn
-
-type GetExecutorFn func(dsInfo *models.DataSource) (Executor, error)
-
-func init() {
-	registry = make(map[string]GetExecutorFn)
-}
-
-func getExecutorFor(dsInfo *models.DataSource) (Executor, error) {
-	if fn, exists := registry[dsInfo.Type]; exists {
-		executor, err := fn(dsInfo)
-		if err != nil {
-			return nil, err
-		}
-
-		return executor, nil
-	}
-	return nil, fmt.Errorf("Could not find executor for data source type: %s", dsInfo.Type)
-}
-
-func RegisterExecutor(pluginId string, fn GetExecutorFn) {
-	registry[pluginId] = fn
-}

+ 3 - 3
pkg/tsdb/fake_test.go

@@ -11,7 +11,7 @@ type FakeExecutor struct {
 	resultsFn map[string]ResultsFn
 }
 
-type ResultsFn func(context *QueryContext) *QueryResult
+type ResultsFn func(context *TsdbQuery) *QueryResult
 
 func NewFakeExecutor(dsInfo *models.DataSource) (*FakeExecutor, error) {
 	return &FakeExecutor{
@@ -20,9 +20,9 @@ func NewFakeExecutor(dsInfo *models.DataSource) (*FakeExecutor, error) {
 	}, nil
 }
 
-func (e *FakeExecutor) Execute(ctx context.Context, queries QuerySlice, context *QueryContext) *BatchResult {
+func (e *FakeExecutor) Query(ctx context.Context, dsInfo *models.DataSource, context *TsdbQuery) *BatchResult {
 	result := &BatchResult{QueryResults: make(map[string]*QueryResult)}
-	for _, query := range queries {
+	for _, query := range context.Queries {
 		if results, has := e.results[query.RefId]; has {
 			result.QueryResults[query.RefId] = results
 		}

+ 39 - 25
pkg/tsdb/graphite/graphite.go

@@ -17,24 +17,15 @@ import (
 	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/tsdb"
+	opentracing "github.com/opentracing/opentracing-go"
 )
 
 type GraphiteExecutor struct {
-	*models.DataSource
 	HttpClient *http.Client
 }
 
-func NewGraphiteExecutor(datasource *models.DataSource) (tsdb.Executor, error) {
-	httpClient, err := datasource.GetHttpClient()
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &GraphiteExecutor{
-		DataSource: datasource,
-		HttpClient: httpClient,
-	}, nil
+func NewGraphiteExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
+	return &GraphiteExecutor{}, nil
 }
 
 var (
@@ -43,38 +34,61 @@ var (
 
 func init() {
 	glog = log.New("tsdb.graphite")
-	tsdb.RegisterExecutor("graphite", NewGraphiteExecutor)
+	tsdb.RegisterTsdbQueryEndpoint("graphite", NewGraphiteExecutor)
 }
 
-func (e *GraphiteExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
+func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
 	result := &tsdb.BatchResult{}
 
+	from := "-" + formatTimeRange(tsdbQuery.TimeRange.From)
+	until := formatTimeRange(tsdbQuery.TimeRange.To)
+	var target string
+
 	formData := url.Values{
-		"from":          []string{"-" + formatTimeRange(context.TimeRange.From)},
-		"until":         []string{formatTimeRange(context.TimeRange.To)},
+		"from":          []string{from},
+		"until":         []string{until},
 		"format":        []string{"json"},
 		"maxDataPoints": []string{"500"},
 	}
 
-	for _, query := range queries {
+	for _, query := range tsdbQuery.Queries {
 		if fullTarget, err := query.Model.Get("targetFull").String(); err == nil {
-			formData["target"] = []string{fixIntervalFormat(fullTarget)}
+			target = fixIntervalFormat(fullTarget)
 		} else {
-			formData["target"] = []string{fixIntervalFormat(query.Model.Get("target").MustString())}
+			target = fixIntervalFormat(query.Model.Get("target").MustString())
 		}
 	}
 
+	formData["target"] = []string{target}
+
 	if setting.Env == setting.DEV {
 		glog.Debug("Graphite request", "params", formData)
 	}
 
-	req, err := e.createRequest(formData)
+	req, err := e.createRequest(dsInfo, formData)
 	if err != nil {
 		result.Error = err
 		return result
 	}
 
-	res, err := ctxhttp.Do(ctx, e.HttpClient, req)
+	httpClient, err := dsInfo.GetHttpClient()
+	if err != nil {
+		result.Error = err
+		return result
+	}
+
+	span, ctx := opentracing.StartSpanFromContext(ctx, "graphite query")
+	span.SetTag("target", target)
+	span.SetTag("from", from)
+	span.SetTag("until", until)
+	defer span.Finish()
+
+	opentracing.GlobalTracer().Inject(
+		span.Context(),
+		opentracing.HTTPHeaders,
+		opentracing.HTTPHeadersCarrier(req.Header))
+
+	res, err := ctxhttp.Do(ctx, httpClient, req)
 	if err != nil {
 		result.Error = err
 		return result
@@ -126,8 +140,8 @@ func (e *GraphiteExecutor) parseResponse(res *http.Response) ([]TargetResponseDT
 	return data, nil
 }
 
-func (e *GraphiteExecutor) createRequest(data url.Values) (*http.Request, error) {
-	u, _ := url.Parse(e.Url)
+func (e *GraphiteExecutor) createRequest(dsInfo *models.DataSource, data url.Values) (*http.Request, error) {
+	u, _ := url.Parse(dsInfo.Url)
 	u.Path = path.Join(u.Path, "render")
 
 	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(data.Encode()))
@@ -137,8 +151,8 @@ func (e *GraphiteExecutor) createRequest(data url.Values) (*http.Request, error)
 	}
 
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	if e.BasicAuth {
-		req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
+	if dsInfo.BasicAuth {
+		req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.BasicAuthPassword)
 	}
 
 	return req, err

+ 23 - 26
pkg/tsdb/influxdb/influxdb.go

@@ -17,24 +17,16 @@ import (
 )
 
 type InfluxDBExecutor struct {
-	*models.DataSource
+	//*models.DataSource
 	QueryParser    *InfluxdbQueryParser
 	ResponseParser *ResponseParser
-	HttpClient     *http.Client
+	//HttpClient     *http.Client
 }
 
-func NewInfluxDBExecutor(datasource *models.DataSource) (tsdb.Executor, error) {
-	httpClient, err := datasource.GetHttpClient()
-
-	if err != nil {
-		return nil, err
-	}
-
+func NewInfluxDBExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
 	return &InfluxDBExecutor{
-		DataSource:     datasource,
 		QueryParser:    &InfluxdbQueryParser{},
 		ResponseParser: &ResponseParser{},
-		HttpClient:     httpClient,
 	}, nil
 }
 
@@ -44,18 +36,18 @@ var (
 
 func init() {
 	glog = log.New("tsdb.influxdb")
-	tsdb.RegisterExecutor("influxdb", NewInfluxDBExecutor)
+	tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor)
 }
 
-func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
+func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
 	result := &tsdb.BatchResult{}
 
-	query, err := e.getQuery(queries, context)
+	query, err := e.getQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
 	if err != nil {
 		return result.WithError(err)
 	}
 
-	rawQuery, err := query.Build(context)
+	rawQuery, err := query.Build(tsdbQuery)
 	if err != nil {
 		return result.WithError(err)
 	}
@@ -64,12 +56,17 @@ func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
 		glog.Debug("Influxdb query", "raw query", rawQuery)
 	}
 
-	req, err := e.createRequest(rawQuery)
+	req, err := e.createRequest(dsInfo, rawQuery)
+	if err != nil {
+		return result.WithError(err)
+	}
+
+	httpClient, err := dsInfo.GetHttpClient()
 	if err != nil {
 		return result.WithError(err)
 	}
 
-	resp, err := ctxhttp.Do(ctx, e.HttpClient, req)
+	resp, err := ctxhttp.Do(ctx, httpClient, req)
 	if err != nil {
 		return result.WithError(err)
 	}
@@ -98,10 +95,10 @@ func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
 	return result
 }
 
-func (e *InfluxDBExecutor) getQuery(queries tsdb.QuerySlice, context *tsdb.QueryContext) (*Query, error) {
+func (e *InfluxDBExecutor) getQuery(dsInfo *models.DataSource, queries []*tsdb.Query, context *tsdb.TsdbQuery) (*Query, error) {
 	for _, v := range queries {
 
-		query, err := e.QueryParser.Parse(v.Model, e.DataSource)
+		query, err := e.QueryParser.Parse(v.Model, dsInfo)
 		if err != nil {
 			return nil, err
 		}
@@ -112,8 +109,8 @@ func (e *InfluxDBExecutor) getQuery(queries tsdb.QuerySlice, context *tsdb.Query
 	return nil, fmt.Errorf("query request contains no queries")
 }
 
-func (e *InfluxDBExecutor) createRequest(query string) (*http.Request, error) {
-	u, _ := url.Parse(e.Url)
+func (e *InfluxDBExecutor) createRequest(dsInfo *models.DataSource, query string) (*http.Request, error) {
+	u, _ := url.Parse(dsInfo.Url)
 	u.Path = path.Join(u.Path, "query")
 
 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
@@ -123,18 +120,18 @@ func (e *InfluxDBExecutor) createRequest(query string) (*http.Request, error) {
 
 	params := req.URL.Query()
 	params.Set("q", query)
-	params.Set("db", e.Database)
+	params.Set("db", dsInfo.Database)
 	params.Set("epoch", "s")
 	req.URL.RawQuery = params.Encode()
 
 	req.Header.Set("User-Agent", "Grafana")
 
-	if e.BasicAuth {
-		req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
+	if dsInfo.BasicAuth {
+		req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.BasicAuthPassword)
 	}
 
-	if !e.BasicAuth && e.User != "" {
-		req.SetBasicAuth(e.User, e.Password)
+	if !dsInfo.BasicAuth && dsInfo.User != "" {
+		req.SetBasicAuth(dsInfo.User, dsInfo.Password)
 	}
 
 	glog.Debug("Influxdb request", "url", req.URL.String())

+ 5 - 5
pkg/tsdb/influxdb/query.go

@@ -16,7 +16,7 @@ var (
 	regexpMeasurementPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`)
 )
 
-func (query *Query) Build(queryContext *tsdb.QueryContext) (string, error) {
+func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) {
 	var res string
 
 	if query.UseRawQuery && query.RawQuery != "" {
@@ -41,7 +41,7 @@ func (query *Query) Build(queryContext *tsdb.QueryContext) (string, error) {
 	return res, nil
 }
 
-func getDefinedInterval(query *Query, queryContext *tsdb.QueryContext) (*tsdb.Interval, error) {
+func getDefinedInterval(query *Query, queryContext *tsdb.TsdbQuery) (*tsdb.Interval, error) {
 	defaultInterval := tsdb.CalculateInterval(queryContext.TimeRange)
 
 	if query.Interval == "" {
@@ -104,7 +104,7 @@ func (query *Query) renderTags() []string {
 	return res
 }
 
-func (query *Query) renderTimeFilter(queryContext *tsdb.QueryContext) string {
+func (query *Query) renderTimeFilter(queryContext *tsdb.TsdbQuery) string {
 	from := "now() - " + queryContext.TimeRange.From
 	to := ""
 
@@ -115,7 +115,7 @@ func (query *Query) renderTimeFilter(queryContext *tsdb.QueryContext) string {
 	return fmt.Sprintf("time > %s%s", from, to)
 }
 
-func (query *Query) renderSelectors(queryContext *tsdb.QueryContext) string {
+func (query *Query) renderSelectors(queryContext *tsdb.TsdbQuery) string {
 	res := "SELECT "
 
 	var selectors []string
@@ -163,7 +163,7 @@ func (query *Query) renderWhereClause() string {
 	return res
 }
 
-func (query *Query) renderGroupBy(queryContext *tsdb.QueryContext) string {
+func (query *Query) renderGroupBy(queryContext *tsdb.TsdbQuery) string {
 	groupBy := ""
 	for i, group := range query.GroupBy {
 		if i == 0 {

+ 7 - 7
pkg/tsdb/influxdb/query_part.go

@@ -15,7 +15,7 @@ type DefinitionParameters struct {
 }
 
 type QueryDefinition struct {
-	Renderer func(query *Query, queryContext *tsdb.QueryContext, part *QueryPart, innerExpr string) string
+	Renderer func(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string
 	Params   []DefinitionParameters
 }
 
@@ -94,14 +94,14 @@ func init() {
 	renders["alias"] = QueryDefinition{Renderer: aliasRenderer}
 }
 
-func fieldRenderer(query *Query, queryContext *tsdb.QueryContext, part *QueryPart, innerExpr string) string {
+func fieldRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
 	if part.Params[0] == "*" {
 		return "*"
 	}
 	return fmt.Sprintf(`"%s"`, part.Params[0])
 }
 
-func functionRenderer(query *Query, queryContext *tsdb.QueryContext, part *QueryPart, innerExpr string) string {
+func functionRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
 	for i, param := range part.Params {
 		if part.Type == "time" && param == "auto" {
 			part.Params[i] = "$__interval"
@@ -117,15 +117,15 @@ func functionRenderer(query *Query, queryContext *tsdb.QueryContext, part *Query
 	return fmt.Sprintf("%s(%s)", part.Type, params)
 }
 
-func suffixRenderer(query *Query, queryContext *tsdb.QueryContext, part *QueryPart, innerExpr string) string {
+func suffixRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
 	return fmt.Sprintf("%s %s", innerExpr, part.Params[0])
 }
 
-func aliasRenderer(query *Query, queryContext *tsdb.QueryContext, part *QueryPart, innerExpr string) string {
+func aliasRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
 	return fmt.Sprintf(`%s AS "%s"`, innerExpr, part.Params[0])
 }
 
-func (r QueryDefinition) Render(query *Query, queryContext *tsdb.QueryContext, part *QueryPart, innerExpr string) string {
+func (r QueryDefinition) Render(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
 	return r.Renderer(query, queryContext, part, innerExpr)
 }
 
@@ -149,6 +149,6 @@ type QueryPart struct {
 	Params []string
 }
 
-func (qp *QueryPart) Render(query *Query, queryContext *tsdb.QueryContext, expr string) string {
+func (qp *QueryPart) Render(query *Query, queryContext *tsdb.TsdbQuery, expr string) string {
 	return qp.Def.Renderer(query, queryContext, qp, expr)
 }

+ 1 - 1
pkg/tsdb/influxdb/query_part_test.go

@@ -10,7 +10,7 @@ import (
 func TestInfluxdbQueryPart(t *testing.T) {
 	Convey("Influxdb query parts", t, func() {
 
-		queryContext := &tsdb.QueryContext{TimeRange: tsdb.NewTimeRange("5m", "now")}
+		queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("5m", "now")}
 		query := &Query{}
 
 		Convey("render field ", func() {

+ 3 - 3
pkg/tsdb/influxdb/query_test.go

@@ -28,7 +28,7 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
 		tag1 := &Tag{Key: "hostname", Value: "server1", Operator: "="}
 		tag2 := &Tag{Key: "hostname", Value: "server2", Operator: "=", Condition: "OR"}
 
-		queryContext := &tsdb.QueryContext{
+		queryContext := &tsdb.TsdbQuery{
 			TimeRange: tsdb.NewTimeRange("5m", "now"),
 		}
 
@@ -101,12 +101,12 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
 			query := Query{}
 			Convey("render from: 2h to now-1h", func() {
 				query := Query{}
-				queryContext := &tsdb.QueryContext{TimeRange: tsdb.NewTimeRange("2h", "now-1h")}
+				queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("2h", "now-1h")}
 				So(query.renderTimeFilter(queryContext), ShouldEqual, "time > now() - 2h and time < now() - 1h")
 			})
 
 			Convey("render from: 10m", func() {
-				queryContext := &tsdb.QueryContext{TimeRange: tsdb.NewTimeRange("10m", "now")}
+				queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("10m", "now")}
 				So(query.renderTimeFilter(queryContext), ShouldEqual, "time > now() - 10m")
 			})
 		})

+ 5 - 8
pkg/tsdb/models.go

@@ -6,24 +6,21 @@ import (
 	"github.com/grafana/grafana/pkg/models"
 )
 
+type TsdbQuery struct {
+	TimeRange *TimeRange
+	Queries   []*Query
+}
+
 type Query struct {
 	RefId         string
 	Model         *simplejson.Json
 	Depends       []string
 	DataSource    *models.DataSource
 	Results       []*TimeSeries
-	Exclude       bool
 	MaxDataPoints int64
 	IntervalMs    int64
 }
 
-type QuerySlice []*Query
-
-type Request struct {
-	TimeRange *TimeRange
-	Queries   QuerySlice
-}
-
 type Response struct {
 	BatchTimings []*BatchTiming          `json:"timings"`
 	Results      map[string]*QueryResult `json:"results"`

+ 0 - 129
pkg/tsdb/mqe/httpClient.go

@@ -1,129 +0,0 @@
-package mqe
-
-import (
-	"context"
-	"net/http"
-	"net/url"
-	"path"
-	"strings"
-
-	"github.com/grafana/grafana/pkg/components/simplejson"
-	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/setting"
-	"github.com/grafana/grafana/pkg/tsdb"
-
-	"golang.org/x/net/context/ctxhttp"
-)
-
-var (
-	MaxWorker int = 4
-)
-
-type apiClient struct {
-	*models.DataSource
-	log            log.Logger
-	httpClient     *http.Client
-	responseParser *ResponseParser
-}
-
-func NewApiClient(httpClient *http.Client, datasource *models.DataSource) *apiClient {
-	return &apiClient{
-		DataSource:     datasource,
-		log:            log.New("tsdb.mqe"),
-		httpClient:     httpClient,
-		responseParser: NewResponseParser(),
-	}
-}
-
-func (e *apiClient) PerformRequests(ctx context.Context, queries []QueryToSend) (*tsdb.QueryResult, error) {
-	queryResult := &tsdb.QueryResult{}
-
-	queryCount := len(queries)
-	jobsChan := make(chan QueryToSend, queryCount)
-	resultChan := make(chan []*tsdb.TimeSeries, queryCount)
-	errorsChan := make(chan error, 1)
-	for w := 1; w <= MaxWorker; w++ {
-		go e.spawnWorker(ctx, w, jobsChan, resultChan, errorsChan)
-	}
-
-	for _, v := range queries {
-		jobsChan <- v
-	}
-	close(jobsChan)
-
-	resultCounter := 0
-	for {
-		select {
-		case timeseries := <-resultChan:
-			queryResult.Series = append(queryResult.Series, timeseries...)
-			resultCounter++
-
-			if resultCounter == queryCount {
-				close(resultChan)
-				return queryResult, nil
-			}
-		case err := <-errorsChan:
-			return nil, err
-		case <-ctx.Done():
-			return nil, ctx.Err()
-		}
-	}
-}
-
-func (e *apiClient) spawnWorker(ctx context.Context, id int, jobs chan QueryToSend, results chan []*tsdb.TimeSeries, errors chan error) {
-	e.log.Debug("Spawning worker", "id", id)
-	for query := range jobs {
-		if setting.Env == setting.DEV {
-			e.log.Debug("Sending request", "query", query.RawQuery)
-		}
-
-		req, err := e.createRequest(query.RawQuery)
-
-		resp, err := ctxhttp.Do(ctx, e.httpClient, req)
-		if err != nil {
-			errors <- err
-			return
-		}
-
-		series, err := e.responseParser.Parse(resp, query)
-		if err != nil {
-			errors <- err
-			return
-		}
-
-		results <- series
-	}
-	e.log.Debug("Worker is complete", "id", id)
-}
-
-func (e *apiClient) createRequest(query string) (*http.Request, error) {
-	u, err := url.Parse(e.Url)
-	if err != nil {
-		return nil, err
-	}
-
-	u.Path = path.Join(u.Path, "query")
-
-	payload := simplejson.New()
-	payload.Set("query", query)
-
-	jsonPayload, err := payload.MarshalJSON()
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(string(jsonPayload)))
-	if err != nil {
-		return nil, err
-	}
-
-	req.Header.Set("User-Agent", "Grafana")
-	req.Header.Set("Content-Type", "application/json")
-
-	if e.BasicAuth {
-		req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
-	}
-
-	return req, nil
-}

+ 0 - 60
pkg/tsdb/mqe/model_parser.go

@@ -1,60 +0,0 @@
-package mqe
-
-import (
-	"github.com/grafana/grafana/pkg/components/simplejson"
-	"github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/tsdb"
-)
-
-func NewQueryParser() *QueryParser {
-	return &QueryParser{}
-}
-
-type QueryParser struct{}
-
-func (qp *QueryParser) Parse(model *simplejson.Json, dsInfo *models.DataSource, queryContext *tsdb.QueryContext) (*Query, error) {
-	query := &Query{TimeRange: queryContext.TimeRange}
-	query.AddClusterToAlias = model.Get("addClusterToAlias").MustBool(false)
-	query.AddHostToAlias = model.Get("addHostToAlias").MustBool(false)
-	query.UseRawQuery = model.Get("rawQuery").MustBool(false)
-	query.RawQuery = model.Get("query").MustString("")
-
-	query.Cluster = model.Get("cluster").MustStringArray([]string{})
-	query.Hosts = model.Get("hosts").MustStringArray([]string{})
-
-	var metrics []Metric
-	var err error
-	for _, metricsObj := range model.Get("metrics").MustArray() {
-		metricJson := simplejson.NewFromAny(metricsObj)
-		var m Metric
-
-		m.Alias = metricJson.Get("alias").MustString("")
-		m.Metric, err = metricJson.Get("metric").String()
-		if err != nil {
-			return nil, err
-		}
-
-		metrics = append(metrics, m)
-	}
-
-	query.Metrics = metrics
-
-	var functions []Function
-	for _, functionListObj := range model.Get("functionList").MustArray() {
-		functionListJson := simplejson.NewFromAny(functionListObj)
-		var f Function
-
-		f.Func = functionListJson.Get("func").MustString("")
-		if err != nil {
-			return nil, err
-		}
-
-		if f.Func != "" {
-			functions = append(functions, f)
-		}
-	}
-
-	query.FunctionList = functions
-
-	return query, nil
-}

+ 0 - 127
pkg/tsdb/mqe/model_parser_test.go

@@ -1,127 +0,0 @@
-package mqe
-
-import (
-	"testing"
-
-	"github.com/grafana/grafana/pkg/components/simplejson"
-	"github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/tsdb"
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-func TestMQEQueryParser(t *testing.T) {
-	Convey("MQE query parser", t, func() {
-		parser := &QueryParser{}
-
-		dsInfo := &models.DataSource{JsonData: simplejson.New()}
-		queryContext := &tsdb.QueryContext{}
-
-		Convey("can parse simple mqe model", func() {
-			json := `
-      {
-        "cluster": [],
-        "hosts": [
-          "staples-lab-1"
-        ],
-        "metrics": [
-          {
-            "metric": "os.cpu.all*"
-          }
-        ],
-        "rawQuery": "",
-        "refId": "A"
-      }
-      `
-			modelJson, err := simplejson.NewJson([]byte(json))
-			So(err, ShouldBeNil)
-
-			query, err := parser.Parse(modelJson, dsInfo, queryContext)
-			So(err, ShouldBeNil)
-			So(query.UseRawQuery, ShouldBeFalse)
-
-			So(len(query.Cluster), ShouldEqual, 0)
-			So(query.Hosts[0], ShouldEqual, "staples-lab-1")
-			So(query.Metrics[0].Metric, ShouldEqual, "os.cpu.all*")
-		})
-
-		Convey("can parse multi serie mqe model", func() {
-			json := `
-      {
-        "cluster": [
-          "demoapp"
-        ],
-        "hosts": [
-          "staples-lab-1"
-        ],
-        "metrics": [
-          {
-            "metric": "os.cpu.all.active_percentage"
-          },
-          {
-            "metric": "os.disk.sda.io_time"
-          }
-        ],
-        "functionList": [
-          {
-            "func": "aggregate.min"
-          },
-          {
-             "func": "aggregate.max"
-          }
-        ],
-        "rawQuery": "",
-        "refId": "A",
-        "addClusterToAlias": true,
-        "addHostToAlias": true
-      }
-      `
-			modelJson, err := simplejson.NewJson([]byte(json))
-			So(err, ShouldBeNil)
-
-			query, err := parser.Parse(modelJson, dsInfo, queryContext)
-			So(err, ShouldBeNil)
-			So(query.UseRawQuery, ShouldBeFalse)
-			So(query.Cluster[0], ShouldEqual, "demoapp")
-			So(query.Metrics[0].Metric, ShouldEqual, "os.cpu.all.active_percentage")
-			So(query.Metrics[1].Metric, ShouldEqual, "os.disk.sda.io_time")
-			So(query.FunctionList[0].Func, ShouldEqual, "aggregate.min")
-			So(query.FunctionList[1].Func, ShouldEqual, "aggregate.max")
-		})
-
-		Convey("can parse raw query", func() {
-			json := `
-      {
-        "addClusterToAlias": true,
-        "addHostToAlias": true,
-        "cluster": [],
-        "hosts": [
-          "staples-lab-1"
-        ],
-        "metrics": [
-          {
-            "alias": "cpu active",
-            "metric": "os.cpu.all.active_percentage"
-          },
-          {
-            "alias": "disk sda time",
-            "metric": "os.disk.sda.io_time"
-          }
-        ],
-        "rawQuery": true,
-        "query": "raw-query",
-        "refId": "A"
-      }
-      `
-			modelJson, err := simplejson.NewJson([]byte(json))
-			So(err, ShouldBeNil)
-
-			query, err := parser.Parse(modelJson, dsInfo, queryContext)
-			So(err, ShouldBeNil)
-
-			So(query.UseRawQuery, ShouldBeTrue)
-			So(query.RawQuery, ShouldEqual, "raw-query")
-			So(query.AddClusterToAlias, ShouldBeTrue)
-			So(query.AddHostToAlias, ShouldBeTrue)
-		})
-	})
-}

+ 0 - 85
pkg/tsdb/mqe/mqe.go

@@ -1,85 +0,0 @@
-package mqe
-
-import (
-	"context"
-	"net/http"
-
-	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/tsdb"
-)
-
-type MQEExecutor struct {
-	*models.DataSource
-	queryParser *QueryParser
-	apiClient   *apiClient
-	httpClient  *http.Client
-	log         log.Logger
-	tokenClient *TokenClient
-}
-
-func NewMQEExecutor(dsInfo *models.DataSource) (tsdb.Executor, error) {
-	httpclient, err := dsInfo.GetHttpClient()
-	if err != nil {
-		return nil, err
-	}
-
-	return &MQEExecutor{
-		DataSource:  dsInfo,
-		httpClient:  httpclient,
-		log:         log.New("tsdb.mqe"),
-		queryParser: NewQueryParser(),
-		apiClient:   NewApiClient(httpclient, dsInfo),
-		tokenClient: NewTokenClient(dsInfo),
-	}, nil
-}
-
-func init() {
-	tsdb.RegisterExecutor("mqe-datasource", NewMQEExecutor)
-}
-
-type QueryToSend struct {
-	RawQuery string
-	Metric   Metric
-	QueryRef *Query
-}
-
-func (e *MQEExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult {
-	result := &tsdb.BatchResult{}
-
-	availableSeries, err := e.tokenClient.GetTokenData(ctx)
-	if err != nil {
-		return result.WithError(err)
-	}
-
-	var mqeQueries []*Query
-	for _, v := range queries {
-		q, err := e.queryParser.Parse(v.Model, e.DataSource, queryContext)
-		if err != nil {
-			return result.WithError(err)
-		}
-		mqeQueries = append(mqeQueries, q)
-	}
-
-	var rawQueries []QueryToSend
-	for _, v := range mqeQueries {
-		queries, err := v.Build(availableSeries.Metrics)
-		if err != nil {
-			return result.WithError(err)
-		}
-
-		rawQueries = append(rawQueries, queries...)
-	}
-
-	e.log.Debug("Sending request", "url", e.DataSource.Url)
-
-	queryResult, err := e.apiClient.PerformRequests(ctx, rawQueries)
-	if err != nil {
-		return result.WithError(err)
-	}
-
-	result.QueryResults = make(map[string]*tsdb.QueryResult)
-	result.QueryResults["A"] = queryResult
-
-	return result
-}

+ 0 - 177
pkg/tsdb/mqe/response_parser.go

@@ -1,177 +0,0 @@
-package mqe
-
-import (
-	"encoding/json"
-	"io/ioutil"
-	"net/http"
-	"strconv"
-	"strings"
-
-	"fmt"
-
-	"regexp"
-
-	"github.com/grafana/grafana/pkg/components/null"
-	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/tsdb"
-)
-
-func NewResponseParser() *ResponseParser {
-	return &ResponseParser{
-		log: log.New("tsdb.mqe"),
-	}
-}
-
-var (
-	indexAliasPattern    *regexp.Regexp
-	wildcardAliasPattern *regexp.Regexp
-)
-
-func init() {
-	indexAliasPattern = regexp.MustCompile(`\$(\d)`)
-	wildcardAliasPattern = regexp.MustCompile(`[*!]`)
-}
-
-type MQEResponse struct {
-	Success bool               `json:"success"`
-	Name    string             `json:"name"`
-	Body    []MQEResponseSerie `json:"body"`
-}
-
-type ResponseTimeRange struct {
-	Start      int64 `json:"start"`
-	End        int64 `json:"end"`
-	Resolution int64 `json:"Resolution"`
-}
-
-type MQEResponseSerie struct {
-	Query     string            `json:"query"`
-	Name      string            `json:"name"`
-	Type      string            `json:"type"`
-	Series    []MQESerie        `json:"series"`
-	TimeRange ResponseTimeRange `json:"timerange"`
-}
-
-type MQESerie struct {
-	Values []null.Float      `json:"values"`
-	Tagset map[string]string `json:"tagset"`
-}
-
-type ResponseParser struct {
-	log log.Logger
-}
-
-func (parser *ResponseParser) Parse(res *http.Response, queryRef QueryToSend) ([]*tsdb.TimeSeries, error) {
-	body, err := ioutil.ReadAll(res.Body)
-	defer res.Body.Close()
-	if err != nil {
-		return nil, err
-	}
-
-	if res.StatusCode/100 != 2 {
-		parser.log.Error("Request failed", "status code", res.StatusCode, "body", string(body))
-		return nil, fmt.Errorf("Returned invalid statuscode")
-	}
-
-	var data *MQEResponse = &MQEResponse{}
-	err = json.Unmarshal(body, data)
-	if err != nil {
-		parser.log.Info("Failed to unmarshal response", "error", err, "status", res.Status, "body", string(body))
-		return nil, err
-	}
-
-	if !data.Success {
-		return nil, fmt.Errorf("Request failed.")
-	}
-
-	var series []*tsdb.TimeSeries
-	for _, body := range data.Body {
-		for _, mqeSerie := range body.Series {
-			serie := &tsdb.TimeSeries{
-				Tags: map[string]string{},
-				Name: parser.formatLegend(body, mqeSerie, queryRef),
-			}
-			for key, value := range mqeSerie.Tagset {
-				serie.Tags[key] = value
-			}
-
-			for i, value := range mqeSerie.Values {
-				timestamp := body.TimeRange.Start + int64(i)*body.TimeRange.Resolution
-				serie.Points = append(serie.Points, tsdb.NewTimePoint(value, float64(timestamp)))
-			}
-
-			series = append(series, serie)
-		}
-	}
-
-	return series, nil
-}
-
-func (parser *ResponseParser) formatLegend(body MQEResponseSerie, mqeSerie MQESerie, queryToSend QueryToSend) string {
-	namePrefix := ""
-
-	//append predefined tags to seriename
-	for key, value := range mqeSerie.Tagset {
-		if key == "cluster" && queryToSend.QueryRef.AddClusterToAlias {
-			namePrefix += value + " "
-		}
-	}
-	for key, value := range mqeSerie.Tagset {
-		if key == "host" && queryToSend.QueryRef.AddHostToAlias {
-			namePrefix += value + " "
-		}
-	}
-
-	return namePrefix + parser.formatName(body, queryToSend)
-}
-
-func (parser *ResponseParser) formatName(body MQEResponseSerie, queryToSend QueryToSend) string {
-	if indexAliasPattern.MatchString(queryToSend.Metric.Alias) {
-		return parser.indexAlias(body, queryToSend)
-	}
-
-	if wildcardAliasPattern.MatchString(queryToSend.Metric.Metric) && wildcardAliasPattern.MatchString(queryToSend.Metric.Alias) {
-		return parser.wildcardAlias(body, queryToSend)
-	}
-
-	return body.Name
-}
-
-func (parser *ResponseParser) wildcardAlias(body MQEResponseSerie, queryToSend QueryToSend) string {
-	regString := strings.Replace(queryToSend.Metric.Metric, `*`, `(.*)`, 1)
-	reg, err := regexp.Compile(regString)
-	if err != nil {
-		return queryToSend.Metric.Alias
-	}
-
-	matches := reg.FindAllStringSubmatch(queryToSend.RawQuery, -1)
-
-	if len(matches) == 0 || len(matches[0]) < 2 {
-		return queryToSend.Metric.Alias
-	}
-
-	return matches[0][1]
-}
-
-func (parser *ResponseParser) indexAlias(body MQEResponseSerie, queryToSend QueryToSend) string {
-	queryNameParts := strings.Split(queryToSend.Metric.Metric, `.`)
-
-	name := indexAliasPattern.ReplaceAllStringFunc(queryToSend.Metric.Alias, func(in string) string {
-		positionName := strings.TrimSpace(strings.Replace(in, "$", "", 1))
-
-		pos, err := strconv.Atoi(positionName)
-		if err != nil {
-			return ""
-		}
-
-		for i, part := range queryNameParts {
-			if i == pos-1 {
-				return strings.TrimSpace(part)
-			}
-		}
-
-		return ""
-	})
-
-	return strings.Replace(name, " ", ".", -1)
-}

+ 0 - 187
pkg/tsdb/mqe/response_parser_test.go

@@ -1,187 +0,0 @@
-package mqe
-
-import (
-	"testing"
-
-	"net/http"
-	"strings"
-
-	"io/ioutil"
-
-	"github.com/grafana/grafana/pkg/components/null"
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-var (
-	testJson string
-)
-
-func TestMQEResponseParser(t *testing.T) {
-	Convey("MQE response parser", t, func() {
-		parser := NewResponseParser()
-
-		Convey("Can parse response", func() {
-			queryRef := QueryToSend{
-				QueryRef: &Query{
-					AddClusterToAlias: true,
-					AddHostToAlias:    true,
-				},
-				Metric: Metric{Alias: ""},
-			}
-
-			response := &http.Response{
-				StatusCode: 200,
-				Body:       ioutil.NopCloser(strings.NewReader(testJson)),
-			}
-			res, err := parser.Parse(response, queryRef)
-			So(err, ShouldBeNil)
-			So(len(res), ShouldEqual, 2)
-			So(len(res[0].Points), ShouldEqual, 14)
-			So(res[0].Name, ShouldEqual, "demoapp staples-lab-1 os.disk.sda3.weighted_io_time")
-			startTime := 1479287280000
-			for i := 0; i < 11; i++ {
-				So(res[0].Points[i][0].Float64, ShouldEqual, i+1)
-				So(res[0].Points[i][1].Float64, ShouldEqual, startTime+(i*30000))
-			}
-
-		})
-
-		Convey("Can format legend", func() {
-			mqeSerie := MQESerie{
-				Tagset: map[string]string{
-					"cluster": "demoapp",
-					"host":    "staples-lab-1",
-				},
-				Values: []null.Float{null.NewFloat(3, true)},
-			}
-
-			Convey("with empty alias", func() {
-				serie := MQEResponseSerie{Name: "os.disk.sda3.weighted_io_time"}
-				queryRef := QueryToSend{
-					QueryRef: &Query{
-						AddClusterToAlias: true,
-						AddHostToAlias:    true,
-					},
-					Metric: Metric{Alias: ""},
-				}
-				legend := parser.formatLegend(serie, mqeSerie, queryRef)
-				So(legend, ShouldEqual, "demoapp staples-lab-1 os.disk.sda3.weighted_io_time")
-			})
-
-			Convey("with index alias (ex $2 $3)", func() {
-				serie := MQEResponseSerie{Name: "os.disk.sda3.weighted_io_time"}
-				queryRef := QueryToSend{
-					QueryRef: &Query{
-						AddClusterToAlias: true,
-						AddHostToAlias:    true,
-					},
-					Metric: Metric{Alias: "$2 $3", Metric: "os.disk.sda3.weighted_io_time"},
-				}
-				legend := parser.formatLegend(serie, mqeSerie, queryRef)
-				So(legend, ShouldEqual, "demoapp staples-lab-1 disk.sda3")
-			})
-
-			Convey("with wildcard alias", func() {
-				serie := MQEResponseSerie{Name: "os.disk.sda3.weighted_io_time", Query: "os.disk.*"}
-
-				queryRef := QueryToSend{
-					QueryRef: &Query{
-						AddClusterToAlias: true,
-						AddHostToAlias:    true,
-					},
-					RawQuery: "os.disk.sda3.weighted_io_time",
-					Metric:   Metric{Alias: "*", Metric: "os.disk.*.weighted_io_time"},
-				}
-				legend := parser.formatLegend(serie, mqeSerie, queryRef)
-				So(legend, ShouldEqual, "demoapp staples-lab-1 sda3")
-			})
-		})
-	})
-}
-
-func init() {
-	testJson = `{
-    "success": true,
-    "name": "select",
-    "body": [
-      {
-        "query": "os.disk.sda3.weighted_io_time",
-        "name": "os.disk.sda3.weighted_io_time",
-        "type": "series",
-        "series": [
-          {
-            "tagset": {
-              "cluster": "demoapp",
-              "host": "staples-lab-1"
-            },
-            "values": [1,2,3,4,5,6,7,8,9,10,11, null, null, null]
-          },
-          {
-            "tagset": {
-              "cluster": "demoapp",
-              "host": "staples-lab-2"
-            },
-            "values": [11,10,9,8,7,6,5,4,3,2,1]
-          }
-        ],
-        "timerange": {
-          "start": 1479287280000,
-          "end": 1479287580000,
-          "resolution": 30000
-        }
-      }
-    ],
-    "metadata": {
-      "description": {
-        "cluster": [
-          "demoapp"
-        ],
-        "host": [
-          "staples-lab-1",
-          "staples-lab-2"
-        ]
-      },
-      "notes": null,
-      "profile": [
-        {
-          "name": "Parsing Query",
-          "start": "2016-11-16T04:16:21.874354721-05:00",
-          "finish": "2016-11-16T04:16:21.874762291-05:00"
-        },
-        {
-          "name": "Cassandra GetAllTags",
-          "start": "2016-11-16T04:16:21.874907171-05:00",
-          "finish": "2016-11-16T04:16:21.876401922-05:00"
-        },
-        {
-          "name": "CachedMetricMetadataAPI_GetAllTags_Expired",
-          "start": "2016-11-16T04:16:21.874904751-05:00",
-          "finish": "2016-11-16T04:16:21.876407852-05:00"
-        },
-        {
-          "name": "CachedMetricMetadataAPI_GetAllTags",
-          "start": "2016-11-16T04:16:21.874899491-05:00",
-          "finish": "2016-11-16T04:16:21.876410382-05:00"
-        },
-        {
-          "name": "Blueflood FetchSingleTimeseries Resolution",
-          "description": "os.disk.sda3.weighted_io_time [app=demoapp,host=staples-lab-1]\n at 30s",
-          "start": "2016-11-16T04:16:21.876623312-05:00",
-          "finish": "2016-11-16T04:16:21.881763444-05:00"
-        },
-        {
-          "name": "Blueflood FetchSingleTimeseries Resolution",
-          "description": "os.disk.sda3.weighted_io_time [app=demoapp,host=staples-lab-2]\n at 30s",
-          "start": "2016-11-16T04:16:21.876642682-05:00",
-          "finish": "2016-11-16T04:16:21.881895914-05:00"
-        },
-        {
-          "name": "Blueflood FetchMultipleTimeseries",
-          "start": "2016-11-16T04:16:21.876418022-05:00",
-          "finish": "2016-11-16T04:16:21.881921474-05:00"
-        }
-      ]
-    }
-  }
-  `
-}

+ 0 - 101
pkg/tsdb/mqe/token_client.go

@@ -1,101 +0,0 @@
-package mqe
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"path"
-	"time"
-
-	"golang.org/x/net/context/ctxhttp"
-
-	"strconv"
-
-	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/models"
-	"github.com/patrickmn/go-cache"
-)
-
-var tokenCache *cache.Cache
-
-func init() {
-	tokenCache = cache.New(5*time.Minute, 30*time.Second)
-}
-
-type TokenClient struct {
-	log        log.Logger
-	Datasource *models.DataSource
-	HttpClient *http.Client
-}
-
-func NewTokenClient(datasource *models.DataSource) *TokenClient {
-	httpClient, _ := datasource.GetHttpClient()
-
-	return &TokenClient{
-		log:        log.New("tsdb.mqe.tokenclient"),
-		Datasource: datasource,
-		HttpClient: httpClient,
-	}
-}
-
-func (client *TokenClient) GetTokenData(ctx context.Context) (*TokenBody, error) {
-	key := strconv.FormatInt(client.Datasource.Id, 10)
-
-	item, found := tokenCache.Get(key)
-	if found {
-		if result, ok := item.(*TokenBody); ok {
-			return result, nil
-		}
-	}
-
-	b, err := client.RequestTokenData(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	tokenCache.Set(key, b, cache.DefaultExpiration)
-
-	return b, nil
-}
-
-func (client *TokenClient) RequestTokenData(ctx context.Context) (*TokenBody, error) {
-	u, _ := url.Parse(client.Datasource.Url)
-	u.Path = path.Join(u.Path, "token")
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		client.log.Info("Failed to create request", "error", err)
-	}
-
-	res, err := ctxhttp.Do(ctx, client.HttpClient, req)
-	if err != nil {
-		return nil, err
-	}
-
-	body, err := ioutil.ReadAll(res.Body)
-	defer res.Body.Close()
-	if err != nil {
-		return nil, err
-	}
-
-	if res.StatusCode/100 != 2 {
-		client.log.Info("Request failed", "status", res.Status, "body", string(body))
-		return nil, fmt.Errorf("Request failed status: %v", res.Status)
-	}
-
-	var result *TokenResponse
-	err = json.Unmarshal(body, &result)
-	if err != nil {
-		client.log.Info("Failed to unmarshal response", "error", err, "status", res.Status, "body", string(body))
-		return nil, err
-	}
-
-	if !result.Success {
-		return nil, fmt.Errorf("Request failed for unknown reason.")
-	}
-
-	return &result.Body, nil
-}

+ 0 - 27
pkg/tsdb/mqe/token_client_test.go

@@ -1,27 +0,0 @@
-package mqe
-
-import (
-	"context"
-	"testing"
-
-	"github.com/grafana/grafana/pkg/components/simplejson"
-	"github.com/grafana/grafana/pkg/models"
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-func TestTokenClient(t *testing.T) {
-	SkipConvey("Token client", t, func() {
-		dsInfo := &models.DataSource{
-			JsonData: simplejson.New(),
-			Url:      "",
-		}
-
-		client := NewTokenClient(dsInfo)
-
-		body, err := client.RequestTokenData(context.TODO())
-
-		So(err, ShouldBeNil)
-		//So(len(body.Functions), ShouldBeGreaterThan, 1)
-		So(len(body.Metrics), ShouldBeGreaterThan, 1)
-	})
-}

+ 0 - 137
pkg/tsdb/mqe/types.go

@@ -1,137 +0,0 @@
-package mqe
-
-import (
-	"fmt"
-
-	"strings"
-
-	"regexp"
-
-	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/tsdb"
-)
-
-type Metric struct {
-	Metric string
-	Alias  string
-}
-
-type Function struct {
-	Func string
-}
-
-type Query struct {
-	Metrics           []Metric
-	Hosts             []string
-	Cluster           []string
-	FunctionList      []Function
-	AddClusterToAlias bool
-	AddHostToAlias    bool
-
-	TimeRange   *tsdb.TimeRange
-	UseRawQuery bool
-	RawQuery    string
-}
-
-var (
-	containsWildcardPattern *regexp.Regexp = regexp.MustCompile(`\*`)
-)
-
-func (q *Query) Build(availableSeries []string) ([]QueryToSend, error) {
-	var queriesToSend []QueryToSend
-	where := q.buildWhereClause()
-	functions := q.buildFunctionList()
-
-	for _, metric := range q.Metrics {
-		alias := ""
-		if metric.Alias != "" {
-			alias = fmt.Sprintf(" {%s}", metric.Alias)
-		}
-
-		if !containsWildcardPattern.Match([]byte(metric.Metric)) {
-			rawQuery := q.renderQuerystring(metric.Metric, functions, alias, where, q.TimeRange)
-			queriesToSend = append(queriesToSend, QueryToSend{
-				RawQuery: rawQuery,
-				QueryRef: q,
-				Metric:   metric,
-			})
-		} else {
-			m := strings.Replace(metric.Metric, "*", ".*", -1)
-			mp, err := regexp.Compile(m)
-
-			if err != nil {
-				log.Error2("failed to compile regex for ", "metric", m)
-				continue
-			}
-
-			//TODO: this lookup should be cached
-			for _, wildcardMatch := range availableSeries {
-				if mp.Match([]byte(wildcardMatch)) {
-					rawQuery := q.renderQuerystring(wildcardMatch, functions, alias, where, q.TimeRange)
-					queriesToSend = append(queriesToSend, QueryToSend{
-						RawQuery: rawQuery,
-						QueryRef: q,
-						Metric:   metric,
-					})
-				}
-			}
-		}
-	}
-
-	return queriesToSend, nil
-}
-
-func (q *Query) renderQuerystring(path, functions, alias, where string, timerange *tsdb.TimeRange) string {
-	return fmt.Sprintf(
-		"`%s`%s%s %s from %v to %v",
-		path,
-		functions,
-		alias,
-		where,
-		q.TimeRange.GetFromAsMsEpoch(),
-		q.TimeRange.GetToAsMsEpoch())
-}
-
-func (q *Query) buildFunctionList() string {
-	functions := ""
-	for _, v := range q.FunctionList {
-		functions = fmt.Sprintf("%s|%s", functions, v.Func)
-	}
-
-	return functions
-}
-
-func (q *Query) buildWhereClause() string {
-	hasApps := len(q.Cluster) > 0
-	hasHosts := len(q.Hosts) > 0
-
-	where := ""
-	if hasHosts || hasApps {
-		where += "where "
-	}
-
-	if hasApps {
-		apps := strings.Join(q.Cluster, "', '")
-		where += fmt.Sprintf("cluster in ('%s')", apps)
-	}
-
-	if hasHosts && hasApps {
-		where += " and "
-	}
-
-	if hasHosts {
-		hosts := strings.Join(q.Hosts, "', '")
-		where += fmt.Sprintf("host in ('%s')", hosts)
-	}
-
-	return where
-}
-
-type TokenBody struct {
-	Metrics []string
-}
-
-type TokenResponse struct {
-	Success bool
-	Body    TokenBody
-}

+ 0 - 95
pkg/tsdb/mqe/types_test.go

@@ -1,95 +0,0 @@
-package mqe
-
-import (
-	"testing"
-
-	"time"
-
-	"fmt"
-
-	"github.com/grafana/grafana/pkg/tsdb"
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-func TestWildcardExpansion(t *testing.T) {
-	availableMetrics := []string{
-		"os.cpu.all.idle",
-		"os.cpu.1.idle",
-		"os.cpu.2.idle",
-		"os.cpu.3.idle",
-	}
-
-	now := time.Now()
-	from := now.Add((time.Minute*5)*-1).UnixNano() / int64(time.Millisecond)
-	to := now.UnixNano() / int64(time.Millisecond)
-
-	Convey("Can expanding query", t, func() {
-		Convey("Without wildcard series", func() {
-			query := &Query{
-				Metrics: []Metric{
-					{Metric: "os.cpu.3.idle", Alias: ""},
-					{Metric: "os.cpu.2.idle", Alias: ""},
-					{Metric: "os.cpu.1.idle", Alias: "cpu"},
-				},
-				Hosts:             []string{"staples-lab-1", "staples-lab-2"},
-				Cluster:           []string{"demoapp-1", "demoapp-2"},
-				AddClusterToAlias: false,
-				AddHostToAlias:    false,
-				FunctionList: []Function{
-					{Func: "aggregate.min"},
-				},
-				TimeRange: &tsdb.TimeRange{Now: now, From: "5m", To: "now"},
-			}
-
-			expandeQueries, err := query.Build(availableMetrics)
-			So(err, ShouldBeNil)
-			So(len(expandeQueries), ShouldEqual, 3)
-			So(expandeQueries[0].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.3.idle`|aggregate.min where cluster in ('demoapp-1', 'demoapp-2') and host in ('staples-lab-1', 'staples-lab-2') from %v to %v", from, to))
-			So(expandeQueries[1].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.2.idle`|aggregate.min where cluster in ('demoapp-1', 'demoapp-2') and host in ('staples-lab-1', 'staples-lab-2') from %v to %v", from, to))
-			So(expandeQueries[2].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.1.idle`|aggregate.min {cpu} where cluster in ('demoapp-1', 'demoapp-2') and host in ('staples-lab-1', 'staples-lab-2') from %v to %v", from, to))
-		})
-
-		Convey("With two aggregate functions", func() {
-			query := &Query{
-				Metrics: []Metric{
-					{Metric: "os.cpu.3.idle", Alias: ""},
-				},
-				Hosts:             []string{"staples-lab-1", "staples-lab-2"},
-				Cluster:           []string{"demoapp-1", "demoapp-2"},
-				AddClusterToAlias: false,
-				AddHostToAlias:    false,
-				FunctionList: []Function{
-					{Func: "aggregate.min"},
-					{Func: "aggregate.max"},
-				},
-				TimeRange: &tsdb.TimeRange{Now: now, From: "5m", To: "now"},
-			}
-
-			expandeQueries, err := query.Build(availableMetrics)
-			So(err, ShouldBeNil)
-			So(len(expandeQueries), ShouldEqual, 1)
-			So(expandeQueries[0].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.3.idle`|aggregate.min|aggregate.max where cluster in ('demoapp-1', 'demoapp-2') and host in ('staples-lab-1', 'staples-lab-2') from %v to %v", from, to))
-		})
-
-		Convey("Containing wildcard series", func() {
-			query := &Query{
-				Metrics: []Metric{
-					{Metric: "os.cpu*", Alias: ""},
-				},
-				Hosts:             []string{"staples-lab-1"},
-				AddClusterToAlias: false,
-				AddHostToAlias:    false,
-				TimeRange:         &tsdb.TimeRange{Now: now, From: "5m", To: "now"},
-			}
-
-			expandeQueries, err := query.Build(availableMetrics)
-			So(err, ShouldBeNil)
-			So(len(expandeQueries), ShouldEqual, 4)
-
-			So(expandeQueries[0].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.all.idle` where host in ('staples-lab-1') from %v to %v", from, to))
-			So(expandeQueries[1].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.1.idle` where host in ('staples-lab-1') from %v to %v", from, to))
-			So(expandeQueries[2].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.2.idle` where host in ('staples-lab-1') from %v to %v", from, to))
-			So(expandeQueries[3].RawQuery, ShouldEqual, fmt.Sprintf("`os.cpu.3.idle` where host in ('staples-lab-1') from %v to %v", from, to))
-		})
-	})
-}

+ 20 - 18
pkg/tsdb/mysql/mysql.go

@@ -21,9 +21,8 @@ import (
 )
 
 type MysqlExecutor struct {
-	datasource *models.DataSource
-	engine     *xorm.Engine
-	log        log.Logger
+	engine *xorm.Engine
+	log    log.Logger
 }
 
 type engineCacheType struct {
@@ -38,16 +37,15 @@ var engineCache = engineCacheType{
 }
 
 func init() {
-	tsdb.RegisterExecutor("mysql", NewMysqlExecutor)
+	tsdb.RegisterTsdbQueryEndpoint("mysql", NewMysqlExecutor)
 }
 
-func NewMysqlExecutor(datasource *models.DataSource) (tsdb.Executor, error) {
+func NewMysqlExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
 	executor := &MysqlExecutor{
-		datasource: datasource,
-		log:        log.New("tsdb.mysql"),
+		log: log.New("tsdb.mysql"),
 	}
 
-	err := executor.initEngine()
+	err := executor.initEngine(datasource)
 	if err != nil {
 		return nil, err
 	}
@@ -55,18 +53,24 @@ func NewMysqlExecutor(datasource *models.DataSource) (tsdb.Executor, error) {
 	return executor, nil
 }
 
-func (e *MysqlExecutor) initEngine() error {
+func (e *MysqlExecutor) initEngine(dsInfo *models.DataSource) error {
 	engineCache.Lock()
 	defer engineCache.Unlock()
 
-	if engine, present := engineCache.cache[e.datasource.Id]; present {
-		if version, _ := engineCache.versions[e.datasource.Id]; version == e.datasource.Version {
+	if engine, present := engineCache.cache[dsInfo.Id]; present {
+		if version, _ := engineCache.versions[dsInfo.Id]; version == dsInfo.Version {
 			e.engine = engine
 			return nil
 		}
 	}
 
-	cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&parseTime=true&loc=UTC", e.datasource.User, e.datasource.Password, "tcp", e.datasource.Url, e.datasource.Database)
+	cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&parseTime=true&loc=UTC",
+		dsInfo.User,
+		dsInfo.Password,
+		"tcp",
+		dsInfo.Url,
+		dsInfo.Database)
+
 	e.log.Debug("getEngine", "connection", cnnstr)
 
 	engine, err := xorm.NewEngine("mysql", cnnstr)
@@ -76,22 +80,22 @@ func (e *MysqlExecutor) initEngine() error {
 		return err
 	}
 
-	engineCache.cache[e.datasource.Id] = engine
+	engineCache.cache[dsInfo.Id] = engine
 	e.engine = engine
 	return nil
 }
 
-func (e *MysqlExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
+func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
 	result := &tsdb.BatchResult{
 		QueryResults: make(map[string]*tsdb.QueryResult),
 	}
 
-	macroEngine := NewMysqlMacroEngine(context.TimeRange)
+	macroEngine := NewMysqlMacroEngine(tsdbQuery.TimeRange)
 	session := e.engine.NewSession()
 	defer session.Close()
 	db := session.DB()
 
-	for _, query := range queries {
+	for _, query := range tsdbQuery.Queries {
 		rawSql := query.Model.Get("rawSql").MustString()
 		if rawSql == "" {
 			continue
@@ -272,8 +276,6 @@ func (e MysqlExecutor) TransformToTimeSeries(query *tsdb.Query, rows *core.Rows,
 			rowData.metric = "Unknown"
 		}
 
-		//e.log.Debug("Rows", "metric", rowData.metric, "time", rowData.time, "value", rowData.value)
-
 		if !rowData.time.Valid {
 			return fmt.Errorf("Found row with no time value")
 		}

+ 26 - 18
pkg/tsdb/opentsdb/opentsdb.go

@@ -22,20 +22,22 @@ import (
 )
 
 type OpenTsdbExecutor struct {
-	*models.DataSource
-	httpClient *http.Client
+	//*models.DataSource
+	//httpClient *http.Client
 }
 
-func NewOpenTsdbExecutor(datasource *models.DataSource) (tsdb.Executor, error) {
-	httpClient, err := datasource.GetHttpClient()
+func NewOpenTsdbExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
+	/*
+		httpClient, err := datasource.GetHttpClient()
 
-	if err != nil {
-		return nil, err
-	}
+		if err != nil {
+			return nil, err
+		}
+	*/
 
 	return &OpenTsdbExecutor{
-		DataSource: datasource,
-		httpClient: httpClient,
+	//DataSource: datasource,
+	//httpClient: httpClient,
 	}, nil
 }
 
@@ -45,10 +47,10 @@ var (
 
 func init() {
 	plog = log.New("tsdb.opentsdb")
-	tsdb.RegisterExecutor("opentsdb", NewOpenTsdbExecutor)
+	tsdb.RegisterTsdbQueryEndpoint("opentsdb", NewOpenTsdbExecutor)
 }
 
-func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult {
+func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource, queryContext *tsdb.TsdbQuery) *tsdb.BatchResult {
 	result := &tsdb.BatchResult{}
 
 	var tsdbQuery OpenTsdbQuery
@@ -56,7 +58,7 @@ func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
 	tsdbQuery.Start = queryContext.TimeRange.GetFromAsMsEpoch()
 	tsdbQuery.End = queryContext.TimeRange.GetToAsMsEpoch()
 
-	for _, query := range queries {
+	for _, query := range queryContext.Queries {
 		metric := e.buildMetric(query)
 		tsdbQuery.Queries = append(tsdbQuery.Queries, metric)
 	}
@@ -65,13 +67,19 @@ func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
 		plog.Debug("OpenTsdb request", "params", tsdbQuery)
 	}
 
-	req, err := e.createRequest(tsdbQuery)
+	req, err := e.createRequest(dsInfo, tsdbQuery)
+	if err != nil {
+		result.Error = err
+		return result
+	}
+
+	httpClient, err := dsInfo.GetHttpClient()
 	if err != nil {
 		result.Error = err
 		return result
 	}
 
-	res, err := ctxhttp.Do(ctx, e.httpClient, req)
+	res, err := ctxhttp.Do(ctx, httpClient, req)
 	if err != nil {
 		result.Error = err
 		return result
@@ -86,8 +94,8 @@ func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
 	return result
 }
 
-func (e *OpenTsdbExecutor) createRequest(data OpenTsdbQuery) (*http.Request, error) {
-	u, _ := url.Parse(e.Url)
+func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsdbQuery) (*http.Request, error) {
+	u, _ := url.Parse(dsInfo.Url)
 	u.Path = path.Join(u.Path, "api/query")
 
 	postData, err := json.Marshal(data)
@@ -99,8 +107,8 @@ func (e *OpenTsdbExecutor) createRequest(data OpenTsdbQuery) (*http.Request, err
 	}
 
 	req.Header.Set("Content-Type", "application/json")
-	if e.BasicAuth {
-		req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
+	if dsInfo.BasicAuth {
+		req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.BasicAuthPassword)
 	}
 
 	return req, err

+ 20 - 16
pkg/tsdb/prometheus/prometheus.go

@@ -7,6 +7,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/opentracing/opentracing-go"
+
 	"net/http"
 
 	"github.com/grafana/grafana/pkg/components/null"
@@ -16,12 +18,9 @@ import (
 	api "github.com/prometheus/client_golang/api"
 	apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
 	"github.com/prometheus/common/model"
-	//api "github.com/prometheus/client_golang/api"
-	//apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
 )
 
 type PrometheusExecutor struct {
-	*models.DataSource
 	Transport *http.Transport
 }
 
@@ -37,15 +36,14 @@ func (bat basicAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro
 	return bat.Transport.RoundTrip(req)
 }
 
-func NewPrometheusExecutor(dsInfo *models.DataSource) (tsdb.Executor, error) {
+func NewPrometheusExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
 	transport, err := dsInfo.GetHttpTransport()
 	if err != nil {
 		return nil, err
 	}
 
 	return &PrometheusExecutor{
-		DataSource: dsInfo,
-		Transport:  transport,
+		Transport: transport,
 	}, nil
 }
 
@@ -56,21 +54,21 @@ var (
 
 func init() {
 	plog = log.New("tsdb.prometheus")
-	tsdb.RegisterExecutor("prometheus", NewPrometheusExecutor)
+	tsdb.RegisterTsdbQueryEndpoint("prometheus", NewPrometheusExecutor)
 	legendFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
 }
 
-func (e *PrometheusExecutor) getClient() (apiv1.API, error) {
+func (e *PrometheusExecutor) getClient(dsInfo *models.DataSource) (apiv1.API, error) {
 	cfg := api.Config{
-		Address:      e.DataSource.Url,
+		Address:      dsInfo.Url,
 		RoundTripper: e.Transport,
 	}
 
-	if e.BasicAuth {
+	if dsInfo.BasicAuth {
 		cfg.RoundTripper = basicAuthTransport{
 			Transport: e.Transport,
-			username:  e.BasicAuthUser,
-			password:  e.BasicAuthPassword,
+			username:  dsInfo.BasicAuthUser,
+			password:  dsInfo.BasicAuthPassword,
 		}
 	}
 
@@ -82,15 +80,15 @@ func (e *PrometheusExecutor) getClient() (apiv1.API, error) {
 	return apiv1.NewAPI(client), nil
 }
 
-func (e *PrometheusExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult {
+func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
 	result := &tsdb.BatchResult{}
 
-	client, err := e.getClient()
+	client, err := e.getClient(dsInfo)
 	if err != nil {
 		return result.WithError(err)
 	}
 
-	query, err := parseQuery(queries, queryContext)
+	query, err := parseQuery(tsdbQuery.Queries, tsdbQuery)
 	if err != nil {
 		return result.WithError(err)
 	}
@@ -101,6 +99,12 @@ func (e *PrometheusExecutor) Execute(ctx context.Context, queries tsdb.QuerySlic
 		Step:  query.Step,
 	}
 
+	span, ctx := opentracing.StartSpanFromContext(ctx, "alerting.prometheus")
+	span.SetTag("expr", query.Expr)
+	span.SetTag("start_unixnano", int64(query.Start.UnixNano()))
+	span.SetTag("stop_unixnano", int64(query.End.UnixNano()))
+	defer span.Finish()
+
 	value, err := client.QueryRange(ctx, query.Expr, timeRange)
 
 	if err != nil {
@@ -134,7 +138,7 @@ func formatLegend(metric model.Metric, query *PrometheusQuery) string {
 	return string(result)
 }
 
-func parseQuery(queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) (*PrometheusQuery, error) {
+func parseQuery(queries []*tsdb.Query, queryContext *tsdb.TsdbQuery) (*PrometheusQuery, error) {
 	queryModel := queries[0]
 
 	expr, err := queryModel.Model.Get("expr").String()

+ 0 - 21
pkg/tsdb/query_context.go

@@ -1,21 +0,0 @@
-package tsdb
-
-import "sync"
-
-type QueryContext struct {
-	TimeRange   *TimeRange
-	Queries     QuerySlice
-	Results     map[string]*QueryResult
-	ResultsChan chan *BatchResult
-	Lock        sync.RWMutex
-	BatchWaits  sync.WaitGroup
-}
-
-func NewQueryContext(queries QuerySlice, timeRange *TimeRange) *QueryContext {
-	return &QueryContext{
-		TimeRange:   timeRange,
-		Queries:     queries,
-		ResultsChan: make(chan *BatchResult),
-		Results:     make(map[string]*QueryResult),
-	}
-}

+ 36 - 0
pkg/tsdb/query_endpoint.go

@@ -0,0 +1,36 @@
+package tsdb
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/grafana/grafana/pkg/models"
+)
+
+type TsdbQueryEndpoint interface {
+	Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) *BatchResult
+}
+
+var registry map[string]GetTsdbQueryEndpointFn
+
+type GetTsdbQueryEndpointFn func(dsInfo *models.DataSource) (TsdbQueryEndpoint, error)
+
+func init() {
+	registry = make(map[string]GetTsdbQueryEndpointFn)
+}
+
+func getTsdbQueryEndpointFor(dsInfo *models.DataSource) (TsdbQueryEndpoint, error) {
+	if fn, exists := registry[dsInfo.Type]; exists {
+		executor, err := fn(dsInfo)
+		if err != nil {
+			return nil, err
+		}
+
+		return executor, nil
+	}
+	return nil, fmt.Errorf("Could not find executor for data source type: %s", dsInfo.Type)
+}
+
+func RegisterTsdbQueryEndpoint(pluginId string, fn GetTsdbQueryEndpointFn) {
+	registry[pluginId] = fn
+}

+ 12 - 49
pkg/tsdb/request.go

@@ -4,60 +4,23 @@ import (
 	"context"
 )
 
-type HandleRequestFunc func(ctx context.Context, req *Request) (*Response, error)
+type HandleRequestFunc func(ctx context.Context, req *TsdbQuery) (*Response, error)
 
-func HandleRequest(ctx context.Context, req *Request) (*Response, error) {
-	context := NewQueryContext(req.Queries, req.TimeRange)
-
-	batches, err := getBatches(req)
+func HandleRequest(ctx context.Context, req *TsdbQuery) (*Response, error) {
+	//TODO niceify
+	ds := req.Queries[0].DataSource
+	endpoint, err := getTsdbQueryEndpointFor(ds)
 	if err != nil {
 		return nil, err
 	}
 
-	currentlyExecuting := 0
-
-	for _, batch := range batches {
-		if len(batch.Depends) == 0 {
-			currentlyExecuting += 1
-			batch.Started = true
-			go batch.process(ctx, context)
-		}
-	}
-
-	response := &Response{}
-
-	for currentlyExecuting != 0 {
-		select {
-		case batchResult := <-context.ResultsChan:
-			currentlyExecuting -= 1
-
-			response.BatchTimings = append(response.BatchTimings, batchResult.Timings)
-
-			if batchResult.Error != nil {
-				return nil, batchResult.Error
-			}
-
-			for refId, result := range batchResult.QueryResults {
-				context.Results[refId] = result
-			}
-
-			for _, batch := range batches {
-				// not interested in started batches
-				if batch.Started {
-					continue
-				}
-
-				if batch.allDependenciesAreIn(context) {
-					currentlyExecuting += 1
-					batch.Started = true
-					go batch.process(ctx, context)
-				}
-			}
-		case <-ctx.Done():
-			return nil, ctx.Err()
-		}
+	res := endpoint.Query(ctx, ds, req)
+	if res.Error != nil {
+		return nil, res.Error
 	}
 
-	response.Results = context.Results
-	return response, nil
+	return &Response{
+		Results:      res.QueryResults,
+		BatchTimings: []*BatchTiming{res.Timings},
+	}, nil
 }

+ 7 - 7
pkg/tsdb/testdata/scenarios.go

@@ -11,7 +11,7 @@ import (
 	"github.com/grafana/grafana/pkg/tsdb"
 )
 
-type ScenarioHandler func(query *tsdb.Query, context *tsdb.QueryContext) *tsdb.QueryResult
+type ScenarioHandler func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult
 
 type Scenario struct {
 	Id          string          `json:"id"`
@@ -33,9 +33,9 @@ func init() {
 		Id:   "random_walk",
 		Name: "Random Walk",
 
-		Handler: func(query *tsdb.Query, context *tsdb.QueryContext) *tsdb.QueryResult {
-			timeWalkerMs := context.TimeRange.GetFromAsMsEpoch()
-			to := context.TimeRange.GetToAsMsEpoch()
+		Handler: func(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery) *tsdb.QueryResult {
+			timeWalkerMs := tsdbQuery.TimeRange.GetFromAsMsEpoch()
+			to := tsdbQuery.TimeRange.GetToAsMsEpoch()
 
 			series := newSeriesForQuery(query)
 
@@ -60,7 +60,7 @@ func init() {
 	registerScenario(&Scenario{
 		Id:   "no_data_points",
 		Name: "No Data Points",
-		Handler: func(query *tsdb.Query, context *tsdb.QueryContext) *tsdb.QueryResult {
+		Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
 			return tsdb.NewQueryResult()
 		},
 	})
@@ -68,7 +68,7 @@ func init() {
 	registerScenario(&Scenario{
 		Id:   "datapoints_outside_range",
 		Name: "Datapoints Outside Range",
-		Handler: func(query *tsdb.Query, context *tsdb.QueryContext) *tsdb.QueryResult {
+		Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
 			queryRes := tsdb.NewQueryResult()
 
 			series := newSeriesForQuery(query)
@@ -85,7 +85,7 @@ func init() {
 		Id:          "csv_metric_values",
 		Name:        "CSV Metric Values",
 		StringInput: "1,20,90,30,5,0",
-		Handler: func(query *tsdb.Query, context *tsdb.QueryContext) *tsdb.QueryResult {
+		Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
 			queryRes := tsdb.NewQueryResult()
 
 			stringInput := query.Model.Get("stringInput").MustString()

+ 5 - 5
pkg/tsdb/testdata/testdata.go

@@ -13,7 +13,7 @@ type TestDataExecutor struct {
 	log log.Logger
 }
 
-func NewTestDataExecutor(dsInfo *models.DataSource) (tsdb.Executor, error) {
+func NewTestDataExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
 	return &TestDataExecutor{
 		DataSource: dsInfo,
 		log:        log.New("tsdb.testdata"),
@@ -21,17 +21,17 @@ func NewTestDataExecutor(dsInfo *models.DataSource) (tsdb.Executor, error) {
 }
 
 func init() {
-	tsdb.RegisterExecutor("grafana-testdata-datasource", NewTestDataExecutor)
+	tsdb.RegisterTsdbQueryEndpoint("grafana-testdata-datasource", NewTestDataExecutor)
 }
 
-func (e *TestDataExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
+func (e *TestDataExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
 	result := &tsdb.BatchResult{}
 	result.QueryResults = make(map[string]*tsdb.QueryResult)
 
-	for _, query := range queries {
+	for _, query := range tsdbQuery.Queries {
 		scenarioId := query.Model.Get("scenarioId").MustString("random_walk")
 		if scenario, exist := ScenarioRegistry[scenarioId]; exist {
-			result.QueryResults[query.RefId] = scenario.Handler(query, context)
+			result.QueryResults[query.RefId] = scenario.Handler(query, tsdbQuery)
 			result.QueryResults[query.RefId].RefId = query.RefId
 		} else {
 			e.log.Error("Scenario not found", "scenarioId", scenarioId)

+ 7 - 108
pkg/tsdb/tsdb_test.go

@@ -3,60 +3,15 @@ package tsdb
 import (
 	"context"
 	"testing"
-	"time"
 
 	"github.com/grafana/grafana/pkg/models"
 	. "github.com/smartystreets/goconvey/convey"
 )
 
 func TestMetricQuery(t *testing.T) {
-
-	Convey("When batches groups for query", t, func() {
-
-		Convey("Given 3 queries for 2 data sources", func() {
-			request := &Request{
-				Queries: QuerySlice{
-					{RefId: "A", DataSource: &models.DataSource{Id: 1}},
-					{RefId: "B", DataSource: &models.DataSource{Id: 1}},
-					{RefId: "C", DataSource: &models.DataSource{Id: 2}},
-				},
-			}
-
-			batches, err := getBatches(request)
-			So(err, ShouldBeNil)
-
-			Convey("Should group into two batches", func() {
-				So(len(batches), ShouldEqual, 2)
-			})
-		})
-
-		Convey("Given query 2 depends on query 1", func() {
-			request := &Request{
-				Queries: QuerySlice{
-					{RefId: "A", DataSource: &models.DataSource{Id: 1}},
-					{RefId: "B", DataSource: &models.DataSource{Id: 2}},
-					{RefId: "C", DataSource: &models.DataSource{Id: 3}, Depends: []string{"A", "B"}},
-				},
-			}
-
-			batches, err := getBatches(request)
-			So(err, ShouldBeNil)
-
-			Convey("Should return three batch groups", func() {
-				So(len(batches), ShouldEqual, 3)
-			})
-
-			Convey("Group 3 should have group 1 and 2 as dependencies", func() {
-				So(batches[2].Depends["A"], ShouldEqual, true)
-				So(batches[2].Depends["B"], ShouldEqual, true)
-			})
-
-		})
-	})
-
 	Convey("When executing request with one query", t, func() {
-		req := &Request{
-			Queries: QuerySlice{
+		req := &TsdbQuery{
+			Queries: []*Query{
 				{RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}},
 			},
 		}
@@ -74,8 +29,8 @@ func TestMetricQuery(t *testing.T) {
 	})
 
 	Convey("When executing one request with two queries from same data source", t, func() {
-		req := &Request{
-			Queries: QuerySlice{
+		req := &TsdbQuery{
+			Queries: []*Query{
 				{RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}},
 				{RefId: "B", DataSource: &models.DataSource{Id: 1, Type: "test"}},
 			},
@@ -99,26 +54,9 @@ func TestMetricQuery(t *testing.T) {
 
 	})
 
-	Convey("When executing one request with three queries from different datasources", t, func() {
-		req := &Request{
-			Queries: QuerySlice{
-				{RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}},
-				{RefId: "B", DataSource: &models.DataSource{Id: 1, Type: "test"}},
-				{RefId: "C", DataSource: &models.DataSource{Id: 2, Type: "test"}},
-			},
-		}
-
-		res, err := HandleRequest(context.TODO(), req)
-		So(err, ShouldBeNil)
-
-		Convey("Should have been batched in two requests", func() {
-			So(len(res.BatchTimings), ShouldEqual, 2)
-		})
-	})
-
 	Convey("When query uses data source of unknown type", t, func() {
-		req := &Request{
-			Queries: QuerySlice{
+		req := &TsdbQuery{
+			Queries: []*Query{
 				{RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "asdasdas"}},
 			},
 		}
@@ -126,50 +64,11 @@ func TestMetricQuery(t *testing.T) {
 		_, err := HandleRequest(context.TODO(), req)
 		So(err, ShouldNotBeNil)
 	})
-
-	Convey("When executing request that depend on other query", t, func() {
-		req := &Request{
-			Queries: QuerySlice{
-				{
-					RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "test"},
-				},
-				{
-					RefId: "B", DataSource: &models.DataSource{Id: 2, Type: "test"}, Depends: []string{"A"},
-				},
-			},
-		}
-
-		fakeExecutor := registerFakeExecutor()
-		fakeExecutor.HandleQuery("A", func(c *QueryContext) *QueryResult {
-			time.Sleep(10 * time.Millisecond)
-			return &QueryResult{
-				Series: TimeSeriesSlice{
-					&TimeSeries{Name: "Ares"},
-				}}
-		})
-		fakeExecutor.HandleQuery("B", func(c *QueryContext) *QueryResult {
-			return &QueryResult{
-				Series: TimeSeriesSlice{
-					&TimeSeries{Name: "Bres+" + c.Results["A"].Series[0].Name},
-				}}
-		})
-
-		res, err := HandleRequest(context.TODO(), req)
-		So(err, ShouldBeNil)
-
-		Convey("Should have been batched in two requests", func() {
-			So(len(res.BatchTimings), ShouldEqual, 2)
-		})
-
-		Convey("Query B should have access to Query A results", func() {
-			So(res.Results["B"].Series[0].Name, ShouldEqual, "Bres+Ares")
-		})
-	})
 }
 
 func registerFakeExecutor() *FakeExecutor {
 	executor, _ := NewFakeExecutor(nil)
-	RegisterExecutor("test", func(dsInfo *models.DataSource) (Executor, error) {
+	RegisterTsdbQueryEndpoint("test", func(dsInfo *models.DataSource) (TsdbQueryEndpoint, error) {
 		return executor, nil
 	})
 

+ 2 - 0
public/app/app.ts

@@ -9,6 +9,7 @@ import 'angular-sanitize';
 import 'angular-dragdrop';
 import 'angular-bindonce';
 import 'angular-ui';
+import 'ngreact';
 
 import $ from 'jquery';
 import angular from 'angular';
@@ -84,6 +85,7 @@ export class GrafanaApp {
       'pasvaz.bindonce',
       'ui.bootstrap',
       'ui.bootstrap.tpls',
+      'react'
     ];
 
     var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];

+ 39 - 0
public/app/core/components/PasswordStrength.tsx

@@ -0,0 +1,39 @@
+import * as React from 'react';
+import 'react-dom';
+import coreModule from '../core_module';
+
+export interface IProps {
+  password: string;
+}
+
+export class PasswordStrength extends React.Component<IProps, any> {
+
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    let strengthText = "strength: strong like a bull.";
+    let strengthClass = "password-strength-good";
+
+    if (this.props.password.length < 4) {
+      strengthText = "strength: weak sauce.";
+      strengthClass = "password-strength-bad";
+    }
+
+    if (this.props.password.length <= 8) {
+      strengthText = "strength: you can do better.";
+      strengthClass = "password-strength-ok";
+    }
+
+    return (
+      <div className={`password-strength small ${strengthClass}`}>
+        <em>{strengthText}</em>
+      </div>
+    );
+  }
+}
+
+coreModule.directive('passwordStrength', function(reactDirective) {
+  return reactDirective(PasswordStrength, ['password']);
+});

+ 0 - 58
public/app/core/components/collapse_box.ts

@@ -1,58 +0,0 @@
-///<reference path="../../headers/common.d.ts" />
-
-import coreModule from 'app/core/core_module';
-
-const template = `
-<div class="collapse-box">
-  <div class="collapse-box__header">
-    <a class="collapse-box__header-title pointer" ng-click="ctrl.toggle()">
-      <span class="fa fa-fw fa-caret-right" ng-hide="ctrl.isOpen"></span>
-      <span class="fa fa-fw fa-caret-down" ng-hide="!ctrl.isOpen"></span>
-      {{ctrl.title}}
-    </a>
-    <div class="collapse-box__header-actions" ng-transclude="actions" ng-if="ctrl.isOpen"></div>
-  </div>
-  <div class="collapse-box__body" ng-transclude="body" ng-if="ctrl.isOpen">
-  </div>
-</div>
-`;
-
-export class CollapseBoxCtrl {
-  isOpen: boolean;
-  stateChanged: () => void;
-
-  /** @ngInject **/
-  constructor(private $timeout) {
-    this.isOpen = false;
-  }
-
-  toggle() {
-    this.isOpen = !this.isOpen;
-    this.$timeout(() => {
-      this.stateChanged();
-    });
-  }
-}
-
-export function collapseBox() {
-  return {
-    restrict: 'E',
-    template: template,
-    controller: CollapseBoxCtrl,
-    bindToController: true,
-    controllerAs: 'ctrl',
-    scope: {
-      "title": "@",
-      "isOpen": "=?",
-      "stateChanged": "&"
-    },
-    transclude: {
-      'actions': '?collapseBoxActions',
-      'body': 'collapseBoxBody',
-    },
-    link: function(scope, elem, attrs) {
-    }
-  };
-}
-
-coreModule.directive('collapseBox', collapseBox);

+ 1 - 4
public/app/core/components/colorpicker.ts

@@ -1,8 +1,5 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import config from 'app/core/config';
-import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from 'app/core/core_module';
 
 var template = `
@@ -37,7 +34,7 @@ export class ColorPickerCtrl {
   showAxisControls: boolean;
 
   /** @ngInject */
-  constructor(private $scope, private $rootScope) {
+  constructor(private $scope, $rootScope) {
     this.colors = $rootScope.colors;
     this.autoClose = $scope.autoClose;
     this.series = $scope.series;

+ 0 - 3
public/app/core/components/dashboard_selector.ts

@@ -1,8 +1,5 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import config from 'app/core/config';
-import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from 'app/core/core_module';
 
 var template = `

+ 0 - 1
public/app/core/components/form_dropdown/form_dropdown.ts

@@ -1,6 +1,5 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import config from 'app/core/config';
 import _ from 'lodash';
 import $ from 'jquery';
 import coreModule from '../../core_module';

+ 0 - 2
public/app/core/components/grafana_app.ts

@@ -1,9 +1,7 @@
 ///<reference path="../../headers/common.d.ts" />
 
 import config from 'app/core/config';
-import store from 'app/core/store';
 import _ from 'lodash';
-import angular from 'angular';
 import $ from 'jquery';
 
 import coreModule from 'app/core/core_module';

+ 1 - 1
public/app/core/components/help/help.ts

@@ -8,7 +8,7 @@ export class HelpCtrl {
   shortcuts: any;
 
   /** @ngInject */
-  constructor(private $scope, $sce) {
+  constructor() {
     this.tabIndex = 0;
     this.shortcuts = {
       'Global': [

+ 0 - 1
public/app/core/components/info_popover.ts

@@ -1,7 +1,6 @@
 ///<reference path="../../headers/common.d.ts" />
 
 import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from 'app/core/core_module';
 import Drop from 'tether-drop';
 

+ 1 - 1
public/app/core/components/json_explorer/helpers.ts

@@ -60,7 +60,7 @@ export function getValuePreview (object: Object, value: string): string {
   if (type === 'string') {
     value = '"' + escapeString(value) + '"';
   }
-  if (type === 'function'){
+  if (type === 'function') {
 
     // Remove content of the function
     return object.toString()

+ 1 - 2
public/app/core/components/json_explorer/json_explorer.ts

@@ -6,7 +6,6 @@ import {
   getObjectName,
   getType,
   getValuePreview,
-  getPreview,
   cssClass,
   createElement
 } from './helpers';
@@ -191,7 +190,7 @@ export class JsonExplorer {
     if (this.element) {
       if (this.isOpen) {
         this.appendChildren(this.config.animateOpen);
-      } else{
+      } else {
         this.removeChildren(this.config.animateClose);
       }
       this.element.classList.toggle(cssClass('open'));

+ 0 - 3
public/app/core/components/layout_selector/layout_selector.ts

@@ -1,9 +1,6 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import config from 'app/core/config';
 import store from 'app/core/store';
-import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from 'app/core/core_module';
 
 var template = `

+ 2 - 5
public/app/core/components/navbar/navbar.ts

@@ -1,16 +1,13 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import config from 'app/core/config';
-import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from '../../core_module';
-import {NavModel, NavModelItem}  from '../../nav_model_srv';
+import {NavModel}  from '../../nav_model_srv';
 
 export class NavbarCtrl {
   model: NavModel;
 
   /** @ngInject */
-  constructor(private $scope, private $rootScope, private contextSrv) {
+  constructor(private $rootScope) {
   }
 
   showSearch() {

+ 0 - 1
public/app/core/components/org_switcher.ts

@@ -1,7 +1,6 @@
 ///<reference path="../../headers/common.d.ts" />
 
 import coreModule from 'app/core/core_module';
-import config from 'app/core/config';
 import {contextSrv} from 'app/core/services/context_srv';
 
 const template = `

+ 1 - 1
public/app/core/components/row_ctrl.ts

@@ -30,7 +30,7 @@ export class DashboardRowCtrl {
   dashboard: any;
   panel: any;
 
-  constructor(private $rootScope) {
+  constructor() {
     this.panel.hiddenPanels = this.panel.hiddenPanels || [];
   }
 

+ 0 - 1
public/app/core/components/scroll/scroll.ts

@@ -2,7 +2,6 @@
 
 import GeminiScrollbar from 'gemini-scrollbar';
 import coreModule from 'app/core/core_module';
-import _ from 'lodash';
 
 export function geminiScrollbar() {
   return {

+ 1 - 5
public/app/core/components/search/search.ts

@@ -1,11 +1,7 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import angular from 'angular';
-import config from 'app/core/config';
 import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from '../../core_module';
-import appEvents from 'app/core/app_events';
 
 export class SearchCtrl {
   isOpen: boolean;
@@ -22,7 +18,7 @@ export class SearchCtrl {
   openCompleted: boolean;
 
   /** @ngInject */
-  constructor(private $scope, private $location, private $timeout, private backendSrv, private contextSrv, private $rootScope) {
+  constructor($scope, private $location, private $timeout, private backendSrv, public contextSrv, $rootScope) {
     $rootScope.onAppEvent('show-dash-search', this.openSearch.bind(this), $scope);
     $rootScope.onAppEvent('hide-dash-search', this.closeSearch.bind(this), $scope);
   }

+ 1 - 1
public/app/core/components/sidemenu/sidemenu.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import config from 'app/core/config';
 import _ from 'lodash';
+import config from 'app/core/config';
 import $ from 'jquery';
 import coreModule from '../../core_module';
 

+ 0 - 4
public/app/core/components/switch.ts

@@ -1,10 +1,6 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import config from 'app/core/config';
-import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from 'app/core/core_module';
-import Drop from 'tether-drop';
 
 var template = `
 <label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer" ng-show="ctrl.label">

+ 1 - 2
public/app/core/components/user_group_picker.ts

@@ -1,5 +1,4 @@
 import coreModule from 'app/core/core_module';
-import appEvents from 'app/core/app_events';
 import _ from 'lodash';
 
 const template = `
@@ -17,7 +16,7 @@ export class UserGroupPickerCtrl {
   debouncedSearchGroups: any;
 
   /** @ngInject */
-  constructor(private backendSrv, private $scope, $sce, private uiSegmentSrv) {
+  constructor(private backendSrv) {
     this.debouncedSearchGroups = _.debounce(this.searchGroups, 500, {'leading': true, 'trailing': false});
     this.reset();
   }

+ 1 - 2
public/app/core/components/user_picker.ts

@@ -1,5 +1,4 @@
 import coreModule from 'app/core/core_module';
-import appEvents from 'app/core/app_events';
 import _ from 'lodash';
 
 const template = `
@@ -17,7 +16,7 @@ export class UserPickerCtrl {
   userPicked: any;
 
   /** @ngInject */
-  constructor(private backendSrv, private $scope, $sce) {
+  constructor(private backendSrv) {
     this.reset();
     this.debouncedSearchUsers = _.debounce(this.searchUsers, 500, {'leading': true, 'trailing': false});
   }

+ 0 - 32
public/app/core/components/wizard/wizard.html

@@ -1,32 +0,0 @@
-<div class="modal-body">
-	<div class="modal-header">
-		<h2 class="modal-header-title">
-			<i class="fa fa-cog fa-spin"></i>
-			<span class="p-l-1">{{model.name}}</span>
-		</h2>
-
-		<a class="modal-header-close" ng-click="dismiss();">
-			<i class="fa fa-remove"></i>
-		</a>
-	</div>
-
-	<div class="modal-content">
-    <div ng-if="activeStep">
-
-    </div>
-
-		<!-- <table class="filter&#45;table"> -->
-		<!-- 	<tbody> -->
-		<!-- 		<tr ng&#45;repeat="step in model.steps"> -->
-		<!-- 			<td>{{step.name}}</td> -->
-		<!-- 			<td>{{step.status}}</td> -->
-		<!-- 			<td width="1%"> -->
-		<!-- 				<i class="fa fa&#45;check" style="color: #39A039"></i> -->
-		<!-- 			</td> -->
-		<!-- 		</tr> -->
-		<!-- 	</tbody> -->
-		<!-- </table> -->
-	</div>
-
-</div>
-

+ 0 - 73
public/app/core/components/wizard/wizard.ts

@@ -1,73 +0,0 @@
-///<reference path="../../../headers/common.d.ts" />
-
-import config from 'app/core/config';
-import _ from 'lodash';
-import $ from 'jquery';
-
-import coreModule from 'app/core/core_module';
-import appEvents from 'app/core/app_events';
-
-export class WizardSrv {
-  /** @ngInject */
-  constructor() {
-  }
-}
-
-export interface WizardStep {
-  name: string;
-  type: string;
-  process: any;
-}
-
-export class SelectOptionStep {
-  type: string;
-  name: string;
-  fulfill: any;
-
-  constructor() {
-    this.type = 'select';
-  }
-
-  process() {
-    return new Promise((fulfill, reject) => {
-
-    });
-  }
-}
-
-export class WizardFlow {
-  name: string;
-  steps: WizardStep[];
-
-  constructor(name) {
-    this.name = name;
-    this.steps = [];
-  }
-
-  addStep(step) {
-    this.steps.push(step);
-  }
-
-  next(index) {
-    var step = this.steps[0];
-
-    return step.process().then(() => {
-      if (this.steps.length === index+1) {
-        return;
-      }
-
-      return this.next(index+1);
-    });
-  }
-
-  start() {
-    appEvents.emit('show-modal', {
-      src: 'public/app/core/components/wizard/wizard.html',
-      model: this
-    });
-
-    return this.next(0);
-  }
-}
-
-coreModule.service('wizardSrv', WizardSrv);

+ 3 - 3
public/app/core/controllers/signup_ctrl.ts

@@ -8,9 +8,9 @@ export class SignUpCtrl {
   /** @ngInject */
   constructor(
       private $scope: any,
-      private $location: any,
-      private contextSrv: any,
-      private backendSrv: any) {
+      private backendSrv: any,
+      $location: any,
+      contextSrv: any) {
 
     contextSrv.sidemenu = false;
     $scope.ctrl = this;

+ 2 - 4
public/app/core/core.ts

@@ -35,7 +35,6 @@ import {layoutSelector} from './components/layout_selector/layout_selector';
 import {switchDirective} from './components/switch';
 import {dashboardSelector} from './components/dashboard_selector';
 import {queryPartEditorDirective} from './components/query_part/query_part_editor';
-import {WizardFlow} from './components/wizard/wizard';
 import {formDropdownDirective} from './components/form_dropdown/form_dropdown';
 import 'app/core/controllers/all';
 import 'app/core/services/all';
@@ -48,7 +47,7 @@ import {assignModelProperties} from './utils/model_utils';
 import {contextSrv} from './services/context_srv';
 import {KeybindingSrv} from './services/keybindingSrv';
 import {helpModal} from './components/help/help';
-import {collapseBox} from './components/collapse_box';
+import {PasswordStrength} from './components/PasswordStrength';
 import {JsonExplorer} from './components/json_explorer/json_explorer';
 import {NavModelSrv, NavModel} from './nav_model_srv';
 import {userPicker} from './components/user_picker';
@@ -73,14 +72,12 @@ export {
   appEvents,
   dashboardSelector,
   queryPartEditorDirective,
-  WizardFlow,
   colors,
   formDropdownDirective,
   assignModelProperties,
   contextSrv,
   KeybindingSrv,
   helpModal,
-  collapseBox,
   JsonExplorer,
   NavModelSrv,
   NavModel,
@@ -89,4 +86,5 @@ export {
   geminiScrollbar,
   gfPageDirective,
   orgSwitcher,
+  PasswordStrength,
 };

+ 1 - 1
public/app/core/directives/diff-view.ts

@@ -7,7 +7,7 @@ export class DeltaCtrl {
   observer: any;
 
   /** @ngInject */
-  constructor(private $rootScope) {
+  constructor($rootScope) {
     const waitForCompile = function(mutations) {
       if (mutations.length === 1) {
         this.$rootScope.appEvent('json-diff-ready');

+ 1 - 1
public/app/core/directives/password_strength.js

@@ -4,7 +4,7 @@ define([
 function (coreModule) {
   'use strict';
 
-  coreModule.default.directive('passwordStrength', function() {
+  coreModule.default.directive('passwordStrength2', function() {
     var template = '<div class="password-strength small" ng-if="!loginMode" ng-class="strengthClass">' +
       '<em>{{strengthText}}</em>' +
       '</div>';

+ 0 - 2
public/app/core/directives/rebuild_on_change.ts

@@ -1,7 +1,5 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import angular from 'angular';
-import _ from 'lodash';
 import $ from 'jquery';
 
 import coreModule from '../core_module';

+ 0 - 1
public/app/core/live/live_srv.ts

@@ -2,7 +2,6 @@
 
 import _ from 'lodash';
 import config from 'app/core/config';
-import coreModule from 'app/core/core_module';
 
 import {Observable} from 'vendor/npm/rxjs/Observable';
 

+ 0 - 1
public/app/core/profiler.ts

@@ -1,7 +1,6 @@
 ///<reference path="../headers/common.d.ts" />
 
 import $ from 'jquery';
-import _ from 'lodash';
 import angular from 'angular';
 
 export class Profiler {

+ 0 - 1
public/app/core/routes/routes.ts

@@ -2,7 +2,6 @@
 
 import './dashboard_loaders';
 
-import angular from 'angular';
 import coreModule from 'app/core/core_module';
 import {BundleLoader} from './bundle_loader';
 

+ 1 - 2
public/app/core/services/alert_srv.ts

@@ -2,7 +2,6 @@
 
 import angular from 'angular';
 import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from 'app/core/core_module';
 import appEvents from 'app/core/app_events';
 
@@ -10,7 +9,7 @@ export class AlertSrv {
   list: any[];
 
   /** @ngInject */
-  constructor(private $timeout, private $sce, private $rootScope, private $modal) {
+  constructor(private $timeout, private $rootScope, private $modal) {
     this.list = [];
   }
 

+ 1 - 3
public/app/core/services/backend_srv.ts

@@ -1,8 +1,6 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import angular from 'angular';
 import _ from 'lodash';
-import config from 'app/core/config';
 import coreModule from 'app/core/core_module';
 import appEvents from 'app/core/app_events';
 
@@ -12,7 +10,7 @@ export class BackendSrv {
   private noBackendCache: boolean;
 
   /** @ngInject */
-  constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout, private contextSrv) {
+  constructor(private $http, private alertSrv, private $q, private $timeout, private contextSrv) {
   }
 
   get(url, params?) {

+ 1 - 1
public/app/core/services/dynamic_directive_srv.ts

@@ -6,7 +6,7 @@ import coreModule from '../core_module';
 class DynamicDirectiveSrv {
 
   /** @ngInject */
-  constructor(private $compile, private $parse, private $rootScope) {}
+  constructor(private $compile, private $rootScope) {}
 
   addDirective(element, name, scope) {
     var child = angular.element(document.createElement(name));

+ 1 - 4
public/app/core/services/keybindingSrv.ts

@@ -14,10 +14,7 @@ export class KeybindingSrv {
   /** @ngInject */
   constructor(
     private $rootScope,
-    private $modal,
-    private $location,
-    private contextSrv,
-    private $timeout) {
+    private $location) {
 
     // clear out all shortcuts on route change
     $rootScope.$on('$routeChangeSuccess', () => {

+ 0 - 2
public/app/core/services/popover_srv.ts

@@ -1,8 +1,6 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import config from 'app/core/config';
 import _ from 'lodash';
-import $ from 'jquery';
 import coreModule from 'app/core/core_module';
 import Drop from 'tether-drop';
 

+ 0 - 4
public/app/core/services/util_srv.ts

@@ -1,9 +1,5 @@
 ///<reference path="../../headers/common.d.ts" />
 
-import config from 'app/core/config';
-import _ from 'lodash';
-import $ from 'jquery';
-
 import coreModule from 'app/core/core_module';
 import appEvents from 'app/core/app_events';
 

+ 14 - 0
public/app/core/specs/PasswordStrength_specs.tsx

@@ -0,0 +1,14 @@
+// import React from 'react';
+// import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
+// import {shallow} from 'enzyme';
+//
+// import {PasswordStrength} from '../components/PasswordStrength';
+//
+// describe('PasswordStrength', () => {
+//
+//   it.skip('should have class bad if length below 4', () => {
+//     const wrapper = shallow(<PasswordStrength password="asd" />);
+//     expect(wrapper.find(".password-strength-bad")).to.have.length(3);
+//   });
+// });
+//

+ 4 - 3
public/app/core/time_series2.ts

@@ -168,15 +168,16 @@ export default class TimeSeries {
         if (currentValue < this.stats.min) {
           this.stats.min = currentValue;
         }
-        if (this.stats.first === null){
+
+        if (this.stats.first === null) {
           this.stats.first = currentValue;
-        }else{
+        } else {
           if (previousValue > currentValue) {   // counter reset
             previousDeltaUp = false;
             if (i === this.datapoints.length-1) {  // reset on last
                 this.stats.delta += currentValue;
             }
-          }else{
+          } else {
             if (previousDeltaUp) {
               this.stats.delta += currentValue - previousValue;    // normal increment
             } else {

+ 0 - 6
public/app/core/utils/emitter.ts

@@ -2,12 +2,6 @@
 
 import EventEmitter from 'eventemitter3';
 
-var hasOwnProp = {}.hasOwnProperty;
-
-function createName(name) {
-    return '$' + name;
-}
-
 export class Emitter {
   emitter: any;
 

部分文件因为文件数量过多而无法显示