Browse Source

Merge branch 'master' into docs-5.1

Marcus Efraimsson 7 years ago
parent
commit
85ccefdb84
46 changed files with 218 additions and 118 deletions
  1. 15 9
      .circleci/config.yml
  2. 4 4
      docs/sources/features/datasources/postgres.md
  3. 2 0
      docs/sources/http_api/annotations.md
  4. 2 2
      latest.json
  5. 1 1
      package.json
  6. 9 16
      pkg/api/annotations.go
  7. 1 1
      pkg/cmd/grafana-server/main.go
  8. 4 0
      pkg/components/dashdiffs/compare.go
  9. 4 0
      pkg/components/dynmap/dynmap_test.go
  10. 1 0
      pkg/components/imguploader/azureblobuploader_test.go
  11. 9 6
      pkg/components/imguploader/imguploader_test.go
  12. 7 1
      pkg/components/imguploader/webdavuploader.go
  13. 2 0
      pkg/log/file_test.go
  14. 6 1
      pkg/login/ldap.go
  15. 1 0
      pkg/models/dashboard_acl.go
  16. 7 3
      pkg/services/alerting/notifiers/telegram.go
  17. 0 21
      pkg/services/alerting/notifiers/victorops.go
  18. 1 1
      pkg/services/alerting/result_handler.go
  19. 5 0
      pkg/services/annotations/annotations.go
  20. 1 13
      pkg/services/guardian/guardian.go
  21. 7 4
      pkg/services/guardian/guardian_test.go
  22. 0 1
      pkg/services/provisioning/dashboards/file_reader.go
  23. 18 1
      pkg/services/sqlstore/annotation.go
  24. 11 0
      pkg/services/sqlstore/annotation_test.go
  25. 1 1
      pkg/services/sqlstore/dashboard.go
  26. 4 2
      pkg/services/sqlstore/dashboard_acl.go
  27. 26 0
      pkg/services/sqlstore/dashboard_acl_test.go
  28. 25 0
      pkg/services/sqlstore/migrations/annotation_mig.go
  29. 2 2
      pkg/services/sqlstore/migrations/migrations_test.go
  30. 3 0
      pkg/services/sqlstore/playlist.go
  31. 3 0
      pkg/services/sqlstore/plugin_setting.go
  32. 3 0
      pkg/services/sqlstore/preferences.go
  33. 2 1
      pkg/services/sqlstore/team_test.go
  34. 2 6
      pkg/services/sqlstore/user.go
  35. 2 2
      pkg/services/sqlstore/user_auth_test.go
  36. 1 1
      pkg/tsdb/cloudwatch/cloudwatch.go
  37. 3 0
      pkg/tsdb/influxdb/model_parser.go
  38. 2 3
      pkg/tsdb/influxdb/query.go
  39. 4 0
      pkg/tsdb/opentsdb/opentsdb.go
  40. 1 1
      public/app/core/components/Permissions/PermissionsListItem.tsx
  41. 1 2
      public/app/features/dashboard/dashgrid/DashboardRow.tsx
  42. 6 8
      public/app/features/dashboard/specs/DashboardRow.jest.tsx
  43. 7 1
      public/app/plugins/datasource/graphite/datasource.ts
  44. 1 0
      public/app/stores/PermissionsStore/PermissionsStore.jest.ts
  45. 0 2
      public/app/stores/PermissionsStore/PermissionsStore.ts
  46. 1 1
      scripts/build/rpmmacros

+ 15 - 9
.circleci/config.yml

@@ -20,18 +20,20 @@ jobs:
   gometalinter:
   gometalinter:
     docker:
     docker:
       - image: circleci/golang:1.10
       - image: circleci/golang:1.10
+        environment:
+          # we need CGO because of go-sqlite3
+          CGO_ENABLED: 1
     working_directory: /go/src/github.com/grafana/grafana
     working_directory: /go/src/github.com/grafana/grafana
     steps:
     steps:
       - checkout
       - checkout
-      - run:
-          name: install gometalinter tool
-          command: 'go get -u github.com/alecthomas/gometalinter'
-      - run:
-          name: install linters
-          command: 'gometalinter --install'
-      - run:
-          name: run some linters
-          command: 'gometalinter --vendor --deadline 6m --disable-all --enable=structcheck --enable=unconvert --enable=varcheck ./pkg/...'
+      - run: 'go get -u gopkg.in/alecthomas/gometalinter.v2'
+      - run: 'go get -u github.com/gordonklaus/ineffassign'
+      - run: 'go get -u github.com/opennota/check/cmd/structcheck'
+      - run: 'go get -u github.com/mdempsky/unconvert'
+      - run: 'go get -u github.com/opennota/check/cmd/varcheck'
+      - run:
+          name: run linters
+          command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...'
 
 
   test-frontend:
   test-frontend:
     docker:
     docker:
@@ -139,6 +141,10 @@ workflows:
           filters:
           filters:
             tags:
             tags:
               only: /.*/
               only: /.*/
+      - gometalinter:
+          filters:
+            tags:
+              only: /.*/
       - build:
       - build:
           filters:
           filters:
             tags:
             tags:

+ 4 - 4
docs/sources/features/datasources/postgres.md

@@ -56,10 +56,10 @@ To simplify syntax and to allow for dynamic parts, like date range filters, the
 Macro example | Description
 Macro example | Description
 ------------ | -------------
 ------------ | -------------
 *$__time(dateColumn)* | Will be replaced by an expression to rename the column to `time`. For example, *dateColumn as time*
 *$__time(dateColumn)* | Will be replaced by an expression to rename the column to `time`. For example, *dateColumn as time*
-*$__timeEpoch(dateColumn)* | Will be replaced by an expression to rename the column to `time` and converting the value to unix timestamp. For example, *extract(epoch from dateColumn) as time*
-*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN '2017-05-10T10:06:23Z' AND '2017-05-10T10:06:23Z'*
-*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-05-10T10:06:23Z'*
-*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-05-10T10:06:23Z'*
+*$__timeSec(dateColumn)* | Will be replaced by an expression to rename the column to `time` and converting the value to unix timestamp. For example, *extract(epoch from dateColumn) as time*
+*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:06:17Z'*
+*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'*
+*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'*
 *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from dateColumn)/300)::bigint*300 AS time*
 *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from dateColumn)/300)::bigint*300 AS time*
 *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example).
 *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example).
 *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn >= 1494410783 AND dateColumn <= 1494497183*
 *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn >= 1494410783 AND dateColumn <= 1494497183*

+ 2 - 0
docs/sources/http_api/annotations.md

@@ -36,6 +36,8 @@ Query Parameters:
 - `alertId`: number. Optional. Find annotations for a specified alert.
 - `alertId`: number. Optional. Find annotations for a specified alert.
 - `dashboardId`: number. Optional. Find annotations that are scoped to a specific dashboard
 - `dashboardId`: number. Optional. Find annotations that are scoped to a specific dashboard
 - `panelId`: number. Optional. Find annotations that are scoped to a specific panel
 - `panelId`: number. Optional. Find annotations that are scoped to a specific panel
+- `userId`: number. Optional. Find annotations created by a specific user
+- `type`: string. Optional. `alert`|`annotation` Return alerts or user created annotations
 - `tags`: string. Optional. Use this to filter global annotations. Global annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g. `tags=tag1&tags=tag2`.
 - `tags`: string. Optional. Use this to filter global annotations. Global annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g. `tags=tag1&tags=tag2`.
 
 
 **Example Response**:
 **Example Response**:

+ 2 - 2
latest.json

@@ -1,4 +1,4 @@
 {
 {
-  "stable": "5.0.0",
-	"testing": "5.0.0"
+  "stable": "5.0.4",
+  "testing": "5.0.4"
 }
 }

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
     "company": "Grafana Labs"
     "company": "Grafana Labs"
   },
   },
   "name": "grafana",
   "name": "grafana",
