Quellcode durchsuchen

Merge remote-tracking branch 'grafana/master' into influx-db-query2

ryan vor 8 Jahren
Ursprung
Commit
8c5972dc55

+ 1 - 0
CHANGELOG.md

@@ -13,6 +13,7 @@
 ## Minor Enchancements
 ## Minor Enchancements
 
 
 * **Prometheus**: Make Prometheus query field a textarea [#7663](https://github.com/grafana/grafana/issues/7663), thx [@hagen1778](https://github.com/hagen1778)
 * **Prometheus**: Make Prometheus query field a textarea [#7663](https://github.com/grafana/grafana/issues/7663), thx [@hagen1778](https://github.com/hagen1778)
+* **Prometheus**: Step parameter changed semantics to min step to reduce the load on Prometheus and rendering in browser [#8073](https://github.com/grafana/grafana/pull/8073), thx [@bobrik](https://github.com/bobrik)
 * **Templating**: Should not be possible to create self-referencing (recursive) template variable definitions [#7614](https://github.com/grafana/grafana/issues/7614) thx [@thuck](https://github.com/thuck)
 * **Templating**: Should not be possible to create self-referencing (recursive) template variable definitions [#7614](https://github.com/grafana/grafana/issues/7614) thx [@thuck](https://github.com/thuck)
 * **Cloudwatch**: Correctly obtain IAM roles within ECS container tasks [#7892](https://github.com/grafana/grafana/issues/7892) thx [@gomlgs](https://github.com/gomlgs)
 * **Cloudwatch**: Correctly obtain IAM roles within ECS container tasks [#7892](https://github.com/grafana/grafana/issues/7892) thx [@gomlgs](https://github.com/gomlgs)
 * **Units**: New number format: Scientific notation [#7781](https://github.com/grafana/grafana/issues/7781) thx [@cadnce](https://github.com/cadnce)
 * **Units**: New number format: Scientific notation [#7781](https://github.com/grafana/grafana/issues/7781) thx [@cadnce](https://github.com/cadnce)

+ 15 - 15
docs/sources/http_api/alerting.md

@@ -12,8 +12,8 @@ parent = "http_api"
 
 
 # Alerting API
 # Alerting API
 
 
-You can use the Alerting API to get information about alerts and their states but this API cannot be used to modify the alert. 
-To create new alerts or modify them you need to update the dashboard json that contains the alerts. 
+You can use the Alerting API to get information about alerts and their states but this API cannot be used to modify the alert.
+To create new alerts or modify them you need to update the dashboard json that contains the alerts.
 
 
 This API can also be used to create, update and delete alert notifications.
 This API can also be used to create, update and delete alert notifications.
 
 
@@ -115,7 +115,7 @@ This API can also be used to create, update and delete alert notifications.
 
 
     HTTP/1.1 200
     HTTP/1.1 200
     Content-Type: application/json
     Content-Type: application/json
-    
+
     {
     {
       "id": 1,
       "id": 1,
       "name": "Team A",
       "name": "Team A",
@@ -127,11 +127,11 @@ This API can also be used to create, update and delete alert notifications.
 
 
 ## Create alert notification
 ## Create alert notification
 
 
-`POST /api/alerts-notifications`
+`POST /api/alert-notifications`
 
 
 **Example Request**:
 **Example Request**:
 
 
-    POST /api/alerts-notifications HTTP/1.1
+    POST /api/alert-notifications HTTP/1.1
     Accept: application/json
     Accept: application/json
     Content-Type: application/json
     Content-Type: application/json
     Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
     Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
@@ -144,29 +144,29 @@ This API can also be used to create, update and delete alert notifications.
         "addresses": "carl@grafana.com;dev@grafana.com"
         "addresses": "carl@grafana.com;dev@grafana.com"
       }
       }
     }
     }
-    
+
 
 
 **Example Response**:
 **Example Response**:
 
 
     HTTP/1.1 200
     HTTP/1.1 200
     Content-Type: application/json
     Content-Type: application/json
     {
     {
-      "id": 1, 
+      "id": 1,
       "name": "new alert notification",
       "name": "new alert notification",
       "type": "email",
       "type": "email",
       "isDefault": false,
       "isDefault": false,
       "settings": { addresses: "carl@grafana.com;dev@grafana.com"} }
       "settings": { addresses: "carl@grafana.com;dev@grafana.com"} }
-      "created": "2017-01-01 12:34", 
+      "created": "2017-01-01 12:34",
       "updated": "2017-01-01 12:34"
       "updated": "2017-01-01 12:34"
     }
     }
 
 
 ## Update alert notification
 ## Update alert notification
 
 
-`PUT /api/alerts-notifications/1`
+`PUT /api/alert-notifications/1`
 
 
 **Example Request**:
 **Example Request**:
 
 
-    PUT /api/alerts-notifications/1 HTTP/1.1
+    PUT /api/alert-notifications/1 HTTP/1.1
     Accept: application/json
     Accept: application/json
     Content-Type: application/json
     Content-Type: application/json
     Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
     Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
@@ -176,29 +176,29 @@ This API can also be used to create, update and delete alert notifications.
       "name": "new alert notification",  //Required
       "name": "new alert notification",  //Required
       "type":  "email", //Required
       "type":  "email", //Required
       "isDefault": false,
       "isDefault": false,
-      "settings": { 
+      "settings": {
         "addresses: "carl@grafana.com;dev@grafana.com"
         "addresses: "carl@grafana.com;dev@grafana.com"
       }
       }
     }
     }
-    
+
 
 
 **Example Response**:
 **Example Response**:
 
 
     HTTP/1.1 200
     HTTP/1.1 200
     Content-Type: application/json
     Content-Type: application/json
     {
     {
-      "id": 1, 
+      "id": 1,
       "name": "new alert notification",
       "name": "new alert notification",
       "type": "email",
       "type": "email",
       "isDefault": false,
       "isDefault": false,
       "settings": { addresses: "carl@grafana.com;dev@grafana.com"} }
       "settings": { addresses: "carl@grafana.com;dev@grafana.com"} }
-      "created": "2017-01-01 12:34", 
+      "created": "2017-01-01 12:34",
       "updated": "2017-01-01 12:34"
       "updated": "2017-01-01 12:34"
     }
     }
 
 
 ## Delete alert notification
 ## Delete alert notification
 
 
-`DELETE /api/alerts-notifications/:notificationId`
+`DELETE /api/alert-notifications/:notificationId`
 
 
 **Example Request**:
 **Example Request**:
 
 

+ 6 - 6
pkg/log/log.go

@@ -34,7 +34,7 @@ func New(logger string, ctx ...interface{}) Logger {
 func Trace(format string, v ...interface{}) {
 func Trace(format string, v ...interface{}) {
 	var message string
 	var message string
 	if len(v) > 0 {
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 	} else {
 		message = format
 		message = format
 	}
 	}
@@ -45,7 +45,7 @@ func Trace(format string, v ...interface{}) {
 func Debug(format string, v ...interface{}) {
 func Debug(format string, v ...interface{}) {
 	var message string
 	var message string
 	if len(v) > 0 {
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 	} else {
 		message = format
 		message = format
 	}
 	}
@@ -60,7 +60,7 @@ func Debug2(message string, v ...interface{}) {
 func Info(format string, v ...interface{}) {
 func Info(format string, v ...interface{}) {
 	var message string
 	var message string
 	if len(v) > 0 {
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 	} else {
 		message = format
 		message = format
 	}
 	}
@@ -75,7 +75,7 @@ func Info2(message string, v ...interface{}) {
 func Warn(format string, v ...interface{}) {
 func Warn(format string, v ...interface{}) {
 	var message string
 	var message string
 	if len(v) > 0 {
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 	} else {
 		message = format
 		message = format
 	}
 	}
@@ -88,7 +88,7 @@ func Warn2(message string, v ...interface{}) {
 }
 }
 
 
 func Error(skip int, format string, v ...interface{}) {
 func Error(skip int, format string, v ...interface{}) {
-	Root.Error(fmt.Sprintf(format, v))
+	Root.Error(fmt.Sprintf(format, v...))
 }
 }
 
 
 func Error2(message string, v ...interface{}) {
 func Error2(message string, v ...interface{}) {
@@ -96,7 +96,7 @@ func Error2(message string, v ...interface{}) {
 }
 }
 
 
 func Critical(skip int, format string, v ...interface{}) {
 func Critical(skip int, format string, v ...interface{}) {
-	Root.Crit(fmt.Sprintf(format, v))
+	Root.Crit(fmt.Sprintf(format, v...))
 }
 }
 
 
 func Fatal(skip int, format string, v ...interface{}) {
 func Fatal(skip int, format string, v ...interface{}) {

+ 3 - 2
pkg/middleware/auth_proxy.go

@@ -13,7 +13,7 @@ import (
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
 )
 )
 
 
-func initContextWithAuthProxy(ctx *Context) bool {
+func initContextWithAuthProxy(ctx *Context, orgId int64) bool {
 	if !setting.AuthProxyEnabled {
 	if !setting.AuthProxyEnabled {
 		return false
 		return false
 	}
 	}
@@ -30,6 +30,7 @@ func initContextWithAuthProxy(ctx *Context) bool {
 	}
 	}
 
 
 	query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
 	query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
+	query.OrgId = orgId
 	if err := bus.Dispatch(query); err != nil {
 	if err := bus.Dispatch(query); err != nil {
 		if err != m.ErrUserNotFound {
 		if err != m.ErrUserNotFound {
 			ctx.Handle(500, "Failed to find user specified in auth proxy header", err)
 			ctx.Handle(500, "Failed to find user specified in auth proxy header", err)
@@ -46,7 +47,7 @@ func initContextWithAuthProxy(ctx *Context) bool {
 				ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
 				ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
 				return true
 				return true
 			}
 			}
-			query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id}
+			query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id, OrgId: orgId}
 			if err := bus.Dispatch(query); err != nil {
 			if err := bus.Dispatch(query); err != nil {
 				ctx.Handle(500, "Failed find user after creation", err)
 				ctx.Handle(500, "Failed find user after creation", err)
 				return true
 				return true

+ 42 - 35
pkg/middleware/middleware.go

@@ -39,6 +39,12 @@ func GetContextHandler() macaron.Handler {
 			Logger:         log.New("context"),
 			Logger:         log.New("context"),
 		}
 		}
 
 
+		orgId := int64(0)
+		orgIdHeader := ctx.Req.Header.Get("X-Grafana-Org-Id")
+		if orgIdHeader != "" {
+			orgId, _ = strconv.ParseInt(orgIdHeader, 10, 64)
+		}
+
 		// the order in which these are tested are important
 		// the order in which these are tested are important
 		// look for api key in Authorization header first
 		// look for api key in Authorization header first
 		// then init session and look for userId in session
 		// then init session and look for userId in session
@@ -46,9 +52,9 @@ func GetContextHandler() macaron.Handler {
 		// then test if anonymous access is enabled
 		// then test if anonymous access is enabled
 		if initContextWithRenderAuth(ctx) ||
 		if initContextWithRenderAuth(ctx) ||
 			initContextWithApiKey(ctx) ||
 			initContextWithApiKey(ctx) ||
-			initContextWithBasicAuth(ctx) ||
-			initContextWithAuthProxy(ctx) ||
-			initContextWithUserSessionCookie(ctx) ||
+			initContextWithBasicAuth(ctx, orgId) ||
+			initContextWithAuthProxy(ctx, orgId) ||
+			initContextWithUserSessionCookie(ctx, orgId) ||
 			initContextWithAnonymousUser(ctx) {
 			initContextWithAnonymousUser(ctx) {
 		}
 		}
 
 
@@ -68,18 +74,18 @@ func initContextWithAnonymousUser(ctx *Context) bool {
 	if err := bus.Dispatch(&orgQuery); err != nil {
 	if err := bus.Dispatch(&orgQuery); err != nil {
 		log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err)
 		log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err)
 		return false
 		return false
-	} else {
-		ctx.IsSignedIn = false
-		ctx.AllowAnonymous = true
-		ctx.SignedInUser = &m.SignedInUser{}
-		ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
-		ctx.OrgId = orgQuery.Result.Id
-		ctx.OrgName = orgQuery.Result.Name
-		return true
 	}
 	}
+
+	ctx.IsSignedIn = false
+	ctx.AllowAnonymous = true
+	ctx.SignedInUser = &m.SignedInUser{}
+	ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
+	ctx.OrgId = orgQuery.Result.Id
+	ctx.OrgName = orgQuery.Result.Name
+	return true
 }
 }
 
 
-func initContextWithUserSessionCookie(ctx *Context) bool {
+func initContextWithUserSessionCookie(ctx *Context, orgId int64) bool {
 	// initialize session
 	// initialize session
 	if err := ctx.Session.Start(ctx); err != nil {
 	if err := ctx.Session.Start(ctx); err != nil {
 		ctx.Logger.Error("Failed to start session", "error", err)
 		ctx.Logger.Error("Failed to start session", "error", err)
@@ -91,15 +97,15 @@ func initContextWithUserSessionCookie(ctx *Context) bool {
 		return false
 		return false
 	}
 	}
 
 
-	query := m.GetSignedInUserQuery{UserId: userId}
+	query := m.GetSignedInUserQuery{UserId: userId, OrgId: orgId}
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
 		ctx.Logger.Error("Failed to get user with id", "userId", userId)
 		ctx.Logger.Error("Failed to get user with id", "userId", userId)
 		return false
 		return false
-	} else {
-		ctx.SignedInUser = query.Result
-		ctx.IsSignedIn = true
-		return true
 	}
 	}
+
+	ctx.SignedInUser = query.Result
+	ctx.IsSignedIn = true
+	return true
 }
 }
 
 
 func initContextWithApiKey(ctx *Context) bool {
 func initContextWithApiKey(ctx *Context) bool {
@@ -114,30 +120,31 @@ func initContextWithApiKey(ctx *Context) bool {
 		ctx.JsonApiErr(401, "Invalid API key", err)
 		ctx.JsonApiErr(401, "Invalid API key", err)
 		return true
 		return true
 	}
 	}
+
 	// fetch key
 	// fetch key
 	keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
 	keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
 	if err := bus.Dispatch(&keyQuery); err != nil {
 	if err := bus.Dispatch(&keyQuery); err != nil {
 		ctx.JsonApiErr(401, "Invalid API key", err)
 		ctx.JsonApiErr(401, "Invalid API key", err)
 		return true
 		return true
-	} else {
-		apikey := keyQuery.Result
+	}
 
 
-		// validate api key
-		if !apikeygen.IsValid(decoded, apikey.Key) {
-			ctx.JsonApiErr(401, "Invalid API key", err)
-			return true
-		}
+	apikey := keyQuery.Result
 
 
-		ctx.IsSignedIn = true
-		ctx.SignedInUser = &m.SignedInUser{}
-		ctx.OrgRole = apikey.Role
-		ctx.ApiKeyId = apikey.Id
-		ctx.OrgId = apikey.OrgId
+	// validate api key
+	if !apikeygen.IsValid(decoded, apikey.Key) {
+		ctx.JsonApiErr(401, "Invalid API key", err)
 		return true
 		return true
 	}
 	}
+
+	ctx.IsSignedIn = true
+	ctx.SignedInUser = &m.SignedInUser{}
+	ctx.OrgRole = apikey.Role
+	ctx.ApiKeyId = apikey.Id
+	ctx.OrgId = apikey.OrgId
+	return true
 }
 }
 
 
-func initContextWithBasicAuth(ctx *Context) bool {
+func initContextWithBasicAuth(ctx *Context, orgId int64) bool {
 
 
 	if !setting.BasicAuthEnabled {
 	if !setting.BasicAuthEnabled {
 		return false
 		return false
@@ -168,15 +175,15 @@ func initContextWithBasicAuth(ctx *Context) bool {
 		return true
 		return true
 	}
 	}
 
 
-	query := m.GetSignedInUserQuery{UserId: user.Id}
+	query := m.GetSignedInUserQuery{UserId: user.Id, OrgId: orgId}
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
 		ctx.JsonApiErr(401, "Authentication error", err)
 		ctx.JsonApiErr(401, "Authentication error", err)
 		return true
 		return true
-	} else {
-		ctx.SignedInUser = query.Result
-		ctx.IsSignedIn = true
-		return true
 	}
 	}
+
+	ctx.SignedInUser = query.Result
+	ctx.IsSignedIn = true
+	return true
 }
 }
 
 
 // Handle handles and logs error by given status.
 // Handle handles and logs error by given status.

+ 1 - 0
pkg/models/user.go

@@ -117,6 +117,7 @@ type GetSignedInUserQuery struct {
 	UserId int64
 	UserId int64
 	Login  string
 	Login  string
 	Email  string
 	Email  string
+	OrgId  int64
 	Result *SignedInUser
 	Result *SignedInUser
 }
 }
 
 

+ 19 - 13
pkg/services/sqlstore/user.go

@@ -1,6 +1,7 @@
 package sqlstore
 package sqlstore
 
 
 import (
 import (
+	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -273,7 +274,7 @@ func SetUsingOrg(cmd *m.SetUsingOrgCommand) error {
 	}
 	}
 
 
 	if !valid {
 	if !valid {
-		return fmt.Errorf("user does not belong ot org")
+		return fmt.Errorf("user does not belong to org")
 	}
 	}
 
 
 	return inTransaction(func(sess *xorm.Session) error {
 	return inTransaction(func(sess *xorm.Session) error {
@@ -319,19 +320,24 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error {
 }
 }
 
 
 func GetSignedInUser(query *m.GetSignedInUserQuery) error {
 func GetSignedInUser(query *m.GetSignedInUserQuery) error {
+	orgId := "u.org_id"
+	if query.OrgId > 0 {
+		orgId = strconv.FormatInt(query.OrgId, 10)
+	}
+
 	var rawSql = `SELECT
 	var rawSql = `SELECT
-	                u.id           as user_id,
-	                u.is_admin     as is_grafana_admin,
-	                u.email        as email,
-	                u.login        as login,
-									u.name         as name,
-									u.help_flags1  as help_flags1,
-	                org.name       as org_name,
-	                org_user.role  as org_role,
-	                org.id         as org_id
-	                FROM ` + dialect.Quote("user") + ` as u
-									LEFT OUTER JOIN org_user on org_user.org_id = u.org_id and org_user.user_id = u.id
-	                LEFT OUTER JOIN org on org.id = u.org_id `
+		u.id           as user_id,
+		u.is_admin     as is_grafana_admin,
+		u.email        as email,
+		u.login        as login,
+		u.name         as name,
+		u.help_flags1  as help_flags1,
+		org.name       as org_name,
+		org_user.role  as org_role,
+		org.id         as org_id
+		FROM ` + dialect.Quote("user") + ` as u
+		LEFT OUTER JOIN org_user on org_user.org_id = ` + orgId + ` and org_user.user_id = u.id
+		LEFT OUTER JOIN org on org.id = org_user.org_id `
 
 
 	sess := x.Table("user")
 	sess := x.Table("user")
 	if query.UserId > 0 {
 	if query.UserId > 0 {

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

@@ -40,7 +40,14 @@ export function infoPopover() {
           openOn: openOn,
           openOn: openOn,
           hoverOpenDelay: 400,
           hoverOpenDelay: 400,
           tetherOptions: {
           tetherOptions: {
-            offset: offset
+            offset: offset,
+            constraints: [
+                {
+                  to: 'window',
+                  attachment: 'together',
+                  pin: true
+                }
+              ],
           }
           }
         });
         });
 
 

+ 26 - 13
public/app/core/services/backend_srv.ts

@@ -9,8 +9,8 @@ export class BackendSrv {
   inFlightRequests = {};
   inFlightRequests = {};
   HTTP_REQUEST_CANCELLED = -1;
   HTTP_REQUEST_CANCELLED = -1;
 
 
-    /** @ngInject */
-  constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout) {
+  /** @ngInject */
+  constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout, private contextSrv) {
   }
   }
 
 
   get(url, params?) {
   get(url, params?) {
@@ -63,12 +63,18 @@ export class BackendSrv {
 
 
   request(options) {
   request(options) {
     options.retry = options.retry || 0;
     options.retry = options.retry || 0;
-    var requestIsLocal = options.url.indexOf('/') === 0;
+    var requestIsLocal = !options.url.match(/^http/);
     var firstAttempt = options.retry === 0;
     var firstAttempt = options.retry === 0;
 
 
-    if (requestIsLocal && !options.hasSubUrl) {
-      options.url = config.appSubUrl + options.url;
-      options.hasSubUrl = true;
+    if (requestIsLocal) {
+      if (this.contextSrv.user && this.contextSrv.user.orgId) {
+        options.headers = options.headers || {};
+        options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
+      }
+
+      if (options.url.indexOf("/") === 0) {
+        options.url = options.url.substring(1);
+      }
     }
     }
 
 
     return this.$http(options).then(results => {
     return this.$http(options).then(results => {
@@ -125,16 +131,23 @@ export class BackendSrv {
       this.addCanceler(requestId, canceler);
       this.addCanceler(requestId, canceler);
     }
     }
 
 
-    var requestIsLocal = options.url.indexOf('/') === 0;
+    var requestIsLocal = !options.url.match(/^http/);
     var firstAttempt = options.retry === 0;
     var firstAttempt = options.retry === 0;
 
 
-    if (requestIsLocal && !options.hasSubUrl && options.retry === 0) {
-      options.url = config.appSubUrl + options.url;
-    }
+    if (requestIsLocal) {
+      if (this.contextSrv.user && this.contextSrv.user.orgId) {
+        options.headers = options.headers || {};
+        options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
+      }
+
+      if (options.url.indexOf("/") === 0) {
+        options.url = options.url.substring(1);
+      }
 
 
-    if (requestIsLocal && options.headers && options.headers.Authorization) {
-      options.headers['X-DS-Authorization'] = options.headers.Authorization;
-      delete options.headers.Authorization;
+      if (options.headers && options.headers.Authorization) {
+        options.headers['X-DS-Authorization'] = options.headers.Authorization;
+        delete options.headers.Authorization;
+      }
     }
     }
 
 
     return this.$http(options).catch(err => {
     return this.$http(options).catch(err => {

+ 1 - 1
public/app/plugins/datasource/influxdb/partials/query.options.html

@@ -54,7 +54,7 @@
 		<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
 		<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
 			<h5>Stacking and fill</h5>
 			<h5>Stacking and fill</h5>
 			<ul>
 			<ul>
-				<li>When stacking is enabled it important that points align</li>
+				<li>When stacking is enabled it is important that points align</li>
 				<li>If there are missing points for one series it can cause gaps or missing bars</li>
 				<li>If there are missing points for one series it can cause gaps or missing bars</li>
 				<li>You must use fill(0), and select a group by time low limit</li>
 				<li>You must use fill(0), and select a group by time low limit</li>
 				<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
 				<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>

+ 9 - 5
public/app/plugins/datasource/prometheus/datasource.ts

@@ -89,7 +89,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
       var intervalFactor = target.intervalFactor || 1;
       var intervalFactor = target.intervalFactor || 1;
       target.step = query.step = this.calculateInterval(interval, intervalFactor);
       target.step = query.step = this.calculateInterval(interval, intervalFactor);
       var range = Math.ceil(end - start);
       var range = Math.ceil(end - start);
-      target.step = query.step = this.adjustStep(query.step, range);
+      target.step = query.step = this.adjustStep(query.step, this.intervalSeconds(options.interval), range);
       queries.push(query);
       queries.push(query);
     });
     });
 
 
@@ -122,13 +122,13 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
     });
     });
   };
   };
 
 
-  this.adjustStep = function(step, range) {
+  this.adjustStep = function(step, autoStep, range) {
     // Prometheus drop query if range/step > 11000
     // Prometheus drop query if range/step > 11000
     // calibrate step if it is too big
     // calibrate step if it is too big
     if (step !== 0 && range / step > 11000) {
     if (step !== 0 && range / step > 11000) {
       return Math.ceil(range / 11000);
       return Math.ceil(range / 11000);
     }
     }
-    return step;
+    return Math.max(step, autoStep);
   };
   };
 
 
   this.performTimeSeriesQuery = function(query, start, end) {
   this.performTimeSeriesQuery = function(query, start, end) {
@@ -189,7 +189,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
     var end = this.getPrometheusTime(options.range.to, true);
     var end = this.getPrometheusTime(options.range.to, true);
     var query = {
     var query = {
       expr: interpolated,
       expr: interpolated,
-      step: this.adjustStep(kbn.interval_to_seconds(step), Math.ceil(end - start)) + 's'
+      step: this.adjustStep(kbn.interval_to_seconds(step), 0, Math.ceil(end - start)) + 's'
     };
     };
 
 
     var self = this;
     var self = this;
@@ -229,6 +229,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
   };
   };
 
 
   this.calculateInterval = function(interval, intervalFactor) {
   this.calculateInterval = function(interval, intervalFactor) {
+    return Math.ceil(this.intervalSeconds(interval) * intervalFactor);
+  };
+
+  this.intervalSeconds = function(interval) {
     var m = interval.match(durationSplitRegexp);
     var m = interval.match(durationSplitRegexp);
     var dur = moment.duration(parseInt(m[1]), m[2]);
     var dur = moment.duration(parseInt(m[1]), m[2]);
     var sec = dur.asSeconds();
     var sec = dur.asSeconds();
@@ -236,7 +240,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
       sec = 1;
       sec = 1;
     }
     }
 
 
-    return Math.ceil(sec * intervalFactor);
+    return sec;
   };
   };
 
 
   this.transformMetricData = function(md, options, start, end) {
   this.transformMetricData = function(md, options, start, end) {

+ 1 - 1
public/app/plugins/datasource/prometheus/partials/query.editor.html

@@ -14,7 +14,7 @@
 			</input>
 			</input>
 		</div>
 		</div>
 		<div class="gf-form">
 		<div class="gf-form">
-			<label class="gf-form-label width-5">Step</label>
+			<label class="gf-form-label">Min step</label>
 			<input type="text" class="gf-form-input max-width-5" ng-model="ctrl.target.interval"
 			<input type="text" class="gf-form-input max-width-5" ng-model="ctrl.target.interval"
 					   data-placement="right"
 					   data-placement="right"
 			       spellcheck='false'
 			       spellcheck='false'

+ 2 - 2
public/app/plugins/panel/gettingstarted/module.ts

@@ -58,7 +58,7 @@ class GettingStartedPanelCtrl extends PanelCtrl {
       icon: 'icon-gf icon-gf-users',
       icon: 'icon-gf icon-gf-users',
       href: 'org/users?gettingstarted',
       href: 'org/users?gettingstarted',
       check: () => {
       check: () => {
-        return  this.backendSrv.get('api/org/users').then(res => {
+        return  this.backendSrv.get('/api/org/users').then(res => {
           return res.length > 1;
           return res.length > 1;
         });
         });
       }
       }
@@ -71,7 +71,7 @@ class GettingStartedPanelCtrl extends PanelCtrl {
       icon: 'icon-gf icon-gf-apps',
       icon: 'icon-gf icon-gf-apps',
       href: 'https://grafana.com/plugins?utm_source=grafana_getting_started',
       href: 'https://grafana.com/plugins?utm_source=grafana_getting_started',
       check: () => {
       check: () => {
-        return this.backendSrv.get('api/plugins', {embedded: 0, core: 0}).then(plugins => {
+        return this.backendSrv.get('/api/plugins', {embedded: 0, core: 0}).then(plugins => {
           return plugins.length > 0;
           return plugins.length > 0;
         });
         });
       }
       }