Просмотр исходного кода

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

ryan 8 лет назад
Родитель
Сommit
8c5972dc55

+ 1 - 0
CHANGELOG.md

@@ -13,6 +13,7 @@
 ## Minor Enchancements
 
 * **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)
 * **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)

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

@@ -12,8 +12,8 @@ parent = "http_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.
 
@@ -115,7 +115,7 @@ This API can also be used to create, update and delete alert notifications.
 
     HTTP/1.1 200
     Content-Type: application/json
-    
+
     {
       "id": 1,
       "name": "Team A",
@@ -127,11 +127,11 @@ This API can also be used to create, update and delete alert notifications.
 
 ## Create alert notification
 
-`POST /api/alerts-notifications`
+`POST /api/alert-notifications`
 
 **Example Request**:
 
-    POST /api/alerts-notifications HTTP/1.1
+    POST /api/alert-notifications HTTP/1.1
     Accept: application/json
     Content-Type: application/json
     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"
       }
     }
-    
+
 
 **Example Response**:
 
     HTTP/1.1 200
     Content-Type: application/json
     {
-      "id": 1, 
+      "id": 1,
       "name": "new alert notification",
       "type": "email",
       "isDefault": false,
       "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"
     }
 
 ## Update alert notification
 
-`PUT /api/alerts-notifications/1`
+`PUT /api/alert-notifications/1`
 
 **Example Request**:
 
-    PUT /api/alerts-notifications/1 HTTP/1.1
+    PUT /api/alert-notifications/1 HTTP/1.1
     Accept: application/json
     Content-Type: application/json
     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
       "type":  "email", //Required
       "isDefault": false,
-      "settings": { 
+      "settings": {
         "addresses: "carl@grafana.com;dev@grafana.com"
       }
     }
-    
+
 
 **Example Response**:
 
     HTTP/1.1 200
     Content-Type: application/json
     {
-      "id": 1, 
+      "id": 1,
       "name": "new alert notification",
       "type": "email",
       "isDefault": false,
       "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"
     }
 
 ## Delete alert notification
 
-`DELETE /api/alerts-notifications/:notificationId`
+`DELETE /api/alert-notifications/:notificationId`
 
 **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{}) {
 	var message string
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 		message = format
 	}