-  "version": "5.1.0-pre1",
+  "version": "5.2.0-pre1",
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"
     "url": "http://github.com/grafana/grafana.git"

+ 9 - 16
pkg/api/annotations.go

@@ -2,7 +2,6 @@ package api
 
 
 import (
 import (
 	"strings"
 	"strings"
-	"time"
 
 
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/components/simplejson"
@@ -15,9 +14,10 @@ import (
 func GetAnnotations(c *m.ReqContext) Response {
 func GetAnnotations(c *m.ReqContext) Response {
 
 
 	query := &annotations.ItemQuery{
 	query := &annotations.ItemQuery{
-		From:        c.QueryInt64("from") / 1000,
-		To:          c.QueryInt64("to") / 1000,
+		From:        c.QueryInt64("from"),
+		To:          c.QueryInt64("to"),
 		OrgId:       c.OrgId,
 		OrgId:       c.OrgId,
+		UserId:      c.QueryInt64("userId"),
 		AlertId:     c.QueryInt64("alertId"),
 		AlertId:     c.QueryInt64("alertId"),
 		DashboardId: c.QueryInt64("dashboardId"),
 		DashboardId: c.QueryInt64("dashboardId"),
 		PanelId:     c.QueryInt64("panelId"),
 		PanelId:     c.QueryInt64("panelId"),
@@ -37,7 +37,7 @@ func GetAnnotations(c *m.ReqContext) Response {
 		if item.Email != "" {
 		if item.Email != "" {
 			item.AvatarUrl = dtos.GetGravatarUrl(item.Email)
 			item.AvatarUrl = dtos.GetGravatarUrl(item.Email)
 		}
 		}
-		item.Time = item.Time * 1000
+		item.Time = item.Time
 	}
 	}
 
 
 	return JSON(200, items)
 	return JSON(200, items)
@@ -68,16 +68,12 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
 		UserId:      c.UserId,
 		UserId:      c.UserId,
 		DashboardId: cmd.DashboardId,
 		DashboardId: cmd.DashboardId,
 		PanelId:     cmd.PanelId,
 		PanelId:     cmd.PanelId,
-		Epoch:       cmd.Time / 1000,
+		Epoch:       cmd.Time,
 		Text:        cmd.Text,
 		Text:        cmd.Text,
 		Data:        cmd.Data,
 		Data:        cmd.Data,
 		Tags:        cmd.Tags,
 		Tags:        cmd.Tags,
 	}
 	}
 
 
-	if item.Epoch == 0 {
-		item.Epoch = time.Now().Unix()
-	}
-
 	if err := repo.Save(&item); err != nil {
 	if err := repo.Save(&item); err != nil {
 		return Error(500, "Failed to save annotation", err)
 		return Error(500, "Failed to save annotation", err)
 	}
 	}
@@ -97,7 +93,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
 		}
 		}
 
 
 		item.Id = 0
 		item.Id = 0
-		item.Epoch = cmd.TimeEnd / 1000
+		item.Epoch = cmd.TimeEnd
 
 
 		if err := repo.Save(&item); err != nil {
 		if err := repo.Save(&item); err != nil {
 			return Error(500, "Failed save annotation for region end time", err)
 			return Error(500, "Failed save annotation for region end time", err)
@@ -132,9 +128,6 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
 		return Error(500, "Failed to save Graphite annotation", err)
 		return Error(500, "Failed to save Graphite annotation", err)
 	}
 	}
 
 
-	if cmd.When == 0 {
-		cmd.When = time.Now().Unix()
-	}
 	text := formatGraphiteAnnotation(cmd.What, cmd.Data)
 	text := formatGraphiteAnnotation(cmd.What, cmd.Data)
 
 
 	// Support tags in prior to Graphite 0.10.0 format (string of tags separated by space)
 	// Support tags in prior to Graphite 0.10.0 format (string of tags separated by space)
@@ -163,7 +156,7 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
 	item := annotations.Item{
 	item := annotations.Item{
 		OrgId:  c.OrgId,
 		OrgId:  c.OrgId,
 		UserId: c.UserId,
 		UserId: c.UserId,
-		Epoch:  cmd.When,
+		Epoch:  cmd.When * 1000,
 		Text:   text,
 		Text:   text,
 		Tags:   tagsArray,
 		Tags:   tagsArray,
 	}
 	}
@@ -191,7 +184,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
 		OrgId:  c.OrgId,
 		OrgId:  c.OrgId,
 		UserId: c.UserId,
 		UserId: c.UserId,
 		Id:     annotationID,
 		Id:     annotationID,
-		Epoch:  cmd.Time / 1000,
+		Epoch:  cmd.Time,
 		Text:   cmd.Text,
 		Text:   cmd.Text,
 		Tags:   cmd.Tags,
 		Tags:   cmd.Tags,
 	}
 	}
@@ -203,7 +196,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
 	if cmd.IsRegion {
 	if cmd.IsRegion {
 		itemRight := item
 		itemRight := item
 		itemRight.RegionId = item.Id
 		itemRight.RegionId = item.Id
-		itemRight.Epoch = cmd.TimeEnd / 1000
+		itemRight.Epoch = cmd.TimeEnd
 
 
 		// We don't know id of region right event, so set it to 0 and find then using query like
 		// We don't know id of region right event, so set it to 0 and find then using query like
 		// ... WHERE region_id = <item.RegionId> AND id != <item.RegionId> ...
 		// ... WHERE region_id = <item.RegionId> AND id != <item.RegionId> ...

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

@@ -100,9 +100,9 @@ func main() {
 }
 }
 
 
 func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) {
 func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) {
+	var code int
 	signalChan := make(chan os.Signal, 1)
 	signalChan := make(chan os.Signal, 1)
 	ignoreChan := make(chan os.Signal, 1)
 	ignoreChan := make(chan os.Signal, 1)
-	code := 0
 
 
 	signal.Notify(ignoreChan, syscall.SIGHUP)
 	signal.Notify(ignoreChan, syscall.SIGHUP)
 	signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)
 	signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)

+ 4 - 0
pkg/components/dashdiffs/compare.go

@@ -141,5 +141,9 @@ func getDiff(baseData, newData *simplejson.Json) (interface{}, diff.Diff, error)
 
 
 	left := make(map[string]interface{})
 	left := make(map[string]interface{})
 	err = json.Unmarshal(leftBytes, &left)
 	err = json.Unmarshal(leftBytes, &left)
+	if err != nil {
+		return nil, nil, err
+	}
+
 	return left, jsonDiff, nil
 	return left, jsonDiff, nil
 }
 }

+ 4 - 0
pkg/components/dynmap/dynmap_test.go

@@ -60,6 +60,7 @@ func TestFirst(t *testing.T) {
   }`
   }`
 
 
 	j, err := NewObjectFromBytes([]byte(testJSON))
 	j, err := NewObjectFromBytes([]byte(testJSON))
+	assert.True(err == nil, "failed to create new object from bytes")
 
 
 	a, err := j.GetObject("address")
 	a, err := j.GetObject("address")
 	assert.True(a != nil && err == nil, "failed to create json from string")
 	assert.True(a != nil && err == nil, "failed to create json from string")
