diff --git a/.circleci/config.yml b/.circleci/config.yml index b6514c4cb47..3e95583ecae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,18 +20,20 @@ jobs: gometalinter: docker: - image: circleci/golang:1.10 + environment: + # we need CGO because of go-sqlite3 + CGO_ENABLED: 1 working_directory: /go/src/github.com/grafana/grafana steps: - checkout + - 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: 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/...' + name: run linters + command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...' test-frontend: docker: @@ -139,6 +141,10 @@ workflows: filters: tags: only: /.*/ + - gometalinter: + filters: + tags: + only: /.*/ - build: filters: tags: diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index 8aa1b7f59b0..f9af60a2efc 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/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 ------------ | ------------- *$__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', 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* diff --git a/docs/sources/http_api/annotations.md b/docs/sources/http_api/annotations.md index 824571422b0..6633714d77b 100644 --- a/docs/sources/http_api/annotations.md +++ b/docs/sources/http_api/annotations.md @@ -36,6 +36,8 @@ Query Parameters: - `alertId`: number. Optional. Find annotations for a specified alert. - `dashboardId`: number. Optional. Find annotations that are scoped to a specific dashboard - `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`. **Example Response**: diff --git a/latest.json b/latest.json index b476f44a00a..5a68ca428b4 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "5.0.0", - "testing": "5.0.0" + "stable": "5.0.4", + "testing": "5.0.4" } diff --git a/package.json b/package.json index ce861a25f7b..c1f2c3e86a9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "5.1.0-pre1", + "version": "5.2.0-pre1", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 886913d6324..fdf577a6a6f 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -2,7 +2,6 @@ package api import ( "strings" - "time" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/components/simplejson" @@ -15,9 +14,10 @@ import ( func GetAnnotations(c *m.ReqContext) Response { query := &annotations.ItemQuery{ - From: c.QueryInt64("from") / 1000, - To: c.QueryInt64("to") / 1000, + From: c.QueryInt64("from"), + To: c.QueryInt64("to"), OrgId: c.OrgId, + UserId: c.QueryInt64("userId"), AlertId: c.QueryInt64("alertId"), DashboardId: c.QueryInt64("dashboardId"), PanelId: c.QueryInt64("panelId"), @@ -37,7 +37,7 @@ func GetAnnotations(c *m.ReqContext) Response { if item.Email != "" { item.AvatarUrl = dtos.GetGravatarUrl(item.Email) } - item.Time = item.Time * 1000 + item.Time = item.Time } return JSON(200, items) @@ -68,16 +68,12 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response { UserId: c.UserId, DashboardId: cmd.DashboardId, PanelId: cmd.PanelId, - Epoch: cmd.Time / 1000, + Epoch: cmd.Time, Text: cmd.Text, Data: cmd.Data, Tags: cmd.Tags, } - if item.Epoch == 0 { - item.Epoch = time.Now().Unix() - } - if err := repo.Save(&item); err != nil { 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.Epoch = cmd.TimeEnd / 1000 + item.Epoch = cmd.TimeEnd if err := repo.Save(&item); err != nil { 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) } - if cmd.When == 0 { - cmd.When = time.Now().Unix() - } text := formatGraphiteAnnotation(cmd.What, cmd.Data) // 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{ OrgId: c.OrgId, UserId: c.UserId, - Epoch: cmd.When, + Epoch: cmd.When * 1000, Text: text, Tags: tagsArray, } @@ -191,7 +184,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response { OrgId: c.OrgId, UserId: c.UserId, Id: annotationID, - Epoch: cmd.Time / 1000, + Epoch: cmd.Time, Text: cmd.Text, Tags: cmd.Tags, } @@ -203,7 +196,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response { if cmd.IsRegion { itemRight := item 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 // ... WHERE region_id = AND id != ... diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 777176f27da..da99bc9ba40 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -100,9 +100,9 @@ func main() { } func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) { + var code int signalChan := make(chan os.Signal, 1) ignoreChan := make(chan os.Signal, 1) - code := 0 signal.Notify(ignoreChan, syscall.SIGHUP) signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM) diff --git a/pkg/components/dashdiffs/compare.go b/pkg/components/dashdiffs/compare.go index d51011fbead..ae940091ed1 100644 --- a/pkg/components/dashdiffs/compare.go +++ b/pkg/components/dashdiffs/compare.go @@ -141,5 +141,9 @@ func getDiff(baseData, newData *simplejson.Json) (interface{}, diff.Diff, error) left := make(map[string]interface{}) err = json.Unmarshal(leftBytes, &left) + if err != nil { + return nil, nil, err + } + return left, jsonDiff, nil } diff --git a/pkg/components/dynmap/dynmap_test.go b/pkg/components/dynmap/dynmap_test.go index fa5f73c3719..62d356bd67d 100644 --- a/pkg/components/dynmap/dynmap_test.go +++ b/pkg/components/dynmap/dynmap_test.go @@ -60,6 +60,7 @@ func TestFirst(t *testing.T) { }` j, err := NewObjectFromBytes([]byte(testJSON)) + assert.True(err == nil, "failed to create new object from bytes") a, err := j.GetObject("address") 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) s, err = address.GetString("street") + assert.True(s == "Street 42" && err == nil, "street mismatching") addressAsString, err := j.GetString("address") 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") element, err := elementValue.Object() + assert.True(err == nil, "create element fail") s, err = element.GetString("street") 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") actions, err := dataItem.GetObjectArray("actions") + assert.True(err == nil, "get object from array failed") for index, action := range actions { diff --git a/pkg/components/imguploader/azureblobuploader_test.go b/pkg/components/imguploader/azureblobuploader_test.go index 570e105b321..ca978f70e3d 100644 --- a/pkg/components/imguploader/azureblobuploader_test.go +++ b/pkg/components/imguploader/azureblobuploader_test.go @@ -13,6 +13,7 @@ func TestUploadToAzureBlob(t *testing.T) { err := setting.NewConfigContext(&setting.CommandLineArgs{ HomePath: "../../../", }) + So(err, ShouldBeNil) uploader, _ := NewImageUploader() diff --git a/pkg/components/imguploader/imguploader_test.go b/pkg/components/imguploader/imguploader_test.go index b0311dac975..b272a45e7a5 100644 --- a/pkg/components/imguploader/imguploader_test.go +++ b/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() { 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("access_key", "access_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() { 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("access_key", "access_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() { 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("access_key", "access_key") s3sec.NewKey("secret_key", "secret_key") uploader, err := NewImageUploader() - So(err, ShouldBeNil) - original, ok := uploader.(*S3Uploader) + original, ok := uploader.(*S3Uploader) So(ok, ShouldBeTrue) So(original.region, ShouldEqual, "us-west-2") So(original.bucket, ShouldEqual, "my.bucket.com") @@ -82,6 +84,7 @@ func TestImageUploaderFactory(t *testing.T) { setting.ImageUploadProvider = "webdav" webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav") + So(err, ShouldBeNil) webdavSec.NewKey("url", "webdavUrl") webdavSec.NewKey("username", "username") webdavSec.NewKey("password", "password") @@ -107,14 +110,14 @@ func TestImageUploaderFactory(t *testing.T) { setting.ImageUploadProvider = "gcs" gcpSec, err := setting.Cfg.GetSection("external_image_storage.gcs") + So(err, ShouldBeNil) gcpSec.NewKey("key_file", "/etc/secrets/project-79a52befa3f6.json") gcpSec.NewKey("bucket", "project-grafana-east") uploader, err := NewImageUploader() - So(err, ShouldBeNil) - original, ok := uploader.(*GCSUploader) + original, ok := uploader.(*GCSUploader) So(ok, ShouldBeTrue) So(original.keyFile, ShouldEqual, "/etc/secrets/project-79a52befa3f6.json") So(original.bucket, ShouldEqual, "project-grafana-east") @@ -128,15 +131,15 @@ func TestImageUploaderFactory(t *testing.T) { Convey("with container name", func() { azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob") + So(err, ShouldBeNil) azureBlobSec.NewKey("account_name", "account_name") azureBlobSec.NewKey("account_key", "account_key") azureBlobSec.NewKey("container_name", "container_name") uploader, err := NewImageUploader() - So(err, ShouldBeNil) - original, ok := uploader.(*AzureBlobUploader) + original, ok := uploader.(*AzureBlobUploader) So(ok, ShouldBeTrue) So(original.account_name, ShouldEqual, "account_name") So(original.account_key, ShouldEqual, "account_key") diff --git a/pkg/components/imguploader/webdavuploader.go b/pkg/components/imguploader/webdavuploader.go index 53d75247c76..f5478ea8a2f 100644 --- a/pkg/components/imguploader/webdavuploader.go +++ b/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) imgData, err := ioutil.ReadFile(pa) + if err != nil { + return "", err + } + req, err := http.NewRequest("PUT", url.String(), bytes.NewReader(imgData)) + if err != nil { + return "", err + } if u.username != "" { req.SetBasicAuth(u.username, u.password) } res, err := netClient.Do(req) - if err != nil { return "", err } diff --git a/pkg/log/file_test.go b/pkg/log/file_test.go index 3e98e0786cc..97a3b8fe82f 100644 --- a/pkg/log/file_test.go +++ b/pkg/log/file_test.go @@ -32,7 +32,9 @@ func TestLogFile(t *testing.T) { Convey("Logging should add lines", func() { err := fileLogWrite.WriteLine("test1\n") + So(err, ShouldBeNil) err = fileLogWrite.WriteLine("test2\n") + So(err, ShouldBeNil) err = fileLogWrite.WriteLine("test3\n") So(err, ShouldBeNil) So(fileLogWrite.maxlines_curlines, ShouldEqual, 3) diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index ccf44f77f23..de530c0cf63 100644 --- a/pkg/login/ldap.go +++ b/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 var groupSearchResult *ldap.SearchResult for _, groupSearchBase := range a.server.GroupSearchBaseDNs { - filter_replace := getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult) + var filter_replace string if a.server.GroupSearchFilterUserAttribute == "" { 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) @@ -346,6 +348,9 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) { } 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 { if attr.Name == name { if len(attr.Values) > 0 { diff --git a/pkg/models/dashboard_acl.go b/pkg/models/dashboard_acl.go index 4ef8061486b..5fc09bd16b5 100644 --- a/pkg/models/dashboard_acl.go +++ b/pkg/models/dashboard_acl.go @@ -69,6 +69,7 @@ type DashboardAclInfoDTO struct { Slug string `json:"slug"` IsFolder bool `json:"isFolder"` Url string `json:"url"` + Inherited bool `json:"inherited"` } func (dto *DashboardAclInfoDTO) hasSameRoleAs(other *DashboardAclInfoDTO) bool { diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index 0b8efe808d5..1e62c68d7eb 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -3,13 +3,14 @@ package notifiers import ( "bytes" "fmt" + "io" + "mime/multipart" + "os" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" - "io" - "mime/multipart" - "os" ) const ( @@ -133,6 +134,9 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval } ruleUrl, err := evalContext.GetRuleUrl() + if err != nil { + return nil, err + } metrics := generateMetricsMessage(evalContext) message := generateImageCaption(evalContext, ruleUrl, metrics) diff --git a/pkg/services/alerting/notifiers/victorops.go b/pkg/services/alerting/notifiers/victorops.go index 4b4db553cde..a753ca3cbf6 100644 --- a/pkg/services/alerting/notifiers/victorops.go +++ b/pkg/services/alerting/notifiers/victorops.go @@ -83,27 +83,6 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { 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 if evalContext.Rule.State == models.AlertStateAlerting { // translate 'Alerting' to 'CRITICAL' (Victorops analog) messageType = AlertStateCritical diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 5d95e090c9e..0c92fc32110 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -77,7 +77,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { Text: "", NewState: string(evalContext.Rule.State), PrevState: string(evalContext.PrevAlertState), - Epoch: time.Now().Unix(), + Epoch: time.Now().UnixNano() / int64(time.Millisecond), Data: annotationData, } diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index a6cd7a33318..5cebb3d2df9 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -13,6 +13,7 @@ type ItemQuery struct { OrgId int64 `json:"orgId"` From int64 `json:"from"` To int64 `json:"to"` + UserId int64 `json:"userId"` AlertId int64 `json:"alertId"` DashboardId int64 `json:"dashboardId"` PanelId int64 `json:"panelId"` @@ -63,6 +64,8 @@ type Item struct { PrevState string `json:"prevState"` NewState string `json:"newState"` Epoch int64 `json:"epoch"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` Tags []string `json:"tags"` Data *simplejson.Json `json:"data"` @@ -80,6 +83,8 @@ type ItemDTO struct { UserId int64 `json:"userId"` NewState string `json:"newState"` PrevState string `json:"prevState"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` Time int64 `json:"time"` Text string `json:"text"` RegionId int64 `json:"regionId"` diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index 700f22d8d26..bf455adc7ca 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -154,12 +154,7 @@ func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.Permiss // validate overridden permissions to be higher for _, a := range acl { for _, existingPerm := range existingPermissions { - // handle default permissions - if existingPerm.DashboardId == -1 { - existingPerm.DashboardId = g.dashId - } - - if a.DashboardId == existingPerm.DashboardId { + if !existingPerm.Inherited { continue } @@ -187,13 +182,6 @@ func (g *dashboardGuardianImpl) GetAcl() ([]*m.DashboardAclInfoDTO, error) { return nil, err } - for _, a := range query.Result { - // handle default permissions - if a.DashboardId == -1 { - a.DashboardId = g.dashId - } - } - g.acl = query.Result return g.acl, nil } diff --git a/pkg/services/guardian/guardian_test.go b/pkg/services/guardian/guardian_test.go index 9de12c60fea..5e56b1d88c3 100644 --- a/pkg/services/guardian/guardian_test.go +++ b/pkg/services/guardian/guardian_test.go @@ -217,13 +217,13 @@ func (sc *scenarioContext) parentFolderPermissionScenario(pt permissionType, per switch pt { 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: - 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: - 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: - 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) { @@ -649,6 +649,9 @@ func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShou } _, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList) + if err != nil { + sc.reportFailure(tc, nil, err) + } sc.updatePermissions = permissionList ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList) diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index de0a49d34d9..7d4231deeae 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/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) { checkFilepath, err := filepath.EvalSymlinks(path) if path != checkFilepath { - path = checkFilepath fi, err := os.Lstat(checkFilepath) if err != nil { return nil, err diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 076c0e250b6..1066be0ef74 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/grafana/grafana/pkg/models" "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 { tags := models.ParseTagPairs(item.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 { return err } @@ -79,6 +86,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { return errors.New("Annotation not found") } + existing.Updated = time.Now().UnixNano() / int64(time.Millisecond) existing.Epoch = item.Epoch existing.Text = item.Text if item.RegionId != 0 { @@ -102,7 +110,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { 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 }) } @@ -124,6 +132,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I annotation.text, annotation.tags, annotation.data, + annotation.created, + annotation.updated, usr.email, usr.login, alert.name as alert_name @@ -161,6 +171,11 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I 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 { sql.WriteString(` AND annotation.epoch BETWEEN ? AND ?`) params = append(params, query.From, query.To) @@ -168,6 +183,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I if query.Type == "alert" { sql.WriteString(` AND annotation.alert_id > 0`) + } else if query.Type == "annotation" { + sql.WriteString(` AND annotation.alert_id = 0`) } if len(query.Tags) > 0 { diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index d5cee110b9a..949ed8135ba 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -79,6 +79,12 @@ func TestAnnotations(t *testing.T) { Convey("Can read tags", func() { 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() { @@ -231,6 +237,10 @@ func TestAnnotations(t *testing.T) { So(items[0].Tags, ShouldResemble, []string{"newtag1", "newtag2"}) 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() { @@ -246,6 +256,7 @@ func TestAnnotations(t *testing.T) { annotationId := items[0].Id err = repo.Delete(&annotations.DeleteParams{Id: annotationId}) + So(err, ShouldBeNil) items, err = repo.Find(query) So(err, ShouldBeNil) diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index c0848f08863..4238967417f 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -77,7 +77,7 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error { } parentVersion := dash.Version - affectedRows := int64(0) + var affectedRows int64 var err error if dash.Id == 0 { diff --git a/pkg/services/sqlstore/dashboard_acl.go b/pkg/services/sqlstore/dashboard_acl.go index 2034ccf0d30..0b195c4562b 100644 --- a/pkg/services/sqlstore/dashboard_acl.go +++ b/pkg/services/sqlstore/dashboard_acl.go @@ -67,7 +67,8 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { '' as title, '' as slug, '' as uid,` + - falseStr + ` AS is_folder + falseStr + ` AS is_folder,` + + falseStr + ` AS inherited FROM dashboard_acl as da WHERE da.dashboard_id = -1` query.Result = make([]*m.DashboardAclInfoDTO, 0) @@ -94,7 +95,8 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { d.title, d.slug, 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 LEFT JOIN dashboard folder on folder.id = d.folder_id LEFT JOIN dashboard_acl AS da ON diff --git a/pkg/services/sqlstore/dashboard_acl_test.go b/pkg/services/sqlstore/dashboard_acl_test.go index 8fbb9c0d813..a034a0565a3 100644 --- a/pkg/services/sqlstore/dashboard_acl_test.go +++ b/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("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() { query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1} @@ -36,8 +52,10 @@ func TestDashboardAclDataAccess(t *testing.T) { defaultPermissionsId := -1 So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId) 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].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(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].Inherited, ShouldBeFalse) }) }) }) @@ -118,9 +138,12 @@ func TestDashboardAclDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 3) So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId) 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].Role, ShouldEqual, m.ROLE_EDITOR) + So(query.Result[1].Inherited, ShouldBeTrue) 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, Permission: m.PERMISSION_EDIT, }) + So(err, ShouldBeNil) q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1} err = GetDashboardAclInfoList(q1) @@ -209,8 +233,10 @@ func TestDashboardAclDataAccess(t *testing.T) { 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) }) }) }) diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 8d2bf94bc42..7fac0001e5b 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -90,4 +90,29 @@ func addAnnotationMig(mg *Migrator) { Sqlite(updateTextFieldSql). Postgres(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)) } diff --git a/pkg/services/sqlstore/migrations/migrations_test.go b/pkg/services/sqlstore/migrations/migrations_test.go index 8e8e4af824f..53b398124af 100644 --- a/pkg/services/sqlstore/migrations/migrations_test.go +++ b/pkg/services/sqlstore/migrations/migrations_test.go @@ -27,7 +27,7 @@ func TestMigrations(t *testing.T) { sqlutil.CleanDB(x) - has, err := x.SQL(sql).Get(&r) + _, err = x.SQL(sql).Get(&r) So(err, ShouldNotBeNil) mg := NewMigrator(x) @@ -36,7 +36,7 @@ func TestMigrations(t *testing.T) { err = mg.Start() So(err, ShouldBeNil) - has, err = x.SQL(sql).Get(&r) + has, err := x.SQL(sql).Get(&r) So(err, ShouldBeNil) 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 diff --git a/pkg/services/sqlstore/playlist.go b/pkg/services/sqlstore/playlist.go index 67720cbadb8..7b726880b9e 100644 --- a/pkg/services/sqlstore/playlist.go +++ b/pkg/services/sqlstore/playlist.go @@ -22,6 +22,9 @@ func CreatePlaylist(cmd *m.CreatePlaylistCommand) error { } _, err := x.Insert(&playlist) + if err != nil { + return err + } playlistItems := make([]m.PlaylistItem, 0) for _, item := range cmd.Items { diff --git a/pkg/services/sqlstore/plugin_setting.go b/pkg/services/sqlstore/plugin_setting.go index 312a3bda523..f694fbbd5f0 100644 --- a/pkg/services/sqlstore/plugin_setting.go +++ b/pkg/services/sqlstore/plugin_setting.go @@ -48,6 +48,9 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error { var pluginSetting m.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("pinned") if !exists { diff --git a/pkg/services/sqlstore/preferences.go b/pkg/services/sqlstore/preferences.go index 399b23f3ffa..a070fa621b5 100644 --- a/pkg/services/sqlstore/preferences.go +++ b/pkg/services/sqlstore/preferences.go @@ -72,6 +72,9 @@ func SavePreferences(cmd *m.SavePreferencesCommand) error { var prefs m.Preferences exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&prefs) + if err != nil { + return err + } if !exists { prefs = m.Preferences{ diff --git a/pkg/services/sqlstore/team_test.go b/pkg/services/sqlstore/team_test.go index f136411eeba..f4b022906da 100644 --- a/pkg/services/sqlstore/team_test.go +++ b/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() { groupId := group2.Result.Id err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]}) + So(err, ShouldBeNil) query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]} err = GetTeamsByUser(query) @@ -103,7 +104,7 @@ func TestTeamCommandsAndQueries(t *testing.T) { err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]}) So(err, ShouldBeNil) 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}) So(err, ShouldBeNil) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 1546fb83b21..5e2efbd7fde 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -168,11 +168,9 @@ func GetUserByLogin(query *m.GetUserByLoginQuery) error { return m.ErrUserNotFound } - user := new(m.User) - // Try and find the user by login first. // 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) if err != nil { @@ -202,9 +200,7 @@ func GetUserByEmail(query *m.GetUserByEmailQuery) error { return m.ErrUserNotFound } - user := new(m.User) - - user = &m.User{Email: query.Email} + user := &m.User{Email: query.Email} has, err := x.Get(user) if err != nil { diff --git a/pkg/services/sqlstore/user_auth_test.go b/pkg/services/sqlstore/user_auth_test.go index 279fd7aa0f5..882e0c7afa5 100644 --- a/pkg/services/sqlstore/user_auth_test.go +++ b/pkg/services/sqlstore/user_auth_test.go @@ -32,7 +32,7 @@ func TestUserAuth(t *testing.T) { So(err, ShouldBeNil) _, err = x.Exec("DELETE FROM org WHERE 1=1") 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) _, err = x.Exec("DELETE FROM user_auth WHERE 1=1") So(err, ShouldBeNil) @@ -117,7 +117,7 @@ func TestUserAuth(t *testing.T) { So(query.Result.Login, ShouldEqual, "loginuser1") // 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) // get via user_auth for deleted user diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index d98805c661d..499a3ed6e03 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/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)) { period, err = strconv.Atoi(p) if err != nil { diff --git a/pkg/tsdb/influxdb/model_parser.go b/pkg/tsdb/influxdb/model_parser.go index deb2f15e3ce..f1113511bae 100644 --- a/pkg/tsdb/influxdb/model_parser.go +++ b/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) + if err != nil { + return nil, err + } return &Query{ Measurement: measurement, diff --git a/pkg/tsdb/influxdb/query.go b/pkg/tsdb/influxdb/query.go index 0a16a507877..9fbe133c055 100644 --- a/pkg/tsdb/influxdb/query.go +++ b/pkg/tsdb/influxdb/query.go @@ -62,9 +62,8 @@ func (query *Query) renderTags() []string { } } - textValue := "" - // quote value unless regex or number + var textValue string if tag.Operator == "=~" || tag.Operator == "!~" { textValue = tag.Value } else if tag.Operator == "<" || tag.Operator == ">" { @@ -107,7 +106,7 @@ func (query *Query) renderSelectors(queryContext *tsdb.TsdbQuery) string { } func (query *Query) renderMeasurement() string { - policy := "" + var policy string if query.Policy == "" || query.Policy == "default" { policy = "" } else { diff --git a/pkg/tsdb/opentsdb/opentsdb.go b/pkg/tsdb/opentsdb/opentsdb.go index 692b891eddd..16da764de54 100644 --- a/pkg/tsdb/opentsdb/opentsdb.go +++ b/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") 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))) if err != nil { diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index ee1108a6998..b0158525d52 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/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); }; - const inheritedFromRoot = item.dashboardId === -1 && folderInfo && folderInfo.id === 0; + const inheritedFromRoot = item.dashboardId === -1 && !item.inherited; return ( diff --git a/public/app/features/dashboard/dashgrid/DashboardRow.tsx b/public/app/features/dashboard/dashgrid/DashboardRow.tsx index c2a84cb7da9..b133d4450bb 100644 --- a/public/app/features/dashboard/dashgrid/DashboardRow.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardRow.tsx @@ -4,7 +4,6 @@ import { PanelModel } from '../panel_model'; import { PanelContainer } from './PanelContainer'; import templateSrv from 'app/features/templating/template_srv'; import appEvents from 'app/core/app_events'; -import config from 'app/core/config'; export interface DashboardRowProps { panel: PanelModel; @@ -95,7 +94,7 @@ export class DashboardRow extends React.Component { {title} ({hiddenPanels} hidden panels) - {config.bootData.user.orgRole !== 'Viewer' && ( + {this.dashboard.meta.canEdit === true && (
diff --git a/public/app/features/dashboard/specs/DashboardRow.jest.tsx b/public/app/features/dashboard/specs/DashboardRow.jest.tsx index c0ac172aa26..8424346b0c5 100644 --- a/public/app/features/dashboard/specs/DashboardRow.jest.tsx +++ b/public/app/features/dashboard/specs/DashboardRow.jest.tsx @@ -2,17 +2,15 @@ import React from 'react'; import { shallow } from 'enzyme'; import { DashboardRow } from '../dashgrid/DashboardRow'; import { PanelModel } from '../panel_model'; -import config from '../../../core/config'; describe('DashboardRow', () => { let wrapper, panel, getPanelContainer, dashboardMock; 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); }); - 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 }); wrapper = shallow(); expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(0); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 82205f1754e..0b79673a14c 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -453,7 +453,13 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv }; 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' }; }); }; diff --git a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts index d6a20e25846..6d88401e0d6 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts @@ -16,6 +16,7 @@ describe('PermissionsStore', () => { permissionName: 'View', teamId: 1, team: 'MyTestTeam', + inherited: true, }, { id: 5, diff --git a/public/app/stores/PermissionsStore/PermissionsStore.ts b/public/app/stores/PermissionsStore/PermissionsStore.ts index 833d1bdaac7..95d63c8527a 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.ts +++ b/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) => { - item.inherited = !isFolder && !isInRoot && dashboardId !== item.dashboardId; - item.sortRank = 0; if (item.userId > 0) { item.name = item.userLogin; diff --git a/scripts/build/rpmmacros b/scripts/build/rpmmacros index a91ba9b8290..c00c8ec2eee 100644 --- a/scripts/build/rpmmacros +++ b/scripts/build/rpmmacros @@ -1,4 +1,4 @@ %_signature gpg -%_gpg_path /home/ubuntu/.gnupg +%_gpg_path /root/.gnupg %_gpg_name Grafana %_gpgbin /usr/bin/gpg