@@ -45,7 +45,7 @@ func Trace(format string, v ...interface{}) {
 func Debug(format string, v ...interface{}) {
 	var message string
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 		message = format
 	}
@@ -60,7 +60,7 @@ func Debug2(message string, v ...interface{}) {
 func Info(format string, v ...interface{}) {
 	var message string
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 		message = format
 	}
@@ -75,7 +75,7 @@ func Info2(message string, v ...interface{}) {
 func Warn(format string, v ...interface{}) {
 	var message string
 	if len(v) > 0 {
-		message = fmt.Sprintf(format, v)
+		message = fmt.Sprintf(format, v...)
 	} else {
 		message = format
 	}
@@ -88,7 +88,7 @@ func Warn2(message 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{}) {
@@ -96,7 +96,7 @@ func Error2(message 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{}) {

+ 3 - 2
pkg/middleware/auth_proxy.go

@@ -13,7 +13,7 @@ import (
 	"github.com/grafana/grafana/pkg/setting"
 )
 
-func initContextWithAuthProxy(ctx *Context) bool {
+func initContextWithAuthProxy(ctx *Context, orgId int64) bool {
 	if !setting.AuthProxyEnabled {
 		return false
 	}
@@ -30,6 +30,7 @@ func initContextWithAuthProxy(ctx *Context) bool {
 	}
 
 	query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
+	query.OrgId = orgId
 	if err := bus.Dispatch(query); err != nil {
 		if err != m.ErrUserNotFound {
 			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)
 				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 {
 				ctx.Handle(500, "Failed find user after creation", err)
 				return true

+ 42 - 35
pkg/middleware/middleware.go

@@ -39,6 +39,12 @@ func GetContextHandler() macaron.Handler {
 			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
 		// look for api key in Authorization header first
 		// then init session and look for userId in session
@@ -46,9 +52,9 @@ func GetContextHandler() macaron.Handler {
 		// then test if anonymous access is enabled
 		if initContextWithRenderAuth(ctx) ||
 			initContextWithApiKey(ctx) ||
-			initContextWithBasicAuth(ctx) ||
-			initContextWithAuthProxy(ctx) ||
-			initContextWithUserSessionCookie(ctx) ||
+			initContextWithBasicAuth(ctx, orgId) ||
+			initContextWithAuthProxy(ctx, orgId) ||
+			initContextWithUserSessionCookie(ctx, orgId) ||
 			initContextWithAnonymousUser(ctx) {
 		}
 
@@ -68,18 +74,18 @@ func initContextWithAnonymousUser(ctx *Context) bool {
 	if err := bus.Dispatch(&orgQuery); err != nil {
 		log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err)
 		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
 	if err := ctx.Session.Start(ctx); err != nil {
 		ctx.Logger.Error("Failed to start session", "error", err)
@@ -91,15 +97,15 @@ func initContextWithUserSessionCookie(ctx *Context) bool {
 		return false
 	}
 
-	query := m.GetSignedInUserQuery{UserId: userId}
+	query := m.GetSignedInUserQuery{UserId: userId, OrgId: orgId}
 	if err := bus.Dispatch(&query); err != nil {
 		ctx.Logger.Error("Failed to get user with id", "userId", userId)
 		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 {
@@ -114,30 +120,31 @@ func initContextWithApiKey(ctx *Context) bool {
 		ctx.JsonApiErr(401, "Invalid API key", err)
 		return true
 	}
+
 	// fetch key
 	keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
 	if err := bus.Dispatch(&keyQuery); err != nil {
 		ctx.JsonApiErr(401, "Invalid API key", err)
 		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
 	}
+
+	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 {
 		return false
@@ -168,15 +175,15 @@ func initContextWithBasicAuth(ctx *Context) bool {
 		return true
 	}
 
-	query := m.GetSignedInUserQuery{UserId: user.Id}
+	query := m.GetSignedInUserQuery{UserId: user.Id, OrgId: orgId}
 	if err := bus.Dispatch(&query); err != nil {
 		ctx.JsonApiErr(401, "Authentication error", err)
 		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.

+ 1 - 0
pkg/models/user.go

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

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

@@ -1,6 +1,7 @@
 package sqlstore
 
 import (
+	"strconv"
 	"strings"
 	"time"
 
@@ -273,7 +274,7 @@ func SetUsingOrg(cmd *m.SetUsingOrgCommand) error {
 	}
 
 	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 {
@@ -319,19 +320,24 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error {
 }
 
 func GetSignedInUser(query *m.GetSignedInUserQuery) error {
+	orgId := "u.org_id"
+	if query.OrgId > 0 {
+		orgId = strconv.FormatInt(query.OrgId, 10)
+	}
+
 	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")
 	if query.UserId > 0 {

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

@@ -40,7 +40,14 @@ export function infoPopover() {
           openOn: openOn,
           hoverOpenDelay: 400,
           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 = {};
   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?) {
@@ -63,12 +63,18 @@ export class BackendSrv {
 
   request(options) {
     options.retry = options.retry || 0;
-    var requestIsLocal = options.url.indexOf('/') === 0;
+    var requestIsLocal = !options.url.match(/^http/);
     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 => {
@@ -125,16 +131,23 @@ export class BackendSrv {
       this.addCanceler(requestId, canceler);
     }
 
-    var requestIsLocal = options.url.indexOf('/') === 0;
+    var requestIsLocal = !options.url.match(/^http/);
     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 => {

+ 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">
 			<h5>Stacking and fill</h5>
 			<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>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>

+ 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;
       target.step = query.step = this.calculateInterval(interval, intervalFactor);
       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);
     });
 
@@ -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
     // calibrate step if it is too big
     if (step !== 0 && range / step > 11000) {
       return Math.ceil(range / 11000);
     }
-    return step;
+    return Math.max(step, autoStep);
   };
 
   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 query = {
       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;
@@ -229,6 +229,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
   };
 
   this.calculateInterval = function(interval, intervalFactor) {
+    return Math.ceil(this.intervalSeconds(interval) * intervalFactor);
+  };
+
+  this.intervalSeconds = function(interval) {
     var m = interval.match(durationSplitRegexp);
     var dur = moment.duration(parseInt(m[1]), m[2]);
     var sec = dur.asSeconds();
@@ -236,7 +240,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
       sec = 1;
     }
 
-    return Math.ceil(sec * intervalFactor);
+    return sec;
   };
 
   this.transformMetricData = function(md, options, start, end) {

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

@@ -14,7 +14,7 @@
 			</input>
 		</div>
 		<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"
 					   data-placement="right"
 			       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',
       href: 'org/users?gettingstarted',
       check: () => {
-        return  this.backendSrv.get('api/org/users').then(res => {
+        return  this.backendSrv.get('/api/org/users').then(res => {
           return res.length > 1;
         });
       }
@@ -71,7 +71,7 @@ class GettingStartedPanelCtrl extends PanelCtrl {
       icon: 'icon-gf icon-gf-apps',
       href: 'https://grafana.com/plugins?utm_source=grafana_getting_started',
       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;
         });
       }