@@ -108,6 +109,7 @@ func TestFirst(t *testing.T) {
 	//log.Println("address: ", address)
 	//log.Println("address: ", address)
 
 
 	s, err = address.GetString("street")
 	s, err = address.GetString("street")
+	assert.True(s == "Street 42" && err == nil, "street mismatching")
 
 
 	addressAsString, err := j.GetString("address")
 	addressAsString, err := j.GetString("address")
 	assert.True(addressAsString == "" && err != nil, "address should not be an string")
 	assert.True(addressAsString == "" && err != nil, "address should not be an string")
@@ -148,6 +150,7 @@ func TestFirst(t *testing.T) {
 		//assert.True(element.IsObject() == true, "first fail")
 		//assert.True(element.IsObject() == true, "first fail")
 
 
 		element, err := elementValue.Object()
 		element, err := elementValue.Object()
+		assert.True(err == nil, "create element fail")
 
 
 		s, err = element.GetString("street")
 		s, err = element.GetString("street")
 		assert.True(s == "Street 42" && err == nil, "second fail")
 		assert.True(s == "Street 42" && err == nil, "second fail")
@@ -232,6 +235,7 @@ func TestSecond(t *testing.T) {
 			assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch")
 			assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch")
 
 
 			actions, err := dataItem.GetObjectArray("actions")
 			actions, err := dataItem.GetObjectArray("actions")
+			assert.True(err == nil, "get object from array failed")
 
 
 			for index, action := range actions {
 			for index, action := range actions {
 
 

+ 1 - 0
pkg/components/imguploader/azureblobuploader_test.go

@@ -13,6 +13,7 @@ func TestUploadToAzureBlob(t *testing.T) {
 		err := setting.NewConfigContext(&setting.CommandLineArgs{
 		err := setting.NewConfigContext(&setting.CommandLineArgs{
 			HomePath: "../../../",
 			HomePath: "../../../",
 		})
 		})
+		So(err, ShouldBeNil)
 
 
 		uploader, _ := NewImageUploader()
 		uploader, _ := NewImageUploader()
 
 

+ 9 - 6
pkg/components/imguploader/imguploader_test.go

@@ -19,6 +19,7 @@ func TestImageUploaderFactory(t *testing.T) {
 
 
 			Convey("with bucket url https://foo.bar.baz.s3-us-east-2.amazonaws.com", func() {
 			Convey("with bucket url https://foo.bar.baz.s3-us-east-2.amazonaws.com", func() {
 				s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
 				s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
+				So(err, ShouldBeNil)
 				s3sec.NewKey("bucket_url", "https://foo.bar.baz.s3-us-east-2.amazonaws.com")
 				s3sec.NewKey("bucket_url", "https://foo.bar.baz.s3-us-east-2.amazonaws.com")
 				s3sec.NewKey("access_key", "access_key")
 				s3sec.NewKey("access_key", "access_key")
 				s3sec.NewKey("secret_key", "secret_key")
 				s3sec.NewKey("secret_key", "secret_key")
@@ -37,6 +38,7 @@ func TestImageUploaderFactory(t *testing.T) {
 
 
 			Convey("with bucket url https://s3.amazonaws.com/mybucket", func() {
 			Convey("with bucket url https://s3.amazonaws.com/mybucket", func() {
 				s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
 				s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
+				So(err, ShouldBeNil)
 				s3sec.NewKey("bucket_url", "https://s3.amazonaws.com/my.bucket.com")
 				s3sec.NewKey("bucket_url", "https://s3.amazonaws.com/my.bucket.com")
 				s3sec.NewKey("access_key", "access_key")
 				s3sec.NewKey("access_key", "access_key")
 				s3sec.NewKey("secret_key", "secret_key")
 				s3sec.NewKey("secret_key", "secret_key")
@@ -55,15 +57,15 @@ func TestImageUploaderFactory(t *testing.T) {
 
 
 			Convey("with bucket url https://s3-us-west-2.amazonaws.com/mybucket", func() {
 			Convey("with bucket url https://s3-us-west-2.amazonaws.com/mybucket", func() {
 				s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
 				s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
+				So(err, ShouldBeNil)
 				s3sec.NewKey("bucket_url", "https://s3-us-west-2.amazonaws.com/my.bucket.com")
 				s3sec.NewKey("bucket_url", "https://s3-us-west-2.amazonaws.com/my.bucket.com")
 				s3sec.NewKey("access_key", "access_key")
 				s3sec.NewKey("access_key", "access_key")
 				s3sec.NewKey("secret_key", "secret_key")
 				s3sec.NewKey("secret_key", "secret_key")
 
 
 				uploader, err := NewImageUploader()
 				uploader, err := NewImageUploader()
-
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-				original, ok := uploader.(*S3Uploader)
 
 
+				original, ok := uploader.(*S3Uploader)
 				So(ok, ShouldBeTrue)
 				So(ok, ShouldBeTrue)
 				So(original.region, ShouldEqual, "us-west-2")
 				So(original.region, ShouldEqual, "us-west-2")
 				So(original.bucket, ShouldEqual, "my.bucket.com")
 				So(original.bucket, ShouldEqual, "my.bucket.com")
@@ -82,6 +84,7 @@ func TestImageUploaderFactory(t *testing.T) {
 			setting.ImageUploadProvider = "webdav"
 			setting.ImageUploadProvider = "webdav"
 
 
 			webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
 			webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
+			So(err, ShouldBeNil)
 			webdavSec.NewKey("url", "webdavUrl")
 			webdavSec.NewKey("url", "webdavUrl")
 			webdavSec.NewKey("username", "username")
 			webdavSec.NewKey("username", "username")
 			webdavSec.NewKey("password", "password")
 			webdavSec.NewKey("password", "password")
@@ -107,14 +110,14 @@ func TestImageUploaderFactory(t *testing.T) {
 			setting.ImageUploadProvider = "gcs"
 			setting.ImageUploadProvider = "gcs"
 
 
 			gcpSec, err := setting.Cfg.GetSection("external_image_storage.gcs")
 			gcpSec, err := setting.Cfg.GetSection("external_image_storage.gcs")
+			So(err, ShouldBeNil)
 			gcpSec.NewKey("key_file", "/etc/secrets/project-79a52befa3f6.json")
 			gcpSec.NewKey("key_file", "/etc/secrets/project-79a52befa3f6.json")
 			gcpSec.NewKey("bucket", "project-grafana-east")
 			gcpSec.NewKey("bucket", "project-grafana-east")
 
 
 			uploader, err := NewImageUploader()
 			uploader, err := NewImageUploader()
-
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
-			original, ok := uploader.(*GCSUploader)
 
 
+			original, ok := uploader.(*GCSUploader)
 			So(ok, ShouldBeTrue)
 			So(ok, ShouldBeTrue)
 			So(original.keyFile, ShouldEqual, "/etc/secrets/project-79a52befa3f6.json")
 			So(original.keyFile, ShouldEqual, "/etc/secrets/project-79a52befa3f6.json")
 			So(original.bucket, ShouldEqual, "project-grafana-east")
 			So(original.bucket, ShouldEqual, "project-grafana-east")
@@ -128,15 +131,15 @@ func TestImageUploaderFactory(t *testing.T) {
 
 
 			Convey("with container name", func() {
 			Convey("with container name", func() {
 				azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob")
 				azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob")
+				So(err, ShouldBeNil)
 				azureBlobSec.NewKey("account_name", "account_name")
 				azureBlobSec.NewKey("account_name", "account_name")
 				azureBlobSec.NewKey("account_key", "account_key")
 				azureBlobSec.NewKey("account_key", "account_key")
 				azureBlobSec.NewKey("container_name", "container_name")
 				azureBlobSec.NewKey("container_name", "container_name")
 
 
 				uploader, err := NewImageUploader()
 				uploader, err := NewImageUploader()
-
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-				original, ok := uploader.(*AzureBlobUploader)
 
 
+				original, ok := uploader.(*AzureBlobUploader)
 				So(ok, ShouldBeTrue)
 				So(ok, ShouldBeTrue)
 				So(original.account_name, ShouldEqual, "account_name")
 				So(original.account_name, ShouldEqual, "account_name")
 				So(original.account_key, ShouldEqual, "account_key")
 				So(original.account_key, ShouldEqual, "account_key")

+ 7 - 1
pkg/components/imguploader/webdavuploader.go

@@ -41,14 +41,20 @@ func (u *WebdavUploader) Upload(ctx context.Context, pa string) (string, error)
 	url.Path = path.Join(url.Path, filename)
 	url.Path = path.Join(url.Path, filename)
 
 
 	imgData, err := ioutil.ReadFile(pa)
 	imgData, err := ioutil.ReadFile(pa)
+	if err != nil {
+		return "", err
+	}
+
 	req, err := http.NewRequest("PUT", url.String(), bytes.NewReader(imgData))
 	req, err := http.NewRequest("PUT", url.String(), bytes.NewReader(imgData))
+	if err != nil {
+		return "", err
+	}
 
 
 	if u.username != "" {
 	if u.username != "" {
 		req.SetBasicAuth(u.username, u.password)
 		req.SetBasicAuth(u.username, u.password)
 	}
 	}
 
 
 	res, err := netClient.Do(req)
 	res, err := netClient.Do(req)
-
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}

+ 2 - 0
pkg/log/file_test.go

@@ -32,7 +32,9 @@ func TestLogFile(t *testing.T) {
 
 
 		Convey("Logging should add lines", func() {
 		Convey("Logging should add lines", func() {
 			err := fileLogWrite.WriteLine("test1\n")
 			err := fileLogWrite.WriteLine("test1\n")
+			So(err, ShouldBeNil)
 			err = fileLogWrite.WriteLine("test2\n")
 			err = fileLogWrite.WriteLine("test2\n")
+			So(err, ShouldBeNil)
 			err = fileLogWrite.WriteLine("test3\n")
 			err = fileLogWrite.WriteLine("test3\n")
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 			So(fileLogWrite.maxlines_curlines, ShouldEqual, 3)
 			So(fileLogWrite.maxlines_curlines, ShouldEqual, 3)

+ 6 - 1
pkg/login/ldap.go

@@ -302,9 +302,11 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
 		// If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups
 		// If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups
 		var groupSearchResult *ldap.SearchResult
 		var groupSearchResult *ldap.SearchResult
 		for _, groupSearchBase := range a.server.GroupSearchBaseDNs {
 		for _, groupSearchBase := range a.server.GroupSearchBaseDNs {
-			filter_replace := getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult)
+			var filter_replace string
 			if a.server.GroupSearchFilterUserAttribute == "" {
 			if a.server.GroupSearchFilterUserAttribute == "" {
 				filter_replace = getLdapAttr(a.server.Attr.Username, searchResult)
 				filter_replace = getLdapAttr(a.server.Attr.Username, searchResult)
+			} else {
+				filter_replace = getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult)
 			}
 			}
 			filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1)
 			filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1)
 
 
@@ -346,6 +348,9 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
 }
 }
 
 
 func getLdapAttrN(name string, result *ldap.SearchResult, n int) string {
 func getLdapAttrN(name string, result *ldap.SearchResult, n int) string {
+	if name == "DN" {
+		return result.Entries[0].DN
+	}
 	for _, attr := range result.Entries[n].Attributes {
 	for _, attr := range result.Entries[n].Attributes {
 		if attr.Name == name {
 		if attr.Name == name {
 			if len(attr.Values) > 0 {
 			if len(attr.Values) > 0 {

+ 1 - 0
pkg/models/dashboard_acl.go

@@ -69,6 +69,7 @@ type DashboardAclInfoDTO struct {
 	Slug           string         `json:"slug"`
 	Slug           string         `json:"slug"`
 	IsFolder       bool           `json:"isFolder"`
 	IsFolder       bool           `json:"isFolder"`
 	Url            string         `json:"url"`
 	Url            string         `json:"url"`
+	Inherited      bool           `json:"inherited"`
 }
 }
 
 
 func (dto *DashboardAclInfoDTO) hasSameRoleAs(other *DashboardAclInfoDTO) bool {
 func (dto *DashboardAclInfoDTO) hasSameRoleAs(other *DashboardAclInfoDTO) bool {

+ 7 - 3
pkg/services/alerting/notifiers/telegram.go

@@ -3,13 +3,14 @@ package notifiers
 import (
 import (
 	"bytes"
 	"bytes"
 	"fmt"
 	"fmt"
+	"io"
+	"mime/multipart"
+	"os"
+
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/alerting"
 	"github.com/grafana/grafana/pkg/services/alerting"
-	"io"
-	"mime/multipart"
-	"os"
 )
 )
 
 
 const (
 const (
@@ -133,6 +134,9 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval
 	}
 	}
 
 
 	ruleUrl, err := evalContext.GetRuleUrl()
 	ruleUrl, err := evalContext.GetRuleUrl()
+	if err != nil {
+		return nil, err
+	}
 
 
 	metrics := generateMetricsMessage(evalContext)
 	metrics := generateMetricsMessage(evalContext)
 	message := generateImageCaption(evalContext, ruleUrl, metrics)
 	message := generateImageCaption(evalContext, ruleUrl, metrics)

+ 0 - 21
pkg/services/alerting/notifiers/victorops.go

@@ -83,27 +83,6 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	fields := make([]map[string]interface{}, 0)
-	fieldLimitCount := 4
-	for index, evt := range evalContext.EvalMatches {
-		fields = append(fields, map[string]interface{}{
-			"title": evt.Metric,
-			"value": evt.Value,
-			"short": true,
-		})
-		if index > fieldLimitCount {
-			break
-		}
-	}
-
-	if evalContext.Error != nil {
-		fields = append(fields, map[string]interface{}{
-			"title": "Error message",
-			"value": evalContext.Error.Error(),
-			"short": false,
-		})
-	}
-
 	messageType := evalContext.Rule.State
 	messageType := evalContext.Rule.State
 	if evalContext.Rule.State == models.AlertStateAlerting { // translate 'Alerting' to 'CRITICAL' (Victorops analog)
 	if evalContext.Rule.State == models.AlertStateAlerting { // translate 'Alerting' to 'CRITICAL' (Victorops analog)
 		messageType = AlertStateCritical
 		messageType = AlertStateCritical

+ 1 - 1
pkg/services/alerting/result_handler.go

@@ -77,7 +77,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
 			Text:        "",
 			Text:        "",
 			NewState:    string(evalContext.Rule.State),
 			NewState:    string(evalContext.Rule.State),
 			PrevState:   string(evalContext.PrevAlertState),
 			PrevState:   string(evalContext.PrevAlertState),
-			Epoch:       time.Now().Unix(),
+			Epoch:       time.Now().UnixNano() / int64(time.Millisecond),
 			Data:        annotationData,
 			Data:        annotationData,
 		}
 		}
 
 

+ 5 - 0
pkg/services/annotations/annotations.go

@@ -13,6 +13,7 @@ type ItemQuery struct {
 	OrgId        int64    `json:"orgId"`
 	OrgId        int64    `json:"orgId"`
 	From         int64    `json:"from"`
 	From         int64    `json:"from"`
 	To           int64    `json:"to"`
 	To           int64    `json:"to"`
+	UserId       int64    `json:"userId"`
 	AlertId      int64    `json:"alertId"`
 	AlertId      int64    `json:"alertId"`
 	DashboardId  int64    `json:"dashboardId"`
 	DashboardId  int64    `json:"dashboardId"`
 	PanelId      int64    `json:"panelId"`
 	PanelId      int64    `json:"panelId"`
@@ -63,6 +64,8 @@ type Item struct {
 	PrevState   string           `json:"prevState"`
 	PrevState   string           `json:"prevState"`
 	NewState    string           `json:"newState"`
 	NewState    string           `json:"newState"`
 	Epoch       int64            `json:"epoch"`
 	Epoch       int64            `json:"epoch"`
+	Created     int64            `json:"created"`
+	Updated     int64            `json:"updated"`
 	Tags        []string         `json:"tags"`
 	Tags        []string         `json:"tags"`
 	Data        *simplejson.Json `json:"data"`
 	Data        *simplejson.Json `json:"data"`
 
 
@@ -80,6 +83,8 @@ type ItemDTO struct {
 	UserId      int64            `json:"userId"`
 	UserId      int64            `json:"userId"`
 	NewState    string           `json:"newState"`
 	NewState    string           `json:"newState"`
 	PrevState   string           `json:"prevState"`
 	PrevState   string           `json:"prevState"`
+	Created     int64            `json:"created"`
+	Updated     int64            `json:"updated"`
 	Time        int64            `json:"time"`
 	Time        int64            `json:"time"`
 	Text        string           `json:"text"`
 	Text        string           `json:"text"`
 	RegionId    int64            `json:"regionId"`
 	RegionId    int64            `json:"regionId"`

+ 1 - 13
pkg/services/guardian/guardian.go

@@ -154,12 +154,7 @@ func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.Permiss
 	// validate overridden permissions to be higher
 	// validate overridden permissions to be higher
 	for _, a := range acl {
 	for _, a := range acl {
 		for _, existingPerm := range existingPermissions {
 		for _, existingPerm := range existingPermissions {
-			// handle default permissions
-			if existingPerm.DashboardId == -1 {
-				existingPerm.DashboardId = g.dashId
-			}
-
-			if a.DashboardId == existingPerm.DashboardId {
+			if !existingPerm.Inherited {
 				continue
 				continue
 			}
 			}
 
 
@@ -187,13 +182,6 @@ func (g *dashboardGuardianImpl) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	for _, a := range query.Result {
-		// handle default permissions
-		if a.DashboardId == -1 {
-			a.DashboardId = g.dashId
-		}
-	}
-
 	g.acl = query.Result
 	g.acl = query.Result
 	return g.acl, nil
 	return g.acl, nil
 }
 }

+ 7 - 4
pkg/services/guardian/guardian_test.go

@@ -217,13 +217,13 @@ func (sc *scenarioContext) parentFolderPermissionScenario(pt permissionType, per
 
 
 	switch pt {
 	switch pt {
 	case USER:
 	case USER:
-		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, UserId: userID, Permission: permission}}
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, UserId: userID, Permission: permission, Inherited: true}}
 	case TEAM:
 	case TEAM:
-		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, TeamId: teamID, Permission: permission}}
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, TeamId: teamID, Permission: permission, Inherited: true}}
 	case EDITOR:
 	case EDITOR:
-		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &editorRole, Permission: permission}}
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &editorRole, Permission: permission, Inherited: true}}
 	case VIEWER:
 	case VIEWER:
-		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &viewerRole, Permission: permission}}
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &viewerRole, Permission: permission, Inherited: true}}
 	}
 	}
 
 
 	permissionScenario(fmt.Sprintf("and parent folder has %s with permission to %s", pt.String(), permission.String()), childDashboardID, sc, folderPermissionList, func(sc *scenarioContext) {
 	permissionScenario(fmt.Sprintf("and parent folder has %s with permission to %s", pt.String(), permission.String()), childDashboardID, sc, folderPermissionList, func(sc *scenarioContext) {
@@ -649,6 +649,9 @@ func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShou
 			}
 			}
 
 
 			_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
 			_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+			if err != nil {
+				sc.reportFailure(tc, nil, err)
+			}
 			sc.updatePermissions = permissionList
 			sc.updatePermissions = permissionList
 			ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
 			ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
 
 

+ 0 - 1
pkg/services/provisioning/dashboards/file_reader.go

@@ -235,7 +235,6 @@ func getOrCreateFolderId(cfg *DashboardsAsConfig, service dashboards.DashboardPr
 func resolveSymlink(fileinfo os.FileInfo, path string) (os.FileInfo, error) {
 func resolveSymlink(fileinfo os.FileInfo, path string) (os.FileInfo, error) {
 	checkFilepath, err := filepath.EvalSymlinks(path)
 	checkFilepath, err := filepath.EvalSymlinks(path)
 	if path != checkFilepath {
 	if path != checkFilepath {
-		path = checkFilepath
 		fi, err := os.Lstat(checkFilepath)
 		fi, err := os.Lstat(checkFilepath)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err

+ 18 - 1
pkg/services/sqlstore/annotation.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
+	"time"
 
 
 	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/annotations"
 	"github.com/grafana/grafana/pkg/services/annotations"
@@ -17,6 +18,12 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 		tags := models.ParseTagPairs(item.Tags)
 		tags := models.ParseTagPairs(item.Tags)
 		item.Tags = models.JoinTagPairs(tags)
 		item.Tags = models.JoinTagPairs(tags)
+		item.Created = time.Now().UnixNano() / int64(time.Millisecond)
+		item.Updated = item.Created
+		if item.Epoch == 0 {
+			item.Epoch = item.Created
+		}
+
 		if _, err := sess.Table("annotation").Insert(item); err != nil {
 		if _, err := sess.Table("annotation").Insert(item); err != nil {
 			return err
 			return err
 		}
 		}
@@ -79,6 +86,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
 			return errors.New("Annotation not found")
 			return errors.New("Annotation not found")
 		}
 		}
 
 
+		existing.Updated = time.Now().UnixNano() / int64(time.Millisecond)
 		existing.Epoch = item.Epoch
 		existing.Epoch = item.Epoch
 		existing.Text = item.Text
 		existing.Text = item.Text
 		if item.RegionId != 0 {
 		if item.RegionId != 0 {
@@ -102,7 +110,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
 
 
 		existing.Tags = item.Tags
 		existing.Tags = item.Tags
 
 
-		_, err = sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "tags").Update(existing)
+		_, err = sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "updated", "tags").Update(existing)
 		return err
 		return err
 	})
 	})
 }
 }
@@ -124,6 +132,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
 			annotation.text,
 			annotation.text,
 			annotation.tags,
 			annotation.tags,
 			annotation.data,
 			annotation.data,
+			annotation.created,
+			annotation.updated,
 			usr.email,
 			usr.email,
 			usr.login,
 			usr.login,
 			alert.name as alert_name
 			alert.name as alert_name
@@ -161,6 +171,11 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
 		params = append(params, query.PanelId)
 		params = append(params, query.PanelId)
 	}
 	}
 
 
+	if query.UserId != 0 {
+		sql.WriteString(` AND annotation.user_id = ?`)
+		params = append(params, query.UserId)
+	}
+
 	if query.From > 0 && query.To > 0 {
 	if query.From > 0 && query.To > 0 {
 		sql.WriteString(` AND annotation.epoch BETWEEN ? AND ?`)
 		sql.WriteString(` AND annotation.epoch BETWEEN ? AND ?`)
 		params = append(params, query.From, query.To)
 		params = append(params, query.From, query.To)
@@ -168,6 +183,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
 
 
 	if query.Type == "alert" {
 	if query.Type == "alert" {
 		sql.WriteString(` AND annotation.alert_id > 0`)
 		sql.WriteString(` AND annotation.alert_id > 0`)
+	} else if query.Type == "annotation" {
+		sql.WriteString(` AND annotation.alert_id = 0`)
 	}
 	}
 
 
 	if len(query.Tags) > 0 {
 	if len(query.Tags) > 0 {

+ 11 - 0
pkg/services/sqlstore/annotation_test.go

@@ -79,6 +79,12 @@ func TestAnnotations(t *testing.T) {
 				Convey("Can read tags", func() {
 				Convey("Can read tags", func() {
 					So(items[0].Tags, ShouldResemble, []string{"outage", "error", "type:outage", "server:server-1"})
 					So(items[0].Tags, ShouldResemble, []string{"outage", "error", "type:outage", "server:server-1"})
 				})
 				})
+
+				Convey("Has created and updated values", func() {
+					So(items[0].Created, ShouldBeGreaterThan, 0)
+					So(items[0].Updated, ShouldBeGreaterThan, 0)
+					So(items[0].Updated, ShouldEqual, items[0].Created)
+				})
 			})
 			})
 
 
 			Convey("Can query for annotation by id", func() {
 			Convey("Can query for annotation by id", func() {
@@ -231,6 +237,10 @@ func TestAnnotations(t *testing.T) {
 					So(items[0].Tags, ShouldResemble, []string{"newtag1", "newtag2"})
 					So(items[0].Tags, ShouldResemble, []string{"newtag1", "newtag2"})
 					So(items[0].Text, ShouldEqual, "something new")
 					So(items[0].Text, ShouldEqual, "something new")
 				})
 				})
+
+				Convey("Updated time has increased", func() {
+					So(items[0].Updated, ShouldBeGreaterThan, items[0].Created)
+				})
 			})
 			})
 
 
 			Convey("Can delete annotation", func() {
 			Convey("Can delete annotation", func() {
@@ -246,6 +256,7 @@ func TestAnnotations(t *testing.T) {
 				annotationId := items[0].Id
 				annotationId := items[0].Id
 
 
 				err = repo.Delete(&annotations.DeleteParams{Id: annotationId})
 				err = repo.Delete(&annotations.DeleteParams{Id: annotationId})
+				So(err, ShouldBeNil)
 
 
 				items, err = repo.Find(query)
 				items, err = repo.Find(query)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)

+ 1 - 1
pkg/services/sqlstore/dashboard.go

@@ -77,7 +77,7 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
 	}
 	}
 
 
 	parentVersion := dash.Version
 	parentVersion := dash.Version
-	affectedRows := int64(0)
+	var affectedRows int64
 	var err error
 	var err error
 
 
 	if dash.Id == 0 {
 	if dash.Id == 0 {

+ 4 - 2
pkg/services/sqlstore/dashboard_acl.go

@@ -67,7 +67,8 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
 		'' as title,
 		'' as title,
 		'' as slug,
 		'' as slug,
 		'' as uid,` +
 		'' as uid,` +
-			falseStr + ` AS is_folder
+			falseStr + ` AS is_folder,` +
+			falseStr + ` AS inherited
 		FROM dashboard_acl as da
 		FROM dashboard_acl as da
 		WHERE da.dashboard_id = -1`
 		WHERE da.dashboard_id = -1`
 		query.Result = make([]*m.DashboardAclInfoDTO, 0)
 		query.Result = make([]*m.DashboardAclInfoDTO, 0)
@@ -94,7 +95,8 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
 				d.title,
 				d.title,
 				d.slug,
 				d.slug,
 				d.uid,
 				d.uid,
-				d.is_folder
+				d.is_folder,
+				CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
 			FROM dashboard as d
 			FROM dashboard as d
 				LEFT JOIN dashboard folder on folder.id = d.folder_id
 				LEFT JOIN dashboard folder on folder.id = d.folder_id
 				LEFT JOIN dashboard_acl AS da ON
 				LEFT JOIN dashboard_acl AS da ON

+ 26 - 0
pkg/services/sqlstore/dashboard_acl_test.go

@@ -26,6 +26,22 @@ func TestDashboardAclDataAccess(t *testing.T) {
 			})
 			})
 
 
 			Convey("Given dashboard folder with default permissions", func() {
 			Convey("Given dashboard folder with default permissions", func() {
+				Convey("When reading folder acl should include default acl", func() {
+					query := m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
+
+					err := GetDashboardAclInfoList(&query)
+					So(err, ShouldBeNil)
+
+					So(len(query.Result), ShouldEqual, 2)
+					defaultPermissionsId := -1
+					So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
+					So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
+					So(query.Result[0].Inherited, ShouldBeFalse)
+					So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
+					So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
+					So(query.Result[1].Inherited, ShouldBeFalse)
+				})
+
 				Convey("When reading dashboard acl should include acl for parent folder", func() {
 				Convey("When reading dashboard acl should include acl for parent folder", func() {
 					query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
 					query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
 
 
@@ -36,8 +52,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
 					defaultPermissionsId := -1
 					defaultPermissionsId := -1
 					So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
 					So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
+					So(query.Result[0].Inherited, ShouldBeTrue)
 					So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
 					So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
+					So(query.Result[1].Inherited, ShouldBeTrue)
 				})
 				})
 			})
 			})
 
 
@@ -94,7 +112,9 @@ func TestDashboardAclDataAccess(t *testing.T) {
 
 
 						So(len(query.Result), ShouldEqual, 2)
 						So(len(query.Result), ShouldEqual, 2)
 						So(query.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
 						So(query.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
+						So(query.Result[0].Inherited, ShouldBeTrue)
 						So(query.Result[1].DashboardId, ShouldEqual, childDash.Id)
 						So(query.Result[1].DashboardId, ShouldEqual, childDash.Id)
+						So(query.Result[1].Inherited, ShouldBeFalse)
 					})
 					})
 				})
 				})
 			})
 			})
@@ -118,9 +138,12 @@ func TestDashboardAclDataAccess(t *testing.T) {
 					So(len(query.Result), ShouldEqual, 3)
 					So(len(query.Result), ShouldEqual, 3)
 					So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
 					So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
+					So(query.Result[0].Inherited, ShouldBeTrue)
 					So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
 					So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
 					So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
+					So(query.Result[1].Inherited, ShouldBeTrue)
 					So(query.Result[2].DashboardId, ShouldEqual, childDash.Id)
 					So(query.Result[2].DashboardId, ShouldEqual, childDash.Id)
+					So(query.Result[2].Inherited, ShouldBeFalse)
 				})
 				})
 			})
 			})
 
 
@@ -131,6 +154,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
 					DashboardId: savedFolder.Id,
 					DashboardId: savedFolder.Id,
 					Permission:  m.PERMISSION_EDIT,
 					Permission:  m.PERMISSION_EDIT,
 				})
 				})
+				So(err, ShouldBeNil)
 
 
 				q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
 				q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
 				err = GetDashboardAclInfoList(q1)
 				err = GetDashboardAclInfoList(q1)
@@ -209,8 +233,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
 				defaultPermissionsId := -1
 				defaultPermissionsId := -1
 				So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
 				So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
 				So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
 				So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
+				So(query.Result[0].Inherited, ShouldBeFalse)
 				So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
 				So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
 				So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
 				So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
+				So(query.Result[1].Inherited, ShouldBeFalse)
 			})
 			})
 		})
 		})
 	})
 	})

