From 380aa26ea37000adc1bf5a92bf49d4496f0a8320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BERNARD?= Date: Wed, 7 Mar 2018 18:14:18 +0100 Subject: [PATCH 01/41] Fix the code to match the documentation. Permit for LDAP groups to be groupofuniquenames composed of uniquename (DN). For this, propose DN as group_search_filter_user_attribute and DN also for the member_of in the server.attributes section. DN is processed as a special attribute name which returns the LdapSearchResult.DN field instead of a member of attr array. --- pkg/login/ldap.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index be3babac02e..3bb63a2c28e 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -404,9 +404,11 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) { var groupSearchResult *ldap.SearchResult for _, groupSearchBase := range a.server.GroupSearchBaseDNs { var filter_replace string - filter_replace = getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult) + 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) @@ -448,6 +450,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 { From abef722265b0199133d64ccb683a0be00ab87a0a Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Wed, 7 Mar 2018 14:41:05 -0500 Subject: [PATCH 02/41] Fix indent --- pkg/login/ldap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index 3bb63a2c28e..bc5fe13dba3 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -450,7 +450,7 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) { } func getLdapAttrN(name string, result *ldap.SearchResult, n int) string { - if name == "DN" { + if name == "DN" { return result.Entries[0].DN } for _, attr := range result.Entries[n].Attributes { From 3898ea02e60c2811feec65e8ed4c32fea862b632 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 02:22:58 +0100 Subject: [PATCH 03/41] adding created column --- pkg/api/annotations.go | 1 + pkg/services/annotations/annotations.go | 3 +++ pkg/services/sqlstore/annotation.go | 15 ++++++++++++++- .../sqlstore/migrations/annotation_mig.go | 10 ++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index fb75e0bf129..e5a97f340bf 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -24,6 +24,7 @@ func GetAnnotations(c *m.ReqContext) Response { Limit: c.QueryInt64("limit"), Tags: c.QueryStrings("tags"), Type: c.Query("type"), + Sort: c.Query("sort"), } repo := annotations.GetRepository() diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index a6cd7a33318..fd178176ef1 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -20,6 +20,7 @@ type ItemQuery struct { RegionId int64 `json:"regionId"` Tags []string `json:"tags"` Type string `json:"type"` + Sort string `json:"sort"` Limit int64 `json:"limit"` } @@ -63,6 +64,7 @@ type Item struct { PrevState string `json:"prevState"` NewState string `json:"newState"` Epoch int64 `json:"epoch"` + Created int64 `json:"created"` Tags []string `json:"tags"` Data *simplejson.Json `json:"data"` @@ -80,6 +82,7 @@ type ItemDTO struct { UserId int64 `json:"userId"` NewState string `json:"newState"` PrevState string `json:"prevState"` + Created int64 `json:"created"` Time int64 `json:"time"` Text string `json:"text"` RegionId int64 `json:"regionId"` diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 76f1819a18c..65f2abd9a54 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,7 @@ 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) if _, err := sess.Table("annotation").Insert(item); err != nil { return err } @@ -127,6 +129,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I annotation.text, annotation.tags, annotation.data, + annotation.created, usr.email, usr.login, alert.name as alert_name @@ -205,7 +208,17 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I query.Limit = 10 } - sql.WriteString(fmt.Sprintf(" ORDER BY epoch DESC LIMIT %v", query.Limit)) + var sort string = "epoch DESC" + switch query.Sort { + case "time.asc": + sort = "epoch ASC" + case "created": + sort = "annotation.created DESC" + case "created.asc": + sort = "annotation.created ASC" + } + + sql.WriteString(fmt.Sprintf(" ORDER BY %s LIMIT %v", sort, query.Limit)) items := make([]*annotations.ItemDTO, 0) diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 8d2bf94bc42..24e2beb2eda 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -90,4 +90,14 @@ func addAnnotationMig(mg *Migrator) { Sqlite(updateTextFieldSql). Postgres(updateTextFieldSql). Mysql(updateTextFieldSql)) + + // + // Add a 'created' column + // + mg.AddMigration("Add created time to annotation table", NewAddColumnMigration(table, &Column{ + Name: "created", 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, + })) } From a2bbd89a9ebb73cd445bc920b0dbda02aa2cb31d Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 15:52:09 +0100 Subject: [PATCH 04/41] adding updated column --- CHANGELOG.md | 1 + docs/sources/http_api/annotations.md | 2 ++ pkg/api/annotations.go | 2 +- pkg/services/annotations/annotations.go | 4 ++- pkg/services/sqlstore/annotation.go | 26 +++++++++++-------- .../sqlstore/migrations/annotation_mig.go | 8 +++++- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 304b1ba6d0b..001433fa652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * **Alerting**: Support Pagerduty notification channel using Pagerduty V2 API [#10531](https://github.com/grafana/grafana/issues/10531), thx [@jbaublitz](https://github.com/jbaublitz) * **Templating**: Add comma templating format [#10632](https://github.com/grafana/grafana/issues/10632), thx [@mtanda](https://github.com/mtanda) * **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda) +* **Annotations API**: Record creation/update times and add more query options [#11333](https://github.com/grafana/grafana/pull/11333), thx [@mtanda](https://github.com/ryantxu) ### Minor * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes) diff --git a/docs/sources/http_api/annotations.md b/docs/sources/http_api/annotations.md index 19c2a5c386c..c26b7d72a4b 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/pkg/api/annotations.go b/pkg/api/annotations.go index 123a8432f13..5762d56548a 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -18,13 +18,13 @@ func GetAnnotations(c *m.ReqContext) Response { From: c.QueryInt64("from") / 1000, To: c.QueryInt64("to") / 1000, OrgId: c.OrgId, + UserId: c.QueryInt64("userId"), AlertId: c.QueryInt64("alertId"), DashboardId: c.QueryInt64("dashboardId"), PanelId: c.QueryInt64("panelId"), Limit: c.QueryInt64("limit"), Tags: c.QueryStrings("tags"), Type: c.Query("type"), - Sort: c.Query("sort"), } repo := annotations.GetRepository() diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index fd178176ef1..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"` @@ -20,7 +21,6 @@ type ItemQuery struct { RegionId int64 `json:"regionId"` Tags []string `json:"tags"` Type string `json:"type"` - Sort string `json:"sort"` Limit int64 `json:"limit"` } @@ -65,6 +65,7 @@ type Item struct { 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"` @@ -83,6 +84,7 @@ type ItemDTO struct { 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/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 65f2abd9a54..ebba2083576 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -15,10 +15,14 @@ type SqlAnnotationRepo struct { } func (r *SqlAnnotationRepo) Save(item *annotations.Item) error { + if item.DashboardId == 0 { + return errors.New("Annotation is missing dashboard_id") + } 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 _, err := sess.Table("annotation").Insert(item); err != nil { return err } @@ -66,6 +70,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { err error ) existing := new(annotations.Item) + item.Updated = time.Now().UnixNano() / int64(time.Millisecond) if item.Id == 0 && item.RegionId != 0 { // Update region end time @@ -130,6 +135,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I annotation.tags, annotation.data, annotation.created, + annotation.updated, usr.email, usr.login, alert.name as alert_name @@ -167,6 +173,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) @@ -175,6 +186,9 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I if query.Type == "alert" { sql.WriteString(` AND annotation.alert_id > 0`) } + if query.Type == "annotation" { + sql.WriteString(` AND annotation.alert_id = 0`) + } if len(query.Tags) > 0 { keyValueFilters := []string{} @@ -208,17 +222,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I query.Limit = 10 } - var sort string = "epoch DESC" - switch query.Sort { - case "time.asc": - sort = "epoch ASC" - case "created": - sort = "annotation.created DESC" - case "created.asc": - sort = "annotation.created ASC" - } - - sql.WriteString(fmt.Sprintf(" ORDER BY %s LIMIT %v", sort, query.Limit)) + sql.WriteString(fmt.Sprintf(" ORDER BY epoch DESC LIMIT %v", query.Limit)) items := make([]*annotations.ItemDTO, 0) diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 24e2beb2eda..11cc986d669 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -92,12 +92,18 @@ func addAnnotationMig(mg *Migrator) { Mysql(updateTextFieldSql)) // - // Add a 'created' column + // 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, + })) } From 20353db9660fdc3df31bd85041f87f2c954dd8dd Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 16:21:47 +0100 Subject: [PATCH 05/41] convert epoch to milliseconds --- pkg/api/annotations.go | 22 ++++++------------- pkg/services/sqlstore/annotation.go | 9 +++++--- .../sqlstore/migrations/annotation_mig.go | 9 ++++++++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 5762d56548a..e17cabb01a1 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,8 +14,8 @@ 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"), @@ -38,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) @@ -69,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 ApiError(500, "Failed to save annotation", err) } @@ -98,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 ApiError(500, "Failed save annotation for region end time", err) @@ -133,9 +128,6 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd return ApiError(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) @@ -192,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, } @@ -204,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/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index ebba2083576..5906be3736b 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -23,6 +23,10 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error { 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 } @@ -70,7 +74,6 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { err error ) existing := new(annotations.Item) - item.Updated = time.Now().UnixNano() / int64(time.Millisecond) if item.Id == 0 && item.RegionId != 0 { // Update region end time @@ -86,6 +89,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 { @@ -185,8 +189,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I if query.Type == "alert" { sql.WriteString(` AND annotation.alert_id > 0`) - } - if query.Type == "annotation" { + } else if query.Type == "annotation" { sql.WriteString(` AND annotation.alert_id = 0`) } diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 11cc986d669..89fccad0d09 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -106,4 +106,13 @@ func addAnnotationMig(mg *Migrator) { 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)" + mg.AddMigration("Convert existing annotations from seconds to miliseconds", new(RawSqlMigration). + Sqlite(updateEpochSql). + Postgres(updateEpochSql). + Mysql(updateEpochSql)) } From db91033b6e2fa09269f6a9ce4983957c46290914 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 19:33:33 +0100 Subject: [PATCH 06/41] adding tests, but they arent running locally --- pkg/services/sqlstore/annotation_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index d5cee110b9a..e76e1802b75 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].created, ShouldBeEqual, items[1].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() { From fa021b547a4a455e54d979096d23d91e2d7d3835 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 19:39:30 +0100 Subject: [PATCH 07/41] using circle as my tester --- pkg/services/sqlstore/annotation_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index e76e1802b75..8a12c092cbe 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -81,9 +81,9 @@ func TestAnnotations(t *testing.T) { }) Convey("Has created and updated values", func() { - So(items[0].created, ShouldBeGreaterThan, 0) - So(items[0].updated, ShouldBeGreaterThan, 0) - So(items[0].created, ShouldBeEqual, items[1].created) + So(items[0].Created, ShouldBeGreaterThan, 0) + So(items[0].Updated, ShouldBeGreaterThan, 0) + So(items[0].Updated, ShouldBeEqual, items[1].Created) }) }) @@ -239,7 +239,7 @@ func TestAnnotations(t *testing.T) { }) Convey("Updated time has increased", func() { - So(items[0].updated, ShouldBeGreaterThan, items[0].created) + So(items[0].Updated, ShouldBeGreaterThan, items[0].Created) }) }) From d554c6f9be97818b5df17ed19a23ec5cde9f611a Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 19:44:47 +0100 Subject: [PATCH 08/41] using circle as my tester --- pkg/services/sqlstore/annotation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index 8a12c092cbe..c8d733b5ae9 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -83,7 +83,7 @@ func TestAnnotations(t *testing.T) { Convey("Has created and updated values", func() { So(items[0].Created, ShouldBeGreaterThan, 0) So(items[0].Updated, ShouldBeGreaterThan, 0) - So(items[0].Updated, ShouldBeEqual, items[1].Created) + So(items[0].Updated, ShouldEqual, items[1].Created) }) }) From 0c7294593cf58c7b249ce6429ae47d4e2cebfc9a Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 20:05:04 +0100 Subject: [PATCH 09/41] update the updated column! --- pkg/services/sqlstore/annotation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 5906be3736b..0ad531a1dd6 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -113,7 +113,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { existing.Tags = item.Tags - if _, err := sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "tags").Update(existing); err != nil { + if _, err := sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "updated", "tags").Update(existing); err != nil { return err } From 164ddb16c930bd3edfebdd9021ec7e8e3f393154 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 22 Mar 2018 20:48:40 +0100 Subject: [PATCH 10/41] dooh --- pkg/services/sqlstore/annotation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index c8d733b5ae9..5af5f271993 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -83,7 +83,7 @@ func TestAnnotations(t *testing.T) { 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[1].Created) + So(items[0].Updated, ShouldEqual, items[0].Created) }) }) From db92a96067463258516b171e0b8946fb39dcf4ff Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 11:36:44 +0100 Subject: [PATCH 11/41] move dashboard error to API (not sql) --- pkg/api/annotations.go | 5 +++++ pkg/api/annotations_test.go | 29 +++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index e17cabb01a1..2c303f22b2b 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -63,6 +63,11 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response { return ApiError(500, "Failed to save annotation", err) } + if cmd.DashboardId == 0 { + err := &CreateAnnotationError{"Missing DashboardID"} + return ApiError(500, "Failed to save annotation", err) + } + item := annotations.Item{ OrgId: c.OrgId, UserId: c.UserId, diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 7c298550673..bb891e012d2 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -14,10 +14,11 @@ import ( func TestAnnotationsApiEndpoint(t *testing.T) { Convey("Given an annotation without a dashboard id", t, func() { cmd := dtos.PostAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - IsRegion: false, + DashboardId: 1, + Time: 1000, + Text: "annotation text", + Tags: []string{"tag1", "tag2"}, + IsRegion: false, } updateCmd := dtos.UpdateAnnotationsCmd{ @@ -79,6 +80,26 @@ func TestAnnotationsApiEndpoint(t *testing.T) { So(sc.resp.Code, ShouldEqual, 200) }) }) + + Convey("Should note be able to save an annotation", func() { + cmd := dtos.PostAnnotationsCmd{ + Time: 1000, + Text: "annotation text", + } + postAnnotationScenario("When calling POST without dashboardId", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { + sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() + So(sc.resp.Code, ShouldEqual, 500) + }) + + cmd := dtos.PostAnnotationsCmd{ + Time: 1000, + DashboardId: 3, + } + postAnnotationScenario("When calling POST without text", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { + sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() + So(sc.resp.Code, ShouldEqual, 500) + }) + }) }) }) From a0a6fa6fa54932b05bd5653504ef725661e23387 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 11:47:07 +0100 Subject: [PATCH 12/41] remove constraint from sqlstore --- pkg/services/sqlstore/annotation.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 0ad531a1dd6..502ebbd3d02 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -15,9 +15,6 @@ type SqlAnnotationRepo struct { } func (r *SqlAnnotationRepo) Save(item *annotations.Item) error { - if item.DashboardId == 0 { - return errors.New("Annotation is missing dashboard_id") - } return inTransaction(func(sess *DBSession) error { tags := models.ParseTagPairs(item.Tags) item.Tags = models.JoinTagPairs(tags) From b39fb7fdd55a3389807c0db10cf2d14389adb0fb Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 12:01:21 +0100 Subject: [PATCH 13/41] fix operator --- pkg/api/annotations_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index bb891e012d2..8e09b4a41a6 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -82,7 +82,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) { }) Convey("Should note be able to save an annotation", func() { - cmd := dtos.PostAnnotationsCmd{ + cmd = dtos.PostAnnotationsCmd{ Time: 1000, Text: "annotation text", } @@ -91,7 +91,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) { So(sc.resp.Code, ShouldEqual, 500) }) - cmd := dtos.PostAnnotationsCmd{ + cmd = dtos.PostAnnotationsCmd{ Time: 1000, DashboardId: 3, } From 14b737e662a004a26f9d5a949b3d3dd770d4bc0e Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 12:08:32 +0100 Subject: [PATCH 14/41] update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d601469be0a..afcd16c9ef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * **Alerting**: Support Pagerduty notification channel using Pagerduty V2 API [#10531](https://github.com/grafana/grafana/issues/10531), thx [@jbaublitz](https://github.com/jbaublitz) * **Templating**: Add comma templating format [#10632](https://github.com/grafana/grafana/issues/10632), thx [@mtanda](https://github.com/mtanda) * **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda) -* **Annotations API**: Record creation/update times and add more query options [#11333](https://github.com/grafana/grafana/pull/11333), thx [@mtanda](https://github.com/ryantxu) +* **Annotations API**: Save creation/update times and add more query options [#11333](https://github.com/grafana/grafana/pull/11333), thx [@ryantxu](https://github.com/ryantxu) ### Minor * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes) From a58b4ff2d636daa6f096caa269510db997465085 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 12:13:38 +0100 Subject: [PATCH 15/41] remove api tests --- pkg/api/annotations_test.go | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 8e09b4a41a6..7c298550673 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -14,11 +14,10 @@ import ( func TestAnnotationsApiEndpoint(t *testing.T) { Convey("Given an annotation without a dashboard id", t, func() { cmd := dtos.PostAnnotationsCmd{ - DashboardId: 1, - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - IsRegion: false, + Time: 1000, + Text: "annotation text", + Tags: []string{"tag1", "tag2"}, + IsRegion: false, } updateCmd := dtos.UpdateAnnotationsCmd{ @@ -80,26 +79,6 @@ func TestAnnotationsApiEndpoint(t *testing.T) { So(sc.resp.Code, ShouldEqual, 200) }) }) - - Convey("Should note be able to save an annotation", func() { - cmd = dtos.PostAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - } - postAnnotationScenario("When calling POST without dashboardId", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { - sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() - So(sc.resp.Code, ShouldEqual, 500) - }) - - cmd = dtos.PostAnnotationsCmd{ - Time: 1000, - DashboardId: 3, - } - postAnnotationScenario("When calling POST without text", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { - sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() - So(sc.resp.Code, ShouldEqual, 500) - }) - }) }) }) From 2116152295332b6d29f1145e530c4419e9094729 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 12:35:39 +0100 Subject: [PATCH 16/41] add dashboardId to test --- pkg/api/annotations_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 7c298550673..02878750b28 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -14,10 +14,11 @@ import ( func TestAnnotationsApiEndpoint(t *testing.T) { Convey("Given an annotation without a dashboard id", t, func() { cmd := dtos.PostAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - IsRegion: false, + Time: 1000, + Text: "annotation text", + Tags: []string{"tag1", "tag2"}, + IsRegion: false, + DashboardId: 1, } updateCmd := dtos.UpdateAnnotationsCmd{ From e92ea79524f6fa5aac85c4bac9ecc9792d1c2bc2 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 12:48:03 +0100 Subject: [PATCH 17/41] get circle to run tests again --- pkg/api/annotations_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 02878750b28..94dfec10ddb 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -18,7 +18,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) { Text: "annotation text", Tags: []string{"tag1", "tag2"}, IsRegion: false, - DashboardId: 1, + DashboardId: 5, } updateCmd := dtos.UpdateAnnotationsCmd{ From 7defb1adf583de6d086fde2c16475523c4c13dc0 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 23 Mar 2018 12:54:53 +0100 Subject: [PATCH 18/41] remove dashboardId check... i can't figure out how the tests work --- pkg/api/annotations.go | 5 ----- pkg/api/annotations_test.go | 9 ++++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 2c303f22b2b..e17cabb01a1 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -63,11 +63,6 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response { return ApiError(500, "Failed to save annotation", err) } - if cmd.DashboardId == 0 { - err := &CreateAnnotationError{"Missing DashboardID"} - return ApiError(500, "Failed to save annotation", err) - } - item := annotations.Item{ OrgId: c.OrgId, UserId: c.UserId, diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 94dfec10ddb..7c298550673 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -14,11 +14,10 @@ import ( func TestAnnotationsApiEndpoint(t *testing.T) { Convey("Given an annotation without a dashboard id", t, func() { cmd := dtos.PostAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - IsRegion: false, - DashboardId: 5, + Time: 1000, + Text: "annotation text", + Tags: []string{"tag1", "tag2"}, + IsRegion: false, } updateCmd := dtos.UpdateAnnotationsCmd{ From eabcbcda88f7118a4fd381fceee547ddd3f008f2 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 24 Mar 2018 11:39:20 +0100 Subject: [PATCH 19/41] remove README changes --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afcd16c9ef3..1df6266c763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * **Alerting**: Support Pagerduty notification channel using Pagerduty V2 API [#10531](https://github.com/grafana/grafana/issues/10531), thx [@jbaublitz](https://github.com/jbaublitz) * **Templating**: Add comma templating format [#10632](https://github.com/grafana/grafana/issues/10632), thx [@mtanda](https://github.com/mtanda) * **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda) -* **Annotations API**: Save creation/update times and add more query options [#11333](https://github.com/grafana/grafana/pull/11333), thx [@ryantxu](https://github.com/ryantxu) +* **Alerting**: Add support for retries on alert queries [#5855](https://github.com/grafana/grafana/issues/5855), thx [@Thib17](https://github.com/Thib17) ### Minor * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes) From 66d020eb7eb07d9b37819f3c16265c8c3675d5d8 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 5 Apr 2018 09:51:08 +0200 Subject: [PATCH 20/41] skip migration if it is a big number --- pkg/services/sqlstore/migrations/annotation_mig.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 89fccad0d09..7fac0001e5b 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -110,8 +110,8 @@ func addAnnotationMig(mg *Migrator) { // // Convert epoch saved as seconds to miliseconds // - updateEpochSql := "UPDATE annotation SET epoch = (epoch*1000)" - mg.AddMigration("Convert existing annotations from seconds to miliseconds", new(RawSqlMigration). + 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)) From 4cc80efee60d9bfacae20cce60e808465efbe14d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 5 Apr 2018 18:11:35 +0200 Subject: [PATCH 21/41] graphite: use a query when testing data source Using a query (POST /render) request instead of GET /metrics/find to better mimic an actual request for rendering a panel in a dashboard. The POST /render request will add at least a custom http header which can be problematic when using direct access mode since it will trigger a CORS prerequest check. By doing this kind of query when testing the data source possible CORS issues can be detected there instead of later when trying to use the data source in a dashboard. --- public/app/plugins/datasource/graphite/datasource.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 335cb400834..c4bd4178965 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' }; }); }; From 60816f5fc2e1fe6a0856a1d4e1bf13441878498d Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 9 Apr 2018 12:48:01 +0200 Subject: [PATCH 22/41] using millis for annotations too --- pkg/services/alerting/result_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 8f9deb758a6..4b337c858bb 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, } From 5ca972542bd71353233bcd910e405655e2af14e1 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 9 Apr 2018 13:58:09 +0200 Subject: [PATCH 23/41] convert graphite epoch to ms --- pkg/api/annotations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index bc982fa84ea..fdf577a6a6f 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -156,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, } From 048cecdab599e2011d145f5d2f6bcc1475424d5d Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Thu, 19 Apr 2018 16:56:58 +0200 Subject: [PATCH 24/41] adjust timeFilter, timeFrom and timeTo macro examples --- docs/sources/features/datasources/postgres.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index 405e8a7fdec..5442503fef6 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/docs/sources/features/datasources/postgres.md @@ -45,9 +45,9 @@ Macro example | Description ------------ | ------------- *$__time(dateColumn)* | Will be replaced by an expression to rename the column to `time`. For example, *dateColumn as time* *$__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, *extract(epoch from dateColumn) BETWEEN 1494410783 AND 1494497183* -*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)* -*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)* +*$__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:01: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:01: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* *$__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* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* From 5076f9304b1f5be9d92e799bd8cb2db0161bb622 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Thu, 19 Apr 2018 17:01:48 +0200 Subject: [PATCH 25/41] slightly better example --- docs/sources/features/datasources/postgres.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index 5442503fef6..f3321af46f2 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/docs/sources/features/datasources/postgres.md @@ -45,9 +45,9 @@ Macro example | Description ------------ | ------------- *$__time(dateColumn)* | Will be replaced by an expression to rename the column to `time`. For example, *dateColumn as time* *$__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:01:17Z'* +*$__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: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* *$__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* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* From fd20aa7c031089ca9e32502599cec4462ce5e784 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 20 Apr 2018 18:18:22 +0200 Subject: [PATCH 26/41] fix circleci gometalinter test --- .circleci/config.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b6514c4cb47..30146576e63 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,18 +20,19 @@ 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/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=structcheck --enable=unconvert --enable=varcheck ./...' test-frontend: docker: @@ -139,6 +140,10 @@ workflows: filters: tags: only: /.*/ + - gometalinter: + filters: + tags: + only: /.*/ - build: filters: tags: From 556219b19243ae5a954c77ec00311d98ea15c2b1 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Sun, 22 Apr 2018 20:51:58 +0200 Subject: [PATCH 27/41] pkg/tsdb: fix ineffassign isues --- pkg/tsdb/cloudwatch/cloudwatch.go | 2 +- pkg/tsdb/influxdb/model_parser.go | 3 +++ pkg/tsdb/influxdb/query.go | 5 ++--- pkg/tsdb/opentsdb/opentsdb.go | 4 ++++ 4 files changed, 10 insertions(+), 4 deletions(-) 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 { From d86ed679b14d172aa0e3945eed32d7c738b03ecd Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 23 Apr 2018 09:23:14 +0200 Subject: [PATCH 28/41] return inherited property for permissions --- pkg/models/dashboard_acl.go | 1 + pkg/services/guardian/guardian.go | 14 +----------- pkg/services/guardian/guardian_test.go | 8 +++---- pkg/services/sqlstore/dashboard_acl.go | 6 +++-- pkg/services/sqlstore/dashboard_acl_test.go | 25 +++++++++++++++++++++ 5 files changed, 35 insertions(+), 19 deletions(-) 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/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..abf92ae0555 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) { 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..c68ffd08cd7 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) }) }) @@ -209,8 +232,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) }) }) }) From 079346917f42ad677d44d7c14b0942e80e7d64cd Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 23 Apr 2018 09:23:31 +0200 Subject: [PATCH 29/41] use inherited property from api when rendering permissions --- public/app/core/components/Permissions/PermissionsListItem.tsx | 2 +- public/app/stores/PermissionsStore/PermissionsStore.jest.ts | 1 + public/app/stores/PermissionsStore/PermissionsStore.ts | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) 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/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; From 3cca45dd885e8045b55a497b23afa59722c8b4ed Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 23 Apr 2018 13:59:52 +0200 Subject: [PATCH 30/41] bump version --- latest.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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" From d14ac54af665c825893cde3edb0c2f9f920d4986 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 23 Apr 2018 16:02:59 +0200 Subject: [PATCH 31/41] db: fix failing user auth tests for postgres --- pkg/services/sqlstore/user_auth_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 3a48ea8dde80975e781c6292a4af17f91204a8de Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 23 Apr 2018 16:20:17 +0200 Subject: [PATCH 32/41] Fixes signing of packages. Signing was failing as the builds were expected to run as ubuntu but is run as root. Closes #11686 --- scripts/build/rpmmacros | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 3eaaa5d32d835f76bba93336710143b24895f7ba Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 23 Apr 2018 17:44:29 +0200 Subject: [PATCH 33/41] fixed so user who can edit dashboard can edit row, fixes #11466 --- public/app/features/dashboard/dashgrid/DashboardRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardRow.tsx b/public/app/features/dashboard/dashgrid/DashboardRow.tsx index c2a84cb7da9..a95130de1f2 100644 --- a/public/app/features/dashboard/dashgrid/DashboardRow.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardRow.tsx @@ -95,7 +95,7 @@ export class DashboardRow extends React.Component { {title} ({hiddenPanels} hidden panels) - {config.bootData.user.orgRole !== 'Viewer' && ( + {this.dashboard.meta.canEdit === true && (
From 45e6d9fcc4e4d613b3e18b246d7ef7321207ada3 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 23 Apr 2018 17:45:51 +0200 Subject: [PATCH 34/41] removed import config --- public/app/features/dashboard/dashgrid/DashboardRow.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardRow.tsx b/public/app/features/dashboard/dashgrid/DashboardRow.tsx index a95130de1f2..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; From 6eb00000fe070435701c38f14023c88abfa25253 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Mon, 23 Apr 2018 19:28:54 +0200 Subject: [PATCH 35/41] pkg/services: fix ineffassign issues --- pkg/services/alerting/notifiers/telegram.go | 10 ++++++--- pkg/services/alerting/notifiers/victorops.go | 21 ------------------- pkg/services/guardian/guardian_test.go | 3 +++ .../provisioning/dashboards/file_reader.go | 1 - pkg/services/sqlstore/annotation_test.go | 1 + pkg/services/sqlstore/dashboard.go | 2 +- pkg/services/sqlstore/dashboard_acl_test.go | 1 + .../sqlstore/migrations/migrations_test.go | 4 ++-- pkg/services/sqlstore/playlist.go | 3 +++ pkg/services/sqlstore/plugin_setting.go | 3 +++ pkg/services/sqlstore/preferences.go | 3 +++ pkg/services/sqlstore/team_test.go | 3 ++- pkg/services/sqlstore/user.go | 8 ++----- 13 files changed, 28 insertions(+), 35 deletions(-) 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/guardian/guardian_test.go b/pkg/services/guardian/guardian_test.go index abf92ae0555..5e56b1d88c3 100644 --- a/pkg/services/guardian/guardian_test.go +++ b/pkg/services/guardian/guardian_test.go @@ -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_test.go b/pkg/services/sqlstore/annotation_test.go index 5af5f271993..949ed8135ba 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -256,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_test.go b/pkg/services/sqlstore/dashboard_acl_test.go index c68ffd08cd7..a034a0565a3 100644 --- a/pkg/services/sqlstore/dashboard_acl_test.go +++ b/pkg/services/sqlstore/dashboard_acl_test.go @@ -154,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) 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 { From bc570bb140b9c85ca7748b5e11a4d12ddf746e51 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Mon, 23 Apr 2018 19:31:23 +0200 Subject: [PATCH 36/41] pkg/log: fix ineffassign issues --- pkg/log/file_test.go | 2 ++ 1 file changed, 2 insertions(+) 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) From 15f11effa0997df18af9305d4ecce20ebd871d96 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Mon, 23 Apr 2018 19:34:55 +0200 Subject: [PATCH 37/41] pkg/cmd: fix ineffassign issues --- pkg/cmd/grafana-server/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From b02a860e66c439c7b717d8be7b4650a8e9eb5891 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Mon, 23 Apr 2018 20:03:57 +0200 Subject: [PATCH 38/41] pkg/components: fix ineffassign issues --- pkg/components/dashdiffs/compare.go | 4 ++++ pkg/components/dynmap/dynmap_test.go | 4 ++++ .../imguploader/azureblobuploader_test.go | 1 + pkg/components/imguploader/imguploader_test.go | 15 +++++++++------ pkg/components/imguploader/webdavuploader.go | 8 +++++++- 5 files changed, 25 insertions(+), 7 deletions(-) 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 } From 2e927a1053320a15ba6abbe939f9189b227cc507 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Mon, 23 Apr 2018 20:07:31 +0200 Subject: [PATCH 39/41] add ineffassign to circleci gometalinter check --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 30146576e63..3e95583ecae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,12 +27,13 @@ jobs: 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: run linters - command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=structcheck --enable=unconvert --enable=varcheck ./...' + command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...' test-frontend: docker: From 1446f5444787a4e2250cc596bd5c59ed232a1eef Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 24 Apr 2018 09:45:53 +0200 Subject: [PATCH 40/41] fixed test --- .../app/features/dashboard/specs/DashboardRow.jest.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/specs/DashboardRow.jest.tsx b/public/app/features/dashboard/specs/DashboardRow.jest.tsx index c0ac172aa26..fa1e6acead2 100644 --- a/public/app/features/dashboard/specs/DashboardRow.jest.tsx +++ b/public/app/features/dashboard/specs/DashboardRow.jest.tsx @@ -2,19 +2,13 @@ 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.meta = { canEdit: true }; getPanelContainer = jest.fn().mockReturnValue({ getDashboard: jest.fn().mockReturnValue(dashboardMock), @@ -42,7 +36,7 @@ describe('DashboardRow', () => { }); it('should have zero actions as viewer', () => { - config.bootData.user.orgRole = 'Viewer'; + dashboardMock.meta.canEdit = false; panel = new PanelModel({ collapsed: false }); wrapper = shallow(); expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(0); From 38a4a2dc60581b0b74e703ef6650a08ae54fc92c Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 24 Apr 2018 11:22:58 +0200 Subject: [PATCH 41/41] changed test name and dashboardMock code --- .../app/features/dashboard/specs/DashboardRow.jest.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/specs/DashboardRow.jest.tsx b/public/app/features/dashboard/specs/DashboardRow.jest.tsx index fa1e6acead2..8424346b0c5 100644 --- a/public/app/features/dashboard/specs/DashboardRow.jest.tsx +++ b/public/app/features/dashboard/specs/DashboardRow.jest.tsx @@ -7,8 +7,12 @@ describe('DashboardRow', () => { let wrapper, panel, getPanelContainer, dashboardMock; beforeEach(() => { - dashboardMock = { toggleRow: jest.fn() }; - dashboardMock.meta = { canEdit: true }; + dashboardMock = { + toggleRow: jest.fn(), + meta: { + canEdit: true, + }, + }; getPanelContainer = jest.fn().mockReturnValue({ getDashboard: jest.fn().mockReturnValue(dashboardMock), @@ -35,7 +39,7 @@ describe('DashboardRow', () => { expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(2); }); - it('should have zero actions as viewer', () => { + it('should have zero actions when cannot edit', () => { dashboardMock.meta.canEdit = false; panel = new PanelModel({ collapsed: false }); wrapper = shallow();