+ 25 - 0
pkg/services/sqlstore/migrations/annotation_mig.go

@@ -90,4 +90,29 @@ func addAnnotationMig(mg *Migrator) {
 		Sqlite(updateTextFieldSql).
 		Sqlite(updateTextFieldSql).
 		Postgres(updateTextFieldSql).
 		Postgres(updateTextFieldSql).
 		Mysql(updateTextFieldSql))
 		Mysql(updateTextFieldSql))
+
+	//
+	// Add a 'created' & 'updated' column
+	//
+	mg.AddMigration("Add created time to annotation table", NewAddColumnMigration(table, &Column{
+		Name: "created", Type: DB_BigInt, Nullable: true, Default: "0",
+	}))
+	mg.AddMigration("Add updated time to annotation table", NewAddColumnMigration(table, &Column{
+		Name: "updated", Type: DB_BigInt, Nullable: true, Default: "0",
+	}))
+	mg.AddMigration("Add index for created in annotation table", NewAddIndexMigration(table, &Index{
+		Cols: []string{"org_id", "created"}, Type: IndexType,
+	}))
+	mg.AddMigration("Add index for updated in annotation table", NewAddIndexMigration(table, &Index{
+		Cols: []string{"org_id", "updated"}, Type: IndexType,
+	}))
+
+	//
+	// Convert epoch saved as seconds to miliseconds
+	//
+	updateEpochSql := "UPDATE annotation SET epoch = (epoch*1000) where epoch < 9999999999"
+	mg.AddMigration("Convert existing annotations from seconds to milliseconds", new(RawSqlMigration).
+		Sqlite(updateEpochSql).
+		Postgres(updateEpochSql).
+		Mysql(updateEpochSql))
 }
 }

+ 2 - 2
pkg/services/sqlstore/migrations/migrations_test.go

@@ -27,7 +27,7 @@ func TestMigrations(t *testing.T) {
 
 
 			sqlutil.CleanDB(x)
 			sqlutil.CleanDB(x)
 
 
-			has, err := x.SQL(sql).Get(&r)
+			_, err = x.SQL(sql).Get(&r)
 			So(err, ShouldNotBeNil)
 			So(err, ShouldNotBeNil)
 
 
 			mg := NewMigrator(x)
 			mg := NewMigrator(x)
@@ -36,7 +36,7 @@ func TestMigrations(t *testing.T) {
 			err = mg.Start()
 			err = mg.Start()
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
-			has, err = x.SQL(sql).Get(&r)
+			has, err := x.SQL(sql).Get(&r)
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 			So(has, ShouldBeTrue)
 			So(has, ShouldBeTrue)
 			expectedMigrations := mg.MigrationsCount() - 2 //we currently skip to migrations. We should rewrite skipped migrations to write in the log as well. until then we have to keep this
 			expectedMigrations := mg.MigrationsCount() - 2 //we currently skip to migrations. We should rewrite skipped migrations to write in the log as well. until then we have to keep this

+ 3 - 0
pkg/services/sqlstore/playlist.go

@@ -22,6 +22,9 @@ func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
 	}
 	}
 
 
 	_, err := x.Insert(&playlist)
 	_, err := x.Insert(&playlist)
+	if err != nil {
+		return err
+	}
 
 
 	playlistItems := make([]m.PlaylistItem, 0)
 	playlistItems := make([]m.PlaylistItem, 0)
 	for _, item := range cmd.Items {
 	for _, item := range cmd.Items {

+ 3 - 0
pkg/services/sqlstore/plugin_setting.go

@@ -48,6 +48,9 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
 		var pluginSetting m.PluginSetting
 		var pluginSetting m.PluginSetting
 
 
 		exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting)
 		exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting)
+		if err != nil {
+			return err
+		}
 		sess.UseBool("enabled")
 		sess.UseBool("enabled")
 		sess.UseBool("pinned")
 		sess.UseBool("pinned")
 		if !exists {
 		if !exists {

+ 3 - 0
pkg/services/sqlstore/preferences.go

@@ -72,6 +72,9 @@ func SavePreferences(cmd *m.SavePreferencesCommand) error {
 
 
 		var prefs m.Preferences
 		var prefs m.Preferences
 		exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&prefs)
 		exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&prefs)
+		if err != nil {
+			return err
+		}
 
 
 		if !exists {
 		if !exists {
 			prefs = m.Preferences{
 			prefs = m.Preferences{

+ 2 - 1
pkg/services/sqlstore/team_test.go

@@ -74,6 +74,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 			Convey("Should be able to return all teams a user is member of", func() {
 			Convey("Should be able to return all teams a user is member of", func() {
 				groupId := group2.Result.Id
 				groupId := group2.Result.Id
 				err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
 				err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
+				So(err, ShouldBeNil)
 
 
 				query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
 				query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
 				err = GetTeamsByUser(query)
 				err = GetTeamsByUser(query)
@@ -103,7 +104,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
 				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				err = testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
 				err = testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
-
+				So(err, ShouldBeNil)
 				err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
 				err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 

+ 2 - 6
pkg/services/sqlstore/user.go

@@ -168,11 +168,9 @@ func GetUserByLogin(query *m.GetUserByLoginQuery) error {
 		return m.ErrUserNotFound
 		return m.ErrUserNotFound
 	}
 	}
 
 
-	user := new(m.User)
-
 	// Try and find the user by login first.
 	// Try and find the user by login first.
 	// It's not sufficient to assume that a LoginOrEmail with an "@" is an email.
 	// It's not sufficient to assume that a LoginOrEmail with an "@" is an email.
-	user = &m.User{Login: query.LoginOrEmail}
+	user := &m.User{Login: query.LoginOrEmail}
 	has, err := x.Get(user)
 	has, err := x.Get(user)
 
 
 	if err != nil {
 	if err != nil {
@@ -202,9 +200,7 @@ func GetUserByEmail(query *m.GetUserByEmailQuery) error {
 		return m.ErrUserNotFound
 		return m.ErrUserNotFound
 	}
 	}
 
 
-	user := new(m.User)
-
-	user = &m.User{Email: query.Email}
+	user := &m.User{Email: query.Email}
 	has, err := x.Get(user)
 	has, err := x.Get(user)
 
 
 	if err != nil {
 	if err != nil {

+ 2 - 2
pkg/services/sqlstore/user_auth_test.go

@@ -32,7 +32,7 @@ func TestUserAuth(t *testing.T) {
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 			_, err = x.Exec("DELETE FROM org WHERE 1=1")
 			_, err = x.Exec("DELETE FROM org WHERE 1=1")
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
-			_, err = x.Exec("DELETE FROM user WHERE 1=1")
+			_, err = x.Exec("DELETE FROM " + dialect.Quote("user") + " WHERE 1=1")
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 			_, err = x.Exec("DELETE FROM user_auth WHERE 1=1")
 			_, err = x.Exec("DELETE FROM user_auth WHERE 1=1")
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
@@ -117,7 +117,7 @@ func TestUserAuth(t *testing.T) {
 			So(query.Result.Login, ShouldEqual, "loginuser1")
 			So(query.Result.Login, ShouldEqual, "loginuser1")
 
 
 			// remove user
 			// remove user
-			_, err = x.Exec("DELETE FROM user WHERE id=?", query.Result.Id)
+			_, err = x.Exec("DELETE FROM "+dialect.Quote("user")+" WHERE id=?", query.Result.Id)
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
 			// get via user_auth for deleted user
 			// get via user_auth for deleted user

+ 1 - 1
pkg/tsdb/cloudwatch/cloudwatch.go

@@ -271,7 +271,7 @@ func parseQuery(model *simplejson.Json) (*CloudWatchQuery, error) {
 		}
 		}
 	}
 	}
 
 
-	period := 300
+	var period int
 	if regexp.MustCompile(`^\d+$`).Match([]byte(p)) {
 	if regexp.MustCompile(`^\d+$`).Match([]byte(p)) {
 		period, err = strconv.Atoi(p)
 		period, err = strconv.Atoi(p)
 		if err != nil {
 		if err != nil {

+ 3 - 0
pkg/tsdb/influxdb/model_parser.go

@@ -40,6 +40,9 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *models.Data
 	}
 	}
 
 
 	parsedInterval, err := tsdb.GetIntervalFrom(dsInfo, model, time.Millisecond*1)
 	parsedInterval, err := tsdb.GetIntervalFrom(dsInfo, model, time.Millisecond*1)
+	if err != nil {
+		return nil, err
+	}
 
 
 	return &Query{
 	return &Query{
 		Measurement:  measurement,
 		Measurement:  measurement,

+ 2 - 3
pkg/tsdb/influxdb/query.go

@@ -62,9 +62,8 @@ func (query *Query) renderTags() []string {
 			}
 			}
 		}
 		}
 
 
-		textValue := ""
-
 		// quote value unless regex or number
 		// quote value unless regex or number
+		var textValue string
 		if tag.Operator == "=~" || tag.Operator == "!~" {
 		if tag.Operator == "=~" || tag.Operator == "!~" {
 			textValue = tag.Value
 			textValue = tag.Value
 		} else if tag.Operator == "<" || tag.Operator == ">" {
 		} else if tag.Operator == "<" || tag.Operator == ">" {
@@ -107,7 +106,7 @@ func (query *Query) renderSelectors(queryContext *tsdb.TsdbQuery) string {
 }
 }
 
 
 func (query *Query) renderMeasurement() string {
 func (query *Query) renderMeasurement() string {
-	policy := ""
+	var policy string
 	if query.Policy == "" || query.Policy == "default" {
 	if query.Policy == "" || query.Policy == "default" {
 		policy = ""
 		policy = ""
 	} else {
 	} else {

+ 4 - 0
pkg/tsdb/opentsdb/opentsdb.go

@@ -83,6 +83,10 @@ func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsd
 	u.Path = path.Join(u.Path, "api/query")
 	u.Path = path.Join(u.Path, "api/query")
 
 
 	postData, err := json.Marshal(data)
 	postData, err := json.Marshal(data)
+	if err != nil {
+		plog.Info("Failed marshalling data", "error", err)
+		return nil, fmt.Errorf("Failed to create request. error: %v", err)
+	}
 
 
 	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(string(postData)))
 	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(string(postData)))
 	if err != nil {
 	if err != nil {

+ 1 - 1
public/app/core/components/Permissions/PermissionsListItem.tsx

@@ -41,7 +41,7 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde
     permissionChanged(itemIndex, permissionOption.value, permissionOption.label);
     permissionChanged(itemIndex, permissionOption.value, permissionOption.label);
   };
   };
 
 
-  const inheritedFromRoot = item.dashboardId === -1 && folderInfo && folderInfo.id === 0;
+  const inheritedFromRoot = item.dashboardId === -1 && !item.inherited;
 
 
   return (
   return (
     <tr className={setClassNameHelper(item.inherited)}>
     <tr className={setClassNameHelper(item.inherited)}>

+ 1 - 2
public/app/features/dashboard/dashgrid/DashboardRow.tsx

@@ -4,7 +4,6 @@ import { PanelModel } from '../panel_model';
 import { PanelContainer } from './PanelContainer';
 import { PanelContainer } from './PanelContainer';
 import templateSrv from 'app/features/templating/template_srv';
 import templateSrv from 'app/features/templating/template_srv';
 import appEvents from 'app/core/app_events';
 import appEvents from 'app/core/app_events';
-import config from 'app/core/config';
 
 
 export interface DashboardRowProps {
 export interface DashboardRowProps {
   panel: PanelModel;
   panel: PanelModel;
@@ -95,7 +94,7 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
           {title}
           {title}
           <span className="dashboard-row__panel_count">({hiddenPanels} hidden panels)</span>
           <span className="dashboard-row__panel_count">({hiddenPanels} hidden panels)</span>
         </a>
         </a>
-        {config.bootData.user.orgRole !== 'Viewer' && (
+        {this.dashboard.meta.canEdit === true && (
           <div className="dashboard-row__actions">
           <div className="dashboard-row__actions">
             <a className="pointer" onClick={this.openSettings}>
             <a className="pointer" onClick={this.openSettings}>
               <i className="fa fa-cog" />
               <i className="fa fa-cog" />

+ 6 - 8
public/app/features/dashboard/specs/DashboardRow.jest.tsx

@@ -2,17 +2,15 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import { shallow } from 'enzyme';
 import { DashboardRow } from '../dashgrid/DashboardRow';
 import { DashboardRow } from '../dashgrid/DashboardRow';
 import { PanelModel } from '../panel_model';
 import { PanelModel } from '../panel_model';
-import config from '../../../core/config';
 
 
 describe('DashboardRow', () => {
 describe('DashboardRow', () => {
   let wrapper, panel, getPanelContainer, dashboardMock;
   let wrapper, panel, getPanelContainer, dashboardMock;
 
 
   beforeEach(() => {
   beforeEach(() => {
-    dashboardMock = { toggleRow: jest.fn() };
-
-    config.bootData = {
-      user: {
-        orgRole: 'Admin',
+    dashboardMock = {
+      toggleRow: jest.fn(),
+      meta: {
+        canEdit: true,
       },
       },
     };
     };
 
 
@@ -41,8 +39,8 @@ describe('DashboardRow', () => {
     expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(2);
     expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(2);
   });
   });
 
 
-  it('should have zero actions as viewer', () => {
-    config.bootData.user.orgRole = 'Viewer';
+  it('should have zero actions when cannot edit', () => {
+    dashboardMock.meta.canEdit = false;
     panel = new PanelModel({ collapsed: false });
     panel = new PanelModel({ collapsed: false });
     wrapper = shallow(<DashboardRow panel={panel} getPanelContainer={getPanelContainer} />);
     wrapper = shallow(<DashboardRow panel={panel} getPanelContainer={getPanelContainer} />);
     expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(0);
     expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(0);

+ 7 - 1
public/app/plugins/datasource/graphite/datasource.ts

@@ -453,7 +453,13 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
   };
   };
 
 
   this.testDatasource = function() {
   this.testDatasource = function() {
-    return this.metricFindQuery('*').then(function() {
+    let query = {
+      panelId: 3,
+      rangeRaw: { from: 'now-1h', to: 'now' },
+      targets: [{ target: 'constantLine(100)' }],
+      maxDataPoints: 300,
+    };
+    return this.query(query).then(function() {
       return { status: 'success', message: 'Data source is working' };
       return { status: 'success', message: 'Data source is working' };
     });
     });
   };
   };

+ 1 - 0
public/app/stores/PermissionsStore/PermissionsStore.jest.ts

@@ -16,6 +16,7 @@ describe('PermissionsStore', () => {
           permissionName: 'View',
           permissionName: 'View',
           teamId: 1,
           teamId: 1,
           team: 'MyTestTeam',
           team: 'MyTestTeam',
+          inherited: true,
         },
         },
         {
         {
           id: 5,
           id: 5,

+ 0 - 2
public/app/stores/PermissionsStore/PermissionsStore.ts

@@ -224,8 +224,6 @@ const prepareServerResponse = (response, dashboardId: number, isFolder: boolean,
 };
 };
 
 
 const prepareItem = (item, dashboardId: number, isFolder: boolean, isInRoot: boolean) => {
 const prepareItem = (item, dashboardId: number, isFolder: boolean, isInRoot: boolean) => {
-  item.inherited = !isFolder && !isInRoot && dashboardId !== item.dashboardId;
-
   item.sortRank = 0;
   item.sortRank = 0;
   if (item.userId > 0) {
   if (item.userId > 0) {
     item.name = item.userLogin;
     item.name = item.userLogin;

+ 1 - 1
scripts/build/rpmmacros

@@ -1,4 +1,4 @@
 %_signature gpg
 %_signature gpg
-%_gpg_path /home/ubuntu/.gnupg
+%_gpg_path /root/.gnupg
 %_gpg_name Grafana
 %_gpg_name Grafana
 %_gpgbin /usr/bin/gpg
 %_gpgbin /usr/bin/gpg