From db89ac4134088d1558c80284735043c211d75009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 14 Feb 2018 11:50:58 +0100 Subject: [PATCH 001/319] initial fixes for dashboard permission acl list query, fixes #10864 --- pkg/services/sqlstore/dashboard_acl.go | 62 +++++++-------------- pkg/services/sqlstore/dashboard_acl_test.go | 17 ++++++ pkg/services/sqlstore/org_test.go | 15 ++++- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/pkg/services/sqlstore/dashboard_acl.go b/pkg/services/sqlstore/dashboard_acl.go index 829182a8195..a1a308d6497 100644 --- a/pkg/services/sqlstore/dashboard_acl.go +++ b/pkg/services/sqlstore/dashboard_acl.go @@ -1,7 +1,6 @@ package sqlstore import ( - "fmt" "time" "github.com/grafana/grafana/pkg/bus" @@ -40,7 +39,7 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error { // Update dashboard HasAcl flag dashboard := m.Dashboard{HasAcl: true} - if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil { + if _, err := sess.Cols("has_acl").Where("id=?", cmd.DashboardId).Update(&dashboard); err != nil { return err } return nil @@ -134,6 +133,8 @@ func RemoveDashboardAcl(cmd *m.RemoveDashboardAclCommand) error { func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { var err error + falseStr := dialect.BooleanStr(false) + if query.DashboardId == 0 { sql := `SELECT da.id, @@ -151,18 +152,13 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { '' as title, '' as slug, '' as uid,` + - dialect.BooleanStr(false) + ` AS is_folder + falseStr + ` AS is_folder FROM dashboard_acl as da WHERE da.dashboard_id = -1` query.Result = make([]*m.DashboardAclInfoDTO, 0) err = x.SQL(sql).Find(&query.Result) } else { - dashboardFilter := fmt.Sprintf(`IN ( - SELECT %d - UNION - SELECT folder_id from dashboard where id = %d - )`, query.DashboardId, query.DashboardId) rawSQL := ` -- get permissions for the dashboard and its parent folder @@ -183,41 +179,21 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { d.slug, d.uid, d.is_folder - FROM` + dialect.Quote("dashboard_acl") + ` as da - LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id - LEFT OUTER JOIN team ug on ug.id = da.team_id - LEFT OUTER JOIN dashboard d on da.dashboard_id = d.id - WHERE dashboard_id ` + dashboardFilter + ` AND da.org_id = ? - - -- Also include default permissions if folder or dashboard field "has_acl" is false - - UNION - SELECT - da.id, - da.org_id, - da.dashboard_id, - da.user_id, - da.team_id, - da.permission, - da.role, - da.created, - da.updated, - '' as user_login, - '' as user_email, - '' as team, - folder.title, - folder.slug, - folder.uid, - folder.is_folder - FROM dashboard_acl as da, - dashboard as dash - LEFT OUTER JOIN dashboard folder on dash.folder_id = folder.id - WHERE - dash.id = ? AND ( - dash.has_acl = ` + dialect.BooleanStr(false) + ` or - folder.has_acl = ` + dialect.BooleanStr(false) + ` - ) AND - da.dashboard_id = -1 + FROM dashboard as d + LEFT JOIN dashboard folder on folder.id = d.folder_id + LEFT JOIN dashboard_acl AS da ON + da.dashboard_id = d.id OR + da.dashboard_id = d.folder_id OR + ( + -- include default permissions --> + da.org_id = -1 AND ( + (folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR + (folder.id IS NULL AND d.has_acl = ` + falseStr + `) + ) + ) + LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id + LEFT JOIN team ug on ug.id = da.team_id + WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL ORDER BY 1 ASC ` diff --git a/pkg/services/sqlstore/dashboard_acl_test.go b/pkg/services/sqlstore/dashboard_acl_test.go index 8b712c73ece..8d4af9544d9 100644 --- a/pkg/services/sqlstore/dashboard_acl_test.go +++ b/pkg/services/sqlstore/dashboard_acl_test.go @@ -41,6 +41,23 @@ func TestDashboardAclDataAccess(t *testing.T) { }) }) + Convey("Given dashboard folder with removed default permissions", func() { + err := UpdateDashboardAcl(&m.UpdateDashboardAclCommand{ + DashboardId: savedFolder.Id, + Items: []*m.DashboardAcl{}, + }) + So(err, ShouldBeNil) + + Convey("When reading dashboard acl should return no acl items", func() { + query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1} + + err := GetDashboardAclInfoList(&query) + So(err, ShouldBeNil) + + So(len(query.Result), ShouldEqual, 0) + }) + }) + Convey("Given dashboard folder permission", func() { err := SetDashboardAcl(&m.SetDashboardAclCommand{ OrgId: 1, diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go index 5322dfd4748..c57d15a48d5 100644 --- a/pkg/services/sqlstore/org_test.go +++ b/pkg/services/sqlstore/org_test.go @@ -199,10 +199,13 @@ func TestAccountDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 3) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT}) + dash1 := insertTestDashboard("1 test dash", ac1.OrgId, 0, false, "prod", "webapp") + dash2 := insertTestDashboard("2 test dash", ac3.OrgId, 0, false, "prod", "webapp") + + err = testHelperUpdateDashboardAcl(dash1.Id, m.DashboardAcl{DashboardId: dash1.Id, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT}) So(err, ShouldBeNil) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT}) + err = testHelperUpdateDashboardAcl(dash2.Id, m.DashboardAcl{DashboardId: dash2.Id, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT}) So(err, ShouldBeNil) Convey("When org user is deleted", func() { @@ -234,3 +237,11 @@ func TestAccountDataAccess(t *testing.T) { }) }) } + +func testHelperUpdateDashboardAcl(dashboardId int64, items ...m.DashboardAcl) error { + cmd := m.UpdateDashboardAclCommand{DashboardId: dashboardId} + for _, item := range items { + cmd.Items = append(cmd.Items, &item) + } + return UpdateDashboardAcl(&cmd) +} From ec6f0f94b80c10e30226d2bdccb8d9db5c885b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 14 Feb 2018 14:31:20 +0100 Subject: [PATCH 002/319] permissions: refactoring of acl api and query --- pkg/api/api.go | 1 - pkg/api/dashboard_acl.go | 30 ----- pkg/api/dashboard_acl_test.go | 104 ++---------------- pkg/api/dashboard_test.go | 8 +- pkg/models/dashboard_acl.go | 16 --- pkg/services/guardian/guardian.go | 20 ---- pkg/services/sqlstore/dashboard.go | 32 +----- pkg/services/sqlstore/dashboard_acl.go | 85 +------------- pkg/services/sqlstore/dashboard_acl_test.go | 74 ++----------- .../sqlstore/dashboard_folder_test.go | 29 ++--- pkg/services/sqlstore/dashboard_test.go | 19 ---- pkg/services/sqlstore/team_test.go | 2 +- pkg/services/sqlstore/user_test.go | 2 +- .../PermissionsStore/PermissionsStoreItem.ts | 3 +- 14 files changed, 40 insertions(+), 385 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index c03bf7963b8..1320663f630 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -269,7 +269,6 @@ func (hs *HttpServer) registerRoutes() { dashIdRoute.Group("/acl", func(aclRoute RouteRegister) { aclRoute.Get("/", wrap(GetDashboardAclList)) aclRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateDashboardAcl)) - aclRoute.Delete("/:aclId", wrap(DeleteDashboardAcl)) }) }) }) diff --git a/pkg/api/dashboard_acl.go b/pkg/api/dashboard_acl.go index 45f121dd0d0..32b75e80cc0 100644 --- a/pkg/api/dashboard_acl.go +++ b/pkg/api/dashboard_acl.go @@ -84,33 +84,3 @@ func UpdateDashboardAcl(c *middleware.Context, apiCmd dtos.UpdateDashboardAclCom return ApiSuccess("Dashboard acl updated") } - -func DeleteDashboardAcl(c *middleware.Context) Response { - dashId := c.ParamsInt64(":dashboardId") - aclId := c.ParamsInt64(":aclId") - - _, rsp := getDashboardHelper(c.OrgId, "", dashId, "") - if rsp != nil { - return rsp - } - - guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser) - if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin { - return dashboardGuardianResponse(err) - } - - if okToDelete, err := guardian.CheckPermissionBeforeRemove(m.PERMISSION_ADMIN, aclId); err != nil || !okToDelete { - if err != nil { - return ApiError(500, "Error while checking dashboard permissions", err) - } - - return ApiError(403, "Cannot remove own admin permission for a folder", nil) - } - - cmd := m.RemoveDashboardAclCommand{OrgId: c.OrgId, AclId: aclId} - if err := bus.Dispatch(&cmd); err != nil { - return ApiError(500, "Failed to delete permission for user", err) - } - - return Json(200, "") -} diff --git a/pkg/api/dashboard_acl_test.go b/pkg/api/dashboard_acl_test.go index e43e57ed5c0..d6b7e305daf 100644 --- a/pkg/api/dashboard_acl_test.go +++ b/pkg/api/dashboard_acl_test.go @@ -15,11 +15,11 @@ import ( func TestDashboardAclApiEndpoint(t *testing.T) { Convey("Given a dashboard acl", t, func() { mockResult := []*m.DashboardAclInfoDTO{ - {Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW}, - {Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT}, - {Id: 3, OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN}, - {Id: 4, OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW}, - {Id: 5, OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN}, + {OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW}, + {OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT}, + {OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN}, + {OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW}, + {OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN}, } dtoRes := transformDashboardAclsToDTOs(mockResult) @@ -92,21 +92,11 @@ func TestDashboardAclApiEndpoint(t *testing.T) { So(sc.resp.Code, ShouldEqual, 404) }) }) - - loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/2/acl/6", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_ADMIN, func(sc *scenarioContext) { - getDashboardNotFoundError = m.ErrDashboardNotFound - sc.handlerFunc = DeleteDashboardAcl - sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() - - Convey("Should not be able to delete non-existing dashboard", func() { - So(sc.resp.Code, ShouldEqual, 404) - }) - }) }) Convey("When user is org editor and has admin permission in the ACL", func() { loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) + mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) Convey("Should be able to access ACL", func() { sc.handlerFunc = GetDashboardAclList @@ -116,36 +106,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) { }) }) - loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) - - bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error { - return nil - }) - - Convey("Should be able to delete permission", func() { - sc.handlerFunc = DeleteDashboardAcl - sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() - - So(sc.resp.Code, ShouldEqual, 200) - }) - }) - - loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/6", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) - - bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error { - return nil - }) - - Convey("Should not be able to delete their own Admin permission", func() { - sc.handlerFunc = DeleteDashboardAcl - sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() - - So(sc.resp.Code, ShouldEqual, 403) - }) - }) - Convey("Should not be able to downgrade their own Admin permission", func() { cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ @@ -154,7 +114,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) { } postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) + mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) CallPostAcl(sc) So(sc.resp.Code, ShouldEqual, 403) @@ -170,34 +130,18 @@ func TestDashboardAclApiEndpoint(t *testing.T) { } postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) + mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN}) CallPostAcl(sc) So(sc.resp.Code, ShouldEqual, 200) }) }) - Convey("When user is a member of a team in the ACL with admin permission", func() { - loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardsId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) { - teamResp = append(teamResp, &m.Team{Id: 2, OrgId: 1, Name: "UG2"}) - - bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error { - return nil - }) - - Convey("Should be able to delete permission", func() { - sc.handlerFunc = DeleteDashboardAcl - sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() - - So(sc.resp.Code, ShouldEqual, 200) - }) - }) - }) }) Convey("When user is org viewer and has edit permission in the ACL", func() { loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_VIEWER, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT}) + mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT}) // Getting the permissions is an Admin permission Convey("Should not be able to get list of permissions from ACL", func() { @@ -207,21 +151,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) { So(sc.resp.Code, ShouldEqual, 403) }) }) - - loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_VIEWER, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT}) - - bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error { - return nil - }) - - Convey("Should be not be able to delete permission", func() { - sc.handlerFunc = DeleteDashboardAcl - sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() - - So(sc.resp.Code, ShouldEqual, 403) - }) - }) }) Convey("When user is org editor and not in the ACL", func() { @@ -234,20 +163,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) { So(sc.resp.Code, ShouldEqual, 403) }) }) - - loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/user/1", "/api/dashboards/id/:dashboardsId/acl/user/:userId", m.ROLE_EDITOR, func(sc *scenarioContext) { - mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW}) - bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error { - return nil - }) - - Convey("Should be not be able to delete permission", func() { - sc.handlerFunc = DeleteDashboardAcl - sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() - - So(sc.resp.Code, ShouldEqual, 403) - }) - }) }) }) } @@ -257,7 +172,6 @@ func transformDashboardAclsToDTOs(acls []*m.DashboardAclInfoDTO) []*m.DashboardA for _, acl := range acls { dto := &m.DashboardAclInfoDTO{ - Id: acl.Id, OrgId: acl.OrgId, DashboardId: acl.DashboardId, Permission: acl.Permission, diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index e80b3cad4dc..4a45c561d57 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -431,7 +431,7 @@ func TestDashboardApiEndpoint(t *testing.T) { role := m.ROLE_VIEWER mockResult := []*m.DashboardAclInfoDTO{ - {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT}, + {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT}, } bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error { @@ -505,7 +505,7 @@ func TestDashboardApiEndpoint(t *testing.T) { setting.ViewersCanEdit = true mockResult := []*m.DashboardAclInfoDTO{ - {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW}, + {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW}, } bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error { @@ -564,7 +564,7 @@ func TestDashboardApiEndpoint(t *testing.T) { role := m.ROLE_VIEWER mockResult := []*m.DashboardAclInfoDTO{ - {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN}, + {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN}, } bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error { @@ -637,7 +637,7 @@ func TestDashboardApiEndpoint(t *testing.T) { role := m.ROLE_EDITOR mockResult := []*m.DashboardAclInfoDTO{ - {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW}, + {OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW}, } bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error { diff --git a/pkg/models/dashboard_acl.go b/pkg/models/dashboard_acl.go index 933487650e3..202b519207d 100644 --- a/pkg/models/dashboard_acl.go +++ b/pkg/models/dashboard_acl.go @@ -44,7 +44,6 @@ type DashboardAcl struct { } type DashboardAclInfoDTO struct { - Id int64 `json:"id"` OrgId int64 `json:"-"` DashboardId int64 `json:"dashboardId"` @@ -75,21 +74,6 @@ type UpdateDashboardAclCommand struct { Items []*DashboardAcl } -type SetDashboardAclCommand struct { - DashboardId int64 - OrgId int64 - UserId int64 - TeamId int64 - Permission PermissionType - - Result DashboardAcl -} - -type RemoveDashboardAclCommand struct { - AclId int64 - OrgId int64 -} - // // QUERIES // diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index b448561494d..05795b7f2df 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -106,26 +106,6 @@ func (g *DashboardGuardian) checkAcl(permission m.PermissionType, acl []*m.Dashb return false, nil } -func (g *DashboardGuardian) CheckPermissionBeforeRemove(permission m.PermissionType, aclIdToRemove int64) (bool, error) { - if g.user.OrgRole == m.ROLE_ADMIN { - return true, nil - } - - acl, err := g.GetAcl() - if err != nil { - return false, err - } - - for i, p := range acl { - if p.Id == aclIdToRemove { - acl = append(acl[:i], acl[i+1:]...) - break - } - } - - return g.checkAcl(permission, acl) -} - func (g *DashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error) { if g.user.OrgRole == m.ROLE_ADMIN { return true, nil diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index f3fd81ebbe2..42c83da8810 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -79,11 +79,6 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error { dash.Data.Set("uid", uid) } - err = setHasAcl(sess, dash) - if err != nil { - return err - } - parentVersion := dash.Version affectedRows := int64(0) @@ -100,7 +95,7 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error { dash.Updated = cmd.UpdatedAt } - affectedRows, err = sess.MustCols("folder_id", "has_acl").ID(dash.Id).Update(dash) + affectedRows, err = sess.MustCols("folder_id").ID(dash.Id).Update(dash) } if err != nil { @@ -233,31 +228,6 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) { return "", m.ErrDashboardFailedGenerateUniqueUid } -func setHasAcl(sess *DBSession, dash *m.Dashboard) error { - // check if parent has acl - if dash.FolderId > 0 { - var parent m.Dashboard - if hasParent, err := sess.Where("folder_id=?", dash.FolderId).Get(&parent); err != nil { - return err - } else if hasParent && parent.HasAcl { - dash.HasAcl = true - } - } - - // check if dash has its own acl - if dash.Id > 0 { - if res, err := sess.Query("SELECT 1 from dashboard_acl WHERE dashboard_id =?", dash.Id); err != nil { - return err - } else { - if len(res) > 0 { - dash.HasAcl = true - } - } - } - - return nil -} - func GetDashboard(query *m.GetDashboardQuery) error { dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid} has, err := x.Get(&dashboard) diff --git a/pkg/services/sqlstore/dashboard_acl.go b/pkg/services/sqlstore/dashboard_acl.go index a1a308d6497..ae91d1d41f3 100644 --- a/pkg/services/sqlstore/dashboard_acl.go +++ b/pkg/services/sqlstore/dashboard_acl.go @@ -1,16 +1,12 @@ package sqlstore import ( - "time" - "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" ) func init() { - bus.AddHandler("sql", SetDashboardAcl) bus.AddHandler("sql", UpdateDashboardAcl) - bus.AddHandler("sql", RemoveDashboardAcl) bus.AddHandler("sql", GetDashboardAclInfoList) } @@ -23,7 +19,7 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error { } for _, item := range cmd.Items { - if item.UserId == 0 && item.TeamId == 0 && !item.Role.IsValid() { + if item.UserId == 0 && item.TeamId == 0 && (item.Role == nil || !item.Role.IsValid()) { return m.ErrDashboardAclInfoMissing } @@ -46,85 +42,6 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error { }) } -func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error { - return inTransaction(func(sess *DBSession) error { - if cmd.UserId == 0 && cmd.TeamId == 0 { - return m.ErrDashboardAclInfoMissing - } - - if cmd.DashboardId == 0 { - return m.ErrDashboardPermissionDashboardEmpty - } - - if res, err := sess.Query("SELECT 1 from "+dialect.Quote("dashboard_acl")+" WHERE dashboard_id =? and (team_id=? or user_id=?)", cmd.DashboardId, cmd.TeamId, cmd.UserId); err != nil { - return err - } else if len(res) == 1 { - - entity := m.DashboardAcl{ - Permission: cmd.Permission, - Updated: time.Now(), - } - - if _, err := sess.Cols("updated", "permission").Where("dashboard_id =? and (team_id=? or user_id=?)", cmd.DashboardId, cmd.TeamId, cmd.UserId).Update(&entity); err != nil { - return err - } - - return nil - } - - entity := m.DashboardAcl{ - OrgId: cmd.OrgId, - TeamId: cmd.TeamId, - UserId: cmd.UserId, - Created: time.Now(), - Updated: time.Now(), - DashboardId: cmd.DashboardId, - Permission: cmd.Permission, - } - - cols := []string{"org_id", "created", "updated", "dashboard_id", "permission"} - - if cmd.UserId != 0 { - cols = append(cols, "user_id") - } - - if cmd.TeamId != 0 { - cols = append(cols, "team_id") - } - - _, err := sess.Cols(cols...).Insert(&entity) - if err != nil { - return err - } - - cmd.Result = entity - - // Update dashboard HasAcl flag - dashboard := m.Dashboard{ - HasAcl: true, - } - - if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil { - return err - } - - return nil - }) -} - -// RemoveDashboardAcl removes a specified permission from the dashboard acl -func RemoveDashboardAcl(cmd *m.RemoveDashboardAclCommand) error { - return inTransaction(func(sess *DBSession) error { - var rawSQL = "DELETE FROM " + dialect.Quote("dashboard_acl") + " WHERE org_id =? and id=?" - _, err := sess.Exec(rawSQL, cmd.OrgId, cmd.AclId) - if err != nil { - return err - } - - return err - }) -} - // GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three // different places. // 1) Permissions for the dashboard diff --git a/pkg/services/sqlstore/dashboard_acl_test.go b/pkg/services/sqlstore/dashboard_acl_test.go index 8d4af9544d9..8fbb9c0d813 100644 --- a/pkg/services/sqlstore/dashboard_acl_test.go +++ b/pkg/services/sqlstore/dashboard_acl_test.go @@ -17,7 +17,7 @@ func TestDashboardAclDataAccess(t *testing.T) { childDash := insertTestDashboard("2 test dash", 1, savedFolder.Id, false, "prod", "webapp") Convey("When adding dashboard permission with userId and teamId set to 0", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ + err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{ OrgId: 1, DashboardId: savedFolder.Id, Permission: m.PERMISSION_EDIT, @@ -59,7 +59,7 @@ func TestDashboardAclDataAccess(t *testing.T) { }) Convey("Given dashboard folder permission", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ + err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{ OrgId: 1, UserId: currentUser.Id, DashboardId: savedFolder.Id, @@ -78,7 +78,7 @@ func TestDashboardAclDataAccess(t *testing.T) { }) Convey("Given child dashboard permission", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ + err := testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{ OrgId: 1, UserId: currentUser.Id, DashboardId: childDash.Id, @@ -100,7 +100,7 @@ func TestDashboardAclDataAccess(t *testing.T) { }) Convey("Given child dashboard permission in folder with no permissions", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ + err := testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{ OrgId: 1, UserId: currentUser.Id, DashboardId: childDash.Id, @@ -125,17 +125,12 @@ func TestDashboardAclDataAccess(t *testing.T) { }) Convey("Should be able to add dashboard permission", func() { - setDashAclCmd := m.SetDashboardAclCommand{ + err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{ OrgId: 1, UserId: currentUser.Id, DashboardId: savedFolder.Id, Permission: m.PERMISSION_EDIT, - } - - err := SetDashboardAcl(&setDashAclCmd) - So(err, ShouldBeNil) - - So(setDashAclCmd.Result.Id, ShouldEqual, 3) + }) q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1} err = GetDashboardAclInfoList(q1) @@ -147,42 +142,9 @@ func TestDashboardAclDataAccess(t *testing.T) { So(q1.Result[0].UserId, ShouldEqual, currentUser.Id) So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login) So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email) - So(q1.Result[0].Id, ShouldEqual, setDashAclCmd.Result.Id) - - Convey("Should update hasAcl field to true for dashboard folder and its children", func() { - q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}} - err := GetDashboards(q2) - So(err, ShouldBeNil) - So(q2.Result[0].HasAcl, ShouldBeTrue) - So(q2.Result[1].HasAcl, ShouldBeTrue) - }) - - Convey("Should be able to update an existing permission", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ - OrgId: 1, - UserId: 1, - DashboardId: savedFolder.Id, - Permission: m.PERMISSION_ADMIN, - }) - - So(err, ShouldBeNil) - - q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1} - err = GetDashboardAclInfoList(q3) - So(err, ShouldBeNil) - So(len(q3.Result), ShouldEqual, 1) - So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id) - So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN) - So(q3.Result[0].UserId, ShouldEqual, 1) - - }) Convey("Should be able to delete an existing permission", func() { - err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{ - OrgId: 1, - AclId: setDashAclCmd.Result.Id, - }) - + err := testHelperUpdateDashboardAcl(savedFolder.Id) So(err, ShouldBeNil) q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1} @@ -198,14 +160,12 @@ func TestDashboardAclDataAccess(t *testing.T) { So(err, ShouldBeNil) Convey("Should be able to add a user permission for a team", func() { - setDashAclCmd := m.SetDashboardAclCommand{ + err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{ OrgId: 1, TeamId: group1.Result.Id, DashboardId: savedFolder.Id, Permission: m.PERMISSION_EDIT, - } - - err := SetDashboardAcl(&setDashAclCmd) + }) So(err, ShouldBeNil) q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1} @@ -214,23 +174,10 @@ func TestDashboardAclDataAccess(t *testing.T) { So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id) So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT) So(q1.Result[0].TeamId, ShouldEqual, group1.Result.Id) - - Convey("Should be able to delete an existing permission for a team", func() { - err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{ - OrgId: 1, - AclId: setDashAclCmd.Result.Id, - }) - - So(err, ShouldBeNil) - q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1} - err = GetDashboardAclInfoList(q3) - So(err, ShouldBeNil) - So(len(q3.Result), ShouldEqual, 0) - }) }) Convey("Should be able to update an existing permission for a team", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ + err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{ OrgId: 1, TeamId: group1.Result.Id, DashboardId: savedFolder.Id, @@ -246,7 +193,6 @@ func TestDashboardAclDataAccess(t *testing.T) { So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN) So(q3.Result[0].TeamId, ShouldEqual, group1.Result.Id) }) - }) }) diff --git a/pkg/services/sqlstore/dashboard_folder_test.go b/pkg/services/sqlstore/dashboard_folder_test.go index b32a4dfed1d..40d6cf5bcb2 100644 --- a/pkg/services/sqlstore/dashboard_folder_test.go +++ b/pkg/services/sqlstore/dashboard_folder_test.go @@ -41,7 +41,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { Convey("and acl is set for dashboard folder", func() { var otherUser int64 = 999 - updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT) + testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT}) Convey("should not return folder", func() { query := &search.FindPersistedDashboardsQuery{ @@ -55,7 +55,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("when the user is given permission", func() { - updateTestDashboardWithAcl(folder.Id, currentUser.Id, m.PERMISSION_EDIT) + testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT}) Convey("should be able to access folder", func() { query := &search.FindPersistedDashboardsQuery{ @@ -93,9 +93,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { Convey("and acl is set for dashboard child and folder has all permissions removed", func() { var otherUser int64 = 999 - aclId := updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT) - removeAcl(aclId) - updateTestDashboardWithAcl(childDash.Id, otherUser, m.PERMISSION_EDIT) + testHelperUpdateDashboardAcl(folder.Id) + testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT}) Convey("should not return folder or child", func() { query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}} @@ -106,7 +105,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("when the user is given permission to child", func() { - updateTestDashboardWithAcl(childDash.Id, currentUser.Id, m.PERMISSION_EDIT) + testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: childDash.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT}) Convey("should be able to search for child dashboard but not folder", func() { query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}} @@ -165,11 +164,10 @@ func TestDashboardFolderDataAccess(t *testing.T) { Convey("and acl is set for one dashboard folder", func() { var otherUser int64 = 999 - updateTestDashboardWithAcl(folder1.Id, otherUser, m.PERMISSION_EDIT) + testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT}) Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() { - movedDash := moveDashboard(1, childDash2.Data, folder1.Id) - So(movedDash.HasAcl, ShouldBeTrue) + moveDashboard(1, childDash2.Data, folder1.Id) Convey("should not return folder with acl or its children", func() { query := &search.FindPersistedDashboardsQuery{ @@ -184,9 +182,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) }) Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() { - - movedDash := moveDashboard(1, childDash1.Data, folder2.Id) - So(movedDash.HasAcl, ShouldBeFalse) + moveDashboard(1, childDash1.Data, folder2.Id) Convey("should return folder without acl and its children", func() { query := &search.FindPersistedDashboardsQuery{ @@ -205,9 +201,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("and a dashboard with an acl is moved to the folder without an acl", func() { - updateTestDashboardWithAcl(childDash1.Id, otherUser, m.PERMISSION_EDIT) - movedDash := moveDashboard(1, childDash1.Data, folder2.Id) - So(movedDash.HasAcl, ShouldBeTrue) + testHelperUpdateDashboardAcl(childDash1.Id, m.DashboardAcl{DashboardId: childDash1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT}) + moveDashboard(1, childDash1.Data, folder2.Id) Convey("should return folder without acl but not the dashboard with acl", func() { query := &search.FindPersistedDashboardsQuery{ @@ -308,7 +303,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() { - updateTestDashboardWithAcl(folder1.Id, editorUser.Id, m.PERMISSION_VIEW) + testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: editorUser.Id, Permission: m.PERMISSION_VIEW}) err := SearchDashboards(&query) So(err, ShouldBeNil) @@ -352,7 +347,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() { - updateTestDashboardWithAcl(folder1.Id, viewerUser.Id, m.PERMISSION_EDIT) + testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_EDIT}) err := SearchDashboards(&query) So(err, ShouldBeNil) diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index de7cdf19927..7de4c5f5701 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -663,25 +663,6 @@ func createUser(name string, role string, isAdmin bool) m.User { return currentUserCmd.Result } -func updateTestDashboardWithAcl(dashId int64, userId int64, permissions m.PermissionType) int64 { - cmd := &m.SetDashboardAclCommand{ - OrgId: 1, - UserId: userId, - DashboardId: dashId, - Permission: permissions, - } - - err := SetDashboardAcl(cmd) - So(err, ShouldBeNil) - - return cmd.Result.Id -} - -func removeAcl(aclId int64) { - err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{AclId: aclId, OrgId: 1}) - So(err, ShouldBeNil) -} - func moveDashboard(orgId int64, dashboard *simplejson.Json, newFolderId int64) *m.Dashboard { cmd := m.SaveDashboardCommand{ OrgId: orgId, diff --git a/pkg/services/sqlstore/team_test.go b/pkg/services/sqlstore/team_test.go index bebe59f4238..fb76c3fa9d6 100644 --- a/pkg/services/sqlstore/team_test.go +++ b/pkg/services/sqlstore/team_test.go @@ -99,7 +99,7 @@ func TestTeamCommandsAndQueries(t *testing.T) { So(err, ShouldBeNil) err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]}) So(err, ShouldBeNil) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId}) + err = testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId}) err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId}) So(err, ShouldBeNil) diff --git a/pkg/services/sqlstore/user_test.go b/pkg/services/sqlstore/user_test.go index a65b7226eb6..2830733c96a 100644 --- a/pkg/services/sqlstore/user_test.go +++ b/pkg/services/sqlstore/user_test.go @@ -99,7 +99,7 @@ func TestUserDataAccess(t *testing.T) { err = AddOrgUser(&m.AddOrgUserCommand{LoginOrEmail: users[0].Login, Role: m.ROLE_VIEWER, OrgId: users[0].OrgId}) So(err, ShouldBeNil) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT}) + testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT}) So(err, ShouldBeNil) err = SavePreferences(&m.SavePreferencesCommand{UserId: users[0].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark"}) diff --git a/public/app/stores/PermissionsStore/PermissionsStoreItem.ts b/public/app/stores/PermissionsStore/PermissionsStoreItem.ts index 74769891256..92dca0220ca 100644 --- a/public/app/stores/PermissionsStore/PermissionsStoreItem.ts +++ b/public/app/stores/PermissionsStore/PermissionsStoreItem.ts @@ -1,9 +1,8 @@ -import { types } from 'mobx-state-tree'; +import { types } from 'mobx-state-tree'; export const PermissionsStoreItem = types .model('PermissionsStoreItem', { dashboardId: types.optional(types.number, -1), - id: types.maybe(types.number), permission: types.number, permissionName: types.maybe(types.string), role: types.maybe(types.string), From 73eaba076e4de50289c2403e8ab87a1a4485b213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 14 Feb 2018 15:02:42 +0100 Subject: [PATCH 003/319] wip: dashboard acl ux2, #10747 --- pkg/api/dashboard_acl.go | 2 ++ pkg/models/dashboard_acl.go | 3 +++ pkg/services/sqlstore/dashboard_acl.go | 1 + .../DisabledPermissionsListItem.tsx | 6 +++--- .../components/Permissions/Permissions.tsx | 3 +-- .../Permissions/PermissionsList.tsx | 4 ++-- .../Permissions/PermissionsListItem.tsx | 19 +++++++++++++++---- .../PermissionsStore/PermissionsStore.ts | 11 +++-------- .../PermissionsStore/PermissionsStoreItem.ts | 5 +++-- 9 files changed, 33 insertions(+), 21 deletions(-) diff --git a/pkg/api/dashboard_acl.go b/pkg/api/dashboard_acl.go index 32b75e80cc0..d15a575a05e 100644 --- a/pkg/api/dashboard_acl.go +++ b/pkg/api/dashboard_acl.go @@ -30,6 +30,8 @@ func GetDashboardAclList(c *middleware.Context) Response { } for _, perm := range acl { + perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail) + perm.TeamAvatarUrl = dtos.GetGravatarUrl(perm.TeamEmail) if perm.Slug != "" { perm.Url = m.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug) } diff --git a/pkg/models/dashboard_acl.go b/pkg/models/dashboard_acl.go index 202b519207d..0e14a3bfd71 100644 --- a/pkg/models/dashboard_acl.go +++ b/pkg/models/dashboard_acl.go @@ -53,7 +53,10 @@ type DashboardAclInfoDTO struct { UserId int64 `json:"userId"` UserLogin string `json:"userLogin"` UserEmail string `json:"userEmail"` + UserAvatarUrl string `json:"userAvatarUrl"` TeamId int64 `json:"teamId"` + TeamEmail string `json:"teamEmail"` + TeamAvatarUrl string `json:"teamAvatarUrl"` Team string `json:"team"` Role *RoleType `json:"role,omitempty"` Permission PermissionType `json:"permission"` diff --git a/pkg/services/sqlstore/dashboard_acl.go b/pkg/services/sqlstore/dashboard_acl.go index ae91d1d41f3..6e7175335f3 100644 --- a/pkg/services/sqlstore/dashboard_acl.go +++ b/pkg/services/sqlstore/dashboard_acl.go @@ -92,6 +92,7 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { u.login AS user_login, u.email AS user_email, ug.name AS team, + ug.email AS team_email, d.title, d.slug, d.uid, diff --git a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx index db45714136e..e3f3ee56d75 100644 --- a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx +++ b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component } from 'react'; import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker'; import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore'; @@ -12,10 +12,10 @@ export default class DisabledPermissionListItem extends Component { return ( - + - + {item.name} Can diff --git a/public/app/core/components/Permissions/Permissions.tsx b/public/app/core/components/Permissions/Permissions.tsx index 0a0572ed86e..dbdc1682f6b 100644 --- a/public/app/core/components/Permissions/Permissions.tsx +++ b/public/app/core/components/Permissions/Permissions.tsx @@ -15,9 +15,8 @@ export interface DashboardAcl { permissionName?: string; role?: string; icon?: string; - nameHtml?: string; + name?: string; inherited?: boolean; - sortName?: string; sortRank?: number; } diff --git a/public/app/core/components/Permissions/PermissionsList.tsx b/public/app/core/components/Permissions/PermissionsList.tsx index b215dad2391..a77235ecc30 100644 --- a/public/app/core/components/Permissions/PermissionsList.tsx +++ b/public/app/core/components/Permissions/PermissionsList.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component } from 'react'; import PermissionsListItem from './PermissionsListItem'; import DisabledPermissionsListItem from './DisabledPermissionsListItem'; import { observer } from 'mobx-react'; @@ -23,7 +23,7 @@ class PermissionsList extends Component { Admin Role', + name: 'Admin', permission: 4, icon: 'fa fa-fw fa-street-view', }} diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index 3140b8fcc0c..2ab5b948440 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from 'react'; import { observer } from 'mobx-react'; import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker'; import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore'; @@ -7,6 +7,16 @@ const setClassNameHelper = inherited => { return inherited ? 'gf-form-disabled' : ''; }; +function ItemAvatar({ item }) { + if (item.userAvatarUrl) { + return ; + } + if (item.teamAvatarUrl) { + return ; + } + return ; +} + export default observer(({ item, removeItem, permissionChanged, itemIndex, folderInfo }) => { const handleRemoveItem = evt => { evt.preventDefault(); @@ -18,13 +28,14 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde }; const inheritedFromRoot = item.dashboardId === -1 && folderInfo && folderInfo.id === 0; + console.log(item.name); return ( - - - + + + {item.name} {item.inherited && folderInfo && ( diff --git a/public/app/stores/PermissionsStore/PermissionsStore.ts b/public/app/stores/PermissionsStore/PermissionsStore.ts index a7c90d13da0..7838744c541 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.ts @@ -231,19 +231,14 @@ const prepareItem = (item, dashboardId: number, isFolder: boolean, isInRoot: boo item.sortRank = 0; if (item.userId > 0) { - item.icon = 'fa fa-fw fa-user'; - item.nameHtml = item.userLogin; - item.sortName = item.userLogin; + item.name = item.userLogin; item.sortRank = 10; } else if (item.teamId > 0) { - item.icon = 'fa fa-fw fa-users'; - item.nameHtml = item.team; - item.sortName = item.team; + item.name = item.team; item.sortRank = 20; } else if (item.role) { item.icon = 'fa fa-fw fa-street-view'; - item.nameHtml = `Everyone with ${item.role} Role`; - item.sortName = item.role; + item.name = item.role; item.sortRank = 30; if (item.role === 'Viewer') { item.sortRank += 1; diff --git a/public/app/stores/PermissionsStore/PermissionsStoreItem.ts b/public/app/stores/PermissionsStore/PermissionsStoreItem.ts index 92dca0220ca..c4873cb9c01 100644 --- a/public/app/stores/PermissionsStore/PermissionsStoreItem.ts +++ b/public/app/stores/PermissionsStore/PermissionsStoreItem.ts @@ -14,8 +14,9 @@ export const PermissionsStoreItem = types inherited: types.maybe(types.boolean), sortRank: types.maybe(types.number), icon: types.maybe(types.string), - nameHtml: types.maybe(types.string), - sortName: types.maybe(types.string), + name: types.maybe(types.string), + teamAvatarUrl: types.maybe(types.string), + userAvatarUrl: types.maybe(types.string), }) .actions(self => ({ updateRole: role => { From 9ac82f3d0ec213a4936d78c50943ee82d1937f70 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 21 Feb 2018 14:51:28 +0100 Subject: [PATCH 004/319] added tabs and searchfilter to addpanel, fixes#10427 --- .../dashboard/dashgrid/AddPanelPanel.tsx | 98 +++++++++++++++++-- public/sass/components/_panel_add_panel.scss | 19 +++- public/sass/components/_tabs.scss | 12 +-- 3 files changed, 110 insertions(+), 19 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx index aeb840c317a..8d4ebfb3a10 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx @@ -16,6 +16,8 @@ export interface AddPanelPanelProps { export interface AddPanelPanelState { filter: string; panelPlugins: any[]; + copiedPanelPlugins: any[]; + tab: string; } export class AddPanelPanel extends React.Component { @@ -25,12 +27,14 @@ export class AddPanelPanel extends React.Component item) @@ -39,6 +43,19 @@ export class AddPanelPanel extends React.Component item) + .value(); + let copiedPanels = []; + let copiedPanelJson = store.get(LS_PANEL_COPY_KEY); if (copiedPanelJson) { let copiedPanel = JSON.parse(copiedPanelJson); @@ -48,12 +65,13 @@ export class AddPanelPanel extends React.Component { @@ -101,19 +119,85 @@ export class AddPanelPanel extends React.Component { + return regex.test(panel.name); + }); + } + + openCopy() { + this.setState({ tab: 'Copy' }); + this.setState({ filter: '' }); + this.setState({ panelPlugins: this.getPanelPlugins('') }); + this.setState({ copiedPanelPlugins: this.getCopiedPanelPlugins('') }); + } + + openAdd() { + this.setState({ tab: 'Add' }); + this.setState({ filter: '' }); + this.setState({ panelPlugins: this.getPanelPlugins('') }); + this.setState({ copiedPanelPlugins: this.getCopiedPanelPlugins('') }); + } + render() { + let addClass; + let copyClass; + let panelTab; + + if (this.state.tab === 'Add') { + addClass = 'active active--panel'; + copyClass = ''; + panelTab = this.state.panelPlugins.map(this.renderPanelItem); + } else if (this.state.tab === 'Copy') { + addClass = ''; + copyClass = 'active active--panel'; + panelTab = this.state.copiedPanelPlugins.map(this.renderPanelItem); + } + return (
New Panel - Select a visualization +
    +
  • +
    + Add +
    +
  • +
  • +
    + Copy +
    +
  • +
- {this.state.panelPlugins.map(this.renderPanelItem)} + +
+ +
+ {panelTab} +
); diff --git a/public/sass/components/_panel_add_panel.scss b/public/sass/components/_panel_add_panel.scss index 51754a54d92..70aff32a945 100644 --- a/public/sass/components/_panel_add_panel.scss +++ b/public/sass/components/_panel_add_panel.scss @@ -3,9 +3,12 @@ } .add-panel__header { - padding: 5px 15px; + padding: 0 15px; display: flex; align-items: center; + background: $page-header-bg; + box-shadow: $page-header-shadow; + border-bottom: 1px solid $page-header-border-color; .gicon { font-size: 30px; @@ -23,7 +26,7 @@ .add-panel__title { font-size: $font-size-md; - margin-right: $spacer/2; + margin-right: $spacer*2; } .add-panel__sub-title { @@ -39,9 +42,9 @@ flex-direction: row; flex-wrap: wrap; overflow: auto; - height: calc(100% - 43px); + height: calc(100% - 50px); align-content: flex-start; - justify-content: space-around; + justify-content: space-between; position: relative; } @@ -51,7 +54,7 @@ border-radius: 3px; padding: $spacer/3 $spacer; - width: 31%; + width: 32%; height: 60px; text-align: center; margin: $gf-form-margin; @@ -77,3 +80,9 @@ .add-panel__item-icon { padding: 2px; } + +.add-panel__searchbar { + width: 100%; + margin-bottom: 10px; + margin-top: 7px; +} diff --git a/public/sass/components/_tabs.scss b/public/sass/components/_tabs.scss index 197d5892652..eb3c8ce13f5 100644 --- a/public/sass/components/_tabs.scss +++ b/public/sass/components/_tabs.scss @@ -44,18 +44,16 @@ &::before { display: block; - content: " "; + content: ' '; position: absolute; left: 0; right: 0; height: 2px; top: 0; - background-image: linear-gradient( - to right, - #ffd500 0%, - #ff4400 99%, - #ff4400 100% - ); + background-image: linear-gradient(to right, #ffd500 0%, #ff4400 99%, #ff4400 100%); } } + &.active--panel { + background: $panel-bg !important; + } } From 5e5a4cf1b0f8391b85521182c44b995d7c6eee3d Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 21 Feb 2018 15:39:15 +0100 Subject: [PATCH 005/319] added highlighter, fixed setState and changed back flex to spacea around --- .../dashboard/dashgrid/AddPanelPanel.tsx | 39 +++++++++++++------ public/sass/components/_panel_add_panel.scss | 4 +- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx index 8d4ebfb3a10..1c2eeb8fcc6 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx @@ -7,6 +7,7 @@ import { PanelContainer } from './PanelContainer'; import ScrollBar from 'app/core/components/ScrollBar/ScrollBar'; import store from 'app/core/store'; import { LS_PANEL_COPY_KEY } from 'app/core/constants'; +import Highlighter from 'react-highlight-words'; export interface AddPanelPanelProps { panel: PanelModel; @@ -110,19 +111,29 @@ export class AddPanelPanel extends React.Component; + //} + //return text; + } + renderPanelItem(panel, index) { return (
this.onAddPanel(panel)} title={panel.name}> -
{panel.name}
+
{this.renderText(panel.name)}
); } filterChange(evt) { - this.setState({ filter: evt.target.value }); - this.setState({ panelPlugins: this.getPanelPlugins(evt.target.value) }); - this.setState({ copiedPanelPlugins: this.getCopiedPanelPlugins(evt.target.value) }); + this.setState({ + filter: evt.target.value, + panelPlugins: this.getPanelPlugins(evt.target.value), + copiedPanelPlugins: this.getCopiedPanelPlugins(evt.target.value), + }); } filterPanels(panels, filter) { @@ -133,17 +144,21 @@ export class AddPanelPanel extends React.Component Date: Thu, 22 Feb 2018 09:58:52 +0100 Subject: [PATCH 006/319] added no copies div --- .../features/dashboard/dashgrid/AddPanelPanel.tsx | 14 ++++++++++---- public/sass/components/_panel_add_panel.scss | 7 +++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx index 1c2eeb8fcc6..23042285754 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx @@ -112,11 +112,8 @@ export class AddPanelPanel extends React.Component; - //} - //return text; } renderPanelItem(panel, index) { @@ -128,6 +125,10 @@ export class AddPanelPanel extends React.ComponentNo copied panels yet.; + } + filterChange(evt) { this.setState({ filter: evt.target.value, @@ -173,7 +174,12 @@ export class AddPanelPanel extends React.Component 0) { + panelTab = this.state.copiedPanelPlugins.map(this.renderPanelItem); + } else { + panelTab = this.noCopiedPanelPlugins(); + } } return ( diff --git a/public/sass/components/_panel_add_panel.scss b/public/sass/components/_panel_add_panel.scss index 6dd609ee544..5322d8fcea0 100644 --- a/public/sass/components/_panel_add_panel.scss +++ b/public/sass/components/_panel_add_panel.scss @@ -86,3 +86,10 @@ margin-bottom: 10px; margin-top: 7px; } + +.add-panel__no-panels { + color: $text-color-weak; + font-style: italic; + width: 100%; + padding: 3px 8px; +} From 07c3fb7e0f1a86009c9497698fa79d731d88dff7 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 22 Feb 2018 10:38:22 +0100 Subject: [PATCH 007/319] changed name of copy tab to paste --- public/app/features/dashboard/dashgrid/AddPanelPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx index 23042285754..d5b301a9ea1 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx @@ -196,7 +196,7 @@ export class AddPanelPanel extends React.Component
  • - Copy + Paste
  • From e037ef21f790f6ea4aea3c3e0ee29e66375e636c Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 26 Feb 2018 10:21:24 +0100 Subject: [PATCH 008/319] added admin icon and permission member definitions(role,team,user) --- .../Permissions/DisabledPermissionsListItem.tsx | 7 +++++-- .../Permissions/PermissionsListItem.tsx | 16 ++++++++++++++-- public/sass/components/_filter-table.scss | 4 ++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx index e3f3ee56d75..adc2bec3d81 100644 --- a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx +++ b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx @@ -13,9 +13,12 @@ export default class DisabledPermissionListItem extends Component { return ( - + + + + {item.name} + (Role) - {item.name} Can diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index 2ab5b948440..1bec7003f1f 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -14,7 +14,17 @@ function ItemAvatar({ item }) { if (item.teamAvatarUrl) { return ; } - return ; + return ; +} + +function ItemDescription({ item }) { + if (item.userId) { + return (User); + } + if (item.teamId) { + return (Team); + } + return (Role); } export default observer(({ item, removeItem, permissionChanged, itemIndex, folderInfo }) => { @@ -35,7 +45,9 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde - {item.name} + + {item.name} + {item.inherited && folderInfo && ( diff --git a/public/sass/components/_filter-table.scss b/public/sass/components/_filter-table.scss index 00f9b93dcfd..bfa9fbbbc5a 100644 --- a/public/sass/components/_filter-table.scss +++ b/public/sass/components/_filter-table.scss @@ -85,3 +85,7 @@ } } } +.filter-table__weak-italic { + font-style: italic; + color: $text-color-weak; +} From 8a1bd2ee223410b09fe0833f9de2749bb74c9776 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 6 Mar 2018 09:24:36 +0100 Subject: [PATCH 009/319] docs: fill for mysql/postgres ref #10138 --- docs/sources/features/datasources/mysql.md | 14 ++++++++++++++ docs/sources/features/datasources/postgres.md | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/docs/sources/features/datasources/mysql.md b/docs/sources/features/datasources/mysql.md index 7fae7441b6d..6c15006949e 100644 --- a/docs/sources/features/datasources/mysql.md +++ b/docs/sources/features/datasources/mysql.md @@ -50,6 +50,7 @@ Macro example | Description *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *FROM_UNIXTIME(1494410783)* *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *FROM_UNIXTIME(1494497183)* *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed) as time_sec,* +*$__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* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* *$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183* @@ -119,6 +120,19 @@ GROUP BY 1, metric_name ORDER BY 1 ``` +Example using the fill parameter in the $__timeGroup macro to convert null values to be zero instead: + +```sql +SELECT + $__timeGroup(atimestamp,'24h',0) as time_sec, + avg(afloat) as value, + avarchar as metric +FROM testdata.grafana_metrics +WHERE $__timeFilter(atimestamp) +GROUP BY 1, avarchar +ORDER BY 1 +``` + Currently, there is no support for a dynamic group by time based on time range & panel width. This is something we plan to add. diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index 7d52df2fd3e..270640a93dc 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/docs/sources/features/datasources/postgres.md @@ -49,6 +49,7 @@ Macro example | Description *$__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)* *$__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* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* *$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183* @@ -103,6 +104,20 @@ GROUP BY time ORDER BY time ``` +Example using the fill parameter in the $__timeGroup macro to convert null values to be zero instead: + +```sql +SELECT + $__timeGroup("createdAt",'5m',0), + sum(value) as value, + measurement +FROM public.grafana_metric +WHERE + $__timeFilter("createdAt") +GROUP BY time, measurement +ORDER BY time +``` + Example with multiple columns: ```sql From 8d4c439eebeaa07af8eeab17395a31d26466cbb6 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 7 Mar 2018 12:46:27 +0100 Subject: [PATCH 010/319] add panel to list now copy, started on jest --- public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx | 4 ++++ public/app/features/panel/panel_ctrl.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx new file mode 100644 index 00000000000..e68d84ad8bb --- /dev/null +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx @@ -0,0 +1,4 @@ +import React from 'react'; +import { AddPanelPanel } from './AddPanelPanel'; + +describe('AddPanelPanel', () => {}); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index d8757f49be6..11dac549aea 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -193,7 +193,7 @@ export class PanelCtrl { }); menu.push({ - text: 'Add to Panel List', + text: 'Copy', click: 'ctrl.addToPanelList()', role: 'Editor', }); From 834c42194321920e969f0e1155cbf476fe69a8df Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 7 Mar 2018 15:01:50 +0100 Subject: [PATCH 011/319] replaced if with classNames --- .../dashboard/dashgrid/AddPanelPanel.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx index d5b301a9ea1..eb677b4b4b2 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.tsx @@ -1,6 +1,6 @@ import React from 'react'; import _ from 'lodash'; - +import classNames from 'classnames'; import config from 'app/core/config'; import { PanelModel } from '../panel_model'; import { PanelContainer } from './PanelContainer'; @@ -163,18 +163,21 @@ export class AddPanelPanel extends React.Component 0) { panelTab = this.state.copiedPanelPlugins.map(this.renderPanelItem); } else { 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 012/319] 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 013/319] 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 1d190de91800223c732a0ebc57e6c71921f2e0c4 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 12 Mar 2018 11:58:47 +0100 Subject: [PATCH 014/319] added test for sorting and filtering --- .../dashboard/dashgrid/AddPanelPanel.jest.tsx | 100 +++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx b/public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx index e68d84ad8bb..be7659ae030 100644 --- a/public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx +++ b/public/app/features/dashboard/dashgrid/AddPanelPanel.jest.tsx @@ -1,4 +1,102 @@ import React from 'react'; import { AddPanelPanel } from './AddPanelPanel'; +import { PanelModel } from '../panel_model'; +import { shallow } from 'enzyme'; +import config from '../../../core/config'; -describe('AddPanelPanel', () => {}); +jest.mock('app/core/store', () => ({ + get: key => { + return null; + }, + delete: key => { + return null; + }, +})); + +describe('AddPanelPanel', () => { + let wrapper, dashboardMock, getPanelContainer, panel; + + beforeEach(() => { + config.panels = [ + { + id: 'singlestat', + hideFromList: false, + name: 'Singlestat', + sort: 2, + info: { + logos: { + small: '', + }, + }, + }, + { + id: 'hidden', + hideFromList: true, + name: 'Hidden', + sort: 100, + info: { + logos: { + small: '', + }, + }, + }, + { + id: 'graph', + hideFromList: false, + name: 'Graph', + sort: 1, + info: { + logos: { + small: '', + }, + }, + }, + { + id: 'alexander_zabbix', + hideFromList: false, + name: 'Zabbix', + sort: 100, + info: { + logos: { + small: '', + }, + }, + }, + { + id: 'piechart', + hideFromList: false, + name: 'Piechart', + sort: 100, + info: { + logos: { + small: '', + }, + }, + }, + ]; + + dashboardMock = { toggleRow: jest.fn() }; + + getPanelContainer = jest.fn().mockReturnValue({ + getDashboard: jest.fn().mockReturnValue(dashboardMock), + getPanelLoader: jest.fn(), + }); + + panel = new PanelModel({ collapsed: false }); + wrapper = shallow(); + }); + + it('should fetch all panels sorted with core plugins first', () => { + //console.log(wrapper.debug()); + //console.log(wrapper.find('.add-panel__item').get(0).props.title); + expect(wrapper.find('.add-panel__item').get(1).props.title).toBe('Singlestat'); + expect(wrapper.find('.add-panel__item').get(4).props.title).toBe('Piechart'); + }); + + it('should filter', () => { + wrapper.find('input').simulate('change', { target: { value: 'p' } }); + + expect(wrapper.find('.add-panel__item').get(1).props.title).toBe('Piechart'); + expect(wrapper.find('.add-panel__item').get(0).props.title).toBe('Graph'); + }); +}); From 1f8a2a67bff9ed3cd25312f4617198bdb4b4927a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Mar 2018 11:06:10 +0100 Subject: [PATCH 015/319] docs: Using Microsoft SQL Server in Grafana --- docs/sources/features/datasources/index.md | 1 + docs/sources/features/datasources/mssql.md | 229 +++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 docs/sources/features/datasources/mssql.md diff --git a/docs/sources/features/datasources/index.md b/docs/sources/features/datasources/index.md index 54606d20988..a892f38a448 100644 --- a/docs/sources/features/datasources/index.md +++ b/docs/sources/features/datasources/index.md @@ -30,6 +30,7 @@ The following datasources are officially supported: * [Prometheus]({{< relref "prometheus.md" >}}) * [MySQL]({{< relref "mysql.md" >}}) * [Postgres]({{< relref "postgres.md" >}}) +* [Microsoft SQL Server (MSSQL)]({{< relref "mssql.md" >}}) ## Data source plugins diff --git a/docs/sources/features/datasources/mssql.md b/docs/sources/features/datasources/mssql.md new file mode 100644 index 00000000000..2bcfd9dc59c --- /dev/null +++ b/docs/sources/features/datasources/mssql.md @@ -0,0 +1,229 @@ ++++ +title = "Using Microsoft SQL Server in Grafana" +description = "Guide for using Microsoft SQL Server in Grafana" +keywords = ["grafana", "MSSQL", "Microsoft", "SQL", "guide"] +type = "docs" +[menu.docs] +name = "Microsoft SQL Server" +parent = "datasources" +weight = 7 ++++ + +# Using Microsoft SQL Server in Grafana + +Grafana ships with a built-in Microsoft SQL Server (MSSQL) data source plugin that allows you to query and visualize data from any Microsoft SQL Server 2005 or newer. + +## Adding the data source + +1. Open the side menu by clicking the Grafana icon in the top header. +2. In the side menu under the `Configuration` link you should find a link named `Data Sources`. +3. Click the `+ Add data source` button in the top header. +4. Select *Microsoft SQL Server* from the *Type* dropdown. + +### Database User Permissions (Important!) + +The database user you specify when you add the data source should only be granted SELECT permissions on +the specified database & tables you want to query. Grafana does not validate that the query is safe. The query +could include any SQL statement. For example, statements like `DELETE FROM user;` and `DROP TABLE user;` would be +executed. To protect against this we **Highly** recommmend you create a specific MSSQL user with restricted permissions. + +Example: + +```sql + CREATE USER grafanareader WITH PASSWORD 'password' + GRANT SELECT ON dbo.YourTable3 TO grafanareader +``` + +Make sure the user does not get any unwanted privileges from the public role. + +## Macros + +To simplify syntax and to allow for dynamic parts, like date range filters, the query can contain macros. + +Macro example | Description +------------ | ------------- +*$__time(dateColumn)* | Will be replaced by an expression to rename the column to `time`. For example, *`dateColumn as time`* +*$__utcTime(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to UTC depending on the server's local timeoffset and rename it to `time`. For example, *`DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time`* +*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename it to `time`. For example, *`DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time`* +*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *`dateColumn >= DATEADD(s, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND dateColumn <= DATEADD(s, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')`* +*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *`DATEADD(second, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')`* +*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *`DATEADD(second, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')`* +*$__timeGroup(dateColumn,'5m', NULL)* | Will be replaced by an expression usable in GROUP BY clause. For example, *`cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumns))/300 as int)*300 as int)`* +*$__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`* +*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *`1494497183`* + +We plan to add many more macros. If you have suggestions for what macros you would like to see, please [open an issue](https://github.com/grafana/grafana) in our GitHub repo. + +The query editor has a link named `Generated SQL` that shows up after a query has been executed, while in panel edit mode. Click on it and it will expand and show the raw interpolated SQL string that was executed. + +## Table queries + +If the `Format as` query option is set to `Table` then you can basically do any type of SQL query. The table panel will automatically show the results of whatever columns & rows your query returns. + +Query editor with example query: + +![](/img/docs/v47/mssql_table_query.png) + + +The query: + +```sql +SELECT COLUMN_NAME AS [Name], + DATA_TYPE AS [Type], + CHARACTER_OCTET_LENGTH AS [Length], + NUMERIC_PRECISION as [Precisopn], + NUMERIC_PRECISION_RADIX AS [Radix], + NUMERIC_SCALE AS [Scale] +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_NAME = 'mssql_types'; +``` + +You can control the name of the Table panel columns by using regular `AS ` SQL column selection syntax. + +The resulting table panel: + +![](/img/docs/v47/mssql_table.png) + +### Time series queries + +If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must must have a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch in seconds. You may return a column named `metric` that is used as metric name for the value column. Any column except `time` and `metric` is treated as a value column. If you ommit the `metric` column, tha name of the value column will be the metric name. You may select multiple value columns, each will have its name as metric. If you select multiple value columns along with a `metric` column, the names ("MetircName - ColumnName") will be combined to make the metric name. + +Example with `metric` column + +```sql +SELECT + [time_date_time] as [time], + [value_double] as [value], + [metric1] as [metric] +FROM [test_data] +WHERE $__timeFilter([time_date_time]) +ORDER BY [time_date_time] +``` + +Example with multiple `value` culumns + +```sql +SELECT + [time_date_time] as [time], + [value_double1] as [metric_name1], + [value_int2] as [metric_name2] +FROM [test_data] +WHERE $__timeFilter([time_date_time]) +ORDER BY [time_date_time] +``` + +Example with multiple `value` culumns combined with a `metric` column + +```sql +SELECT + [time_date_time] as [time], + [value_double1] as [value1], + [value_int2] as [value2], + [metric_col] as [metric] +FROM [test_data] +WHERE $__timeFilter([time_date_time]) +ORDER BY [time_date_time] +``` +The result of the above query would look something like the below + +![](/img/docs/v47/mssql_metric_value.png) + +Currently, there is no support for a dynamic group by time based on time range & panel width. +This is something we plan to add. + +## Templating + +Instead of hard-coding things like server, application and sensor name in you metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of the dashboard. These dropdowns makes it easy to change the data being displayed in your dashboard. + +Checkout the [Templating]({{< relref "reference/templating.md" >}}) documentation for an introduction to the templating feature and the different types of template variables. + +### Query Variable + +If you add a template variable of the type `Query`, you can write a MSSQL query that can +return things like measurement names, key names or key values that are shown as a dropdown select box. + +For example, you can have a variable that contains all values for the `hostname` column in a table if you specify a query like this in the templating variable *Query* setting. + +```sql +SELECT hostname FROM host +``` + +A query can return multiple columns and Grafana will automatically create a list from them. For example, the query below will return a list with values from `hostname` and `hostname2`. + +```sql +SELECT [host].[hostname], [other_host].[hostname2] FROM host JOIN other_host ON [host].[city] = [other_host].[city] +``` + +Another option is a query that can create a key/value variable. The query should return two columns that are named `__text` and `__value`. The `__text` column value should be unique (if it is not unique then the first value is used). The options in the dropdown will have a text and value that allows you to have a friendly name as text and an id as the value. An example query with `hostname` as the text and `id` as the value: + +```sql +SELECT hostname __text, id __value FROM host +``` + +You can also create nested variables. For example if you had another variable named `region`. Then you could have +the hosts variable only show hosts from the current selected region with a query like this (if `region` is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values): + +```sql +SELECT hostname FROM host WHERE region IN ($region) +``` + +### Using Variables in Queries + +From Grafana 4.3.0 to 4.6.0, template variables are always quoted automatically so if it is a string value do not wrap them in quotes in where clauses. + +From Grafana 4.7.0, template variable values are only quoted when the template variable is a `multi-value`. + +If the variable is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values. + +There are two syntaxes: + +`$` Example with a template variable named `hostname`: + +```sql +SELECT + atimestamp time, + aint value +FROM table +WHERE $__timeFilter(atimestamp) and hostname in($hostname) +ORDER BY atimestamp +``` + +`[[varname]]` Example with a template variable named `hostname`: + +```sql +SELECT + atimestamp as time, + aint as value +FROM table +WHERE $__timeFilter(atimestamp) and hostname in([[hostname]]) +ORDER BY atimestamp +``` + +## Annotations + +[Annotations]({{< relref "reference/annotations.md" >}}) allows you to overlay rich event information on top of graphs. You add annotation queries via the Dashboard menu / Annotations view. + +An example query: + +```sql +SELECT + DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) as [time], + metric1 as [text], + convert(varvhar, metric1) + ',' + convert(varchar, metric2) as [tags] +FROM + test_data +WHERE + $__timeFilter(time_column) +``` + +Name | Description +------------ | ------------- +time | The name of the date/time field. could be in a native sql time datatype +text | Event description field. +tags | Optional field name to use for event tags as a comma separated string. + +## Alerting + +Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule +conditions. From 74c3f732c15df7e59a587f12f98bdce2af3bb505 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Mar 2018 22:08:10 +0100 Subject: [PATCH 016/319] docs: update using mssql in grafana --- docs/sources/features/datasources/mssql.md | 430 +++++++++++++++++---- 1 file changed, 363 insertions(+), 67 deletions(-) diff --git a/docs/sources/features/datasources/mssql.md b/docs/sources/features/datasources/mssql.md index 2bcfd9dc59c..325e1fe3596 100644 --- a/docs/sources/features/datasources/mssql.md +++ b/docs/sources/features/datasources/mssql.md @@ -11,6 +11,8 @@ weight = 7 # Using Microsoft SQL Server in Grafana +> Only available in Grafana v5.1+. + Grafana ships with a built-in Microsoft SQL Server (MSSQL) data source plugin that allows you to query and visualize data from any Microsoft SQL Server 2005 or newer. ## Adding the data source @@ -20,6 +22,17 @@ Grafana ships with a built-in Microsoft SQL Server (MSSQL) data source plugin th 3. Click the `+ Add data source` button in the top header. 4. Select *Microsoft SQL Server* from the *Type* dropdown. +### Data source options + +Name | Description +------------ | ------------- +*Name* | The data source name. This is how you refer to the data source in panels & queries. +*Default* | Default data source means that it will be pre-selected for new panels. +*Host* | The IP address/hostname and optional port of your MSSQL instance. If port is omitted, default 1443 will be used. +*Database* | Name of your MSSQL database. +*User* | Database user's login/username +*Password* | Database user's password + ### Database User Permissions (Important!) The database user you specify when you add the data source should only be granted SELECT permissions on @@ -36,101 +49,213 @@ Example: Make sure the user does not get any unwanted privileges from the public role. +## Query Editor +{{< docs-imagebox img="/img/docs/v51/mssql_query_editor.png" class="docs-image--no-shadow" >}} + +You find the MSSQL query editor in the metrics tab in Graph, Singlestat or Table panel's edit mode. You enter edit mode by clicking the +panel title, then edit. The editor allows you to define a SQL query to select data to be visualized. + +1. Select *Format as* `Time series` (for use in Graph or Singlestat panel's among others) or `Table` (for use in Table panel among others). +2. This is the actual editor where you write your SQL queries. +3. Show help section for MSSQL below the query editor. +4. Show actual executed SQL query. Will be available first after a successful query has been executed. +5. Add an additional query where an additional query editor will be displayed. + +
    + ## Macros To simplify syntax and to allow for dynamic parts, like date range filters, the query can contain macros. Macro example | Description ------------ | ------------- -*$__time(dateColumn)* | Will be replaced by an expression to rename the column to `time`. For example, *`dateColumn as time`* -*$__utcTime(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to UTC depending on the server's local timeoffset and rename it to `time`. For example, *`DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time`* -*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename it to `time`. For example, *`DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time`* -*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *`dateColumn >= DATEADD(s, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND dateColumn <= DATEADD(s, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')`* -*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *`DATEADD(second, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')`* -*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *`DATEADD(second, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')`* -*$__timeGroup(dateColumn,'5m', NULL)* | Will be replaced by an expression usable in GROUP BY clause. For example, *`cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumns))/300 as int)*300 as int)`* -*$__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`* -*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *`1494497183`* +*$__time(dateColumn)* | Will be replaced by an expression to rename the column to *time*. For example, *dateColumn as time* +*$__utcTime(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to UTC depending on the server's local timeoffset and rename it to *time*.
    For example, *DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time* +*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename it to *time*.
    For example, *DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time* +*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name.
    For example, *dateColumn >= DATEADD(s, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND dateColumn <= DATEADD(s, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')* +*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *DATEADD(second, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')* +*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *DATEADD(second, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')* +*$__timeGroup(dateColumn,'5m'[, fillvalue])* | Will be replaced by an expression usable in GROUP BY clause. Providing a *fillValue* of *NULL* or *floating value* will automatically fill empty series in timerange with that value.
    For example, *cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), column))/300 as int)*300 as int)*. +*$__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* +*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* +*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183* We plan to add many more macros. If you have suggestions for what macros you would like to see, please [open an issue](https://github.com/grafana/grafana) in our GitHub repo. The query editor has a link named `Generated SQL` that shows up after a query has been executed, while in panel edit mode. Click on it and it will expand and show the raw interpolated SQL string that was executed. ## Table queries - If the `Format as` query option is set to `Table` then you can basically do any type of SQL query. The table panel will automatically show the results of whatever columns & rows your query returns. +**Example database table:** + +```sql +CREATE TABLE [event] ( + time_sec bigint, + description nvarchar(100), + tags nvarchar(100), +) +``` + +```sql +CREATE TABLE [mssql_types] ( + c_bit bit, c_tinyint tinyint, c_smallint smallint, c_int int, c_bigint bigint, c_money money, c_smallmoney smallmoney, c_numeric numeric(10,5), + c_real real, c_decimal decimal(10,2), c_float float, + c_char char(10), c_varchar varchar(10), c_text text, + c_nchar nchar(12), c_nvarchar nvarchar(12), c_ntext ntext, + c_datetime datetime, c_datetime2 datetime2, c_smalldatetime smalldatetime, c_date date, c_time time, c_datetimeoffset datetimeoffset +) + +INSERT INTO [mssql_types] +SELECT + 1, 5, 20020, 980300, 1420070400, '$20000.15', '£2.15', 12345.12, + 1.11, 2.22, 3.33, + 'char10', 'varchar10', 'text', + N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺', + GETDATE(), CAST(GETDATE() AS DATETIME2), CAST(GETDATE() AS SMALLDATETIME), CAST(GETDATE() AS DATE), CAST(GETDATE() AS TIME), SWITCHOFFSET(CAST(GETDATE() AS DATETIMEOFFSET), '-07:00')) +``` + Query editor with example query: -![](/img/docs/v47/mssql_table_query.png) +{{< docs-imagebox img="/img/docs/v51/mssql_table_query.png" max-width="500px" class="docs-image--no-shadow" >}} The query: ```sql -SELECT COLUMN_NAME AS [Name], - DATA_TYPE AS [Type], - CHARACTER_OCTET_LENGTH AS [Length], - NUMERIC_PRECISION as [Precisopn], - NUMERIC_PRECISION_RADIX AS [Radix], - NUMERIC_SCALE AS [Scale] -FROM INFORMATION_SCHEMA.COLUMNS -WHERE TABLE_NAME = 'mssql_types'; +SELECT * FROM [mssql_types] ``` -You can control the name of the Table panel columns by using regular `AS ` SQL column selection syntax. +You can control the name of the Table panel columns by using regular `AS ` SQL column selection syntax. Example: + +```sql +SELECT + c_bit as [column1], c_tinyint as [column2] +FROM + [mssql_types] +``` The resulting table panel: -![](/img/docs/v47/mssql_table.png) +{{< docs-imagebox img="/img/docs/v51/mssql_table_result.png" max-width="1489px" class="docs-image--no-shadow" >}} -### Time series queries +## Time series queries -If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must must have a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch in seconds. You may return a column named `metric` that is used as metric name for the value column. Any column except `time` and `metric` is treated as a value column. If you ommit the `metric` column, tha name of the value column will be the metric name. You may select multiple value columns, each will have its name as metric. If you select multiple value columns along with a `metric` column, the names ("MetircName - ColumnName") will be combined to make the metric name. +If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must must have a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch in seconds. You may return a column named `metric` that is used as metric name for the value column. Any column except `time` and `metric` is treated as a value column. If you ommit the `metric` column, tha name of the value column will be the metric name. You may select multiple value columns, each will have its name as metric. -Example with `metric` column +**Example database table:** + +```sql +CREATE TABLE [event] ( + time_sec bigint, + description nvarchar(100), + tags nvarchar(100), +) +``` + + +```sql +CREATE TABLE metric_values ( + time datetime, + measurement nvarchar(100), + valueOne int, + valueTwo int, +) + +INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 12:30:00', 'Metric A', 62, 6) +INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 12:30:00', 'Metric B', 49, 11) +... +INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 13:55:00', 'Metric A', 14, 25) +INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 13:55:00', 'Metric B', 48, 10) + +``` + +{{< docs-imagebox img="/img/docs/v51/mssql_time_series_one.png" class="docs-image--no-shadow docs-image--right" >}} + +**Example with one `value` and one `metric` column.** ```sql SELECT - [time_date_time] as [time], - [value_double] as [value], - [metric1] as [metric] -FROM [test_data] -WHERE $__timeFilter([time_date_time]) -ORDER BY [time_date_time] + time, + valueOne, + measurement as metric +FROM + metric_values +WHERE + $__timeFilter(time) +ORDER BY 1 ``` -Example with multiple `value` culumns +When above query are used in a graph panel the result will be two series named `Metric A` and `Metric B` with value of `valueOne` and `valueTwo` plotted over `time`. + +
    + +{{< docs-imagebox img="/img/docs/v51/mssql_time_series_two.png" class="docs-image--no-shadow docs-image--right" >}} + +**Example with multiple `value` culumns:** ```sql SELECT - [time_date_time] as [time], - [value_double1] as [metric_name1], - [value_int2] as [metric_name2] -FROM [test_data] -WHERE $__timeFilter([time_date_time]) -ORDER BY [time_date_time] + time, + valueOne, + valueTwo +FROM + metric_values +WHERE + $__timeFilter(time) +ORDER BY 1 ``` -Example with multiple `value` culumns combined with a `metric` column +When above query are used in a graph panel the result will be two series named `valueOne` and `valueTwo` with value of `valueOne` and `valueTwo` plotted over `time`. + +
    + +{{< docs-imagebox img="/img/docs/v51/mssql_time_series_three.png" class="docs-image--no-shadow docs-image--right" >}} + +**Example using the $__timeGroup macro:** ```sql SELECT - [time_date_time] as [time], - [value_double1] as [value1], - [value_int2] as [value2], - [metric_col] as [metric] -FROM [test_data] -WHERE $__timeFilter([time_date_time]) -ORDER BY [time_date_time] + $__timeGroup(time, '3m') as time, + measurement as metric, + avg(valueOne) +FROM + metric_values +WHERE + $__timeFilter(time) +GROUP BY + $__timeGroup(time, '3m'), + measurement +ORDER BY 1 ``` -The result of the above query would look something like the below -![](/img/docs/v47/mssql_metric_value.png) +When above query are used in a graph panel the result will be two series named `Metric A` and `Metric B` with an average of `valueOne` plotted over `time`. +Any two series lacking a value in a 3 minute window will render a line between those two lines. You'll notice that the graph to the right never goes down to zero. -Currently, there is no support for a dynamic group by time based on time range & panel width. -This is something we plan to add. +
    + +{{< docs-imagebox img="/img/docs/v51/mssql_time_series_four.png" class="docs-image--no-shadow docs-image--right" >}} + +**Example using the $__timeGroup macro with fill parameter set to zero:** + +```sql +SELECT + $__timeGroup(time, '3m', 0) as time, + measurement as metric, + sum(valueTwo) +FROM + metric_values +WHERE + $__timeFilter(time) +GROUP BY + $__timeGroup(time, '3m'), + measurement +ORDER BY 1 +``` + +When above query are used in a graph panel the result will be two series named `Metric A` and `Metric B` with a sum of `valueTwo` plotted over `time`. +Any series lacking a value in a 3 minute window will have a value of zero which you'll see rendered in the graph to the right. ## Templating @@ -169,10 +294,9 @@ SELECT hostname FROM host WHERE region IN ($region) ``` ### Using Variables in Queries - -From Grafana 4.3.0 to 4.6.0, template variables are always quoted automatically so if it is a string value do not wrap them in quotes in where clauses. - -From Grafana 4.7.0, template variable values are only quoted when the template variable is a `multi-value`. +> From Grafana 4.3.0 to 4.6.0, template variables are always quoted automatically so if it is a string value do not wrap them in quotes in where clauses. +> +> From Grafana 5.0.0, template variable values are only quoted when the template variable is a `multi-value`. If the variable is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values. @@ -204,25 +328,197 @@ ORDER BY atimestamp [Annotations]({{< relref "reference/annotations.md" >}}) allows you to overlay rich event information on top of graphs. You add annotation queries via the Dashboard menu / Annotations view. -An example query: - -```sql -SELECT - DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) as [time], - metric1 as [text], - convert(varvhar, metric1) + ',' + convert(varchar, metric2) as [tags] -FROM - test_data -WHERE - $__timeFilter(time_column) -``` +**Columns:** Name | Description ------------ | ------------- -time | The name of the date/time field. could be in a native sql time datatype +time | The name of the date/time field. Could be in a native sql time datatype or epoch seconds. text | Event description field. tags | Optional field name to use for event tags as a comma separated string. +**Example database tables:** + +```sql +CREATE TABLE [events] ( + time_sec bigint, + description nvarchar(100), + tags nvarchar(100), +) +``` + +We also use the database table defined in [Time series queries](#time-series-queries). + +**Example query using time column of type epoch seconds:** + +```sql +SELECT + time_sec as time, + description as [text], + tags +FROM + [events] +WHERE + $__unixEpochFilter(time_sec) +ORDER BY 1 +``` + +**Example query using time column of type datetime:** + +```sql +SELECT + time, + measurement as text, + convert(varchar, valueOne) + ',' + convert(varchar, valueTwo) as tags +FROM + metric_values +WHERE + $__timeFilter(time_column) +ORDER BY 1 +``` + +## Stored procedure support +Stored procedures have been verified to work. However, please note that we haven't done anything special to support this why there may exist edge cases where it won't work as you would expect. +Stored procedures should be supported in table, time series and annotation queries as long as you use the same naming of columns and return data in the same format as describe above under respective section. + +Please note that any macro function will not work inside a stored procedure. + +### Examples +{{< docs-imagebox img="/img/docs/v51/mssql_metrics_graph.png" class="docs-image--no-shadow docs-image--right" >}} +For the following examples the database table defined in [Time series queries](#time-series-queries). Let's say that we want to visualize 4 series in a graph panel, i.e. all combinations of columns `valueOne`, `valueTwo` and `measurement`. Graph panel to the right visualizes what we want to achieve. To solve this we actually need to use two queries: + +**First query:** +```sql +SELECT + $__timeGroup(time, '5m') as time, + measurement + ' - value one' as metric, + avg(valueOne) as valueOne +FROM + metric_values +WHERE + $__timeFilter(time) +GROUP BY + $__timeGroup(time, '5m'), + measurement +ORDER BY 1 +``` + +**Second query:** +```sql +SELECT + $__timeGroup(time, '5m') as time, + measurement + ' - value two' as metric, + avg(valueTwo) as valueTwo +FROM + metric_values +GROUP BY + $__timeGroup(time, '5m'), + measurement +ORDER BY 1 +``` + +#### Stored procedure using time in epoch format +We can define a stored procedure that will return all data we need to render 4 series in a graph panel like above. +In this case the stored procedure accepts two parameters `@from` and `@to` of `int` data types which should be a timerange (from-to) in epoch format +which will be used to filter the data to return from the stored procedure. + +We're mimicking the `$__timeGroup(time, '5m')` in the select and group by expressions and that's why there's a lot of lengthy expressions needed - +these could be extracted to MSSQL functions, if wanted. + +```sql +CREATE PROCEDURE sp_test_epoch( + @from int, + @to int +) AS +BEGIN + SELECT + cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int) as time, + measurement + ' - value one' as metric, + avg(valueOne) as value + FROM + metric_values + WHERE + time >= DATEADD(s, @from, '1970-01-01') AND time <= DATEADD(s, @to, '1970-01-01') + GROUP BY + cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int), + measurement + UNION ALL + SELECT + cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int) as time, + measurement + ' - value two' as metric, + avg(valueTwo) as value + FROM + metric_values + WHERE + time >= DATEADD(s, @from, '1970-01-01') AND time <= DATEADD(s, @to, '1970-01-01') + GROUP BY + cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int), + measurement + ORDER BY 1 +END +``` + +Then we can use the following query for our graph panel. + +```sql +DECLARE + @from int = $__unixEpochFrom(), + @to int = $__unixEpochTo() + +EXEC dbo.sp_test_epoch @from, @to +``` + +#### Stored procedure using time in datetime format +We can define a stored procedure that will return all data we need to render 4 series in a graph panel like above. +In this case the stored procedure accepts two parameters `@from` and `@to` of `datetime` data types which should be a timerange (from-to) +which will be used to filter the data to return from the stored procedure. + +We're mimicking the `$__timeGroup(time, '5m')` in the select and group by expressions and that's why there's a lot of lengthy expressions needed - +these could be extracted to MSSQL functions, if wanted. + +```sql +CREATE PROCEDURE sp_test_datetime( + @from datetime, + @to datetime +) AS +BEGIN + SELECT + cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int) as time, + measurement + ' - value one' as metric, + avg(valueOne) as value + FROM + metric_values + WHERE + time >= @from AND time <= @to + GROUP BY + cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int), + measurement + UNION ALL + SELECT + cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int) as time, + measurement + ' - value two' as metric, + avg(valueTwo) as value + FROM + metric_values + WHERE + time >= @from AND time <= @to + GROUP BY + cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int), + measurement + ORDER BY 1 +END + +``` + +Then we can use the following query for our graph panel. + +```sql +DECLARE + @from datetime = $__timeFrom(), + @to datetime = $__timeTo() + +EXEC dbo.sp_test_datetime @from, @to +``` + ## Alerting Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule From e5df179c7cf41a986123980a594fe68f1668f86c Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 20 Mar 2018 20:38:20 +0100 Subject: [PATCH 017/319] docs: spelling --- docs/sources/features/datasources/mssql.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/sources/features/datasources/mssql.md b/docs/sources/features/datasources/mssql.md index 325e1fe3596..71baf4bd050 100644 --- a/docs/sources/features/datasources/mssql.md +++ b/docs/sources/features/datasources/mssql.md @@ -86,6 +86,7 @@ We plan to add many more macros. If you have suggestions for what macros you wou The query editor has a link named `Generated SQL` that shows up after a query has been executed, while in panel edit mode. Click on it and it will expand and show the raw interpolated SQL string that was executed. ## Table queries + If the `Format as` query option is set to `Table` then you can basically do any type of SQL query. The table panel will automatically show the results of whatever columns & rows your query returns. **Example database table:** @@ -142,7 +143,7 @@ The resulting table panel: ## Time series queries -If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must must have a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch in seconds. You may return a column named `metric` that is used as metric name for the value column. Any column except `time` and `metric` is treated as a value column. If you ommit the `metric` column, tha name of the value column will be the metric name. You may select multiple value columns, each will have its name as metric. +If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must must have a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch in seconds. You may return a column named `metric` that is used as metric name for the value column. Any column except `time` and `metric` is treated as a value column. If you omit the `metric` column, tha name of the value column will be the metric name. You may select multiple value columns, each will have its name as metric. **Example database table:** @@ -377,16 +378,19 @@ ORDER BY 1 ``` ## Stored procedure support + Stored procedures have been verified to work. However, please note that we haven't done anything special to support this why there may exist edge cases where it won't work as you would expect. Stored procedures should be supported in table, time series and annotation queries as long as you use the same naming of columns and return data in the same format as describe above under respective section. Please note that any macro function will not work inside a stored procedure. ### Examples + {{< docs-imagebox img="/img/docs/v51/mssql_metrics_graph.png" class="docs-image--no-shadow docs-image--right" >}} For the following examples the database table defined in [Time series queries](#time-series-queries). Let's say that we want to visualize 4 series in a graph panel, i.e. all combinations of columns `valueOne`, `valueTwo` and `measurement`. Graph panel to the right visualizes what we want to achieve. To solve this we actually need to use two queries: **First query:** + ```sql SELECT $__timeGroup(time, '5m') as time, @@ -417,6 +421,7 @@ ORDER BY 1 ``` #### Stored procedure using time in epoch format + We can define a stored procedure that will return all data we need to render 4 series in a graph panel like above. In this case the stored procedure accepts two parameters `@from` and `@to` of `int` data types which should be a timerange (from-to) in epoch format which will be used to filter the data to return from the stored procedure. @@ -468,6 +473,7 @@ EXEC dbo.sp_test_epoch @from, @to ``` #### Stored procedure using time in datetime format + We can define a stored procedure that will return all data we need to render 4 series in a graph panel like above. In this case the stored procedure accepts two parameters `@from` and `@to` of `datetime` data types which should be a timerange (from-to) which will be used to filter the data to return from the stored procedure. @@ -521,5 +527,5 @@ EXEC dbo.sp_test_datetime @from, @to ## Alerting -Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule +Time series queries should work in alerting conditions. Table formatted queries are not yet supported in alert rule conditions. From 3898ea02e60c2811feec65e8ed4c32fea862b632 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 22 Mar 2018 02:22:58 +0100 Subject: [PATCH 018/319] 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 019/319] 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 020/319] 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 021/319] 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 022/319] 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 023/319] 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 024/319] 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 025/319] 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 026/319] 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 027/319] 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 028/319] 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 029/319] 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 030/319] 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 031/319] 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 032/319] 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 033/319] 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 1b8103f0ea2aeb5fdf9a3670559d15ce0474a4a1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 23 Mar 2018 14:21:07 +0100 Subject: [PATCH 034/319] docs: update graph panel documentation Added new versions of images and new images to better highlight the possible configuration options. Documentation was very outdated so tried to fix that to better reflect the current state/latest release. --- docs/sources/features/panels/graph.md | 128 ++++++++++++++++---------- 1 file changed, 79 insertions(+), 49 deletions(-) diff --git a/docs/sources/features/panels/graph.md b/docs/sources/features/panels/graph.md index c3b0260c98b..d47131dc47c 100644 --- a/docs/sources/features/panels/graph.md +++ b/docs/sources/features/panels/graph.md @@ -22,15 +22,18 @@ options for the panel. ## General -{{< docs-imagebox img="/img/docs/v43/graph_general.png" max-width= "900px" >}} +{{< docs-imagebox img="/img/docs/v51/graph_general.png" max-width= "800px" >}} The general tab allows customization of a panel's appearance and menu options. -### General Options +### Info -- **Title** - The panel title on the dashboard -- **Span** - The panel width in columns -- **Height** - The panel contents height in pixels +- **Title** - The panel title of the dashboard, displayed at the top. +- **Description** - The panel description, displayed on hover of info icon in the upper left corner of the panel. +- **Transparent** - If checked, removes the solid background of the panel (default not checked). + +### Repeat +Repeat a panel for each value of a variable. Repeating panels are described in more detail [here]({{< relref "reference/templating.md#repeating-panels" >}}). ### Drilldown / detail link @@ -54,47 +57,65 @@ options. ## Axes -{{< docs-imagebox img="/img/docs/v43/graph_axes_grid_options.png" max-width= "900px" >}} +{{< docs-imagebox img="/img/docs/v51/graph_axes_grid_options.png" max-width= "800px" >}} -The Axes tab controls the display of axes, grids and legend. The **Left Y** and **Right Y** can be customized using: +The Axes tab controls the display of axes. + +### Left Y/Right Y + +The **Left Y** and **Right Y** can be customized using: - **Unit** - The display unit for the Y value -- **Scale** - +- **Scale** - The scale to use for the Y value, linear or logarithmic. (default linear) - **Y-Min** - The minimum Y value. (default auto) - **Y-Max** - The maximum Y value. (default auto) +- **Decimals** - Controls how many decimals are displayed for Y value (default auto) - **Label** - The Y axis label (default "") Axes can also be hidden by unchecking the appropriate box from **Show**. -### X-Axis Mode +### X-Axis -There are three options: +Axis can be hidden by unchecking **Show**. + +For **Mode** there are three options: - The default option is **Time** and means the x-axis represents time and that the data is grouped by time (for example, by hour or by minute). - The **Series** option means that the data is grouped by series and not by time. The y-axis still represents the value. - {{< docs-imagebox img="/img/docs/v45/graph-x-axis-mode-series.png" max-width="700px">}} + {{< docs-imagebox img="/img/docs/v51/graph-x-axis-mode-series.png" max-width="800px">}} - The **Histogram** option converts the graph into a histogram. A Histogram is a kind of bar chart that groups numbers into ranges, often called buckets or bins. Taller bars show that more data falls in that range. Histograms and buckets are described in more detail [here](http://docs.grafana.org/features/panels/heatmap/#histograms-and-buckets). -### Legend -The legend hand be hidden by checking the **Show** checkbox. If it's shown, it can be -displayed as a table of values by checking the **Table** checkbox. Series with no -values can be hidden from the legend using the **Hide empty** checkbox. +### Y-Axes -### Legend Values +- **Align** - Check to align left and right Y-axes by value (default unchecked/false) +- **Level** - Available when *Align* is checked. Value to use for alignment of left and right Y-axes, starting from Y=0 (default 0) + +## Legend + +{{< docs-imagebox img="/img/docs/v51/graph-legend.png" max-width= "800px" >}} + +### Options + +- **Show** - Uncheck to hide the legend (default checked/true) +- **Table** - Check to display legend in table (default unchecked/false) +- **To the right** - Check to display legend to the right (default unchecked/false) +- **Width** - Available when *To the right* is checked. Value to contral the minimum width for the legend (default 0) + +### Values Additional values can be shown along-side the legend names: -- **Total** - Sum of all values returned from metric query -- **Current** - Last value returned from the metric query - **Min** - Minimum of all values returned from metric query - **Max** - Maximum of all values returned from the metric query - **Avg** - Average of all values returned from metric query +- **Current** - Last value returned from the metric query +- **Total** - Sum of all values returned from metric query - **Decimals** - Controls how many decimals are displayed for legend values (and graph hover tooltips) The legend values are calculated client side by Grafana and depend on what type of @@ -103,63 +124,72 @@ be correct at the same time. For example if you plot a rate like requests/second using average as aggregator, then the Total in the legend will not represent the total number of requests. It is just the sum of all data points received by Grafana. +### Hide series + +Hide series when all values of a serie from a metric query are of a specific value: + +- **With only nulls** - Value=*null* (default unchecked) +- **With only zeros** - Value=*zero* (default unchecked) + ## Display styles -{{< docs-imagebox img="/img/docs/v43/graph_display_styles.png" max-width= "900px" >}} +{{< docs-imagebox img="/img/docs/v51/graph_display_styles.png" max-width= "800px" >}} Display styles control visual properties of the graph. -### Thresholds +### Draw Options -Thresholds allow you to add arbitrary lines or sections to the graph to make it easier to see when -the graph crosses a particular threshold. - - -### Chart Options +#### Draw Modes - **Bar** - Display values as a bar chart - **Lines** - Display values as a line graph - **Points** - Display points for values -### Line Options +#### Mode Options -- **Line Fill** - Amount of color fill for a series. 0 is none. -- **Line Width** - The width of the line for a series. -- **Null point mode** - How null values are displayed -- **Staircase line** - Draws adjacent points as staircase +- **Fill** - Amount of color fill for a series (default 1). 0 is none. +- **Line Width** - The width of the line for a series (default 1). +- **Staircase** - Draws adjacent points as staircase +- **Points Radius** - Asjust the size of points when *Points* are selected as *Draw Mode*. -### Multiple Series +#### Hover tooltip + +- **Mode** - Controls how many series to display in the tooltip when hover over a point in time, All series or single (default All series). +- **Sort order** - Controls how series displayed in tooltip are sorted, None, Ascending or Descending (default None). +- **Stacked value** - Available when *Stack* are checked and controls how stacked values are displayed in tooltip (default Individual). + - Individual: the value for the series you hover over + - Cumulative - sum of series below plus the series you hover over + +#### Stacking & Null value If there are multiple series, they can be displayed as a group. - **Stack** - Each series is stacked on top of another -- **Percent** - Each series is drawn as a percentage of the total of all series +- **Percent** - Available when *Stack* are checked. Each series is drawn as a percentage of the total of all series +- **Null value** - How null values are displayed -If you have stack enabled, you can select what the mouse hover feature should show. +### Series overrides -- Cumulative - Sum of series below plus the series you hover over -- Individual - Just the value for the series you hover over - -### Rendering - -- **Flot** - Render the graphs in the browser using Flot (default) -- **Graphite PNG** - Render the graph on the server using graphite's render API. - -### Tooltip - -- **All series** - Show all series on the same tooltip and a x crosshairs to help follow all series - -### Series Specific Overrides +{{< docs-imagebox img="/img/docs/v51/graph_display_overrides.png" max-width= "800px" >}} The section allows a series to be rendered differently from the others. For example, one series can be given -a thicker line width to make it stand out. +a thicker line width to make it stand out and/or be moved to the right Y-axis. #### Dashes Drawing Style There is an option under Series overrides to draw lines as dashes. Set Dashes to the value True to override the line draw setting for a specific series. +### Thresholds + +{{< docs-imagebox img="/img/docs/v51/graph_display_thresholds.png" max-width= "800px" >}} + +Thresholds allow you to add arbitrary lines or sections to the graph to make it easier to see when +the graph crosses a particular threshold. + ## Time Range -The time range tab allows you to override the dashboard time range and specify a panel specific time. Either through a relative from now time option or through a timeshift. +{{< docs-imagebox img="/img/docs/v51/graph-time-range.png" max-width= "900px" >}} -{{< docs-imagebox img="/img/docs/v45/graph-time-range.png" max-width= "900px" >}} +The time range tab allows you to override the dashboard time range and specify a panel specific time. +Either through a relative from now time option or through a timeshift. +Panel time overrides & timeshift are described in more detail [here]({{< relref "reference/timerange.md#panel-time-overrides-timeshift" >}}). From 403c64ab20b75b284803ed1a46c6b26a1e2220d7 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 23 Mar 2018 15:48:04 +0100 Subject: [PATCH 035/319] docs: update postgres, mysql and mssql documentation Due to changes closing #11306 --- docs/sources/features/datasources/mssql.md | 17 ++- docs/sources/features/datasources/mysql.md | 118 +++++++++++------- docs/sources/features/datasources/postgres.md | 63 +++++++--- 3 files changed, 124 insertions(+), 74 deletions(-) diff --git a/docs/sources/features/datasources/mssql.md b/docs/sources/features/datasources/mssql.md index 71baf4bd050..26ba6b31ceb 100644 --- a/docs/sources/features/datasources/mssql.md +++ b/docs/sources/features/datasources/mssql.md @@ -70,12 +70,11 @@ 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* -*$__utcTime(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to UTC depending on the server's local timeoffset and rename it to *time*.
    For example, *DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time* -*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename it to *time*.
    For example, *DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time* -*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name.
    For example, *dateColumn >= DATEADD(s, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND dateColumn <= DATEADD(s, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')* -*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *DATEADD(second, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')* -*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *DATEADD(second, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')* -*$__timeGroup(dateColumn,'5m'[, fillvalue])* | Will be replaced by an expression usable in GROUP BY clause. Providing a *fillValue* of *NULL* or *floating value* will automatically fill empty series in timerange with that value.
    For example, *cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), column))/300 as int)*300 as int)*. +*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename it to *time*.
    For example, *DATEDIFF(second, '1970-01-01', dateColumn) AS time* +*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name.
    For example, *dateColumn >= DATEADD(s, 1494410783, '1970-01-01') AND dateColumn <= DATEADD(s, 1494410783, '1970-01-01')* +*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *DATEADD(second, 1494410783, '1970-01-01')* +*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *DATEADD(second, 1494410783, '1970-01-01')* +*$__timeGroup(dateColumn,'5m'[, fillvalue])* | Will be replaced by an expression usable in GROUP BY clause. Providing a *fillValue* of *NULL* or *floating value* will automatically fill empty series in timerange with that value.
    For example, *CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)\*300*. *$__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* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* @@ -333,7 +332,7 @@ ORDER BY atimestamp Name | Description ------------ | ------------- -time | The name of the date/time field. Could be in a native sql time datatype or epoch seconds. +time | The name of the date/time field. Could be a column with a native sql date/time data type or epoch value. text | Event description field. tags | Optional field name to use for event tags as a comma separated string. @@ -349,7 +348,7 @@ CREATE TABLE [events] ( We also use the database table defined in [Time series queries](#time-series-queries). -**Example query using time column of type epoch seconds:** +**Example query using time column with epoch values:** ```sql SELECT @@ -363,7 +362,7 @@ WHERE ORDER BY 1 ``` -**Example query using time column of type datetime:** +**Example query using time column of native sql date/time data type:** ```sql SELECT diff --git a/docs/sources/features/datasources/mysql.md b/docs/sources/features/datasources/mysql.md index 6c15006949e..5334f21e178 100644 --- a/docs/sources/features/datasources/mysql.md +++ b/docs/sources/features/datasources/mysql.md @@ -12,6 +12,8 @@ weight = 7 # Using MySQL in Grafana > Only available in Grafana v4.3+. +> +> Starting from Grafana v5.1 you can name the time column *time* in addition to earlier supported *time_sec*. Usage of *time_sec* will eventually be deprecated. Grafana ships with a built-in MySQL data source plugin that allow you to query any visualize data from a MySQL compatible database. @@ -23,6 +25,17 @@ data from a MySQL compatible database. 3. Click the `+ Add data source` button in the top header. 4. Select *MySQL* from the *Type* dropdown. +### Data source options + +Name | Description +------------ | ------------- +*Name* | The data source name. This is how you refer to the data source in panels & queries. +*Default* | Default data source means that it will be pre-selected for new panels. +*Host* | The IP address/hostname and optional port of your MySQL instance. +*Database* | Name of your MySQL database. +*User* | Database user's login/username +*Password* | Database user's password + ### Database User Permissions (Important!) The database user you specify when you add the data source should only be granted SELECT permissions on @@ -46,10 +59,11 @@ 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 convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec* +*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec* *$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > FROM_UNIXTIME(1494410783) AND dateColumn < FROM_UNIXTIME(1494497183)* *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *FROM_UNIXTIME(1494410783)* *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *FROM_UNIXTIME(1494497183)* -*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed) as time_sec,* +*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),* *$__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* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* @@ -85,52 +99,50 @@ The resulting table panel: ![](/img/docs/v43/mysql_table.png) -### Time series queries +## Time series queries -If you set `Format as` to `Time series`, for use in Graph panel for example, then there are some requirements for -what your query returns. +If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must return a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch. +Any column except `time` and `metric` is treated as a value column. +You may return a column named `metric` that is used as metric name for the value column. -- Must be a column named `time_sec` representing a unix epoch in seconds. -- Must be a column named `value` representing the time series value. -- Must be a column named `metric` representing the time series name. - -Example: +**Example with `metric` column:** ```sql SELECT - min(UNIX_TIMESTAMP(time_date_time)) as time_sec, - max(value_double) as value, - metric1 as metric -FROM test_data -WHERE $__timeFilter(time_date_time) -GROUP BY metric1, UNIX_TIMESTAMP(time_date_time) DIV 300 -ORDER BY time_sec asc -``` - -Example with $__timeGroup macro: - -```sql -SELECT - $__timeGroup(time_date_time,'5m') as time_sec, - min(value_double) as value, - metric_name as metric + $__timeGroup(time_date_time,'5m'), + min(value_double), + 'min' as metric FROM test_data WHERE $__timeFilter(time_date_time) -GROUP BY 1, metric_name -ORDER BY 1 +GROUP BY time +ORDER BY time ``` -Example using the fill parameter in the $__timeGroup macro to convert null values to be zero instead: +**Example using the fill parameter in the $__timeGroup macro to convert null values to be zero instead:** ```sql -SELECT - $__timeGroup(atimestamp,'24h',0) as time_sec, - avg(afloat) as value, - avarchar as metric -FROM testdata.grafana_metrics -WHERE $__timeFilter(atimestamp) -GROUP BY 1, avarchar -ORDER BY 1 +SELECT + $__timeGroup(createdAt,'5m',0), + sum(value_double) as value, + measurement +FROM test_data +WHERE + $__timeFilter(createdAt) +GROUP BY time, measurement +ORDER BY time +``` + +**Example with multiple columns:** + +```sql +SELECT + $__timeGroup(time_date_time,'5m'), + min(value_double) as min_value, + max(value_double) as max_value +FROM test_data +WHERE $__timeFilter(time_date_time) +GROUP BY time +ORDER BY time ``` Currently, there is no support for a dynamic group by time based on time range & panel width. @@ -194,7 +206,7 @@ There are two syntaxes: ```sql SELECT - UNIX_TIMESTAMP(atimestamp) as time_sec, + UNIX_TIMESTAMP(atimestamp) as time, aint as value, avarchar as metric FROM my_table @@ -206,7 +218,7 @@ ORDER BY atimestamp ASC ```sql SELECT - UNIX_TIMESTAMP(atimestamp) as time_sec, + UNIX_TIMESTAMP(atimestamp) as time, aint as value, avarchar as metric FROM my_table @@ -216,23 +228,37 @@ ORDER BY atimestamp ASC ## Annotations -[Annotations]({{< relref "reference/annotations.md" >}}) allows you to overlay rich event information on top of graphs. You add annotation queries via the Dashboard menu / Annotations view. +[Annotations]({{< relref "reference/annotations.md" >}}) allow you to overlay rich event information on top of graphs. You add annotation queries via the Dashboard menu / Annotations view. -An example query: +**Example query using time column with epoch values:** ```sql SELECT - UNIX_TIMESTAMP(atimestamp) as time_sec, - value as text, + epoch_time as time, + metric1 as text, CONCAT(tag1, ',', tag2) as tags -FROM my_table -WHERE $__timeFilter(atimestamp) -ORDER BY atimestamp ASC +FROM + public.test_data +WHERE + $__unixEpochFilter(epoch_time) +``` + +**Example query using time column of native sql date/time data type:** + +```sql +SELECT + native_date_time as time, + metric1 as text, + CONCAT(tag1, ',', tag2) as tags +FROM + public.test_data +WHERE + $__timeFilter(native_date_time) ``` Name | Description ------------ | ------------- -time_sec | The name of the date/time field. +time | The name of the date/time field. Could be a column with a native sql date/time data type or epoch value. text | Event description field. tags | Optional field name to use for event tags as a comma separated string. diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index 270640a93dc..d830d8b9ed9 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/docs/sources/features/datasources/postgres.md @@ -20,6 +20,18 @@ Grafana ships with a built-in PostgreSQL data source plugin that allows you to q 3. Click the `+ Add data source` button in the top header. 4. Select *PostgreSQL* from the *Type* dropdown. +### Data source options + +Name | Description +------------ | ------------- +*Name* | The data source name. This is how you refer to the data source in panels & queries. +*Default* | Default data source means that it will be pre-selected for new panels. +*Host* | The IP address/hostname and optional port of your PostgreSQL instance. +*Database* | Name of your PostgreSQL database. +*User* | Database user's login/username +*Password* | Database user's password +*SSL Mode* | This option determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server. + ### Database User Permissions (Important!) The database user you specify when you add the data source should only be granted SELECT permissions on @@ -44,7 +56,7 @@ 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* -*$__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* +*$__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, *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)* @@ -85,48 +97,48 @@ The resulting table panel: ![](/img/docs/v46/postgres_table.png) -### Time series queries +## Time series queries -If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must return a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch in seconds. +If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must return a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch. Any column except `time` and `metric` is treated as a value column. You may return a column named `metric` that is used as metric name for the value column. -Example with `metric` column +**Example with `metric` column:** ```sql SELECT - $__timeGroup(time_date_time,'5m'), - min(value_double), + $__timeGroup("time_date_time",'5m'), + min("value_double"), 'min' as metric FROM test_data -WHERE $__timeFilter(time_date_time) +WHERE $__timeFilter("time_date_time") GROUP BY time ORDER BY time ``` -Example using the fill parameter in the $__timeGroup macro to convert null values to be zero instead: +**Example using the fill parameter in the $__timeGroup macro to convert null values to be zero instead:** ```sql SELECT $__timeGroup("createdAt",'5m',0), sum(value) as value, measurement -FROM public.grafana_metric +FROM test_data WHERE $__timeFilter("createdAt") GROUP BY time, measurement ORDER BY time ``` -Example with multiple columns: +**Example with multiple columns:** ```sql SELECT - $__timeGroup(time_date_time,'5m'), - min(value_double) as min_value, - max(value_double) as max_value + $__timeGroup("time_date_time",'5m'), + min("value_double") as "min_value", + max("value_double") as "max_value" FROM test_data -WHERE $__timeFilter(time_date_time) +WHERE $__timeFilter("time_date_time") GROUP BY time ORDER BY time ``` @@ -209,22 +221,35 @@ ORDER BY atimestamp ASC [Annotations]({{< relref "reference/annotations.md" >}}) allow you to overlay rich event information on top of graphs. You add annotation queries via the Dashboard menu / Annotations view. -An example query: +**Example query using time column with epoch values:** ```sql SELECT - extract(epoch from time_date_time) AS time, - metric1 as text, + epoch_time as time, + metric1 as text, concat_ws(', ', metric1::text, metric2::text) as tags FROM public.test_data WHERE - $__timeFilter(time_date_time) + $__unixEpochFilter(epoch_time) +``` + +**Example query using time column of native sql date/time data type:** + +```sql +SELECT + native_date_time as time, + metric1 as text, + concat_ws(', ', metric1::text, metric2::text) as tags +FROM + public.test_data +WHERE + $__timeFilter(native_date_time) ``` Name | Description ------------ | ------------- -time | The name of the date/time field. +time | The name of the date/time field. Could be a column with a native sql date/time data type or epoch value. text | Event description field. tags | Optional field name to use for event tags as a comma separated string. From 0a487c484d28768b6b8e257caeb537610bb415f1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 23 Mar 2018 15:49:54 +0100 Subject: [PATCH 036/319] docs: fix typos --- docs/sources/features/panels/graph.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/features/panels/graph.md b/docs/sources/features/panels/graph.md index d47131dc47c..bc0f896775e 100644 --- a/docs/sources/features/panels/graph.md +++ b/docs/sources/features/panels/graph.md @@ -105,7 +105,7 @@ For **Mode** there are three options: - **Show** - Uncheck to hide the legend (default checked/true) - **Table** - Check to display legend in table (default unchecked/false) - **To the right** - Check to display legend to the right (default unchecked/false) -- **Width** - Available when *To the right* is checked. Value to contral the minimum width for the legend (default 0) +- **Width** - Available when *To the right* is checked. Value to control the minimum width for the legend (default 0) ### Values @@ -150,7 +150,7 @@ Display styles control visual properties of the graph. - **Fill** - Amount of color fill for a series (default 1). 0 is none. - **Line Width** - The width of the line for a series (default 1). - **Staircase** - Draws adjacent points as staircase -- **Points Radius** - Asjust the size of points when *Points* are selected as *Draw Mode*. +- **Points Radius** - Adjust the size of points when *Points* are selected as *Draw Mode*. #### Hover tooltip From eabcbcda88f7118a4fd381fceee547ddd3f008f2 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 24 Mar 2018 11:39:20 +0100 Subject: [PATCH 037/319] 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 d6faa3d06f606410070b38ae78fc6665836358eb Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 27 Feb 2018 18:51:04 +0100 Subject: [PATCH 038/319] provisioning: improve UX when saving provisioned dashboards --- pkg/api/dashboard.go | 7 ++ pkg/api/dtos/dashboard.go | 1 + pkg/models/dashboards.go | 6 ++ pkg/services/provisioning/dashboards/types.go | 3 - .../sqlstore/dashboard_provisioning.go | 13 ++++ .../sqlstore/dashboard_provisioning_test.go | 10 +++ public/app/features/dashboard/all.ts | 1 + .../app/features/dashboard/dashboard_srv.ts | 11 +++ .../features/dashboard/dashnav/dashnav.html | 2 +- .../dashboard/save_provisioned_modal.ts | 74 +++++++++++++++++++ .../specs/save_provisioned_modal.jest.ts | 28 +++++++ 11 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 public/app/features/dashboard/save_provisioned_modal.ts create mode 100644 public/app/features/dashboard/specs/save_provisioned_modal.jest.ts diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 11a028cdd29..e5e4fc560e1 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -102,6 +102,13 @@ func GetDashboard(c *m.ReqContext) Response { meta.FolderUrl = query.Result.GetUrl() } + dpQuery := &m.GetProvisionedDashboardByDashboardId{DashboardId: dash.Id} + err = bus.Dispatch(dpQuery) + if dpQuery.Result != nil { + meta.CanEdit = true + meta.Provisioned = true + } + // make sure db version is in sync with json model version dash.Data.Set("version", dash.Version) diff --git a/pkg/api/dtos/dashboard.go b/pkg/api/dtos/dashboard.go index e4c66aebbda..39a6dca580d 100644 --- a/pkg/api/dtos/dashboard.go +++ b/pkg/api/dtos/dashboard.go @@ -28,6 +28,7 @@ type DashboardMeta struct { FolderId int64 `json:"folderId"` FolderTitle string `json:"folderTitle"` FolderUrl string `json:"folderUrl"` + Provisioned bool `json:"provisioned"` } type DashboardFullWithMeta struct { diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 4b771038df6..e4f0758fc19 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -317,6 +317,12 @@ type GetDashboardSlugByIdQuery struct { Result string } +type GetProvisionedDashboardByDashboardId struct { + DashboardId int64 + + Result *DashboardProvisioning +} + type GetProvisionedDashboardDataQuery struct { Name string diff --git a/pkg/services/provisioning/dashboards/types.go b/pkg/services/provisioning/dashboards/types.go index f742b321552..4a55351d3e4 100644 --- a/pkg/services/provisioning/dashboards/types.go +++ b/pkg/services/provisioning/dashboards/types.go @@ -55,9 +55,6 @@ func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *Das dash.OrgId = cfg.OrgId dash.Dashboard.OrgId = cfg.OrgId dash.Dashboard.FolderId = folderId - if !cfg.Editable { - dash.Dashboard.Data.Set("editable", cfg.Editable) - } if dash.Dashboard.Title == "" { return nil, models.ErrDashboardTitleEmpty diff --git a/pkg/services/sqlstore/dashboard_provisioning.go b/pkg/services/sqlstore/dashboard_provisioning.go index 69409c3b873..99178d38f9c 100644 --- a/pkg/services/sqlstore/dashboard_provisioning.go +++ b/pkg/services/sqlstore/dashboard_provisioning.go @@ -8,6 +8,7 @@ import ( func init() { bus.AddHandler("sql", GetProvisionedDashboardDataQuery) bus.AddHandler("sql", SaveProvisionedDashboard) + bus.AddHandler("sql", GetProvisionedDataByDashboardId) } type DashboardExtras struct { @@ -17,6 +18,18 @@ type DashboardExtras struct { Value string } +func GetProvisionedDataByDashboardId(cmd *models.GetProvisionedDashboardByDashboardId) error { + result := &models.DashboardProvisioning{} + + _, err := x.Where("dashboard_id = ?", cmd.DashboardId).Get(result) + if err != nil { + return err + } + + cmd.Result = result + return nil +} + func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error { return inTransaction(func(sess *DBSession) error { err := saveDashboard(sess, cmd.DashboardCmd) diff --git a/pkg/services/sqlstore/dashboard_provisioning_test.go b/pkg/services/sqlstore/dashboard_provisioning_test.go index b752173b67d..89b3451a3ac 100644 --- a/pkg/services/sqlstore/dashboard_provisioning_test.go +++ b/pkg/services/sqlstore/dashboard_provisioning_test.go @@ -50,6 +50,16 @@ func TestDashboardProvisioningTest(t *testing.T) { So(query.Result[0].DashboardId, ShouldEqual, dashId) So(query.Result[0].Updated, ShouldEqual, now.Unix()) }) + + Convey("Can query for one provisioned dashboard", func() { + query := &models.GetProvisionedDashboardByDashboardId{DashboardId: cmd.Result.Id} + + err := GetProvisionedDataByDashboardId(query) + So(err, ShouldBeNil) + + So(query.Result.DashboardId, ShouldEqual, cmd.Result.Id) + So(query.Result.Updated, ShouldEqual, now.Unix()) + }) }) }) } diff --git a/public/app/features/dashboard/all.ts b/public/app/features/dashboard/all.ts index f2e2e3dcdc0..a8f491f3ddd 100644 --- a/public/app/features/dashboard/all.ts +++ b/public/app/features/dashboard/all.ts @@ -6,6 +6,7 @@ import './dashnav/dashnav'; import './submenu/submenu'; import './save_as_modal'; import './save_modal'; +import './save_provisioned_modal'; import './shareModalCtrl'; import './share_snapshot_ctrl'; import './dashboard_srv'; diff --git a/public/app/features/dashboard/dashboard_srv.ts b/public/app/features/dashboard/dashboard_srv.ts index 9d766fdfc3f..3aa7ca118fb 100644 --- a/public/app/features/dashboard/dashboard_srv.ts +++ b/public/app/features/dashboard/dashboard_srv.ts @@ -105,6 +105,10 @@ export class DashboardSrv { this.setCurrent(this.create(clone, this.dash.meta)); } + if (this.dash.meta.provisioned) { + return this.showDashboardProvisionedModal(); + } + if (!this.dash.meta.canSave && options.makeEditable !== true) { return Promise.resolve(); } @@ -120,6 +124,13 @@ export class DashboardSrv { return this.save(this.dash.getSaveModelClone(), options); } + showDashboardProvisionedModal() { + this.$rootScope.appEvent('show-modal', { + templateHtml: '', + modalClass: 'modal--narrow', + }); + } + showSaveAsModal() { this.$rootScope.appEvent('show-modal', { templateHtml: '', diff --git a/public/app/features/dashboard/dashnav/dashnav.html b/public/app/features/dashboard/dashnav/dashnav.html index 269d4b0bada..0c3f949ed7c 100644 --- a/public/app/features/dashboard/dashnav/dashnav.html +++ b/public/app/features/dashboard/dashnav/dashnav.html @@ -17,7 +17,7 @@
    - Title (depricated) + Title (deprecated)
    diff --git a/public/app/plugins/datasource/influxdb/specs/query_builder.jest.ts b/public/app/plugins/datasource/influxdb/specs/query_builder.jest.ts index 439bf7b1fc5..eeae987b139 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_builder.jest.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_builder.jest.ts @@ -97,7 +97,7 @@ describe('InfluxQueryBuilder', function() { expect(query).toBe('SHOW TAG VALUES FROM "one_week"."cpu" WITH KEY = "app" WHERE "host" = \'server1\''); }); - it('should not includ policy when policy is default', function() { + it('should not include policy when policy is default', function() { var builder = new InfluxQueryBuilder({ measurement: 'cpu', policy: 'default', diff --git a/public/app/plugins/datasource/mssql/partials/annotations.editor.html b/public/app/plugins/datasource/mssql/partials/annotations.editor.html index 8a94c470379..b2c0d7b97a6 100644 --- a/public/app/plugins/datasource/mssql/partials/annotations.editor.html +++ b/public/app/plugins/datasource/mssql/partials/annotations.editor.html @@ -18,7 +18,7 @@
    Annotation Query Format
    -An annotation is an event that is overlayed on top of graphs. The query can have up to three columns per row, the time column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. +An annotation is an event that is overlaid on top of graphs. The query can have up to three columns per row, the time column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. - column with alias: time for the annotation event time. Use epoch time or any native date data type. - column with alias: text for the annotation text. diff --git a/public/app/plugins/datasource/mysql/partials/annotations.editor.html b/public/app/plugins/datasource/mysql/partials/annotations.editor.html index d142e091fed..23ec726a9f0 100644 --- a/public/app/plugins/datasource/mysql/partials/annotations.editor.html +++ b/public/app/plugins/datasource/mysql/partials/annotations.editor.html @@ -18,7 +18,7 @@
    Annotation Query Format
    -An annotation is an event that is overlayed on top of graphs. The query can have up to three columns per row, the time or time_sec column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. +An annotation is an event that is overlaid on top of graphs. The query can have up to three columns per row, the time or time_sec column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. - column with alias: time or time_sec for the annotation event time. Use epoch time or any native date data type. - column with alias: text for the annotation text diff --git a/public/app/plugins/datasource/postgres/img/postgresql_logo.svg b/public/app/plugins/datasource/postgres/img/postgresql_logo.svg index d98e3659c39..40a39970070 100644 --- a/public/app/plugins/datasource/postgres/img/postgresql_logo.svg +++ b/public/app/plugins/datasource/postgres/img/postgresql_logo.svg @@ -3,7 +3,7 @@ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> - + @@ -19,4 +19,4 @@ - \ No newline at end of file + diff --git a/public/app/plugins/datasource/postgres/partials/annotations.editor.html b/public/app/plugins/datasource/postgres/partials/annotations.editor.html index 09232d6f8ed..907b1b10be4 100644 --- a/public/app/plugins/datasource/postgres/partials/annotations.editor.html +++ b/public/app/plugins/datasource/postgres/partials/annotations.editor.html @@ -18,7 +18,7 @@
    Annotation Query Format
    -An annotation is an event that is overlayed on top of graphs. The query can have up to three columns per row, the time column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. +An annotation is an event that is overlaid on top of graphs. The query can have up to three columns per row, the time column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. - column with alias: time for the annotation event time. Use epoch time or any native date data type. - column with alias: text for the annotation text diff --git a/public/app/plugins/panel/graph/jquery.flot.events.js b/public/app/plugins/panel/graph/jquery.flot.events.js index 1aa79c5056f..3ea3ca8f330 100644 --- a/public/app/plugins/panel/graph/jquery.flot.events.js +++ b/public/app/plugins/panel/graph/jquery.flot.events.js @@ -52,14 +52,14 @@ function ($, _, angular, Drop) { var eventManager = plot.getOptions().events.manager; if (eventManager.editorOpen) { // update marker element to attach to (needed in case of legend on the right - // when there is a double render pass and the inital marker element is removed) + // when there is a double render pass and the initial marker element is removed) markerElementToAttachTo = element; return; } // mark as openend eventManager.editorOpened(); - // set marker elment to attache to + // set marker element to attache to markerElementToAttachTo = element; // wait for element to be attached and positioned diff --git a/public/app/plugins/panel/graph/legend.ts b/public/app/plugins/panel/graph/legend.ts index b668555b6a6..6b6c89444dc 100644 --- a/public/app/plugins/panel/graph/legend.ts +++ b/public/app/plugins/panel/graph/legend.ts @@ -129,7 +129,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { elem.empty(); - // Set min-width if side style and there is a value, otherwise remove the CSS propery + // Set min-width if side style and there is a value, otherwise remove the CSS property // Set width so it works with IE11 var width: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + 'px' : ''; var ieWidth: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth - 1 + 'px' : ''; diff --git a/public/app/plugins/panel/graph/series_overrides_ctrl.ts b/public/app/plugins/panel/graph/series_overrides_ctrl.ts index 703c4648716..ecf79a8a4fb 100644 --- a/public/app/plugins/panel/graph/series_overrides_ctrl.ts +++ b/public/app/plugins/panel/graph/series_overrides_ctrl.ts @@ -31,7 +31,7 @@ export class SeriesOverridesCtrl { $scope.override[item.propertyName] = subItem.value; - // automatically disable lines for this series and the fill bellow to series + // automatically disable lines for this series and the fill below to series // can be removed by the user if they still want lines if (item.propertyName === 'fillBelowTo') { $scope.override['lines'] = false; diff --git a/public/app/plugins/panel/table/specs/transformers.jest.ts b/public/app/plugins/panel/table/specs/transformers.jest.ts index a59b3ae48ee..eefe3f9bdc0 100644 --- a/public/app/plugins/panel/table/specs/transformers.jest.ts +++ b/public/app/plugins/panel/table/specs/transformers.jest.ts @@ -221,7 +221,7 @@ describe('when transforming time series table', () => { expect(table.rows[0][2]).toBe(42); }); - it('should return 2 rows for a mulitple queries with same label values plus one extra row', () => { + it('should return 2 rows for a multiple queries with same label values plus one extra row', () => { table = transformDataToTable(multipleQueriesDataSameLabels, panel); expect(table.rows.length).toBe(2); expect(table.rows[0][0]).toBe(time); @@ -238,7 +238,7 @@ describe('when transforming time series table', () => { expect(table.rows[1][5]).toBe(7); }); - it('should return 2 rows for mulitple queries with different label values', () => { + it('should return 2 rows for multiple queries with different label values', () => { table = transformDataToTable(multipleQueriesDataDifferentLabels, panel); expect(table.rows.length).toBe(2); expect(table.columns.length).toBe(6); diff --git a/public/app/plugins/panel/table/transformers.ts b/public/app/plugins/panel/table/transformers.ts index 43088dc22ac..1659ba3e3aa 100644 --- a/public/app/plugins/panel/table/transformers.ts +++ b/public/app/plugins/panel/table/transformers.ts @@ -243,7 +243,7 @@ transformers['table'] = { row[columnIndex] = matchedRow[columnIndex]; } } - // Dont visit this row again + // Don't visit this row again mergedRows[match] = matchedRow; // Keep looking for more rows to merge offset = match + 1; diff --git a/public/dashboards/scripted_templated.js b/public/dashboards/scripted_templated.js index 5a05aa55b5d..f1b0b115fa1 100644 --- a/public/dashboards/scripted_templated.js +++ b/public/dashboards/scripted_templated.js @@ -22,7 +22,7 @@ var dashboard; // All url parameters are available via the ARGS object var ARGS; -// Intialize a skeleton with nothing but a rows array and service object +// Initialize a skeleton with nothing but a rows array and service object dashboard = { rows : [], schemaVersion: 13, From 638f7d23d4c4cb0cbfd839eb6237d79343c4f84d Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 13 Apr 2018 20:02:45 +0200 Subject: [PATCH 105/319] docs: fix codespell issues --- docs/sources/administration/provisioning.md | 2 +- docs/sources/alerting/notifications.md | 2 +- docs/sources/alerting/rules.md | 2 +- docs/sources/contribute/cla.md | 4 ++-- docs/sources/features/datasources/opentsdb.md | 4 ++-- docs/sources/features/panels/alertlist.md | 2 +- docs/sources/features/panels/dashlist.md | 2 +- docs/sources/features/panels/singlestat.md | 4 ++-- docs/sources/guides/whats-new-in-v2-6.md | 2 +- docs/sources/guides/whats-new-in-v4-1.md | 2 +- docs/sources/guides/whats-new-in-v4-5.md | 4 ++-- docs/sources/guides/whats-new-in-v4-6.md | 2 +- docs/sources/http_api/org.md | 4 ++-- docs/sources/installation/configuration.md | 2 +- docs/sources/installation/docker.md | 2 +- docs/sources/installation/upgrading.md | 2 +- docs/sources/reference/templating.md | 4 ++-- docs/sources/tutorials/authproxy.md | 10 +++++----- 18 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index 7936a1708eb..23fbe0c89fd 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -206,7 +206,7 @@ When Grafana starts, it will update/insert all dashboards available in the confi ### Reuseable Dashboard Urls -If the dashboard in the json file contains an [uid](/reference/dashboard/#json-fields), Grafana will force insert/update on that uid. This allows you to migrate dashboards betweens Grafana instances and provisioning Grafana from configuration without breaking the urls given since the new dashboard url uses the uid as identifer. +If the dashboard in the json file contains an [uid](/reference/dashboard/#json-fields), Grafana will force insert/update on that uid. This allows you to migrate dashboards betweens Grafana instances and provisioning Grafana from configuration without breaking the urls given since the new dashboard url uses the uid as identifier. When Grafana starts, it will update/insert all dashboards available in the configured folders. If you modify the file, the dashboard will also be updated. By default Grafana will delete dashboards in the database if the file is removed. You can disable this behavior using the `disableDeletion` setting. diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index bb119687750..d279d3af20b 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -153,7 +153,7 @@ Prometheus Alertmanager | `prometheus-alertmanager` | no # Enable images in notifications {#external-image-store} -Grafana can render the panel associated with the alert rule and include that in the notification. Most Notification Channels require that this image be publicly accessable (Slack and PagerDuty for example). In order to include images in alert notifications, Grafana can upload the image to an image store. It currently supports +Grafana can render the panel associated with the alert rule and include that in the notification. Most Notification Channels require that this image be publicly accessible (Slack and PagerDuty for example). In order to include images in alert notifications, Grafana can upload the image to an image store. It currently supports Amazon S3, Webdav, Google Cloud Storage and Azure Blob Storage. So to set that up you need to configure the [external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini config file. Be aware that some notifiers requires public access to the image to be able to include it in the notification. So make sure to enable public access to the images. If your using local image uploader, your Grafana instance need to be accessible by the internet. diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 9bbbd70641d..bcca3c6b2fb 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -110,7 +110,7 @@ to `Keep Last State` in order to basically ignore them. ## Notifications -In alert tab you can also specify alert rule notifications along with a detailed messsage about the alert rule. +In alert tab you can also specify alert rule notifications along with a detailed message about the alert rule. The message can contain anything, information about how you might solve the issue, link to runbook, etc. The actual notifications are configured and shared between multiple alerts. Read the diff --git a/docs/sources/contribute/cla.md b/docs/sources/contribute/cla.md index b990187d809..ffb2aaef1b9 100644 --- a/docs/sources/contribute/cla.md +++ b/docs/sources/contribute/cla.md @@ -1,6 +1,6 @@ +++ title = "Contributor Licence Agreement (CLA)" -description = "Contributer Licence Agreement (CLA)" +description = "Contributor Licence Agreement (CLA)" type = "docs" aliases = ["/project/cla", "docs/contributing/cla.html"] [menu.docs] @@ -101,4 +101,4 @@ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU [OR US]


    -This CLA aggreement is based on the [Harmony Contributor Aggrement Template (combined)](http://www.harmonyagreements.org/agreements.html), [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/) +This CLA agreement is based on the [Harmony Contributor Aggrement Template (combined)](http://www.harmonyagreements.org/agreements.html), [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/) diff --git a/docs/sources/features/datasources/opentsdb.md b/docs/sources/features/datasources/opentsdb.md index 6333861dca7..0959817c015 100644 --- a/docs/sources/features/datasources/opentsdb.md +++ b/docs/sources/features/datasources/opentsdb.md @@ -78,7 +78,7 @@ the existing time series data in OpenTSDB, you need to run `tsdb uid metasync` o ### Nested Templating -One template variable can be used to filter tag values for another template varible. First parameter is the metric name, +One template variable can be used to filter tag values for another template variable. First parameter is the metric name, second parameter is the tag key for which you need to find tag values, and after that all other dependent template variables. Some examples are mentioned below to make nested template queries work successfully. @@ -106,4 +106,4 @@ datasources: jsonData: tsdbResolution: 1 tsdbVersion: 1 -``` \ No newline at end of file +``` diff --git a/docs/sources/features/panels/alertlist.md b/docs/sources/features/panels/alertlist.md index 9307bb71391..58aa2c0966a 100644 --- a/docs/sources/features/panels/alertlist.md +++ b/docs/sources/features/panels/alertlist.md @@ -14,7 +14,7 @@ weight = 4 {{< docs-imagebox img="/img/docs/v45/alert-list-panel.png" max-width="850px" >}} -The alert list panel allows you to display your dashbords alerts. The list can be configured to show current state or recent state changes. You can read more about alerts [here](http://docs.grafana.org/alerting/rules). +The alert list panel allows you to display your dashboards alerts. The list can be configured to show current state or recent state changes. You can read more about alerts [here](http://docs.grafana.org/alerting/rules). ## Alert List Options diff --git a/docs/sources/features/panels/dashlist.md b/docs/sources/features/panels/dashlist.md index 8a4ed60875d..2ee578c5b7e 100644 --- a/docs/sources/features/panels/dashlist.md +++ b/docs/sources/features/panels/dashlist.md @@ -25,7 +25,7 @@ The dashboard list panel allows you to display dynamic links to other dashboards 1. **Starred**: The starred dashboard selection displays starred dashboards in alphabetical order. 2. **Recently Viewed**: The recently viewed dashboard selection displays recently viewed dashboards in alphabetical order. 3. **Search**: The search dashboard selection displays dashboards by search query or tag(s). -4. **Show Headings**: When show headings is ticked the choosen list selection(Starred, Recently Viewed, Search) is shown as a heading. +4. **Show Headings**: When show headings is ticked the chosen list selection(Starred, Recently Viewed, Search) is shown as a heading. 5. **Max Items**: Max items set the maximum of items in a list. 6. **Query**: Here is where you enter your query you want to search by. Queries are case-insensitive, and partial values are accepted. 7. **Tags**: Here is where you enter your tag(s) you want to search by. Note that existing tags will not appear as you type, and *are* case sensitive. To see a list of existing tags, you can always return to the dashboard, open the Dashboard Picker at the top and click `tags` link in the search bar. diff --git a/docs/sources/features/panels/singlestat.md b/docs/sources/features/panels/singlestat.md index 510642337ff..0eb442914f5 100644 --- a/docs/sources/features/panels/singlestat.md +++ b/docs/sources/features/panels/singlestat.md @@ -30,7 +30,7 @@ The singlestat panel has a normal query editor to allow you define your exact me * **total** - The sum of all the non-null values in the series * **first** - The first value in the series * **delta** - The total incremental increase (of a counter) in the series. An attempt is made to account for counter resets, but this will only be accurate for single instance metrics. Used to show total counter increase in time series. - * **diff** - The difference betwen 'current' (last value) and 'first'. + * **diff** - The difference between 'current' (last value) and 'first'. * **range** - The difference between 'min' and 'max'. Useful the show the range of change for a gauge. 2. **Prefix/Postfix**: The Prefix/Postfix fields let you define a custom label to appear *before/after* the value. The `$__name` variable can be used here to use the series name or alias from the metric query. 3. **Units**: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value. @@ -70,7 +70,7 @@ Gauges gives a clear picture of how high a value is in it's context. It's a grea {{< docs-imagebox img="/img/docs/v45/singlestat-gauge-options.png" max-width="500px" class="docs-image--right docs-image--no-shadow">}} -1. **Show**: The show checkbox will toggle wether the gauge is shown in the panel. When unselected, only the Singlestat value will appear. +1. **Show**: The show checkbox will toggle whether the gauge is shown in the panel. When unselected, only the Singlestat value will appear. 2. **Min/Max**: This sets the start and end point for the gauge. 3. **Threshold Labels**: Check if you want to show the threshold labels. Thresholds are set in the color options. 4. **Threshold Markers**: Check if you want to have a second meter showing the thresholds. diff --git a/docs/sources/guides/whats-new-in-v2-6.md b/docs/sources/guides/whats-new-in-v2-6.md index b8996680ce6..1e6f30c597b 100644 --- a/docs/sources/guides/whats-new-in-v2-6.md +++ b/docs/sources/guides/whats-new-in-v2-6.md @@ -15,7 +15,7 @@ support for multiple Cloudwatch credentials. The new table panel is very flexible, supporting both multiple modes for time series as well as for -table, annotation and raw JSON data. It also provides date formating and value formating and coloring options. +table, annotation and raw JSON data. It also provides date formatting and value formatting and coloring options. ### Time series to rows diff --git a/docs/sources/guides/whats-new-in-v4-1.md b/docs/sources/guides/whats-new-in-v4-1.md index bd2b0f1b75f..217b21b545e 100644 --- a/docs/sources/guides/whats-new-in-v4-1.md +++ b/docs/sources/guides/whats-new-in-v4-1.md @@ -33,7 +33,7 @@ You can enable/disable the shared tooltip from the dashboard settings menu or cy {{< imgbox max-width="60%" img="/img/docs/v41/helptext_for_panel_settings.png" caption="Hovering help text" >}} -You can set a help text in the general tab on any panel. The help text is using Markdown to enable better formating and linking to other sites that can provide more information. +You can set a help text in the general tab on any panel. The help text is using Markdown to enable better formatting and linking to other sites that can provide more information.
    diff --git a/docs/sources/guides/whats-new-in-v4-5.md b/docs/sources/guides/whats-new-in-v4-5.md index b2de451308a..a5cd3ca982d 100644 --- a/docs/sources/guides/whats-new-in-v4-5.md +++ b/docs/sources/guides/whats-new-in-v4-5.md @@ -12,7 +12,7 @@ weight = -4 # What's New in Grafana v4.5 -## Hightlights +## Highlights ### New prometheus query editor @@ -62,7 +62,7 @@ Datas source selection & options & help are now above your metric queries. ### Minor Changes * **InfluxDB**: Change time range filter for absolute time ranges to be inclusive instead of exclusive [#8319](https://github.com/grafana/grafana/issues/8319), thx [@Oxydros](https://github.com/Oxydros) -* **InfluxDB**: Added paranthesis around tag filters in queries [#9131](https://github.com/grafana/grafana/pull/9131) +* **InfluxDB**: Added parenthesis around tag filters in queries [#9131](https://github.com/grafana/grafana/pull/9131) ## Bug Fixes diff --git a/docs/sources/guides/whats-new-in-v4-6.md b/docs/sources/guides/whats-new-in-v4-6.md index fd75384761f..09955fa58cc 100644 --- a/docs/sources/guides/whats-new-in-v4-6.md +++ b/docs/sources/guides/whats-new-in-v4-6.md @@ -45,7 +45,7 @@ This makes exploring and filtering Prometheus data much easier. * **GCS**: Adds support for Google Cloud Storage [#8370](https://github.com/grafana/grafana/issues/8370) thx [@chuhlomin](https://github.com/chuhlomin) * **Prometheus**: Adds /metrics endpoint for exposing Grafana metrics. [#9187](https://github.com/grafana/grafana/pull/9187) -* **Graph**: Add support for local formating in axis. [#1395](https://github.com/grafana/grafana/issues/1395), thx [@m0nhawk](https://github.com/m0nhawk) +* **Graph**: Add support for local formatting in axis. [#1395](https://github.com/grafana/grafana/issues/1395), thx [@m0nhawk](https://github.com/m0nhawk) * **Jaeger**: Add support for open tracing using jaeger in Grafana. [#9213](https://github.com/grafana/grafana/pull/9213) * **Unit types**: New date & time unit types added, useful in singlestat to show dates & times. [#3678](https://github.com/grafana/grafana/issues/3678), [#6710](https://github.com/grafana/grafana/issues/6710), [#2764](https://github.com/grafana/grafana/issues/2764) * **CLI**: Make it possible to install plugins from any url [#5873](https://github.com/grafana/grafana/issues/5873) diff --git a/docs/sources/http_api/org.md b/docs/sources/http_api/org.md index 4c1dff904c8..b9a15450786 100644 --- a/docs/sources/http_api/org.md +++ b/docs/sources/http_api/org.md @@ -307,7 +307,7 @@ Content-Type: application/json `PUT /api/orgs/:orgId` -Update Organisation, fields *Adress 1*, *Adress 2*, *City* are not implemented yet. +Update Organisation, fields *Address 1*, *Address 2*, *City* are not implemented yet. **Example Request**: @@ -436,4 +436,4 @@ HTTP/1.1 200 Content-Type: application/json {"message":"User removed from organization"} -``` \ No newline at end of file +``` diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 6169280b798..b7fe9040574 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -482,7 +482,7 @@ Set api_url to the resource that returns [OpenID UserInfo](https://connect2id.co First set up Grafana as an OpenId client "webapplication" in Okta. Then set the Base URIs to `https:///` and set the Login redirect URIs to `https:///login/generic_oauth`. -Finaly set up the generic oauth module like this: +Finally set up the generic oauth module like this: ```bash [auth.generic_oauth] name = Okta diff --git a/docs/sources/installation/docker.md b/docs/sources/installation/docker.md index 3ca5ba06638..f246bd55d33 100644 --- a/docs/sources/installation/docker.md +++ b/docs/sources/installation/docker.md @@ -12,7 +12,7 @@ weight = 4 # Installing using Docker -Grafana is very easy to install and run using the offical docker container. +Grafana is very easy to install and run using the official docker container. ```bash $ docker run -d -p 3000:3000 grafana/grafana diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index 49cdd4ca1d3..c72bb4c0921 100644 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -25,7 +25,7 @@ Before upgrading it can be a good idea to backup your Grafana database. This wil If you use sqlite you only need to make a backup of your `grafana.db` file. This is usually located at `/var/lib/grafana/grafana.db` on unix system. If you are unsure what database you use and where it is stored check you grafana configuration file. If you -installed grafana to custom location using a binary tar/zip it is usally in `/data`. +installed grafana to custom location using a binary tar/zip it is usually in `/data`. #### mysql diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md index 016d64d9ee9..6dbc9cc9d11 100644 --- a/docs/sources/reference/templating.md +++ b/docs/sources/reference/templating.md @@ -168,7 +168,7 @@ Option | Description *Include All option* | Add a special `All` option whose value includes all options. *Custom all value* | By default the `All` value will include all options in combined expression. This can become very long and can have performance problems. Many times it can be better to specify a custom all value, like a wildcard regex. To make it possible to have custom regex, globs or lucene syntax in the **Custom all value** option it is never escaped so you will have to think avbout what is a valid value for your data source. -### Formating multiple values +### Formatting multiple values Interpolating a variable with multiple values selected is tricky as it is not straight forward how to format the multiple values to into a string that is valid in the given context where the variable is used. Grafana tries to solve this by allowing each data source plugin to @@ -186,7 +186,7 @@ break the regex expression. **Elasticsearch** uses lucene query syntax, so the same variable would, in this case, be formatted as `("host1" OR "host2" OR "host3")`. In this case every value needs to be escaped so that the value can contain lucene control words and quotation marks. -#### Formating troubles +#### Formatting troubles Automatic escaping & formatting can cause problems and it can be tricky to grasp the logic is behind it. Especially for InfluxDB and Prometheus where the use of regex syntax requires that the variable is used in regex operator context. diff --git a/docs/sources/tutorials/authproxy.md b/docs/sources/tutorials/authproxy.md index 8003be20644..6f13de85c18 100644 --- a/docs/sources/tutorials/authproxy.md +++ b/docs/sources/tutorials/authproxy.md @@ -108,7 +108,7 @@ In this example we use Apache as a reverseProxy in front of Grafana. Apache hand * The next part of the configuration is the tricky part. We use Apache’s rewrite engine to create our **X-WEBAUTH-USER header**, populated with the authenticated user. - * **RewriteRule .* - [E=PROXY_USER:%{LA-U:REMOTE_USER}, NS]**: This line is a little bit of magic. What it does, is for every request use the rewriteEngines look-ahead (LA-U) feature to determine what the REMOTE_USER variable would be set to after processing the request. Then assign the result to the variable PROXY_USER. This is neccessary as the REMOTE_USER variable is not available to the RequestHeader function. + * **RewriteRule .* - [E=PROXY_USER:%{LA-U:REMOTE_USER}, NS]**: This line is a little bit of magic. What it does, is for every request use the rewriteEngines look-ahead (LA-U) feature to determine what the REMOTE_USER variable would be set to after processing the request. Then assign the result to the variable PROXY_USER. This is necessary as the REMOTE_USER variable is not available to the RequestHeader function. * **RequestHeader set X-WEBAUTH-USER “%{PROXY_USER}e”**: With the authenticated username now stored in the PROXY_USER variable, we create a new HTTP request header that will be sent to our backend Grafana containing the username. @@ -149,7 +149,7 @@ auto_sign_up = true ##### Grafana Container -For this example, we use the offical Grafana docker image available at [Docker Hub](https://hub.docker.com/r/grafana/grafana/) +For this example, we use the official Grafana docker image available at [Docker Hub](https://hub.docker.com/r/grafana/grafana/) * Create a file `grafana.ini` with the following contents @@ -166,7 +166,7 @@ header_property = username auto_sign_up = true ``` -* Launch the Grafana container, using our custom grafana.ini to replace `/etc/grafana/grafana.ini`. We dont expose any ports for this container as it will only be connected to by our Apache container. +* Launch the Grafana container, using our custom grafana.ini to replace `/etc/grafana/grafana.ini`. We don't expose any ports for this container as it will only be connected to by our Apache container. ```bash docker run -i -v $(pwd)/grafana.ini:/etc/grafana/grafana.ini --name grafana grafana/grafana @@ -174,7 +174,7 @@ docker run -i -v $(pwd)/grafana.ini:/etc/grafana/grafana.ini --name grafana graf ### Apache Container -For this example we use the offical Apache docker image available at [Docker Hub](https://hub.docker.com/_/httpd/) +For this example we use the official Apache docker image available at [Docker Hub](https://hub.docker.com/_/httpd/) * Create a file `httpd.conf` with the following contents @@ -244,4 +244,4 @@ ProxyPassReverse / http://grafana:3000/ ### Use grafana. -With our Grafana and Apache containers running, you can now connect to http://localhost/ and log in using the username/password we created in the htpasswd file. \ No newline at end of file +With our Grafana and Apache containers running, you can now connect to http://localhost/ and log in using the username/password we created in the htpasswd file. From e2add988ec9eb7bec985c901002964b1646d4458 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Sat, 14 Apr 2018 09:56:47 -0400 Subject: [PATCH 106/319] Documentation spelling fix --- docs/sources/alerting/notifications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index bb119687750..19e7d6982fc 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -156,7 +156,7 @@ Prometheus Alertmanager | `prometheus-alertmanager` | no Grafana can render the panel associated with the alert rule and include that in the notification. Most Notification Channels require that this image be publicly accessable (Slack and PagerDuty for example). In order to include images in alert notifications, Grafana can upload the image to an image store. It currently supports Amazon S3, Webdav, Google Cloud Storage and Azure Blob Storage. So to set that up you need to configure the [external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini config file. -Be aware that some notifiers requires public access to the image to be able to include it in the notification. So make sure to enable public access to the images. If your using local image uploader, your Grafana instance need to be accessible by the internet. +Be aware that some notifiers requires public access to the image to be able to include it in the notification. So make sure to enable public access to the images. If you're using local image uploader, your Grafana instance need to be accessible by the internet. Currently only the Email Channels attaches images if no external image store is specified. To include images in alert notifications for other channels then you need to set up an external image store. From 52bd51f2d0afa0e0edae4e4114f25ebcbb89bfb9 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sat, 14 Apr 2018 17:59:33 +0200 Subject: [PATCH 107/319] changelog: adds note for #11530 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 170d366cb24..9a50a910471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * **Prometheus**: Escape backslash in labels correctly. [#10555](https://github.com/grafana/grafana/issues/10555), thx [@roidelapluie](https://github.com/roidelapluie) * **Variables**: Case-insensitive sorting for template values [#11128](https://github.com/grafana/grafana/issues/11128) thx [@cross](https://github.com/cross) * **Annotations (native)**: Change default limit from 10 to 100 when querying api [#11569](https://github.com/grafana/grafana/issues/11569), thx [@flopp999](https://github.com/flopp999) +* **MySQL/Postgres/MSSQL**: PostgreSQL datasource generates invalid query with dates before 1970 [#11530](https://github.com/grafana/grafana/issues/11530) thx [@ryantxu](https://github.com/ryantxu) # 5.0.4 (2018-03-28) From 1161c7bc3e4a5baabbde9b7eaec8f444fbc80fac Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Sun, 15 Apr 2018 17:56:56 +0200 Subject: [PATCH 108/319] add postgresVersion to postgres settings --- public/app/plugins/datasource/postgres/module.ts | 8 +++++++- .../datasource/postgres/partials/config.html | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/postgres/module.ts b/public/app/plugins/datasource/postgres/module.ts index acd23318b6d..766e7b2ec37 100644 --- a/public/app/plugins/datasource/postgres/module.ts +++ b/public/app/plugins/datasource/postgres/module.ts @@ -8,8 +8,14 @@ class PostgresConfigCtrl { /** @ngInject **/ constructor($scope) { - this.current.jsonData.sslmode = this.current.jsonData.sslmode || 'require'; + this.current.jsonData.sslmode = this.current.jsonData.sslmode || 'verify-full'; } + + /* the values are chosen to be equivalent to `select current_setting('server_version_num');` */ + postgresVersions = [ + { name: '8.0+', value: 80000 }, + { name: '8.1+', value: 80100 }, + ]; } const defaultQuery = `SELECT diff --git a/public/app/plugins/datasource/postgres/partials/config.html b/public/app/plugins/datasource/postgres/partials/config.html index 77f0dcfa4a5..51fd66d7ed6 100644 --- a/public/app/plugins/datasource/postgres/partials/config.html +++ b/public/app/plugins/datasource/postgres/partials/config.html @@ -38,6 +38,22 @@
    +

    PostgreSQL details

    + +
    +
    + + Version + + This option controls what functions are used when expanding grafana macros. + + + + + +
    +
    +
    User Permission
    From 9b61ffb48ad9544b52b35f5870bfca1db098ae47 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Sun, 15 Apr 2018 18:38:20 +0200 Subject: [PATCH 109/319] make timefilter macro aware of pg version --- pkg/tsdb/postgres/macros.go | 12 +++++- pkg/tsdb/postgres/macros_test.go | 65 +++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index bd0ac0cc620..b9a7580b3ce 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -79,11 +79,19 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, } return fmt.Sprintf("extract(epoch from %s) as \"time\"", args[0]), nil case "__timeFilter": - // dont use to_timestamp in this macro for redshift compatibility #9566 if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("extract(epoch from %s) BETWEEN %d AND %d", args[0], m.TimeRange.GetFromAsSecondsEpoch(), m.TimeRange.GetToAsSecondsEpoch()), nil + + pg_version := m.Query.DataSource.JsonData.Get("postgresVersion").MustInt(0) + if pg_version >= 80100 { + // postgres has to_timestamp(double) starting with 8.1 + return fmt.Sprintf("%s BETWEEN to_timestamp(%d) AND to_timestamp(%d)", args[0], m.TimeRange.GetFromAsSecondsEpoch(), m.TimeRange.GetToAsSecondsEpoch()), nil + } + + // dont use to_timestamp in this macro for redshift compatibility #9566 + return fmt.Sprintf("%s BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", args[0], m.TimeRange.GetFromAsSecondsEpoch(), m.TimeRange.GetToAsSecondsEpoch()), nil + case "__timeFrom": return fmt.Sprintf("to_timestamp(%d)", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__timeTo": diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index f441690a429..b4ee043a87d 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -6,14 +6,27 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/tsdb" . "github.com/smartystreets/goconvey/convey" ) func TestMacroEngine(t *testing.T) { Convey("MacroEngine", t, func() { - engine := &PostgresMacroEngine{} - query := &tsdb.Query{} + engine := NewPostgresMacroEngine() + // datasource with no pg version specified + ds := &models.DataSource{Id: 1, Type: "postgres", JsonData: simplejson.New()} + // datasource with postgres 8.0 configured + ds_80 := &models.DataSource{Id: 2, Type: "postgres", JsonData: simplejson.New()} + ds_80.JsonData.Set("postgresVersion", 80000) + // datasource with postgres 8.1 configured + ds_81 := &models.DataSource{Id: 3, Type: "postgres", JsonData: simplejson.New()} + ds_81.JsonData.Set("postgresVersion", 80100) + + query := &tsdb.Query{RefId: "A", DataSource: ds} + query_80 := &tsdb.Query{RefId: "A", DataSource: ds_80} + query_81 := &tsdb.Query{RefId: "A", DataSource: ds_81} Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() { from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC) @@ -38,7 +51,21 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE extract(epoch from time_column) BETWEEN %d AND %d", from.Unix(), to.Unix())) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFilter function for postgres 8.0", func() { + sql, err := engine.Interpolate(query_80, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFilter function for postgres 8.1", func() { + sql, err := engine.Interpolate(query_81, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN to_timestamp(%d) AND to_timestamp(%d)", from.Unix(), to.Unix())) }) Convey("interpolate __timeFrom function", func() { @@ -102,7 +129,21 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE extract(epoch from time_column) BETWEEN %d AND %d", from.Unix(), to.Unix())) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFilter function for 8.0", func() { + sql, err := engine.Interpolate(query_80, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFilter function for 8.1", func() { + sql, err := engine.Interpolate(query_81, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN to_timestamp(%d) AND to_timestamp(%d)", from.Unix(), to.Unix())) }) Convey("interpolate __timeFrom function", func() { @@ -150,7 +191,21 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE extract(epoch from time_column) BETWEEN %d AND %d", from.Unix(), to.Unix())) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFilter function for 8.0", func() { + sql, err := engine.Interpolate(query_80, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFilter function for 8.1", func() { + sql, err := engine.Interpolate(query_81, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN to_timestamp(%d) AND to_timestamp(%d)", from.Unix(), to.Unix())) }) Convey("interpolate __timeFrom function", func() { From ee623e2091677efd68eaf22c1018f07538584b23 Mon Sep 17 00:00:00 2001 From: Matthew McGinn Date: Sun, 15 Apr 2018 13:44:17 -0400 Subject: [PATCH 110/319] Grafana-CLI: mention the plugins directory is not writable on failure --- pkg/cmd/grafana-cli/commands/install_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index f40bc9c081b..6f6849ccddf 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -33,7 +33,7 @@ func validateInput(c CommandLine, pluginFolder string) error { fileInfo, err := os.Stat(pluginsDir) if err != nil { if err = os.MkdirAll(pluginsDir, os.ModePerm); err != nil { - return errors.New(fmt.Sprintf("pluginsDir (%s) is not a directory", pluginsDir)) + return errors.New(fmt.Sprintf("pluginsDir (%s) is not a writable directory", pluginsDir)) } return nil } From 7534f0bff6e702970daa48695c9143d1124f6e5d Mon Sep 17 00:00:00 2001 From: Kim Christensen Date: Sun, 15 Apr 2018 21:37:34 +0200 Subject: [PATCH 111/319] Support deleting empty playlist --- pkg/api/playlist.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go index d2413dfbb4c..a90b6425cb6 100644 --- a/pkg/api/playlist.go +++ b/pkg/api/playlist.go @@ -33,7 +33,7 @@ func ValidateOrgPlaylist(c *m.ReqContext) { return } - if len(items) == 0 { + if len(items) == 0 && c.Context.Req.Method != "DELETE" { c.JsonApiErr(404, "Playlist is empty", itemsErr) return } From 6d3da9a73df9f3cc74648d2913555437d9bbea1a Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Sun, 15 Apr 2018 22:14:13 +0200 Subject: [PATCH 112/319] remove postgresversion and convert unix timestamp in go --- pkg/tsdb/postgres/macros.go | 10 +-- pkg/tsdb/postgres/macros_test.go | 63 ++----------------- .../app/plugins/datasource/postgres/module.ts | 5 -- .../datasource/postgres/partials/config.html | 16 ----- 4 files changed, 5 insertions(+), 89 deletions(-) diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index b9a7580b3ce..a13912f0e1d 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -83,15 +83,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, return "", fmt.Errorf("missing time column argument for macro %v", name) } - pg_version := m.Query.DataSource.JsonData.Get("postgresVersion").MustInt(0) - if pg_version >= 80100 { - // postgres has to_timestamp(double) starting with 8.1 - return fmt.Sprintf("%s BETWEEN to_timestamp(%d) AND to_timestamp(%d)", args[0], m.TimeRange.GetFromAsSecondsEpoch(), m.TimeRange.GetToAsSecondsEpoch()), nil - } - - // dont use to_timestamp in this macro for redshift compatibility #9566 - return fmt.Sprintf("%s BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", args[0], m.TimeRange.GetFromAsSecondsEpoch(), m.TimeRange.GetToAsSecondsEpoch()), nil - + return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.TimeRange.MustGetFrom().UTC().Format(time.RFC3339), m.TimeRange.MustGetTo().UTC().Format(time.RFC3339)), nil case "__timeFrom": return fmt.Sprintf("to_timestamp(%d)", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__timeTo": diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index b4ee043a87d..d1bcaff796d 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -6,8 +6,6 @@ import ( "testing" "time" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/tsdb" . "github.com/smartystreets/goconvey/convey" ) @@ -15,18 +13,7 @@ import ( func TestMacroEngine(t *testing.T) { Convey("MacroEngine", t, func() { engine := NewPostgresMacroEngine() - // datasource with no pg version specified - ds := &models.DataSource{Id: 1, Type: "postgres", JsonData: simplejson.New()} - // datasource with postgres 8.0 configured - ds_80 := &models.DataSource{Id: 2, Type: "postgres", JsonData: simplejson.New()} - ds_80.JsonData.Set("postgresVersion", 80000) - // datasource with postgres 8.1 configured - ds_81 := &models.DataSource{Id: 3, Type: "postgres", JsonData: simplejson.New()} - ds_81.JsonData.Set("postgresVersion", 80100) - - query := &tsdb.Query{RefId: "A", DataSource: ds} - query_80 := &tsdb.Query{RefId: "A", DataSource: ds_80} - query_81 := &tsdb.Query{RefId: "A", DataSource: ds_81} + query := &tsdb.Query{} Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() { from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC) @@ -51,21 +38,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) - }) - - Convey("interpolate __timeFilter function for postgres 8.0", func() { - sql, err := engine.Interpolate(query_80, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) - }) - - Convey("interpolate __timeFilter function for postgres 8.1", func() { - sql, err := engine.Interpolate(query_81, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN to_timestamp(%d) AND to_timestamp(%d)", from.Unix(), to.Unix())) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) }) Convey("interpolate __timeFrom function", func() { @@ -129,21 +102,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) - }) - - Convey("interpolate __timeFilter function for 8.0", func() { - sql, err := engine.Interpolate(query_80, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) - }) - - Convey("interpolate __timeFilter function for 8.1", func() { - sql, err := engine.Interpolate(query_81, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN to_timestamp(%d) AND to_timestamp(%d)", from.Unix(), to.Unix())) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) }) Convey("interpolate __timeFrom function", func() { @@ -191,21 +150,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) - }) - - Convey("interpolate __timeFilter function for 8.0", func() { - sql, err := engine.Interpolate(query_80, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN 'epoch'::timestamptz + %d * '1s'::interval AND 'epoch'::timestamptz + %d * '1s'::interval", from.Unix(), to.Unix())) - }) - - Convey("interpolate __timeFilter function for 8.1", func() { - sql, err := engine.Interpolate(query_81, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN to_timestamp(%d) AND to_timestamp(%d)", from.Unix(), to.Unix())) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) }) Convey("interpolate __timeFrom function", func() { diff --git a/public/app/plugins/datasource/postgres/module.ts b/public/app/plugins/datasource/postgres/module.ts index 766e7b2ec37..9deb7909167 100644 --- a/public/app/plugins/datasource/postgres/module.ts +++ b/public/app/plugins/datasource/postgres/module.ts @@ -11,11 +11,6 @@ class PostgresConfigCtrl { this.current.jsonData.sslmode = this.current.jsonData.sslmode || 'verify-full'; } - /* the values are chosen to be equivalent to `select current_setting('server_version_num');` */ - postgresVersions = [ - { name: '8.0+', value: 80000 }, - { name: '8.1+', value: 80100 }, - ]; } const defaultQuery = `SELECT diff --git a/public/app/plugins/datasource/postgres/partials/config.html b/public/app/plugins/datasource/postgres/partials/config.html index 51fd66d7ed6..77f0dcfa4a5 100644 --- a/public/app/plugins/datasource/postgres/partials/config.html +++ b/public/app/plugins/datasource/postgres/partials/config.html @@ -38,22 +38,6 @@
    -

    PostgreSQL details

    - -
    -
    - - Version - - This option controls what functions are used when expanding grafana macros. - - - - - -
    -
    -
    User Permission
    From 738fb29134edc60187d9c27e969a117fbef650fb Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 16 Apr 2018 09:37:55 +0200 Subject: [PATCH 113/319] changelog: adds note about closing #11228 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8449e4e7a20..7d27f15e5e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ * **Variables**: Case-insensitive sorting for template values [#11128](https://github.com/grafana/grafana/issues/11128) thx [@cross](https://github.com/cross) * **Annotations (native)**: Change default limit from 10 to 100 when querying api [#11569](https://github.com/grafana/grafana/issues/11569), thx [@flopp999](https://github.com/flopp999) * **MySQL/Postgres/MSSQL**: PostgreSQL datasource generates invalid query with dates before 1970 [#11530](https://github.com/grafana/grafana/issues/11530) thx [@ryantxu](https://github.com/ryantxu) +* **Kiosk**: Adds url parameter for starting a dashboard in inactive mode [#11228](https://github.com/grafana/grafana/issues/11228), thx [@towolf](https://github.com/towolf) # 5.0.4 (2018-03-28) From 6b4ef7f5981811651e010bce19346b2500f78a05 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 16 Apr 2018 10:42:39 +0200 Subject: [PATCH 114/319] wip: writing tests for permission sorting --- .../PermissionsStore/PermissionsStore.jest.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts index c3bc6016e50..2bb2f1b6a0c 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts @@ -17,6 +17,22 @@ describe('PermissionsStore', () => { teamId: 1, teamName: 'MyTestTeam', }, + { + id: 5, + dashboardId: 10, + permission: 1, + permissionName: 'View', + userId: 1, + userName: 'MyTestUser', + }, + { + id: 6, + dashboardId: 10, + permission: 1, + permissionName: 'Edit', + teamId: 2, + teamName: 'MyTestTeam2', + }, ]) ); @@ -32,7 +48,10 @@ describe('PermissionsStore', () => { } ); + console.log(store); + await store.load(1, false, false); + console.log(store); }); it('should save update on permission change', async () => { From 6c6b74fc390fa6281b4caf85059312f8629f9a72 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 16 Apr 2018 09:57:41 +0200 Subject: [PATCH 115/319] removes codecov from front-end tests --- Gruntfile.js | 1 - codecov.yml | 13 ------------- package.json | 1 - scripts/circle-test-frontend.sh | 9 ++------- scripts/grunt/options/exec.js | 7 +------ 5 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 codecov.yml diff --git a/Gruntfile.js b/Gruntfile.js index a0607ef49dc..03f70565b57 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,7 +22,6 @@ module.exports = function (grunt) { } } - config.coverage = grunt.option('coverage'); config.phjs = grunt.option('phjsToRelease'); config.pkg.version = grunt.option('pkgVer') || config.pkg.version; diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 82a86e0232b..00000000000 --- a/codecov.yml +++ /dev/null @@ -1,13 +0,0 @@ -coverage: - precision: 2 - round: down - range: "50...100" - - status: - project: yes - patch: yes - changes: no - -comment: - layout: "diff" - behavior: "once" diff --git a/package.json b/package.json index ce861a25f7b..b74d23f33b2 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "watch": "webpack --progress --colors --watch --config scripts/webpack/webpack.dev.js", "build": "grunt build", "test": "grunt test", - "test:coverage": "grunt test --coverage=true", "lint": "tslint -c tslint.json --project tsconfig.json --type-check", "karma": "grunt karma:dev", "jest": "jest --notify --watch", diff --git a/scripts/circle-test-frontend.sh b/scripts/circle-test-frontend.sh index 9857e00f70d..325c24ae7a9 100755 --- a/scripts/circle-test-frontend.sh +++ b/scripts/circle-test-frontend.sh @@ -10,10 +10,5 @@ function exit_if_fail { fi } -exit_if_fail npm run test:coverage -exit_if_fail npm run build - -# publish code coverage -echo "Publishing javascript code coverage" -bash <(curl -s https://codecov.io/bash) -cF javascript -rm -rf coverage +exit_if_fail npm run test +exit_if_fail npm run build \ No newline at end of file diff --git a/scripts/grunt/options/exec.js b/scripts/grunt/options/exec.js index e22d060ea04..be163581bf6 100644 --- a/scripts/grunt/options/exec.js +++ b/scripts/grunt/options/exec.js @@ -1,14 +1,9 @@ module.exports = function(config, grunt) { 'use strict'; - var coverage = ''; - if (config.coverage) { - coverage = '--coverage --maxWorkers 2'; - } - return { tslint: 'node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json', - jest: 'node ./node_modules/jest-cli/bin/jest.js ' + coverage, + jest: 'node ./node_modules/jest-cli/bin/jest.js --maxWorkers 2', webpack: 'node ./node_modules/webpack/bin/webpack.js --config scripts/webpack/webpack.prod.js', }; }; From aff336d4e7c104391033184d6117a2d9f77e6e62 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Mon, 16 Apr 2018 11:44:50 +0200 Subject: [PATCH 116/319] add codespell to circleci --- .circleci/config.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index cfa8b762e49..fc04e69af6e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,18 @@ version: 2 jobs: + codespell: + docker: + - image: circleci/python + steps: + - checkout + - run: + name: install codespell + command: 'sudo pip install codespell' + - run: + name: check documentation spelling errors + command: 'codespell -x docs/sources/project/building_from_source.md docs/' + test-frontend: docker: - image: circleci/node:6.11.4 @@ -103,6 +115,10 @@ workflows: version: 2 test-and-build: jobs: + - codespell: + filters: + tags: + only: /.*/ - build: filters: tags: From 9337972a0fdc11c2ed1d1e0cc89178f0c8182c76 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 16 Apr 2018 11:06:23 +0200 Subject: [PATCH 117/319] sqlds: fix text in comments for tests --- pkg/tsdb/mssql/mssql_test.go | 4 ++-- pkg/tsdb/mysql/mysql_test.go | 5 +++-- pkg/tsdb/postgres/postgres_test.go | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index dc527d09bd9..599f4869f6a 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -16,10 +16,10 @@ import ( ) // To run this test, remove the Skip from SkipConvey -// and set up a MSSQL db named grafanatest and a user/password grafana/Password! +// The tests require a MSSQL db named grafanatest and a user/password grafana/Password! // Use the docker/blocks/mssql_tests/docker-compose.yaml to spin up a // preconfigured MSSQL server suitable for running these tests. -// Thers's also a dashboard.json in same directory that you can import to Grafana +// There is also a dashboard.json in same directory that you can import to Grafana // once you've created a datasource for the test server/database. // If needed, change the variable below to the IP address of the database. var serverIP string = "localhost" diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 827ebfa9555..74cedea803a 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -17,10 +17,11 @@ import ( ) // To run this test, set runMySqlTests=true -// and set up a MySQL db named grafana_ds_tests and a user/password grafana/password +// Or from the commandline: GRAFANA_TEST_DB=mysql go test -v ./pkg/tsdb/mysql +// The tests require a MySQL db named grafana_ds_tests and a user/password grafana/password // Use the docker/blocks/mysql_tests/docker-compose.yaml to spin up a // preconfigured MySQL server suitable for running these tests. -// Thers's also a dashboard.json in same directory that you can import to Grafana +// There is also a dashboard.json in same directory that you can import to Grafana // once you've created a datasource for the test server/database. func TestMySQL(t *testing.T) { // change to true to run the MySQL tests diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index d35ba2b3209..d18251bac7d 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -17,11 +17,12 @@ import ( . "github.com/smartystreets/goconvey/convey" ) -// To run this test, set runMySqlTests=true -// and set up a PostgreSQL db named grafanadstest and a user/password grafanatest/grafanatest! +// To run this test, set runPostgresTests=true +// Or from the commandline: GRAFANA_TEST_DB=postgres go test -v ./pkg/tsdb/postgres +// The tests require a PostgreSQL db named grafanadstest and a user/password grafanatest/grafanatest! // Use the docker/blocks/postgres_tests/docker-compose.yaml to spin up a // preconfigured Postgres server suitable for running these tests. -// Thers's also a dashboard.json in same directory that you can import to Grafana +// There is also a dashboard.json in same directory that you can import to Grafana // once you've created a datasource for the test server/database. func TestPostgres(t *testing.T) { // change to true to run the MySQL tests From 645658d79765a9d9945ca3a3b3d6f46472b5598e Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 16 Apr 2018 13:08:00 +0200 Subject: [PATCH 118/319] changlelog: notes about closing issues/pr's #11053, #11252, #10836, #11185, #11168, #11332, #11391, #11073, #9342, #11001, #11183, #11211, #11384, #11095, #10792, #11138, #11516 [skip ci] --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d27f15e5e3..90b92efc979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * **Alerting**: Add support for retries on alert queries [#5855](https://github.com/grafana/grafana/issues/5855), thx [@Thib17](https://github.com/Thib17) * **Table**: Table plugin value mappings [#7119](https://github.com/grafana/grafana/issues/7119), thx [infernix](https://github.com/infernix) * **IE11**: IE 11 compatibility [#11165](https://github.com/grafana/grafana/issues/11165) +* **Scrolling**: Better scrolling experience [#11053](https://github.com/grafana/grafana/issues/11053), [#11252](https://github.com/grafana/grafana/issues/11252), [#10836](https://github.com/grafana/grafana/issues/10836), [#11185](https://github.com/grafana/grafana/issues/11185), [#11168](https://github.com/grafana/grafana/issues/11168) ### Minor * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes) @@ -32,6 +33,21 @@ * **Annotations (native)**: Change default limit from 10 to 100 when querying api [#11569](https://github.com/grafana/grafana/issues/11569), thx [@flopp999](https://github.com/flopp999) * **MySQL/Postgres/MSSQL**: PostgreSQL datasource generates invalid query with dates before 1970 [#11530](https://github.com/grafana/grafana/issues/11530) thx [@ryantxu](https://github.com/ryantxu) * **Kiosk**: Adds url parameter for starting a dashboard in inactive mode [#11228](https://github.com/grafana/grafana/issues/11228), thx [@towolf](https://github.com/towolf) +* **Dashboard**: Enable closing timepicker using escape key [#11332](https://github.com/grafana/grafana/issues/11332) +* **Datasources**: Rename direct access mode in the data source settings [#11391](https://github.com/grafana/grafana/issues/11391) +* **Search**: Display dashboards in folder indented [#11073](https://github.com/grafana/grafana/issues/11073) +* **Units**: Use B/s instead Bps for Bytes per second [#9342](https://github.com/grafana/grafana/pull/9342), thx [@mayli](https://github.com/mayli) +* **Units**: Radiation units [#11001](https://github.com/grafana/grafana/issues/11001), thx [@victorclaessen](https://github.com/victorclaessen) +* **Units**: Timeticks unit [#11183](https://github.com/grafana/grafana/pull/11183), thx [@jtyr](https://github.com/jtyr) +* **Units**: Concentration units and "Normal cubic metre" [#11211](https://github.com/grafana/grafana/issues/11211), thx [@flopp999](https://github.com/flopp999) +* **Units**: New currency - Czech koruna [#11384](https://github.com/grafana/grafana/pull/11384), thx [@Rohlik](https://github.com/Rohlik) +* **Avatar**: Fix DISABLE_GRAVATAR option [#11095](https://github.com/grafana/grafana/issues/11095) +* **Heatmap**: Disable log scale when using time time series buckets [#10792](https://github.com/grafana/grafana/issues/10792) +* **Provisioning**: Remove `id` from json when provisioning dashboards, [#11138](https://github.com/grafana/grafana/issues/11138) +* **Prometheus**: tooltip for legend format not showing properly [#11516](https://github.com/grafana/grafana/issues/11516), thx [@svenklemm](https://github.com/svenklemm) + +### Tech +* Migrated JavaScript files to TypeScript # 5.0.4 (2018-03-28) From 712212d6aa36bab1881ba8697ffce5bd0ad6fb7c Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 16 Apr 2018 13:37:05 +0200 Subject: [PATCH 119/319] Show Grafana version and build in Help menu * establishes Help as the single place to look for the Grafana version * version is passed as menu sub-title to side menu * added rendering of sub-title, plus styles * sub-title was used by profile menu (its value is the login string), but was not shown; now showing this value on condition that login name is different from user name --- pkg/api/index.go | 8 +++++++- public/app/core/components/sidemenu/sidemenu.html | 5 ++++- public/sass/components/_sidemenu.scss | 8 ++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index a1d21d1c686..0f8b5a6fc78 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -118,9 +118,14 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { }) if c.IsSignedIn { + // Only set login if it's different from the name + var login string + if c.SignedInUser.Login != c.SignedInUser.NameOrFallback() { + login = c.SignedInUser.Login + } profileNode := &dtos.NavLink{ Text: c.SignedInUser.NameOrFallback(), - SubTitle: c.SignedInUser.Login, + SubTitle: login, Id: "profile", Img: data.User.GravatarUrl, Url: setting.AppSubUrl + "/profile", @@ -284,6 +289,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { data.NavTree = append(data.NavTree, &dtos.NavLink{ Text: "Help", + SubTitle: fmt.Sprintf(`Grafana version: %s+%s`, setting.BuildVersion, setting.BuildCommit), Id: "help", Url: "#", Icon: "gicon gicon-question", diff --git a/public/app/core/components/sidemenu/sidemenu.html b/public/app/core/components/sidemenu/sidemenu.html index 1b301363e62..a9ebbe2681d 100644 --- a/public/app/core/components/sidemenu/sidemenu.html +++ b/public/app/core/components/sidemenu/sidemenu.html @@ -70,9 +70,12 @@ {{::child.text}} +
  • + {{::item.subTitle}} +
  • {{::item.text}}
  • -
    +
    \ No newline at end of file diff --git a/public/sass/components/_sidemenu.scss b/public/sass/components/_sidemenu.scss index d1372484074..dde01c2ba9c 100644 --- a/public/sass/components/_sidemenu.scss +++ b/public/sass/components/_sidemenu.scss @@ -149,6 +149,14 @@ color: #ebedf2; } +.side-menu-subtitle { + padding: 0.5rem 0.5rem 0.5rem 1rem; + font-size: $font-size-sm; + color: $text-color-weak; + border-top: 1px solid $dropdownDividerBottom; + margin-top: 0.25rem; +} + li.sidemenu-org-switcher { border-bottom: 1px solid $dropdownDividerBottom; } From ce3dcadfeffbd12b02b014ebf1314e033720fe36 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 16 Apr 2018 14:30:50 +0200 Subject: [PATCH 120/319] addeds test for sort order --- .../PermissionsStore/PermissionsStore.jest.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts index 2bb2f1b6a0c..0bfcadb4874 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts @@ -11,27 +11,27 @@ describe('PermissionsStore', () => { { id: 3, dashboardId: 1, role: 'Editor', permission: 1, permissionName: 'Edit' }, { id: 4, - dashboardId: 10, + dashboardId: 1, permission: 1, permissionName: 'View', teamId: 1, - teamName: 'MyTestTeam', + team: 'MyTestTeam', }, { id: 5, - dashboardId: 10, + dashboardId: 1, permission: 1, permissionName: 'View', userId: 1, - userName: 'MyTestUser', + userLogin: 'MyTestUser', }, { id: 6, - dashboardId: 10, + dashboardId: 1, permission: 1, permissionName: 'Edit', teamId: 2, - teamName: 'MyTestTeam2', + team: 'MyTestTeam2', }, ]) ); @@ -48,15 +48,12 @@ describe('PermissionsStore', () => { } ); - console.log(store); - await store.load(1, false, false); - console.log(store); }); it('should save update on permission change', async () => { expect(store.items[0].permission).toBe(1); - expect(store.items[0].permissionName).toBe('View'); + expect(store.items[0].permissionName).toBe('Edit'); await store.updatePermissionOnIndex(0, 2, 'Edit'); @@ -67,15 +64,20 @@ describe('PermissionsStore', () => { }); it('should save removed permissions automatically', async () => { - expect(store.items.length).toBe(3); + expect(store.items.length).toBe(5); await store.removeStoreItem(2); - expect(store.items.length).toBe(2); + expect(store.items.length).toBe(4); expect(backendSrv.post.mock.calls.length).toBe(1); expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/permissions'); }); + it('should be sorted by sort rank and alphabetically', async () => { + expect(store.items[3].name).toBe('MyTestTeam2'); + expect(store.items[4].name).toBe('MyTestUser'); + }); + describe('when one inherited and one not inherited team permission are added', () => { beforeEach(async () => { const overridingItemForChildDashboard = { @@ -92,7 +94,7 @@ describe('PermissionsStore', () => { }); it('should add new overriding permission', () => { - expect(store.items.length).toBe(4); + expect(store.items.length).toBe(6); }); }); }); From 5200196092091972983c0e3dbed2c5c263e98d51 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 16 Apr 2018 14:49:47 +0200 Subject: [PATCH 121/319] added fix for test --- public/app/stores/PermissionsStore/PermissionsStore.jest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts index 0bfcadb4874..24f1705367a 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.jest.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.jest.ts @@ -11,7 +11,7 @@ describe('PermissionsStore', () => { { id: 3, dashboardId: 1, role: 'Editor', permission: 1, permissionName: 'Edit' }, { id: 4, - dashboardId: 1, + dashboardId: 10, permission: 1, permissionName: 'View', teamId: 1, @@ -53,7 +53,7 @@ describe('PermissionsStore', () => { it('should save update on permission change', async () => { expect(store.items[0].permission).toBe(1); - expect(store.items[0].permissionName).toBe('Edit'); + expect(store.items[0].permissionName).toBe('View'); await store.updatePermissionOnIndex(0, 2, 'Edit'); From abed9c055f5d5154d6bc43b0b11069d3de524c56 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Thu, 22 Mar 2018 17:33:53 +0100 Subject: [PATCH 122/319] docs: new docker image in Grafana 5.1.0. --- docs/sources/installation/docker.md | 124 +++++++++++++++++++++------- 1 file changed, 94 insertions(+), 30 deletions(-) diff --git a/docs/sources/installation/docker.md b/docs/sources/installation/docker.md index f246bd55d33..d6f3ae16466 100644 --- a/docs/sources/installation/docker.md +++ b/docs/sources/installation/docker.md @@ -18,28 +18,6 @@ Grafana is very easy to install and run using the official docker container. $ docker run -d -p 3000:3000 grafana/grafana ``` -All Grafana configuration settings can be defined using environment -variables, this is especially useful when using the above container. - -## Docker volumes & ENV config - -The Docker container exposes two volumes, the sqlite3 database in the -folder `/var/lib/grafana` and configuration files is in `/etc/grafana/` -folder. You can map these volumes to host folders when you start the -container: - -```bash -$ docker run -d -p 3000:3000 \ - -v /var/lib/grafana:/var/lib/grafana \ - -e "GF_SECURITY_ADMIN_PASSWORD=secret" \ - grafana/grafana -``` - -In the above example I map the data folder and sets a configuration option via -an `ENV` instruction. - -See the [docker volumes documentation](https://docs.docker.com/engine/admin/volumes/volumes/) if you want to create a volume to use with the Grafana docker image instead of a bind mount (binding to a directory in the host system). - ## Configuration All options defined in conf/grafana.ini can be overridden using environment @@ -56,15 +34,24 @@ $ docker run \ grafana/grafana ``` -You can use your own grafana.ini file by using environment variable `GF_PATHS_CONFIG`. - The back-end web server has a number of configuration options. Go to the [Configuration]({{< relref "configuration.md" >}}) page for details on all those options. +## Running a Specific Version of Grafana + +```bash +# specify right tag, e.g. 5.1.0 - see Docker Hub for available tags +$ docker run \ + -d \ + -p 3000:3000 \ + --name grafana \ + grafana/grafana:5.1.0 +``` + ## Installing Plugins for Grafana -Pass the plugins you want installed to docker with the `GF_INSTALL_PLUGINS` environment variable as a comma separated list. This will pass each plugin name to `grafana-cli plugins install ${plugin}`. +Pass the plugins you want installed to docker with the `GF_INSTALL_PLUGINS` environment variable as a comma separated list. This will pass each plugin name to `grafana-cli plugins install ${plugin}` and install them when Grafana starts. ```bash docker run \ @@ -75,15 +62,22 @@ docker run \ grafana/grafana ``` -## Running a Specific Version of Grafana +## Building a custom Grafana image with pre-installed plugins +In the [grafana-docker](https://github.com/grafana/grafana-docker/) there is a folder called `custom/` which includes a `Dockerfile` that can be used to build a custom Grafana image. It accepts `GRAFANA_VERSION` and `GF_INSTALL_PLUGINS` as build arguments. + +Example of how to build and run: ```bash -# specify right tag, e.g. 4.5.2 - see Docker Hub for available tags -$ docker run \ +cd custom +docker build -t grafana:latest-with-plugins \ + --build-arg "GRAFANA_VERSION=latest" \ + --build-arg "GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource" . + +docker run \ -d \ -p 3000:3000 \ - --name grafana \ - grafana/grafana:5.0.2 + --name=grafana \ + grafana:latest-with-plugins ``` ## Configuring AWS Credentials for CloudWatch Support @@ -108,3 +102,73 @@ Supported variables: - `GF_AWS_${profile}_ACCESS_KEY_ID`: AWS access key ID (required). - `GF_AWS_${profile}_SECRET_ACCESS_KEY`: AWS secret access key (required). - `GF_AWS_${profile}_REGION`: AWS region (optional). + +## Grafana container with persistent storage (recommended) + +```bash +# create a persistent volume for your data in /var/lib/grafana (database and plugins) +docker volume create grafana-storage + +# start grafana +docker run \ + -d \ + -p 3000:3000 \ + --name=grafana \ + -v grafana-storage:/var/lib/grafana \ + grafana/grafana +``` + +## Grafana container using bind mounts + +You may want to run Grafana in Docker but use folders on your host for the database or configuration. When doing so it becomes important to start the container with a user that is able to access and write to the folder you map into the container. + +```bash +mkdir data # creates a folder for your data +ID=$(id -u) # saves your user id in the ID variable + +# starts grafana with your user id and using the data folder +docker run -d --user $ID --volume "$PWD/data:/var/lib/grafana" -p 3000:3000 grafana/grafana:5.1.0 +``` + +## Migration from a previous version of the docker container to 5.1 or later + +In 5.1 we switched the id of the grafana user. Unfortunately this means that files created prior to 5.1 won't have the correct permissions for later versions. We made this change so that it would be easier for you to control what user Grafana is executed as (see examples below). + +Version | User | User ID +--------|---------|--------- +< 5.1 | grafana | 104 +>= 5.1 | grafana | 472 + +There are two possible solutions to this problem. Either you start the new container as the root user and change ownership from `104` to `472` or you start the upgraded container as user `104`. + +### Running docker as a different user + +```bash +docker run --user 104 --volume "" grafana/grafana:5.1.0 +``` + +#### docker-compose.yml with custom user +```yaml +version: "2" + +services: + grafana: + image: grafana/grafana:5.1.0 + ports: + - 3000:3000 + user: "104" +``` + +### Modifying permissions + +The commands below will run bash inside the Grafana container with your volume mapped in. This makes it possible to modify the file ownership to match the new container. Always be careful when modifying permissions. + +```bash +$ docker run -ti --user root --volume "" --entrypoint bash grafana/grafana:5.1.0 + +# in the container you just started: +chown -R root:root /etc/grafana && \ + chmod -R a+r /etc/grafana && \ + chown -R grafana:grafana /var/lib/grafana && \ + chown -R grafana:grafana /usr/share/grafana +``` From 8d963e27332a6a80fba7f42a3c0726a40f1608f3 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 16 Apr 2018 15:45:55 +0200 Subject: [PATCH 123/319] changelog: improved docker image --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90b92efc979..09336084570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * **Table**: Table plugin value mappings [#7119](https://github.com/grafana/grafana/issues/7119), thx [infernix](https://github.com/infernix) * **IE11**: IE 11 compatibility [#11165](https://github.com/grafana/grafana/issues/11165) * **Scrolling**: Better scrolling experience [#11053](https://github.com/grafana/grafana/issues/11053), [#11252](https://github.com/grafana/grafana/issues/11252), [#10836](https://github.com/grafana/grafana/issues/10836), [#11185](https://github.com/grafana/grafana/issues/11185), [#11168](https://github.com/grafana/grafana/issues/11168) +* **Docker**: Improved docker image (breaking changes regarding file ownership) [grafana-docker #141](https://github.com/grafana/grafana-docker/issues/141), thx [@Spindel](https://github.com/Spindel), [@ChristianKniep](https://github.com/ChristianKniep), [@brancz](https://github.com/brancz) and [@jangaraj](https://github.com/jangaraj) ### Minor * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes) From 90ed046ce35a75b020632b6d5704c0fa475e3dec Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 16 Apr 2018 16:03:50 +0200 Subject: [PATCH 124/319] docs: elasticsearch and influxdb docs for group by time interval option (#11609) --- .../features/datasources/elasticsearch.md | 16 ++++++++++++++++ docs/sources/features/datasources/influxdb.md | 16 ++++++++++++++++ .../elasticsearch/partials/config.html | 2 +- .../plugins/datasource/influxdb/query_help.md | 5 ++--- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/sources/features/datasources/elasticsearch.md b/docs/sources/features/datasources/elasticsearch.md index db17aafd271..7e6e281df7e 100644 --- a/docs/sources/features/datasources/elasticsearch.md +++ b/docs/sources/features/datasources/elasticsearch.md @@ -55,6 +55,22 @@ a time pattern for the index name or a wildcard. Be sure to specify your Elasticsearch version in the version selection dropdown. This is very important as there are differences how queries are composed. Currently only 2.x and 5.x are supported. +### Min time interval +A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example `1m` if your data is written every minute. +This option can also be overridden/configured in a dashboard panel under data source options. It's important to note that this value **needs** to be formated as a +number followed by a valid time identifier, e.g. `1m` (1 minute) or `30s` (30 seconds). The following time identifiers are supported: + +Identifier | Description +------------ | ------------- +`y` | year +`M` | month +`w` | week +`d` | day +`h` | hour +`m` | minute +`s` | second +`ms` | millisecond + ## Metric Query editor ![](/img/docs/elasticsearch/query_editor.png) diff --git a/docs/sources/features/datasources/influxdb.md b/docs/sources/features/datasources/influxdb.md index b49e0f9dfc6..fccdd3cc35e 100644 --- a/docs/sources/features/datasources/influxdb.md +++ b/docs/sources/features/datasources/influxdb.md @@ -39,6 +39,22 @@ Proxy access means that the Grafana backend will proxy all requests from the bro `grafana-server`. This means that the URL you specify needs to be accessible from the server you are running Grafana on. Proxy access mode is also more secure as the username & password will never reach the browser. +### Min time interval +A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example `1m` if your data is written every minute. +This option can also be overridden/configured in a dashboard panel under data source options. It's important to note that this value **needs** to be formated as a +number followed by a valid time identifier, e.g. `1m` (1 minute) or `30s` (30 seconds). The following time identifiers are supported: + +Identifier | Description +------------ | ------------- +`y` | year +`M` | month +`w` | week +`d` | day +`h` | hour +`m` | minute +`s` | second +`ms` | millisecond + ## Query Editor {{< docs-imagebox img="/img/docs/v45/influxdb_query_still.png" class="docs-image--no-shadow" animated-gif="/img/docs/v45/influxdb_query.gif" >}} diff --git a/public/app/plugins/datasource/elasticsearch/partials/config.html b/public/app/plugins/datasource/elasticsearch/partials/config.html index da23e9ddab1..def59518624 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/config.html +++ b/public/app/plugins/datasource/elasticsearch/partials/config.html @@ -35,7 +35,7 @@
    - Min interval + Min time interval A lower limit for the auto group by time interval. Recommended to be set to write frequency, diff --git a/public/app/plugins/datasource/influxdb/query_help.md b/public/app/plugins/datasource/influxdb/query_help.md index 0d4fd941ca5..4930ccbc83f 100644 --- a/public/app/plugins/datasource/influxdb/query_help.md +++ b/public/app/plugins/datasource/influxdb/query_help.md @@ -10,7 +10,7 @@ - When stacking is enabled it is important that points align - If there are missing points for one series it can cause gaps or missing bars - You must use fill(0), and select a group by time low limit -- Use the group by time option below your queries and specify for example >10s if your metrics are written every 10 seconds +- Use the group by time option below your queries and specify for example 10s if your metrics are written every 10 seconds - This will insert zeros for series that are missing measurements and will make stacking work properly #### Group by time @@ -18,8 +18,7 @@ - Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph - If you use fill(0) or fill(null) set a low limit for the auto group by time interval - The low limit can only be set in the group by time option below your queries -- You set a low limit by adding a greater sign before the interval -- Example: >60s if you write metrics to InfluxDB every 60 seconds +- Example: 60s if you write metrics to InfluxDB every 60 seconds #### Documentation links: From 5a29c1728225643b3aa9018fd319214889dc8edf Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 16 Apr 2018 16:25:28 +0200 Subject: [PATCH 125/319] moved version in help menu to top --- pkg/api/index.go | 2 +- public/app/core/components/sidemenu/sidemenu.html | 6 +++--- public/sass/components/_sidemenu.scss | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 0f8b5a6fc78..94094706f68 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -289,7 +289,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { data.NavTree = append(data.NavTree, &dtos.NavLink{ Text: "Help", - SubTitle: fmt.Sprintf(`Grafana version: %s+%s`, setting.BuildVersion, setting.BuildCommit), + SubTitle: fmt.Sprintf(`Grafana v%s (%s)`, setting.BuildVersion, setting.BuildCommit), Id: "help", Url: "#", Icon: "gicon gicon-question", diff --git a/public/app/core/components/sidemenu/sidemenu.html b/public/app/core/components/sidemenu/sidemenu.html index a9ebbe2681d..9de61345cd0 100644 --- a/public/app/core/components/sidemenu/sidemenu.html +++ b/public/app/core/components/sidemenu/sidemenu.html @@ -54,6 +54,9 @@
    From 76bd2aea44da99550ca3713442fc65dfe7a9d135 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 24 Apr 2018 19:50:14 +0200 Subject: [PATCH 260/319] sql datasource: extract common logic for converting value column to float --- pkg/tsdb/sql_engine.go | 109 ++++++++++++++++++++++++++++++++++++ pkg/tsdb/sql_engine_test.go | 101 ++++++++++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 3 deletions(-) diff --git a/pkg/tsdb/sql_engine.go b/pkg/tsdb/sql_engine.go index 56ed2cd3cb6..274e5b05dc1 100644 --- a/pkg/tsdb/sql_engine.go +++ b/pkg/tsdb/sql_engine.go @@ -2,9 +2,12 @@ package tsdb import ( "context" + "fmt" "sync" "time" + "github.com/grafana/grafana/pkg/components/null" + "github.com/go-xorm/core" "github.com/go-xorm/xorm" "github.com/grafana/grafana/pkg/components/simplejson" @@ -185,3 +188,109 @@ func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) { } } } + +// ConvertSqlValueColumnToFloat converts timeseries value column to float. +func ConvertSqlValueColumnToFloat(columnName string, columnValue interface{}) (null.Float, error) { + var value null.Float + + switch typedValue := columnValue.(type) { + case int: + value = null.FloatFrom(float64(typedValue)) + case *int: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case int64: + value = null.FloatFrom(float64(typedValue)) + case *int64: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case int32: + value = null.FloatFrom(float64(typedValue)) + case *int32: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case int16: + value = null.FloatFrom(float64(typedValue)) + case *int16: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case int8: + value = null.FloatFrom(float64(typedValue)) + case *int8: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case uint: + value = null.FloatFrom(float64(typedValue)) + case *uint: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case uint64: + value = null.FloatFrom(float64(typedValue)) + case *uint64: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case uint32: + value = null.FloatFrom(float64(typedValue)) + case *uint32: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case uint16: + value = null.FloatFrom(float64(typedValue)) + case *uint16: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case uint8: + value = null.FloatFrom(float64(typedValue)) + case *uint8: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case float64: + value = null.FloatFrom(typedValue) + case *float64: + value = null.FloatFromPtr(typedValue) + case float32: + value = null.FloatFrom(float64(typedValue)) + case *float32: + if typedValue == nil { + value.Valid = false + } else { + value = null.FloatFrom(float64(*typedValue)) + } + case nil: + value.Valid = false + default: + return null.NewFloat(0, false), fmt.Errorf("Value column must have numeric datatype, column: %s type: %T value: %v", columnName, typedValue, typedValue) + } + + return value, nil +} diff --git a/pkg/tsdb/sql_engine_test.go b/pkg/tsdb/sql_engine_test.go index 4c6951a0196..ce1fb45de21 100644 --- a/pkg/tsdb/sql_engine_test.go +++ b/pkg/tsdb/sql_engine_test.go @@ -1,10 +1,11 @@ package tsdb import ( - "fmt" "testing" "time" + "github.com/grafana/grafana/pkg/components/null" + . "github.com/smartystreets/goconvey/convey" ) @@ -156,8 +157,6 @@ func TestSqlEngine(t *testing.T) { So(fixtures[1].(float64), ShouldEqual, tMilliseconds) So(fixtures[2].(float64), ShouldEqual, tMilliseconds) So(fixtures[3].(float64), ShouldEqual, tMilliseconds) - fmt.Println(fixtures[4].(float64)) - fmt.Println(tMilliseconds) So(fixtures[4].(float64), ShouldEqual, tMilliseconds) So(fixtures[5].(float64), ShouldEqual, tMilliseconds) So(fixtures[6], ShouldBeNil) @@ -183,5 +182,101 @@ func TestSqlEngine(t *testing.T) { So(fixtures[2], ShouldBeNil) }) }) + + Convey("Given row with value columns", func() { + intValue := 1 + int64Value := int64(1) + int32Value := int32(1) + int16Value := int16(1) + int8Value := int8(1) + float64Value := float64(1) + float32Value := float32(1) + uintValue := uint(1) + uint64Value := uint64(1) + uint32Value := uint32(1) + uint16Value := uint16(1) + uint8Value := uint8(1) + + fixtures := make([]interface{}, 24) + fixtures[0] = intValue + fixtures[1] = &intValue + fixtures[2] = int64Value + fixtures[3] = &int64Value + fixtures[4] = int32Value + fixtures[5] = &int32Value + fixtures[6] = int16Value + fixtures[7] = &int16Value + fixtures[8] = int8Value + fixtures[9] = &int8Value + fixtures[10] = float64Value + fixtures[11] = &float64Value + fixtures[12] = float32Value + fixtures[13] = &float32Value + fixtures[14] = uintValue + fixtures[15] = &uintValue + fixtures[16] = uint64Value + fixtures[17] = &uint64Value + fixtures[18] = uint32Value + fixtures[19] = &uint32Value + fixtures[20] = uint16Value + fixtures[21] = &uint16Value + fixtures[22] = uint8Value + fixtures[23] = &uint8Value + + var intNilPointer *int + var int64NilPointer *int64 + var int32NilPointer *int32 + var int16NilPointer *int16 + var int8NilPointer *int8 + var float64NilPointer *float64 + var float32NilPointer *float32 + var uintNilPointer *uint + var uint64NilPointer *uint64 + var uint32NilPointer *uint32 + var uint16NilPointer *uint16 + var uint8NilPointer *uint8 + + nilPointerFixtures := make([]interface{}, 12) + nilPointerFixtures[0] = intNilPointer + nilPointerFixtures[1] = int64NilPointer + nilPointerFixtures[2] = int32NilPointer + nilPointerFixtures[3] = int16NilPointer + nilPointerFixtures[4] = int8NilPointer + nilPointerFixtures[5] = float64NilPointer + nilPointerFixtures[6] = float32NilPointer + nilPointerFixtures[7] = uintNilPointer + nilPointerFixtures[8] = uint64NilPointer + nilPointerFixtures[9] = uint32NilPointer + nilPointerFixtures[10] = uint16NilPointer + nilPointerFixtures[11] = uint8NilPointer + + Convey("When converting values to float should return expected value", func() { + for _, f := range fixtures { + value, _ := ConvertSqlValueColumnToFloat("col", f) + + if !value.Valid { + t.Fatalf("Failed to convert %T value, expected a valid float value", f) + } + + if value.Float64 != null.FloatFrom(1).Float64 { + t.Fatalf("Failed to convert %T value, expected a float value of 1.000, but got %v", f, value) + } + } + }) + + Convey("When converting nil pointer values to float should return expected value", func() { + for _, f := range nilPointerFixtures { + value, err := ConvertSqlValueColumnToFloat("col", f) + + if err != nil { + t.Fatalf("Failed to convert %T value, expected a non nil error, but got %v", f, err) + } + + if value.Valid { + t.Fatalf("Failed to convert %T value, expected an invalid float value", f) + } + } + }) + }) }) } From 346577b664acdbbc219666437dbd3b3ecf312671 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 24 Apr 2018 19:53:06 +0200 Subject: [PATCH 261/319] mysql: fix value columns conversion to float when using timeseries query --- pkg/tsdb/mysql/mysql.go | 12 +++--------- pkg/tsdb/mysql/mysql_test.go | 32 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 4f5cd1b0784..7eceaffdb09 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -265,16 +265,10 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. continue } - switch columnValue := values[i].(type) { - case int64: - value = null.FloatFrom(float64(columnValue)) - case float64: - value = null.FloatFrom(columnValue) - case nil: - value.Valid = false - default: - return fmt.Errorf("Value column must have numeric datatype, column: %s type: %T value: %v", col, columnValue, columnValue) + if value, err = tsdb.ConvertSqlValueColumnToFloat(col, values[i]); err != nil { + return err } + if metricIndex == -1 { metric = col } diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 74cedea803a..29c5b72b408 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -420,12 +420,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeInt64 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeInt64 as time, timeInt64 FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -442,12 +442,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeInt64Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeInt64Nullable as time, timeInt64Nullable FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -464,12 +464,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeFloat64 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeFloat64 as time, timeFloat64 FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -486,12 +486,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeFloat64Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeFloat64Nullable as time, timeFloat64Nullable FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -508,12 +508,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() { + FocusConvey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeInt32 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeInt32 as time, timeInt32 FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -530,12 +530,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeInt32Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeInt32Nullable as time, timeInt32Nullable FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -552,12 +552,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeFloat32 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeFloat32 as time, timeFloat32 FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -574,12 +574,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3) }) - Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT timeFloat32Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT timeFloat32Nullable as time, timeFloat32Nullable FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", From cf43007531fb9593574c3ba6a8888b0af6a7a78c Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 24 Apr 2018 19:53:36 +0200 Subject: [PATCH 262/319] postgres: fix value columns conversion to float when using timeseries query --- pkg/tsdb/postgres/postgres.go | 12 +++-------- pkg/tsdb/postgres/postgres_test.go | 32 +++++++++++++++--------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go index 72d50b32d04..fdf09216e51 100644 --- a/pkg/tsdb/postgres/postgres.go +++ b/pkg/tsdb/postgres/postgres.go @@ -245,16 +245,10 @@ func (e PostgresQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *co continue } - switch columnValue := values[i].(type) { - case int64: - value = null.FloatFrom(float64(columnValue)) - case float64: - value = null.FloatFrom(columnValue) - case nil: - value.Valid = false - default: - return fmt.Errorf("Value column must have numeric datatype, column: %s type: %T value: %v", col, columnValue, columnValue) + if value, err = tsdb.ConvertSqlValueColumnToFloat(col, values[i]); err != nil { + return err } + if metricIndex == -1 { metric = col } diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index d18251bac7d..7f24d5a2063 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -353,12 +353,12 @@ func TestPostgres(t *testing.T) { _, err = sess.InsertMulti(series) So(err, ShouldBeNil) - Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeInt64" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeInt64" as time, "timeInt64" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -375,12 +375,12 @@ func TestPostgres(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeInt64Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeInt64Nullable" as time, "timeInt64Nullable" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -397,12 +397,12 @@ func TestPostgres(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeFloat64" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeFloat64" as time, "timeFloat64" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -419,12 +419,12 @@ func TestPostgres(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeFloat64Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeFloat64Nullable" as time, "timeFloat64Nullable" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -441,12 +441,12 @@ func TestPostgres(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeInt32" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeInt32" as time, "timeInt32" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -463,12 +463,12 @@ func TestPostgres(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeInt32Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeInt32Nullable" as time, "timeInt32Nullable" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -485,12 +485,12 @@ func TestPostgres(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeFloat32" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeFloat32" as time, "timeFloat32" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", @@ -507,12 +507,12 @@ func TestPostgres(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3) }) - Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT "timeFloat32Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`, + "rawSql": `SELECT "timeFloat32Nullable" as time, "timeFloat32Nullable" FROM metric_values ORDER BY time LIMIT 1`, "format": "time_series", }), RefId: "A", From 1452634a2a5dc26b25deb958a46145d76c9a21a6 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 24 Apr 2018 19:54:08 +0200 Subject: [PATCH 263/319] mssql: fix value columns conversion to float when using timeseries query --- pkg/tsdb/mssql/mssql.go | 12 +++--------- pkg/tsdb/mssql/mssql_test.go | 32 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index a598b7239ed..eb71259b46b 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -256,16 +256,10 @@ func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. continue } - switch columnValue := values[i].(type) { - case int64: - value = null.FloatFrom(float64(columnValue)) - case float64: - value = null.FloatFrom(columnValue) - case nil: - value.Valid = false - default: - return fmt.Errorf("Value column must have numeric datatype, column: %s type: %T value: %v", col, columnValue, columnValue) + if value, err = tsdb.ConvertSqlValueColumnToFloat(col, values[i]); err != nil { + return err } + if metricIndex == -1 { metric = col } diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index 599f4869f6a..167d02a1e07 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -374,12 +374,12 @@ func TestMSSQL(t *testing.T) { _, err = sess.InsertMulti(series) So(err, ShouldBeNil) - Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeInt64 as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeInt64 as time, timeInt64 FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", @@ -396,12 +396,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeInt64Nullable as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeInt64Nullable as time, timeInt64Nullable FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", @@ -418,12 +418,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeFloat64 as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeFloat64 as time, timeFloat64 FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", @@ -440,12 +440,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeFloat64Nullable as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeFloat64Nullable as time, timeFloat64Nullable FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", @@ -462,12 +462,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeInt32 as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeInt32 as time, timeInt32 FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", @@ -484,12 +484,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeInt32Nullable as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeInt32Nullable as time, timeInt32Nullable FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", @@ -506,12 +506,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) }) - Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeFloat32 as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeFloat32 as time, timeFloat32 FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", @@ -528,12 +528,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3) }) - Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() { + Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": `SELECT TOP 1 timeFloat32Nullable as time, valueOne FROM metric_values ORDER BY time`, + "rawSql": `SELECT TOP 1 timeFloat32Nullable as time, timeFloat32Nullable FROM metric_values ORDER BY time`, "format": "time_series", }), RefId: "A", From 1290087b7869019a07478757dff36ad944fda8a1 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Wed, 25 Apr 2018 11:01:33 +0200 Subject: [PATCH 264/319] dev: Mac compatible prometheus block. (#11718) --- docker/blocks/prometheus_mac/Dockerfile | 3 ++ docker/blocks/prometheus_mac/alert.rules | 10 +++++ .../blocks/prometheus_mac/docker-compose.yaml | 26 +++++++++++++ docker/blocks/prometheus_mac/prometheus.yml | 39 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 docker/blocks/prometheus_mac/Dockerfile create mode 100644 docker/blocks/prometheus_mac/alert.rules create mode 100644 docker/blocks/prometheus_mac/docker-compose.yaml create mode 100644 docker/blocks/prometheus_mac/prometheus.yml diff --git a/docker/blocks/prometheus_mac/Dockerfile b/docker/blocks/prometheus_mac/Dockerfile new file mode 100644 index 00000000000..2098e6527d3 --- /dev/null +++ b/docker/blocks/prometheus_mac/Dockerfile @@ -0,0 +1,3 @@ +FROM prom/prometheus:v1.8.2 +ADD prometheus.yml /etc/prometheus/ +ADD alert.rules /etc/prometheus/ diff --git a/docker/blocks/prometheus_mac/alert.rules b/docker/blocks/prometheus_mac/alert.rules new file mode 100644 index 00000000000..563d1e89994 --- /dev/null +++ b/docker/blocks/prometheus_mac/alert.rules @@ -0,0 +1,10 @@ +# Alert Rules + +ALERT AppCrash + IF process_open_fds > 0 + FOR 15s + LABELS { severity="critical" } + ANNOTATIONS { + summary = "Number of open fds > 0", + description = "Just testing" + } diff --git a/docker/blocks/prometheus_mac/docker-compose.yaml b/docker/blocks/prometheus_mac/docker-compose.yaml new file mode 100644 index 00000000000..ef53b07418a --- /dev/null +++ b/docker/blocks/prometheus_mac/docker-compose.yaml @@ -0,0 +1,26 @@ + prometheus: + build: blocks/prometheus_mac + ports: + - "9090:9090" + + node_exporter: + image: prom/node-exporter + ports: + - "9100:9100" + + fake-prometheus-data: + image: grafana/fake-data-gen + ports: + - "9091:9091" + environment: + FD_DATASOURCE: prom + + alertmanager: + image: quay.io/prometheus/alertmanager + ports: + - "9093:9093" + + prometheus-random-data: + build: blocks/prometheus_random_data + ports: + - "8081:8080" diff --git a/docker/blocks/prometheus_mac/prometheus.yml b/docker/blocks/prometheus_mac/prometheus.yml new file mode 100644 index 00000000000..299447ffb25 --- /dev/null +++ b/docker/blocks/prometheus_mac/prometheus.yml @@ -0,0 +1,39 @@ +# my global config +global: + scrape_interval: 10s # By default, scrape targets every 15 seconds. + evaluation_interval: 10s # By default, scrape targets every 15 seconds. + # scrape_timeout is set to the global default (10s). + +# Load and evaluate rules in this file every 'evaluation_interval' seconds. +rule_files: + - "alert.rules" + # - "first.rules" + # - "second.rules" + +alerting: + alertmanagers: + - scheme: http + static_configs: + - targets: + - "alertmanager:9093" + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'node_exporter' + static_configs: + - targets: ['node_exporter:9100'] + + - job_name: 'fake-data-gen' + static_configs: + - targets: ['fake-prometheus-data:9091'] + + - job_name: 'grafana' + static_configs: + - targets: ['host.docker.internal:3000'] + + - job_name: 'prometheus-random-data' + static_configs: + - targets: ['prometheus-random-data:8080'] From 99aa9a46bcd67de0dc477df003331f7405f04dff Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 25 Apr 2018 12:16:43 +0200 Subject: [PATCH 265/319] replaced border hack carot with fontawesome carot fixes #11677 --- public/sass/components/_dropdown.scss | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/public/sass/components/_dropdown.scss b/public/sass/components/_dropdown.scss index cc94a379e07..37dbdcd89ef 100644 --- a/public/sass/components/_dropdown.scss +++ b/public/sass/components/_dropdown.scss @@ -256,17 +256,15 @@ // Caret to indicate there is a submenu .dropdown-submenu > a::after { - display: block; - content: ' '; - float: right; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - border-width: 5px 0 5px 5px; - border-left-color: $text-color-weak; - margin-top: 5px; - margin-right: -4px; + position: absolute; + top: 35%; + right: $input-padding-x; + background-color: transparent; + color: $text-color-weak; + font: normal normal normal $font-size-sm/1 FontAwesome; + content: '\f0da'; + pointer-events: none; + font-size: 11px; } .dropdown-submenu:hover > a::after { border-left-color: $dropdownLinkColorHover; From 6836268f3ebbb576bda961bfa1198db7092a6c16 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 25 Apr 2018 12:44:39 +0200 Subject: [PATCH 266/319] removed height 100% from panel-container to fix ie11 panel edit mode --- public/sass/pages/_dashboard.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 871db4dfc2d..cf32522df7f 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -33,7 +33,6 @@ div.flot-text { border: $panel-border; position: relative; border-radius: 3px; - height: 100%; &.panel-transparent { background-color: transparent; From 6687409efba0efb2e6b71625b9782370b19ff111 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 25 Apr 2018 15:36:00 +0200 Subject: [PATCH 267/319] prometheus: fix variable query to fallback correctly to series query Using a query of for example up or up{job=job1} --- public/app/plugins/datasource/prometheus/datasource.ts | 4 ++++ public/app/plugins/datasource/prometheus/metric_find_query.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index cbf701a0abe..2a8b3069a53 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -329,4 +329,8 @@ export class PrometheusDatasource { } return Math.ceil(date.valueOf() / 1000); } + + getOriginalMetricName(labelData) { + return this.resultTransformer.getOriginalMetricName(labelData); + } } diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.ts b/public/app/plugins/datasource/prometheus/metric_find_query.ts index 337cd74c14c..13b6d7df8e3 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.ts +++ b/public/app/plugins/datasource/prometheus/metric_find_query.ts @@ -121,7 +121,7 @@ export default class PrometheusMetricFindQuery { var self = this; return this.datasource.metadataRequest(url).then(function(result) { - return _.map(result.data.data, function(metric) { + return _.map(result.data.data, metric => { return { text: self.datasource.getOriginalMetricName(metric), expandable: true, From f112e38266a4cbb96c170fc213e2533d0c06c814 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 25 Apr 2018 15:36:47 +0200 Subject: [PATCH 268/319] prometheus: convert metric find query tests to jest --- .../prometheus/specs/datasource.jest.ts | 20 ++ .../specs/metric_find_query.jest.ts | 205 ++++++++++++++++++ .../specs/metric_find_query_specs.ts | 181 ---------------- 3 files changed, 225 insertions(+), 181 deletions(-) create mode 100644 public/app/plugins/datasource/prometheus/specs/metric_find_query.jest.ts delete mode 100644 public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts diff --git a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts index a997a2d8233..2ab2895d731 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts @@ -43,6 +43,26 @@ describe('PrometheusDatasource', () => { }); }); + describe('When performing performSuggestQuery', () => { + it('should cache response', async () => { + ctx.backendSrvMock.datasourceRequest.mockReturnValue( + Promise.resolve({ + status: 'success', + data: { data: ['value1', 'value2', 'value3'] }, + }) + ); + + let results = await ctx.ds.performSuggestQuery('value', true); + + expect(results).toHaveLength(3); + + ctx.backendSrvMock.datasourceRequest.mockReset(); + results = await ctx.ds.performSuggestQuery('value', true); + + expect(results).toHaveLength(3); + }); + }); + describe('When converting prometheus histogram to heatmap format', () => { beforeEach(() => { ctx.query = { diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query.jest.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query.jest.ts new file mode 100644 index 00000000000..88f6830cd31 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/specs/metric_find_query.jest.ts @@ -0,0 +1,205 @@ +import moment from 'moment'; +import { PrometheusDatasource } from '../datasource'; +import PrometheusMetricFindQuery from '../metric_find_query'; +import q from 'q'; + +describe('PrometheusMetricFindQuery', function() { + let instanceSettings = { + url: 'proxied', + directUrl: 'direct', + user: 'test', + password: 'mupp', + jsonData: { httpMethod: 'GET' }, + }; + const raw = { + from: moment.utc('2018-04-25 10:00'), + to: moment.utc('2018-04-25 11:00'), + }; + let ctx: any = { + backendSrvMock: { + datasourceRequest: jest.fn(() => Promise.resolve({})), + }, + templateSrvMock: { + replace: a => a, + }, + timeSrvMock: { + timeRange: () => ({ + from: raw.from, + to: raw.to, + raw: raw, + }), + }, + }; + + ctx.setupMetricFindQuery = (data: any) => { + ctx.backendSrvMock.datasourceRequest.mockReturnValue(Promise.resolve({ status: 'success', data: data.response })); + return new PrometheusMetricFindQuery(ctx.ds, data.query, ctx.timeSrvMock); + }; + + beforeEach(() => { + ctx.backendSrvMock.datasourceRequest.mockReset(); + ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock); + }); + + describe('When performing metricFindQuery', () => { + it('label_values(resource) should generate label search query', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'label_values(resource)', + response: { + data: ['value1', 'value2', 'value3'], + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(3); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: 'proxied/api/v1/label/resource/values', + silent: true, + }); + }); + + it('label_values(metric, resource) should generate series query with correct time', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'label_values(metric, resource)', + response: { + data: [ + { __name__: 'metric', resource: 'value1' }, + { __name__: 'metric', resource: 'value2' }, + { __name__: 'metric', resource: 'value3' }, + ], + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(3); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: `proxied/api/v1/series?match[]=metric&start=${raw.from.unix()}&end=${raw.to.unix()}`, + silent: true, + }); + }); + + it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query with correct time', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'label_values(metric{label1="foo", label2="bar", label3="baz"}, resource)', + response: { + data: [ + { __name__: 'metric', resource: 'value1' }, + { __name__: 'metric', resource: 'value2' }, + { __name__: 'metric', resource: 'value3' }, + ], + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(3); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: `proxied/api/v1/series?match[]=${encodeURIComponent( + 'metric{label1="foo", label2="bar", label3="baz"}' + )}&start=${raw.from.unix()}&end=${raw.to.unix()}`, + silent: true, + }); + }); + + it('label_values(metric, resource) result should not contain empty string', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'label_values(metric, resource)', + response: { + data: [ + { __name__: 'metric', resource: 'value1' }, + { __name__: 'metric', resource: 'value2' }, + { __name__: 'metric', resource: '' }, + ], + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(2); + expect(results[0].text).toBe('value1'); + expect(results[1].text).toBe('value2'); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: `proxied/api/v1/series?match[]=metric&start=${raw.from.unix()}&end=${raw.to.unix()}`, + silent: true, + }); + }); + + it('metrics(metric.*) should generate metric name query', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'metrics(metric.*)', + response: { + data: ['metric1', 'metric2', 'metric3', 'nomatch'], + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(3); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: 'proxied/api/v1/label/__name__/values', + silent: true, + }); + }); + + it('query_result(metric) should generate metric name query', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'query_result(metric)', + response: { + data: { + resultType: 'vector', + result: [ + { + metric: { __name__: 'metric', job: 'testjob' }, + value: [1443454528.0, '3846'], + }, + ], + }, + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(1); + expect(results[0].text).toBe('metric{job="testjob"} 3846 1443454528000'); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: `proxied/api/v1/query?query=metric&time=${raw.to.unix()}`, + requestId: undefined, + }); + }); + + it('up{job="job1"} should fallback using generate series query', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'up{job="job1"}', + response: { + data: [ + { __name__: 'up', instance: '127.0.0.1:1234', job: 'job1' }, + { __name__: 'up', instance: '127.0.0.1:5678', job: 'job1' }, + { __name__: 'up', instance: '127.0.0.1:9102', job: 'job1' }, + ], + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(3); + expect(results[0].text).toBe('up{instance="127.0.0.1:1234",job="job1"}'); + expect(results[1].text).toBe('up{instance="127.0.0.1:5678",job="job1"}'); + expect(results[2].text).toBe('up{instance="127.0.0.1:9102",job="job1"}'); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: `proxied/api/v1/series?match[]=${encodeURIComponent( + 'up{job="job1"}' + )}&start=${raw.from.unix()}&end=${raw.to.unix()}`, + silent: true, + }); + }); + }); +}); diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts deleted file mode 100644 index e5d7aa81210..00000000000 --- a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common'; - -import moment from 'moment'; -import helpers from 'test/specs/helpers'; -import { PrometheusDatasource } from '../datasource'; -import PrometheusMetricFindQuery from '../metric_find_query'; - -describe('PrometheusMetricFindQuery', function() { - var ctx = new helpers.ServiceTestContext(); - var instanceSettings = { - url: 'proxied', - directUrl: 'direct', - user: 'test', - password: 'mupp', - jsonData: { httpMethod: 'GET' }, - }; - - beforeEach(angularMocks.module('grafana.core')); - beforeEach(angularMocks.module('grafana.services')); - beforeEach( - angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { - ctx.$q = $q; - ctx.$httpBackend = $httpBackend; - ctx.$rootScope = $rootScope; - ctx.ds = $injector.instantiate(PrometheusDatasource, { - instanceSettings: instanceSettings, - }); - $httpBackend.when('GET', /\.html$/).respond(''); - }) - ); - - describe('When performing metricFindQuery', function() { - var results; - var response; - it('label_values(resource) should generate label search query', function() { - response = { - status: 'success', - data: ['value1', 'value2', 'value3'], - }; - ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/resource/values').respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)', ctx.timeSrv); - pm.process().then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(3); - }); - it('label_values(metric, resource) should generate series query', function() { - response = { - status: 'success', - data: [ - { __name__: 'metric', resource: 'value1' }, - { __name__: 'metric', resource: 'value2' }, - { __name__: 'metric', resource: 'value3' }, - ], - }; - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); - pm.process().then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(3); - }); - it('label_values(metric, resource) should pass correct time', function() { - ctx.timeSrv.setTime({ - from: moment.utc('2011-01-01'), - to: moment.utc('2015-01-01'), - }); - ctx.$httpBackend - .expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=1293840000&end=1420070400/) - .respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); - pm.process().then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - }); - it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query', function() { - response = { - status: 'success', - data: [ - { __name__: 'metric', resource: 'value1' }, - { __name__: 'metric', resource: 'value2' }, - { __name__: 'metric', resource: 'value3' }, - ], - }; - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); - pm.process().then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(3); - }); - it('label_values(metric, resource) result should not contain empty string', function() { - response = { - status: 'success', - data: [ - { __name__: 'metric', resource: 'value1' }, - { __name__: 'metric', resource: 'value2' }, - { __name__: 'metric', resource: '' }, - ], - }; - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); - pm.process().then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(2); - expect(results[0].text).to.be('value1'); - expect(results[1].text).to.be('value2'); - }); - it('metrics(metric.*) should generate metric name query', function() { - response = { - status: 'success', - data: ['metric1', 'metric2', 'metric3', 'nomatch'], - }; - ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)', ctx.timeSrv); - pm.process().then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(3); - }); - it('query_result(metric) should generate metric name query', function() { - response = { - status: 'success', - data: { - resultType: 'vector', - result: [ - { - metric: { __name__: 'metric', job: 'testjob' }, - value: [1443454528.0, '3846'], - }, - ], - }, - }; - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)', ctx.timeSrv); - pm.process().then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(1); - expect(results[0].text).to.be('metric{job="testjob"} 3846 1443454528000'); - }); - }); - - describe('When performing performSuggestQuery', function() { - var results; - var response; - it('cache response', function() { - response = { - status: 'success', - data: ['value1', 'value2', 'value3'], - }; - ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response); - ctx.ds.performSuggestQuery('value', true).then(function(data) { - results = data; - }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(3); - ctx.ds.performSuggestQuery('value', true).then(function(data) { - // get from cache, no need to flush - results = data; - expect(results.length).to.be(3); - }); - }); - }); -}); From ddeba41638806bf7174e7a51ce78ecc53dd29309 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 26 Apr 2018 15:49:22 +0200 Subject: [PATCH 269/319] fix so that google analytics script are cached --- public/app/core/services/analytics.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/core/services/analytics.ts b/public/app/core/services/analytics.ts index 370773154e5..4f9994302ab 100644 --- a/public/app/core/services/analytics.ts +++ b/public/app/core/services/analytics.ts @@ -7,7 +7,11 @@ export class Analytics { constructor(private $rootScope, private $location) {} gaInit() { - $.getScript('https://www.google-analytics.com/analytics.js'); // jQuery shortcut + $.ajax({ + url: 'https://www.google-analytics.com/analytics.js', + dataType: 'script', + cache: true, + }); var ga = ((window).ga = (window).ga || function() { From 44a61a6db33cc3eefe559b1ccb226521c53fb658 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 26 Apr 2018 18:18:54 +0200 Subject: [PATCH 270/319] changelog: update for v5.1.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ec1965098..0a82a8d0498 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 5.1.0 (2018-04-26) + +* **Folders**: Default permissions on folder are not shown as inherited in its dashboards [#11668](https://github.com/grafana/grafana/issues/11668) +* **Templating**: Allow more than 20 previews when creating a variable [#11508](https://github.com/grafana/grafana/issues/11508) +* **Dashboard**: Row edit icon not shown [#11466](https://github.com/grafana/grafana/issues/11466) +* **SQL**: Unsupported data types for value column using time series query [#11703](https://github.com/grafana/grafana/issues/11703) +* **Prometheus**: Prometheus query inspector expands to be very large on autocomplete queries [#11673](https://github.com/grafana/grafana/issues/11673) + # 5.1.0-beta1 (2018-04-20) * **MSSQL**: New Microsoft SQL Server data source [#10093](https://github.com/grafana/grafana/pull/10093), [#11298](https://github.com/grafana/grafana/pull/11298), thx [@linuxchips](https://github.com/linuxchips) From 6fa7ffc23fb5e2ae3be9e80d846efe163a110762 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 26 Apr 2018 18:56:44 +0200 Subject: [PATCH 271/319] docs: update installation instructions targeting v5.1.0 stable --- docs/sources/installation/debian.md | 12 +++++++----- docs/sources/installation/rpm.md | 17 +++++++++-------- docs/sources/installation/windows.md | 5 ++++- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 8b2e15ad124..dccb880ec74 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -15,8 +15,10 @@ weight = 1 Description | Download ------------ | ------------- -Stable for Debian-based Linux | [grafana_5.0.4_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.4_amd64.deb) +Stable for Debian-based Linux | [grafana_5.1.0_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.0_amd64.deb) + Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. @@ -25,17 +27,17 @@ installation. ```bash -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.4_amd64.deb +wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.0_amd64.deb sudo apt-get install -y adduser libfontconfig -sudo dpkg -i grafana_5.0.4_amd64.deb +sudo dpkg -i grafana_5.1.0_amd64.deb ``` -## Install Latest Beta + ## APT Repository diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index 3650560d5cf..e142d6e2c95 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -15,9 +15,10 @@ weight = 2 Description | Download ------------ | ------------- -Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.4 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm) +Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [5.1.0 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.0-1.x86_64.rpm) + Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. @@ -27,29 +28,29 @@ installation. You can install Grafana using Yum directly. ```bash -$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm +$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.0-1.x86_64.rpm ``` -## Install Beta + Or install manually using `rpm`. #### On CentOS / Fedora / Redhat: ```bash -$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm +$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.0-1.x86_64.rpm $ sudo yum install initscripts fontconfig -$ sudo rpm -Uvh grafana-5.0.4-1.x86_64.rpm +$ sudo rpm -Uvh grafana-5.1.0-1.x86_64.rpm ``` #### On OpenSuse: ```bash -$ sudo rpm -i --nodeps grafana-5.0.4-1.x86_64.rpm +$ sudo rpm -i --nodeps grafana-5.1.0-1.x86_64.rpm ``` ## Install via YUM Repository diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index 31fe243c01d..8baccac6211 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -12,8 +12,11 @@ weight = 3 Description | Download ------------ | ------------- -Latest stable package for Windows | [grafana-5.0.4.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4.windows-x64.zip) +Latest stable package for Windows | [grafana-5.1.0.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.0.windows-x64.zip) + + Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. From b53a57610bb2758648c8473084a18ce9f176c7dd Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 26 Apr 2018 18:59:45 +0200 Subject: [PATCH 272/319] docs: update current version to 5.1 --- docs/versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/versions.json b/docs/versions.json index 2dcc7ebe776..61e471938f2 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1,6 +1,6 @@ [ - { "version": "v5.1", "path": "/v5.1", "archived": false }, - { "version": "v5.0", "path": "/", "archived": false, "current": true }, + { "version": "v5.1", "path": "/", "archived": false, "current": true }, + { "version": "v5.0", "path": "/v5.0", "archived": true }, { "version": "v4.6", "path": "/v4.6", "archived": true }, { "version": "v4.5", "path": "/v4.5", "archived": true }, { "version": "v4.4", "path": "/v4.4", "archived": true }, From 7aaa1884711f16361e9535540f4b7f7c836c7143 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Tue, 24 Apr 2018 18:42:27 +0200 Subject: [PATCH 273/319] build.go: fix deadcode issues --- build.go | 43 ++++++++++--------------------------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/build.go b/build.go index b86fc838e6b..21b528071e8 100644 --- a/build.go +++ b/build.go @@ -16,7 +16,6 @@ import ( "os/exec" "path" "path/filepath" - "regexp" "runtime" "strconv" "strings" @@ -24,14 +23,14 @@ import ( ) var ( - versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`) - goarch string - goos string - gocc string - gocxx string - cgo string - pkgArch string - version string = "v1" + //versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`) + goarch string + goos string + gocc string + gocxx string + cgo string + pkgArch string + version string = "v1" // deb & rpm does not support semver so have to handle their version a little differently linuxPackageVersion string = "v1" linuxPackageIteration string = "" @@ -44,14 +43,14 @@ var ( isDev bool = false ) -const minGoVersion = 1.8 - func main() { log.SetOutput(os.Stdout) log.SetFlags(0) ensureGoPath() + verifyGitRepoIsClean() + flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH") flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS") flag.StringVar(&gocc, "cc", "", "CC") @@ -352,10 +351,6 @@ func ensureGoPath() { } } -func ChangeWorkingDir(dir string) { - os.Chdir(dir) -} - func grunt(params ...string) { if runtime.GOOS == "windows" { runPrint(`.\node_modules\.bin\grunt`, params...) @@ -492,24 +487,6 @@ func buildStamp() int64 { return s } -func buildArch() string { - os := goos - if os == "darwin" { - os = "macosx" - } - return fmt.Sprintf("%s-%s", os, goarch) -} - -func run(cmd string, args ...string) []byte { - bs, err := runError(cmd, args...) - if err != nil { - log.Println(cmd, strings.Join(args, " ")) - log.Println(string(bs)) - log.Fatal(err) - } - return bytes.TrimSpace(bs) -} - func runError(cmd string, args ...string) ([]byte, error) { ecmd := exec.Command(cmd, args...) bs, err := ecmd.CombinedOutput() From 97fd66db2e52ae830a324c9e82ee681b1851a5c0 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Tue, 24 Apr 2018 18:52:57 +0200 Subject: [PATCH 274/319] pkg: fix deadcode issues --- pkg/api/annotations.go | 16 ----- pkg/middleware/render_auth.go | 2 - pkg/services/sqlstore/migrations/stats_mig.go | 63 ++++++++++--------- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index fdf577a6a6f..52eeb57dbb9 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -294,19 +294,3 @@ func canSave(c *m.ReqContext, repo annotations.Repository, annotationID int64) R return nil } - -func canSaveByRegionID(c *m.ReqContext, repo annotations.Repository, regionID int64) Response { - items, err := repo.Find(&annotations.ItemQuery{RegionId: regionID, OrgId: c.OrgId}) - - if err != nil || len(items) == 0 { - return Error(500, "Could not find annotation to update", err) - } - - dashboardID := items[0].DashboardId - - if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave { - return dashboardGuardianResponse(err) - } - - return nil -} diff --git a/pkg/middleware/render_auth.go b/pkg/middleware/render_auth.go index 6c338becbda..c382eb8e707 100644 --- a/pkg/middleware/render_auth.go +++ b/pkg/middleware/render_auth.go @@ -31,8 +31,6 @@ func initContextWithRenderAuth(ctx *m.ReqContext) bool { return true } -type renderContextFunc func(key string) (string, error) - func AddRenderAuthKey(orgId int64, userId int64, orgRole m.RoleType) string { renderKeysLock.Lock() diff --git a/pkg/services/sqlstore/migrations/stats_mig.go b/pkg/services/sqlstore/migrations/stats_mig.go index 7e10eeb9f90..c47b8202c53 100644 --- a/pkg/services/sqlstore/migrations/stats_mig.go +++ b/pkg/services/sqlstore/migrations/stats_mig.go @@ -2,37 +2,38 @@ package migrations import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" -func addStatsMigrations(mg *Migrator) { - statTable := Table{ - Name: "stat", - Columns: []*Column{ - {Name: "id", Type: DB_Int, IsPrimaryKey: true, IsAutoIncrement: true}, - {Name: "metric", Type: DB_Varchar, Length: 20, Nullable: false}, - {Name: "type", Type: DB_Int, Nullable: false}, - }, - Indices: []*Index{ - {Cols: []string{"metric"}, Type: UniqueIndex}, - }, - } - - // create table - mg.AddMigration("create stat table", NewAddTableMigration(statTable)) - - // create indices - mg.AddMigration("add index stat.metric", NewAddIndexMigration(statTable, statTable.Indices[0])) - - statValue := Table{ - Name: "stat_value", - Columns: []*Column{ - {Name: "id", Type: DB_Int, IsPrimaryKey: true, IsAutoIncrement: true}, - {Name: "value", Type: DB_Double, Nullable: false}, - {Name: "time", Type: DB_DateTime, Nullable: false}, - }, - } - - // create table - mg.AddMigration("create stat_value table", NewAddTableMigration(statValue)) -} +// commented out because of the deadcode CI check +//func addStatsMigrations(mg *Migrator) { +// statTable := Table{ +// Name: "stat", +// Columns: []*Column{ +// {Name: "id", Type: DB_Int, IsPrimaryKey: true, IsAutoIncrement: true}, +// {Name: "metric", Type: DB_Varchar, Length: 20, Nullable: false}, +// {Name: "type", Type: DB_Int, Nullable: false}, +// }, +// Indices: []*Index{ +// {Cols: []string{"metric"}, Type: UniqueIndex}, +// }, +// } +// +// // create table +// mg.AddMigration("create stat table", NewAddTableMigration(statTable)) +// +// // create indices +// mg.AddMigration("add index stat.metric", NewAddIndexMigration(statTable, statTable.Indices[0])) +// +// statValue := Table{ +// Name: "stat_value", +// Columns: []*Column{ +// {Name: "id", Type: DB_Int, IsPrimaryKey: true, IsAutoIncrement: true}, +// {Name: "value", Type: DB_Double, Nullable: false}, +// {Name: "time", Type: DB_DateTime, Nullable: false}, +// }, +// } +// +// // create table +// mg.AddMigration("create stat_value table", NewAddTableMigration(statValue)) +//} func addTestDataMigrations(mg *Migrator) { testData := Table{ From 0459261d1910642f7e2ac25d28d0d924abecf994 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Tue, 24 Apr 2018 18:55:43 +0200 Subject: [PATCH 275/319] add deadcode linter to circleci --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e95583ecae..4b717083853 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,13 +27,14 @@ jobs: steps: - checkout - run: 'go get -u gopkg.in/alecthomas/gometalinter.v2' + - run: 'go get -u github.com/tsenart/deadcode' - run: 'go get -u github.com/gordonklaus/ineffassign' - run: 'go get -u github.com/opennota/check/cmd/structcheck' - run: 'go get -u github.com/mdempsky/unconvert' - run: 'go get -u github.com/opennota/check/cmd/varcheck' - run: name: run linters - command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...' + command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...' test-frontend: docker: From f1220fd2a4d46eacf9b28b3e5b4b1d91b9615856 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Thu, 26 Apr 2018 11:58:42 +0200 Subject: [PATCH 276/319] Explore WIP --- package.json | 6 + pkg/api/index.go | 11 + public/app/containers/Explore/ElapsedTime.tsx | 46 + public/app/containers/Explore/Explore.tsx | 246 ++ public/app/containers/Explore/Graph.tsx | 123 + public/app/containers/Explore/Legend.tsx | 22 + public/app/containers/Explore/QueryField.tsx | 562 +++ public/app/containers/Explore/Table.tsx | 24 + public/app/containers/Explore/Typeahead.tsx | 66 + .../Explore/slate-plugins/braces.test.ts | 47 + .../Explore/slate-plugins/braces.ts | 51 + .../Explore/slate-plugins/clear.test.ts | 38 + .../containers/Explore/slate-plugins/clear.ts | 22 + .../Explore/slate-plugins/newline.ts | 35 + .../Explore/slate-plugins/prism/index.tsx | 122 + .../Explore/slate-plugins/prism/promql.ts | 123 + .../Explore/slate-plugins/runner.ts | 14 + .../app/containers/Explore/utils/debounce.ts | 14 + public/app/containers/Explore/utils/dom.ts | 40 + .../containers/Explore/utils/prometheus.ts | 20 + public/app/core/components/grafana_app.ts | 16 +- public/app/features/plugins/datasource_srv.ts | 2 +- public/app/routes/ReactContainer.tsx | 10 +- public/app/routes/routes.ts | 8 + public/app/stores/store.ts | 4 +- public/sass/_grafana.scss | 1 + public/sass/layout/_page.scss | 7 + public/sass/pages/_explore.scss | 304 ++ scripts/webpack/webpack.dev.js | 1 + scripts/webpack/webpack.prod.js | 7 +- yarn.lock | 3306 +++++++++-------- 31 files changed, 3685 insertions(+), 1613 deletions(-) create mode 100644 public/app/containers/Explore/ElapsedTime.tsx create mode 100644 public/app/containers/Explore/Explore.tsx create mode 100644 public/app/containers/Explore/Graph.tsx create mode 100644 public/app/containers/Explore/Legend.tsx create mode 100644 public/app/containers/Explore/QueryField.tsx create mode 100644 public/app/containers/Explore/Table.tsx create mode 100644 public/app/containers/Explore/Typeahead.tsx create mode 100644 public/app/containers/Explore/slate-plugins/braces.test.ts create mode 100644 public/app/containers/Explore/slate-plugins/braces.ts create mode 100644 public/app/containers/Explore/slate-plugins/clear.test.ts create mode 100644 public/app/containers/Explore/slate-plugins/clear.ts create mode 100644 public/app/containers/Explore/slate-plugins/newline.ts create mode 100644 public/app/containers/Explore/slate-plugins/prism/index.tsx create mode 100644 public/app/containers/Explore/slate-plugins/prism/promql.ts create mode 100644 public/app/containers/Explore/slate-plugins/runner.ts create mode 100644 public/app/containers/Explore/utils/debounce.ts create mode 100644 public/app/containers/Explore/utils/dom.ts create mode 100644 public/app/containers/Explore/utils/prometheus.ts create mode 100644 public/sass/pages/_explore.scss diff --git a/package.json b/package.json index 495507b2f00..383e0e39ab5 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "axios": "^0.17.1", "babel-core": "^6.26.0", "babel-loader": "^7.1.2", + "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-preset-es2015": "^6.24.1", "clean-webpack-plugin": "^0.1.19", "css-loader": "^0.28.7", @@ -150,6 +151,7 @@ "d3-scale-chromatic": "^1.1.1", "eventemitter3": "^2.0.3", "file-saver": "^1.3.3", + "immutable": "^3.8.2", "jquery": "^3.2.1", "lodash": "^4.17.4", "mobx": "^3.4.1", @@ -158,6 +160,7 @@ "moment": "^2.18.1", "mousetrap": "^1.6.0", "mousetrap-global-bind": "^1.1.0", + "prismjs": "^1.6.0", "prop-types": "^15.6.0", "react": "^16.2.0", "react-dom": "^16.2.0", @@ -170,6 +173,9 @@ "remarkable": "^1.7.1", "rst2html": "github:thoward/rst2html#990cb89", "rxjs": "^5.4.3", + "slate": "^0.33.4", + "slate-plain-serializer": "^0.5.10", + "slate-react": "^0.12.4", "tether": "^1.4.0", "tether-drop": "https://github.com/torkelo/drop/tarball/master", "tinycolor2": "^1.4.1" diff --git a/pkg/api/index.go b/pkg/api/index.go index 94094706f68..64eaddcd1a7 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -117,6 +117,17 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { Children: dashboardChildNavs, }) + // data.NavTree = append(data.NavTree, &dtos.NavLink{ + // Text: "Explore", + // Id: "explore", + // SubTitle: "Explore your data", + // Icon: "fa fa-rocket", + // Url: setting.AppSubUrl + "/explore", + // Children: []*dtos.NavLink{ + // {Text: "New tab", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/explore/new"}, + // }, + // }) + if c.IsSignedIn { // Only set login if it's different from the name var login string diff --git a/public/app/containers/Explore/ElapsedTime.tsx b/public/app/containers/Explore/ElapsedTime.tsx new file mode 100644 index 00000000000..123299fd96a --- /dev/null +++ b/public/app/containers/Explore/ElapsedTime.tsx @@ -0,0 +1,46 @@ +import React, { PureComponent } from 'react'; + +const INTERVAL = 150; + +export default class ElapsedTime extends PureComponent { + offset: number; + timer: NodeJS.Timer; + + state = { + elapsed: 0, + }; + + start() { + this.offset = Date.now(); + this.timer = setInterval(this.tick, INTERVAL); + } + + tick = () => { + const jetzt = Date.now(); + const elapsed = jetzt - this.offset; + this.setState({ elapsed }); + }; + + componentWillReceiveProps(nextProps) { + if (nextProps.time) { + clearInterval(this.timer); + } else if (this.props.time) { + this.start(); + } + } + + componentDidMount() { + this.start(); + } + + componentWillUnmount() { + clearInterval(this.timer); + } + + render() { + const { elapsed } = this.state; + const { className, time } = this.props; + const value = (time || elapsed) / 1000; + return {value.toFixed(1)}s; + } +} diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx new file mode 100644 index 00000000000..55c1d088ccc --- /dev/null +++ b/public/app/containers/Explore/Explore.tsx @@ -0,0 +1,246 @@ +import React from 'react'; +import { hot } from 'react-hot-loader'; +import colors from 'app/core/utils/colors'; +import TimeSeries from 'app/core/time_series2'; + +import ElapsedTime from './ElapsedTime'; +import Legend from './Legend'; +import QueryField from './QueryField'; +import Graph from './Graph'; +import Table from './Table'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; + +function buildQueryOptions({ format, interval, instant, now, query }) { + const to = now; + const from = to - 1000 * 60 * 60 * 3; + return { + interval, + range: { + from, + to, + }, + targets: [ + { + expr: query, + format, + instant, + }, + ], + }; +} + +function makeTimeSeriesList(dataList, options) { + return dataList.map((seriesData, index) => { + const datapoints = seriesData.datapoints || []; + const alias = seriesData.target; + + const colorIndex = index % colors.length; + const color = colors[colorIndex]; + + const series = new TimeSeries({ + datapoints: datapoints, + alias: alias, + color: color, + unit: seriesData.unit, + }); + + if (datapoints && datapoints.length > 0) { + const last = datapoints[datapoints.length - 1][1]; + const from = options.range.from; + if (last - from < -10000) { + series.isOutsideRange = true; + } + } + + return series; + }); +} + +interface IExploreState { + datasource: any; + datasourceError: any; + datasourceLoading: any; + graphResult: any; + latency: number; + loading: any; + requestOptions: any; + showingGraph: boolean; + showingTable: boolean; + tableResult: any; +} + +// @observer +export class Explore extends React.Component { + datasourceSrv: DatasourceSrv; + query: string; + + constructor(props) { + super(props); + this.state = { + datasource: null, + datasourceError: null, + datasourceLoading: true, + graphResult: null, + latency: 0, + loading: false, + requestOptions: null, + showingGraph: true, + showingTable: true, + tableResult: null, + }; + } + + async componentDidMount() { + const datasource = await this.props.datasourceSrv.get(); + const testResult = await datasource.testDatasource(); + if (testResult.status === 'success') { + this.setState({ datasource, datasourceError: null, datasourceLoading: false }); + } else { + this.setState({ datasource: null, datasourceError: testResult.message, datasourceLoading: false }); + } + } + + handleClickGraphButton = () => { + this.setState(state => ({ showingGraph: !state.showingGraph })); + }; + + handleClickTableButton = () => { + this.setState(state => ({ showingTable: !state.showingTable })); + }; + + handleRequestError({ error }) { + console.error(error); + } + + handleQueryChange = query => { + this.query = query; + }; + + handleSubmit = () => { + const { showingGraph, showingTable } = this.state; + if (showingTable) { + this.runTableQuery(); + } + if (showingGraph) { + this.runGraphQuery(); + } + }; + + async runGraphQuery() { + const { query } = this; + const { datasource } = this.state; + if (!query) { + return; + } + this.setState({ latency: 0, loading: true, graphResult: null }); + const now = Date.now(); + const options = buildQueryOptions({ + format: 'time_series', + interval: datasource.interval, + instant: false, + now, + query, + }); + try { + const res = await datasource.query(options); + const result = makeTimeSeriesList(res.data, options); + const latency = Date.now() - now; + this.setState({ latency, loading: false, graphResult: result, requestOptions: options }); + } catch (error) { + console.error(error); + this.setState({ loading: false, graphResult: error }); + } + } + + async runTableQuery() { + const { query } = this; + const { datasource } = this.state; + if (!query) { + return; + } + this.setState({ latency: 0, loading: true, tableResult: null }); + const now = Date.now(); + const options = buildQueryOptions({ format: 'table', interval: datasource.interval, instant: true, now, query }); + try { + const res = await datasource.query(options); + const tableModel = res.data[0]; + const latency = Date.now() - now; + this.setState({ latency, loading: false, tableResult: tableModel, requestOptions: options }); + } catch (error) { + console.error(error); + this.setState({ loading: false, tableResult: null }); + } + } + + request = url => { + const { datasource } = this.state; + return datasource.metadataRequest(url); + }; + + render() { + const { + datasource, + datasourceError, + datasourceLoading, + latency, + loading, + requestOptions, + graphResult, + showingGraph, + showingTable, + tableResult, + } = this.state; + const showingBoth = showingGraph && showingTable; + const graphHeight = showingBoth ? '200px' : null; + const graphButtonClassName = showingBoth || showingGraph ? 'btn m-r-1' : 'btn btn-inverse m-r-1'; + const tableButtonClassName = showingBoth || showingTable ? 'btn m-r-1' : 'btn btn-inverse m-r-1'; + return ( +
    +
    +

    Explore

    + {datasourceLoading ?
    Loading datasource...
    : null} + + {datasourceError ?
    Error connecting to datasource.
    : null} + + {datasource ? ( +
    +
    +
    + +
    +
    + + +
    +
    +
    + +
    + {loading || latency ? : null} +
    + {showingGraph ? ( + + ) : null} + {showingGraph ? : null} + {showingTable ? : null} + + + ) : null} + + + ); + } +} + +export default hot(module)(Explore); diff --git a/public/app/containers/Explore/Graph.tsx b/public/app/containers/Explore/Graph.tsx new file mode 100644 index 00000000000..0a13b39619d --- /dev/null +++ b/public/app/containers/Explore/Graph.tsx @@ -0,0 +1,123 @@ +import $ from 'jquery'; +import React, { Component } from 'react'; + +import TimeSeries from 'app/core/time_series2'; + +import 'vendor/flot/jquery.flot'; +import 'vendor/flot/jquery.flot.time'; + +// Copied from graph.ts +function time_format(ticks, min, max) { + if (min && max && ticks) { + var range = max - min; + var secPerTick = range / ticks / 1000; + var oneDay = 86400000; + var oneYear = 31536000000; + + if (secPerTick <= 45) { + return '%H:%M:%S'; + } + if (secPerTick <= 7200 || range <= oneDay) { + return '%H:%M'; + } + if (secPerTick <= 80000) { + return '%m/%d %H:%M'; + } + if (secPerTick <= 2419200 || range <= oneYear) { + return '%m/%d'; + } + return '%Y-%m'; + } + + return '%H:%M'; +} + +const FLOT_OPTIONS = { + legend: { + show: false, + }, + series: { + lines: { + linewidth: 1, + zero: false, + }, + shadowSize: 0, + }, + grid: { + minBorderMargin: 0, + markings: [], + backgroundColor: null, + borderWidth: 0, + // hoverable: true, + clickable: true, + color: '#a1a1a1', + margin: { left: 0, right: 0 }, + labelMarginX: 0, + }, + // selection: { + // mode: 'x', + // color: '#666', + // }, + // crosshair: { + // mode: 'x', + // }, +}; + +class Graph extends Component { + componentDidMount() { + this.draw(); + } + + componentDidUpdate(prevProps) { + if ( + prevProps.data !== this.props.data || + prevProps.options !== this.props.options || + prevProps.height !== this.props.height + ) { + this.draw(); + } + } + + draw() { + const { data, options: userOptions } = this.props; + if (!data) { + return; + } + const series = data.map((ts: TimeSeries) => ({ + label: ts.label, + data: ts.getFlotPairs('null'), + })); + + const $el = $(`#${this.props.id}`); + const ticks = $el.width() / 100; + const min = userOptions.range.from.valueOf(); + const max = userOptions.range.to.valueOf(); + const dynamicOptions = { + xaxis: { + mode: 'time', + min: min, + max: max, + label: 'Datetime', + ticks: ticks, + timeformat: time_format(ticks, min, max), + }, + }; + const options = { + ...FLOT_OPTIONS, + ...dynamicOptions, + ...userOptions, + }; + $.plot($el, series, options); + } + + render() { + const style = { + height: this.props.height || '400px', + width: this.props.width || '100%', + }; + + return
    ; + } +} + +export default Graph; diff --git a/public/app/containers/Explore/Legend.tsx b/public/app/containers/Explore/Legend.tsx new file mode 100644 index 00000000000..e00932fe566 --- /dev/null +++ b/public/app/containers/Explore/Legend.tsx @@ -0,0 +1,22 @@ +import React, { PureComponent } from 'react'; + +const LegendItem = ({ series }) => ( +
    +
    + +
    + {series.alias} +
    +); + +export default class Legend extends PureComponent { + render() { + const { className = '', data } = this.props; + const items = data || []; + return ( +
    + {items.map(series => )} +
    + ); + } +} diff --git a/public/app/containers/Explore/QueryField.tsx b/public/app/containers/Explore/QueryField.tsx new file mode 100644 index 00000000000..816473619fd --- /dev/null +++ b/public/app/containers/Explore/QueryField.tsx @@ -0,0 +1,562 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Value } from 'slate'; +import { Editor } from 'slate-react'; +import Plain from 'slate-plain-serializer'; + +// dom also includes Element polyfills +import { getNextCharacter, getPreviousCousin } from './utils/dom'; +import BracesPlugin from './slate-plugins/braces'; +import ClearPlugin from './slate-plugins/clear'; +import NewlinePlugin from './slate-plugins/newline'; +import PluginPrism, { configurePrismMetricsTokens } from './slate-plugins/prism/index'; +import RunnerPlugin from './slate-plugins/runner'; +import debounce from './utils/debounce'; +import { processLabels, RATE_RANGES, cleanText } from './utils/prometheus'; + +import Typeahead from './Typeahead'; + +const EMPTY_METRIC = ''; +const TYPEAHEAD_DEBOUNCE = 300; + +function flattenSuggestions(s) { + return s ? s.reduce((acc, g) => acc.concat(g.items), []) : []; +} + +const getInitialValue = query => + Value.fromJSON({ + document: { + nodes: [ + { + object: 'block', + type: 'paragraph', + nodes: [ + { + object: 'text', + leaves: [ + { + text: query, + }, + ], + }, + ], + }, + ], + }, + }); + +class Portal extends React.Component { + node: any; + constructor(props) { + super(props); + this.node = document.createElement('div'); + this.node.classList.add(`query-field-portal-${props.index}`); + document.body.appendChild(this.node); + } + + componentWillUnmount() { + document.body.removeChild(this.node); + } + + render() { + return ReactDOM.createPortal(this.props.children, this.node); + } +} + +class QueryField extends React.Component { + menuEl: any; + plugins: any; + resetTimer: any; + + constructor(props, context) { + super(props, context); + + this.plugins = [ + BracesPlugin(), + ClearPlugin(), + RunnerPlugin({ handler: props.onPressEnter }), + NewlinePlugin(), + PluginPrism(), + ]; + + this.state = { + labelKeys: {}, + labelValues: {}, + metrics: props.metrics || [], + suggestions: [], + typeaheadIndex: 0, + typeaheadPrefix: '', + value: getInitialValue(props.initialQuery || ''), + }; + } + + componentDidMount() { + this.updateMenu(); + + if (this.props.metrics === undefined) { + this.fetchMetricNames(); + } + } + + componentWillUnmount() { + clearTimeout(this.resetTimer); + } + + componentDidUpdate() { + this.updateMenu(); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.metrics && nextProps.metrics !== this.props.metrics) { + this.setState({ metrics: nextProps.metrics }, this.onMetricsReceived); + } + // initialQuery is null in case the user typed + if (nextProps.initialQuery !== null && nextProps.initialQuery !== this.props.initialQuery) { + this.setState({ value: getInitialValue(nextProps.initialQuery) }); + } + } + + onChange = ({ value }) => { + const changed = value.document !== this.state.value.document; + this.setState({ value }, () => { + if (changed) { + this.handleChangeQuery(); + } + }); + + window.requestAnimationFrame(this.handleTypeahead); + }; + + onMetricsReceived = () => { + if (!this.state.metrics) { + return; + } + configurePrismMetricsTokens(this.state.metrics); + // Trigger re-render + window.requestAnimationFrame(() => { + // Bogus edit to trigger highlighting + const change = this.state.value + .change() + .insertText(' ') + .deleteBackward(1); + this.onChange(change); + }); + }; + + request = url => { + if (this.props.request) { + return this.props.request(url); + } + return fetch(url); + }; + + handleChangeQuery = () => { + // Send text change to parent + const { onQueryChange } = this.props; + if (onQueryChange) { + onQueryChange(Plain.serialize(this.state.value)); + } + }; + + handleTypeahead = debounce(() => { + const selection = window.getSelection(); + if (selection.anchorNode) { + const wrapperNode = selection.anchorNode.parentElement; + const editorNode = wrapperNode.closest('.query-field'); + if (!editorNode || this.state.value.isBlurred) { + // Not inside this editor + return; + } + + const range = selection.getRangeAt(0); + const text = selection.anchorNode.textContent; + const offset = range.startOffset; + const prefix = cleanText(text.substr(0, offset)); + + // Determine candidates by context + const suggestionGroups = []; + const wrapperClasses = wrapperNode.classList; + let typeaheadContext = null; + + // Take first metric as lucky guess + const metricNode = editorNode.querySelector('.metric'); + + if (wrapperClasses.contains('context-range')) { + // Rate ranges + typeaheadContext = 'context-range'; + suggestionGroups.push({ + label: 'Range vector', + items: [...RATE_RANGES], + }); + } else if (wrapperClasses.contains('context-labels') && metricNode) { + const metric = metricNode.textContent; + const labelKeys = this.state.labelKeys[metric]; + if (labelKeys) { + if ((text && text.startsWith('=')) || wrapperClasses.contains('attr-value')) { + // Label values + const labelKeyNode = getPreviousCousin(wrapperNode, '.attr-name'); + if (labelKeyNode) { + const labelKey = labelKeyNode.textContent; + const labelValues = this.state.labelValues[metric][labelKey]; + typeaheadContext = 'context-label-values'; + suggestionGroups.push({ + label: 'Label values', + items: labelValues, + }); + } + } else { + // Label keys + typeaheadContext = 'context-labels'; + suggestionGroups.push({ label: 'Labels', items: labelKeys }); + } + } else { + this.fetchMetricLabels(metric); + } + } else if (wrapperClasses.contains('context-labels') && !metricNode) { + // Empty name queries + const defaultKeys = ['job', 'instance']; + // Munge all keys that we have seen together + const labelKeys = Object.keys(this.state.labelKeys).reduce((acc, metric) => { + return acc.concat(this.state.labelKeys[metric].filter(key => acc.indexOf(key) === -1)); + }, defaultKeys); + if ((text && text.startsWith('=')) || wrapperClasses.contains('attr-value')) { + // Label values + const labelKeyNode = getPreviousCousin(wrapperNode, '.attr-name'); + if (labelKeyNode) { + const labelKey = labelKeyNode.textContent; + if (this.state.labelValues[EMPTY_METRIC]) { + const labelValues = this.state.labelValues[EMPTY_METRIC][labelKey]; + typeaheadContext = 'context-label-values'; + suggestionGroups.push({ + label: 'Label values', + items: labelValues, + }); + } else { + // Can only query label values for now (API to query keys is under development) + this.fetchLabelValues(labelKey); + } + } + } else { + // Label keys + typeaheadContext = 'context-labels'; + suggestionGroups.push({ label: 'Labels', items: labelKeys }); + } + } else if (metricNode && wrapperClasses.contains('context-aggregation')) { + typeaheadContext = 'context-aggregation'; + const metric = metricNode.textContent; + const labelKeys = this.state.labelKeys[metric]; + if (labelKeys) { + suggestionGroups.push({ label: 'Labels', items: labelKeys }); + } else { + this.fetchMetricLabels(metric); + } + } else if ( + (this.state.metrics && ((prefix && !wrapperClasses.contains('token')) || text.match(/[+\-*/^%]/))) || + wrapperClasses.contains('context-function') + ) { + // Need prefix for metrics + typeaheadContext = 'context-metrics'; + suggestionGroups.push({ + label: 'Metrics', + items: this.state.metrics, + }); + } + + let results = 0; + const filteredSuggestions = suggestionGroups.map(group => { + if (group.items) { + group.items = group.items.filter(c => c.length !== prefix.length && c.indexOf(prefix) > -1); + results += group.items.length; + } + return group; + }); + + console.log('handleTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext); + + this.setState({ + typeaheadPrefix: prefix, + typeaheadContext, + typeaheadText: text, + suggestions: results > 0 ? filteredSuggestions : [], + }); + } + }, TYPEAHEAD_DEBOUNCE); + + applyTypeahead(change, suggestion) { + const { typeaheadPrefix, typeaheadContext, typeaheadText } = this.state; + + // Modify suggestion based on context + switch (typeaheadContext) { + case 'context-labels': { + const nextChar = getNextCharacter(); + if (!nextChar || nextChar === '}' || nextChar === ',') { + suggestion += '='; + } + break; + } + + case 'context-label-values': { + // Always add quotes and remove existing ones instead + if (!(typeaheadText.startsWith('="') || typeaheadText.startsWith('"'))) { + suggestion = `"${suggestion}`; + } + if (getNextCharacter() !== '"') { + suggestion = `${suggestion}"`; + } + break; + } + + default: + } + + this.resetTypeahead(); + + // Remove the current, incomplete text and replace it with the selected suggestion + let backward = typeaheadPrefix.length; + const text = cleanText(typeaheadText); + const suffixLength = text.length - typeaheadPrefix.length; + const offset = typeaheadText.indexOf(typeaheadPrefix); + const midWord = typeaheadPrefix && ((suffixLength > 0 && offset > -1) || suggestion === typeaheadText); + const forward = midWord ? suffixLength + offset : 0; + + return ( + change + // TODO this line breaks if cursor was moved left and length is longer than whole prefix + .deleteBackward(backward) + .deleteForward(forward) + .insertText(suggestion) + .focus() + ); + } + + onKeyDown = (event, change) => { + if (this.menuEl) { + const { typeaheadIndex, suggestions } = this.state; + + switch (event.key) { + case 'Escape': { + if (this.menuEl) { + event.preventDefault(); + this.resetTypeahead(); + return true; + } + break; + } + + case 'Tab': { + // Dont blur input + event.preventDefault(); + if (!suggestions || suggestions.length === 0) { + return undefined; + } + + // Get the currently selected suggestion + const flattenedSuggestions = flattenSuggestions(suggestions); + const selected = Math.abs(typeaheadIndex); + const selectedIndex = selected % flattenedSuggestions.length || 0; + const suggestion = flattenedSuggestions[selectedIndex]; + + this.applyTypeahead(change, suggestion); + return true; + } + + case 'ArrowDown': { + // Select next suggestion + event.preventDefault(); + this.setState({ typeaheadIndex: typeaheadIndex + 1 }); + break; + } + + case 'ArrowUp': { + // Select previous suggestion + event.preventDefault(); + this.setState({ typeaheadIndex: Math.max(0, typeaheadIndex - 1) }); + break; + } + + default: { + // console.log('default key', event.key, event.which, event.charCode, event.locale, data.key); + break; + } + } + } + return undefined; + }; + + resetTypeahead = () => { + this.setState({ + suggestions: [], + typeaheadIndex: 0, + typeaheadPrefix: '', + typeaheadContext: null, + }); + }; + + async fetchLabelValues(key) { + const url = `/api/v1/label/${key}/values`; + try { + const res = await this.request(url); + const body = await (res.data || res.json()); + const pairs = this.state.labelValues[EMPTY_METRIC]; + const values = { + ...pairs, + [key]: body.data, + }; + // const labelKeys = { + // ...this.state.labelKeys, + // [EMPTY_METRIC]: keys, + // }; + const labelValues = { + ...this.state.labelValues, + [EMPTY_METRIC]: values, + }; + this.setState({ labelValues }, this.handleTypeahead); + } catch (e) { + if (this.props.onRequestError) { + this.props.onRequestError(e); + } else { + console.error(e); + } + } + } + + async fetchMetricLabels(name) { + const url = `/api/v1/series?match[]=${name}`; + try { + const res = await this.request(url); + const body = await (res.data || res.json()); + const { keys, values } = processLabels(body.data); + const labelKeys = { + ...this.state.labelKeys, + [name]: keys, + }; + const labelValues = { + ...this.state.labelValues, + [name]: values, + }; + this.setState({ labelKeys, labelValues }, this.handleTypeahead); + } catch (e) { + if (this.props.onRequestError) { + this.props.onRequestError(e); + } else { + console.error(e); + } + } + } + + async fetchMetricNames() { + const url = '/api/v1/label/__name__/values'; + try { + const res = await this.request(url); + const body = await (res.data || res.json()); + this.setState({ metrics: body.data }, this.onMetricsReceived); + } catch (error) { + if (this.props.onRequestError) { + this.props.onRequestError(error); + } else { + console.error(error); + } + } + } + + handleBlur = () => { + const { onBlur } = this.props; + // If we dont wait here, menu clicks wont work because the menu + // will be gone. + this.resetTimer = setTimeout(this.resetTypeahead, 100); + if (onBlur) { + onBlur(); + } + }; + + handleFocus = () => { + const { onFocus } = this.props; + if (onFocus) { + onFocus(); + } + }; + + handleClickMenu = item => { + // Manually triggering change + const change = this.applyTypeahead(this.state.value.change(), item); + this.onChange(change); + }; + + updateMenu = () => { + const { suggestions } = this.state; + const menu = this.menuEl; + const selection = window.getSelection(); + const node = selection.anchorNode; + + // No menu, nothing to do + if (!menu) { + return; + } + + // No suggestions or blur, remove menu + const hasSuggesstions = suggestions && suggestions.length > 0; + if (!hasSuggesstions) { + menu.removeAttribute('style'); + return; + } + + // Align menu overlay to editor node + if (node) { + const rect = node.parentElement.getBoundingClientRect(); + menu.style.opacity = 1; + menu.style.top = `${rect.top + window.scrollY + rect.height + 4}px`; + menu.style.left = `${rect.left + window.scrollX - 2}px`; + } + }; + + menuRef = el => { + this.menuEl = el; + }; + + renderMenu = () => { + const { suggestions } = this.state; + const hasSuggesstions = suggestions && suggestions.length > 0; + if (!hasSuggesstions) { + return null; + } + + // Guard selectedIndex to be within the length of the suggestions + let selectedIndex = Math.max(this.state.typeaheadIndex, 0); + const flattenedSuggestions = flattenSuggestions(suggestions); + selectedIndex = selectedIndex % flattenedSuggestions.length || 0; + const selectedKeys = flattenedSuggestions.length > 0 ? [flattenedSuggestions[selectedIndex]] : []; + + // Create typeahead in DOM root so we can later position it absolutely + return ( + + + + ); + }; + + render() { + return ( +
    + {this.renderMenu()} + +
    + ); + } +} + +export default QueryField; diff --git a/public/app/containers/Explore/Table.tsx b/public/app/containers/Explore/Table.tsx new file mode 100644 index 00000000000..7179a0fc89a --- /dev/null +++ b/public/app/containers/Explore/Table.tsx @@ -0,0 +1,24 @@ +import React, { PureComponent } from 'react'; +// import TableModel from 'app/core/table_model'; + +const EMPTY_TABLE = { + columns: [], + rows: [], +}; + +export default class Table extends PureComponent { + render() { + const { className = '', data } = this.props; + const tableModel = data || EMPTY_TABLE; + return ( +
    + + {tableModel.columns.map(col => )} + + + {tableModel.rows.map((row, i) => {row.map((content, j) => )})} + +
    {col.text}
    {content}
    + ); + } +} diff --git a/public/app/containers/Explore/Typeahead.tsx b/public/app/containers/Explore/Typeahead.tsx new file mode 100644 index 00000000000..4943622fe4e --- /dev/null +++ b/public/app/containers/Explore/Typeahead.tsx @@ -0,0 +1,66 @@ +import React from 'react'; + +function scrollIntoView(el) { + if (!el || !el.offsetParent) { + return; + } + const container = el.offsetParent; + if (el.offsetTop > container.scrollTop + container.offsetHeight || el.offsetTop < container.scrollTop) { + container.scrollTop = el.offsetTop - container.offsetTop; + } +} + +class TypeaheadItem extends React.PureComponent { + el: any; + componentDidUpdate(prevProps) { + if (this.props.isSelected && !prevProps.isSelected) { + scrollIntoView(this.el); + } + } + + getRef = el => { + this.el = el; + }; + + render() { + const { isSelected, label, onClickItem } = this.props; + const className = isSelected ? 'typeahead-item typeahead-item__selected' : 'typeahead-item'; + const onClick = () => onClickItem(label); + return ( +
  • + {label} +
  • + ); + } +} + +class TypeaheadGroup extends React.PureComponent { + render() { + const { items, label, selected, onClickItem } = this.props; + return ( +
  • +
    {label}
    +
      + {items.map(item => ( + -1} label={item} /> + ))} +
    +
  • + ); + } +} + +class Typeahead extends React.PureComponent { + render() { + const { groupedItems, menuRef, selectedItems, onClickItem } = this.props; + return ( +
      + {groupedItems.map(g => ( + + ))} +
    + ); + } +} + +export default Typeahead; diff --git a/public/app/containers/Explore/slate-plugins/braces.test.ts b/public/app/containers/Explore/slate-plugins/braces.test.ts new file mode 100644 index 00000000000..5c9a90ae034 --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/braces.test.ts @@ -0,0 +1,47 @@ +import Plain from 'slate-plain-serializer'; + +import BracesPlugin from './braces'; + +declare global { + interface Window { + KeyboardEvent: any; + } +} + +describe('braces', () => { + const handler = BracesPlugin().onKeyDown; + + it('adds closing braces around empty value', () => { + const change = Plain.deserialize('').change(); + const event = new window.KeyboardEvent('keydown', { key: '(' }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual('()'); + }); + + it('adds closing braces around a value', () => { + const change = Plain.deserialize('foo').change(); + const event = new window.KeyboardEvent('keydown', { key: '(' }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual('(foo)'); + }); + + it('adds closing braces around the following value only', () => { + const change = Plain.deserialize('foo bar ugh').change(); + let event; + event = new window.KeyboardEvent('keydown', { key: '(' }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual('(foo) bar ugh'); + + // Wrap bar + change.move(5); + event = new window.KeyboardEvent('keydown', { key: '(' }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual('(foo) (bar) ugh'); + + // Create empty parens after (bar) + change.move(4); + event = new window.KeyboardEvent('keydown', { key: '(' }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual('(foo) (bar)() ugh'); + }); +}); diff --git a/public/app/containers/Explore/slate-plugins/braces.ts b/public/app/containers/Explore/slate-plugins/braces.ts new file mode 100644 index 00000000000..b92a224d111 --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/braces.ts @@ -0,0 +1,51 @@ +const BRACES = { + '[': ']', + '{': '}', + '(': ')', +}; + +export default function BracesPlugin() { + return { + onKeyDown(event, change) { + const { value } = change; + if (!value.isCollapsed) { + return undefined; + } + + switch (event.key) { + case '{': + case '[': { + event.preventDefault(); + // Insert matching braces + change + .insertText(`${event.key}${BRACES[event.key]}`) + .move(-1) + .focus(); + return true; + } + + case '(': { + event.preventDefault(); + const text = value.anchorText.text; + const offset = value.anchorOffset; + const space = text.indexOf(' ', offset); + const length = space > 0 ? space : text.length; + const forward = length - offset; + // Insert matching braces + change + .insertText(event.key) + .move(forward) + .insertText(BRACES[event.key]) + .move(-1 - forward) + .focus(); + return true; + } + + default: { + break; + } + } + return undefined; + }, + }; +} diff --git a/public/app/containers/Explore/slate-plugins/clear.test.ts b/public/app/containers/Explore/slate-plugins/clear.test.ts new file mode 100644 index 00000000000..28ba371df14 --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/clear.test.ts @@ -0,0 +1,38 @@ +import Plain from 'slate-plain-serializer'; + +import ClearPlugin from './clear'; + +describe('clear', () => { + const handler = ClearPlugin().onKeyDown; + + it('does not change the empty value', () => { + const change = Plain.deserialize('').change(); + const event = new window.KeyboardEvent('keydown', { + key: 'k', + ctrlKey: true, + }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual(''); + }); + + it('clears to the end of the line', () => { + const change = Plain.deserialize('foo').change(); + const event = new window.KeyboardEvent('keydown', { + key: 'k', + ctrlKey: true, + }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual(''); + }); + + it('clears from the middle to the end of the line', () => { + const change = Plain.deserialize('foo bar').change(); + change.move(4); + const event = new window.KeyboardEvent('keydown', { + key: 'k', + ctrlKey: true, + }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual('foo '); + }); +}); diff --git a/public/app/containers/Explore/slate-plugins/clear.ts b/public/app/containers/Explore/slate-plugins/clear.ts new file mode 100644 index 00000000000..5e2789bf544 --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/clear.ts @@ -0,0 +1,22 @@ +// Clears the rest of the line after the caret +export default function ClearPlugin() { + return { + onKeyDown(event, change) { + const { value } = change; + if (!value.isCollapsed) { + return undefined; + } + + if (event.key === 'k' && event.ctrlKey) { + event.preventDefault(); + const text = value.anchorText.text; + const offset = value.anchorOffset; + const length = text.length; + const forward = length - offset; + change.deleteForward(forward); + return true; + } + return undefined; + }, + }; +} diff --git a/public/app/containers/Explore/slate-plugins/newline.ts b/public/app/containers/Explore/slate-plugins/newline.ts new file mode 100644 index 00000000000..cae8af3acb0 --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/newline.ts @@ -0,0 +1,35 @@ +function getIndent(text) { + let offset = text.length - text.trimLeft().length; + if (offset) { + let indent = text[0]; + while (--offset) { + indent += text[0]; + } + return indent; + } + return ''; +} + +export default function NewlinePlugin() { + return { + onKeyDown(event, change) { + const { value } = change; + if (!value.isCollapsed) { + return undefined; + } + + if (event.key === 'Enter' && event.shiftKey) { + event.preventDefault(); + + const { startBlock } = value; + const currentLineText = startBlock.text; + const indent = getIndent(currentLineText); + + return change + .splitBlock() + .insertText(indent) + .focus(); + } + }, + }; +} diff --git a/public/app/containers/Explore/slate-plugins/prism/index.tsx b/public/app/containers/Explore/slate-plugins/prism/index.tsx new file mode 100644 index 00000000000..7c3fa296d8e --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/prism/index.tsx @@ -0,0 +1,122 @@ +import React from 'react'; +import Prism from 'prismjs'; + +import Promql from './promql'; + +Prism.languages.promql = Promql; + +const TOKEN_MARK = 'prism-token'; + +export function configurePrismMetricsTokens(metrics) { + Prism.languages.promql.metric = { + alias: 'variable', + pattern: new RegExp(`(?:^|\\s)(${metrics.join('|')})(?:$|\\s)`), + }; +} + +/** + * Code-highlighting plugin based on Prism and + * https://github.com/ianstormtaylor/slate/blob/master/examples/code-highlighting/index.js + * + * (Adapted to handle nested grammar definitions.) + */ + +export default function PrismPlugin() { + return { + /** + * Render a Slate mark with appropiate CSS class names + * + * @param {Object} props + * @return {Element} + */ + + renderMark(props) { + const { children, mark } = props; + // Only apply spans to marks identified by this plugin + if (mark.type !== TOKEN_MARK) { + return undefined; + } + const className = `token ${mark.data.get('types')}`; + return {children}; + }, + + /** + * Decorate code blocks with Prism.js highlighting. + * + * @param {Node} node + * @return {Array} + */ + + decorateNode(node) { + if (node.type !== 'paragraph') { + return []; + } + + const texts = node.getTexts().toArray(); + const tstring = texts.map(t => t.text).join('\n'); + const grammar = Prism.languages.promql; + const tokens = Prism.tokenize(tstring, grammar); + const decorations = []; + let startText = texts.shift(); + let endText = startText; + let startOffset = 0; + let endOffset = 0; + let start = 0; + + function processToken(token, acc?) { + // Accumulate token types down the tree + const types = `${acc || ''} ${token.type || ''} ${token.alias || ''}`; + + // Add mark for token node + if (typeof token === 'string' || typeof token.content === 'string') { + startText = endText; + startOffset = endOffset; + + const content = typeof token === 'string' ? token : token.content; + const newlines = content.split('\n').length - 1; + const length = content.length - newlines; + const end = start + length; + + let available = startText.text.length - startOffset; + let remaining = length; + + endOffset = startOffset + remaining; + + while (available < remaining) { + endText = texts.shift(); + remaining = length - available; + available = endText.text.length; + endOffset = remaining; + } + + // Inject marks from up the tree (acc) as well + if (typeof token !== 'string' || acc) { + const range = { + anchorKey: startText.key, + anchorOffset: startOffset, + focusKey: endText.key, + focusOffset: endOffset, + marks: [{ type: TOKEN_MARK, data: { types } }], + }; + + decorations.push(range); + } + + start = end; + } else if (token.content && token.content.length) { + // Tokens can be nested + for (const subToken of token.content) { + processToken(subToken, types); + } + } + } + + // Process top-level tokens + for (const token of tokens) { + processToken(token); + } + + return decorations; + }, + }; +} diff --git a/public/app/containers/Explore/slate-plugins/prism/promql.ts b/public/app/containers/Explore/slate-plugins/prism/promql.ts new file mode 100644 index 00000000000..0f0be18cb6f --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/prism/promql.ts @@ -0,0 +1,123 @@ +export const OPERATORS = ['by', 'group_left', 'group_right', 'ignoring', 'on', 'offset', 'without']; + +const AGGREGATION_OPERATORS = [ + 'sum', + 'min', + 'max', + 'avg', + 'stddev', + 'stdvar', + 'count', + 'count_values', + 'bottomk', + 'topk', + 'quantile', +]; + +export const FUNCTIONS = [ + ...AGGREGATION_OPERATORS, + 'abs', + 'absent', + 'ceil', + 'changes', + 'clamp_max', + 'clamp_min', + 'count_scalar', + 'day_of_month', + 'day_of_week', + 'days_in_month', + 'delta', + 'deriv', + 'drop_common_labels', + 'exp', + 'floor', + 'histogram_quantile', + 'holt_winters', + 'hour', + 'idelta', + 'increase', + 'irate', + 'label_replace', + 'ln', + 'log2', + 'log10', + 'minute', + 'month', + 'predict_linear', + 'rate', + 'resets', + 'round', + 'scalar', + 'sort', + 'sort_desc', + 'sqrt', + 'time', + 'vector', + 'year', + 'avg_over_time', + 'min_over_time', + 'max_over_time', + 'sum_over_time', + 'count_over_time', + 'quantile_over_time', + 'stddev_over_time', + 'stdvar_over_time', +]; + +const tokenizer = { + comment: { + pattern: /(^|[^\n])#.*/, + lookbehind: true, + }, + 'context-aggregation': { + pattern: /((by|without)\s*)\([^)]*\)/, // by () + lookbehind: true, + inside: { + 'label-key': { + pattern: /[^,\s][^,]*[^,\s]*/, + alias: 'attr-name', + }, + }, + }, + 'context-labels': { + pattern: /\{[^}]*(?=})/, + inside: { + 'label-key': { + pattern: /[a-z_]\w*(?=\s*(=|!=|=~|!~))/, + alias: 'attr-name', + }, + 'label-value': { + pattern: /"(?:\\.|[^\\"])*"/, + greedy: true, + alias: 'attr-value', + }, + }, + }, + function: new RegExp(`\\b(?:${FUNCTIONS.join('|')})(?=\\s*\\()`, 'i'), + 'context-range': [ + { + pattern: /\[[^\]]*(?=])/, // [1m] + inside: { + 'range-duration': { + pattern: /\b\d+[smhdwy]\b/i, + alias: 'number', + }, + }, + }, + { + pattern: /(offset\s+)\w+/, // offset 1m + lookbehind: true, + inside: { + 'range-duration': { + pattern: /\b\d+[smhdwy]\b/i, + alias: 'number', + }, + }, + }, + ], + number: /\b-?\d+((\.\d*)?([eE][+-]?\d+)?)?\b/, + operator: new RegExp(`/[-+*/=%^~]|&&?|\\|?\\||!=?|<(?:=>?|<|>)?|>[>=]?|\\b(?:${OPERATORS.join('|')})\\b`, 'i'), + punctuation: /[{};()`,.]/, +}; + +export default tokenizer; diff --git a/public/app/containers/Explore/slate-plugins/runner.ts b/public/app/containers/Explore/slate-plugins/runner.ts new file mode 100644 index 00000000000..44b5943c4a2 --- /dev/null +++ b/public/app/containers/Explore/slate-plugins/runner.ts @@ -0,0 +1,14 @@ +export default function RunnerPlugin({ handler }) { + return { + onKeyDown(event) { + // Handle enter + if (handler && event.key === 'Enter' && !event.shiftKey) { + // Submit on Enter + event.preventDefault(); + handler(event); + return true; + } + return undefined; + }, + }; +} diff --git a/public/app/containers/Explore/utils/debounce.ts b/public/app/containers/Explore/utils/debounce.ts new file mode 100644 index 00000000000..9f2bd35e116 --- /dev/null +++ b/public/app/containers/Explore/utils/debounce.ts @@ -0,0 +1,14 @@ +// Based on underscore.js debounce() +export default function debounce(func, wait) { + let timeout; + return function() { + const context = this; + const args = arguments; + const later = function() { + timeout = null; + func.apply(context, args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} diff --git a/public/app/containers/Explore/utils/dom.ts b/public/app/containers/Explore/utils/dom.ts new file mode 100644 index 00000000000..6ba21b54c83 --- /dev/null +++ b/public/app/containers/Explore/utils/dom.ts @@ -0,0 +1,40 @@ +// Node.closest() polyfill +if ('Element' in window && !Element.prototype.closest) { + Element.prototype.closest = function(s) { + const matches = (this.document || this.ownerDocument).querySelectorAll(s); + let el = this; + let i; + // eslint-disable-next-line + do { + i = matches.length; + // eslint-disable-next-line + while (--i >= 0 && matches.item(i) !== el) {} + } while (i < 0 && (el = el.parentElement)); + return el; + }; +} + +export function getPreviousCousin(node, selector) { + let sibling = node.parentElement.previousSibling; + let el; + while (sibling) { + el = sibling.querySelector(selector); + if (el) { + return el; + } + sibling = sibling.previousSibling; + } + return undefined; +} + +export function getNextCharacter(global = window) { + const selection = global.getSelection(); + if (!selection.anchorNode) { + return null; + } + + const range = selection.getRangeAt(0); + const text = selection.anchorNode.textContent; + const offset = range.startOffset; + return text.substr(offset, 1); +} diff --git a/public/app/containers/Explore/utils/prometheus.ts b/public/app/containers/Explore/utils/prometheus.ts new file mode 100644 index 00000000000..30f9c25b8f7 --- /dev/null +++ b/public/app/containers/Explore/utils/prometheus.ts @@ -0,0 +1,20 @@ +export const RATE_RANGES = ['1m', '5m', '10m', '30m', '1h']; + +export function processLabels(labels) { + const values = {}; + labels.forEach(l => { + const { __name__, ...rest } = l; + Object.keys(rest).forEach(key => { + if (!values[key]) { + values[key] = []; + } + if (values[key].indexOf(rest[key]) === -1) { + values[key].push(rest[key]); + } + }); + }); + return { values, keys: Object.keys(values) }; +} + +// Strip syntax chars +export const cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index 4f4b3a64fa5..89f25776a40 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -8,11 +8,23 @@ import appEvents from 'app/core/app_events'; import Drop from 'tether-drop'; import { createStore } from 'app/stores/store'; import colors from 'app/core/utils/colors'; +import { BackendSrv } from 'app/core/services/backend_srv'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; export class GrafanaCtrl { /** @ngInject */ - constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, bridgeSrv, backendSrv) { - createStore(backendSrv); + constructor( + $scope, + alertSrv, + utilSrv, + $rootScope, + $controller, + contextSrv, + bridgeSrv, + backendSrv: BackendSrv, + datasourceSrv: DatasourceSrv + ) { + createStore({ backendSrv, datasourceSrv }); $scope.init = function() { $scope.contextSrv = contextSrv; diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index fb7a9ece37a..aef43a4760b 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -15,7 +15,7 @@ export class DatasourceSrv { this.datasources = {}; } - get(name) { + get(name?) { if (!name) { return this.get(config.defaultDatasource); } diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx index b7613d9474d..deb16f68bf7 100644 --- a/public/app/routes/ReactContainer.tsx +++ b/public/app/routes/ReactContainer.tsx @@ -1,8 +1,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { Provider } from 'mobx-react'; + import coreModule from 'app/core/core_module'; import { store } from 'app/stores/store'; -import { Provider } from 'mobx-react'; +import { BackendSrv } from 'app/core/services/backend_srv'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; function WrapInProvider(store, Component, props) { return ( @@ -13,14 +16,15 @@ function WrapInProvider(store, Component, props) { } /** @ngInject */ -export function reactContainer($route, $location, backendSrv) { +export function reactContainer($route, $location, backendSrv: BackendSrv, datasourceSrv: DatasourceSrv) { return { restrict: 'E', template: '', link(scope, elem) { - let component = $route.current.locals.component; + let component = $route.current.locals.component.default; let props = { backendSrv: backendSrv, + datasourceSrv: datasourceSrv, }; ReactDOM.render(WrapInProvider(store, component, props), elem[0]); diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index d9732256c2b..49690561728 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -1,7 +1,9 @@ import './dashboard_loaders'; import './ReactContainer'; + import ServerStats from 'app/containers/ServerStats/ServerStats'; import AlertRuleList from 'app/containers/AlertRuleList/AlertRuleList'; +// import Explore from 'app/containers/Explore/Explore'; import FolderSettings from 'app/containers/ManageDashboards/FolderSettings'; import FolderPermissions from 'app/containers/ManageDashboards/FolderPermissions'; @@ -109,6 +111,12 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { controller: 'FolderDashboardsCtrl', controllerAs: 'ctrl', }) + .when('/explore', { + template: '', + resolve: { + component: () => import(/* webpackChunkName: "explore" */ 'app/containers/Explore/Explore'), + }, + }) .when('/org', { templateUrl: 'public/app/features/org/partials/orgDetails.html', controller: 'OrgDetailsCtrl', diff --git a/public/app/stores/store.ts b/public/app/stores/store.ts index 8ad53607ac2..dfbd8141198 100644 --- a/public/app/stores/store.ts +++ b/public/app/stores/store.ts @@ -3,11 +3,11 @@ import config from 'app/core/config'; export let store: IRootStore; -export function createStore(backendSrv) { +export function createStore(services) { store = RootStore.create( {}, { - backendSrv: backendSrv, + ...services, navTree: config.bootData.navTree, } ); diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 36072fe8929..afc869f8b15 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -104,5 +104,6 @@ @import 'pages/signup'; @import 'pages/styleguide'; @import 'pages/errorpage'; +@import 'pages/explore'; @import 'old_responsive'; @import 'components/view_states.scss'; diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index c80d461541e..faa5b94d4ad 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -23,6 +23,13 @@ @include clearfix(); } +.page-full { + margin-left: $page-sidebar-margin; + padding-left: $spacer; + padding-right: $spacer; + @include clearfix(); +} + .scroll-canvas { position: absolute; width: 100%; diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss new file mode 100644 index 00000000000..4bd0162563b --- /dev/null +++ b/public/sass/pages/_explore.scss @@ -0,0 +1,304 @@ +.explore { + .graph-legend { + flex-wrap: wrap; + } +} + +.query-field { + font-size: 14px; + font-family: Consolas, Menlo, Courier, monospace; + height: auto; +} + +.query-field-wrapper { + position: relative; + display: inline-block; + padding: 6px 7px 4px; + width: calc(100% - 6rem); + cursor: text; + line-height: 1.5; + color: rgba(0, 0, 0, 0.65); + background-color: #fff; + background-image: none; + border: 1px solid lightgray; + border-radius: 4px; + transition: all 0.3s; +} + +.typeahead { + position: absolute; + z-index: auto; + top: -10000px; + left: -10000px; + opacity: 0; + border-radius: 4px; + transition: opacity 0.75s; + border: 1px solid #e4e4e4; + max-height: calc(66vh); + overflow-y: scroll; + max-width: calc(66%); + overflow-x: hidden; + outline: none; + list-style: none; + background: #fff; + color: rgba(0, 0, 0, 0.65); + transition: opacity 0.4s ease-out; +} + +.typeahead-group__title { + color: rgba(0, 0, 0, 0.43); + font-size: 12px; + line-height: 1.5; + padding: 8px 16px; +} + +.typeahead-item { + line-height: 200%; + height: auto; + font-family: Consolas, Menlo, Courier, monospace; + padding: 0 16px 0 28px; + font-size: 12px; + text-overflow: ellipsis; + overflow: hidden; + margin-left: -1px; + left: 1px; + position: relative; + z-index: 1; + display: block; + white-space: nowrap; + cursor: pointer; + transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), + background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); +} + +.typeahead-item__selected { + background-color: #ecf6fd; + color: #108ee9; +} + +/* SYNTAX */ + +/** + * prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML + * Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics); + * @author Tim Shedor + */ + +code[class*='language-'], +pre[class*='language-'] { + color: black; + background: none; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*='language-'] { + position: relative; + margin: 0.5em 0; + overflow: visible; + padding: 0; +} +pre[class*='language-'] > code { + position: relative; + border-left: 10px solid #358ccb; + box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; + background-color: #fdfdfd; + background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); + background-size: 3em 3em; + background-origin: content-box; + background-attachment: local; +} + +code[class*='language'] { + max-height: inherit; + height: inherit; + padding: 0 1em; + display: block; + overflow: auto; +} + +/* Margin bottom to accomodate shadow */ +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background-color: #fdfdfd; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-bottom: 1em; +} + +/* Inline code */ +:not(pre) > code[class*='language-'] { + position: relative; + padding: 0.2em; + border-radius: 0.3em; + color: #c92c2c; + border: 1px solid rgba(0, 0, 0, 0.1); + display: inline; + white-space: normal; +} + +pre[class*='language-']:before, +pre[class*='language-']:after { + content: ''; + z-index: -2; + display: block; + position: absolute; + bottom: 0.75em; + left: 0.18em; + width: 40%; + height: 20%; + max-height: 13em; + box-shadow: 0px 13px 8px #979797; + -webkit-transform: rotate(-2deg); + -moz-transform: rotate(-2deg); + -ms-transform: rotate(-2deg); + -o-transform: rotate(-2deg); + transform: rotate(-2deg); +} + +:not(pre) > code[class*='language-']:after, +pre[class*='language-']:after { + right: 0.75em; + left: auto; + -webkit-transform: rotate(2deg); + -moz-transform: rotate(2deg); + -ms-transform: rotate(2deg); + -o-transform: rotate(2deg); + transform: rotate(2deg); +} + +.token.comment, +.token.block-comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #7d8b99; +} + +.token.punctuation { + color: #5f6364; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.function-name, +.token.constant, +.token.symbol, +.token.deleted { + color: #c92c2c; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.function, +.token.builtin, +.token.inserted { + color: #2f9c0a; +} + +.token.operator, +.token.entity, +.token.url, +.token.variable { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword, +.token.class-name { + color: #1990b8; +} + +.token.regex, +.token.important { + color: #e90; +} + +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); +} + +.token.important { + font-weight: normal; +} + +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.namespace { + opacity: 0.7; +} + +@media screen and (max-width: 767px) { + pre[class*='language-']:before, + pre[class*='language-']:after { + bottom: 14px; + box-shadow: none; + } +} + +/* Plugin styles */ +.token.tab:not(:empty):before, +.token.cr:before, +.token.lf:before { + color: #e0d7d1; +} + +/* Plugin styles: Line Numbers */ +pre[class*='language-'].line-numbers { + padding-left: 0; +} + +pre[class*='language-'].line-numbers code { + padding-left: 3.8em; +} + +pre[class*='language-'].line-numbers .line-numbers-rows { + left: 0; +} + +/* Plugin styles: Line Highlight */ +pre[class*='language-'][data-line] { + padding-top: 0; + padding-bottom: 0; + padding-left: 0; +} +pre[data-line] code { + position: relative; + padding-left: 4em; +} +pre .line-highlight { + margin-top: 0; +} diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index ab06967364a..26af661bf9d 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -71,6 +71,7 @@ module.exports = merge(common, { loader: 'babel-loader', options: { plugins: [ + 'syntax-dynamic-import', 'react-hot-loader/babel', ], }, diff --git a/scripts/webpack/webpack.prod.js b/scripts/webpack/webpack.prod.js index f55de9ec5b3..c01a45adc03 100644 --- a/scripts/webpack/webpack.prod.js +++ b/scripts/webpack/webpack.prod.js @@ -36,7 +36,12 @@ module.exports = merge(common, { test: /\.tsx?$/, exclude: /node_modules/, use: [ - { loader: "awesome-typescript-loader" } + { + loader: 'awesome-typescript-loader', + options: { + errorsAsWarnings: false, + }, + }, ] }, require('./sass.rule.js')({ diff --git a/yarn.lock b/yarn.lock index 35287b2fc39..f23d44867f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,24 +3,30 @@ "@babel/code-frame@^7.0.0-beta.35": - version "7.0.0-beta.36" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.36.tgz#2349d7ec04b3a06945ae173280ef8579b63728e4" + version "7.0.0-beta.46" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.46.tgz#e0d002100805daab1461c0fcb32a07e304f3a4f4" + dependencies: + "@babel/highlight" "7.0.0-beta.46" + +"@babel/highlight@7.0.0-beta.46": + version "7.0.0-beta.46" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.46.tgz#c553c51e65f572bdedd6eff66fc0bb563016645e" dependencies: chalk "^2.0.0" esutils "^2.0.2" js-tokens "^3.0.0" "@types/cheerio@*": - version "0.22.5" - resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.5.tgz#db749e8470d98f103d51407db9bee5a8b9d20d45" + version "0.22.7" + resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.7.tgz#4a92eafedfb2b9f4437d3a4410006d81114c66ce" "@types/d3-array@*": version "1.2.1" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.1.tgz#e489605208d46a1c9d980d2e5772fa9c75d9ec65" "@types/d3-axis@*": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-1.0.9.tgz#62ce7bc8d04354298cda57f3f1d1f856ad69b89a" + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-1.0.10.tgz#41d6b3ea9032f9531ec0d71d83bcf49294511210" dependencies: "@types/d3-selection" "*" @@ -35,12 +41,12 @@ resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-1.0.6.tgz#0589eb97a3191f4edaf17b7bde498462890ce1ec" "@types/d3-collection@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.5.tgz#bb1f3aa97cdc8d881645541b9d6cf87edfee9bc3" + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.6.tgz#0a5a87fe241fcbd253a637d024b4d8c55f84a369" "@types/d3-color@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.0.5.tgz#cad755f0fc6de7b70fa6e5e08afa81ef4c2248de" + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.0.6.tgz#6e955f739c3f92bf94e9e3a8cfa2806734244b60" "@types/d3-dispatch@*": version "1.0.5" @@ -65,12 +71,12 @@ resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-1.1.0.tgz#40925ca3512b63bd424f7c9685e1781b5b0a1d7e" "@types/d3-format@*": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.2.1.tgz#9435fb1771d2fbf6a858c93218f4097c9aa396c1" + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.2.2.tgz#bc60b936bd3cc805225ab4423081eb218e6d1db0" "@types/d3-geo@*": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-1.9.3.tgz#742ceafa808c6853affccfb11f956cfc8bdccecb" + version "1.10.1" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-1.10.1.tgz#a70732541adde9d2cfcf705ff58622cf4a6819e3" dependencies: "@types/geojson" "*" @@ -110,19 +116,19 @@ dependencies: "@types/d3-dsv" "*" -"@types/d3-scale@*": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-1.0.10.tgz#8c5c1dca54a159eed042b46719dbb3bdb7e8c842" +"@types/d3-scale@^1": + version "1.0.12" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-1.0.12.tgz#f6300e886ce38dc8834172a9ce4a2cbfad74d029" dependencies: "@types/d3-time" "*" "@types/d3-selection@*": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-1.2.0.tgz#f0a4cca0a0e4187c336c6712a82600cdcd24093f" + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-1.3.0.tgz#acede3d22c18ec085cc401d4fdab9f040e1a73c7" "@types/d3-shape@*": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.2.1.tgz#cac2d9f0122f173220c32c8c152dc42ee9349df2" + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.2.2.tgz#f8dcdff7772a7ae37858bf04abd43848a78e590e" dependencies: "@types/d3-path" "*" @@ -156,8 +162,8 @@ "@types/d3-selection" "*" "@types/d3@^4.10.1": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@types/d3/-/d3-4.12.0.tgz#445ede4ab7707db1a011ef43b2bd187d21bdaffc" + version "4.13.0" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-4.13.0.tgz#aae092b368266409cfbf19c611203145ec0d5f65" dependencies: "@types/d3-array" "*" "@types/d3-axis" "*" @@ -180,7 +186,7 @@ "@types/d3-queue" "*" "@types/d3-random" "*" "@types/d3-request" "*" - "@types/d3-scale" "*" + "@types/d3-scale" "^1" "@types/d3-selection" "*" "@types/d3-shape" "*" "@types/d3-time" "*" @@ -198,31 +204,33 @@ "@types/react" "*" "@types/geojson@*": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.6.tgz#3e02972728c69248c2af08d60a48cbb8680fffdf" + version "7946.0.2" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.2.tgz#0bd0a01ef04e813c2b7580318da9e37c2eadea9c" "@types/jest@^21.1.4": - version "21.1.8" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-21.1.8.tgz#d497213725684f1e5a37900b17a47c9c018f1a97" + version "21.1.10" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-21.1.10.tgz#dcacb5217ddf997a090cc822bba219b4b2fd7984" "@types/node@*": - version "8.5.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.2.tgz#83b8103fa9a2c2e83d78f701a9aa7c9539739aa5" + version "9.6.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.6.tgz#439b91f9caf3983cad2eef1e11f6bedcbf9431d2" "@types/node@^8.0.31": - version "8.0.53" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8" + version "8.10.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.10.tgz#fec07bc2ad549d9e6d2f7aa0fb0be3491b83163a" "@types/react-dom@^16.0.3": - version "16.0.3" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.3.tgz#8accad7eabdab4cca3e1a56f5ccb57de2da0ff64" + version "16.0.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.5.tgz#a757457662e3819409229e8f86795ff37b371f96" dependencies: "@types/node" "*" "@types/react" "*" "@types/react@*", "@types/react@^16.0.25": - version "16.0.25" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.25.tgz#bf696b83fe480c5e0eff4335ee39ebc95884a1ed" + version "16.3.12" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.3.12.tgz#68d9146f3e9797e38ffbf22f7ed1dde91a2cfd2e" + dependencies: + csstype "^2.2.0" "@types/tapable@^0": version "0.2.5" @@ -235,26 +243,26 @@ source-map "^0.6.1" "@types/webpack@^3.0.5": - version "3.8.11" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-3.8.11.tgz#df2d7f8db43dbc15b4e8ecbdc91e6f68ed6b83ab" + version "3.8.12" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-3.8.12.tgz#c5db4f273fb8f2a4929db6c486e19e68c350e7ac" dependencies: "@types/node" "*" "@types/tapable" "^0" "@types/uglify-js" "*" source-map "^0.6.0" -JSONStream@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a" +JSONStream@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea" dependencies: jsonparse "^1.2.0" through ">=2.2.7 <3" -"JSV@>= 4.0.x": +JSV@^4.0.x: version "4.0.2" resolved "https://registry.yarnpkg.com/JSV/-/JSV-4.0.2.tgz#d077f6825571f82132f9dffaed587b4029feff57" -abab@^1.0.3: +abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -269,14 +277,7 @@ accepts@1.3.3: mime-types "~2.1.11" negotiator "0.6.1" -accepts@~1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" - dependencies: - mime-types "~2.1.16" - negotiator "0.6.1" - -accepts@~1.3.5: +accepts@~1.3.4, accepts@~1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" dependencies: @@ -293,7 +294,7 @@ acorn-es7-plugin@^1.0.12: version "1.1.7" resolved "https://registry.yarnpkg.com/acorn-es7-plugin/-/acorn-es7-plugin-1.1.7.tgz#f2ee1f3228a90eead1245f9ab1922eb2e71d336b" -acorn-globals@^4.0.0: +acorn-globals@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.1.0.tgz#ab716025dbe17c54d3ef81d32ece2b2d99fe2538" dependencies: @@ -313,13 +314,9 @@ acorn@^4.0.0, acorn@^4.0.3: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" -acorn@^5.0.0, acorn@^5.1.1, acorn@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" - -acorn@^5.1.2: - version "5.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" +acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0: + version "5.5.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" acorn@~2.6.4: version "2.6.4" @@ -330,14 +327,14 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" agent-base@4, agent-base@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.1.2.tgz#80fa6cde440f4dcf9af2617cf246099b5d99f0c8" + version "4.2.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" dependencies: es6-promisify "^5.0.0" agentkeepalive@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.3.0.tgz#6d5de5829afd3be2712201a39275fd11c651857c" + version "3.4.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.4.1.tgz#aa95aebc3a749bca5ed53e3880a09f5235b48f0c" dependencies: humanize-ms "^1.2.1" @@ -345,24 +342,20 @@ ajv-keywords@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" -ajv-keywords@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" - ajv-keywords@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be" -ajv@^4.7.0, ajv@^4.9.1: +ajv@^4.7.0: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" dependencies: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5: - version "5.5.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.0.tgz#eb2840746e9dc48bd5e063a36e3fd400c5eab5a9" +ajv@^5.0.0, ajv@^5.1.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: co "^4.6.0" fast-deep-equal "^1.0.0" @@ -405,24 +398,24 @@ angular-bindonce@^0.3.1: resolved "https://registry.yarnpkg.com/angular-bindonce/-/angular-bindonce-0.3.1.tgz#af19574abd43f608b9236a302cc5ce49d71dc9c6" angular-mocks@^1.6.6: - version "1.6.7" - resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.6.7.tgz#85bf45a2537eac59fc6f4cf319846102e8000e65" + version "1.6.10" + resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.6.10.tgz#6a139e43c461d0c9a5a1acebc91e63db16031176" angular-native-dragdrop@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/angular-native-dragdrop/-/angular-native-dragdrop-1.2.2.tgz#d646c6b75b131c48073c3f6e36a225b2726d8bae" angular-route@^1.6.6: - version "1.6.7" - resolved "https://registry.yarnpkg.com/angular-route/-/angular-route-1.6.7.tgz#020970d93d8b2ce4ca6aff0e0d7922579543cbcf" + version "1.6.10" + resolved "https://registry.yarnpkg.com/angular-route/-/angular-route-1.6.10.tgz#4247a32eab19495624623e96c1626dfba17ebf21" angular-sanitize@^1.6.6: - version "1.6.7" - resolved "https://registry.yarnpkg.com/angular-sanitize/-/angular-sanitize-1.6.7.tgz#5a3d61ad7b8b699923329635d99248bcfce26408" + version "1.6.10" + resolved "https://registry.yarnpkg.com/angular-sanitize/-/angular-sanitize-1.6.10.tgz#635a362afb2dd040179f17d3a5455962b2c1918f" angular@^1.6.6: - version "1.6.7" - resolved "https://registry.yarnpkg.com/angular/-/angular-1.6.7.tgz#0f89837dae1776b01ccb1fa2096db0d9373d9897" + version "1.6.10" + resolved "https://registry.yarnpkg.com/angular/-/angular-1.6.10.tgz#eed3080a34d29d0f681ff119b18ce294e3f74826" ansi-align@^2.0.0: version "2.0.0" @@ -435,8 +428,8 @@ ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" ansi-escapes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" ansi-html@0.0.7: version "0.0.7" @@ -454,9 +447,9 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0, ansi-styles@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" dependencies: color-convert "^1.9.0" @@ -541,8 +534,8 @@ are-we-there-yet@~1.1.2: readable-stream "^2.0.6" argparse@^1.0.2, argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" dependencies: sprintf-js "~1.0.2" @@ -649,8 +642,8 @@ asap@^2.0.0, asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" asn1.js@^4.0.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a" + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" dependencies: bn.js "^4.0.0" inherits "^2.0.1" @@ -706,23 +699,19 @@ async@^1.4.0, async@^1.5.2, async@~1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.0.0, async@^2.1.2, async@^2.1.4, async@^2.1.5, async@^2.4.1: +async@^2.0.0, async@^2.1.2, async@^2.1.4, async@^2.4.1: version "2.6.0" resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" dependencies: lodash "^4.14.0" -async@~0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" + version "2.1.0" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.0.tgz#ab2b150e51d7b122b9efc8d7340c06b6c41076bc" autolinker@~0.15.0: version "0.15.3" @@ -740,17 +729,16 @@ autoprefixer@^6.3.1, autoprefixer@^6.4.0: postcss-value-parser "^3.2.3" awesome-typescript-loader@^3.2.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.4.0.tgz#aed2c83af614d617d11e3ec368ac3befb55d002f" + version "3.5.0" + resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.5.0.tgz#4d4d10cba7a04ed433dfa0334250846fb11a1a5a" dependencies: - colors "^1.1.2" + chalk "^2.3.1" enhanced-resolve "3.3.0" loader-utils "^1.1.0" lodash "^4.17.4" micromatch "^3.0.3" mkdirp "^0.5.1" - object-assign "^4.1.1" - source-map-support "^0.4.15" + source-map-support "^0.5.3" aws-sign2@~0.6.0: version "0.6.0" @@ -761,8 +749,8 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" aws4@^1.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" axios@^0.17.1: version "0.17.1" @@ -771,7 +759,7 @@ axios@^0.17.1: follow-redirects "^1.2.5" is-buffer "^1.1.5" -babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -779,7 +767,7 @@ babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@^6.0.0, babel-core@^6.24.1, babel-core@^6.26.0: +babel-core@^6.0.0, babel-core@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" dependencies: @@ -804,8 +792,8 @@ babel-core@^6.0.0, babel-core@^6.24.1, babel-core@^6.26.0: source-map "^0.5.6" babel-generator@^6.18.0, babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" dependencies: babel-messages "^6.23.0" babel-runtime "^6.26.0" @@ -813,7 +801,7 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.17.4" - source-map "^0.5.6" + source-map "^0.5.7" trim-right "^1.0.1" babel-helper-call-delegate@^6.24.1: @@ -891,16 +879,16 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.0.4.tgz#533c46de37d7c9d7612f408c76314be9277e0c26" +babel-jest@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.3.tgz#4b7a0b6041691bbd422ab49b3b73654a49a6627a" dependencies: babel-plugin-istanbul "^4.1.5" - babel-preset-jest "^22.0.3" + babel-preset-jest "^22.4.3" babel-loader@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126" + version "7.1.4" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.4.tgz#e3463938bd4e6d55d1c174c5485d406a188ed015" dependencies: find-cache-dir "^1.0.0" loader-utils "^1.0.2" @@ -919,16 +907,21 @@ babel-plugin-check-es2015-constants@^6.22.0: babel-runtime "^6.22.0" babel-plugin-istanbul@^4.1.4, babel-plugin-istanbul@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" find-up "^2.1.0" - istanbul-lib-instrument "^1.7.5" - test-exclude "^4.1.1" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" -babel-plugin-jest-hoist@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.0.3.tgz#62cde5fe962fd41ae89c119f481ca5cd7dd48bb4" +babel-plugin-jest-hoist@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.3.tgz#7d8bcccadc2667f96a0dcc6afe1891875ee6c14a" + +babel-plugin-syntax-dynamic-import@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" babel-plugin-syntax-object-rest-spread@^6.13.0: version "6.13.0" @@ -1018,7 +1011,7 @@ babel-plugin-transform-es2015-modules-amd@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@^6.24.1: +babel-plugin-transform-es2015-modules-commonjs@^6.24.1, babel-plugin-transform-es2015-modules-commonjs@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" dependencies: @@ -1152,11 +1145,11 @@ babel-preset-es2015@^6.24.1: babel-plugin-transform-es2015-unicode-regex "^6.24.1" babel-plugin-transform-regenerator "^6.24.1" -babel-preset-jest@^22.0.1, babel-preset-jest@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.0.3.tgz#e2bb6f6b4a509d3ea0931f013db78c5a84856693" +babel-preset-jest@^22.4.0, babel-preset-jest@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.3.tgz#e92eef9813b7026ab4ca675799f37419b5a44156" dependencies: - babel-plugin-jest-hoist "^22.0.3" + babel-plugin-jest-hoist "^22.4.3" babel-plugin-syntax-object-rest-spread "^6.13.0" babel-register@^6.26.0: @@ -1171,7 +1164,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2: +babel-runtime@^6.0.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1236,8 +1229,8 @@ base64-arraybuffer@0.1.5: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" base64-js@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" base64id@1.0.0: version "1.0.0" @@ -1279,23 +1272,38 @@ better-assert@~1.0.0: dependencies: callsite "1.0.0" +bfj-node4@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/bfj-node4/-/bfj-node4-5.3.1.tgz#e23d8b27057f1d0214fc561142ad9db998f26830" + dependencies: + bluebird "^3.5.1" + check-types "^7.3.0" + tryer "^1.0.0" + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" +bin-links@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-1.1.2.tgz#fb74bd54bae6b7befc6c6221f25322ac830d9757" + dependencies: + bluebird "^3.5.0" + cmd-shim "^2.0.2" + gentle-fs "^2.0.0" + graceful-fs "^4.1.11" + write-file-atomic "^2.3.0" + binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" -bindings@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" - bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" dependencies: - readable-stream "^2.0.5" + readable-stream "^2.3.5" + safe-buffer "^5.1.1" blob@0.0.4: version "0.0.4" @@ -1307,7 +1315,7 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.0, bluebird@~3.5.0: +bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@~3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -1363,9 +1371,9 @@ boom@5.x.x: dependencies: hoek "4.x.x" -boxen@^1.0.0, boxen@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.2.2.tgz#3f1d4032c30ffea9d4b02c322eaf2ea741dcbce5" +boxen@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" dependencies: ansi-align "^2.0.0" camelcase "^4.0.0" @@ -1373,11 +1381,11 @@ boxen@^1.0.0, boxen@^1.2.1: cli-boxes "^1.0.0" string-width "^2.0.0" term-size "^1.2.0" - widest-line "^1.0.0" + widest-line "^2.0.0" brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" @@ -1402,23 +1410,7 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e" - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^2.3.1: +braces@^2.3.0, braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" dependencies: @@ -1452,8 +1444,8 @@ browser-stdout@1.3.0: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" dependencies: buffer-xor "^1.0.3" cipher-base "^1.0.0" @@ -1463,16 +1455,16 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4: safe-buffer "^5.0.1" browserify-cipher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" dependencies: browserify-aes "^1.0.4" browserify-des "^1.0.0" evp_bytestokey "^1.0.0" browserify-des@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c" dependencies: cipher-base "^1.0.1" des.js "^1.0.0" @@ -1526,6 +1518,10 @@ buffer-crc32@^0.2.1: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-from@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -1554,63 +1550,31 @@ builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" -cacache@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.1.tgz#3e05f6e616117d9b54665b1b20c8aeb93ea5d36f" +cacache@^10.0.0, cacache@^10.0.4: + version "10.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" dependencies: - bluebird "^3.5.0" + bluebird "^3.5.1" chownr "^1.0.1" glob "^7.1.2" graceful-fs "^4.1.11" lru-cache "^4.1.1" - mississippi "^1.3.0" + mississippi "^2.0.0" mkdirp "^0.5.1" move-concurrently "^1.0.1" promise-inflight "^1.0.1" - rimraf "^2.6.1" - ssri "^5.0.0" + rimraf "^2.6.2" + ssri "^5.2.4" unique-filename "^1.1.0" - y18n "^3.2.1" - -cacache@^9.2.9: - version "9.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-9.3.0.tgz#9cd58f2dd0b8c8cacf685b7067b416d6d3cf9db1" - dependencies: - bluebird "^3.5.0" - chownr "^1.0.1" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.1" - mississippi "^1.3.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.1" - ssri "^4.1.6" - unique-filename "^1.1.0" - y18n "^3.2.1" - -cacache@~9.2.9: - version "9.2.9" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-9.2.9.tgz#f9d7ffe039851ec94c28290662afa4dd4bb9e8dd" - dependencies: - bluebird "^3.5.0" - chownr "^1.0.1" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.1" - mississippi "^1.3.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.1" - ssri "^4.1.6" - unique-filename "^1.1.0" - y18n "^3.2.1" + y18n "^4.0.0" cache-base@^1.0.1: version "1.0.1" @@ -1692,8 +1656,8 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000772" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" + version "1.0.30000830" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000830.tgz#6e45255b345649fd15ff59072da1e12bb3de2f13" capture-stack-trace@^1.0.0: version "1.0.0" @@ -1714,10 +1678,6 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chain-function@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" - chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.0, chalk@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1728,13 +1688,13 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.0, chalk@~1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: - ansi-styles "^3.1.0" + ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" - supports-color "^4.0.0" + supports-color "^5.3.0" chalk@~0.4.0: version "0.4.0" @@ -1745,8 +1705,8 @@ chalk@~0.4.0: strip-ansi "~0.1.0" change-case@3.0.x: - version "3.0.1" - resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.1.tgz#ee5f5ad0415ad1ad9e8072cf49cd4cfa7660a554" + version "3.0.2" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.2.tgz#fd48746cce02f03f0a672577d1d3a8dc2eceb037" dependencies: camel-case "^3.0.0" constant-case "^2.0.0" @@ -1756,7 +1716,7 @@ change-case@3.0.x: is-upper-case "^1.1.0" lower-case "^1.1.1" lower-case-first "^1.0.0" - no-case "^2.2.0" + no-case "^2.3.2" param-case "^2.1.0" pascal-case "^2.0.0" path-case "^2.1.0" @@ -1767,6 +1727,10 @@ change-case@3.0.x: upper-case "^1.1.1" upper-case-first "^1.1.0" +check-types@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.3.0.tgz#468f571a4435c24248f5fd0cb0e8d87c3c341e7d" + cheerio@^1.0.0-rc.2: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -1778,7 +1742,7 @@ cheerio@^1.0.0-rc.2: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@^1.4.1, chokidar@^1.6.0, chokidar@^1.7.0: +chokidar@^1.4.1, chokidar@^1.6.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1793,7 +1757,7 @@ chokidar@^1.4.1, chokidar@^1.6.0, chokidar@^1.7.0: optionalDependencies: fsevents "^1.0.0" -chokidar@^2.0.0: +chokidar@^2.0.0, chokidar@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" dependencies: @@ -1816,8 +1780,8 @@ chownr@^1.0.1, chownr@~1.0.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" ci-info@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" + version "1.1.3" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" cidr-regex@1.0.6: version "1.0.6" @@ -1841,13 +1805,12 @@ clap@^1.0.9: chalk "^1.1.3" class-utils@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" dependencies: arr-union "^3.1.0" define-property "^0.2.5" isobject "^3.0.0" - lazy-cache "^2.0.2" static-extend "^0.1.1" classnames@2.x, classnames@^2.2.4, classnames@^2.2.5: @@ -1862,8 +1825,8 @@ clean-css@3.4.x, clean-css@~3.4.2: source-map "0.4.x" clean-css@4.1.x: - version "4.1.9" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301" + version "4.1.11" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a" dependencies: source-map "0.5.x" @@ -1928,6 +1891,14 @@ clipboard@^1.7.1: select "^1.1.2" tiny-emitter "^2.0.0" +clipboard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.0.tgz#4661dc972fb72a4c4770b8db78aa9b1caef52b50" + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -1944,24 +1915,32 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -clone-deep@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8" +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + +clone-deep@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" dependencies: for-own "^1.0.0" - is-plain-object "^2.0.1" - kind-of "^3.2.2" - shallow-clone "^0.1.2" + is-plain-object "^2.0.4" + kind-of "^6.0.0" + shallow-clone "^1.0.0" clone@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" clone@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" -cmd-shim@~2.0.2: +cmd-shim@^2.0.2, cmd-shim@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" dependencies: @@ -2037,7 +2016,11 @@ colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" -colors@^1.1.0, colors@^1.1.2, colors@~1.1.2: +colors@^1.1.0, colors@^1.1.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" + +colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -2054,15 +2037,15 @@ combine-lists@^1.0.0: dependencies: lodash "^4.5.0" -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" +combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" -commander@2, commander@2.12.x, commander@^2.11.0, commander@^2.8.1, commander@^2.9.0, commander@~2.12.1: - version "2.12.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.1.tgz#468635c4168d06145b9323356d1da84d14ac4a7a" +commander@2, commander@2.15.x, commander@^2.11.0, commander@^2.12.1, commander@^2.13.0, commander@^2.8.1, commander@^2.9.0, commander@~2.15.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" commander@2.11.0: version "2.11.0" @@ -2090,6 +2073,10 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +compare-versions@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5" + component-bind@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" @@ -2137,7 +2124,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@1.6.0, concat-stream@^1.4.1, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2: +concat-stream@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -2145,6 +2132,15 @@ concat-stream@1.6.0, concat-stream@^1.4.1, concat-stream@^1.4.6, concat-stream@^ readable-stream "^2.2.2" typedarray "^0.0.6" +concat-stream@^1.4.1, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + config-chain@~1.1.11: version "1.1.11" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2" @@ -2153,8 +2149,8 @@ config-chain@~1.1.11: proto-list "~1.2.1" configstore@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" + version "3.1.2" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" dependencies: dot-prop "^4.1.0" graceful-fs "^4.1.2" @@ -2168,11 +2164,11 @@ connect-history-api-fallback@^1.3.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" connect@^3.6.0: - version "3.6.5" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.5.tgz#fb8dde7ba0763877d0ec9df9dac0b4b40e72c7da" + version "3.6.6" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.6.tgz#09eff6c55af7236e137135a72574858b6786f524" dependencies: debug "2.6.9" - finalhandler "1.0.6" + finalhandler "1.1.0" parseurl "~1.3.2" utils-merge "1.0.1" @@ -2201,10 +2197,6 @@ content-disposition@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" -content-type-parser@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" - content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -2244,13 +2236,9 @@ core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" -core-js@^2.0.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" - -core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" +core-js@^2.0.0, core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b" core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -2268,13 +2256,13 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: parse-json "^2.2.0" require-from-string "^1.1.0" -cosmiconfig@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.1.0.tgz#640a94bf9847f321800403cd273af60665c73397" +cosmiconfig@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc" dependencies: is-directory "^0.3.1" js-yaml "^3.9.0" - parse-json "^3.0.0" + parse-json "^4.0.0" require-from-string "^2.0.1" cpx@^1.5.0: @@ -2305,8 +2293,8 @@ crc@^3.4.4: resolved "https://registry.yarnpkg.com/crc/-/crc-3.5.0.tgz#98b8ba7d489665ba3979f59b21381374101a1964" create-ecdh@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" + version "4.0.1" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.1.tgz#44223dfed533193ba5ba54e0df5709b89acf1f82" dependencies: bn.js "^4.1.0" elliptic "^6.0.0" @@ -2318,17 +2306,18 @@ create-error-class@^3.0.0: capture-stack-trace "^1.0.0" create-hash@^1.1.0, create-hash@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" dependencies: cipher-base "^1.0.1" inherits "^2.0.1" - ripemd160 "^2.0.0" + md5.js "^1.3.4" + ripemd160 "^2.0.1" sha.js "^2.4.0" create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" @@ -2389,21 +2378,21 @@ css-color-names@0.0.4: resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" css-loader@^0.28.7: - version "0.28.7" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.7.tgz#5f2ee989dd32edd907717f953317656160999c1b" + version "0.28.11" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7" dependencies: - babel-code-frame "^6.11.0" + babel-code-frame "^6.26.0" css-selector-tokenizer "^0.7.0" - cssnano ">=2.6.1 <4" + cssnano "^3.10.0" icss-utils "^2.1.0" loader-utils "^1.0.2" lodash.camelcase "^4.3.0" - object-assign "^4.0.1" + object-assign "^4.1.1" postcss "^5.0.6" - postcss-modules-extract-imports "^1.0.0" - postcss-modules-local-by-default "^1.0.1" - postcss-modules-scope "^1.0.0" - postcss-modules-values "^1.1.0" + postcss-modules-extract-imports "^1.2.0" + postcss-modules-local-by-default "^1.2.0" + postcss-modules-scope "^1.1.0" + postcss-modules-values "^1.3.0" postcss-value-parser "^3.3.0" source-list-map "^2.0.0" @@ -2432,7 +2421,7 @@ cssesc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" -"cssnano@>=2.6.1 <4": +cssnano@^3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" dependencies: @@ -2486,6 +2475,10 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" +csstype@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.4.1.tgz#ba35a94259cffc07ed022954737a1da690dcae2c" + cst@^0.4.3: version "0.4.10" resolved "https://registry.yarnpkg.com/cst/-/cst-0.4.10.tgz#9c05c825290a762f0a85c0aabb8c0fe035ae8516" @@ -2541,7 +2534,11 @@ d3-collection@1, d3-collection@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" -d3-color@1, d3-color@1.0.3: +d3-color@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.1.0.tgz#73957299b63ca935bf19c6c9d835e90066028329" + +d3-color@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" @@ -2577,13 +2574,13 @@ d3-force@1.1.0: d3-quadtree "1" d3-timer "1" -d3-format@1, d3-format@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f" +d3-format@1, d3-format@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" -d3-geo@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.0.tgz#15c7d7a8ea9346e59ed150dc7b1f7f95479056e9" +d3-geo@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356" dependencies: d3-array "1" @@ -2627,9 +2624,10 @@ d3-request@1.0.6: xmlhttprequest "1" d3-scale-chromatic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.1.1.tgz#811406e8e09dab78a49dac4a32047d5d3edd0c44" + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.2.0.tgz#25820d059c0eccc33e85f77561f37382a817ab58" dependencies: + d3-color "1" d3-interpolate "1" d3-scale@1.0.7: @@ -2644,9 +2642,9 @@ d3-scale@1.0.7: d3-time "1" d3-time-format "2" -d3-selection@1, d3-selection@1.2.0, d3-selection@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.2.0.tgz#1b8ec1c7cedadfb691f2ba20a4a3cfbeb71bbc88" +d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d" d3-shape@1.2.0: version "1.2.0" @@ -2694,8 +2692,8 @@ d3-zoom@1.7.1: d3-transition "1" d3@^4.11.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/d3/-/d3-4.12.0.tgz#75eccb39ea40f6018de8cfa2752905bee7daa46f" + version "4.13.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d" dependencies: d3-array "1.2.1" d3-axis "1.0.8" @@ -2708,8 +2706,8 @@ d3@^4.11.0: d3-dsv "1.0.8" d3-ease "1.0.3" d3-force "1.1.0" - d3-format "1.2.1" - d3-geo "1.9.0" + d3-format "1.2.2" + d3-geo "1.9.1" d3-hierarchy "1.1.5" d3-interpolate "1.1.6" d3-path "1.0.5" @@ -2719,7 +2717,7 @@ d3@^4.11.0: d3-random "1.1.0" d3-request "1.0.6" d3-scale "1.0.7" - d3-selection "1.2.0" + d3-selection "1.3.0" d3-shape "1.2.0" d3-time "1.0.8" d3-time-format "2.1.1" @@ -2740,6 +2738,14 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-urls@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.0.0.tgz#24802de4e81c298ea8a9388bb0d8e461c774684f" + dependencies: + abab "^1.0.4" + whatwg-mimetype "^2.0.0" + whatwg-url "^6.4.0" + date-fns@^1.27.2: version "1.29.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" @@ -2755,12 +2761,6 @@ dateformat@~1.0.12: get-stdin "^4.0.1" meow "^3.3.0" -debug@2, debug@2.6.9, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.4.1, debug@^2.6.6, debug@^2.6.8: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - debug@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -2773,6 +2773,12 @@ debug@2.3.3: dependencies: ms "0.7.2" +debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.2, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + debug@3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -2791,6 +2797,12 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + dependencies: + mimic-response "^1.0.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -2803,11 +2815,11 @@ deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" -deep-for-each@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/deep-for-each/-/deep-for-each-1.0.6.tgz#afa0ce249c58492a9720539478a18d37e1b10bae" +deep-for-each@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/deep-for-each/-/deep-for-each-2.0.3.tgz#640b17b88c69892e33caba853004aa89ce00f5c4" dependencies: - is-plain-object "^2.0.1" + lodash.isplainobject "^4.0.6" deep-is@~0.1.3: version "0.1.3" @@ -2883,18 +2895,18 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" delegate@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.1.3.tgz#9a8251a777d7025faa55737bc3b071742127a9fd" + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@1.1.1, depd@~1.1.1: +depd@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" -depd@~1.1.2: +depd@~1.1.1, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -2923,7 +2935,7 @@ detect-libc@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-0.2.0.tgz#47fdf567348a17ec25fcbf0b9e446348a76f9fb5" -detect-libc@^1.0.2: +detect-libc@^1.0.2, detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" @@ -2959,17 +2971,21 @@ diff@^2.0.2: resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" diff@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" diffie-hellman@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" randombytes "^2.0.0" +direction@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/direction/-/direction-0.1.5.tgz#ce5d797f97e26f8be7beff53f7dc40e1c1a9ec4c" + discontinuous-range@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" @@ -3004,7 +3020,7 @@ dom-converter@~0.1: dependencies: utila "~0.3" -dom-helpers@^3.2.0: +dom-helpers@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" @@ -3029,8 +3045,8 @@ dom-walk@^0.1.0: resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" domain-browser@^1.1.1: - version "1.1.7" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" domelementtype@1, domelementtype@^1.3.0: version "1.3.0" @@ -3041,8 +3057,10 @@ domelementtype@~1.1.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.0.tgz#81fe5df81b3f057052cde3a9fa9bf536a85b9ab0" + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + dependencies: + webidl-conversions "^4.0.2" domhandler@2.1: version "2.1.0" @@ -3076,8 +3094,8 @@ domutils@1.5, domutils@1.5.1: domelementtype "1" domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" dependencies: dom-serializer "0" domelementtype "1" @@ -3094,9 +3112,9 @@ dot-prop@^4.1.0: dependencies: is-obj "^1.0.0" -dotenv@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" +dotenv@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" duplexer3@^0.1.4: version "0.1.4" @@ -3106,9 +3124,9 @@ duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" -duplexify@^3.1.2, duplexify@^3.4.2: - version "3.5.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.1.tgz#4e1516be68838bc90a49994f0b39a6e5960befcd" +duplexify@^3.4.2, duplexify@^3.5.3: + version "3.5.4" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4" dependencies: end-of-stream "^1.0.0" inherits "^2.0.1" @@ -3140,21 +3158,21 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -ejs@^2.5.6: - version "2.5.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" +ejs@^2.5.7: + version "2.5.9" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.9.tgz#7ba254582a560d267437109a68354112475b0ce5" electron-to-chromium@^1.2.7: - version "1.3.27" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" + version "1.3.42" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz#95c33bf01d0cc405556aec899fe61fd4d76ea0f9" elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" element-resize-detector@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.12.tgz#8b3fd6eedda17f9c00b360a0ea2df9927ae80ba2" + version "1.1.14" + resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.14.tgz#af064a0a618a820ad570a95c5eec5b77be0128c1" dependencies: batch-processor "^1.0.0" @@ -3188,11 +3206,7 @@ empower@^1.2.3: core-js "^2.0.0" empower-core "^0.6.2" -encodeurl@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" - -encodeurl@~1.0.2: +encodeurl@~1.0.1, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -3203,8 +3217,8 @@ encoding@^0.1.11: iconv-lite "~0.4.13" end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" dependencies: once "^1.4.0" @@ -3278,27 +3292,28 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" enzyme-adapter-react-16@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.0.tgz#86c5db7c10f0be6ec25d54ca41b59f2abb397cf4" + version "1.1.1" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" dependencies: - enzyme-adapter-utils "^1.1.0" + enzyme-adapter-utils "^1.3.0" lodash "^4.17.4" object.assign "^4.0.4" object.values "^1.0.4" - prop-types "^15.5.10" + prop-types "^15.6.0" + react-reconciler "^0.7.0" react-test-renderer "^16.0.0-0" -enzyme-adapter-utils@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.2.0.tgz#7f4471ee0a70b91169ec8860d2bf0a6b551664b2" +enzyme-adapter-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7" dependencies: lodash "^4.17.4" object.assign "^4.0.4" - prop-types "^15.5.10" + prop-types "^15.6.0" enzyme-to-json@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.3.0.tgz#553e23a09ffb4b0cf09287e2edf9c6539fddaa84" + version "3.3.3" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.3.3.tgz#ede45938fb309cd87ebd4386f60c754525515a07" dependencies: lodash "^4.17.4" @@ -3327,11 +3342,11 @@ err-code@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" -errno@^0.1.3, errno@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" dependencies: - prr "~0.0.0" + prr "~1.0.1" error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.1" @@ -3339,17 +3354,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.5.1, es-abstract@^1.6.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" - dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" - -es-abstract@^1.7.0: +es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.11.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681" dependencies: @@ -3368,13 +3373,14 @@ es-to-primitive@^1.1.1: is-symbol "^1.0.1" es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.37" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.37.tgz#0ee741d148b80069ba27d020393756af257defc3" + version "0.10.42" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.42.tgz#8c07dd33af04d5dcd1310b5cef13bea63a89ba8d" dependencies: - es6-iterator "~2.0.1" + es6-iterator "~2.0.3" es6-symbol "~3.1.1" + next-tick "1" -es6-iterator@^2.0.1, es6-iterator@~2.0.1: +es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" dependencies: @@ -3398,8 +3404,8 @@ es6-promise@^3.0.2: resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" es6-promise@^4.0.3: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" + version "4.2.4" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" es6-promisify@^5.0.0: version "5.0.0" @@ -3428,7 +3434,7 @@ es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: d "1" es5-ext "~0.10.14" -es6-templates@^0.2.2: +es6-templates@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/es6-templates/-/es6-templates-0.2.3.tgz#5cb9ac9fb1ded6eb1239342b81d792bbb4078ee4" dependencies: @@ -3453,15 +3459,15 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" escodegen@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" dependencies: esprima "^3.1.3" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: - source-map "~0.5.6" + source-map "~0.6.1" escope@^3.6.0: version "3.6.0" @@ -3511,10 +3517,10 @@ eslint@^2.7.0: user-home "^2.0.0" espree@^3.1.6: - version "3.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" dependencies: - acorn "^5.2.1" + acorn "^5.5.0" acorn-jsx "^3.0.0" esprima@^2.6.0: @@ -3536,11 +3542,14 @@ espurify@^1.6.0: core-js "^2.0.0" esrecurse@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" dependencies: estraverse "^4.1.0" - object-assign "^4.0.1" + +esrever@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/esrever/-/esrever-0.2.0.tgz#96e9d28f4f1b1a76784cd5d490eaae010e7407b8" estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" @@ -3565,14 +3574,14 @@ eventemitter2@~0.4.13: version "0.4.14" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" -eventemitter3@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" - eventemitter3@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" +eventemitter3@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" + events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -3624,7 +3633,7 @@ exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" -exit@0.1.2, exit@0.1.x, exit@~0.1.1, exit@~0.1.2: +exit@0.1.2, exit@0.1.x, exit@^0.1.2, exit@~0.1.1, exit@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -3679,55 +3688,20 @@ expect.js@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.2.0.tgz#1028533d2c1c363f74a6796ff57ec0520ded2be1" -expect@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/expect/-/expect-22.0.3.tgz#bb486de7d41bf3eb60d3b16dfd1c158a4d91ddfa" +expect@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-22.4.3.tgz#d5a29d0a0e1fb2153557caef2674d4547e914674" dependencies: ansi-styles "^3.2.0" - jest-diff "^22.0.3" - jest-get-type "^22.0.3" - jest-matcher-utils "^22.0.3" - jest-message-util "^22.0.3" - jest-regex-util "^22.0.3" + jest-diff "^22.4.3" + jest-get-type "^22.4.3" + jest-matcher-utils "^22.4.3" + jest-message-util "^22.4.3" + jest-regex-util "^22.4.3" expose-loader@^0.7.3: - version "0.7.4" - resolved "https://registry.yarnpkg.com/expose-loader/-/expose-loader-0.7.4.tgz#9bcdd3878b5da9107930b55a03f65afe90b3314a" - -express@^4.15.2: - version "4.16.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" - dependencies: - accepts "~1.3.4" - array-flatten "1.1.1" - body-parser "1.18.2" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.1" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.0" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.2" - path-to-regexp "0.1.7" - proxy-addr "~2.0.2" - qs "6.5.1" - range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.1" - serve-static "1.13.1" - setprototypeof "1.1.0" - statuses "~1.3.1" - type-is "~1.6.15" - utils-merge "1.0.1" - vary "~1.1.2" + version "0.7.5" + resolved "https://registry.yarnpkg.com/expose-loader/-/expose-loader-0.7.5.tgz#e29ea2d9aeeed3254a3faa1b35f502db9f9c3f6f" express@^4.16.2: version "4.16.3" @@ -3770,13 +3744,7 @@ extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend-shallow@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.1.tgz#4b6d8c49b147fee029dc9eb9484adb770f689844" - dependencies: - is-extendable "^1.0.1" - -extend-shallow@^3.0.2: +extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" dependencies: @@ -3793,19 +3761,6 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extglob@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.2.tgz#3290f46208db1b2e8eb8be0c94ed9e6ad80edbe2" - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" @@ -3837,17 +3792,21 @@ extract-zip@^1.6.5: mkdirp "0.5.0" yauzl "2.4.1" -extsprintf@1.3.0, extsprintf@^1.2.0: +extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + eyes@0.1.x: version "0.1.8" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -3918,8 +3877,8 @@ file-loader@^0.11.2: loader-utils "^1.0.2" file-saver@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.3.tgz#cdd4c44d3aa264eac2f68ec165bc791c34af1232" + version "1.3.8" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" file-sync-cmp@^0.1.0: version "0.1.1" @@ -3936,9 +3895,9 @@ fileset@^2.0.2: glob "^7.0.3" minimatch "^3.0.3" -filesize@^3.5.9: - version "3.5.11" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" +filesize@^3.5.11: + version "3.6.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" fill-range@^2.1.0: version "2.2.3" @@ -3959,18 +3918,6 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -finalhandler@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.6.tgz#007aea33d1a4d3e42017f624848ad58d212f814f" - dependencies: - debug "2.6.9" - encodeurl "~1.0.1" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.3.1" - unpipe "~1.0.0" - finalhandler@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" @@ -4007,6 +3954,10 @@ find-index@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" +find-npm-prefix@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz#8d8ce2c78b3b4b9e66c8acc6a37c231eb841cfdf" + find-parent-dir@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" @@ -4044,13 +3995,13 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" flush-write-stream@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" + version "1.0.3" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" dependencies: inherits "^2.0.1" readable-stream "^2.0.4" -follow-redirects@^1.2.5: +follow-redirects@^1.0.0, follow-redirects@^1.2.5: version "1.4.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa" dependencies: @@ -4093,11 +4044,11 @@ form-data@~2.1.1: mime-types "^2.1.12" form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" dependencies: asynckit "^0.4.0" - combined-stream "^1.0.5" + combined-stream "1.0.6" mime-types "^2.1.12" formatio@1.1.1: @@ -4170,7 +4121,13 @@ fs-extra@^3.0.1: jsonfile "^3.0.0" universalify "^0.1.0" -fs-vacuum@~1.2.10: +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + dependencies: + minipass "^2.2.1" + +fs-vacuum@^1.2.10, fs-vacuum@~1.2.10: version "1.2.10" resolved "https://registry.yarnpkg.com/fs-vacuum/-/fs-vacuum-1.2.10.tgz#b7629bec07a4031a2548fdf99f5ecf1cc8b31e36" dependencies: @@ -4192,21 +4149,13 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" fsevents@^1.0.0, fsevents@^1.1.1, fsevents@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" + version "1.2.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.2.tgz#4f598f0f69b273188ef4a62ca4e9e08ace314bbf" dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.39" + nan "^2.9.2" + node-pre-gyp "^0.9.0" -fstream-ignore@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" - dependencies: - fstream "^1.0.0" - inherits "2" - minimatch "^3.0.0" - -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: +fstream@^1.0.0, fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" dependencies: @@ -4260,10 +4209,27 @@ genfun@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/genfun/-/genfun-4.0.1.tgz#ed10041f2e4a7f1b0a38466d17a5c3e27df1dfc1" +gentle-fs@^2.0.0, gentle-fs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/gentle-fs/-/gentle-fs-2.0.1.tgz#585cfd612bfc5cd52471fdb42537f016a5ce3687" + dependencies: + aproba "^1.1.2" + fs-vacuum "^1.2.10" + graceful-fs "^4.1.11" + iferr "^0.1.5" + mkdirp "^0.5.1" + path-is-inside "^1.0.2" + read-cmd-shim "^1.0.1" + slide "^1.1.6" + get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-document@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b" + get-own-enumerable-property-symbols@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz#5c4ad87f2834c4b9b4e84549dc1e0650fb38c24b" @@ -4280,6 +4246,12 @@ get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" +get-window@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-window/-/get-window-1.1.2.tgz#65fbaa999fb87f86ea5d30770f4097707044f47f" + dependencies: + get-document "1" + getobject@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/getobject/-/getobject-0.1.0.tgz#047a449789fa160d018f5486ed91320b6ec7885c" @@ -4547,14 +4519,14 @@ grunt-legacy-log-utils@~1.0.0: lodash "~4.3.0" grunt-legacy-log@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz#fb86f1809847bc07dc47843f9ecd6cacb62df2d5" + version "1.0.1" + resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-1.0.1.tgz#c7731b2745f4732aa9950ee4d7ae63c553f68469" dependencies: colors "~1.1.2" grunt-legacy-log-utils "~1.0.0" hooker "~0.2.3" - lodash "~3.10.1" - underscore.string "~3.2.3" + lodash "~4.17.5" + underscore.string "~3.3.4" grunt-legacy-util@~1.0.0: version "1.0.0" @@ -4591,11 +4563,11 @@ grunt-sass-lint@^0.2.2: sass-lint "^1.12.0" grunt-sass@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-sass/-/grunt-sass-2.0.0.tgz#9074cf9d7b4592e20f7788caa727b8f9aa06b60a" + version "2.1.0" + resolved "https://registry.yarnpkg.com/grunt-sass/-/grunt-sass-2.1.0.tgz#b7ba1d85ef4c2d9b7d8195fe65f664ac7554efa1" dependencies: each-async "^1.0.0" - node-sass "^4.0.0" + node-sass "^4.7.2" object-assign "^4.0.1" grunt-usemin@3.1.1: @@ -4608,10 +4580,10 @@ grunt-usemin@3.1.1: path-exists "^1.0.0" grunt-webpack@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/grunt-webpack/-/grunt-webpack-3.0.2.tgz#bcfdea313d431e79b6fc18c42040b48dfac9d32f" + version "3.1.1" + resolved "https://registry.yarnpkg.com/grunt-webpack/-/grunt-webpack-3.1.1.tgz#78de544e88ff41a221c173fc91cad579c97a9087" dependencies: - deep-for-each "^1.0.5" + deep-for-each "^2.0.2" lodash "^4.7.0" grunt@1.0.1: @@ -4642,11 +4614,12 @@ gzip-size@^1.0.0: browserify-zlib "^0.1.4" concat-stream "^1.4.1" -gzip-size@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520" +gzip-size@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-4.1.0.tgz#8ae096257eabe7d69c45be2b67c448124ffb517c" dependencies: duplexer "^0.1.1" + pify "^3.0.0" handle-thing@^1.2.5: version "1.2.5" @@ -4662,10 +4635,6 @@ handlebars@^4.0.3: optionalDependencies: uglify-js "^2.6" -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -4679,13 +4648,6 @@ har-validator@~2.0.6: is-my-json-valid "^2.12.4" pinkie-promise "^2.0.0" -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" @@ -4766,12 +4728,6 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" -hash-base@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" - dependencies: - inherits "^2.0.1" - hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" @@ -4793,7 +4749,7 @@ hasha@^2.2.0: is-stream "^1.0.1" pinkie-promise "^2.0.0" -hawk@3.1.3, hawk@~3.1.3: +hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" dependencies: @@ -4823,8 +4779,8 @@ header-case@^1.0.0: upper-case "^1.1.3" highlight-words-core@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.1.2.tgz#5c2717c4f6c6e7ea2462ab85b43ff8b24f58ec3e" + version "1.2.0" + resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.0.tgz#232bec301cbf2a4943d335dc748ce70e9024f3b1" hmac-drbg@^1.0.0: version "1.0.1" @@ -4839,14 +4795,10 @@ hoek@2.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" -hoist-non-react-statics@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" - -hoist-non-react-statics@^2.5.0: +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40" @@ -4861,9 +4813,9 @@ hooker@^0.2.3, hooker@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" -hosted-git-info@^2.1.4, hosted-git-info@^2.4.2, hosted-git-info@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" +hosted-git-info@^2.1.4, hosted-git-info@^2.4.2, hosted-git-info@^2.5.0, hosted-git-info@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" hpack.js@^2.1.6: version "2.1.6" @@ -4878,7 +4830,7 @@ html-comment-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" -html-encoding-sniffer@^1.0.1: +html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" dependencies: @@ -4889,27 +4841,26 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" html-loader@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/html-loader/-/html-loader-0.5.1.tgz#4f1e8396a1ea6ab42bedc987dfac058070861ebe" + version "0.5.5" + resolved "https://registry.yarnpkg.com/html-loader/-/html-loader-0.5.5.tgz#6356dbeb0c49756d8ebd5ca327f16ff06ab5faea" dependencies: - es6-templates "^0.2.2" + es6-templates "^0.2.3" fastparse "^1.1.1" - html-minifier "^3.0.1" - loader-utils "^1.0.2" - object-assign "^4.1.0" + html-minifier "^3.5.8" + loader-utils "^1.1.0" + object-assign "^4.1.1" -html-minifier@^3.0.1, html-minifier@^3.2.3: - version "3.5.7" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.7.tgz#511e69bb5a8e7677d1012ebe03819aa02ca06208" +html-minifier@^3.2.3, html-minifier@^3.5.8: + version "3.5.15" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.15.tgz#f869848d4543cbfd84f26d5514a2a87cbf9a05e0" dependencies: camel-case "3.0.x" clean-css "4.1.x" - commander "2.12.x" + commander "2.15.x" he "1.1.x" - ncname "1.0.x" param-case "2.1.x" relateurl "0.2.x" - uglify-js "3.2.x" + uglify-js "3.3.x" html-minifier@~2.1.2: version "2.1.7" @@ -4972,14 +4923,14 @@ htmlparser2@~3.3.0: readable-stream "1.0" http-cache-semantics@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.0.tgz#1e3ce248730e189ac692a6697b9e3fdea2ff8da3" + version "3.8.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" -http-errors@1.6.2, http-errors@~1.6.2: +http-errors@1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" dependencies: @@ -4988,16 +4939,25 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-parser-js@>=0.4.0: - version "0.4.11" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.11.tgz#5b720849c650903c27e521633d94696ee95f3529" + version "0.4.12" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.12.tgz#b9cfbf4a2cf26f0fc34b10ca1489a27771e3474f" http-proxy-agent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.0.0.tgz#46482a2f0523a4d6082551709f469cb3e4a85ff4" + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" dependencies: agent-base "4" - debug "2" + debug "3.1.0" http-proxy-middleware@~0.17.4: version "0.17.4" @@ -5009,11 +4969,12 @@ http-proxy-middleware@~0.17.4: micromatch "^2.3.11" http-proxy@^1.13.0, http-proxy@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" + version "1.17.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" dependencies: - eventemitter3 "1.x.x" - requires-port "1.x.x" + eventemitter3 "^3.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" http-signature@~1.1.0: version "1.1.1" @@ -5036,11 +4997,11 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" https-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.1.0.tgz#1391bee7fd66aeabc0df2a1fa90f58954f43e443" + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" dependencies: agent-base "^4.1.0" - debug "^2.4.1" + debug "^3.1.0" humanize-ms@^1.2.1: version "1.2.1" @@ -5060,7 +5021,13 @@ i@0.3.x: version "0.3.6" resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d" -iconv-lite@0.4, iconv-lite@0.4.19, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@^0.4.4, iconv-lite@~0.4.13: + version "0.4.21" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798" + dependencies: + safer-buffer "^2.1.0" + +iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -5075,8 +5042,8 @@ icss-utils@^2.1.0: postcss "^6.0.1" ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + version "1.1.11" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" iferr@^0.1.5, iferr@~0.1.5: version "0.1.5" @@ -5089,8 +5056,8 @@ ignore-walk@^3.0.1: minimatch "^3.0.4" ignore@^3.1.2: - version "3.3.7" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" + version "3.3.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b" iltorb@^1.0.13: version "1.3.10" @@ -5101,6 +5068,10 @@ iltorb@^1.0.13: node-gyp "^3.6.2" prebuild-install "^2.3.0" +immutable@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -5157,16 +5128,16 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" -ini@^1.3.4, ini@~1.3.0, ini@~1.3.4: +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -init-package-json@~1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.10.1.tgz#cd873a167796befb99612b28762a0b6393fd8f6a" +init-package-json@^1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.10.3.tgz#45ffe2f610a8ca134f2bd1db5637b235070f6cbe" dependencies: glob "^7.1.1" - npm-package-arg "^4.0.0 || ^5.0.0" + npm-package-arg "^4.0.0 || ^5.0.0 || ^6.0.0" promzard "^0.3.0" read "~1.0.1" read-package-json "1 || 2" @@ -5199,12 +5170,12 @@ internal-ip@1.2.0: meow "^3.3.0" interpret@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0" + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: loose-envify "^1.0.0" @@ -5216,10 +5187,6 @@ ip@^1.1.0, ip@^1.1.4, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" -ipaddr.js@1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" - ipaddr.js@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" @@ -5258,7 +5225,7 @@ is-boolean-object@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" -is-buffer@^1.0.2, is-buffer@^1.1.5: +is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -5273,8 +5240,8 @@ is-callable@^1.1.1, is-callable@^1.1.3: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" is-ci@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" dependencies: ci-info "^1.0.0" @@ -5308,15 +5275,7 @@ is-descriptor@^0.1.0: is-data-descriptor "^0.1.4" kind-of "^5.0.0" -is-descriptor@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.1.tgz#2c6023599bde2de9d5d2c8b9a9d94082036b6ef2" - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.2: +is-descriptor@^1.0.0, is-descriptor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" dependencies: @@ -5332,6 +5291,10 @@ is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" +is-empty@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-empty/-/is-empty-1.2.0.tgz#de9bb5b278738a05a0b09a57e1fb4d4a341a9f6b" + is-equal-shallow@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" @@ -5372,6 +5335,10 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-generator-fn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -5390,6 +5357,14 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-hotkey@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-hotkey/-/is-hotkey-0.1.2.tgz#aeda5e4f542284700ae18b46980fb0637c021198" + +is-in-browser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" + is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -5403,12 +5378,17 @@ is-lower-case@^1.1.0: dependencies: lower-case "^1.1.0" +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" + version "2.17.2" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" jsonpointer "^4.0.0" xtend "^4.0.0" @@ -5450,12 +5430,6 @@ is-observable@^0.2.0: dependencies: symbol-observable "^0.2.2" -is-odd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088" - dependencies: - is-number "^3.0.0" - is-odd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" @@ -5467,14 +5441,14 @@ is-path-cwd@^1.0.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" dependencies: is-path-inside "^1.0.0" is-path-inside@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" dependencies: path-is-inside "^1.0.1" @@ -5519,10 +5493,8 @@ is-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" is-resolvable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" - dependencies: - tryit "^1.0.1" + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" is-retry-allowed@^1.0.0: version "1.1.0" @@ -5564,6 +5536,10 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" +is-window@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" + is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -5598,6 +5574,10 @@ isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" +isomorphic-base64@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/isomorphic-base64/-/isomorphic-base64-1.0.2.tgz#f426aae82569ba8a4ec5ca73ad21a44ab1ee7803" + isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" @@ -5610,102 +5590,116 @@ isstream@0.1.x, isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" istanbul-api@^1.1.14: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" + version "1.3.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.1.tgz#4c3b05d18c0016d1022e079b98dc82c40f488954" dependencies: async "^2.1.4" + compare-versions "^3.1.0" fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.1.0" - istanbul-lib-instrument "^1.9.1" - istanbul-lib-report "^1.1.2" - istanbul-lib-source-maps "^1.2.2" - istanbul-reports "^1.1.3" + istanbul-lib-coverage "^1.2.0" + istanbul-lib-hook "^1.2.0" + istanbul-lib-instrument "^1.10.1" + istanbul-lib-report "^1.1.4" + istanbul-lib-source-maps "^1.2.4" + istanbul-reports "^1.3.0" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" -istanbul-lib-coverage@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" +istanbul-lib-coverage@^1.1.1, istanbul-lib-coverage@^1.1.2, istanbul-lib-coverage@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341" -istanbul-lib-hook@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" +istanbul-lib-hook@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz#ae556fd5a41a6e8efa0b1002b1e416dfeaf9816c" dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0, istanbul-lib-instrument@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.8.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" babylon "^6.18.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.0" semver "^5.3.0" -istanbul-lib-report@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" +istanbul-lib-report@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz#e886cdf505c4ebbd8e099e4396a90d0a28e2acb5" dependencies: - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.0" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.2.1, istanbul-lib-source-maps@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" +istanbul-lib-source-maps@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz#20fb54b14e14b3fb6edb6aca3571fd2143db44e6" dependencies: debug "^3.1.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.1.2" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" +istanbul-lib-source-maps@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz#cc7ccad61629f4efff8e2f78adb8c522c9976ec7" + dependencies: + debug "^3.1.0" + istanbul-lib-coverage "^1.2.0" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.3.0.tgz#2f322e81e1d9520767597dca3c20a0cce89a3554" dependencies: handlebars "^4.0.3" -jest-changed-files@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.0.3.tgz#3771315acfa24a0ed7e6c545de620db6f1b2d164" +jest-changed-files@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.4.3.tgz#8882181e022c38bd46a2e4d18d44d19d90a90fb2" dependencies: throat "^4.0.0" -jest-cli@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.0.4.tgz#0052abaad45c57861c05da8ab5d27bad13ad224d" +jest-cli@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.3.tgz#bf16c4a5fb7edc3fa5b9bb7819e34139e88a72c7" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" + exit "^0.1.2" glob "^7.1.2" graceful-fs "^4.1.11" + import-local "^1.0.0" is-ci "^1.0.10" istanbul-api "^1.1.14" istanbul-lib-coverage "^1.1.1" istanbul-lib-instrument "^1.8.0" istanbul-lib-source-maps "^1.2.1" - jest-changed-files "^22.0.3" - jest-config "^22.0.4" - jest-environment-jsdom "^22.0.4" - jest-get-type "^22.0.3" - jest-haste-map "^22.0.3" - jest-message-util "^22.0.3" - jest-regex-util "^22.0.3" - jest-resolve-dependencies "^22.0.3" - jest-runner "^22.0.4" - jest-runtime "^22.0.4" - jest-snapshot "^22.0.3" - jest-util "^22.0.4" - jest-worker "^22.0.3" + jest-changed-files "^22.4.3" + jest-config "^22.4.3" + jest-environment-jsdom "^22.4.3" + jest-get-type "^22.4.3" + jest-haste-map "^22.4.3" + jest-message-util "^22.4.3" + jest-regex-util "^22.4.3" + jest-resolve-dependencies "^22.4.3" + jest-runner "^22.4.3" + jest-runtime "^22.4.3" + jest-snapshot "^22.4.3" + jest-util "^22.4.3" + jest-validate "^22.4.3" + jest-worker "^22.4.3" micromatch "^2.3.11" - node-notifier "^5.1.2" + node-notifier "^5.2.1" realpath-native "^1.0.0" rimraf "^2.5.4" slash "^1.0.0" @@ -5714,104 +5708,105 @@ jest-cli@^22.0.4: which "^1.2.12" yargs "^10.0.3" -jest-config@^22.0.1, jest-config@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.0.4.tgz#9c2a46c0907b1a1af54d9cdbf18e99b447034e11" +jest-config@^22.4.2, jest-config@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.3.tgz#0e9d57db267839ea31309119b41dc2fa31b76403" dependencies: chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^22.0.4" - jest-environment-node "^22.0.4" - jest-get-type "^22.0.3" - jest-jasmine2 "^22.0.4" - jest-regex-util "^22.0.3" - jest-resolve "^22.0.4" - jest-util "^22.0.4" - jest-validate "^22.0.3" - pretty-format "^22.0.3" + jest-environment-jsdom "^22.4.3" + jest-environment-node "^22.4.3" + jest-get-type "^22.4.3" + jest-jasmine2 "^22.4.3" + jest-regex-util "^22.4.3" + jest-resolve "^22.4.3" + jest-util "^22.4.3" + jest-validate "^22.4.3" + pretty-format "^22.4.3" -jest-diff@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-22.0.3.tgz#ffed5aba6beaf63bb77819ba44dd520168986321" +jest-diff@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-22.4.3.tgz#e18cc3feff0aeef159d02310f2686d4065378030" dependencies: chalk "^2.0.1" diff "^3.2.0" - jest-get-type "^22.0.3" - pretty-format "^22.0.3" + jest-get-type "^22.4.3" + pretty-format "^22.4.3" -jest-docblock@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.0.3.tgz#c33aa22682b9fc68a5373f5f82994428a2ded601" +jest-docblock@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.3.tgz#50886f132b42b280c903c592373bb6e93bb68b19" dependencies: detect-newline "^2.1.0" -jest-environment-jsdom@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.0.4.tgz#5723d4e724775ed38948de792e62f2d6a7f452df" +jest-environment-jsdom@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz#d67daa4155e33516aecdd35afd82d4abf0fa8a1e" dependencies: - jest-mock "^22.0.3" - jest-util "^22.0.4" + jest-mock "^22.4.3" + jest-util "^22.4.3" jsdom "^11.5.1" -jest-environment-node@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.0.4.tgz#068671f85a545f96a5469be3a3dd228fca79c709" +jest-environment-node@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.4.3.tgz#54c4eaa374c83dd52a9da8759be14ebe1d0b9129" dependencies: - jest-mock "^22.0.3" - jest-util "^22.0.4" + jest-mock "^22.4.3" + jest-util "^22.4.3" jest-get-type@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" -jest-get-type@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.0.3.tgz#fa894b677c0fcd55eff3fd8ee28c7be942e32d36" +jest-get-type@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" -jest-haste-map@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.0.3.tgz#c9ecb5c871c5465d4bde4139e527fa0dc784aa2d" +jest-haste-map@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.4.3.tgz#25842fa2ba350200767ac27f658d58b9d5c2e20b" dependencies: fb-watchman "^2.0.0" graceful-fs "^4.1.11" - jest-docblock "^22.0.3" - jest-worker "^22.0.3" + jest-docblock "^22.4.3" + jest-serializer "^22.4.3" + jest-worker "^22.4.3" micromatch "^2.3.11" sane "^2.0.0" -jest-jasmine2@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.0.4.tgz#f7c0965116efe831ec674dc954b0134639b3dcee" +jest-jasmine2@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.3.tgz#4daf64cd14c793da9db34a7c7b8dcfe52a745965" dependencies: - callsites "^2.0.0" chalk "^2.0.1" - expect "^22.0.3" + co "^4.6.0" + expect "^22.4.3" graceful-fs "^4.1.11" - jest-diff "^22.0.3" - jest-matcher-utils "^22.0.3" - jest-message-util "^22.0.3" - jest-snapshot "^22.0.3" + is-generator-fn "^1.0.0" + jest-diff "^22.4.3" + jest-matcher-utils "^22.4.3" + jest-message-util "^22.4.3" + jest-snapshot "^22.4.3" + jest-util "^22.4.3" source-map-support "^0.5.0" -jest-leak-detector@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.0.3.tgz#b64904f0e8954a11edb79b0809ff4717fa762d99" +jest-leak-detector@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.4.3.tgz#2b7b263103afae8c52b6b91241a2de40117e5b35" dependencies: - pretty-format "^22.0.3" - optionalDependencies: - weak "^1.0.1" + pretty-format "^22.4.3" -jest-matcher-utils@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.0.3.tgz#2ec15ca1af7dcabf4daddc894ccce224b948674e" +jest-matcher-utils@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff" dependencies: chalk "^2.0.1" - jest-get-type "^22.0.3" - pretty-format "^22.0.3" + jest-get-type "^22.4.3" + pretty-format "^22.4.3" -jest-message-util@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.0.3.tgz#bf674b2762ef2dd53facf2136423fcca264976df" +jest-message-util@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.4.3.tgz#cf3d38aafe4befddbfc455e57d65d5239e399eb7" dependencies: "@babel/code-frame" "^7.0.0-beta.35" chalk "^2.0.1" @@ -5819,57 +5814,60 @@ jest-message-util@^22.0.3: slash "^1.0.0" stack-utils "^1.0.1" -jest-mock@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.0.3.tgz#c875e47b5b729c6c020a2fab317b275c0cf88961" +jest-mock@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.4.3.tgz#f63ba2f07a1511772cdc7979733397df770aabc7" -jest-regex-util@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.0.3.tgz#c5c10229de5ce2b27bf4347916d95b802ae9aa4d" +jest-regex-util@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.4.3.tgz#a826eb191cdf22502198c5401a1fc04de9cef5af" -jest-resolve-dependencies@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.0.3.tgz#202ddf370069702cd1865a1952fcc7e52c92720e" +jest-resolve-dependencies@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.4.3.tgz#e2256a5a846732dc3969cb72f3c9ad7725a8195e" dependencies: - jest-regex-util "^22.0.3" + jest-regex-util "^22.4.3" -jest-resolve@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.0.4.tgz#a6e47f55e9388c7341b5e9732aedc6fe30906121" +jest-resolve@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.4.3.tgz#0ce9d438c8438229aa9b916968ec6b05c1abb4ea" dependencies: browser-resolve "^1.11.2" chalk "^2.0.1" -jest-runner@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.0.4.tgz#3aa43a31b05ce8271539df580c2eb916023d3367" +jest-runner@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.3.tgz#298ddd6a22b992c64401b4667702b325e50610c3" dependencies: - jest-config "^22.0.4" - jest-docblock "^22.0.3" - jest-haste-map "^22.0.3" - jest-jasmine2 "^22.0.4" - jest-leak-detector "^22.0.3" - jest-message-util "^22.0.3" - jest-runtime "^22.0.4" - jest-util "^22.0.4" - jest-worker "^22.0.3" + exit "^0.1.2" + jest-config "^22.4.3" + jest-docblock "^22.4.3" + jest-haste-map "^22.4.3" + jest-jasmine2 "^22.4.3" + jest-leak-detector "^22.4.3" + jest-message-util "^22.4.3" + jest-runtime "^22.4.3" + jest-util "^22.4.3" + jest-worker "^22.4.3" throat "^4.0.0" -jest-runtime@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.0.4.tgz#8f69aa7b5fbb3acd35dc262cbf654e563f69b7b4" +jest-runtime@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.3.tgz#b69926c34b851b920f666c93e86ba2912087e3d0" dependencies: babel-core "^6.0.0" - babel-jest "^22.0.4" + babel-jest "^22.4.3" babel-plugin-istanbul "^4.1.5" chalk "^2.0.1" convert-source-map "^1.4.0" + exit "^0.1.2" graceful-fs "^4.1.11" - jest-config "^22.0.4" - jest-haste-map "^22.0.3" - jest-regex-util "^22.0.3" - jest-resolve "^22.0.4" - jest-util "^22.0.4" + jest-config "^22.4.3" + jest-haste-map "^22.4.3" + jest-regex-util "^22.4.3" + jest-resolve "^22.4.3" + jest-util "^22.4.3" + jest-validate "^22.4.3" json-stable-stringify "^1.0.1" micromatch "^2.3.11" realpath-native "^1.0.0" @@ -5878,28 +5876,32 @@ jest-runtime@^22.0.4: write-file-atomic "^2.1.0" yargs "^10.0.3" -jest-snapshot@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.0.3.tgz#a949b393781d2fdb4773f6ea765dd67ad1da291e" +jest-serializer@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-22.4.3.tgz#a679b81a7f111e4766235f4f0c46d230ee0f7436" + +jest-snapshot@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.4.3.tgz#b5c9b42846ffb9faccb76b841315ba67887362d2" dependencies: chalk "^2.0.1" - jest-diff "^22.0.3" - jest-matcher-utils "^22.0.3" + jest-diff "^22.4.3" + jest-matcher-utils "^22.4.3" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^22.0.3" + pretty-format "^22.4.3" -jest-util@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.0.4.tgz#d920a513e0645aaab030cee38e4fe7d5bed8bb6d" +jest-util@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.4.3.tgz#c70fec8eec487c37b10b0809dc064a7ecf6aafac" dependencies: callsites "^2.0.0" chalk "^2.0.1" graceful-fs "^4.1.11" is-ci "^1.0.10" - jest-message-util "^22.0.3" - jest-validate "^22.0.3" + jest-message-util "^22.4.3" mkdirp "^0.5.1" + source-map "^0.6.0" jest-validate@^21.1.0: version "21.2.1" @@ -5910,42 +5912,44 @@ jest-validate@^21.1.0: leven "^2.1.0" pretty-format "^21.2.1" -jest-validate@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.0.3.tgz#2850d949a36c48b1a40f7eebae1d8539126f7829" +jest-validate@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.3.tgz#0780954a5a7daaeec8d3c10834b9280865976b30" dependencies: chalk "^2.0.1" - jest-get-type "^22.0.3" + jest-config "^22.4.3" + jest-get-type "^22.4.3" leven "^2.1.0" - pretty-format "^22.0.3" + pretty-format "^22.4.3" -jest-worker@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.0.3.tgz#30433faca67814a8f80559f75ab2ceaa61332fd2" +jest-worker@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.4.3.tgz#5c421417cba1c0abf64bf56bd5fb7968d79dd40b" dependencies: merge-stream "^1.0.1" jest@^22.0.4: - version "22.0.4" - resolved "https://registry.yarnpkg.com/jest/-/jest-22.0.4.tgz#d3cf560ece6b825b115dce80b9826ceb40f87961" + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.3.tgz#2261f4b117dc46d9a4a1a673d2150958dee92f16" dependencies: - jest-cli "^22.0.4" + import-local "^1.0.0" + jest-cli "^22.4.3" jquery@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787" + version "3.3.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" js-base64@^2.1.8, js-base64@^2.1.9: - version "2.3.2" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" + version "2.4.3" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582" js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" js-yaml@^3.4.3, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.7.0, js-yaml@^3.9.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -5984,8 +5988,8 @@ jscs-jsdoc@^2.0.0: jsdoctypeparser "~1.2.0" jscs-preset-wikimedia@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/jscs-preset-wikimedia/-/jscs-preset-wikimedia-1.0.0.tgz#fff563342038fc2e8826b7bb7309c3ae3406fc7e" + version "1.0.1" + resolved "https://registry.yarnpkg.com/jscs-preset-wikimedia/-/jscs-preset-wikimedia-1.0.1.tgz#a6a5fa5967fd67a5d609038e1c794eaf41d4233d" jscs@~3.0.5: version "3.0.7" @@ -6025,33 +6029,35 @@ jsdoctypeparser@~1.2.0: lodash "^3.7.0" jsdom@^11.5.1: - version "11.5.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.5.1.tgz#5df753b8d0bca20142ce21f4f6c039f99a992929" + version "11.9.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.9.0.tgz#58ac6dfd248d560d736b0202d74eedad55590cd9" dependencies: - abab "^1.0.3" - acorn "^5.1.2" - acorn-globals "^4.0.0" + abab "^1.0.4" + acorn "^5.3.0" + acorn-globals "^4.1.0" array-equal "^1.0.0" - browser-process-hrtime "^0.1.2" - content-type-parser "^1.0.1" cssom ">= 0.3.2 < 0.4.0" cssstyle ">= 0.2.37 < 0.3.0" + data-urls "^1.0.0" domexception "^1.0.0" escodegen "^1.9.0" - html-encoding-sniffer "^1.0.1" + html-encoding-sniffer "^1.0.2" left-pad "^1.2.0" nwmatcher "^1.4.3" - parse5 "^3.0.2" - pn "^1.0.0" + parse5 "4.0.0" + pn "^1.1.0" request "^2.83.0" - request-promise-native "^1.0.3" - sax "^1.2.1" - symbol-tree "^3.2.1" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" tough-cookie "^2.3.3" + w3c-hr-time "^1.0.1" webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.1" - whatwg-url "^6.3.0" - xml-name-validator "^2.0.1" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.0" + ws "^4.0.0" + xml-name-validator "^3.0.0" jsesc@^0.5.0, jsesc@~0.5.0: version "0.5.0" @@ -6089,9 +6095,9 @@ json-loader@^0.5.4, json-loader@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" -json-parse-better-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a" +json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" json-schema-traverse@^0.3.0: version "0.3.1" @@ -6142,11 +6148,11 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" jsonlint@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/jsonlint/-/jsonlint-1.6.2.tgz#5737045085f55eb455c68b1ff4ebc01bd50e8830" + version "1.6.3" + resolved "https://registry.yarnpkg.com/jsonlint/-/jsonlint-1.6.3.tgz#cb5e31efc0b78291d0d862fbef05900adf212988" dependencies: - JSV ">= 4.0.x" - nomnom ">= 1.5.x" + JSV "^4.0.x" + nomnom "^1.5.x" jsonparse@^1.2.0: version "1.3.1" @@ -6202,12 +6208,13 @@ karma-sourcemap-loader@^0.3.7: graceful-fs "^4.1.2" karma-webpack@^2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.6.tgz#967918e59750ebe0f40829263435fde7ac81bdb4" + version "2.0.13" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.13.tgz#cf56e3056c15b7747a0bb2140fc9a6be41dd9f02" dependencies: - async "~0.9.0" - loader-utils "^0.2.5" - lodash "^3.8.0" + async "^2.0.0" + babel-runtime "^6.0.0" + loader-utils "^1.0.0" + lodash "^4.0.0" source-map "^0.5.6" webpack-dev-middleware "^1.12.0" @@ -6247,17 +6254,15 @@ kew@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b" +keycode@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" + killable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" -kind-of@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" - dependencies: - is-buffer "^1.0.2" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^3.2.2: +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" dependencies: @@ -6269,15 +6274,11 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0, kind-of@^5.0.2: +kind-of@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" -kind-of@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.1.tgz#4948e6263553ac3712fc44d305b77851d9e40ea4" - -kind-of@^6.0.2: +kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" @@ -6297,20 +6298,10 @@ latest-version@^3.0.0: dependencies: package-json "^4.0.0" -lazy-cache@^0.2.3: - version "0.2.7" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" - lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-cache@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" - dependencies: - set-getter "^0.1.0" - lazy-property@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147" @@ -6328,8 +6319,8 @@ lcid@^1.0.0: invert-kv "^1.0.0" left-pad@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee" + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" leven@^2.1.0: version "2.1.0" @@ -6342,27 +6333,45 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -libnpx@~9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/libnpx/-/libnpx-9.6.0.tgz#c441ddd698b043bd8e8dc78384fa8eb7d77991e5" +libcipm@^1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/libcipm/-/libcipm-1.6.2.tgz#5a9d83b8606b9733cfff016ad9b37d3b8198ae09" dependencies: - dotenv "^4.0.0" - npm-package-arg "^5.1.2" - rimraf "^2.6.1" + bin-links "^1.1.0" + bluebird "^3.5.1" + find-npm-prefix "^1.0.2" + graceful-fs "^4.1.11" + lock-verify "^2.0.0" + npm-lifecycle "^2.0.0" + npm-logical-tree "^1.2.1" + npm-package-arg "^6.0.0" + pacote "^7.5.1" + protoduck "^5.0.0" + read-package-json "^2.0.12" + rimraf "^2.6.2" + worker-farm "^1.5.4" + +libnpx@^10.0.1: + version "10.2.0" + resolved "https://registry.yarnpkg.com/libnpx/-/libnpx-10.2.0.tgz#1bf4a1c9f36081f64935eb014041da10855e3102" + dependencies: + dotenv "^5.0.1" + npm-package-arg "^6.0.0" + rimraf "^2.6.2" safe-buffer "^5.1.0" - update-notifier "^2.2.0" - which "^1.2.14" - y18n "^3.2.1" - yargs "^8.0.2" + update-notifier "^2.3.0" + which "^1.3.0" + y18n "^4.0.0" + yargs "^11.0.0" lint-staged@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-6.0.0.tgz#7ab7d345f2fe302ff196f1de6a005594ace03210" + version "6.1.1" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-6.1.1.tgz#cd08c4d9b8ccc2d37198d1c47ce77d22be6cf324" dependencies: app-root-path "^2.0.0" chalk "^2.1.0" commander "^2.11.0" - cosmiconfig "^3.1.0" + cosmiconfig "^4.0.0" debug "^3.1.0" dedent "^0.7.0" execa "^0.8.0" @@ -6377,7 +6386,7 @@ lint-staged@^6.0.0: p-map "^1.1.1" path-is-inside "^1.0.2" pify "^3.0.0" - staged-git-files "0.0.4" + staged-git-files "1.0.0" stringify-object "^3.2.0" listr-silent-renderer@^1.1.1: @@ -6460,7 +6469,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@1.1.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@1.1.0, loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: @@ -6468,7 +6477,7 @@ loader-utils@1.1.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1. emojis-list "^2.0.0" json5 "^0.5.0" -loader-utils@^0.2.16, loader-utils@^0.2.5: +loader-utils@^0.2.16: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" dependencies: @@ -6484,9 +6493,18 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lock-verify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/lock-verify/-/lock-verify-2.0.1.tgz#6d671eea60b459c6048b3b26b62959208be67682" + dependencies: + npm-package-arg "^5.1.2" + semver "^5.4.1" + lockfile@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.3.tgz#2638fc39a0331e9cac1a04b71799931c9c50df79" + version "1.0.4" + resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" + dependencies: + signal-exit "^3.0.2" lodash._baseuniq@~4.6.0: version "4.6.0" @@ -6519,6 +6537,10 @@ lodash.clonedeep@^4.3.2, lodash.clonedeep@~4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" @@ -6527,6 +6549,10 @@ lodash.isequal@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + lodash.kebabcase@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -6536,8 +6562,8 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" lodash.mergewith@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" lodash.sortby@^4.7.0: version "4.7.0" @@ -6547,6 +6573,10 @@ lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + lodash.union@4.6.0, lodash.union@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -6563,17 +6593,13 @@ lodash@3.7.x: version "3.7.0" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45" -lodash@^3.10.1, lodash@^3.5.0, lodash@^3.6.0, lodash@^3.7.0, lodash@^3.8.0, lodash@~3.10.0, lodash@~3.10.1: +lodash@^3.10.1, lodash@^3.5.0, lodash@^3.6.0, lodash@^3.7.0, lodash@^3.8.0, lodash@~3.10.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - -lodash@^4.17.2: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" +lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.4, lodash@~4.17.5: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" lodash@~4.3.0: version "4.3.0" @@ -6590,8 +6616,8 @@ log-symbols@^1.0.0, log-symbols@^1.0.2: chalk "^1.0.0" log-symbols@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.1.0.tgz#f35fa60e278832b538dc4dddcbb478a45d3e3be6" + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" dependencies: chalk "^2.0.1" @@ -6645,16 +6671,12 @@ lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" lowercase-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" -lru-cache@2.2.x: - version "2.2.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" - -lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@~4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" +lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@~4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f" dependencies: pseudomap "^1.0.2" yallist "^2.1.2" @@ -6664,12 +6686,12 @@ macaddress@^0.2.8: resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" make-dir@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" + version "1.2.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b" dependencies: pify "^3.0.0" -make-fetch-happen@^2.4.13, make-fetch-happen@^2.5.0: +make-fetch-happen@^2.5.0, make-fetch-happen@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-2.6.0.tgz#8474aa52198f6b1ae4f3094c04e8370d35ea8a38" dependencies: @@ -6797,25 +6819,7 @@ micromatch@^2.1.5, micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.0.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.4.tgz#bb812e741a41f982c854e42b421a7eac458796f4" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.0" - define-property "^1.0.0" - extend-shallow "^2.0.1" - extglob "^2.0.2" - fragment-cache "^0.2.1" - kind-of "^6.0.0" - nanomatch "^1.2.5" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -micromatch@^3.1.4: +micromatch@^3.0.3, micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: @@ -6844,17 +6848,7 @@ miller-rabin@^4.0.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" - -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" - dependencies: - mime-db "~1.30.0" - -mime-types@~2.1.18: +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.7: version "2.1.18" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" dependencies: @@ -6864,13 +6858,17 @@ mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" -mime@^1.3.4, mime@^1.4.1, mime@^1.5.0: +mime@^1.3.4, mime@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + +mimic-response@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" min-document@^2.19.0: version "2.19.0" @@ -6879,8 +6877,8 @@ min-document@^2.19.0: dom-walk "^0.1.0" minimalistic-assert@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" @@ -6914,21 +6912,22 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" -minipass@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.2.1.tgz#5ada97538b1027b4cf7213432428578cb564011f" +minipass@^2.2.1, minipass@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.2.4.tgz#03c824d84551ec38a8d1bb5bc350a5a30a354a40" dependencies: + safe-buffer "^5.1.1" yallist "^3.0.0" -minizlib@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.0.4.tgz#8ebb51dd8bbe40b0126b5633dbb36b284a2f523c" +minizlib@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" dependencies: minipass "^2.2.1" -mississippi@^1.2.0, mississippi@^1.3.0, mississippi@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.0.tgz#d201583eb12327e3c5c1642a404a9cacf94e34f5" +mississippi@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.1.tgz#2a8bb465e86550ac8b36a7b6f45599171d78671e" dependencies: concat-stream "^1.5.0" duplexify "^3.4.2" @@ -6941,12 +6940,42 @@ mississippi@^1.2.0, mississippi@^1.3.0, mississippi@~1.3.0: stream-each "^1.1.0" through2 "^2.0.0" +mississippi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f" + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^2.0.1" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + mixin-deep@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.2.0.tgz#d02b8c6f8b6d4b8f5982d3fd009c4919851c3fe2" + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" dependencies: for-in "^1.0.2" - is-extendable "^0.1.1" + is-extendable "^1.0.1" mixin-object@^2.0.1: version "2.0.1" @@ -6972,22 +7001,22 @@ mobx-react-devtools@^4.2.15: resolved "https://registry.yarnpkg.com/mobx-react-devtools/-/mobx-react-devtools-4.2.15.tgz#881c038fb83db4dffd1e72bbaf5374d26b2fdebb" mobx-react@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-4.3.5.tgz#76853f2f2ef4a6f960c374bcd9f01e875929c04c" + version "4.4.3" + resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-4.4.3.tgz#baa9ec41165ee35ae7b9df19bca10190f36f117e" dependencies: hoist-non-react-statics "^2.3.1" mobx-state-tree@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mobx-state-tree/-/mobx-state-tree-1.3.1.tgz#9e1ba9b8b6ea183f1a4a2ae1f67bfa8f2bcae4fe" + version "1.4.0" + resolved "https://registry.yarnpkg.com/mobx-state-tree/-/mobx-state-tree-1.4.0.tgz#c914c855d5ec5c1c16e4ba6d6925679df42c8110" mobx@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.4.1.tgz#37abe5ee882d401828d9f26c6c1a2f47614bbbef" + version "3.6.2" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.6.2.tgz#fb9f5ff5090539a1ad54e75dc4c098b602693320" mocha@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.0.1.tgz#0aee5a95cf69a4618820f5e51fa31717117daf1b" + version "4.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" dependencies: browser-stdout "1.3.0" commander "2.11.0" @@ -7001,8 +7030,8 @@ mocha@^4.0.1: supports-color "4.4.0" moment@^2.18.1: - version "2.19.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" + version "2.22.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" mousetrap-global-bind@^1.1.0: version "1.1.0" @@ -7012,7 +7041,7 @@ mousetrap@^1.6.0: version "1.6.1" resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.1.tgz#2a085f5c751294c75e7e81f6ec2545b29cbf42d9" -move-concurrently@^1.0.1, move-concurrently@~1.0.1: +move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" dependencies: @@ -7031,10 +7060,14 @@ ms@0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" -ms@2.0.0, ms@^2.0.0: +ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +ms@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -7063,25 +7096,9 @@ mute-stream@~0.0.4: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -nan@^2.0.5, nan@^2.3.0, nan@^2.3.2, nan@^2.6.2: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" - -nanomatch@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.5.tgz#5c9ab02475c76676275731b0bf0a7395c624a9c4" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - is-odd "^1.0.0" - kind-of "^5.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +nan@^2.10.0, nan@^2.6.2, nan@^2.9.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" nanomatch@^1.2.9: version "1.2.9" @@ -7119,17 +7136,34 @@ ncp@0.4.x: resolved "https://registry.yarnpkg.com/ncp/-/ncp-0.4.2.tgz#abcc6cbd3ec2ed2a729ff6e7c1fa8f01784a8574" nearley@^2.7.10: - version "2.11.0" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.11.0.tgz#5e626c79a6cd2f6ab9e7e5d5805e7668967757ae" + version "2.13.0" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.13.0.tgz#6e7b0f4e68bfc3e74c99eaef2eda39e513143439" dependencies: nomnom "~1.6.2" railroad-diagrams "^1.0.0" - randexp "^0.4.2" + randexp "0.4.6" + semver "^5.4.1" + +needle@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.0.tgz#f14efc69cee1024b72c8b21c7bdf94a731dc12fa" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +neo-async@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee" + +next-tick@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + ng-annotate-loader@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/ng-annotate-loader/-/ng-annotate-loader-0.6.1.tgz#e9b7b7a1562b9c79737d50886d558de7f0df4257" @@ -7188,15 +7222,15 @@ ngtemplate-loader@^2.0.1: jsesc "^0.5.0" loader-utils "^1.0.2" -no-case@^2.2.0: +no-case@^2.2.0, no-case@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" dependencies: lower-case "^1.1.1" -node-abi@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.1.2.tgz#4da6caceb6685fcd31e7dd1994ef6bb7d0a9c0b2" +node-abi@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.4.0.tgz#3c27515cb842f5bbc132a31254f9f1e1c55c7b83" dependencies: semver "^5.4.1" @@ -7219,7 +7253,7 @@ node-forge@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300" -node-gyp@^3.3.1, node-gyp@^3.6.2, node-gyp@~3.6.2: +node-gyp@^3.3.1, node-gyp@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" dependencies: @@ -7269,34 +7303,33 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" -node-notifier@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" +node-notifier@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" dependencies: growly "^1.3.0" - semver "^5.3.0" - shellwords "^0.1.0" - which "^1.2.12" + semver "^5.4.1" + shellwords "^0.1.1" + which "^1.3.0" -node-pre-gyp@^0.6.39: - version "0.6.39" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" +node-pre-gyp@^0.9.0: + version "0.9.1" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz#f11c07516dd92f87199dbc7e1838eab7cd56c9e0" dependencies: detect-libc "^1.0.2" - hawk "3.1.3" mkdirp "^0.5.1" + needle "^2.2.0" nopt "^4.0.1" + npm-packlist "^1.1.6" npmlog "^4.0.2" rc "^1.1.7" - request "2.81.0" rimraf "^2.6.1" semver "^5.3.0" - tar "^2.2.1" - tar-pack "^3.4.0" + tar "^4" -node-sass@^4.0.0: - version "4.7.2" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e" +node-sass@^4.7.2: + version "4.9.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.0.tgz#d1b8aa855d98ed684d6848db929a20771cc2ae52" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -7310,7 +7343,7 @@ node-sass@^4.0.0: lodash.mergewith "^4.6.0" meow "^3.7.0" mkdirp "^0.5.1" - nan "^2.3.2" + nan "^2.10.0" node-gyp "^3.3.1" npmlog "^4.0.0" request "~2.79.0" @@ -7318,7 +7351,7 @@ node-sass@^4.0.0: stdout-stream "^1.4.0" "true-case-path" "^1.0.2" -"nomnom@>= 1.5.x": +nomnom@^1.5.x: version "1.8.1" resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7" dependencies: @@ -7399,17 +7432,33 @@ npm-install-checks@~3.0.0: dependencies: semver "^2.3.0 || 3.x || 4 || 5" -npm-lifecycle@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-1.0.3.tgz#4cd60543247dbba631281e48ce665ffd52380cce" +npm-lifecycle@^2.0.0, npm-lifecycle@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-2.0.1.tgz#897313f05ed24db8e28d99fa8b42c31b625e6237" dependencies: + byline "^5.0.0" graceful-fs "^4.1.11" + node-gyp "^3.6.2" + resolve-from "^4.0.0" slide "^1.1.6" uid-number "0.0.6" umask "^1.1.0" which "^1.3.0" -"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0", "npm-package-arg@^4.0.0 || ^5.0.0", npm-package-arg@^5.1.2, npm-package-arg@~5.1.2: +npm-logical-tree@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz#44610141ca24664cad35d1e607176193fd8f5b88" + +"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", "npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", npm-package-arg@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1" + dependencies: + hosted-git-info "^2.6.0" + osenv "^0.1.5" + semver "^5.5.0" + validate-npm-package-name "^3.0.0" + +npm-package-arg@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-5.1.2.tgz#fb18d17bb61e60900d6312619919bd753755ab37" dependencies: @@ -7418,7 +7467,16 @@ npm-lifecycle@~1.0.3: semver "^5.1.0" validate-npm-package-name "^3.0.0" -npm-packlist@^1.1.6, npm-packlist@~1.1.9: +npm-package-arg@~6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.0.0.tgz#8cce04b49d3f9faec3f56b0fe5f4391aeb9d2fac" + dependencies: + hosted-git-info "^2.5.0" + osenv "^0.1.4" + semver "^5.4.1" + validate-npm-package-name "^3.0.0" + +npm-packlist@^1.1.10, npm-packlist@^1.1.6, npm-packlist@~1.1.10: version "1.1.10" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a" dependencies: @@ -7426,39 +7484,40 @@ npm-packlist@^1.1.6, npm-packlist@~1.1.9: npm-bundled "^1.0.1" npm-path@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.3.tgz#15cff4e1c89a38da77f56f6055b24f975dfb2bbe" + version "2.0.4" + resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64" dependencies: which "^1.2.10" -npm-pick-manifest@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-1.0.4.tgz#a5ee6510c1fe7221c0bc0414e70924c14045f7e8" +npm-pick-manifest@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.1.0.tgz#dc381bdd670c35d81655e1d5a94aa3dd4d87fce5" dependencies: - npm-package-arg "^5.1.2" - semver "^5.3.0" + npm-package-arg "^6.0.0" + semver "^5.4.1" -npm-profile@~2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-2.0.5.tgz#0e61b8f1611bd19d1eeff5e3d5c82e557da3b9d7" +npm-profile@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-3.0.1.tgz#65a1018340f14399a086b5d0a9bd0d13145d8e57" dependencies: aproba "^1.1.2" make-fetch-happen "^2.5.0" -npm-registry-client@~8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.5.0.tgz#4878fb6fa1f18a5dc08ae83acf94d0d0112d7ed0" +npm-registry-client@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.5.1.tgz#8115809c0a4b40938b8a109b8ea74d26c6f5d7f1" dependencies: concat-stream "^1.5.2" graceful-fs "^4.1.6" normalize-package-data "~1.0.1 || ^2.0.0" - npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0" + npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" once "^1.3.3" request "^2.74.0" retry "^0.10.0" + safe-buffer "^5.1.1" semver "2 >=2.2.1 || 3.x || 4 || 5" slide "^1.1.3" - ssri "^4.1.2" + ssri "^5.2.4" optionalDependencies: npmlog "2 || ^3.1.0 || ^4.0.0" @@ -7481,18 +7540,19 @@ npm-which@^3.0.1: which "^1.2.10" npm@^5.4.2: - version "5.5.1" - resolved "https://registry.yarnpkg.com/npm/-/npm-5.5.1.tgz#5bef2b01c51c8144412d5873caf83e22f1ec6b84" + version "5.8.0" + resolved "https://registry.yarnpkg.com/npm/-/npm-5.8.0.tgz#5e4bfb8c2e7ada01dd41ec0555d13dd0f446ddb2" dependencies: - JSONStream "~1.3.1" + JSONStream "^1.3.2" abbrev "~1.1.1" ansi-regex "~3.0.0" ansicolors "~0.3.2" ansistyles "~0.1.3" aproba "~1.2.0" archy "~1.0.0" - bluebird "~3.5.0" - cacache "~9.2.9" + bin-links "^1.1.0" + bluebird "~3.5.1" + cacache "^10.0.4" call-limit "~1.1.0" chownr "~1.0.1" cli-table2 "~0.2.0" @@ -7500,22 +7560,27 @@ npm@^5.4.2: columnify "~1.5.4" config-chain "~1.1.11" detect-indent "~5.0.0" + detect-newline "^2.1.0" dezalgo "~1.0.3" editor "~1.0.0" + find-npm-prefix "^1.0.2" fs-vacuum "~1.2.10" fs-write-stream-atomic "~1.0.10" + gentle-fs "^2.0.1" glob "~7.1.2" graceful-fs "~4.1.11" has-unicode "~2.0.1" - hosted-git-info "~2.5.0" + hosted-git-info "^2.6.0" iferr "~0.1.5" inflight "~1.0.6" inherits "~2.0.3" - ini "~1.3.4" - init-package-json "~1.10.1" + ini "^1.3.5" + init-package-json "^1.10.3" is-cidr "~1.0.0" + json-parse-better-errors "^1.0.1" lazy-property "~1.0.0" - libnpx "~9.6.0" + libcipm "^1.6.0" + libnpx "^10.0.1" lockfile "~1.0.3" lodash._baseuniq "~4.6.0" lodash.clonedeep "~4.5.0" @@ -7524,60 +7589,59 @@ npm@^5.4.2: lodash.without "~4.4.0" lru-cache "~4.1.1" meant "~1.0.1" - mississippi "~1.3.0" + mississippi "^3.0.0" mkdirp "~0.5.1" - move-concurrently "~1.0.1" - node-gyp "~3.6.2" + move-concurrently "^1.0.1" nopt "~4.0.1" normalize-package-data "~2.4.0" npm-cache-filename "~1.0.2" npm-install-checks "~3.0.0" - npm-lifecycle "~1.0.3" - npm-package-arg "~5.1.2" - npm-packlist "~1.1.9" - npm-profile "~2.0.4" - npm-registry-client "~8.5.0" + npm-lifecycle "^2.0.1" + npm-package-arg "~6.0.0" + npm-packlist "~1.1.10" + npm-profile "^3.0.1" + npm-registry-client "^8.5.1" npm-user-validate "~1.0.0" npmlog "~4.1.2" once "~1.4.0" opener "~1.4.3" - osenv "~0.1.4" - pacote "~6.0.2" + osenv "^0.1.5" + pacote "^7.6.1" path-is-inside "~1.0.2" promise-inflight "~1.0.1" qrcode-terminal "~0.11.0" - query-string "~5.0.0" + query-string "^5.1.0" qw "~1.0.1" read "~1.0.7" read-cmd-shim "~1.0.1" read-installed "~4.0.3" - read-package-json "~2.0.12" + read-package-json "^2.0.13" read-package-tree "~5.1.6" - readable-stream "~2.3.3" + readable-stream "^2.3.5" request "~2.83.0" retry "~0.10.1" rimraf "~2.6.2" safe-buffer "~5.1.1" - semver "~5.4.1" + semver "^5.5.0" sha "~2.0.1" slide "~1.1.6" sorted-object "~2.0.1" sorted-union-stream "~2.1.3" - ssri "~4.1.6" + ssri "^5.2.4" strip-ansi "~4.0.0" - tar "~4.0.1" + tar "^4.4.0" text-table "~0.2.0" uid-number "0.0.6" umask "~1.1.0" unique-filename "~1.1.0" unpipe "~1.0.0" - update-notifier "~2.2.0" - uuid "~3.1.0" + update-notifier "~2.3.0" + uuid "^3.2.1" validate-npm-package-name "~3.0.0" which "~1.3.0" - worker-farm "~1.5.0" + worker-farm "^1.5.4" wrappy "~1.0.2" - write-file-atomic "~2.1.0" + write-file-atomic "^2.3.0" "npmlog@0 || 1 || 2 || 3 || 4", "npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.0.2, npmlog@~4.1.2: version "4.1.2" @@ -7607,8 +7671,8 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" nwmatcher@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" @@ -7721,7 +7785,7 @@ once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0: onetime@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" opener@^1.4.3, opener@~1.4.3: version "1.4.3" @@ -7806,9 +7870,9 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -osenv@0, osenv@^0.1.4, osenv@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" +osenv@0, osenv@^0.1.4, osenv@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" @@ -7818,8 +7882,10 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" p-limit@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + dependencies: + p-try "^1.0.0" p-locate@^2.0.0: version "2.0.0" @@ -7831,6 +7897,10 @@ p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + package-json@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" @@ -7840,29 +7910,32 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" -pacote@~6.0.2: - version "6.0.4" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-6.0.4.tgz#9384c4ca9a9dbbaa625bfbe653e0330eeaa1427b" +pacote@^7.5.1, pacote@^7.6.1: + version "7.6.1" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-7.6.1.tgz#d44621c89a5a61f173989b60236757728387c094" dependencies: - bluebird "^3.5.0" - cacache "^9.2.9" + bluebird "^3.5.1" + cacache "^10.0.4" + get-stream "^3.0.0" glob "^7.1.2" lru-cache "^4.1.1" - make-fetch-happen "^2.4.13" + make-fetch-happen "^2.6.0" minimatch "^3.0.4" - mississippi "^1.2.0" + mississippi "^3.0.0" + mkdirp "^0.5.1" normalize-package-data "^2.4.0" - npm-package-arg "^5.1.2" - npm-packlist "^1.1.6" - npm-pick-manifest "^1.0.4" - osenv "^0.1.4" + npm-package-arg "^6.0.0" + npm-packlist "^1.1.10" + npm-pick-manifest "^2.1.0" + osenv "^0.1.5" promise-inflight "^1.0.1" promise-retry "^1.1.1" - protoduck "^4.0.0" + protoduck "^5.0.0" + rimraf "^2.6.2" safe-buffer "^5.1.1" - semver "^5.4.1" - ssri "^4.1.6" - tar "^4.0.0" + semver "^5.5.0" + ssri "^5.2.4" + tar "^4.4.0" unique-filename "^1.1.0" which "^1.3.0" @@ -7889,8 +7962,8 @@ param-case@2.1.x, param-case@^2.1.0: no-case "^2.2.0" parse-asn1@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -7913,13 +7986,18 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse-json@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-3.0.0.tgz#fa6f47b18e23826ead32f263e744d0e1e847fb13" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" dependencies: error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" -parse5@^3.0.1, parse5@^3.0.2: +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + +parse5@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" dependencies: @@ -8025,8 +8103,8 @@ pathval@~0.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-0.1.1.tgz#08f911cdca9cce5942880da7817bc0b723b66d82" pbkdf2@^3.0.3: - version "3.0.14" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" + version "3.0.16" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -8038,10 +8116,6 @@ pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" -performance-now@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -8108,13 +8182,13 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" -pn@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pn/-/pn-1.0.0.tgz#1cf5a30b0d806cd18f88fc41a6b5d4ad615b3ba9" +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" popper.js@^1.12.5: - version "1.12.9" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.12.9.tgz#0dfbc2dff96c451bb332edcfcfaaf566d331d5b3" + version "1.14.3" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" portfinder@^1.0.9: version "1.0.13" @@ -8219,13 +8293,13 @@ postcss-load-plugins@^2.3.0: object-assign "^4.1.0" postcss-loader@^2.0.6: - version "2.0.9" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.9.tgz#001fdf7bfeeb159405ee61d1bb8e59b528dbd309" + version "2.1.4" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.4.tgz#f44a6390e03c84108b2b2063182d1a1011b2ce76" dependencies: loader-utils "^1.1.0" postcss "^6.0.0" postcss-load-config "^1.2.0" - schema-utils "^0.3.0" + schema-utils "^0.4.0" postcss-merge-idents@^2.1.5: version "2.1.7" @@ -8288,27 +8362,27 @@ postcss-minify-selectors@^2.0.4: postcss "^5.0.14" postcss-selector-parser "^2.0.0" -postcss-modules-extract-imports@^1.0.0: +postcss-modules-extract-imports@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" dependencies: postcss "^6.0.1" -postcss-modules-local-by-default@^1.0.1: +postcss-modules-local-by-default@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" -postcss-modules-scope@^1.0.0: +postcss-modules-scope@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" -postcss-modules-values@^1.1.0: +postcss-modules-values@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" dependencies: @@ -8414,12 +8488,12 @@ postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0. supports-color "^3.2.3" postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.8: - version "6.0.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885" + version "6.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.21.tgz#8265662694eddf9e9a5960db6da33c39e4cd069d" dependencies: - chalk "^2.3.0" + chalk "^2.3.2" source-map "^0.6.1" - supports-color "^4.4.0" + supports-color "^5.3.0" power-assert-context-formatter@^1.0.7: version "1.1.1" @@ -8500,8 +8574,8 @@ power-assert-util-string-width@^1.1.1: eastasianwidth "^0.1.1" power-assert@^1.2.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/power-assert/-/power-assert-1.4.4.tgz#9295ea7437196f5a601fde420f042631186d7517" + version "1.5.0" + resolved "https://registry.yarnpkg.com/power-assert/-/power-assert-1.5.0.tgz#624caa76a5dc228c00f36704bb1762657c174fee" dependencies: define-properties "^1.1.2" empower "^1.2.3" @@ -8510,23 +8584,24 @@ power-assert@^1.2.0: xtend "^4.0.0" prebuild-install@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.3.0.tgz#19481247df728b854ab57b187ce234211311b485" + version "2.5.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.5.3.tgz#9f65f242782d370296353710e9bc843490c19f69" dependencies: + detect-libc "^1.0.3" expand-template "^1.0.2" github-from-package "0.0.0" minimist "^1.2.0" mkdirp "^0.5.1" - node-abi "^2.1.1" + node-abi "^2.2.0" noop-logger "^0.1.1" npmlog "^4.0.1" os-homedir "^1.0.1" - pump "^1.0.1" + pump "^2.0.1" rc "^1.1.6" - simple-get "^1.4.2" + simple-get "^2.7.0" tar-fs "^1.13.0" tunnel-agent "^0.6.0" - xtend "4.0.1" + which-pm-runs "^1.0.0" prelude-ls@~1.1.2: version "1.1.2" @@ -8569,21 +8644,23 @@ pretty-format@^21.2.1: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -pretty-format@^22.0.3: - version "22.0.3" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.0.3.tgz#a2bfa59fc33ad24aa4429981bb52524b41ba5dd7" +pretty-format@^22.4.3: + version "22.4.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f" dependencies: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +prismjs@^1.6.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.14.0.tgz#bbccfdb8be5d850d26453933cb50122ca0362ae0" + optionalDependencies: + clipboard "^2.0.0" + private@^0.1.6, private@^0.1.7, private@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -8633,15 +8710,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" - -prop-types@^15.6.1: +prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1: version "15.6.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" dependencies: @@ -8653,19 +8722,12 @@ proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" -protoduck@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-4.0.0.tgz#fe4874d8c7913366cfd9ead12453a22cd3657f8e" +protoduck@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.0.tgz#752145e6be0ad834cb25716f670a713c860dce70" dependencies: genfun "^4.0.1" -proxy-addr@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.5.2" - proxy-addr@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" @@ -8673,17 +8735,17 @@ proxy-addr@~2.0.3: forwarded "~0.1.2" ipaddr.js "1.6.0" -prr@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" public-encrypt@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" + version "4.0.2" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" dependencies: bn.js "^4.1.0" browserify-rsa "^4.0.0" @@ -8691,20 +8753,34 @@ public-encrypt@^4.0.0: parse-asn1 "^5.0.0" randombytes "^2.0.1" -pump@^1.0.0, pump@^1.0.1: +pump@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" dependencies: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: - version "1.3.5" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.3.5.tgz#1b671c619940abcaeac0ad0e3a3c164be760993b" +pump@^2.0.0, pump@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" dependencies: - duplexify "^3.1.2" - inherits "^2.0.1" - pump "^1.0.0" + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" + dependencies: + duplexify "^3.5.3" + inherits "^2.0.3" + pump "^2.0.0" punycode@1.3.2: version "1.3.2" @@ -8723,8 +8799,8 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" qjobs@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73" + version "1.2.0" + resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" qrcode-terminal@~0.11.0: version "0.11.0" @@ -8738,10 +8814,6 @@ qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -8749,9 +8821,9 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@~5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.0.1.tgz#6e2b86fe0e08aef682ecbe86e85834765402bd88" +query-string@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" dependencies: decode-uri-component "^0.2.0" object-assign "^4.1.0" @@ -8769,9 +8841,9 @@ querystringify@0.0.x: version "0.0.4" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" -querystringify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" +querystringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" qw@~1.0.1: version "1.0.1" @@ -8787,7 +8859,7 @@ railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" -randexp@^0.4.2: +randexp@0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" dependencies: @@ -8802,14 +8874,14 @@ randomatic@^1.1.3: kind-of "^4.0.0" randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79" + version "2.0.6" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" dependencies: safe-buffer "^5.1.0" randomfill@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62" + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" dependencies: randombytes "^2.0.5" safe-buffer "^5.1.0" @@ -8828,8 +8900,8 @@ raw-body@2.3.2: unpipe "1.0.0" rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: - version "1.2.2" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" + version "1.2.6" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -8837,22 +8909,15 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: strip-json-comments "~2.0.1" react-dom@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.0" -"react-draggable@^2.2.6 || ^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.0.3.tgz#a6f9b3a7171981b76dadecf238316925cb9eacf4" - dependencies: - classnames "^2.2.5" - prop-types "^15.5.10" - -react-draggable@^3.0.3: +"react-draggable@^2.2.6 || ^3.0.3", react-draggable@^3.0.3: version "3.0.5" resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.0.5.tgz#c031e0ed4313531f9409d6cd84c8ebcec0ddfe2d" dependencies: @@ -8877,21 +8942,34 @@ react-highlight-words@^0.10.0: prop-types "^15.5.8" react-hot-loader@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.0.1.tgz#48284350ae5d7ba07dac872bd5bbc6e477352593" + version "4.1.2" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.1.2.tgz#5e8025f5bc5605506586b46eb2c6cc4006fd54d7" dependencies: fast-levenshtein "^2.0.6" global "^4.3.0" hoist-non-react-statics "^2.5.0" prop-types "^15.6.1" + react-lifecycles-compat "^3.0.2" shallowequal "^1.0.2" +react-immutable-proptypes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4" + react-input-autosize@^2.1.2: version "2.2.1" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8" dependencies: prop-types "^15.5.8" +react-is@^16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22" + +react-lifecycles-compat@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.2.tgz#7279047275bd727a912e25f734c0559527e84eff" + react-popper@^0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.7.5.tgz#71c25946f291db381231281f6b95729e8b801596" @@ -8899,6 +8977,21 @@ react-popper@^0.7.5: popper.js "^1.12.5" prop-types "^15.5.10" +react-portal@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-3.2.0.tgz#4224e19b2b05d5cbe730a7ba0e34ec7585de0043" + dependencies: + prop-types "^15.5.8" + +react-reconciler@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + react-resizable@^1.7.5: version "1.7.5" resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e" @@ -8907,50 +9000,49 @@ react-resizable@^1.7.5: react-draggable "^2.2.6 || ^3.0.3" react-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.2.0.tgz#4f91df941c4ecdb94701faca2533b60e31d7508e" + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.2.1.tgz#a2fe58a569eb14dcaa6543816260b97e538120d1" dependencies: classnames "^2.2.4" prop-types "^15.5.8" react-input-autosize "^2.1.2" react-sizeme@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.3.6.tgz#d60ea2634acc3fd827a3c7738d41eea0992fa678" + version "2.4.2" + resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.4.2.tgz#9e1683f926f92b3db7881d09f9efa3879e8dfde2" dependencies: element-resize-detector "^1.1.12" invariant "^2.2.2" - lodash "^4.17.4" + lodash.debounce "^4.0.8" + lodash.throttle "^4.1.1" react-test-renderer@^16.0.0, react-test-renderer@^16.0.0-0: - version "16.1.1" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.1.1.tgz#a05184688d564be799f212449262525d1e350537" + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.2.tgz#3d1ed74fda8db42521fdf03328e933312214749a" dependencies: fbjs "^0.8.16" object-assign "^4.1.1" prop-types "^15.6.0" + react-is "^16.3.2" react-transition-group@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10" + version "2.3.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.3.1.tgz#31d611b33e143a5e0f2d94c348e026a0f3b474b6" dependencies: - chain-function "^1.0.0" - classnames "^2.2.5" - dom-helpers "^3.2.0" + dom-helpers "^3.3.1" loose-envify "^1.3.1" - prop-types "^15.5.8" - warning "^3.0.0" + prop-types "^15.6.1" react@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" + version "16.3.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.0" -read-cmd-shim@~1.0.1: +read-cmd-shim@^1.0.1, read-cmd-shim@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" dependencies: @@ -8969,12 +9061,12 @@ read-installed@~4.0.3: optionalDependencies: graceful-fs "^4.1.2" -"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@~2.0.12: - version "2.0.12" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.12.tgz#68ea45f98b3741cb6e10ae3bbd42a605026a6951" +"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.12, read-package-json@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.13.tgz#2e82ebd9f613baa6d2ebe3aa72cefe3f68e41f4a" dependencies: glob "^7.1.1" - json-parse-better-errors "^1.0.0" + json-parse-better-errors "^1.0.1" normalize-package-data "^2.0.0" slash "^1.0.0" optionalDependencies: @@ -9026,16 +9118,16 @@ read@1, read@1.0.x, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.3.3, readable-stream@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" isarray "~1.0.0" - process-nextick-args "~1.0.6" + process-nextick-args "~2.0.0" safe-buffer "~5.1.1" - string_decoder "~1.0.3" + string_decoder "~1.1.1" util-deprecate "~1.0.1" readable-stream@1.0, readable-stream@~1.0.2: @@ -9056,18 +9148,6 @@ readable-stream@1.1: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.2.9: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@~1.1.10: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -9152,8 +9232,8 @@ regenerator-runtime@^0.10.5: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" regenerator-runtime@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" regenerator-transform@^0.10.0: version "0.10.1" @@ -9169,13 +9249,7 @@ regex-cache@^0.4.2: dependencies: is-equal-shallow "^0.1.3" -regex-not@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9" - dependencies: - extend-shallow "^2.0.1" - -regex-not@^1.0.2: +regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" dependencies: @@ -9199,8 +9273,8 @@ regexpu-core@^2.0.0: regjsparser "^0.1.4" registry-auth-token@^3.0.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006" + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" dependencies: rc "^1.1.6" safe-buffer "^5.0.1" @@ -9276,7 +9350,7 @@ request-promise-core@1.1.1: dependencies: lodash "^4.13.1" -request-promise-native@^1.0.3: +request-promise-native@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5" dependencies: @@ -9284,9 +9358,9 @@ request-promise-native@^1.0.3: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@2, request@^2.74.0, request@^2.81.0, request@^2.83.0, request@~2.83.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" +request@2, request@^2.74.0, request@^2.81.0, request@^2.83.0: + version "2.85.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" dependencies: aws-sign2 "~0.7.0" aws4 "^1.6.0" @@ -9311,33 +9385,6 @@ request@2, request@^2.74.0, request@^2.81.0, request@^2.83.0, request@~2.83.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" - request@~2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -9363,6 +9410,33 @@ request@~2.79.0: tunnel-agent "~0.4.1" uuid "^3.0.0" +request@~2.83.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -9372,8 +9446,8 @@ require-from-string@^1.1.0: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" require-from-string@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.1.tgz#c545233e9d7da6616e9d59adfb39fc9f588676ff" + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" require-main-filename@^1.0.1: version "1.0.1" @@ -9386,7 +9460,7 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" -requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0: +requires-port@1.0.x, requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -9412,6 +9486,10 @@ resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolve-pkg@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-0.1.0.tgz#02cc993410e2936962bd97166a1b077da9725531" @@ -9427,8 +9505,8 @@ resolve@1.1.7, resolve@~1.1.0: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" dependencies: path-parse "^1.0.5" @@ -9466,7 +9544,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@~2.6.2: +rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -9477,10 +9555,10 @@ rimraf@~2.2.8: resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" dependencies: - hash-base "^2.0.0" + hash-base "^3.0.0" inherits "^2.0.1" rst-selector-parser@^2.2.3: @@ -9516,28 +9594,30 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -rxjs@^5.4.2: - version "5.5.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.5.tgz#e164f11d38eaf29f56f08c3447f74ff02dd84e97" +rxjs@^5.4.2, rxjs@^5.4.3: + version "5.5.10" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.10.tgz#fde02d7a614f6c8683d0d1957827f492e09db045" dependencies: symbol-observable "1.0.1" -rxjs@^5.4.3: - version "5.5.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.2.tgz#28d403f0071121967f18ad665563255d54236ac3" - dependencies: - symbol-observable "^1.0.1" - -safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" dependencies: ret "~0.1.10" +safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + samsam@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" @@ -9547,13 +9627,13 @@ samsam@~1.1: resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" sane@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" + version "2.5.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.0.tgz#6359cd676f5efd9988b264d8ce3b827dd6b27bec" dependencies: - anymatch "^1.3.0" + anymatch "^2.0.0" exec-sh "^0.2.0" fb-watchman "^2.0.0" - minimatch "^3.0.2" + micromatch "^3.1.4" minimist "^1.1.1" walker "~1.0.5" watch "~0.18.0" @@ -9589,16 +9669,16 @@ sass-lint@^1.10.2, sass-lint@^1.12.0: util "^0.10.3" sass-loader@^6.0.6: - version "6.0.6" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9" + version "6.0.7" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.7.tgz#dd2fdb3e7eeff4a53f35ba6ac408715488353d00" dependencies: - async "^2.1.5" - clone-deep "^0.3.0" + clone-deep "^2.0.1" loader-utils "^1.0.1" lodash.tail "^4.1.1" + neo-async "^2.5.0" pify "^3.0.0" -sax@^1.2.1, sax@~1.2.1: +sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -9608,7 +9688,7 @@ schema-utils@^0.3.0: dependencies: ajv "^5.0.0" -schema-utils@^0.4.5: +schema-utils@^0.4.0, schema-utils@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" dependencies: @@ -9630,6 +9710,10 @@ select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" +selection-is-backward@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/selection-is-backward/-/selection-is-backward-1.0.0.tgz#97a54633188a511aba6419fc5c1fa91b467e6be1" + selfsigned@^1.9.1: version "1.10.2" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.2.tgz#b4449580d99929b65b10a48389301a6592088758" @@ -9642,9 +9726,9 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@~5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" semver@~4.3.3: version "4.3.6" @@ -9654,24 +9738,6 @@ semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" -send@0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" - dependencies: - debug "2.6.9" - depd "~1.1.1" - destroy "~1.0.4" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.3.1" - send@0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" @@ -9709,15 +9775,6 @@ serve-index@^1.7.2: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" - dependencies: - encodeurl "~1.0.1" - escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.1" - serve-static@1.13.2: version "1.13.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" @@ -9731,12 +9788,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" -set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - dependencies: - to-object-path "^0.3.0" - set-immediate-shim@^1.0.0, set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" @@ -9772,8 +9823,8 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.9" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d" + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" @@ -9785,13 +9836,12 @@ sha@~2.0.1: graceful-fs "^4.1.2" readable-stream "^2.0.2" -shallow-clone@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" +shallow-clone@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" dependencies: is-extendable "^0.1.1" - kind-of "^2.0.1" - lazy-cache "^0.2.3" + kind-of "^5.0.0" mixin-object "^2.0.1" shallowequal@^1.0.2: @@ -9825,7 +9875,7 @@ shelljs@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" -shellwords@^0.1.0: +shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -9833,17 +9883,21 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + simple-fmt@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/simple-fmt/-/simple-fmt-0.1.0.tgz#191bf566a59e6530482cb25ab53b4a8dc85c3a6b" -simple-get@^1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-1.4.3.tgz#e9755eda407e96da40c5e5158c9ea37b33becbeb" +simple-get@^2.7.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" dependencies: + decompress-response "^3.3.0" once "^1.3.1" - unzip-response "^1.0.0" - xtend "^4.0.0" + simple-concat "^1.0.0" simple-is@~0.2.0: version "0.2.0" @@ -9862,11 +9916,71 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" +slate-base64-serializer@^0.2.29: + version "0.2.29" + resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.2.29.tgz#eaf4c92296a52510023ac36dd39a34a6b7a5df00" + dependencies: + isomorphic-base64 "^1.0.2" + +slate-dev-logger@^0.1.39: + version "0.1.39" + resolved "https://registry.yarnpkg.com/slate-dev-logger/-/slate-dev-logger-0.1.39.tgz#744a69b85034244713e6de51483af5713c345af4" + +slate-plain-serializer@^0.5.10: + version "0.5.10" + resolved "https://registry.yarnpkg.com/slate-plain-serializer/-/slate-plain-serializer-0.5.10.tgz#0a430824485f3dd4c7bf5bcae1bae37f13741c5c" + dependencies: + slate-dev-logger "^0.1.39" + +slate-prop-types@^0.4.27: + version "0.4.27" + resolved "https://registry.yarnpkg.com/slate-prop-types/-/slate-prop-types-0.4.27.tgz#9b3c13f0a1a1b034f8e66095a28bd537af03ba95" + dependencies: + slate-dev-logger "^0.1.39" + +slate-react@^0.12.4: + version "0.12.4" + resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.12.4.tgz#36407e38e7230e6cd0c93fa75e49d29bd447f983" + dependencies: + debug "^2.3.2" + get-window "^1.1.1" + is-hotkey "^0.1.1" + is-in-browser "^1.1.3" + is-window "^1.0.2" + keycode "^2.1.2" + lodash "^4.1.1" + prop-types "^15.5.8" + react-immutable-proptypes "^2.1.0" + react-portal "^3.1.0" + selection-is-backward "^1.0.0" + slate-base64-serializer "^0.2.29" + slate-dev-logger "^0.1.39" + slate-plain-serializer "^0.5.10" + slate-prop-types "^0.4.27" + +slate-schema-violations@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/slate-schema-violations/-/slate-schema-violations-0.1.8.tgz#b6bfbcc4defaa4971a96bf2d324c5b67fb4a4e20" + +slate@^0.33.4: + version "0.33.4" + resolved "https://registry.yarnpkg.com/slate/-/slate-0.33.4.tgz#8f39000a7a0fb1ec04b211c3e264e45677dfd9d8" + dependencies: + debug "^2.3.2" + direction "^0.1.5" + esrever "^0.2.0" + is-empty "^1.0.0" + is-plain-object "^2.0.4" + lodash "^4.17.4" + slate-dev-logger "^0.1.39" + slate-schema-violations "^0.1.8" + type-of "^2.0.1" + slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" -slide@^1.1.3, slide@^1.1.5, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6: +slide@^1.1.3, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -9895,8 +10009,8 @@ snapdragon-util@^3.0.1: kind-of "^3.2.0" snapdragon@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370" + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" dependencies: base "^0.11.1" debug "^2.2.0" @@ -9905,7 +10019,7 @@ snapdragon@^0.8.1: map-cache "^0.2.2" source-map "^0.5.6" source-map-resolve "^0.5.0" - use "^2.0.0" + use "^3.1.0" sntp@1.x.x: version "1.0.9" @@ -10036,10 +10150,11 @@ source-map-support@^0.4.0, source-map-support@^0.4.15: dependencies: source-map "^0.5.6" -source-map-support@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.0.tgz#2018a7ad2bdf8faf2691e5fddab26bed5a2bacab" +source-map-support@^0.5.0, source-map-support@^0.5.3: + version "0.5.5" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.5.tgz#0d4af9e00493e855402e8ec36ebed2d266fceb90" dependencies: + buffer-from "^1.0.0" source-map "^0.6.0" source-map-url@^0.4.0: @@ -10056,7 +10171,7 @@ source-map@0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" -source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3, source-map@~0.5.6: +source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -10064,19 +10179,27 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" dependencies: - spdx-license-ids "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" spdy-transport@^2.0.18: version "2.1.0" @@ -10107,13 +10230,17 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +sprintf-js@^1.0.3: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.14.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -10125,21 +10252,15 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -ssri@^4.1.2, ssri@^4.1.6, ssri@~4.1.6: - version "4.1.6" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-4.1.6.tgz#0cb49b6ac84457e7bdd466cb730c3cb623e9a25b" +ssri@^5.0.0, ssri@^5.2.4: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" dependencies: - safe-buffer "^5.1.0" - -ssri@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.0.0.tgz#13c19390b606c821f2a10d02b351c1729b94d8cf" - dependencies: - safe-buffer "^5.1.0" + safe-buffer "^5.1.1" stable@~0.1.3, stable@~0.1.5: - version "0.1.6" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.6.tgz#910f5d2aed7b520c6e777499c1f32e139fdecb10" + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" stack-parser@^0.0.1: version "0.0.1" @@ -10153,9 +10274,9 @@ stack-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" -staged-git-files@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-0.0.4.tgz#d797e1b551ca7a639dec0237dc6eb4bb9be17d35" +staged-git-files@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.0.0.tgz#cdb847837c1fcc52c08a872d4883cc0877668a80" static-extend@^0.1.1: version "0.1.2" @@ -10164,14 +10285,18 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.3.1 < 2", statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + stdout-stream@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" @@ -10201,12 +10326,12 @@ stream-each@^1.1.0: stream-shift "^1.0.0" stream-http@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad" + version "2.8.1" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.1.tgz#d0441be1a457a73a733a8a7b53570bebd9ef66a4" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" - readable-stream "^2.2.6" + readable-stream "^2.3.3" to-arraybuffer "^1.0.0" xtend "^4.0.0" @@ -10252,16 +10377,16 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0: +string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.0.0, string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" +string_decoder@^1.0.0, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" dependencies: safe-buffer "~5.1.0" @@ -10269,12 +10394,6 @@ string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - dependencies: - safe-buffer "~5.1.0" - stringifier@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/stringifier/-/stringifier-1.3.0.tgz#def18342f6933db0f2dbfc9aa02175b448c17959" @@ -10284,8 +10403,8 @@ stringifier@^1.3.0: type-name "^2.0.1" stringify-object@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.1.tgz#2720c2eff940854c819f6ee252aaeb581f30624d" + version "3.2.2" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd" dependencies: get-own-enumerable-property-symbols "^2.0.1" is-obj "^1.0.1" @@ -10380,13 +10499,13 @@ supports-color@^3.1.2, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" -supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0: +supports-color@^4.2.1: version "4.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" dependencies: has-flag "^2.0.0" -supports-color@^5.1.0: +supports-color@^5.1.0, supports-color@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" dependencies: @@ -10419,17 +10538,13 @@ symbol-observable@^0.2.2: version "0.2.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" -symbol-observable@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" - -symbol-tree@^3.2.1: +symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" systemjs-plugin-css@^0.1.36: - version "0.1.36" - resolved "https://registry.yarnpkg.com/systemjs-plugin-css/-/systemjs-plugin-css-0.1.36.tgz#1ab38811ae6dd71190cefe33f1fe41976a09387d" + version "0.1.37" + resolved "https://registry.yarnpkg.com/systemjs-plugin-css/-/systemjs-plugin-css-0.1.37.tgz#684847252ca69b7da24a1201094c86274324e82f" systemjs@0.20.19: version "0.20.19" @@ -10459,19 +10574,6 @@ tar-fs@^1.13.0: pump "^1.0.0" tar-stream "^1.1.2" -tar-pack@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" - dependencies: - debug "^2.2.0" - fstream "^1.0.10" - fstream-ignore "^1.0.5" - once "^1.3.3" - readable-stream "^2.1.4" - rimraf "^2.5.1" - tar "^2.2.1" - uid-number "^0.0.6" - tar-stream@^1.1.2, tar-stream@^1.5.0: version "1.5.5" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" @@ -10481,7 +10583,7 @@ tar-stream@^1.1.2, tar-stream@^1.5.0: readable-stream "^2.0.0" xtend "^4.0.0" -tar@^2.0.0, tar@^2.2.1: +tar@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: @@ -10489,14 +10591,16 @@ tar@^2.0.0, tar@^2.2.1: fstream "^1.0.2" inherits "2" -tar@^4.0.0, tar@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.0.2.tgz#e8e22bf3eec330e5c616d415a698395e294e8fad" +tar@^4, tar@^4.4.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.1.tgz#b25d5a8470c976fd7a9a8a350f42c59e9fa81749" dependencies: chownr "^1.0.1" - minipass "^2.2.1" - minizlib "^1.0.4" + fs-minipass "^1.2.5" + minipass "^2.2.4" + minizlib "^1.1.0" mkdirp "^0.5.0" + safe-buffer "^5.1.1" yallist "^3.0.2" term-size@^1.2.0: @@ -10505,12 +10609,12 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -test-exclude@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" +test-exclude@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" dependencies: arrify "^1.0.1" - micromatch "^2.3.11" + micromatch "^3.1.8" object-assign "^4.1.0" read-pkg-up "^1.0.1" require-main-filename "^1.0.1" @@ -10522,8 +10626,8 @@ test-exclude@^4.1.1: tether "^1.1.0" tether@^1.1.0, tether@^1.4.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.2.tgz#ab9605b5ecf38f088b3da3d54d2b439207e48d04" + version "1.4.4" + resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.4.tgz#9dc6eb2b3e601da2098fd264e7f7a8b264de1125" text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" @@ -10561,8 +10665,8 @@ timed-out@^4.0.0: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" timers-browserify@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" + version "2.0.10" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" dependencies: setimmediate "^1.0.4" @@ -10626,15 +10730,7 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae" - dependencies: - define-property "^0.2.5" - extend-shallow "^2.0.1" - regex-not "^1.0.0" - -to-regex@^3.0.2: +to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" dependencies: @@ -10652,12 +10748,12 @@ toposort@^1.0.0: resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec" tough-cookie@>=2.3.3, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" dependencies: punycode "^1.4.1" -tr46@^1.0.0: +tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" dependencies: @@ -10681,45 +10777,45 @@ trim-right@^1.0.1: dependencies: glob "^6.0.4" -tryit@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" +tryer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7" tryor@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/tryor/-/tryor-0.1.2.tgz#8145e4ca7caff40acde3ccf946e8b8bb75b4172b" ts-jest@^22.0.0: - version "22.0.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-22.0.0.tgz#cce1a5f1106150ca002d09f7e85913355ceb5e8d" + version "22.4.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-22.4.4.tgz#7b5c0abb2188fe7170840df9f80e78659aaf8a24" dependencies: - babel-core "^6.24.1" + babel-core "^6.26.0" babel-plugin-istanbul "^4.1.4" - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-preset-jest "^22.0.1" + babel-plugin-transform-es2015-modules-commonjs "^6.26.0" + babel-preset-jest "^22.4.0" cpx "^1.5.0" fs-extra "4.0.3" - jest-config "^22.0.1" + jest-config "^22.4.2" pkg-dir "^2.0.0" - source-map-support "^0.5.0" - yargs "^10.0.3" + yargs "^11.0.0" ts-loader@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-3.2.0.tgz#23211922179b81f7448754b7fdfca45b8374a15a" + version "3.5.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-3.5.0.tgz#151d004dcddb4cf8e381a3bf9d6b74c2d957a9c0" dependencies: chalk "^2.3.0" enhanced-resolve "^3.0.0" loader-utils "^1.0.2" + micromatch "^3.1.4" semver "^5.0.1" -tslib@^1.7.1: - version "1.8.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" +tslib@^1.8.0, tslib@^1.8.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" tslint-loader@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/tslint-loader/-/tslint-loader-3.5.3.tgz#343f74122d94f356b689457d3f59f64a69ab606f" + version "3.6.0" + resolved "https://registry.yarnpkg.com/tslint-loader/-/tslint-loader-3.6.0.tgz#12ed4d5ef57d68be25cd12692fb2108b66469d76" dependencies: loader-utils "^1.0.2" mkdirp "^0.5.1" @@ -10728,26 +10824,27 @@ tslint-loader@^3.5.3: semver "^5.3.0" tslint@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.8.0.tgz#1f49ad5b2e77c76c3af4ddcae552ae4e3612eb13" + version "5.9.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae" dependencies: babel-code-frame "^6.22.0" builtin-modules "^1.1.1" - chalk "^2.1.0" - commander "^2.9.0" + chalk "^2.3.0" + commander "^2.12.1" diff "^3.2.0" glob "^7.1.1" + js-yaml "^3.7.0" minimatch "^3.0.4" resolve "^1.3.2" semver "^5.3.0" - tslib "^1.7.1" + tslib "^1.8.0" tsutils "^2.12.1" tsutils@^2.12.1: - version "2.12.2" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.12.2.tgz#ad58a4865d17ec3ddb6631b6ca53be14a5656ff3" + version "2.26.2" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.26.2.tgz#a9f9f63434a456a5e0c95a45d9a59181cb32d3bf" dependencies: - tslib "^1.7.1" + tslib "^1.8.1" tty-browserify@0.0.0: version "0.0.0" @@ -10773,14 +10870,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.15: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.15" - -type-is@~1.6.16: +type-is@~1.6.15, type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: @@ -10791,13 +10881,17 @@ type-name@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/type-name/-/type-name-2.0.2.tgz#efe7d4123d8ac52afff7f40c7e4dec5266008fb4" +type-of@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-of/-/type-of-2.0.1.tgz#e72a1741896568e9f628378d816d6912f7f23972" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" typescript@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + version "2.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170" ua-parser-js@^0.7.9: version "0.7.17" @@ -10812,11 +10906,11 @@ uglify-js@2.6.x: uglify-to-browserify "~1.0.0" yargs "~3.10.0" -uglify-js@3.2.x: - version "3.2.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.2.0.tgz#cb411ee4ca0e0cadbfe3a4e1a1da97e6fa0d19c1" +uglify-js@3.3.x: + version "3.3.22" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.22.tgz#e5f0e50ddd386b7e35b728b51600bf7a7ad0b0dc" dependencies: - commander "~2.12.1" + commander "~2.15.0" source-map "~0.6.1" uglify-js@^2.6, uglify-js@^2.8.29: @@ -10840,7 +10934,7 @@ uglifyjs-webpack-plugin@^0.4.6: uglify-js "^2.8.29" webpack-sources "^1.0.1" -uid-number@0.0.6, uid-number@^0.0.6: +uid-number@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -10848,10 +10942,6 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - umask@^1.1.0, umask@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" @@ -10864,6 +10954,13 @@ underscore.string@~3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.2.3.tgz#806992633665d5e5fcb4db1fb3a862eb68e9e6da" +underscore.string@~3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.4.tgz#2c2a3f9f83e64762fdc45e6ceac65142864213db" + dependencies: + sprintf-js "^1.0.3" + util-deprecate "^1.0.2" + underscore@~1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" @@ -10946,10 +11043,6 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -unzip-response@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" - unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" @@ -10958,7 +11051,22 @@ upath@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" -update-notifier@^2.2.0: +update-notifier@^2.3.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" + dependencies: + boxen "^1.2.1" + chalk "^2.0.1" + configstore "^3.0.0" + import-lazy "^2.1.0" + is-ci "^1.0.10" + is-installed-globally "^0.1.0" + is-npm "^1.0.0" + latest-version "^3.0.0" + semver-diff "^2.0.0" + xdg-basedir "^3.0.0" + +update-notifier@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451" dependencies: @@ -10972,19 +11080,6 @@ update-notifier@^2.2.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" -update-notifier@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.2.0.tgz#1b5837cf90c0736d88627732b661c138f86de72f" - dependencies: - boxen "^1.0.0" - chalk "^1.0.0" - configstore "^3.0.0" - import-lazy "^2.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" - upper-case-first@^1.1.0, upper-case-first@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" @@ -11019,11 +11114,11 @@ url-parse@1.0.x: requires-port "1.0.x" url-parse@^1.1.8: - version "1.3.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.3.0.tgz#04a06c420d22beb9804f7ada2d57ad13160a4258" + version "1.4.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.0.tgz#6bfdaad60098c7fe06f623e42b22de62de0d3d75" dependencies: - querystringify "~1.0.0" - requires-port "~1.0.0" + querystringify "^2.0.0" + requires-port "^1.0.0" url@^0.11.0: version "0.11.0" @@ -11032,13 +11127,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -use@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" +use@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" dependencies: - define-property "^0.2.5" - isobject "^3.0.0" - lazy-cache "^2.0.2" + kind-of "^6.0.2" user-home@^2.0.0: version "2.0.0" @@ -11047,13 +11140,13 @@ user-home@^2.0.0: os-homedir "^1.0.0" useragent@^2.1.12: - version "2.2.1" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e" + version "2.3.0" + resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972" dependencies: - lru-cache "2.2.x" + lru-cache "4.1.x" tmp "0.0.x" -util-deprecate@~1.0.1: +util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -11101,20 +11194,16 @@ uuid@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0, uuid@^3.1.0, uuid@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - -uuid@^3.0.1: +uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0: version "3.0.0" @@ -11127,8 +11216,8 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" vendors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + version "1.0.2" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" verror@1.10.0: version "1.10.0" @@ -11171,6 +11260,12 @@ w3c-blob@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/w3c-blob/-/w3c-blob-0.0.1.tgz#b0cd352a1a50f515563420ffd5861f950f1d85b8" +w3c-hr-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" + dependencies: + browser-process-hrtime "^0.1.2" + walkdir@^0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.11.tgz#a16d025eb931bd03b52f308caed0f40fcebe9532" @@ -11181,12 +11276,6 @@ walker@~1.0.5: dependencies: makeerror "1.0.x" -warning@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" - dependencies: - loose-envify "^1.0.0" - watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" @@ -11195,12 +11284,12 @@ watch@~0.18.0: minimist "^1.2.0" watchpack@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" + version "1.5.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.5.0.tgz#231e783af830a22f8966f65c4c4bacc814072eed" dependencies: - async "^2.1.2" - chokidar "^1.7.0" + chokidar "^2.0.2" graceful-fs "^4.1.2" + neo-async "^2.5.0" wbuf@^1.1.0, wbuf@^1.7.2: version "1.7.3" @@ -11214,32 +11303,26 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" -weak@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/weak/-/weak-1.0.1.tgz#ab99aab30706959aa0200cb8cf545bb9cb33b99e" - dependencies: - bindings "^1.2.1" - nan "^2.0.5" - -webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: +webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" webpack-bundle-analyzer@^2.9.0: - version "2.9.1" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.9.1.tgz#c2c8e03e8e5768ed288b39ae9e27a8b8d7b9d476" + version "2.11.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.11.1.tgz#b9fbfb6a32c0a8c1c3237223e90890796b950ab9" dependencies: - acorn "^5.1.1" - chalk "^1.1.3" - commander "^2.9.0" - ejs "^2.5.6" - express "^4.15.2" - filesize "^3.5.9" - gzip-size "^3.0.0" + acorn "^5.3.0" + bfj-node4 "^5.2.0" + chalk "^2.3.0" + commander "^2.13.0" + ejs "^2.5.7" + express "^4.16.2" + filesize "^3.5.11" + gzip-size "^4.1.0" lodash "^4.17.4" mkdirp "^0.5.1" opener "^1.4.3" - ws "^3.3.1" + ws "^4.0.0" webpack-cleanup-plugin@^0.5.1: version "0.5.1" @@ -11256,7 +11339,7 @@ webpack-core@^0.6.5: source-list-map "~0.1.7" source-map "~0.4.1" -webpack-dev-middleware@1.12.2: +webpack-dev-middleware@1.12.2, webpack-dev-middleware@^1.12.0: version "1.12.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" dependencies: @@ -11266,16 +11349,6 @@ webpack-dev-middleware@1.12.2: range-parser "^1.0.3" time-stamp "^2.0.0" -webpack-dev-middleware@^1.12.0: - version "1.12.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.1.tgz#338be3ca930973be1c2ce07d84d275e997e1a25a" - dependencies: - memory-fs "~0.4.1" - mime "^1.4.1" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - time-stamp "^2.0.0" - webpack-dev-server@2.11.1: version "2.11.1" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.1.tgz#6f9358a002db8403f016e336816f4485384e5ec0" @@ -11309,10 +11382,10 @@ webpack-dev-server@2.11.1: yargs "6.6.0" webpack-merge@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.1.tgz#f1197a0a973e69c6fbeeb6d658219aa8c0c13555" + version "4.1.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.2.tgz#5d372dddd3e1e5f8874f5bf5a8e929db09feb216" dependencies: - lodash "^4.17.4" + lodash "^4.17.5" webpack-sources@^1.0.1: version "1.1.0" @@ -11322,13 +11395,13 @@ webpack-sources@^1.0.1: source-map "~0.6.1" webpack@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.10.0.tgz#5291b875078cf2abf42bdd23afe3f8f96c17d725" + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.11.0.tgz#77da451b1d7b4b117adaf41a1a93b5742f24d894" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" - ajv "^5.1.5" - ajv-keywords "^2.0.0" + ajv "^6.1.0" + ajv-keywords "^3.1.0" async "^2.1.2" enhanced-resolve "^3.4.0" escope "^3.6.0" @@ -11359,23 +11432,27 @@ websocket-extensions@>=0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" -whatwg-encoding@^1.0.1: +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" dependencies: iconv-lite "0.4.19" whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" -whatwg-url@^6.3.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" +whatwg-mimetype@^2.0.0, whatwg-mimetype@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4" + +whatwg-url@^6.4.0: + version "6.4.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.1.tgz#fdb94b440fd4ad836202c16e9737d511f012fd67" dependencies: lodash.sortby "^4.7.0" - tr46 "^1.0.0" - webidl-conversions "^4.0.1" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" whet.extend@~0.9.9: version "0.9.9" @@ -11389,7 +11466,11 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@1, which@^1.2.1, which@^1.2.10, which@^1.2.12, which@^1.2.14, which@^1.2.4, which@^1.2.9, which@^1.3.0, which@~1.3.0: +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + +which@1, which@^1.2.1, which@^1.2.10, which@^1.2.12, which@^1.2.4, which@^1.2.9, which@^1.3.0, which@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: @@ -11407,11 +11488,11 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2" -widest-line@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" +widest-line@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.0.tgz#0142a4e8a243f8882c0233aa0e0281aa76152273" dependencies: - string-width "^1.0.1" + string-width "^2.1.1" window-size@0.1.0: version "0.1.0" @@ -11441,12 +11522,11 @@ wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" -worker-farm@~1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae" +worker-farm@^1.5.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" dependencies: - errno "^0.1.4" - xtend "^4.0.1" + errno "~0.1.7" wrap-ansi@^2.0.0: version "2.1.0" @@ -11459,7 +11539,7 @@ wrappy@1, wrappy@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: +write-file-atomic@^2.0.0, write-file-atomic@^2.1.0, write-file-atomic@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" dependencies: @@ -11467,14 +11547,6 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.1.0.tgz#1769f4b551eedce419f0505deae2e26763542d37" - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - slide "^1.1.5" - write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" @@ -11488,13 +11560,12 @@ ws@1.1.2: options ">=0.0.5" ultron "1.0.x" -ws@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.2.tgz#96c1d08b3fefda1d5c1e33700d3bfaa9be2d5608" +ws@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289" dependencies: async-limiter "~1.0.0" safe-buffer "~5.1.0" - ultron "~1.1.0" wtf-8@1.0.0: version "1.0.0" @@ -11508,9 +11579,9 @@ xml-char-classes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" -xml-name-validator@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" xmlbuilder@^3.1.0: version "3.1.0" @@ -11526,7 +11597,7 @@ xmlhttprequest@1: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" -xtend@4.0.1, xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -11534,6 +11605,10 @@ y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -11560,9 +11635,15 @@ yargs-parser@^7.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.0.0.tgz#21d476330e5a82279a4b881345bf066102e219c6" +yargs-parser@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" + dependencies: + camelcase "^4.1.0" + +yargs-parser@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" dependencies: camelcase "^4.1.0" @@ -11585,10 +11666,10 @@ yargs@6.6.0: yargs-parser "^4.2.0" yargs@^10.0.3: - version "10.0.3" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.0.3.tgz#6542debd9080ad517ec5048fb454efe9e4d4aaae" + version "10.1.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" dependencies: - cliui "^3.2.0" + cliui "^4.0.0" decamelize "^1.1.1" find-up "^2.1.0" get-caller-file "^1.0.1" @@ -11599,7 +11680,24 @@ yargs@^10.0.3: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^8.0.0" + yargs-parser "^8.1.0" + +yargs@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^9.0.2" yargs@^7.0.0: version "7.1.0" From 8e9b3507c5df813aae4d47d62d4fe88550df2676 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 27 Apr 2018 10:39:06 +0200 Subject: [PATCH 277/319] tech: removes unused code --- build.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/build.go b/build.go index 21b528071e8..1bdbf4aac5c 100644 --- a/build.go +++ b/build.go @@ -49,8 +49,6 @@ func main() { ensureGoPath() - verifyGitRepoIsClean() - flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH") flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS") flag.StringVar(&gocc, "cc", "", "CC") @@ -325,20 +323,6 @@ func createPackage(options linuxPackageOptions) { runPrint("fpm", append([]string{"-t", options.packageType}, args...)...) } -func verifyGitRepoIsClean() { - rs, err := runError("git", "ls-files", "--modified") - if err != nil { - log.Fatalf("Failed to check if git tree was clean, %v, %v\n", string(rs), err) - return - } - count := len(string(rs)) - if count > 0 { - log.Fatalf("Git repository has modified files, aborting") - } - - log.Println("Git repository is clean") -} - func ensureGoPath() { if os.Getenv("GOPATH") == "" { cwd, err := os.Getwd() From 1e6e89121ca8649cf27eb82d221c926fff2659dd Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 27 Apr 2018 11:39:14 +0200 Subject: [PATCH 278/319] Settings to enable Explore UI --- conf/defaults.ini | 5 +++++ conf/sample.ini | 5 +++++ pkg/api/index.go | 22 ++++++++++++---------- pkg/setting/setting.go | 6 ++++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 11d173d955d..d45e270d65d 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -442,6 +442,11 @@ enabled = true # Makes it possible to turn off alert rule execution but alerting UI is visible execute_alerts = true +#################################### Explore ############################# +[explore] +# Enable the Explore section +enabled = false + #################################### Internal Grafana Metrics ############ # Metrics available at HTTP API Url /metrics [metrics] diff --git a/conf/sample.ini b/conf/sample.ini index 9f0c2a73c25..f12d917039d 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -377,6 +377,11 @@ log_queries = # Makes it possible to turn off alert rule execution but alerting UI is visible ;execute_alerts = true +#################################### Explore ############################# +[explore] +# Enable the Explore section +;enabled = false + #################################### Internal Grafana Metrics ########################## # Metrics available at HTTP API Url /metrics [metrics] diff --git a/pkg/api/index.go b/pkg/api/index.go index 64eaddcd1a7..75e3594d854 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -117,16 +117,18 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { Children: dashboardChildNavs, }) - // data.NavTree = append(data.NavTree, &dtos.NavLink{ - // Text: "Explore", - // Id: "explore", - // SubTitle: "Explore your data", - // Icon: "fa fa-rocket", - // Url: setting.AppSubUrl + "/explore", - // Children: []*dtos.NavLink{ - // {Text: "New tab", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/explore/new"}, - // }, - // }) + if setting.ExploreEnabled { + data.NavTree = append(data.NavTree, &dtos.NavLink{ + Text: "Explore", + Id: "explore", + SubTitle: "Explore your data", + Icon: "fa fa-rocket", + Url: setting.AppSubUrl + "/explore", + Children: []*dtos.NavLink{ + {Text: "New tab", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/explore/new"}, + }, + }) + } if c.IsSignedIn { // Only set login if it's different from the name diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 30a40602b1c..37646979095 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -168,6 +168,9 @@ var ( AlertingEnabled bool ExecuteAlerts bool + // Explore UI + ExploreEnabled bool + // logger logger log.Logger @@ -609,6 +612,9 @@ func NewConfigContext(args *CommandLineArgs) error { AlertingEnabled = alerting.Key("enabled").MustBool(true) ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true) + explore := Cfg.Section("explore") + ExploreEnabled = explore.Key("enabled").MustBool(true) + readSessionConfig() readSmtpSettings() readQuotaSettings() From d338b7ea7bf5b4561b77d779233030fc40c5dec8 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 27 Apr 2018 11:49:11 +0200 Subject: [PATCH 279/319] Import and typescript fixups --- public/app/containers/Explore/ElapsedTime.tsx | 4 ++-- public/app/routes/ReactContainer.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/public/app/containers/Explore/ElapsedTime.tsx b/public/app/containers/Explore/ElapsedTime.tsx index 123299fd96a..9cd8f674186 100644 --- a/public/app/containers/Explore/ElapsedTime.tsx +++ b/public/app/containers/Explore/ElapsedTime.tsx @@ -4,7 +4,7 @@ const INTERVAL = 150; export default class ElapsedTime extends PureComponent { offset: number; - timer: NodeJS.Timer; + timer: number; state = { elapsed: 0, @@ -12,7 +12,7 @@ export default class ElapsedTime extends PureComponent { start() { this.offset = Date.now(); - this.timer = setInterval(this.tick, INTERVAL); + this.timer = window.setInterval(this.tick, INTERVAL); } tick = () => { diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx index deb16f68bf7..d6d34372090 100644 --- a/public/app/routes/ReactContainer.tsx +++ b/public/app/routes/ReactContainer.tsx @@ -21,8 +21,12 @@ export function reactContainer($route, $location, backendSrv: BackendSrv, dataso restrict: 'E', template: '', link(scope, elem) { - let component = $route.current.locals.component.default; - let props = { + let component = $route.current.locals.component; + // Dynamic imports return whole module, need to extract default export + if (component.default) { + component = component.default; + } + const props = { backendSrv: backendSrv, datasourceSrv: datasourceSrv, }; From c2b720835b8f7cf1f58b14ab120a723b6835aed4 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Fri, 27 Apr 2018 19:34:10 +0900 Subject: [PATCH 280/319] fix to match table column name and order --- public/app/plugins/datasource/prometheus/datasource.ts | 1 + .../app/plugins/datasource/prometheus/result_transformer.ts | 6 +++--- public/app/plugins/panel/table/module.ts | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 2a8b3069a53..6d654438271 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -153,6 +153,7 @@ export class PrometheusDatasource { end: end, responseListLength: responseList.length, responseIndex: index, + refId: activeTargets[index].refId, }; this.resultTransformer.transform(result, response, transformerOptions); diff --git a/public/app/plugins/datasource/prometheus/result_transformer.ts b/public/app/plugins/datasource/prometheus/result_transformer.ts index 6d97b783983..d5feda7d28c 100644 --- a/public/app/plugins/datasource/prometheus/result_transformer.ts +++ b/public/app/plugins/datasource/prometheus/result_transformer.ts @@ -8,7 +8,7 @@ export class ResultTransformer { let prometheusResult = response.data.data.result; if (options.format === 'table') { - result.push(this.transformMetricDataToTable(prometheusResult, options.responseListLength, options.responseIndex)); + result.push(this.transformMetricDataToTable(prometheusResult, options.responseListLength, options.refId)); } else if (options.format === 'heatmap') { let seriesList = []; prometheusResult.sort(sortSeriesByLabel); @@ -58,7 +58,7 @@ export class ResultTransformer { return { target: metricLabel, datapoints: dps }; } - transformMetricDataToTable(md, resultCount: number, resultIndex: number) { + transformMetricDataToTable(md, resultCount: number, refId: string) { var table = new TableModel(); var i, j; var metricLabels = {}; @@ -83,7 +83,7 @@ export class ResultTransformer { metricLabels[label] = labelIndex + 1; table.columns.push({ text: label }); }); - let valueText = resultCount > 1 ? `Value #${String.fromCharCode(65 + resultIndex)}` : 'Value'; + let valueText = resultCount > 1 ? `Value #${refId}` : 'Value'; table.columns.push({ text: valueText }); // Populate rows, set value to empty string when label not present. diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 27eab205f09..f4728982ad5 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -154,6 +154,11 @@ class TablePanelCtrl extends MetricsPanelCtrl { this.render(); } + moveQuery(target, direction) { + super.moveQuery(target, direction); + super.refresh(); + } + exportCsv() { var scope = this.$scope.$new(true); scope.tableData = this.renderer.render_values(); From 138c8c348eaf209c59c72d478528006d6082e6d1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 27 Apr 2018 13:41:20 +0200 Subject: [PATCH 281/319] revert renaming of unit key ppm #11211 removed the unit key ppm in favor of conppm. A change which is not forward compatible. This commit revert the unit key back to ppm. Also adds some better error description if trying to use a unit which don't exists. Fixes #11743 --- public/app/core/utils/kbn.ts | 4 ++-- public/app/plugins/panel/graph/graph.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index 0909bd36f69..f4ee7af3383 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -596,7 +596,7 @@ kbn.valueFormats.radr = kbn.formatBuilders.decimalSIPrefix('R'); kbn.valueFormats.radsvh = kbn.formatBuilders.decimalSIPrefix('Sv/h'); // Concentration -kbn.valueFormats.conppm = kbn.formatBuilders.fixedUnit('ppm'); +kbn.valueFormats.ppm = kbn.formatBuilders.fixedUnit('ppm'); kbn.valueFormats.conppb = kbn.formatBuilders.fixedUnit('ppb'); kbn.valueFormats.conngm3 = kbn.formatBuilders.fixedUnit('ng/m3'); kbn.valueFormats.conngNm3 = kbn.formatBuilders.fixedUnit('ng/Nm3'); @@ -1101,7 +1101,7 @@ kbn.getUnitFormats = function() { { text: 'concentration', submenu: [ - { text: 'parts-per-million (ppm)', value: 'conppm' }, + { text: 'parts-per-million (ppm)', value: 'ppm' }, { text: 'parts-per-billion (ppb)', value: 'conppb' }, { text: 'nanogram per cubic metre (ng/m3)', value: 'conngm3' }, { text: 'nanogram per normal cubic metre (ng/Nm3)', value: 'conngNm3' }, diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 8a2aea8c4c2..07ce0fed49f 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -634,6 +634,9 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { function configureAxisMode(axis, format) { axis.tickFormatter = function(val, axis) { + if (!kbn.valueFormats[format]) { + throw new Error(`Unit '${format}' is not supported`); + } return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals); }; } From 28f7b6dad1c0f80ae49085e8883ac7a37dbb8b51 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Fri, 27 Apr 2018 13:41:58 +0200 Subject: [PATCH 282/319] Enable Grafana extensions at build time. (#11752) * extensions: import and build * bus: use predefined error * enterprise: build script for enterprise packages * poc: auto registering services and dependency injection (cherry picked from commit b5b1ef875f905473af41e49f8071cb9028edc845) * poc: backend services registry progress (cherry picked from commit 97be69725881241bfbf1e7adf0e66801d6b0af3d) * poc: minor update (cherry picked from commit 03d7a6888b81403f458b94305792e075568f0794) * ioc: introduce manuel ioc * enterprise: adds setting for enterprise * build: test and build specific ee commit * cleanup: test testing code * removes example hello service --- .circleci/config.yml | 20 + .gitignore | 1 + Gopkg.lock | 211 ++++++- build.go | 23 +- pkg/api/api.go | 2 +- pkg/api/http_server.go | 9 +- pkg/api/index.go | 2 +- pkg/api/route_register.go | 5 +- pkg/api/route_register_test.go | 6 +- pkg/bus/bus.go | 13 +- pkg/cmd/grafana-server/main.go | 3 + pkg/cmd/grafana-server/server.go | 96 ++- pkg/extensions/main.go | 3 + pkg/plugins/plugins.go | 2 +- pkg/registry/registry.go | 33 + pkg/services/alerting/engine.go | 38 +- pkg/services/cleanup/cleanup.go | 25 +- pkg/services/search/handlers.go | 16 +- pkg/services/search/handlers_test.go | 3 +- pkg/services/sqlstore/sqlstore.go | 4 +- pkg/setting/setting.go | 13 +- scripts/build/build_enterprise.sh | 58 ++ vendor/github.com/facebookgo/inject/inject.go | 576 ++++++++++++++++++ vendor/github.com/facebookgo/inject/license | 30 + vendor/github.com/facebookgo/inject/patents | 33 + .../github.com/facebookgo/structtag/license | 27 + .../facebookgo/structtag/structtag.go | 61 ++ vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/errors.go | 269 ++++++++ vendor/github.com/pkg/errors/stack.go | 178 ++++++ 30 files changed, 1678 insertions(+), 105 deletions(-) create mode 100644 pkg/extensions/main.go create mode 100644 pkg/registry/registry.go create mode 100755 scripts/build/build_enterprise.sh create mode 100644 vendor/github.com/facebookgo/inject/inject.go create mode 100644 vendor/github.com/facebookgo/inject/license create mode 100644 vendor/github.com/facebookgo/inject/patents create mode 100644 vendor/github.com/facebookgo/structtag/license create mode 100644 vendor/github.com/facebookgo/structtag/structtag.go create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/stack.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b717083853..d3e6c71b520 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,6 +93,22 @@ jobs: - scripts/*.sh - scripts/publish + build-enterprise: + docker: + - image: grafana/build-container:v0.1 + working_directory: /go/src/github.com/grafana/grafana + steps: + - checkout + - run: + name: build and package grafana + command: './scripts/build/build_enterprise.sh' + - run: + name: sign packages + command: './scripts/build/sign_packages.sh' + - run: + name: sha-sum packages + command: 'go run build.go sha-dist' + deploy-master: docker: - image: circleci/python:2.7-stretch @@ -176,3 +192,7 @@ workflows: ignore: /.*/ tags: only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ + - build-enterprise: + filters: + tags: + only: /.*/ diff --git a/.gitignore b/.gitignore index 974fb618af9..cf13dac6d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ profile.cov /pkg/cmd/grafana-cli/grafana-cli /pkg/cmd/grafana-server/grafana-server /pkg/cmd/grafana-server/debug +/pkg/extensions debug.test /examples/*/dist /packaging/**/*.rpm diff --git a/Gopkg.lock b/Gopkg.lock index a35f5b23cda..3a7466c312a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -27,7 +27,37 @@ [[projects]] name = "github.com/aws/aws-sdk-go" - packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"] + packages = [ + "aws", + "aws/awserr", + "aws/awsutil", + "aws/client", + "aws/client/metadata", + "aws/corehandlers", + "aws/credentials", + "aws/credentials/ec2rolecreds", + "aws/credentials/endpointcreds", + "aws/credentials/stscreds", + "aws/defaults", + "aws/ec2metadata", + "aws/endpoints", + "aws/request", + "aws/session", + "aws/signer/v4", + "internal/shareddefaults", + "private/protocol", + "private/protocol/ec2query", + "private/protocol/query", + "private/protocol/query/queryutil", + "private/protocol/rest", + "private/protocol/restxml", + "private/protocol/xml/xmlutil", + "service/cloudwatch", + "service/ec2", + "service/ec2/ec2iface", + "service/s3", + "service/sts" + ] revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28" version = "v1.12.67" @@ -75,7 +105,10 @@ [[projects]] name = "github.com/denisenkom/go-mssqldb" - packages = [".","internal/cp"] + packages = [ + ".", + "internal/cp" + ] revision = "270bc3860bb94dd3a3ffd047377d746c5e276726" [[projects]] @@ -117,7 +150,12 @@ [[projects]] branch = "master" name = "github.com/go-macaron/session" - packages = [".","memcache","postgres","redis"] + packages = [ + ".", + "memcache", + "postgres", + "redis" + ] revision = "b8e286a0dba8f4999042d6b258daf51b31d08938" [[projects]] @@ -152,7 +190,13 @@ [[projects]] branch = "master" name = "github.com/golang/protobuf" - packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"] + packages = [ + "proto", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/timestamp" + ] revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237" [[projects]] @@ -221,7 +265,10 @@ [[projects]] name = "github.com/klauspost/compress" - packages = ["flate","gzip"] + packages = [ + "flate", + "gzip" + ] revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf" version = "v1.2.1" @@ -252,7 +299,10 @@ [[projects]] branch = "master" name = "github.com/lib/pq" - packages = [".","oid"] + packages = [ + ".", + "oid" + ] revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345" [[projects]] @@ -287,7 +337,11 @@ [[projects]] name = "github.com/opentracing/opentracing-go" - packages = [".","ext","log"] + packages = [ + ".", + "ext", + "log" + ] revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" version = "v1.0.2" @@ -297,9 +351,20 @@ revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0" version = "v2.1.0" +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + [[projects]] name = "github.com/prometheus/client_golang" - packages = ["api","api/prometheus/v1","prometheus","prometheus/promhttp"] + packages = [ + "api", + "api/prometheus/v1", + "prometheus", + "prometheus/promhttp" + ] revision = "967789050ba94deca04a5e84cce8ad472ce313c1" version = "v0.9.0-pre1" @@ -312,13 +377,22 @@ [[projects]] branch = "master" name = "github.com/prometheus/common" - packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model" + ] revision = "89604d197083d4781071d3c65855d24ecfb0a563" [[projects]] branch = "master" name = "github.com/prometheus/procfs" - packages = [".","internal/util","nfsd","xfs"] + packages = [ + ".", + "internal/util", + "nfsd", + "xfs" + ] revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1" [[projects]] @@ -335,13 +409,21 @@ [[projects]] name = "github.com/smartystreets/assertions" - packages = [".","internal/go-render/render","internal/oglematchers"] + packages = [ + ".", + "internal/go-render/render", + "internal/oglematchers" + ] revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea" version = "1.8.1" [[projects]] name = "github.com/smartystreets/goconvey" - packages = ["convey","convey/gotest","convey/reporting"] + packages = [ + "convey", + "convey/gotest", + "convey/reporting" + ] revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857" version = "1.6.3" @@ -353,7 +435,21 @@ [[projects]] name = "github.com/uber/jaeger-client-go" - packages = [".","config","internal/baggage","internal/baggage/remote","internal/spanlog","log","rpcmetrics","thrift-gen/agent","thrift-gen/baggage","thrift-gen/jaeger","thrift-gen/sampling","thrift-gen/zipkincore","utils"] + packages = [ + ".", + "config", + "internal/baggage", + "internal/baggage/remote", + "internal/spanlog", + "log", + "rpcmetrics", + "thrift-gen/agent", + "thrift-gen/baggage", + "thrift-gen/jaeger", + "thrift-gen/sampling", + "thrift-gen/zipkincore", + "utils" + ] revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7" version = "v2.11.2" @@ -365,7 +461,10 @@ [[projects]] name = "github.com/yudai/gojsondiff" - packages = [".","formatter"] + packages = [ + ".", + "formatter" + ] revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6" version = "1.0.0" @@ -378,19 +477,37 @@ [[projects]] branch = "master" name = "golang.org/x/crypto" - packages = ["md4","pbkdf2"] + packages = [ + "md4", + "pbkdf2" + ] revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b" [[projects]] branch = "master" name = "golang.org/x/net" - packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"] + packages = [ + "context", + "context/ctxhttp", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "lex/httplex", + "trace" + ] revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" [[projects]] branch = "master" name = "golang.org/x/oauth2" - packages = [".","google","internal","jws","jwt"] + packages = [ + ".", + "google", + "internal", + "jws", + "jwt" + ] revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067" [[projects]] @@ -408,12 +525,39 @@ [[projects]] branch = "master" name = "golang.org/x/text" - packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"] + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable" + ] revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" [[projects]] name = "google.golang.org/appengine" - packages = [".","cloudsql","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"] + packages = [ + ".", + "cloudsql", + "internal", + "internal/app_identity", + "internal/base", + "internal/datastore", + "internal/log", + "internal/modules", + "internal/remote_api", + "internal/urlfetch", + "urlfetch" + ] revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" @@ -425,7 +569,32 @@ [[projects]] name = "google.golang.org/grpc" - packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"] + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "codes", + "connectivity", + "credentials", + "encoding", + "grpclb/grpc_lb_v1/messages", + "grpclog", + "health", + "health/grpc_health_v1", + "internal", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + "transport" + ] revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef" version = "v1.9.2" @@ -480,6 +649,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "ad3c71fd3244369c313978e9e7464c7116faee764386439a17de0707a08103aa" + inputs-digest = "2bd5b309496d57e2189a1cc28f5c1c41398c19729ba0cf53c8cbb17ea3f706b5" solver-name = "gps-cdcl" solver-version = 1 diff --git a/build.go b/build.go index 1bdbf4aac5c..7e7183b8b83 100644 --- a/build.go +++ b/build.go @@ -41,6 +41,7 @@ var ( buildNumber int = 0 binaries []string = []string{"grafana-server", "grafana-cli"} isDev bool = false + enterprise bool = false ) func main() { @@ -58,6 +59,7 @@ func main() { flag.StringVar(&phjsToRelease, "phjs", "", "PhantomJS binary") flag.BoolVar(&race, "race", race, "Use race detector") flag.BoolVar(&includeBuildNumber, "includeBuildNumber", includeBuildNumber, "IncludeBuildNumber in package name") + flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana") flag.IntVar(&buildNumber, "buildNumber", 0, "Build number from CI system") flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps") flag.Parse() @@ -283,19 +285,33 @@ func createPackage(options linuxPackageOptions) { "-s", "dir", "--description", "Grafana", "-C", packageRoot, - "--vendor", "Grafana", "--url", "https://grafana.com", - "--license", "\"Apache 2.0\"", "--maintainer", "contact@grafana.com", "--config-files", options.initdScriptFilePath, "--config-files", options.etcDefaultFilePath, "--config-files", options.systemdServiceFilePath, "--after-install", options.postinstSrc, - "--name", "grafana", + "--version", linuxPackageVersion, "-p", "./dist", } + name := "grafana" + if enterprise { + name += "-enterprise" + } + args = append(args, "--name", name) + + description := "Grafana" + if enterprise { + description += " Enterprise" + } + args = append(args, "--vendor", description) + + if !enterprise { + args = append(args, "--license", "\"Apache 2.0\"") + } + if options.packageType == "rpm" { args = append(args, "--rpm-posttrans", "packaging/rpm/control/posttrans") } @@ -412,6 +428,7 @@ func ldflags() string { b.WriteString(fmt.Sprintf(" -X main.version=%s", version)) b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha())) b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp())) + b.WriteString(fmt.Sprintf(" -X main.enterprise=%t", enterprise)) return b.String() } diff --git a/pkg/api/api.go b/pkg/api/api.go index 96b764b95b9..493f9eb9d01 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -23,7 +23,7 @@ func (hs *HTTPServer) registerRoutes() { // automatically set HEAD for every GET macaronR.SetAutoHead(true) - r := newRouteRegister(middleware.RequestMetrics, middleware.RequestTracing) + r := hs.RouteRegister // not logged in views r.Get("/", reqSignedIn, Index) diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 8e01e869329..8d1d0dc0a60 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -35,15 +35,14 @@ type HTTPServer struct { context context.Context streamManager *live.StreamManager cache *gocache.Cache + RouteRegister RouteRegister `inject:""` httpSrv *http.Server } -func NewHTTPServer() *HTTPServer { - return &HTTPServer{ - log: log.New("http.server"), - cache: gocache.New(5*time.Minute, 10*time.Minute), - } +func (hs *HTTPServer) Init() { + hs.log = log.New("http.server") + hs.cache = gocache.New(5*time.Minute, 10*time.Minute) } func (hs *HTTPServer) Start(ctx context.Context) error { diff --git a/pkg/api/index.go b/pkg/api/index.go index 94094706f68..7c954f89ada 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -289,7 +289,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { data.NavTree = append(data.NavTree, &dtos.NavLink{ Text: "Help", - SubTitle: fmt.Sprintf(`Grafana v%s (%s)`, setting.BuildVersion, setting.BuildCommit), + SubTitle: fmt.Sprintf(`%s v%s (%s)`, setting.ApplicationName, setting.BuildVersion, setting.BuildCommit), Id: "help", Url: "#", Icon: "gicon gicon-question", diff --git a/pkg/api/route_register.go b/pkg/api/route_register.go index 76ebb633ca1..926de13c546 100644 --- a/pkg/api/route_register.go +++ b/pkg/api/route_register.go @@ -11,6 +11,8 @@ type Router interface { Get(pattern string, handlers ...macaron.Handler) *macaron.Route } +// RouteRegister allows you to add routes and macaron.Handlers +// that the web server should serve. type RouteRegister interface { Get(string, ...macaron.Handler) Post(string, ...macaron.Handler) @@ -26,7 +28,8 @@ type RouteRegister interface { type RegisterNamedMiddleware func(name string) macaron.Handler -func newRouteRegister(namedMiddleware ...RegisterNamedMiddleware) RouteRegister { +// NewRouteRegister creates a new RouteRegister with all middlewares sent as params +func NewRouteRegister(namedMiddleware ...RegisterNamedMiddleware) RouteRegister { return &routeRegister{ prefix: "", routes: []route{}, diff --git a/pkg/api/route_register_test.go b/pkg/api/route_register_test.go index f8a043c48df..3b5d79599a8 100644 --- a/pkg/api/route_register_test.go +++ b/pkg/api/route_register_test.go @@ -51,7 +51,7 @@ func TestRouteSimpleRegister(t *testing.T) { } // Setup - rr := newRouteRegister(func(name string) macaron.Handler { + rr := NewRouteRegister(func(name string) macaron.Handler { return emptyHandler(name) }) @@ -96,7 +96,7 @@ func TestRouteGroupedRegister(t *testing.T) { } // Setup - rr := newRouteRegister() + rr := NewRouteRegister() rr.Delete("/admin", emptyHandler("1")) rr.Get("/down", emptyHandler("1"), emptyHandler("2")) @@ -150,7 +150,7 @@ func TestNamedMiddlewareRouteRegister(t *testing.T) { } // Setup - rr := newRouteRegister(func(name string) macaron.Handler { + rr := NewRouteRegister(func(name string) macaron.Handler { return emptyHandler(name) }) diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go index 59d4592766e..437796991a5 100644 --- a/pkg/bus/bus.go +++ b/pkg/bus/bus.go @@ -2,7 +2,7 @@ package bus import ( "context" - "fmt" + "errors" "reflect" ) @@ -10,6 +10,8 @@ type HandlerFunc interface{} type CtxHandlerFunc func() type Msg interface{} +var ErrHandlerNotFound = errors.New("handler not found") + type Bus interface { Dispatch(msg Msg) error DispatchCtx(ctx context.Context, msg Msg) error @@ -38,12 +40,17 @@ func New() Bus { return bus } +// Want to get rid of global bus +func GetBus() Bus { + return globalBus +} + func (b *InProcBus) DispatchCtx(ctx context.Context, msg Msg) error { var msgName = reflect.TypeOf(msg).Elem().Name() var handler = b.handlers[msgName] if handler == nil { - return fmt.Errorf("handler not found for %s", msgName) + return ErrHandlerNotFound } var params = make([]reflect.Value, 2) @@ -64,7 +71,7 @@ func (b *InProcBus) Dispatch(msg Msg) error { var handler = b.handlers[msgName] if handler == nil { - return fmt.Errorf("handler not found for %s", msgName) + return ErrHandlerNotFound } var params = make([]reflect.Value, 1) diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index da99bc9ba40..466e97ff2d6 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/setting" + _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers" _ "github.com/grafana/grafana/pkg/tsdb/cloudwatch" @@ -33,6 +34,7 @@ import ( var version = "5.0.0" var commit = "NA" var buildstamp string +var enterprise string var configFile = flag.String("config", "", "path to config file") var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory") @@ -76,6 +78,7 @@ func main() { setting.BuildVersion = version setting.BuildCommit = commit setting.BuildStamp = buildstampInt64 + setting.Enterprise, _ = strconv.ParseBool(enterprise) metrics.M_Grafana_Version.WithLabelValues(version).Set(1) shutdownCompleted := make(chan int) diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index b8387403161..1bf0e90915f 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -8,9 +8,15 @@ import ( "net" "os" "path/filepath" + "reflect" "strconv" "time" + "github.com/facebookgo/inject" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/provisioning" "golang.org/x/sync/errgroup" @@ -20,15 +26,17 @@ import ( "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/services/alerting" - "github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/notifications" - "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/tracing" + + _ "github.com/grafana/grafana/pkg/extensions" + _ "github.com/grafana/grafana/pkg/services/alerting" + _ "github.com/grafana/grafana/pkg/services/cleanup" + _ "github.com/grafana/grafana/pkg/services/search" ) func NewGrafanaServer() *GrafanaServerImpl { @@ -48,18 +56,20 @@ type GrafanaServerImpl struct { shutdownFn context.CancelFunc childRoutines *errgroup.Group log log.Logger + RouteRegister api.RouteRegister `inject:""` - httpServer *api.HTTPServer + HttpServer *api.HTTPServer `inject:""` } func (g *GrafanaServerImpl) Start() error { g.initLogging() g.writePIDFile() - initSql() + // initSql + sqlstore.NewEngine() // TODO: this should return an error + sqlstore.EnsureAdminUser() metrics.Init(setting.Cfg) - search.Init() login.Init() social.NewOAuthService() @@ -79,30 +89,64 @@ func (g *GrafanaServerImpl) Start() error { } defer tracingCloser.Close() - // init alerting - if setting.AlertingEnabled && setting.ExecuteAlerts { - engine := alerting.NewEngine() - g.childRoutines.Go(func() error { return engine.Run(g.context) }) - } - - // cleanup service - cleanUpService := cleanup.NewCleanUpService() - g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) }) - if err = notifications.Init(); err != nil { return fmt.Errorf("Notification service failed to initialize. error: %v", err) } + serviceGraph := inject.Graph{} + serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) + serviceGraph.Provide(&inject.Object{Value: dashboards.NewProvisioningService()}) + serviceGraph.Provide(&inject.Object{Value: api.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)}) + serviceGraph.Provide(&inject.Object{Value: api.HTTPServer{}}) + services := registry.GetServices() + + // Add all services to dependency graph + for _, service := range services { + serviceGraph.Provide(&inject.Object{Value: service}) + } + + serviceGraph.Provide(&inject.Object{Value: g}) + + // Inject dependencies to services + if err := serviceGraph.Populate(); err != nil { + return fmt.Errorf("Failed to populate service dependency: %v", err) + } + + // Init & start services + for _, service := range services { + if registry.IsDisabled(service) { + continue + } + + g.log.Info("Initializing " + reflect.TypeOf(service).Elem().Name()) + + if err := service.Init(); err != nil { + return fmt.Errorf("Service init failed %v", err) + } + } + + // Start background services + for index := range services { + service, ok := services[index].(registry.BackgroundService) + if !ok { + continue + } + + if registry.IsDisabled(services[index]) { + continue + } + + g.childRoutines.Go(func() error { + err := service.Run(g.context) + g.log.Info("Stopped "+reflect.TypeOf(service).Elem().Name(), "reason", err) + return err + }) + } + sendSystemdNotification("READY=1") - return g.startHttpServer() } -func initSql() { - sqlstore.NewEngine() - sqlstore.EnsureAdminUser() -} - func (g *GrafanaServerImpl) initLogging() { err := setting.NewConfigContext(&setting.CommandLineArgs{ Config: *configFile, @@ -115,14 +159,14 @@ func (g *GrafanaServerImpl) initLogging() { os.Exit(1) } - g.log.Info("Starting Grafana", "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0)) + g.log.Info("Starting "+setting.ApplicationName, "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0)) setting.LogConfigurationInfo() } func (g *GrafanaServerImpl) startHttpServer() error { - g.httpServer = api.NewHTTPServer() + g.HttpServer.Init() - err := g.httpServer.Start(g.context) + err := g.HttpServer.Start(g.context) if err != nil { return fmt.Errorf("Fail to start server. error: %v", err) @@ -134,7 +178,7 @@ func (g *GrafanaServerImpl) startHttpServer() error { func (g *GrafanaServerImpl) Shutdown(code int, reason string) { g.log.Info("Shutdown started", "code", code, "reason", reason) - err := g.httpServer.Shutdown(g.context) + err := g.HttpServer.Shutdown(g.context) if err != nil { g.log.Error("Failed to shutdown server", "error", err) } diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go new file mode 100644 index 00000000000..34ac9da7e86 --- /dev/null +++ b/pkg/extensions/main.go @@ -0,0 +1,3 @@ +package extensions + +import _ "github.com/pkg/errors" diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 417f565dd0c..45e7c934bea 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -58,7 +58,7 @@ func (p *PluginManager) Run(ctx context.Context) error { p.Kill() } - p.log.Info("Stopped Plugins", "error", ctx.Err()) + p.log.Info("Stopped Plugins", "reason", ctx.Err()) return ctx.Err() } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go new file mode 100644 index 00000000000..ba3229d6df6 --- /dev/null +++ b/pkg/registry/registry.go @@ -0,0 +1,33 @@ +package registry + +import ( + "context" +) + +var services = []Service{} + +func RegisterService(srv Service) { + services = append(services, srv) +} + +func GetServices() []Service { + return services +} + +type Service interface { + Init() error +} + +// Useful for alerting service +type CanBeDisabled interface { + IsDisabled() bool +} + +type BackgroundService interface { + Run(ctx context.Context) error +} + +func IsDisabled(srv Service) bool { + canBeDisabled, ok := srv.(CanBeDisabled) + return ok && canBeDisabled.IsDisabled() +} diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 0945a2a5330..bdd8ff2cfe2 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -11,6 +11,8 @@ import ( "github.com/benbjohnson/clock" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" "golang.org/x/sync/errgroup" ) @@ -25,31 +27,37 @@ type Engine struct { resultHandler ResultHandler } -func NewEngine() *Engine { - e := &Engine{ - ticker: NewTicker(time.Now(), time.Second*0, clock.New()), - execQueue: make(chan *Job, 1000), - scheduler: NewScheduler(), - evalHandler: NewEvalHandler(), - ruleReader: NewRuleReader(), - log: log.New("alerting.engine"), - resultHandler: NewResultHandler(), - } +func init() { + registry.RegisterService(&Engine{}) +} +func NewEngine() *Engine { + e := &Engine{} + e.Init() return e } +func (e *Engine) IsDisabled() bool { + return !setting.AlertingEnabled || !setting.ExecuteAlerts +} + +func (e *Engine) Init() error { + e.ticker = NewTicker(time.Now(), time.Second*0, clock.New()) + e.execQueue = make(chan *Job, 1000) + e.scheduler = NewScheduler() + e.evalHandler = NewEvalHandler() + e.ruleReader = NewRuleReader() + e.log = log.New("alerting.engine") + e.resultHandler = NewResultHandler() + return nil +} + func (e *Engine) Run(ctx context.Context) error { - e.log.Info("Initializing Alerting") - alertGroup, ctx := errgroup.WithContext(ctx) - alertGroup.Go(func() error { return e.alertingTicker(ctx) }) alertGroup.Go(func() error { return e.runJobDispatcher(ctx) }) err := alertGroup.Wait() - - e.log.Info("Stopped Alerting", "reason", err) return err } diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 5e9efeea3b0..ef474fd2eb2 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -7,11 +7,10 @@ import ( "path" "time" - "golang.org/x/sync/errgroup" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) @@ -19,24 +18,16 @@ type CleanUpService struct { log log.Logger } -func NewCleanUpService() *CleanUpService { - return &CleanUpService{ - log: log.New("cleanup"), - } +func init() { + registry.RegisterService(&CleanUpService{}) +} + +func (service *CleanUpService) Init() error { + service.log = log.New("cleanup") + return nil } func (service *CleanUpService) Run(ctx context.Context) error { - service.log.Info("Initializing CleanUpService") - - g, _ := errgroup.WithContext(ctx) - g.Go(func() error { return service.start(ctx) }) - - err := g.Wait() - service.log.Info("Stopped CleanUpService", "reason", err) - return err -} - -func (service *CleanUpService) start(ctx context.Context) error { service.cleanUpTmpFiles() ticker := time.NewTicker(time.Minute * 10) diff --git a/pkg/services/search/handlers.go b/pkg/services/search/handlers.go index cf194c320bb..9d40697f489 100644 --- a/pkg/services/search/handlers.go +++ b/pkg/services/search/handlers.go @@ -5,13 +5,23 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" ) -func Init() { - bus.AddHandler("search", searchHandler) +func init() { + registry.RegisterService(&SearchService{}) } -func searchHandler(query *Query) error { +type SearchService struct { + Bus bus.Bus `inject:""` +} + +func (s *SearchService) Init() error { + s.Bus.AddHandler(s.searchHandler) + return nil +} + +func (s *SearchService) searchHandler(query *Query) error { dashQuery := FindPersistedDashboardsQuery{ Title: query.Title, SignedInUser: query.SignedInUser, diff --git a/pkg/services/search/handlers_test.go b/pkg/services/search/handlers_test.go index fc223b2ef4b..5cf934cbc92 100644 --- a/pkg/services/search/handlers_test.go +++ b/pkg/services/search/handlers_test.go @@ -12,6 +12,7 @@ func TestSearch(t *testing.T) { Convey("Given search query", t, func() { query := Query{Limit: 2000, SignedInUser: &m.SignedInUser{IsGrafanaAdmin: true}} + ss := &SearchService{} bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error { query.Result = HitList{ @@ -35,7 +36,7 @@ func TestSearch(t *testing.T) { }) Convey("That is empty", func() { - err := searchHandler(&query) + err := ss.searchHandler(&query) So(err, ShouldBeNil) Convey("should return sorted results", func() { diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 782318fa188..e4be3208c86 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -77,7 +77,7 @@ func EnsureAdminUser() { log.Info("Created default admin user: %v", setting.AdminUser) } -func NewEngine() { +func NewEngine() *xorm.Engine { x, err := getEngine() if err != nil { @@ -91,6 +91,8 @@ func NewEngine() { sqlog.Error("Fail to initialize orm engine", "error", err) os.Exit(1) } + + return x } func SetEngine(engine *xorm.Engine) (err error) { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 30a40602b1c..58a33b2202f 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -45,9 +45,11 @@ var ( InstanceName string // build - BuildVersion string - BuildCommit string - BuildStamp int64 + BuildVersion string + BuildCommit string + BuildStamp int64 + Enterprise bool + ApplicationName string // Paths LogsPath string @@ -486,6 +488,11 @@ func NewConfigContext(args *CommandLineArgs) error { return err } + ApplicationName = "Grafana" + if Enterprise { + ApplicationName += " Enterprise" + } + Env = Cfg.Section("").Key("app_mode").MustString("development") InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name") PluginsPath = makeAbsolute(Cfg.Section("paths").Key("plugins").String(), HomePath) diff --git a/scripts/build/build_enterprise.sh b/scripts/build/build_enterprise.sh new file mode 100755 index 00000000000..02d8c78c885 --- /dev/null +++ b/scripts/build/build_enterprise.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# +# This script is executed from within the container. +# + +echo "building enterprise version" + +GOPATH=/go +REPO_PATH=$GOPATH/src/github.com/grafana/grafana + + +cd /go/src/github.com/grafana/grafana +echo "current dir: $(pwd)" + +cd .. +git clone -b ee_build --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10 +cd grafana-enterprise +git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f +./build.sh + +cd ../grafana + +function exit_if_fail { + command=$@ + echo "Executing '$command'" + eval $command + rc=$? + if [ $rc -ne 0 ]; then + echo "'$command' returned $rc." + exit $rc + fi +} + +exit_if_fail go test ./pkg/extensions/... + + +if [ "$CIRCLE_TAG" != "" ]; then + echo "Building a release from tag $ls" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true -includeBuildNumber=false build +else + echo "Building incremental build for $CIRCLE_BRANCH" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true build +fi + +yarn install --pure-lockfile --no-progress + +source /etc/profile.d/rvm.sh + +echo "current dir: $(pwd)" + +if [ "$CIRCLE_TAG" != "" ]; then + echo "Packaging a release from tag $CIRCLE_TAG" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true -includeBuildNumber=false package latest +else + echo "Packaging incremental build for $CIRCLE_BRANCH" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true package latest +fi diff --git a/vendor/github.com/facebookgo/inject/inject.go b/vendor/github.com/facebookgo/inject/inject.go new file mode 100644 index 00000000000..300b9a37622 --- /dev/null +++ b/vendor/github.com/facebookgo/inject/inject.go @@ -0,0 +1,576 @@ +// Package inject provides a reflect based injector. A large application built +// with dependency injection in mind will typically involve the boring work of +// setting up the object graph. This library attempts to take care of this +// boring work by creating and connecting the various objects. Its use involves +// you seeding the object graph with some (possibly incomplete) objects, where +// the underlying types have been tagged for injection. Given this, the +// library will populate the objects creating new ones as necessary. It uses +// singletons by default, supports optional private instances as well as named +// instances. +// +// It works using Go's reflection package and is inherently limited in what it +// can do as opposed to a code-gen system with respect to private fields. +// +// The usage pattern for the library involves struct tags. It requires the tag +// format used by the various standard libraries, like json, xml etc. It +// involves tags in one of the three forms below: +// +// `inject:""` +// `inject:"private"` +// `inject:"dev logger"` +// +// The first no value syntax is for the common case of a singleton dependency +// of the associated type. The second triggers creation of a private instance +// for the associated type. Finally the last form is asking for a named +// dependency called "dev logger". +package inject + +import ( + "bytes" + "fmt" + "math/rand" + "reflect" + + "github.com/facebookgo/structtag" +) + +// Logger allows for simple logging as inject traverses and populates the +// object graph. +type Logger interface { + Debugf(format string, v ...interface{}) +} + +// Populate is a short-hand for populating a graph with the given incomplete +// object values. +func Populate(values ...interface{}) error { + var g Graph + for _, v := range values { + if err := g.Provide(&Object{Value: v}); err != nil { + return err + } + } + return g.Populate() +} + +// An Object in the Graph. +type Object struct { + Value interface{} + Name string // Optional + Complete bool // If true, the Value will be considered complete + Fields map[string]*Object // Populated with the field names that were injected and their corresponding *Object. + reflectType reflect.Type + reflectValue reflect.Value + private bool // If true, the Value will not be used and will only be populated + created bool // If true, the Object was created by us + embedded bool // If true, the Object is an embedded struct provided internally +} + +// String representation suitable for human consumption. +func (o *Object) String() string { + var buf bytes.Buffer + fmt.Fprint(&buf, o.reflectType) + if o.Name != "" { + fmt.Fprintf(&buf, " named %s", o.Name) + } + return buf.String() +} + +func (o *Object) addDep(field string, dep *Object) { + if o.Fields == nil { + o.Fields = make(map[string]*Object) + } + o.Fields[field] = dep +} + +// The Graph of Objects. +type Graph struct { + Logger Logger // Optional, will trigger debug logging. + unnamed []*Object + unnamedType map[reflect.Type]bool + named map[string]*Object +} + +// Provide objects to the Graph. The Object documentation describes +// the impact of various fields. +func (g *Graph) Provide(objects ...*Object) error { + for _, o := range objects { + o.reflectType = reflect.TypeOf(o.Value) + o.reflectValue = reflect.ValueOf(o.Value) + + if o.Fields != nil { + return fmt.Errorf( + "fields were specified on object %s when it was provided", + o, + ) + } + + if o.Name == "" { + if !isStructPtr(o.reflectType) { + return fmt.Errorf( + "expected unnamed object value to be a pointer to a struct but got type %s "+ + "with value %v", + o.reflectType, + o.Value, + ) + } + + if !o.private { + if g.unnamedType == nil { + g.unnamedType = make(map[reflect.Type]bool) + } + + if g.unnamedType[o.reflectType] { + return fmt.Errorf( + "provided two unnamed instances of type *%s.%s", + o.reflectType.Elem().PkgPath(), o.reflectType.Elem().Name(), + ) + } + g.unnamedType[o.reflectType] = true + } + g.unnamed = append(g.unnamed, o) + } else { + if g.named == nil { + g.named = make(map[string]*Object) + } + + if g.named[o.Name] != nil { + return fmt.Errorf("provided two instances named %s", o.Name) + } + g.named[o.Name] = o + } + + if g.Logger != nil { + if o.created { + g.Logger.Debugf("created %s", o) + } else if o.embedded { + g.Logger.Debugf("provided embedded %s", o) + } else { + g.Logger.Debugf("provided %s", o) + } + } + } + return nil +} + +// Populate the incomplete Objects. +func (g *Graph) Populate() error { + for _, o := range g.named { + if o.Complete { + continue + } + + if err := g.populateExplicit(o); err != nil { + return err + } + } + + // We append and modify our slice as we go along, so we don't use a standard + // range loop, and do a single pass thru each object in our graph. + i := 0 + for { + if i == len(g.unnamed) { + break + } + + o := g.unnamed[i] + i++ + + if o.Complete { + continue + } + + if err := g.populateExplicit(o); err != nil { + return err + } + } + + // A Second pass handles injecting Interface values to ensure we have created + // all concrete types first. + for _, o := range g.unnamed { + if o.Complete { + continue + } + + if err := g.populateUnnamedInterface(o); err != nil { + return err + } + } + + for _, o := range g.named { + if o.Complete { + continue + } + + if err := g.populateUnnamedInterface(o); err != nil { + return err + } + } + + return nil +} + +func (g *Graph) populateExplicit(o *Object) error { + // Ignore named value types. + if o.Name != "" && !isStructPtr(o.reflectType) { + return nil + } + +StructLoop: + for i := 0; i < o.reflectValue.Elem().NumField(); i++ { + field := o.reflectValue.Elem().Field(i) + fieldType := field.Type() + fieldTag := o.reflectType.Elem().Field(i).Tag + fieldName := o.reflectType.Elem().Field(i).Name + tag, err := parseTag(string(fieldTag)) + if err != nil { + return fmt.Errorf( + "unexpected tag format `%s` for field %s in type %s", + string(fieldTag), + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Skip fields without a tag. + if tag == nil { + continue + } + + // Cannot be used with unexported fields. + if !field.CanSet() { + return fmt.Errorf( + "inject requested on unexported field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Inline tag on anything besides a struct is considered invalid. + if tag.Inline && fieldType.Kind() != reflect.Struct { + return fmt.Errorf( + "inline requested on non inlined field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Don't overwrite existing values. + if !isNilOrZero(field, fieldType) { + continue + } + + // Named injects must have been explicitly provided. + if tag.Name != "" { + existing := g.named[tag.Name] + if existing == nil { + return fmt.Errorf( + "did not find object named %s required by field %s in type %s", + tag.Name, + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + if !existing.reflectType.AssignableTo(fieldType) { + return fmt.Errorf( + "object named %s of type %s is not assignable to field %s (%s) in type %s", + tag.Name, + fieldType, + o.reflectType.Elem().Field(i).Name, + existing.reflectType, + o.reflectType, + ) + } + + field.Set(reflect.ValueOf(existing.Value)) + if g.Logger != nil { + g.Logger.Debugf( + "assigned %s to field %s in %s", + existing, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, existing) + continue StructLoop + } + + // Inline struct values indicate we want to traverse into it, but not + // inject itself. We require an explicit "inline" tag for this to work. + if fieldType.Kind() == reflect.Struct { + if tag.Private { + return fmt.Errorf( + "cannot use private inject on inline struct on field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + if !tag.Inline { + return fmt.Errorf( + "inline struct on field %s in type %s requires an explicit \"inline\" tag", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + err := g.Provide(&Object{ + Value: field.Addr().Interface(), + private: true, + embedded: o.reflectType.Elem().Field(i).Anonymous, + }) + if err != nil { + return err + } + continue + } + + // Interface injection is handled in a second pass. + if fieldType.Kind() == reflect.Interface { + continue + } + + // Maps are created and required to be private. + if fieldType.Kind() == reflect.Map { + if !tag.Private { + return fmt.Errorf( + "inject on map field %s in type %s must be named or private", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + field.Set(reflect.MakeMap(fieldType)) + if g.Logger != nil { + g.Logger.Debugf( + "made map for field %s in %s", + o.reflectType.Elem().Field(i).Name, + o, + ) + } + continue + } + + // Can only inject Pointers from here on. + if !isStructPtr(fieldType) { + return fmt.Errorf( + "found inject tag on unsupported field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Unless it's a private inject, we'll look for an existing instance of the + // same type. + if !tag.Private { + for _, existing := range g.unnamed { + if existing.private { + continue + } + if existing.reflectType.AssignableTo(fieldType) { + field.Set(reflect.ValueOf(existing.Value)) + if g.Logger != nil { + g.Logger.Debugf( + "assigned existing %s to field %s in %s", + existing, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, existing) + continue StructLoop + } + } + } + + newValue := reflect.New(fieldType.Elem()) + newObject := &Object{ + Value: newValue.Interface(), + private: tag.Private, + created: true, + } + + // Add the newly ceated object to the known set of objects. + err = g.Provide(newObject) + if err != nil { + return err + } + + // Finally assign the newly created object to our field. + field.Set(newValue) + if g.Logger != nil { + g.Logger.Debugf( + "assigned newly created %s to field %s in %s", + newObject, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, newObject) + } + return nil +} + +func (g *Graph) populateUnnamedInterface(o *Object) error { + // Ignore named value types. + if o.Name != "" && !isStructPtr(o.reflectType) { + return nil + } + + for i := 0; i < o.reflectValue.Elem().NumField(); i++ { + field := o.reflectValue.Elem().Field(i) + fieldType := field.Type() + fieldTag := o.reflectType.Elem().Field(i).Tag + fieldName := o.reflectType.Elem().Field(i).Name + tag, err := parseTag(string(fieldTag)) + if err != nil { + return fmt.Errorf( + "unexpected tag format `%s` for field %s in type %s", + string(fieldTag), + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Skip fields without a tag. + if tag == nil { + continue + } + + // We only handle interface injection here. Other cases including errors + // are handled in the first pass when we inject pointers. + if fieldType.Kind() != reflect.Interface { + continue + } + + // Interface injection can't be private because we can't instantiate new + // instances of an interface. + if tag.Private { + return fmt.Errorf( + "found private inject tag on interface field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Don't overwrite existing values. + if !isNilOrZero(field, fieldType) { + continue + } + + // Named injects must have already been handled in populateExplicit. + if tag.Name != "" { + panic(fmt.Sprintf("unhandled named instance with name %s", tag.Name)) + } + + // Find one, and only one assignable value for the field. + var found *Object + for _, existing := range g.unnamed { + if existing.private { + continue + } + if existing.reflectType.AssignableTo(fieldType) { + if found != nil { + return fmt.Errorf( + "found two assignable values for field %s in type %s. one type "+ + "%s with value %v and another type %s with value %v", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + found.reflectType, + found.Value, + existing.reflectType, + existing.reflectValue, + ) + } + found = existing + field.Set(reflect.ValueOf(existing.Value)) + if g.Logger != nil { + g.Logger.Debugf( + "assigned existing %s to interface field %s in %s", + existing, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, existing) + } + } + + // If we didn't find an assignable value, we're missing something. + if found == nil { + return fmt.Errorf( + "found no assignable value for field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + } + return nil +} + +// Objects returns all known objects, named as well as unnamed. The returned +// elements are not in a stable order. +func (g *Graph) Objects() []*Object { + objects := make([]*Object, 0, len(g.unnamed)+len(g.named)) + for _, o := range g.unnamed { + if !o.embedded { + objects = append(objects, o) + } + } + for _, o := range g.named { + if !o.embedded { + objects = append(objects, o) + } + } + // randomize to prevent callers from relying on ordering + for i := 0; i < len(objects); i++ { + j := rand.Intn(i + 1) + objects[i], objects[j] = objects[j], objects[i] + } + return objects +} + +var ( + injectOnly = &tag{} + injectPrivate = &tag{Private: true} + injectInline = &tag{Inline: true} +) + +type tag struct { + Name string + Inline bool + Private bool +} + +func parseTag(t string) (*tag, error) { + found, value, err := structtag.Extract("inject", t) + if err != nil { + return nil, err + } + if !found { + return nil, nil + } + if value == "" { + return injectOnly, nil + } + if value == "inline" { + return injectInline, nil + } + if value == "private" { + return injectPrivate, nil + } + return &tag{Name: value}, nil +} + +func isStructPtr(t reflect.Type) bool { + return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct +} + +func isNilOrZero(v reflect.Value, t reflect.Type) bool { + switch v.Kind() { + default: + return reflect.DeepEqual(v.Interface(), reflect.Zero(t).Interface()) + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } +} diff --git a/vendor/github.com/facebookgo/inject/license b/vendor/github.com/facebookgo/inject/license new file mode 100644 index 00000000000..953e8f7f10d --- /dev/null +++ b/vendor/github.com/facebookgo/inject/license @@ -0,0 +1,30 @@ +BSD License + +For inject software + +Copyright (c) 2015, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/facebookgo/inject/patents b/vendor/github.com/facebookgo/inject/patents new file mode 100644 index 00000000000..f33dc8011a8 --- /dev/null +++ b/vendor/github.com/facebookgo/inject/patents @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the inject software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook’s rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/vendor/github.com/facebookgo/structtag/license b/vendor/github.com/facebookgo/structtag/license new file mode 100644 index 00000000000..74487567632 --- /dev/null +++ b/vendor/github.com/facebookgo/structtag/license @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/facebookgo/structtag/structtag.go b/vendor/github.com/facebookgo/structtag/structtag.go new file mode 100644 index 00000000000..be9bc2293be --- /dev/null +++ b/vendor/github.com/facebookgo/structtag/structtag.go @@ -0,0 +1,61 @@ +// Package structtag provides parsing of the defacto struct tag style. +package structtag + +import ( + "errors" + "strconv" +) + +var errInvalidTag = errors.New("invalid tag") + +// Extract the quoted value for the given name returning it if it is found. The +// found boolean helps differentiate between the "empty and found" vs "empty +// and not found" nature of default empty strings. +func Extract(name, tag string) (found bool, value string, err error) { + for tag != "" { + // skip leading space + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // scan to colon. + // a space or a quote is a syntax error + i = 0 + for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { + i++ + } + if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + return false, "", errInvalidTag + } + foundName := string(tag[:i]) + tag = tag[i+1:] + + // scan quoted string to find value + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + return false, "", errInvalidTag + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if foundName == name { + value, err := strconv.Unquote(qvalue) + if err != nil { + return false, "", err + } + return true, value, nil + } + } + return false, "", nil +} diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 00000000000..835ba3e755c --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 00000000000..842ee80456d --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 00000000000..6b1f2891a5a --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,178 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} From df71fe33fdaa048c51e709327d6d971cadf4fbde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 27 Apr 2018 13:01:32 +0200 Subject: [PATCH 283/319] refactor: refactoring notification service to use new service registry hooks --- pkg/api/org_invite.go | 2 + pkg/cmd/grafana-server/server.go | 11 +-- pkg/services/notifications/mailer.go | 32 ------- pkg/services/notifications/notifications.go | 91 ++++++++++++++----- .../notifications/notifications_test.go | 17 ++-- .../send_email_integration_test.go | 20 ++-- pkg/services/notifications/webhook.go | 31 +------ 7 files changed, 93 insertions(+), 111 deletions(-) diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index d6ab1c9d372..9f4f714af45 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -60,7 +60,9 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response { } // send invite email + c.Logger.Error("sending?") if inviteDto.SendEmail && util.IsEmail(inviteDto.LoginOrEmail) { + c.Logger.Error("yes sending?") emailCmd := m.SendEmailCommand{ To: []string{inviteDto.LoginOrEmail}, Template: "new_user_invite.html", diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 1bf0e90915f..3d4f75978bd 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -26,16 +26,17 @@ import ( "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/tracing" + // self registering services _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" + _ "github.com/grafana/grafana/pkg/services/notifications" _ "github.com/grafana/grafana/pkg/services/search" ) @@ -56,9 +57,9 @@ type GrafanaServerImpl struct { shutdownFn context.CancelFunc childRoutines *errgroup.Group log log.Logger - RouteRegister api.RouteRegister `inject:""` - HttpServer *api.HTTPServer `inject:""` + RouteRegister api.RouteRegister `inject:""` + HttpServer *api.HTTPServer `inject:""` } func (g *GrafanaServerImpl) Start() error { @@ -89,10 +90,6 @@ func (g *GrafanaServerImpl) Start() error { } defer tracingCloser.Close() - if err = notifications.Init(); err != nil { - return fmt.Errorf("Notification service failed to initialize. error: %v", err) - } - serviceGraph := inject.Graph{} serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) serviceGraph.Provide(&inject.Object{Value: dashboards.NewProvisioningService()}) diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index 1bac5025244..37169661d73 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -11,44 +11,12 @@ import ( "html/template" "net" "strconv" - "strings" - "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" gomail "gopkg.in/mail.v2" ) -var mailQueue chan *Message - -func initMailQueue() { - mailQueue = make(chan *Message, 10) - go processMailQueue() -} - -func processMailQueue() { - for { - select { - case msg := <-mailQueue: - num, err := send(msg) - tos := strings.Join(msg.To, "; ") - info := "" - if err != nil { - if len(msg.Info) > 0 { - info = ", info: " + msg.Info - } - log.Error(4, fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err)) - } else { - log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info)) - } - } - } -} - -var addToMailQueue = func(msg *Message) { - mailQueue <- msg -} - func send(msg *Message) (int, error) { dialer, err := createDialer() if err != nil { diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go index 25eb2b5936a..ad776057ad7 100644 --- a/pkg/services/notifications/notifications.go +++ b/pkg/services/notifications/notifications.go @@ -7,11 +7,13 @@ import ( "html/template" "net/url" "path/filepath" + "strings" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -21,20 +23,31 @@ var tmplResetPassword = "reset_password.html" var tmplSignUpStarted = "signup_started.html" var tmplWelcomeOnSignUp = "welcome_on_signup.html" -func Init() error { - initMailQueue() - initWebhookQueue() +func init() { + registry.RegisterService(&NotificationService{}) +} - bus.AddHandler("email", sendResetPasswordEmail) - bus.AddHandler("email", validateResetPasswordCode) - bus.AddHandler("email", sendEmailCommandHandler) +type NotificationService struct { + Bus bus.Bus `inject:""` + mailQueue chan *Message + webhookQueue chan *Webhook + log log.Logger +} - bus.AddCtxHandler("email", sendEmailCommandHandlerSync) +func (ns *NotificationService) Init() error { + ns.log = log.New("notifications") + ns.mailQueue = make(chan *Message, 10) + ns.webhookQueue = make(chan *Webhook, 10) - bus.AddCtxHandler("webhook", SendWebhookSync) + ns.Bus.AddHandler(ns.sendResetPasswordEmail) + ns.Bus.AddHandler(ns.validateResetPasswordCode) + ns.Bus.AddHandler(ns.sendEmailCommandHandler) - bus.AddEventListener(signUpStartedHandler) - bus.AddEventListener(signUpCompletedHandler) + ns.Bus.AddCtxHandler(ns.sendEmailCommandHandlerSync) + ns.Bus.AddCtxHandler(ns.SendWebhookSync) + + ns.Bus.AddEventListener(ns.signUpStartedHandler) + ns.Bus.AddEventListener(ns.signUpCompletedHandler) mailTemplates = template.New("name") mailTemplates.Funcs(template.FuncMap{ @@ -58,8 +71,37 @@ func Init() error { return nil } -func SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error { - return sendWebRequestSync(ctx, &Webhook{ +func (ns *NotificationService) Run(ctx context.Context) error { + for { + select { + case webhook := <-ns.webhookQueue: + err := ns.sendWebRequestSync(context.Background(), webhook) + + if err != nil { + ns.log.Error("Failed to send webrequest ", "error", err) + } + case msg := <-ns.mailQueue: + num, err := send(msg) + tos := strings.Join(msg.To, "; ") + info := "" + if err != nil { + if len(msg.Info) > 0 { + info = ", info: " + msg.Info + } + ns.log.Error(fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err)) + } else { + ns.log.Debug(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info)) + } + case <-ctx.Done(): + return ctx.Err() + } + } + + return nil +} + +func (ns *NotificationService) SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error { + return ns.sendWebRequestSync(ctx, &Webhook{ Url: cmd.Url, User: cmd.User, Password: cmd.Password, @@ -74,7 +116,7 @@ func subjectTemplateFunc(obj map[string]interface{}, value string) string { return "" } -func sendEmailCommandHandlerSync(ctx context.Context, cmd *m.SendEmailCommandSync) error { +func (ns *NotificationService) sendEmailCommandHandlerSync(ctx context.Context, cmd *m.SendEmailCommandSync) error { message, err := buildEmailMessage(&m.SendEmailCommand{ Data: cmd.Data, Info: cmd.Info, @@ -89,24 +131,22 @@ func sendEmailCommandHandlerSync(ctx context.Context, cmd *m.SendEmailCommandSyn } _, err = send(message) - return err } -func sendEmailCommandHandler(cmd *m.SendEmailCommand) error { +func (ns *NotificationService) sendEmailCommandHandler(cmd *m.SendEmailCommand) error { message, err := buildEmailMessage(cmd) if err != nil { return err } - addToMailQueue(message) - + ns.mailQueue <- message return nil } -func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error { - return sendEmailCommandHandler(&m.SendEmailCommand{ +func (ns *NotificationService) sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error { + return ns.sendEmailCommandHandler(&m.SendEmailCommand{ To: []string{cmd.User.Email}, Template: tmplResetPassword, Data: map[string]interface{}{ @@ -116,7 +156,7 @@ func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error { }) } -func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error { +func (ns *NotificationService) validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error { login := getLoginForEmailCode(query.Code) if login == "" { return m.ErrInvalidEmailCode @@ -135,18 +175,18 @@ func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error { return nil } -func signUpStartedHandler(evt *events.SignUpStarted) error { +func (ns *NotificationService) signUpStartedHandler(evt *events.SignUpStarted) error { if !setting.VerifyEmailEnabled { return nil } - log.Info("User signup started: %s", evt.Email) + ns.log.Info("User signup started", "email", evt.Email) if evt.Email == "" { return nil } - err := sendEmailCommandHandler(&m.SendEmailCommand{ + err := ns.sendEmailCommandHandler(&m.SendEmailCommand{ To: []string{evt.Email}, Template: tmplSignUpStarted, Data: map[string]interface{}{ @@ -155,6 +195,7 @@ func signUpStartedHandler(evt *events.SignUpStarted) error { "SignUpUrl": setting.ToAbsUrl(fmt.Sprintf("signup/?email=%s&code=%s", url.QueryEscape(evt.Email), url.QueryEscape(evt.Code))), }, }) + if err != nil { return err } @@ -163,12 +204,12 @@ func signUpStartedHandler(evt *events.SignUpStarted) error { return bus.Dispatch(&emailSentCmd) } -func signUpCompletedHandler(evt *events.SignUpCompleted) error { +func (ns *NotificationService) signUpCompletedHandler(evt *events.SignUpCompleted) error { if evt.Email == "" || !setting.Smtp.SendWelcomeEmailOnSignUp { return nil } - return sendEmailCommandHandler(&m.SendEmailCommand{ + return ns.sendEmailCommandHandler(&m.SendEmailCommand{ To: []string{evt.Email}, Template: tmplWelcomeOnSignUp, Data: map[string]interface{}{ diff --git a/pkg/services/notifications/notifications_test.go b/pkg/services/notifications/notifications_test.go index 3a5ff5fedb7..a86bd3b19ed 100644 --- a/pkg/services/notifications/notifications_test.go +++ b/pkg/services/notifications/notifications_test.go @@ -3,6 +3,7 @@ package notifications import ( "testing" + "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" @@ -17,25 +18,23 @@ type testTriggeredAlert struct { func TestNotifications(t *testing.T) { Convey("Given the notifications service", t, func() { - //bus.ClearBusHandlers() - setting.StaticRootPath = "../../../public/" setting.Smtp.Enabled = true setting.Smtp.TemplatesPattern = "emails/*.html" setting.Smtp.FromAddress = "from@address.com" setting.Smtp.FromName = "Grafana Admin" - err := Init() + ns := &NotificationService{} + ns.Bus = bus.New() + + err := ns.Init() So(err, ShouldBeNil) - var sentMsg *Message - addToMailQueue = func(msg *Message) { - sentMsg = msg - } - Convey("When sending reset email password", func() { - err := sendResetPasswordEmail(&m.SendResetPasswordEmailCommand{User: &m.User{Email: "asd@asd.com"}}) + err := ns.sendResetPasswordEmail(&m.SendResetPasswordEmailCommand{User: &m.User{Email: "asd@asd.com"}}) So(err, ShouldBeNil) + + sentMsg := <-ns.mailQueue So(sentMsg.Body, ShouldContainSubstring, "body") So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password - asd@asd.com") So(sentMsg.Body, ShouldNotContainSubstring, "Subject") diff --git a/pkg/services/notifications/send_email_integration_test.go b/pkg/services/notifications/send_email_integration_test.go index a9a5215d3ca..a9f37018a3a 100644 --- a/pkg/services/notifications/send_email_integration_test.go +++ b/pkg/services/notifications/send_email_integration_test.go @@ -12,8 +12,6 @@ import ( func TestEmailIntegrationTest(t *testing.T) { SkipConvey("Given the notifications service", t, func() { - bus.ClearBusHandlers() - setting.StaticRootPath = "../../../public/" setting.Smtp.Enabled = true setting.Smtp.TemplatesPattern = "emails/*.html" @@ -21,14 +19,11 @@ func TestEmailIntegrationTest(t *testing.T) { setting.Smtp.FromName = "Grafana Admin" setting.BuildVersion = "4.0.0" - err := Init() - So(err, ShouldBeNil) + ns := &NotificationService{} + ns.Bus = bus.New() - addToMailQueue = func(msg *Message) { - So(msg.From, ShouldEqual, "Grafana Admin ") - So(msg.To[0], ShouldEqual, "asdf@asdf.com") - ioutil.WriteFile("../../../tmp/test_email.html", []byte(msg.Body), 0777) - } + err := ns.Init() + So(err, ShouldBeNil) Convey("When sending reset email password", func() { cmd := &m.SendEmailCommand{ @@ -59,8 +54,13 @@ func TestEmailIntegrationTest(t *testing.T) { Template: "alert_notification.html", } - err := sendEmailCommandHandler(cmd) + err := ns.sendEmailCommandHandler(cmd) So(err, ShouldBeNil) + + sentMsg := <-ns.mailQueue + So(sentMsg.From, ShouldEqual, "Grafana Admin ") + So(sentMsg.To[0], ShouldEqual, "asdf@asdf.com") + ioutil.WriteFile("../../../tmp/test_email.html", []byte(sentMsg.Body), 0777) }) }) } diff --git a/pkg/services/notifications/webhook.go b/pkg/services/notifications/webhook.go index 0636a6adadc..01db2d56471 100644 --- a/pkg/services/notifications/webhook.go +++ b/pkg/services/notifications/webhook.go @@ -11,7 +11,6 @@ import ( "golang.org/x/net/context/ctxhttp" - "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/util" ) @@ -37,32 +36,8 @@ var netClient = &http.Client{ Transport: netTransport, } -var ( - webhookQueue chan *Webhook - webhookLog log.Logger -) - -func initWebhookQueue() { - webhookLog = log.New("notifications.webhook") - webhookQueue = make(chan *Webhook, 10) - go processWebhookQueue() -} - -func processWebhookQueue() { - for { - select { - case webhook := <-webhookQueue: - err := sendWebRequestSync(context.Background(), webhook) - - if err != nil { - webhookLog.Error("Failed to send webrequest ", "error", err) - } - } - } -} - -func sendWebRequestSync(ctx context.Context, webhook *Webhook) error { - webhookLog.Debug("Sending webhook", "url", webhook.Url, "http method", webhook.HttpMethod) +func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook *Webhook) error { + ns.log.Debug("Sending webhook", "url", webhook.Url, "http method", webhook.HttpMethod) if webhook.HttpMethod == "" { webhook.HttpMethod = http.MethodPost @@ -98,6 +73,6 @@ func sendWebRequestSync(ctx context.Context, webhook *Webhook) error { return err } - webhookLog.Debug("Webhook failed", "statuscode", resp.Status, "body", string(body)) + ns.log.Debug("Webhook failed", "statuscode", resp.Status, "body", string(body)) return fmt.Errorf("Webhook response status %v", resp.Status) } From 44b0f15a61778d9a0c85cf29c5cfc577ea4594e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 27 Apr 2018 14:28:42 +0200 Subject: [PATCH 284/319] fix: removed log calls used while troubleshooting --- pkg/api/org_invite.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index 9f4f714af45..d6ab1c9d372 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -60,9 +60,7 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response { } // send invite email - c.Logger.Error("sending?") if inviteDto.SendEmail && util.IsEmail(inviteDto.LoginOrEmail) { - c.Logger.Error("yes sending?") emailCmd := m.SendEmailCommand{ To: []string{inviteDto.LoginOrEmail}, Template: "new_user_invite.html", From a8eed9d3440513338b656ff87bd2a3a141c0a33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 27 Apr 2018 15:11:55 +0200 Subject: [PATCH 285/319] Refactoring PluginManager to be a self registering service (#11755) * refator: refactored PluginManager to be a self registering service, a lot more work needed to fully make plugin manager use instance variables and not so many globals --- pkg/cmd/grafana-server/server.go | 10 +--- pkg/plugins/dashboard_importer_test.go | 6 +-- pkg/plugins/dashboards_test.go | 5 +- pkg/plugins/dashboards_updater.go | 8 +-- pkg/plugins/datasource_plugin.go | 2 +- pkg/plugins/plugins.go | 73 +++++++++++++++----------- pkg/plugins/plugins_test.go | 9 ++-- pkg/plugins/update_checker.go | 25 +++------ 8 files changed, 64 insertions(+), 74 deletions(-) diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 1bf0e90915f..da070cc0f40 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/provisioning" "golang.org/x/sync/errgroup" @@ -25,8 +26,6 @@ import ( "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/metrics" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" @@ -34,6 +33,7 @@ import ( "github.com/grafana/grafana/pkg/tracing" _ "github.com/grafana/grafana/pkg/extensions" + _ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" _ "github.com/grafana/grafana/pkg/services/search" @@ -73,12 +73,6 @@ func (g *GrafanaServerImpl) Start() error { login.Init() social.NewOAuthService() - pluginManager, err := plugins.NewPluginManager(g.context) - if err != nil { - return fmt.Errorf("Failed to start plugins. error: %v", err) - } - g.childRoutines.Go(func() error { return pluginManager.Run(g.context) }) - if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil { return fmt.Errorf("Failed to provision Grafana from config. error: %v", err) } diff --git a/pkg/plugins/dashboard_importer_test.go b/pkg/plugins/dashboard_importer_test.go index 549b3bb4cf9..d8460a1875c 100644 --- a/pkg/plugins/dashboard_importer_test.go +++ b/pkg/plugins/dashboard_importer_test.go @@ -1,7 +1,6 @@ package plugins import ( - "context" "io/ioutil" "testing" @@ -91,10 +90,11 @@ func pluginScenario(desc string, t *testing.T, fn func()) { setting.Cfg = ini.Empty() sec, _ := setting.Cfg.NewSection("plugin.test-app") sec.NewKey("path", "../../tests/test-app") - err := initPlugins(context.Background()) + + pm := &PluginManager{} + err := pm.Init() So(err, ShouldBeNil) - Convey(desc, fn) }) } diff --git a/pkg/plugins/dashboards_test.go b/pkg/plugins/dashboards_test.go index 8573d452409..241e41d7bb2 100644 --- a/pkg/plugins/dashboards_test.go +++ b/pkg/plugins/dashboards_test.go @@ -1,7 +1,6 @@ package plugins import ( - "context" "testing" "github.com/grafana/grafana/pkg/bus" @@ -18,7 +17,9 @@ func TestPluginDashboards(t *testing.T) { setting.Cfg = ini.Empty() sec, _ := setting.Cfg.NewSection("plugin.test-app") sec.NewKey("path", "../../tests/test-app") - err := initPlugins(context.Background()) + + pm := &PluginManager{} + err := pm.Init() So(err, ShouldBeNil) diff --git a/pkg/plugins/dashboards_updater.go b/pkg/plugins/dashboards_updater.go index 835e8873810..04d3dc035cc 100644 --- a/pkg/plugins/dashboards_updater.go +++ b/pkg/plugins/dashboards_updater.go @@ -1,8 +1,6 @@ package plugins import ( - "time" - "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" ) @@ -11,10 +9,8 @@ func init() { bus.AddEventListener(handlePluginStateChanged) } -func updateAppDashboards() { - time.Sleep(time.Second * 5) - - plog.Debug("Looking for App Dashboard Updates") +func (pm *PluginManager) updateAppDashboards() { + pm.log.Debug("Looking for App Dashboard Updates") query := m.GetPluginSettingsQuery{OrgId: 0} diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go index 37ce175efe4..114b71deefc 100644 --- a/pkg/plugins/datasource_plugin.go +++ b/pkg/plugins/datasource_plugin.go @@ -76,7 +76,7 @@ func composeBinaryName(executable, os, arch string) string { return fmt.Sprintf("%s_%s_%s%s", executable, os, strings.ToLower(arch), extension) } -func (p *DataSourcePlugin) initBackendPlugin(ctx context.Context, log log.Logger) error { +func (p *DataSourcePlugin) startBackendPlugin(ctx context.Context, log log.Logger) error { p.log = log.New("plugin-id", p.Id) err := p.spawnSubProcess() diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 45e7c934bea..7ce0ac38919 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -11,8 +11,10 @@ import ( "path/filepath" "reflect" "strings" + "time" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -39,30 +41,12 @@ type PluginManager struct { log log.Logger } -func NewPluginManager(ctx context.Context) (*PluginManager, error) { - err := initPlugins(ctx) - - if err != nil { - return nil, err - } - - return &PluginManager{ - log: log.New("plugins"), - }, nil +func init() { + registry.RegisterService(&PluginManager{}) } -func (p *PluginManager) Run(ctx context.Context) error { - <-ctx.Done() - - for _, p := range DataSources { - p.Kill() - } - - p.log.Info("Stopped Plugins", "reason", ctx.Err()) - return ctx.Err() -} - -func initPlugins(ctx context.Context) error { +func (pm *PluginManager) Init() error { + pm.log = log.New("plugins") plog = log.New("plugins") DataSources = map[string]*DataSourcePlugin{} @@ -76,7 +60,7 @@ func initPlugins(ctx context.Context) error { "app": AppPlugin{}, } - plog.Info("Starting plugin search") + pm.log.Info("Starting plugin search") scan(path.Join(setting.StaticRootPath, "app/plugins")) // check if plugins dir exists @@ -99,13 +83,6 @@ func initPlugins(ctx context.Context) error { } for _, ds := range DataSources { - if ds.Backend { - err := ds.initBackendPlugin(ctx, plog) - if err != nil { - plog.Error("Failed to init plugin.", "error", err, "plugin", ds.Id) - } - } - ds.initFrontendPlugin() } @@ -113,8 +90,40 @@ func initPlugins(ctx context.Context) error { app.initApp() } - go StartPluginUpdateChecker() - go updateAppDashboards() + return nil +} + +func (pm *PluginManager) startBackendPlugins(ctx context.Context) error { + for _, ds := range DataSources { + if ds.Backend { + if err := ds.startBackendPlugin(ctx, plog); err != nil { + pm.log.Error("Failed to init plugin.", "error", err, "plugin", ds.Id) + } + } + } + + return nil +} + +func (pm *PluginManager) Run(ctx context.Context) error { + pm.startBackendPlugins(ctx) + pm.updateAppDashboards() + pm.checkForUpdates() + + ticker := time.NewTicker(time.Minute * 10) + for { + select { + case <-ticker.C: + pm.checkForUpdates() + case <-ctx.Done(): + break + } + } + + // kil backend plugins + for _, p := range DataSources { + p.Kill() + } return nil } diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index 00329b4a8a1..7566d054b7f 100644 --- a/pkg/plugins/plugins_test.go +++ b/pkg/plugins/plugins_test.go @@ -1,7 +1,6 @@ package plugins import ( - "context" "path/filepath" "testing" @@ -15,7 +14,9 @@ func TestPluginScans(t *testing.T) { Convey("When scanning for plugins", t, func() { setting.StaticRootPath, _ = filepath.Abs("../../public/") setting.Cfg = ini.Empty() - err := initPlugins(context.Background()) + + pm := &PluginManager{} + err := pm.Init() So(err, ShouldBeNil) So(len(DataSources), ShouldBeGreaterThan, 1) @@ -30,7 +31,9 @@ func TestPluginScans(t *testing.T) { setting.Cfg = ini.Empty() sec, _ := setting.Cfg.NewSection("plugin.nginx-app") sec.NewKey("path", "../../tests/test-app") - err := initPlugins(context.Background()) + + pm := &PluginManager{} + err := pm.Init() So(err, ShouldBeNil) So(len(Apps), ShouldBeGreaterThan, 0) diff --git a/pkg/plugins/update_checker.go b/pkg/plugins/update_checker.go index 946d215b1c2..57f6d2ca651 100644 --- a/pkg/plugins/update_checker.go +++ b/pkg/plugins/update_checker.go @@ -26,23 +26,6 @@ type GithubLatest struct { Testing string `json:"testing"` } -func StartPluginUpdateChecker() { - if !setting.CheckForUpdates { - return - } - - // do one check directly - go checkForUpdates() - - ticker := time.NewTicker(time.Minute * 10) - for { - select { - case <-ticker.C: - checkForUpdates() - } - } -} - func getAllExternalPluginSlugs() string { var result []string for _, plug := range Plugins { @@ -56,8 +39,12 @@ func getAllExternalPluginSlugs() string { return strings.Join(result, ",") } -func checkForUpdates() { - log.Trace("Checking for updates") +func (pm *PluginManager) checkForUpdates() { + if !setting.CheckForUpdates { + return + } + + pm.log.Debug("Checking for updates") pluginSlugs := getAllExternalPluginSlugs() resp, err := httpClient.Get("https://grafana.com/api/plugins/versioncheck?slugIn=" + pluginSlugs + "&grafanaVersion=" + setting.BuildVersion) From 25d3ec5bbf09f8d424b36c53288725a6a29cf9bb Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 27 Apr 2018 15:35:46 +0200 Subject: [PATCH 286/319] Fixed settings default and explore path --- pkg/api/index.go | 2 +- pkg/setting/setting.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 75e3594d854..568b04f95ae 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -125,7 +125,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { Icon: "fa fa-rocket", Url: setting.AppSubUrl + "/explore", Children: []*dtos.NavLink{ - {Text: "New tab", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/explore/new"}, + {Text: "New tab", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/explore"}, }, }) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 37646979095..756417c082d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -613,7 +613,7 @@ func NewConfigContext(args *CommandLineArgs) error { ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true) explore := Cfg.Section("explore") - ExploreEnabled = explore.Key("enabled").MustBool(true) + ExploreEnabled = explore.Key("enabled").MustBool(false) readSessionConfig() readSmtpSettings() From 949e3d29e80d62456bedd153e5e777b2aad9111f Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 27 Apr 2018 15:42:35 +0200 Subject: [PATCH 287/319] Explore: add support for multiple queries * adds +/- buttons to query rows in the Explore section * on Run Query all query expressions are submitted * `generateQueryKey` and `ensureQueries` are helpers to ensure each query field has a unique key for react. --- public/app/containers/Explore/Explore.tsx | 102 ++++++++++--------- public/app/containers/Explore/QueryRows.tsx | 69 +++++++++++++ public/app/containers/Explore/utils/query.ts | 31 ++++++ public/sass/pages/_explore.scss | 21 +++- 4 files changed, 175 insertions(+), 48 deletions(-) create mode 100644 public/app/containers/Explore/QueryRows.tsx create mode 100644 public/app/containers/Explore/utils/query.ts diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index 55c1d088ccc..eae3f4d0a1f 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -5,29 +5,11 @@ import TimeSeries from 'app/core/time_series2'; import ElapsedTime from './ElapsedTime'; import Legend from './Legend'; -import QueryField from './QueryField'; +import QueryRows from './QueryRows'; import Graph from './Graph'; import Table from './Table'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; - -function buildQueryOptions({ format, interval, instant, now, query }) { - const to = now; - const from = to - 1000 * 60 * 60 * 3; - return { - interval, - range: { - from, - to, - }, - targets: [ - { - expr: query, - format, - instant, - }, - ], - }; -} +import { buildQueryOptions, ensureQueries, generateQueryKey, hasQuery } from './utils/query'; function makeTimeSeriesList(dataList, options) { return dataList.map((seriesData, index) => { @@ -63,6 +45,7 @@ interface IExploreState { graphResult: any; latency: number; loading: any; + queries: any; requestOptions: any; showingGraph: boolean; showingTable: boolean; @@ -72,7 +55,6 @@ interface IExploreState { // @observer export class Explore extends React.Component { datasourceSrv: DatasourceSrv; - query: string; constructor(props) { super(props); @@ -83,6 +65,7 @@ export class Explore extends React.Component { graphResult: null, latency: 0, loading: false, + queries: ensureQueries(), requestOptions: null, showingGraph: true, showingTable: true, @@ -100,6 +83,27 @@ export class Explore extends React.Component { } } + handleAddQueryRow = index => { + const { queries } = this.state; + const nextQueries = [ + ...queries.slice(0, index + 1), + { query: '', key: generateQueryKey() }, + ...queries.slice(index + 1), + ]; + this.setState({ queries: nextQueries }); + }; + + handleChangeQuery = (query, index) => { + const { queries } = this.state; + const nextQuery = { + ...queries[index], + query, + }; + const nextQueries = [...queries]; + nextQueries[index] = nextQuery; + this.setState({ queries: nextQueries }); + }; + handleClickGraphButton = () => { this.setState(state => ({ showingGraph: !state.showingGraph })); }; @@ -108,12 +112,13 @@ export class Explore extends React.Component { this.setState(state => ({ showingTable: !state.showingTable })); }; - handleRequestError({ error }) { - console.error(error); - } - - handleQueryChange = query => { - this.query = query; + handleRemoveQueryRow = index => { + const { queries } = this.state; + if (queries.length <= 1) { + return; + } + const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)]; + this.setState({ queries: nextQueries }, () => this.handleSubmit()); }; handleSubmit = () => { @@ -127,9 +132,8 @@ export class Explore extends React.Component { }; async runGraphQuery() { - const { query } = this; - const { datasource } = this.state; - if (!query) { + const { datasource, queries } = this.state; + if (!hasQuery(queries)) { return; } this.setState({ latency: 0, loading: true, graphResult: null }); @@ -139,7 +143,7 @@ export class Explore extends React.Component { interval: datasource.interval, instant: false, now, - query, + queries: queries.map(q => q.query), }); try { const res = await datasource.query(options); @@ -153,14 +157,19 @@ export class Explore extends React.Component { } async runTableQuery() { - const { query } = this; - const { datasource } = this.state; - if (!query) { + const { datasource, queries } = this.state; + if (!hasQuery(queries)) { return; } this.setState({ latency: 0, loading: true, tableResult: null }); const now = Date.now(); - const options = buildQueryOptions({ format: 'table', interval: datasource.interval, instant: true, now, query }); + const options = buildQueryOptions({ + format: 'table', + interval: datasource.interval, + instant: true, + now, + queries: queries.map(q => q.query), + }); try { const res = await datasource.query(options); const tableModel = res.data[0]; @@ -182,10 +191,11 @@ export class Explore extends React.Component { datasource, datasourceError, datasourceLoading, + graphResult, latency, loading, + queries, requestOptions, - graphResult, showingGraph, showingTable, tableResult, @@ -205,7 +215,8 @@ export class Explore extends React.Component { {datasource ? (
    -
    +
    + {loading || latency ? : null} @@ -219,15 +230,14 @@ export class Explore extends React.Component {
    -
    - -
    - {loading || latency ? : null} +
    {showingGraph ? ( diff --git a/public/app/containers/Explore/QueryRows.tsx b/public/app/containers/Explore/QueryRows.tsx new file mode 100644 index 00000000000..a0a9981368d --- /dev/null +++ b/public/app/containers/Explore/QueryRows.tsx @@ -0,0 +1,69 @@ +import React, { PureComponent } from 'react'; + +import QueryField from './QueryField'; + +class QueryRow extends PureComponent { + constructor(props) { + super(props); + this.state = { + query: '', + }; + } + + handleChangeQuery = value => { + const { index, onChangeQuery } = this.props; + this.setState({ query: value }); + if (onChangeQuery) { + onChangeQuery(value, index); + } + }; + + handleClickAddButton = () => { + const { index, onAddQueryRow } = this.props; + if (onAddQueryRow) { + onAddQueryRow(index); + } + }; + + handleClickRemoveButton = () => { + const { index, onRemoveQueryRow } = this.props; + if (onRemoveQueryRow) { + onRemoveQueryRow(index); + } + }; + + handlePressEnter = () => { + const { onExecuteQuery } = this.props; + if (onExecuteQuery) { + onExecuteQuery(); + } + }; + + render() { + const { request } = this.props; + return ( +
    +
    + + +
    +
    + +
    +
    + ); + } +} + +export default class QueryRows extends PureComponent { + render() { + const { className = '', queries, ...handlers } = this.props; + return ( +
    {queries.map((q, index) => )}
    + ); + } +} diff --git a/public/app/containers/Explore/utils/query.ts b/public/app/containers/Explore/utils/query.ts new file mode 100644 index 00000000000..d51c7339944 --- /dev/null +++ b/public/app/containers/Explore/utils/query.ts @@ -0,0 +1,31 @@ +export function buildQueryOptions({ format, interval, instant, now, queries }) { + const to = now; + const from = to - 1000 * 60 * 60 * 3; + return { + interval, + range: { + from, + to, + }, + targets: queries.map(expr => ({ + expr, + format, + instant, + })), + }; +} + +export function generateQueryKey(index = 0) { + return `Q-${Date.now()}-${Math.random()}-${index}`; +} + +export function ensureQueries(queries?) { + if (queries && typeof queries === 'object' && queries.length > 0 && typeof queries[0] === 'string') { + return queries.map((query, i) => ({ key: generateQueryKey(i), query })); + } + return [{ key: generateQueryKey(), query: '' }]; +} + +export function hasQuery(queries) { + return queries.some(q => q.query); +} diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 4bd0162563b..74a19c1d2c2 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -4,6 +4,23 @@ } } +.query-row { + position: relative; + + & + & { + margin-top: 0.5rem; + } +} + +.query-row-tools { + position: absolute; + left: -4rem; + top: 0.33rem; + > * { + margin-right: 0.25rem; + } +} + .query-field { font-size: 14px; font-family: Consolas, Menlo, Courier, monospace; @@ -14,14 +31,14 @@ position: relative; display: inline-block; padding: 6px 7px 4px; - width: calc(100% - 6rem); + width: 100%; cursor: text; line-height: 1.5; color: rgba(0, 0, 0, 0.65); background-color: #fff; background-image: none; border: 1px solid lightgray; - border-radius: 4px; + border-radius: 3px; transition: all 0.3s; } From 0cbeb56af16b3a3bcec088a30d34a283ae98775f Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 27 Apr 2018 16:41:07 +0200 Subject: [PATCH 288/319] disable ent build to avoid slowing down build speed --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d3e6c71b520..bad5a7c1cd0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -192,7 +192,7 @@ workflows: ignore: /.*/ tags: only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ - - build-enterprise: - filters: - tags: - only: /.*/ + # - build-enterprise: + # filters: + # tags: + # only: /.*/ From ec23816df65a327cd72bf6125369b9b98d93a661 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Fri, 27 Apr 2018 17:06:08 +0200 Subject: [PATCH 289/319] docs: further documents changes to the docker image. (#11763) * docs: further documents changes to the docker image. * docs: explains the changes to user id better. --- docs/sources/installation/docker.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/docker.md b/docs/sources/installation/docker.md index d6f3ae16466..e78796845c4 100644 --- a/docs/sources/installation/docker.md +++ b/docs/sources/installation/docker.md @@ -132,7 +132,28 @@ docker run -d --user $ID --volume "$PWD/data:/var/lib/grafana" -p 3000:3000 graf ## Migration from a previous version of the docker container to 5.1 or later -In 5.1 we switched the id of the grafana user. Unfortunately this means that files created prior to 5.1 won't have the correct permissions for later versions. We made this change so that it would be easier for you to control what user Grafana is executed as (see examples below). +The docker container for Grafana has seen a major rewrite for 5.1. + +**Important changes** + +* file ownership is no longer modified during startup with `chown` +* default user id `472` instead of `104` +* no more implicit volumes + - `/var/lib/grafana` + - `/etc/grafana` + - `/var/log/grafana` + +### Removal of implicit volumes + +Previously `/var/lib/grafana`, `/etc/grafana` and `/var/log/grafana` were defined as volumes in the `Dockerfile`. This led to the creation of three volumes each time a new instance of the Grafana container started, whether you wanted it or not. + +You should always be careful to define your own named volume for storage, but if you depended on these volumes you should be aware that an upgraded container will no longer have them. + +**Warning**: when migrating from an earlier version to 5.1 or later using docker compose and implicit volumes you need to use `docker inspect` to find out which volumes your container is mapped to so that you can map them to the upgraded container as well. You will also have to change file ownership (or user) as documented below. + +### User ID changes + +In 5.1 we switched the id of the grafana user. Unfortunately this means that files created prior to 5.1 won't have the correct permissions for later versions. We made this change so that it would be more likely that the grafana users id would be unique to Grafana. For example, on Ubuntu 16.04 `104` is already in use by the syslog user. Version | User | User ID --------|---------|--------- @@ -141,13 +162,13 @@ Version | User | User ID There are two possible solutions to this problem. Either you start the new container as the root user and change ownership from `104` to `472` or you start the upgraded container as user `104`. -### Running docker as a different user +#### Running docker as a different user ```bash docker run --user 104 --volume "" grafana/grafana:5.1.0 ``` -#### docker-compose.yml with custom user +##### Specifying a user in docker-compose.yml ```yaml version: "2" @@ -159,7 +180,7 @@ services: user: "104" ``` -### Modifying permissions +#### Modifying permissions The commands below will run bash inside the Grafana container with your volume mapped in. This makes it possible to modify the file ownership to match the new container. Always be careful when modifying permissions. From 7e2fb5e92e4b5dc66846904f6600c9c40e392031 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 27 Apr 2018 16:53:42 +0200 Subject: [PATCH 290/319] appveyor: uppercase the C drive in go path Fixes #11758 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2b0bddde162..a71eb9f81b4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ clone_folder: c:\gopath\src\github.com\grafana\grafana environment: nodejs_version: "6" - GOPATH: c:\gopath + GOPATH: C:\gopath GOVERSION: 1.10 install: From b3531362cafedd3c93c0e5311b8a950db7b17820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 27 Apr 2018 21:22:29 +0200 Subject: [PATCH 291/319] fix: minor fix to plugin service shut down flow --- pkg/plugins/plugins.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 7ce0ac38919..8ccdb9cf18b 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -111,11 +111,14 @@ func (pm *PluginManager) Run(ctx context.Context) error { pm.checkForUpdates() ticker := time.NewTicker(time.Minute * 10) - for { + run := true + + for run { select { case <-ticker.C: pm.checkForUpdates() case <-ctx.Done(): + run = false break } } @@ -125,7 +128,7 @@ func (pm *PluginManager) Run(ctx context.Context) error { p.Kill() } - return nil + return ctx.Err() } func checkPluginPaths() error { From b7adf28501464c9fe5c933d70f0be0ad785846c5 Mon Sep 17 00:00:00 2001 From: Karsten Weiss Date: Fri, 27 Apr 2018 22:14:36 +0200 Subject: [PATCH 292/319] Remove redundancy in variable declarations (golint) This commit fixes the following golint warnings: pkg/api/avatar/avatar.go:229:12: should omit type *http.Client from declaration of var client; it will be inferred from the right-hand side pkg/login/brute_force_login_protection.go:13:26: should omit type time.Duration from declaration of var loginAttemptsWindow; it will be inferred from the right-hand side pkg/metrics/graphitebridge/graphite.go:58:26: should omit type []string from declaration of var metricCategoryPrefix; it will be inferred from the right-hand side pkg/metrics/graphitebridge/graphite.go:69:22: should omit type []string from declaration of var trimMetricPrefix; it will be inferred from the right-hand side pkg/models/alert.go:37:36: should omit type error from declaration of var ErrCannotChangeStateOnPausedAlert; it will be inferred from the right-hand side pkg/models/alert.go:38:36: should omit type error from declaration of var ErrRequiresNewState; it will be inferred from the right-hand side pkg/models/datasource.go:61:28: should omit type map[string]bool from declaration of var knownDatasourcePlugins; it will be inferred from the right-hand side pkg/plugins/update_checker.go:16:13: should omit type http.Client from declaration of var httpClient; it will be inferred from the right-hand side pkg/services/alerting/engine.go:103:24: should omit type time.Duration from declaration of var unfinishedWorkTimeout; it will be inferred from the right-hand side pkg/services/alerting/engine.go:105:19: should omit type time.Duration from declaration of var alertTimeout; it will be inferred from the right-hand side pkg/services/alerting/engine.go:106:19: should omit type int from declaration of var alertMaxAttempts; it will be inferred from the right-hand side pkg/services/alerting/notifier.go:143:23: should omit type map[string]*NotifierPlugin from declaration of var notifierFactories; it will be inferred from the right-hand side pkg/services/alerting/rule.go:136:24: should omit type map[string]ConditionFactory from declaration of var conditionFactories; it will be inferred from the right-hand side pkg/services/alerting/conditions/evaluator.go:12:15: should omit type []string from declaration of var defaultTypes; it will be inferred from the right-hand side pkg/services/alerting/conditions/evaluator.go:13:15: should omit type []string from declaration of var rangedTypes; it will be inferred from the right-hand side pkg/services/alerting/notifiers/opsgenie.go:44:19: should omit type string from declaration of var opsgenieAlertURL; it will be inferred from the right-hand side pkg/services/alerting/notifiers/pagerduty.go:43:23: should omit type string from declaration of var pagerdutyEventApiUrl; it will be inferred from the right-hand side pkg/services/alerting/notifiers/telegram.go:21:17: should omit type string from declaration of var telegramApiUrl; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/config_reader_test.go:11:24: should omit type string from declaration of var simpleDashboardConfig; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/config_reader_test.go:12:24: should omit type string from declaration of var oldVersion; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/config_reader_test.go:13:24: should omit type string from declaration of var brokenConfigs; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/file_reader.go:22:30: should omit type time.Duration from declaration of var checkDiskForChangesInterval; it will be inferred from the right-hand side pkg/services/provisioning/dashboards/file_reader.go:24:23: should omit type error from declaration of var ErrFolderNameMissing; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:15:34: should omit type string from declaration of var twoDatasourcesConfig; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:16:34: should omit type string from declaration of var twoDatasourcesConfigPurgeOthers; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:17:34: should omit type string from declaration of var doubleDatasourcesConfig; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:18:34: should omit type string from declaration of var allProperties; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:19:34: should omit type string from declaration of var versionZero; it will be inferred from the right-hand side pkg/services/provisioning/datasources/config_reader_test.go:20:34: should omit type string from declaration of var brokenYaml; it will be inferred from the right-hand side pkg/services/sqlstore/stats.go:16:25: should omit type time.Duration from declaration of var activeUserTimeLimit; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/mysql_dialect.go:69:14: should omit type bool from declaration of var hasLen1; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/mysql_dialect.go:70:14: should omit type bool from declaration of var hasLen2; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/postgres_dialect.go:95:14: should omit type bool from declaration of var hasLen1; it will be inferred from the right-hand side pkg/services/sqlstore/migrator/postgres_dialect.go:96:14: should omit type bool from declaration of var hasLen2; it will be inferred from the right-hand side pkg/setting/setting.go:42:15: should omit type string from declaration of var Env; it will be inferred from the right-hand side pkg/setting/setting.go:161:18: should omit type bool from declaration of var LdapAllowSignup; it will be inferred from the right-hand side pkg/setting/setting.go:473:30: should omit type bool from declaration of var skipStaticRootValidation; it will be inferred from the right-hand side pkg/tsdb/interval.go:14:21: should omit type time.Duration from declaration of var defaultMinInterval; it will be inferred from the right-hand side pkg/tsdb/interval.go:15:21: should omit type time.Duration from declaration of var year; it will be inferred from the right-hand side pkg/tsdb/interval.go:16:21: should omit type time.Duration from declaration of var day; it will be inferred from the right-hand side pkg/tsdb/cloudwatch/credentials.go:26:24: should omit type map[string]cache from declaration of var awsCredentialCache; it will be inferred from the right-hand side pkg/tsdb/influxdb/query.go:15:27: should omit type *regexp.Regexp from declaration of var regexpOperatorPattern; it will be inferred from the right-hand side pkg/tsdb/influxdb/query.go:16:27: should omit type *regexp.Regexp from declaration of var regexpMeasurementPattern; it will be inferred from the right-hand side pkg/tsdb/mssql/mssql_test.go:25:14: should omit type string from declaration of var serverIP; it will be inferred from the right-hand side --- pkg/api/avatar/avatar.go | 2 +- pkg/login/brute_force_login_protection.go | 4 ++-- pkg/metrics/graphitebridge/graphite.go | 4 ++-- pkg/models/alert.go | 4 ++-- pkg/models/datasource.go | 2 +- pkg/plugins/update_checker.go | 2 +- pkg/services/alerting/conditions/evaluator.go | 4 ++-- pkg/services/alerting/engine.go | 6 +++--- pkg/services/alerting/notifier.go | 2 +- pkg/services/alerting/notifiers/opsgenie.go | 2 +- pkg/services/alerting/notifiers/pagerduty.go | 2 +- pkg/services/alerting/notifiers/telegram.go | 2 +- pkg/services/alerting/rule.go | 2 +- .../provisioning/dashboards/config_reader_test.go | 6 +++--- .../provisioning/dashboards/file_reader.go | 4 ++-- .../datasources/config_reader_test.go | 15 ++++++++------- pkg/services/sqlstore/migrator/mysql_dialect.go | 4 ++-- .../sqlstore/migrator/postgres_dialect.go | 4 ++-- pkg/services/sqlstore/stats.go | 2 +- pkg/setting/setting.go | 6 +++--- pkg/tsdb/cloudwatch/credentials.go | 2 +- pkg/tsdb/influxdb/query.go | 4 ++-- pkg/tsdb/interval.go | 8 ++++---- pkg/tsdb/mssql/mssql_test.go | 2 +- 24 files changed, 48 insertions(+), 47 deletions(-) diff --git a/pkg/api/avatar/avatar.go b/pkg/api/avatar/avatar.go index 9f282794076..5becf90ca35 100644 --- a/pkg/api/avatar/avatar.go +++ b/pkg/api/avatar/avatar.go @@ -226,7 +226,7 @@ func (this *thunderTask) Fetch() { this.Done() } -var client *http.Client = &http.Client{ +var client = &http.Client{ Timeout: time.Second * 2, Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}, } diff --git a/pkg/login/brute_force_login_protection.go b/pkg/login/brute_force_login_protection.go index ca5e0a667ff..d524c420540 100644 --- a/pkg/login/brute_force_login_protection.go +++ b/pkg/login/brute_force_login_protection.go @@ -9,8 +9,8 @@ import ( ) var ( - maxInvalidLoginAttempts int64 = 5 - loginAttemptsWindow time.Duration = time.Minute * 5 + maxInvalidLoginAttempts int64 = 5 + loginAttemptsWindow = time.Minute * 5 ) var validateLoginAttempts = func(username string) error { diff --git a/pkg/metrics/graphitebridge/graphite.go b/pkg/metrics/graphitebridge/graphite.go index 670636cedce..5b61f078e6c 100644 --- a/pkg/metrics/graphitebridge/graphite.go +++ b/pkg/metrics/graphitebridge/graphite.go @@ -55,7 +55,7 @@ const ( AbortOnError ) -var metricCategoryPrefix []string = []string{ +var metricCategoryPrefix = []string{ "proxy_", "api_", "page_", @@ -66,7 +66,7 @@ var metricCategoryPrefix []string = []string{ "go_", "process_"} -var trimMetricPrefix []string = []string{"grafana_"} +var trimMetricPrefix = []string{"grafana_"} // Config defines the Graphite bridge config. type Config struct { diff --git a/pkg/models/alert.go b/pkg/models/alert.go index 88b49350b97..b72d87e94b2 100644 --- a/pkg/models/alert.go +++ b/pkg/models/alert.go @@ -34,8 +34,8 @@ const ( ) var ( - ErrCannotChangeStateOnPausedAlert error = fmt.Errorf("Cannot change state on pause alert") - ErrRequiresNewState error = fmt.Errorf("update alert state requires a new state.") + ErrCannotChangeStateOnPausedAlert = fmt.Errorf("Cannot change state on pause alert") + ErrRequiresNewState = fmt.Errorf("update alert state requires a new state.") ) func (s AlertStateType) IsValid() bool { diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index f2236ad8477..b7e3e3eaa17 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -58,7 +58,7 @@ type DataSource struct { Updated time.Time } -var knownDatasourcePlugins map[string]bool = map[string]bool{ +var knownDatasourcePlugins = map[string]bool{ DS_ES: true, DS_GRAPHITE: true, DS_INFLUXDB: true, diff --git a/pkg/plugins/update_checker.go b/pkg/plugins/update_checker.go index 57f6d2ca651..e61f4cf1df7 100644 --- a/pkg/plugins/update_checker.go +++ b/pkg/plugins/update_checker.go @@ -13,7 +13,7 @@ import ( ) var ( - httpClient http.Client = http.Client{Timeout: 10 * time.Second} + httpClient = http.Client{Timeout: 10 * time.Second} ) type GrafanaNetPlugin struct { diff --git a/pkg/services/alerting/conditions/evaluator.go b/pkg/services/alerting/conditions/evaluator.go index dfc058940cf..8d7ca57f010 100644 --- a/pkg/services/alerting/conditions/evaluator.go +++ b/pkg/services/alerting/conditions/evaluator.go @@ -9,8 +9,8 @@ import ( ) var ( - defaultTypes []string = []string{"gt", "lt"} - rangedTypes []string = []string{"within_range", "outside_range"} + defaultTypes = []string{"gt", "lt"} + rangedTypes = []string{"within_range", "outside_range"} ) type AlertEvaluator interface { diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index bdd8ff2cfe2..ddc91c0eb10 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -100,10 +100,10 @@ func (e *Engine) runJobDispatcher(grafanaCtx context.Context) error { } var ( - unfinishedWorkTimeout time.Duration = time.Second * 5 + unfinishedWorkTimeout = time.Second * 5 // TODO: Make alertTimeout and alertMaxAttempts configurable in the config file. - alertTimeout time.Duration = time.Second * 30 - alertMaxAttempts int = 3 + alertTimeout = time.Second * 30 + alertMaxAttempts = 3 ) func (e *Engine) processJobWithRetry(grafanaCtx context.Context, job *Job) error { diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index af9ba52a52a..a30ca18b41d 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -140,7 +140,7 @@ func (n *notificationService) createNotifierFor(model *m.AlertNotification) (Not type NotifierFactory func(notification *m.AlertNotification) (Notifier, error) -var notifierFactories map[string]*NotifierPlugin = make(map[string]*NotifierPlugin) +var notifierFactories = make(map[string]*NotifierPlugin) func RegisterNotifier(plugin *NotifierPlugin) { notifierFactories[plugin.Type] = plugin diff --git a/pkg/services/alerting/notifiers/opsgenie.go b/pkg/services/alerting/notifiers/opsgenie.go index 5d8b15160c4..f0f5142cf05 100644 --- a/pkg/services/alerting/notifiers/opsgenie.go +++ b/pkg/services/alerting/notifiers/opsgenie.go @@ -41,7 +41,7 @@ func init() { } var ( - opsgenieAlertURL string = "https://api.opsgenie.com/v2/alerts" + opsgenieAlertURL = "https://api.opsgenie.com/v2/alerts" ) func NewOpsGenieNotifier(model *m.AlertNotification) (alerting.Notifier, error) { diff --git a/pkg/services/alerting/notifiers/pagerduty.go b/pkg/services/alerting/notifiers/pagerduty.go index 58484051432..02219b2203d 100644 --- a/pkg/services/alerting/notifiers/pagerduty.go +++ b/pkg/services/alerting/notifiers/pagerduty.go @@ -40,7 +40,7 @@ func init() { } var ( - pagerdutyEventApiUrl string = "https://events.pagerduty.com/v2/enqueue" + pagerdutyEventApiUrl = "https://events.pagerduty.com/v2/enqueue" ) func NewPagerdutyNotifier(model *m.AlertNotification) (alerting.Notifier, error) { diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index 1e62c68d7eb..1b259298eae 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -18,7 +18,7 @@ const ( ) var ( - telegramApiUrl string = "https://api.telegram.org/bot%s/%s" + telegramApiUrl = "https://api.telegram.org/bot%s/%s" ) func init() { diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index 027ff96d6c0..0326b25de32 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -133,7 +133,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { type ConditionFactory func(model *simplejson.Json, index int) (Condition, error) -var conditionFactories map[string]ConditionFactory = make(map[string]ConditionFactory) +var conditionFactories = make(map[string]ConditionFactory) func RegisterCondition(typeName string, factory ConditionFactory) { conditionFactories[typeName] = factory diff --git a/pkg/services/provisioning/dashboards/config_reader_test.go b/pkg/services/provisioning/dashboards/config_reader_test.go index ecbf6435c36..72664c37990 100644 --- a/pkg/services/provisioning/dashboards/config_reader_test.go +++ b/pkg/services/provisioning/dashboards/config_reader_test.go @@ -8,9 +8,9 @@ import ( ) var ( - simpleDashboardConfig string = "./test-configs/dashboards-from-disk" - oldVersion string = "./test-configs/version-0" - brokenConfigs string = "./test-configs/broken-configs" + simpleDashboardConfig = "./test-configs/dashboards-from-disk" + oldVersion = "./test-configs/version-0" + brokenConfigs = "./test-configs/broken-configs" ) func TestDashboardsAsConfig(t *testing.T) { diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index 7d4231deeae..e5186e12f06 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -19,9 +19,9 @@ import ( ) var ( - checkDiskForChangesInterval time.Duration = time.Second * 3 + checkDiskForChangesInterval = time.Second * 3 - ErrFolderNameMissing error = errors.New("Folder name missing") + ErrFolderNameMissing = errors.New("Folder name missing") ) type fileReader struct { diff --git a/pkg/services/provisioning/datasources/config_reader_test.go b/pkg/services/provisioning/datasources/config_reader_test.go index 7d621ffe70f..89ecc5a0b68 100644 --- a/pkg/services/provisioning/datasources/config_reader_test.go +++ b/pkg/services/provisioning/datasources/config_reader_test.go @@ -11,13 +11,14 @@ import ( ) var ( - logger log.Logger = log.New("fake.log") - twoDatasourcesConfig string = "./test-configs/two-datasources" - twoDatasourcesConfigPurgeOthers string = "./test-configs/insert-two-delete-two" - doubleDatasourcesConfig string = "./test-configs/double-default" - allProperties string = "./test-configs/all-properties" - versionZero string = "./test-configs/version-0" - brokenYaml string = "./test-configs/broken-yaml" + logger log.Logger = log.New("fake.log") + + twoDatasourcesConfig = "./test-configs/two-datasources" + twoDatasourcesConfigPurgeOthers = "./test-configs/insert-two-delete-two" + doubleDatasourcesConfig = "./test-configs/double-default" + allProperties = "./test-configs/all-properties" + versionZero = "./test-configs/version-0" + brokenYaml = "./test-configs/broken-yaml" fakeRepo *fakeRepository ) diff --git a/pkg/services/sqlstore/migrator/mysql_dialect.go b/pkg/services/sqlstore/migrator/mysql_dialect.go index 1968558dbb8..300224135f0 100644 --- a/pkg/services/sqlstore/migrator/mysql_dialect.go +++ b/pkg/services/sqlstore/migrator/mysql_dialect.go @@ -66,8 +66,8 @@ func (db *Mysql) SqlType(c *Column) string { res = c.Type } - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) + var hasLen1 = (c.Length > 0) + var hasLen2 = (c.Length2 > 0) if res == DB_BigInt && !hasLen1 && !hasLen2 { c.Length = 20 diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index 8de26194411..e2da562c14e 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -92,8 +92,8 @@ func (db *Postgres) SqlType(c *Column) string { res = t } - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) + var hasLen1 = (c.Length > 0) + var hasLen2 = (c.Length2 > 0) if hasLen2 { res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" } else if hasLen1 { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 47020d1a6f7..173a1e56634 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -13,7 +13,7 @@ func init() { bus.AddHandler("sql", GetAdminStats) } -var activeUserTimeLimit time.Duration = time.Hour * 24 * 30 +var activeUserTimeLimit = time.Hour * 24 * 30 func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error { var rawSql = `SELECT COUNT(*) as count, type FROM data_source GROUP BY type` diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 3aeb9eddaf0..922eea607d1 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -39,7 +39,7 @@ const ( var ( // App settings. - Env string = DEV + Env = DEV AppUrl string AppSubUrl string InstanceName string @@ -158,7 +158,7 @@ var ( // LDAP LdapEnabled bool LdapConfigFile string - LdapAllowSignup bool = true + LdapAllowSignup = true // SMTP email settings Smtp SmtpSettings @@ -470,7 +470,7 @@ func setHomePath(args *CommandLineArgs) { } } -var skipStaticRootValidation bool = false +var skipStaticRootValidation = false func validateStaticRootPath() error { if skipStaticRootValidation { diff --git a/pkg/tsdb/cloudwatch/credentials.go b/pkg/tsdb/cloudwatch/credentials.go index 06848323fbb..8b32c76daa3 100644 --- a/pkg/tsdb/cloudwatch/credentials.go +++ b/pkg/tsdb/cloudwatch/credentials.go @@ -23,7 +23,7 @@ type cache struct { expiration *time.Time } -var awsCredentialCache map[string]cache = make(map[string]cache) +var awsCredentialCache = make(map[string]cache) var credentialCacheLock sync.RWMutex func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { diff --git a/pkg/tsdb/influxdb/query.go b/pkg/tsdb/influxdb/query.go index 9fbe133c055..0637a5bbb44 100644 --- a/pkg/tsdb/influxdb/query.go +++ b/pkg/tsdb/influxdb/query.go @@ -12,8 +12,8 @@ import ( ) var ( - regexpOperatorPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`) - regexpMeasurementPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`) + regexpOperatorPattern = regexp.MustCompile(`^\/.*\/$`) + regexpMeasurementPattern = regexp.MustCompile(`^\/.*\/$`) ) func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) { diff --git a/pkg/tsdb/interval.go b/pkg/tsdb/interval.go index e26d39f3986..49904f27a37 100644 --- a/pkg/tsdb/interval.go +++ b/pkg/tsdb/interval.go @@ -10,10 +10,10 @@ import ( ) var ( - defaultRes int64 = 1500 - defaultMinInterval time.Duration = 1 * time.Millisecond - year time.Duration = time.Hour * 24 * 365 - day time.Duration = time.Hour * 24 + defaultRes int64 = 1500 + defaultMinInterval = time.Millisecond * 1 + year = time.Hour * 24 * 365 + day = time.Hour * 24 ) type Interval struct { diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index 167d02a1e07..e62d30a6325 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -22,7 +22,7 @@ import ( // There is also a dashboard.json in same directory that you can import to Grafana // once you've created a datasource for the test server/database. // If needed, change the variable below to the IP address of the database. -var serverIP string = "localhost" +var serverIP = "localhost" func TestMSSQL(t *testing.T) { SkipConvey("MSSQL", t, func() { From de8696d5d3f1864a495b0246972bd4adc9ba8ea6 Mon Sep 17 00:00:00 2001 From: Karsten Weiss Date: Fri, 27 Apr 2018 22:42:49 +0200 Subject: [PATCH 293/319] Outdent code after if block that ends with return (golint) This commit fixes the following golint warnings: pkg/bus/bus.go:64:9: if block ends with a return statement, so drop this else and outdent its block pkg/bus/bus.go:84:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:137:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:177:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:183:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:199:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:208:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/components/dynmap/dynmap.go:236:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:242:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:257:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:263:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:278:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:284:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:299:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:331:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:350:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:356:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:366:12: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:390:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:396:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:405:12: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:427:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:433:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:442:12: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:459:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:465:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:474:12: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:491:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:497:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:506:12: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:523:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:529:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:538:12: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:555:9: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:561:10: if block ends with a return statement, so drop this else and outdent its block pkg/components/dynmap/dynmap.go:570:12: if block ends with a return statement, so drop this else and outdent its block pkg/login/ldap.go:55:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/login/ldap_test.go:372:10: if block ends with a return statement, so drop this else and outdent its block pkg/middleware/middleware_test.go:213:12: if block ends with a return statement, so drop this else and outdent its block pkg/plugins/dashboard_importer.go:153:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/plugins/dashboards_updater.go:39:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/plugins/dashboards_updater.go:121:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/plugins/plugins.go:210:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/plugins/plugins.go:235:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/eval_context.go:111:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/notifier.go:92:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/notifier.go:98:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/notifier.go:122:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/rule.go:108:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/rule.go:118:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/rule.go:121:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/alerting/notifiers/telegram.go:94:10: if block ends with a return statement, so drop this else and outdent its block pkg/services/sqlstore/annotation.go:34:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/sqlstore/annotation.go:99:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/sqlstore/dashboard_test.go:107:13: if block ends with a return statement, so drop this else and outdent its block pkg/services/sqlstore/plugin_setting.go:78:10: if block ends with a return statement, so drop this else and outdent its block pkg/services/sqlstore/preferences.go:91:10: if block ends with a return statement, so drop this else and outdent its block pkg/services/sqlstore/user.go:50:10: if block ends with a return statement, so drop this else and outdent its block pkg/services/sqlstore/migrator/migrator.go:106:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/services/sqlstore/migrator/postgres_dialect.go:48:10: if block ends with a return statement, so drop this else and outdent its block pkg/tsdb/time_range.go:59:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/tsdb/time_range.go:67:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) pkg/tsdb/cloudwatch/metric_find_query.go:225:9: if block ends with a return statement, so drop this else and outdent its block pkg/util/filepath.go:68:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) --- pkg/bus/bus.go | 6 +- pkg/components/dynmap/dynmap.go | 286 +++++++----------- pkg/login/ldap.go | 10 +- pkg/login/ldap_test.go | 5 +- pkg/middleware/middleware_test.go | 3 +- pkg/plugins/dashboard_importer.go | 6 +- pkg/plugins/dashboards_updater.go | 44 +-- pkg/plugins/plugins.go | 12 +- pkg/services/alerting/eval_context.go | 6 +- pkg/services/alerting/notifier.go | 22 +- pkg/services/alerting/notifiers/telegram.go | 3 +- pkg/services/alerting/rule.go | 20 +- pkg/services/sqlstore/annotation.go | 28 +- pkg/services/sqlstore/dashboard_test.go | 3 +- pkg/services/sqlstore/migrator/migrator.go | 10 +- .../sqlstore/migrator/postgres_dialect.go | 3 +- pkg/services/sqlstore/plugin_setting.go | 53 ++-- pkg/services/sqlstore/preferences.go | 15 +- pkg/services/sqlstore/user.go | 5 +- pkg/tsdb/cloudwatch/metric_find_query.go | 3 +- pkg/tsdb/time_range.go | 12 +- pkg/util/filepath.go | 3 +- 22 files changed, 238 insertions(+), 320 deletions(-) diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go index 437796991a5..32a591b6672 100644 --- a/pkg/bus/bus.go +++ b/pkg/bus/bus.go @@ -61,9 +61,8 @@ func (b *InProcBus) DispatchCtx(ctx context.Context, msg Msg) error { err := ret[0].Interface() if err == nil { return nil - } else { - return err.(error) } + return err.(error) } func (b *InProcBus) Dispatch(msg Msg) error { @@ -81,9 +80,8 @@ func (b *InProcBus) Dispatch(msg Msg) error { err := ret[0].Interface() if err == nil { return nil - } else { - return err.(error) } + return err.(error) } func (b *InProcBus) Publish(msg Msg) error { diff --git a/pkg/components/dynmap/dynmap.go b/pkg/components/dynmap/dynmap.go index 2b86ce384eb..96effb24332 100644 --- a/pkg/components/dynmap/dynmap.go +++ b/pkg/components/dynmap/dynmap.go @@ -134,9 +134,8 @@ func (v *Value) get(key string) (*Value, error) { child, ok := obj.Map()[key] if ok { return child, nil - } else { - return nil, KeyNotFoundError{key} } + return nil, KeyNotFoundError{key} } return nil, err @@ -174,17 +173,13 @@ func (v *Object) GetObject(keys ...string) (*Object, error) { if err != nil { return nil, err - } else { - - obj, err := child.Object() - - if err != nil { - return nil, err - } else { - return obj, nil - } - } + obj, err := child.Object() + + if err != nil { + return nil, err + } + return obj, nil } // Gets the value at key path and attempts to typecast the value into a string. @@ -196,18 +191,17 @@ func (v *Object) GetString(keys ...string) (string, error) { if err != nil { return "", err - } else { - return child.String() } + return child.String() } func (v *Object) MustGetString(path string, def string) string { keys := strings.Split(path, ".") - if str, err := v.GetString(keys...); err != nil { + str, err := v.GetString(keys...) + if err != nil { return def - } else { - return str } + return str } // Gets the value at key path and attempts to typecast the value into null. @@ -233,16 +227,13 @@ func (v *Object) GetNumber(keys ...string) (json.Number, error) { if err != nil { return "", err - } else { - - n, err := child.Number() - - if err != nil { - return "", err - } else { - return n, nil - } } + n, err := child.Number() + + if err != nil { + return "", err + } + return n, nil } // Gets the value at key path and attempts to typecast the value into a float64. @@ -254,16 +245,13 @@ func (v *Object) GetFloat64(keys ...string) (float64, error) { if err != nil { return 0, err - } else { - - n, err := child.Float64() - - if err != nil { - return 0, err - } else { - return n, nil - } } + n, err := child.Float64() + + if err != nil { + return 0, err + } + return n, nil } // Gets the value at key path and attempts to typecast the value into a float64. @@ -275,16 +263,13 @@ func (v *Object) GetInt64(keys ...string) (int64, error) { if err != nil { return 0, err - } else { - - n, err := child.Int64() - - if err != nil { - return 0, err - } else { - return n, nil - } } + n, err := child.Int64() + + if err != nil { + return 0, err + } + return n, nil } // Gets the value at key path and attempts to typecast the value into a float64. @@ -296,9 +281,8 @@ func (v *Object) GetInterface(keys ...string) (interface{}, error) { if err != nil { return nil, err - } else { - return child.Interface(), nil } + return child.Interface(), nil } // Gets the value at key path and attempts to typecast the value into a bool. @@ -311,7 +295,6 @@ func (v *Object) GetBoolean(keys ...string) (bool, error) { if err != nil { return false, err } - return child.Boolean() } @@ -328,11 +311,8 @@ func (v *Object) GetValueArray(keys ...string) ([]*Value, error) { if err != nil { return nil, err - } else { - - return child.Array() - } + return child.Array() } // Gets the value at key path and attempts to typecast the value into an array of objects. @@ -347,30 +327,24 @@ func (v *Object) GetObjectArray(keys ...string) ([]*Object, error) { if err != nil { return nil, err - } else { + } + array, err := child.Array() - array, err := child.Array() + if err != nil { + return nil, err + } + typedArray := make([]*Object, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem. + Object() if err != nil { return nil, err - } else { - - typedArray := make([]*Object, len(array)) - - for index, arrayItem := range array { - typedArrayItem, err := arrayItem. - Object() - - if err != nil { - return nil, err - } else { - typedArray[index] = typedArrayItem - } - - } - return typedArray, nil } + typedArray[index] = typedArrayItem } + return typedArray, nil } // Gets the value at key path and attempts to typecast the value into an array of string. @@ -387,29 +361,23 @@ func (v *Object) GetStringArray(keys ...string) ([]string, error) { if err != nil { return nil, err - } else { + } + array, err := child.Array() - array, err := child.Array() + if err != nil { + return nil, err + } + typedArray := make([]string, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.String() if err != nil { return nil, err - } else { - - typedArray := make([]string, len(array)) - - for index, arrayItem := range array { - typedArrayItem, err := arrayItem.String() - - if err != nil { - return nil, err - } else { - typedArray[index] = typedArrayItem - } - - } - return typedArray, nil } + typedArray[index] = typedArrayItem } + return typedArray, nil } // Gets the value at key path and attempts to typecast the value into an array of numbers. @@ -424,29 +392,23 @@ func (v *Object) GetNumberArray(keys ...string) ([]json.Number, error) { if err != nil { return nil, err - } else { + } + array, err := child.Array() - array, err := child.Array() + if err != nil { + return nil, err + } + typedArray := make([]json.Number, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Number() if err != nil { return nil, err - } else { - - typedArray := make([]json.Number, len(array)) - - for index, arrayItem := range array { - typedArrayItem, err := arrayItem.Number() - - if err != nil { - return nil, err - } else { - typedArray[index] = typedArrayItem - } - - } - return typedArray, nil } + typedArray[index] = typedArrayItem } + return typedArray, nil } // Gets the value at key path and attempts to typecast the value into an array of floats. @@ -456,29 +418,23 @@ func (v *Object) GetFloat64Array(keys ...string) ([]float64, error) { if err != nil { return nil, err - } else { + } + array, err := child.Array() - array, err := child.Array() + if err != nil { + return nil, err + } + typedArray := make([]float64, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Float64() if err != nil { return nil, err - } else { - - typedArray := make([]float64, len(array)) - - for index, arrayItem := range array { - typedArrayItem, err := arrayItem.Float64() - - if err != nil { - return nil, err - } else { - typedArray[index] = typedArrayItem - } - - } - return typedArray, nil } + typedArray[index] = typedArrayItem } + return typedArray, nil } // Gets the value at key path and attempts to typecast the value into an array of ints. @@ -488,29 +444,23 @@ func (v *Object) GetInt64Array(keys ...string) ([]int64, error) { if err != nil { return nil, err - } else { + } + array, err := child.Array() - array, err := child.Array() + if err != nil { + return nil, err + } + typedArray := make([]int64, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Int64() if err != nil { return nil, err - } else { - - typedArray := make([]int64, len(array)) - - for index, arrayItem := range array { - typedArrayItem, err := arrayItem.Int64() - - if err != nil { - return nil, err - } else { - typedArray[index] = typedArrayItem - } - - } - return typedArray, nil } + typedArray[index] = typedArrayItem } + return typedArray, nil } // Gets the value at key path and attempts to typecast the value into an array of bools. @@ -520,29 +470,23 @@ func (v *Object) GetBooleanArray(keys ...string) ([]bool, error) { if err != nil { return nil, err - } else { + } + array, err := child.Array() - array, err := child.Array() + if err != nil { + return nil, err + } + typedArray := make([]bool, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Boolean() if err != nil { return nil, err - } else { - - typedArray := make([]bool, len(array)) - - for index, arrayItem := range array { - typedArrayItem, err := arrayItem.Boolean() - - if err != nil { - return nil, err - } else { - typedArray[index] = typedArrayItem - } - - } - return typedArray, nil } + typedArray[index] = typedArrayItem } + return typedArray, nil } // Gets the value at key path and attempts to typecast the value into an array of nulls. @@ -552,29 +496,23 @@ func (v *Object) GetNullArray(keys ...string) (int64, error) { if err != nil { return 0, err - } else { + } + array, err := child.Array() - array, err := child.Array() + if err != nil { + return 0, err + } + var length int64 = 0 + + for _, arrayItem := range array { + err := arrayItem.Null() if err != nil { return 0, err - } else { - - var length int64 = 0 - - for _, arrayItem := range array { - err := arrayItem.Null() - - if err != nil { - return 0, err - } else { - length++ - } - - } - return length, nil } + length++ } + return length, nil } // Returns an error if the value is not actually null @@ -590,9 +528,7 @@ func (v *Value) Null() error { if valid { return nil } - return ErrNotNull - } // Attempts to typecast the current value into an array. @@ -612,17 +548,13 @@ func (v *Value) Array() ([]*Value, error) { var slice []*Value if valid { - for _, element := range v.data.([]interface{}) { child := Value{element, true} slice = append(slice, &child) } - return slice, nil } - return slice, ErrNotArray - } // Attempts to typecast the current value into a number. diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index de530c0cf63..49b92648561 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -50,12 +50,12 @@ func (a *ldapAuther) Dial() error { if a.server.RootCACert != "" { certPool = x509.NewCertPool() for _, caCertFile := range strings.Split(a.server.RootCACert, " ") { - if pem, err := ioutil.ReadFile(caCertFile); err != nil { + pem, err := ioutil.ReadFile(caCertFile) + if err != nil { return err - } else { - if !certPool.AppendCertsFromPEM(pem) { - return errors.New("Failed to append CA certificate " + caCertFile) - } + } + if !certPool.AppendCertsFromPEM(pem) { + return errors.New("Failed to append CA certificate " + caCertFile) } } } diff --git a/pkg/login/ldap_test.go b/pkg/login/ldap_test.go index 6085fffb638..b8ef261c815 100644 --- a/pkg/login/ldap_test.go +++ b/pkg/login/ldap_test.go @@ -369,10 +369,9 @@ func (sc *scenarioContext) userQueryReturns(user *m.User) { bus.AddHandler("test", func(query *m.GetUserByAuthInfoQuery) error { if user == nil { return m.ErrUserNotFound - } else { - query.Result = user - return nil } + query.Result = user + return nil }) bus.AddHandler("test", func(query *m.SetAuthInfoCommand) error { return nil diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 072cb793d3c..b827751b1a5 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -210,9 +210,8 @@ func TestMiddlewareContext(t *testing.T) { if query.UserId > 0 { query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} return nil - } else { - return m.ErrUserNotFound } + return m.ErrUserNotFound }) bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error { diff --git a/pkg/plugins/dashboard_importer.go b/pkg/plugins/dashboard_importer.go index fb4d63a1fe4..1364fded987 100644 --- a/pkg/plugins/dashboard_importer.go +++ b/pkg/plugins/dashboard_importer.go @@ -148,11 +148,11 @@ func (this *DashTemplateEvaluator) evalValue(source *simplejson.Json) interface{ switch v := sourceValue.(type) { case string: interpolated := this.varRegex.ReplaceAllStringFunc(v, func(match string) string { - if replacement, exists := this.variables[match]; exists { + replacement, exists := this.variables[match] + if exists { return replacement - } else { - return match } + return match }) return interpolated case bool: diff --git a/pkg/plugins/dashboards_updater.go b/pkg/plugins/dashboards_updater.go index 04d3dc035cc..ebe11ed32d4 100644 --- a/pkg/plugins/dashboards_updater.go +++ b/pkg/plugins/dashboards_updater.go @@ -34,23 +34,24 @@ func (pm *PluginManager) updateAppDashboards() { } func autoUpdateAppDashboard(pluginDashInfo *PluginDashboardInfoDTO, orgId int64) error { - if dash, err := loadPluginDashboard(pluginDashInfo.PluginId, pluginDashInfo.Path); err != nil { + dash, err := loadPluginDashboard(pluginDashInfo.PluginId, pluginDashInfo.Path) + if err != nil { return err - } else { - plog.Info("Auto updating App dashboard", "dashboard", dash.Title, "newRev", pluginDashInfo.Revision, "oldRev", pluginDashInfo.ImportedRevision) - updateCmd := ImportDashboardCommand{ - OrgId: orgId, - PluginId: pluginDashInfo.PluginId, - Overwrite: true, - Dashboard: dash.Data, - User: &m.SignedInUser{UserId: 0, OrgRole: m.ROLE_ADMIN}, - Path: pluginDashInfo.Path, - } - - if err := bus.Dispatch(&updateCmd); err != nil { - return err - } } + plog.Info("Auto updating App dashboard", "dashboard", dash.Title, "newRev", pluginDashInfo.Revision, "oldRev", pluginDashInfo.ImportedRevision) + updateCmd := ImportDashboardCommand{ + OrgId: orgId, + PluginId: pluginDashInfo.PluginId, + Overwrite: true, + Dashboard: dash.Data, + User: &m.SignedInUser{UserId: 0, OrgRole: m.ROLE_ADMIN}, + Path: pluginDashInfo.Path, + } + + if err := bus.Dispatch(&updateCmd); err != nil { + return err + } + return nil } @@ -118,15 +119,14 @@ func handlePluginStateChanged(event *m.PluginStateChangedEvent) error { if err := bus.Dispatch(&query); err != nil { return err - } else { - for _, dash := range query.Result { - deleteCmd := m.DeleteDashboardCommand{OrgId: dash.OrgId, Id: dash.Id} + } + for _, dash := range query.Result { + deleteCmd := m.DeleteDashboardCommand{OrgId: dash.OrgId, Id: dash.Id} - plog.Info("Deleting plugin dashboard", "pluginId", event.PluginId, "dashboard", dash.Slug) + plog.Info("Deleting plugin dashboard", "pluginId", event.PluginId, "dashboard", dash.Slug) - if err := bus.Dispatch(&deleteCmd); err != nil { - return err - } + if err := bus.Dispatch(&deleteCmd); err != nil { + return err } } } diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 8ccdb9cf18b..aa4131ae06d 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -205,11 +205,11 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { } var loader PluginLoader - if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists { + pluginGoType, exists := PluginTypes[pluginCommon.Type] + if !exists { return errors.New("Unknown plugin type " + pluginCommon.Type) - } else { - loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) } + loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) reader.Seek(0, 0) return loader.Load(jsonParser, currentDir) @@ -230,9 +230,9 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { return make([]byte, 0), nil } - if data, err := ioutil.ReadFile(path); err != nil { + data, err := ioutil.ReadFile(path) + if err != nil { return nil, err - } else { - return data, nil } + return data, nil } diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 91d0e179a14..d0441d379b7 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -106,11 +106,11 @@ func (c *EvalContext) GetRuleUrl() (string, error) { return setting.AppUrl, nil } - if ref, err := c.GetDashboardUID(); err != nil { + ref, err := c.GetDashboardUID() + if err != nil { return "", err - } else { - return fmt.Sprintf(urlFormat, m.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil } + return fmt.Sprintf(urlFormat, m.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil } func (c *EvalContext) GetNewState() m.AlertStateType { diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index a30ca18b41d..1d5affbd3ec 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -87,17 +87,17 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) { IsAlertContext: true, } - if ref, err := context.GetDashboardUID(); err != nil { + ref, err := context.GetDashboardUID() + if err != nil { return err - } else { - renderOpts.Path = fmt.Sprintf("d-solo/%s/%s?panelId=%d", ref.Uid, ref.Slug, context.Rule.PanelId) } + renderOpts.Path = fmt.Sprintf("d-solo/%s/%s?panelId=%d", ref.Uid, ref.Slug, context.Rule.PanelId) - if imagePath, err := renderer.RenderToPng(renderOpts); err != nil { + imagePath, err := renderer.RenderToPng(renderOpts) + if err != nil { return err - } else { - context.ImageOnDiskPath = imagePath } + context.ImageOnDiskPath = imagePath context.ImagePublicUrl, err = uploader.Upload(context.Ctx, context.ImageOnDiskPath) if err != nil { @@ -117,12 +117,12 @@ func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds [] var result []Notifier for _, notification := range query.Result { - if not, err := n.createNotifierFor(notification); err != nil { + not, err := n.createNotifierFor(notification) + if err != nil { return nil, err - } else { - if not.ShouldNotify(context) { - result = append(result, not) - } + } + if not.ShouldNotify(context) { + result = append(result, not) } } diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index 1b259298eae..ca24c996914 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -91,9 +91,8 @@ func (this *TelegramNotifier) buildMessage(evalContext *alerting.EvalContext, se cmd, err := this.buildMessageInlineImage(evalContext) if err == nil { return cmd - } else { - this.log.Error("Could not generate Telegram message with inline image.", "err", err) } + this.log.Error("Could not generate Telegram message with inline image.", "err", err) } return this.buildMessageLinkedImage(evalContext) diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index 0326b25de32..018d138dbe4 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -103,25 +103,25 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { for _, v := range ruleDef.Settings.Get("notifications").MustArray() { jsonModel := simplejson.NewFromAny(v) - if id, err := jsonModel.Get("id").Int64(); err != nil { + id, err := jsonModel.Get("id").Int64() + if err != nil { return nil, ValidationError{Reason: "Invalid notification schema", DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId} - } else { - model.Notifications = append(model.Notifications, id) } + model.Notifications = append(model.Notifications, id) } for index, condition := range ruleDef.Settings.Get("conditions").MustArray() { conditionModel := simplejson.NewFromAny(condition) conditionType := conditionModel.Get("type").MustString() - if factory, exist := conditionFactories[conditionType]; !exist { + factory, exist := conditionFactories[conditionType] + if !exist { return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId} - } else { - if queryCondition, err := factory(conditionModel, index); err != nil { - return nil, ValidationError{Err: err, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId} - } else { - model.Conditions = append(model.Conditions, queryCondition) - } } + queryCondition, err := factory(conditionModel, index) + if err != nil { + return nil, ValidationError{Err: err, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId} + } + model.Conditions = append(model.Conditions, queryCondition) } if len(model.Conditions) == 0 { diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 1066be0ef74..1710679cea1 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -29,13 +29,13 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error { } if item.Tags != nil { - if tags, err := r.ensureTagsExist(sess, tags); err != nil { + tags, err := r.ensureTagsExist(sess, tags) + if err != nil { return err - } else { - for _, tag := range tags { - if _, err := sess.Exec("INSERT INTO annotation_tag (annotation_id, tag_id) VALUES(?,?)", item.Id, tag.Id); err != nil { - return err - } + } + for _, tag := range tags { + if _, err := sess.Exec("INSERT INTO annotation_tag (annotation_id, tag_id) VALUES(?,?)", item.Id, tag.Id); err != nil { + return err } } } @@ -94,17 +94,17 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { } if item.Tags != nil { - if tags, err := r.ensureTagsExist(sess, models.ParseTagPairs(item.Tags)); err != nil { + tags, err := r.ensureTagsExist(sess, models.ParseTagPairs(item.Tags)) + if err != nil { return err - } else { - if _, err := sess.Exec("DELETE FROM annotation_tag WHERE annotation_id = ?", existing.Id); err != nil { + } + if _, err := sess.Exec("DELETE FROM annotation_tag WHERE annotation_id = ?", existing.Id); err != nil { + return err + } + for _, tag := range tags { + if _, err := sess.Exec("INSERT INTO annotation_tag (annotation_id, tag_id) VALUES(?,?)", existing.Id, tag.Id); err != nil { return err } - for _, tag := range tags { - if _, err := sess.Exec("INSERT INTO annotation_tag (annotation_id, tag_id) VALUES(?,?)", existing.Id, tag.Id); err != nil { - return err - } - } } } diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index 9124a686236..6d7c7a93e47 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -104,9 +104,8 @@ func TestDashboardDataAccess(t *testing.T) { timesCalled += 1 if timesCalled <= 2 { return savedDash.Uid - } else { - return util.GenerateShortUid() } + return util.GenerateShortUid() } cmd := m.SaveDashboardCommand{ OrgId: 1, diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index 0fde3f27c01..cd00cb16712 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -97,17 +97,15 @@ func (mg *Migrator) Start() error { mg.Logger.Debug("Executing", "sql", sql) err := mg.inTransaction(func(sess *xorm.Session) error { - - if err := mg.exec(m, sess); err != nil { + err := mg.exec(m, sess) + if err != nil { mg.Logger.Error("Exec failed", "error", err, "sql", sql) record.Error = err.Error() sess.Insert(&record) return err - } else { - record.Success = true - sess.Insert(&record) } - + record.Success = true + sess.Insert(&record) return nil }) diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index e2da562c14e..e167aa33122 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -45,9 +45,8 @@ func (b *Postgres) Default(col *Column) string { if col.Type == DB_Bool { if col.Default == "0" { return "FALSE" - } else { - return "TRUE" } + return "TRUE" } return col.Default } diff --git a/pkg/services/sqlstore/plugin_setting.go b/pkg/services/sqlstore/plugin_setting.go index f694fbbd5f0..676d26fad56 100644 --- a/pkg/services/sqlstore/plugin_setting.go +++ b/pkg/services/sqlstore/plugin_setting.go @@ -75,34 +75,33 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error { _, err = sess.Insert(&pluginSetting) return err - } else { - for key, data := range cmd.SecureJsonData { - encryptedData, err := util.Encrypt([]byte(data), setting.SecretKey) - if err != nil { - return err - } - - pluginSetting.SecureJsonData[key] = encryptedData - } - - // add state change event on commit success - if pluginSetting.Enabled != cmd.Enabled { - sess.events = append(sess.events, &m.PluginStateChangedEvent{ - PluginId: cmd.PluginId, - OrgId: cmd.OrgId, - Enabled: cmd.Enabled, - }) - } - - pluginSetting.Updated = time.Now() - pluginSetting.Enabled = cmd.Enabled - pluginSetting.JsonData = cmd.JsonData - pluginSetting.Pinned = cmd.Pinned - pluginSetting.PluginVersion = cmd.PluginVersion - - _, err = sess.Id(pluginSetting.Id).Update(&pluginSetting) - return err } + for key, data := range cmd.SecureJsonData { + encryptedData, err := util.Encrypt([]byte(data), setting.SecretKey) + if err != nil { + return err + } + + pluginSetting.SecureJsonData[key] = encryptedData + } + + // add state change event on commit success + if pluginSetting.Enabled != cmd.Enabled { + sess.events = append(sess.events, &m.PluginStateChangedEvent{ + PluginId: cmd.PluginId, + OrgId: cmd.OrgId, + Enabled: cmd.Enabled, + }) + } + + pluginSetting.Updated = time.Now() + pluginSetting.Enabled = cmd.Enabled + pluginSetting.JsonData = cmd.JsonData + pluginSetting.Pinned = cmd.Pinned + pluginSetting.PluginVersion = cmd.PluginVersion + + _, err = sess.Id(pluginSetting.Id).Update(&pluginSetting) + return err }) } diff --git a/pkg/services/sqlstore/preferences.go b/pkg/services/sqlstore/preferences.go index a070fa621b5..885837764fc 100644 --- a/pkg/services/sqlstore/preferences.go +++ b/pkg/services/sqlstore/preferences.go @@ -88,14 +88,13 @@ func SavePreferences(cmd *m.SavePreferencesCommand) error { } _, err = sess.Insert(&prefs) return err - } else { - prefs.HomeDashboardId = cmd.HomeDashboardId - prefs.Timezone = cmd.Timezone - prefs.Theme = cmd.Theme - prefs.Updated = time.Now() - prefs.Version += 1 - _, err := sess.Id(prefs.Id).AllCols().Update(&prefs) - return err } + prefs.HomeDashboardId = cmd.HomeDashboardId + prefs.Timezone = cmd.Timezone + prefs.Theme = cmd.Theme + prefs.Updated = time.Now() + prefs.Version += 1 + _, err = sess.Id(prefs.Id).AllCols().Update(&prefs) + return err }) } diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 5e2efbd7fde..f19019d28a4 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -47,10 +47,9 @@ func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *DBSession) (int64, error } if has { return org.Id, nil - } else { - org.Name = "Main Org." - org.Id = 1 } + org.Name = "Main Org." + org.Id = 1 } else { org.Name = cmd.OrgName if len(org.Name) == 0 { diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index d73516ca88f..a7d33645b9b 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -222,9 +222,8 @@ func parseMultiSelectValue(input string) []string { trimValues[i] = strings.TrimSpace(v) } return trimValues - } else { - return []string{trimmedInput} } + return []string{trimmedInput} } // Whenever this list is updated, frontend list should also be updated. diff --git a/pkg/tsdb/time_range.go b/pkg/tsdb/time_range.go index 777fd15907e..18e389e5993 100644 --- a/pkg/tsdb/time_range.go +++ b/pkg/tsdb/time_range.go @@ -54,19 +54,19 @@ func (tr *TimeRange) GetToAsTimeUTC() time.Time { } func (tr *TimeRange) MustGetFrom() time.Time { - if res, err := tr.ParseFrom(); err != nil { + res, err := tr.ParseFrom() + if err != nil { return time.Unix(0, 0) - } else { - return res } + return res } func (tr *TimeRange) MustGetTo() time.Time { - if res, err := tr.ParseTo(); err != nil { + res, err := tr.ParseTo() + if err != nil { return time.Unix(0, 0) - } else { - return res } + return res } func tryParseUnixMsEpoch(val string) (time.Time, bool) { diff --git a/pkg/util/filepath.go b/pkg/util/filepath.go index 3ad8cac3147..d304236fcb1 100644 --- a/pkg/util/filepath.go +++ b/pkg/util/filepath.go @@ -65,9 +65,8 @@ func walk(path string, info os.FileInfo, resolvedPath string, symlinkPathsFollow if _, ok := symlinkPathsFollowed[path2]; ok { errMsg := "Potential SymLink Infinite Loop. Path: %v, Link To: %v" return fmt.Errorf(errMsg, resolvedPath, path2) - } else { - symlinkPathsFollowed[path2] = true } + symlinkPathsFollowed[path2] = true } info2, err := os.Lstat(path2) if err != nil { From 893a91af3aab546e911f4c5aa57bb81df0df5405 Mon Sep 17 00:00:00 2001 From: Karsten Weiss Date: Sat, 28 Apr 2018 10:45:45 +0200 Subject: [PATCH 294/319] Use opportunities to unindent code (unindent) This commit fixes the following unindent findings: pkg/api/common.go:102:2: "if x { if y" should be "if x && y" pkg/components/dynmap/dynmap.go:642:2: invert condition and early return pkg/components/dynmap/dynmap.go:681:2: invert condition and early return pkg/components/simplejson/simplejson.go:171:2: "if x { if y" should be "if x && y" pkg/middleware/dashboard_redirect.go:42:3: invert condition and early return pkg/tsdb/mssql/mssql.go:301:3: invert condition and early break pkg/tsdb/mysql/mysql.go:312:3: invert condition and early break pkg/tsdb/postgres/postgres.go:292:3: invert condition and early break pkg/tsdb/sql_engine.go:144:2: invert condition and early return --- pkg/api/common.go | 6 +- pkg/components/dynmap/dynmap.go | 56 +++++++--------- pkg/components/simplejson/simplejson.go | 6 +- pkg/middleware/dashboard_redirect.go | 31 +++++---- pkg/tsdb/mssql/mssql.go | 23 ++++--- pkg/tsdb/mysql/mysql.go | 23 ++++--- pkg/tsdb/postgres/postgres.go | 23 ++++--- pkg/tsdb/sql_engine.go | 89 +++++++++++++------------ 8 files changed, 125 insertions(+), 132 deletions(-) diff --git a/pkg/api/common.go b/pkg/api/common.go index 97f41ff7c72..cd64c57dc92 100644 --- a/pkg/api/common.go +++ b/pkg/api/common.go @@ -99,10 +99,8 @@ func Error(status int, message string, err error) *NormalResponse { data["message"] = message } - if err != nil { - if setting.Env != setting.PROD { - data["error"] = err.Error() - } + if err != nil && setting.Env != setting.PROD { + data["error"] = err.Error() } resp := JSON(status, data) diff --git a/pkg/components/dynmap/dynmap.go b/pkg/components/dynmap/dynmap.go index 96effb24332..6d3546f3bc5 100644 --- a/pkg/components/dynmap/dynmap.go +++ b/pkg/components/dynmap/dynmap.go @@ -639,26 +639,24 @@ func (v *Value) Object() (*Object, error) { valid = true } + if !valid { + return nil, ErrNotObject + } + obj := new(Object) + obj.valid = valid + + m := make(map[string]*Value) + if valid { - obj := new(Object) - obj.valid = valid - - m := make(map[string]*Value) - - if valid { - for key, element := range v.data.(map[string]interface{}) { - m[key] = &Value{element, true} - - } + for key, element := range v.data.(map[string]interface{}) { + m[key] = &Value{element, true} } - - obj.data = v.data - obj.m = m - - return obj, nil } - return nil, ErrNotObject + obj.data = v.data + obj.m = m + + return obj, nil } // Attempts to typecast the current value into an object arrau. @@ -678,23 +676,19 @@ func (v *Value) ObjectArray() ([]*Object, error) { // Unsure if this is a good way to use slices, it's probably not var slice []*Object - if valid { - - for _, element := range v.data.([]interface{}) { - childValue := Value{element, true} - childObject, err := childValue.Object() - - if err != nil { - return nil, ErrNotObjectArray - } - slice = append(slice, childObject) - } - - return slice, nil + if !valid { + return nil, ErrNotObjectArray } + for _, element := range v.data.([]interface{}) { + childValue := Value{element, true} + childObject, err := childValue.Object() - return nil, ErrNotObjectArray - + if err != nil { + return nil, ErrNotObjectArray + } + slice = append(slice, childObject) + } + return slice, nil } // Attempts to typecast the current value into a string. diff --git a/pkg/components/simplejson/simplejson.go b/pkg/components/simplejson/simplejson.go index 85e2f955943..15293b0cd93 100644 --- a/pkg/components/simplejson/simplejson.go +++ b/pkg/components/simplejson/simplejson.go @@ -168,10 +168,8 @@ func (j *Json) GetPath(branch ...string) *Json { // js.Get("top_level").Get("array").GetIndex(1).Get("key").Int() func (j *Json) GetIndex(index int) *Json { a, err := j.Array() - if err == nil { - if len(a) > index { - return &Json{a[index]} - } + if err == nil && len(a) > index { + return &Json{a[index]} } return &Json{nil} } diff --git a/pkg/middleware/dashboard_redirect.go b/pkg/middleware/dashboard_redirect.go index 2edf04d543e..1111929c2f6 100644 --- a/pkg/middleware/dashboard_redirect.go +++ b/pkg/middleware/dashboard_redirect.go @@ -24,12 +24,12 @@ func RedirectFromLegacyDashboardURL() macaron.Handler { return func(c *m.ReqContext) { slug := c.Params("slug") - if slug != "" { - if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { - url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) - c.Redirect(url, 301) - return - } + if slug == "" { + return + } + if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { + url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) + c.Redirect(url, 301) } } } @@ -39,17 +39,16 @@ func RedirectFromLegacyDashboardSoloURL() macaron.Handler { slug := c.Params("slug") renderRequest := c.QueryBool("render") - if slug != "" { - if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { - if renderRequest && strings.Contains(url, setting.AppSubUrl) { - url = strings.Replace(url, setting.AppSubUrl, "", 1) - } - - url = strings.Replace(url, "/d/", "/d-solo/", 1) - url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) - c.Redirect(url, 301) - return + if slug == "" { + return + } + if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { + if renderRequest && strings.Contains(url, setting.AppSubUrl) { + url = strings.Replace(url, setting.AppSubUrl, "", 1) } + url = strings.Replace(url, "/d/", "/d-solo/", 1) + url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) + c.Redirect(url, 301) } } } diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index eb71259b46b..221670f1bdb 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -298,18 +298,19 @@ func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. key := elem.Value.(string) result.Series = append(result.Series, pointsBySeries[key]) - if fillMissing { - series := pointsBySeries[key] - // fill in values from last fetched value till interval end - intervalStart := series.Points[len(series.Points)-1][1].Float64 - intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) + if !fillMissing { + break + } + series := pointsBySeries[key] + // fill in values from last fetched value till interval end + intervalStart := series.Points[len(series.Points)-1][1].Float64 + intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) - // align interval start - intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval - for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { - series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) - rowCount++ - } + // align interval start + intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval + for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { + series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) + rowCount++ } } diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 7eceaffdb09..57986eb7c04 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -309,18 +309,19 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. key := elem.Value.(string) result.Series = append(result.Series, pointsBySeries[key]) - if fillMissing { - series := pointsBySeries[key] - // fill in values from last fetched value till interval end - intervalStart := series.Points[len(series.Points)-1][1].Float64 - intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) + if !fillMissing { + break + } + series := pointsBySeries[key] + // fill in values from last fetched value till interval end + intervalStart := series.Points[len(series.Points)-1][1].Float64 + intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) - // align interval start - intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval - for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { - series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) - rowCount++ - } + // align interval start + intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval + for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { + series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) + rowCount++ } } diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go index fdf09216e51..f66c09b5724 100644 --- a/pkg/tsdb/postgres/postgres.go +++ b/pkg/tsdb/postgres/postgres.go @@ -289,18 +289,19 @@ func (e PostgresQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *co key := elem.Value.(string) result.Series = append(result.Series, pointsBySeries[key]) - if fillMissing { - series := pointsBySeries[key] - // fill in values from last fetched value till interval end - intervalStart := series.Points[len(series.Points)-1][1].Float64 - intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) + if !fillMissing { + break + } + series := pointsBySeries[key] + // fill in values from last fetched value till interval end + intervalStart := series.Points[len(series.Points)-1][1].Float64 + intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) - // align interval start - intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval - for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { - series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) - rowCount++ - } + // align interval start + intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval + for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { + series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) + rowCount++ } } diff --git a/pkg/tsdb/sql_engine.go b/pkg/tsdb/sql_engine.go index 274e5b05dc1..ecf46ac689d 100644 --- a/pkg/tsdb/sql_engine.go +++ b/pkg/tsdb/sql_engine.go @@ -141,50 +141,51 @@ func (e *DefaultSqlEngine) Query( // ConvertSqlTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds // to make native datetime types and epoch dates work in annotation and table queries. func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) { - if timeIndex >= 0 { - switch value := values[timeIndex].(type) { - case time.Time: - values[timeIndex] = EpochPrecisionToMs(float64(value.UnixNano())) - case *time.Time: - if value != nil { - values[timeIndex] = EpochPrecisionToMs(float64((*value).UnixNano())) - } - case int64: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *int64: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case uint64: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *uint64: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case int32: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *int32: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case uint32: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *uint32: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case float64: - values[timeIndex] = EpochPrecisionToMs(value) - case *float64: - if value != nil { - values[timeIndex] = EpochPrecisionToMs(*value) - } - case float32: - values[timeIndex] = EpochPrecisionToMs(float64(value)) - case *float32: - if value != nil { - values[timeIndex] = EpochPrecisionToMs(float64(*value)) - } + if timeIndex < 0 { + return + } + switch value := values[timeIndex].(type) { + case time.Time: + values[timeIndex] = EpochPrecisionToMs(float64(value.UnixNano())) + case *time.Time: + if value != nil { + values[timeIndex] = EpochPrecisionToMs(float64((*value).UnixNano())) + } + case int64: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *int64: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case uint64: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *uint64: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case int32: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *int32: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case uint32: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *uint32: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case float64: + values[timeIndex] = EpochPrecisionToMs(value) + case *float64: + if value != nil { + values[timeIndex] = EpochPrecisionToMs(*value) + } + case float32: + values[timeIndex] = EpochPrecisionToMs(float64(value)) + case *float32: + if value != nil { + values[timeIndex] = EpochPrecisionToMs(float64(*value)) } } } From 4f7791b9fa15c2e736b244c6c12b49e472158872 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 30 Apr 2018 11:50:50 +0200 Subject: [PATCH 295/319] fix dropdown typeahead issue New explore feature overriding css for dropdown typeahead component. --- public/sass/pages/_explore.scss | 94 +++++++++++++++++---------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 74a19c1d2c2..855d11cb859 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -42,55 +42,57 @@ transition: all 0.3s; } -.typeahead { - position: absolute; - z-index: auto; - top: -10000px; - left: -10000px; - opacity: 0; - border-radius: 4px; - transition: opacity 0.75s; - border: 1px solid #e4e4e4; - max-height: calc(66vh); - overflow-y: scroll; - max-width: calc(66%); - overflow-x: hidden; - outline: none; - list-style: none; - background: #fff; - color: rgba(0, 0, 0, 0.65); - transition: opacity 0.4s ease-out; -} +.explore { + .typeahead { + position: absolute; + z-index: auto; + top: -10000px; + left: -10000px; + opacity: 0; + border-radius: 4px; + transition: opacity 0.75s; + border: 1px solid #e4e4e4; + max-height: calc(66vh); + overflow-y: scroll; + max-width: calc(66%); + overflow-x: hidden; + outline: none; + list-style: none; + background: #fff; + color: rgba(0, 0, 0, 0.65); + transition: opacity 0.4s ease-out; + } -.typeahead-group__title { - color: rgba(0, 0, 0, 0.43); - font-size: 12px; - line-height: 1.5; - padding: 8px 16px; -} + .typeahead-group__title { + color: rgba(0, 0, 0, 0.43); + font-size: 12px; + line-height: 1.5; + padding: 8px 16px; + } -.typeahead-item { - line-height: 200%; - height: auto; - font-family: Consolas, Menlo, Courier, monospace; - padding: 0 16px 0 28px; - font-size: 12px; - text-overflow: ellipsis; - overflow: hidden; - margin-left: -1px; - left: 1px; - position: relative; - z-index: 1; - display: block; - white-space: nowrap; - cursor: pointer; - transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), - background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); -} + .typeahead-item { + line-height: 200%; + height: auto; + font-family: Consolas, Menlo, Courier, monospace; + padding: 0 16px 0 28px; + font-size: 12px; + text-overflow: ellipsis; + overflow: hidden; + margin-left: -1px; + left: 1px; + position: relative; + z-index: 1; + display: block; + white-space: nowrap; + cursor: pointer; + transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), + background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); + } -.typeahead-item__selected { - background-color: #ecf6fd; - color: #108ee9; + .typeahead-item__selected { + background-color: #ecf6fd; + color: #108ee9; + } } /* SYNTAX */ From 3d9b7a5892f11920c3be744287f0a4b46cfd5464 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 30 Apr 2018 14:41:52 +0200 Subject: [PATCH 296/319] increase length of auth_id column in user_auth table --- pkg/services/sqlstore/migrations/user_auth_mig.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/services/sqlstore/migrations/user_auth_mig.go b/pkg/services/sqlstore/migrations/user_auth_mig.go index 4d8a18ce33e..be4d112f6f6 100644 --- a/pkg/services/sqlstore/migrations/user_auth_mig.go +++ b/pkg/services/sqlstore/migrations/user_auth_mig.go @@ -21,4 +21,9 @@ func addUserAuthMigrations(mg *Migrator) { mg.AddMigration("create user auth table", NewAddTableMigration(userAuthV1)) // add indices addTableIndicesMigrations(mg, "v1", userAuthV1) + + mg.AddMigration("alter user_auth.auth_id to length 255", new(RawSqlMigration). + Sqlite("SELECT 0 WHERE 0;"). + Postgres("ALTER TABLE user_auth ALTER COLUMN auth_id TYPE VARCHAR(255);"). + Mysql("ALTER TABLE user_auth MODIFY auth_id VARCHAR(255);")) } From 770acee56a68d3aa48c17fec30bc6c220e693c18 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 30 Apr 2018 15:34:31 +0200 Subject: [PATCH 297/319] new property for current user indicating if edit permissions in folders --- pkg/api/dtos/models.go | 31 +++++------ pkg/api/index.go | 36 +++++++------ pkg/models/folders.go | 9 ++++ pkg/services/sqlstore/dashboard.go | 25 +++++++++ .../sqlstore/dashboard_folder_test.go | 53 ++++++++++++++++++- 5 files changed, 123 insertions(+), 31 deletions(-) diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index 2348e217a41..aead67cd04c 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -22,21 +22,22 @@ type LoginCommand struct { } type CurrentUser struct { - IsSignedIn bool `json:"isSignedIn"` - Id int64 `json:"id"` - Login string `json:"login"` - Email string `json:"email"` - Name string `json:"name"` - LightTheme bool `json:"lightTheme"` - OrgCount int `json:"orgCount"` - OrgId int64 `json:"orgId"` - OrgName string `json:"orgName"` - OrgRole m.RoleType `json:"orgRole"` - IsGrafanaAdmin bool `json:"isGrafanaAdmin"` - GravatarUrl string `json:"gravatarUrl"` - Timezone string `json:"timezone"` - Locale string `json:"locale"` - HelpFlags1 m.HelpFlags1 `json:"helpFlags1"` + IsSignedIn bool `json:"isSignedIn"` + Id int64 `json:"id"` + Login string `json:"login"` + Email string `json:"email"` + Name string `json:"name"` + LightTheme bool `json:"lightTheme"` + OrgCount int `json:"orgCount"` + OrgId int64 `json:"orgId"` + OrgName string `json:"orgName"` + OrgRole m.RoleType `json:"orgRole"` + IsGrafanaAdmin bool `json:"isGrafanaAdmin"` + GravatarUrl string `json:"gravatarUrl"` + Timezone string `json:"timezone"` + Locale string `json:"locale"` + HelpFlags1 m.HelpFlags1 `json:"helpFlags1"` + HasEditPermissionInFolders bool `json:"hasEditPermissionInFolders"` } type MetricRequest struct { diff --git a/pkg/api/index.go b/pkg/api/index.go index ac68dba65b6..2a905b474ce 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -42,23 +42,29 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { settings["appSubUrl"] = "" } + hasEditPermissionInFoldersQuery := m.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} + if err := bus.Dispatch(&hasEditPermissionInFoldersQuery); err != nil { + return nil, err + } + var data = dtos.IndexViewData{ User: &dtos.CurrentUser{ - Id: c.UserId, - IsSignedIn: c.IsSignedIn, - Login: c.Login, - Email: c.Email, - Name: c.Name, - OrgCount: c.OrgCount, - OrgId: c.OrgId, - OrgName: c.OrgName, - OrgRole: c.OrgRole, - GravatarUrl: dtos.GetGravatarUrl(c.Email), - IsGrafanaAdmin: c.IsGrafanaAdmin, - LightTheme: prefs.Theme == "light", - Timezone: prefs.Timezone, - Locale: locale, - HelpFlags1: c.HelpFlags1, + Id: c.UserId, + IsSignedIn: c.IsSignedIn, + Login: c.Login, + Email: c.Email, + Name: c.Name, + OrgCount: c.OrgCount, + OrgId: c.OrgId, + OrgName: c.OrgName, + OrgRole: c.OrgRole, + GravatarUrl: dtos.GetGravatarUrl(c.Email), + IsGrafanaAdmin: c.IsGrafanaAdmin, + LightTheme: prefs.Theme == "light", + Timezone: prefs.Timezone, + Locale: locale, + HelpFlags1: c.HelpFlags1, + HasEditPermissionInFolders: hasEditPermissionInFoldersQuery.Result, }, Settings: settings, Theme: prefs.Theme, diff --git a/pkg/models/folders.go b/pkg/models/folders.go index 0c876edcfd7..f4dd7e5b776 100644 --- a/pkg/models/folders.go +++ b/pkg/models/folders.go @@ -89,3 +89,12 @@ type UpdateFolderCommand struct { Result *Folder } + +// +// QUERIES +// + +type HasEditPermissionInFoldersQuery struct { + SignedInUser *SignedInUser + Result bool +} diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 4238967417f..aff532bb3b5 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -24,6 +24,7 @@ func init() { bus.AddHandler("sql", GetDashboardPermissionsForUser) bus.AddHandler("sql", GetDashboardsBySlug) bus.AddHandler("sql", ValidateDashboardBeforeSave) + bus.AddHandler("sql", HasEditPermissionInFolders) } var generateNewUid func() string = util.GenerateShortUid @@ -614,3 +615,27 @@ func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err return nil }) } + +func HasEditPermissionInFolders(query *m.HasEditPermissionInFoldersQuery) error { + if query.SignedInUser.HasRole(m.ROLE_EDITOR) { + query.Result = true + return nil + } + + builder := &SqlBuilder{} + builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, dialect.BooleanStr(true)) + builder.writeDashboardPermissionFilter(query.SignedInUser, m.PERMISSION_EDIT) + + type folderCount struct { + Count int64 + } + + resp := make([]*folderCount, 0) + if err := x.Sql(builder.GetSqlString(), builder.params...).Find(&resp); err != nil { + return err + } + + query.Result = len(resp) > 0 && resp[0].Count > 0 + + return nil +} diff --git a/pkg/services/sqlstore/dashboard_folder_test.go b/pkg/services/sqlstore/dashboard_folder_test.go index 4c92c097931..cdd107c3e90 100644 --- a/pkg/services/sqlstore/dashboard_folder_test.go +++ b/pkg/services/sqlstore/dashboard_folder_test.go @@ -221,7 +221,6 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("Given two dashboard folders", func() { - folder1 := insertTestDashboard("1 test dash folder", 1, 0, true, "prod") folder2 := insertTestDashboard("2 test dash folder", 1, 0, true, "prod") insertTestDashboard("folder in another org", 2, 0, true, "prod") @@ -264,6 +263,15 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(query.Result[1].DashboardId, ShouldEqual, folder2.Id) So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_ADMIN) }) + + Convey("should have edit permission in folders", func() { + query := &m.HasEditPermissionInFoldersQuery{ + SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: m.ROLE_ADMIN}, + } + err := HasEditPermissionInFolders(query) + So(err, ShouldBeNil) + So(query.Result, ShouldBeTrue) + }) }) Convey("Editor users", func() { @@ -310,6 +318,14 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(query.Result[0].Id, ShouldEqual, folder2.Id) }) + Convey("should have edit permission in folders", func() { + query := &m.HasEditPermissionInFoldersQuery{ + SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgId: 1, OrgRole: m.ROLE_EDITOR}, + } + err := HasEditPermissionInFolders(query) + So(err, ShouldBeNil) + So(query.Result, ShouldBeTrue) + }) }) Convey("Viewer users", func() { @@ -353,6 +369,41 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 1) So(query.Result[0].Id, ShouldEqual, folder1.Id) }) + + Convey("should not have edit permission in folders", func() { + query := &m.HasEditPermissionInFoldersQuery{ + SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, + } + err := HasEditPermissionInFolders(query) + So(err, ShouldBeNil) + So(query.Result, ShouldBeFalse) + }) + + Convey("and admin permission is given for user with org role viewer in one dashboard folder", func() { + testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_ADMIN}) + + Convey("should have edit permission in folders", func() { + query := &m.HasEditPermissionInFoldersQuery{ + SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, + } + err := HasEditPermissionInFolders(query) + So(err, ShouldBeNil) + So(query.Result, ShouldBeTrue) + }) + }) + + Convey("and edit permission is given for user with org role viewer in one dashboard folder", func() { + testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_EDIT}) + + Convey("should have edit permission in folders", func() { + query := &m.HasEditPermissionInFoldersQuery{ + SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, + } + err := HasEditPermissionInFolders(query) + So(err, ShouldBeNil) + So(query.Result, ShouldBeTrue) + }) + }) }) }) }) From 5c57c7cff56eb5f10c64312b73b9d47b54df571b Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 30 Apr 2018 15:38:46 +0200 Subject: [PATCH 298/319] dashboard: show save as button if can edit and has edit permission to folders --- public/app/core/services/context_srv.ts | 3 +++ public/app/features/dashboard/settings/settings.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index 5a879895267..be8a0af7b7b 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -11,6 +11,7 @@ export class User { timezone: string; helpFlags1: number; lightTheme: boolean; + hasEditPermissionInFolders: boolean; constructor() { if (config.bootData.user) { @@ -28,6 +29,7 @@ export class ContextSrv { isEditor: any; sidemenu: any; sidemenuSmallBreakpoint = false; + hasEditPermissionInFolders: boolean; constructor() { this.sidemenu = store.getBool('grafana.sidemenu', true); @@ -44,6 +46,7 @@ export class ContextSrv { this.isSignedIn = this.user.isSignedIn; this.isGrafanaAdmin = this.user.isGrafanaAdmin; this.isEditor = this.hasRole('Editor') || this.hasRole('Admin'); + this.hasEditPermissionInFolders = this.user.hasEditPermissionInFolders; } hasRole(role) { diff --git a/public/app/features/dashboard/settings/settings.ts b/public/app/features/dashboard/settings/settings.ts index e9d5c6180be..68fd20a3b91 100755 --- a/public/app/features/dashboard/settings/settings.ts +++ b/public/app/features/dashboard/settings/settings.ts @@ -30,7 +30,7 @@ export class SettingsCtrl { }); }); - this.canSaveAs = contextSrv.isEditor; + this.canSaveAs = this.dashboard.meta.canEdit && contextSrv.hasEditPermissionInFolders; this.canSave = this.dashboard.meta.canSave; this.canDelete = this.dashboard.meta.canSave; From b16626c3b5974aaed997fd28ce7708844bf785bc Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 12 Apr 2018 16:31:47 +0300 Subject: [PATCH 299/319] graph histogram: fix invisible highest value bucket --- public/app/plugins/panel/graph/graph.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 07ce0fed49f..2de53b6dce0 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -443,7 +443,8 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { // Expand ticks for pretty view min = Math.floor(min / tickStep) * tickStep; - max = Math.ceil(max / tickStep) * tickStep; + // 1.01 is 101% - ensure we have enough space for last bar + max = Math.ceil(max * 1.01 / tickStep) * tickStep; ticks = []; for (let i = min; i <= max; i += tickStep) { From fc718b8a9a1229248a360c29c5b310b6fc5448ff Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 30 Apr 2018 16:17:37 +0200 Subject: [PATCH 300/319] table: fix for padding The table-panel-wrapper class got removed when clicking on the panel menu which resulted in extra padding for the .panel-content div. This fixes that by setting the table-specific css class lower down in the html. --- public/app/plugins/panel/table/module.ts | 4 ++-- public/sass/components/_panel_table.scss | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 27eab205f09..51caed86c25 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -218,13 +218,13 @@ class TablePanelCtrl extends MetricsPanelCtrl { } function renderPanel() { - var panelElem = elem.parents('.panel'); + var panelElem = elem.parents('.panel-content'); var rootElem = elem.find('.table-panel-scroll'); var tbodyElem = elem.find('tbody'); var footerElem = elem.find('.table-panel-footer'); elem.css({ 'font-size': panel.fontSize }); - panelElem.addClass('table-panel-wrapper'); + panelElem.addClass('table-panel-content'); appendTableRows(tbodyElem); appendPaginationControls(footerElem); diff --git a/public/sass/components/_panel_table.scss b/public/sass/components/_panel_table.scss index f120fcc8b35..8e0ecf15896 100644 --- a/public/sass/components/_panel_table.scss +++ b/public/sass/components/_panel_table.scss @@ -1,7 +1,6 @@ -.table-panel-wrapper { - .panel-content { - padding: 0; - } +.table-panel-content { + padding: 0; + .panel-title-container { padding-bottom: 4px; } From fa7d7ed5df2030a84bb8cf3eb221cf248c92c618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 30 Apr 2018 16:21:04 +0200 Subject: [PATCH 301/319] Initial Baby Step to refactoring settings from global vars to instance (#11777) * wip: start on refactoring settings * settings: progress on settings refactor * refactor: progress on settings refactoring * fix: fixed failing test * settings: moved smtp settings from global to instance --- pkg/api/admin.go | 2 +- pkg/api/http_server.go | 5 +- pkg/cmd/grafana-cli/commands/commands.go | 3 +- pkg/cmd/grafana-server/server.go | 17 +- .../imguploader/azureblobuploader_test.go | 3 +- .../imguploader/gcsuploader_test.go | 3 +- pkg/components/imguploader/imguploader.go | 11 +- .../imguploader/imguploader_test.go | 27 +-- pkg/components/imguploader/s3uploader_test.go | 3 +- pkg/metrics/settings.go | 2 +- pkg/plugins/dashboard_importer_test.go | 4 +- pkg/plugins/dashboards_test.go | 4 +- pkg/plugins/plugins.go | 2 +- pkg/plugins/plugins_test.go | 6 +- pkg/services/cleanup/cleanup.go | 51 +++--- pkg/services/notifications/mailer.go | 27 +-- pkg/services/notifications/notifications.go | 18 +- .../notifications/notifications_test.go | 9 +- .../send_email_integration_test.go | 9 +- pkg/services/sqlstore/sqlstore.go | 4 +- pkg/setting/setting.go | 163 ++++++++++-------- pkg/setting/setting_quota.go | 4 +- pkg/setting/setting_smtp.go | 30 ++-- pkg/setting/setting_test.go | 49 ++++-- pkg/social/social.go | 2 +- pkg/tracing/tracing.go | 2 +- pkg/tsdb/influxdb/response_parser_test.go | 3 +- pkg/tsdb/interval_test.go | 3 +- 28 files changed, 263 insertions(+), 203 deletions(-) diff --git a/pkg/api/admin.go b/pkg/api/admin.go index 52d271ce69b..54a86724f0c 100644 --- a/pkg/api/admin.go +++ b/pkg/api/admin.go @@ -12,7 +12,7 @@ import ( func AdminGetSettings(c *m.ReqContext) { settings := make(map[string]interface{}) - for _, section := range setting.Cfg.Sections() { + for _, section := range setting.Raw.Sections() { jsonSec := make(map[string]interface{}) settings[section.Name()] = jsonSec diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 8d1d0dc0a60..fa27eabbf24 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -35,9 +35,10 @@ type HTTPServer struct { context context.Context streamManager *live.StreamManager cache *gocache.Cache - RouteRegister RouteRegister `inject:""` + httpSrv *http.Server - httpSrv *http.Server + RouteRegister RouteRegister `inject:""` + Bus bus.Bus `inject:""` } func (hs *HTTPServer) Init() { diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go index d8f01bbdcab..43484749670 100644 --- a/pkg/cmd/grafana-cli/commands/commands.go +++ b/pkg/cmd/grafana-cli/commands/commands.go @@ -15,7 +15,8 @@ func runDbCommand(command func(commandLine CommandLine) error) func(context *cli return func(context *cli.Context) { cmd := &contextCommandLine{context} - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ Config: cmd.String("config"), HomePath: cmd.String("homepath"), Args: flag.Args(), diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 86cbe51dd22..911cf092b47 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -49,6 +49,7 @@ func NewGrafanaServer() *GrafanaServerImpl { shutdownFn: shutdownFn, childRoutines: childRoutines, log: log.New("server"), + cfg: setting.NewCfg(), } } @@ -57,28 +58,29 @@ type GrafanaServerImpl struct { shutdownFn context.CancelFunc childRoutines *errgroup.Group log log.Logger + cfg *setting.Cfg RouteRegister api.RouteRegister `inject:""` HttpServer *api.HTTPServer `inject:""` } func (g *GrafanaServerImpl) Start() error { - g.initLogging() + g.loadConfiguration() g.writePIDFile() // initSql sqlstore.NewEngine() // TODO: this should return an error sqlstore.EnsureAdminUser() - metrics.Init(setting.Cfg) + metrics.Init(g.cfg.Raw) login.Init() social.NewOAuthService() - if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil { + if err := provisioning.Init(g.context, setting.HomePath, g.cfg.Raw); err != nil { return fmt.Errorf("Failed to provision Grafana from config. error: %v", err) } - tracingCloser, err := tracing.Init(setting.Cfg) + tracingCloser, err := tracing.Init(g.cfg.Raw) if err != nil { return fmt.Errorf("Tracing settings is not valid. error: %v", err) } @@ -86,6 +88,7 @@ func (g *GrafanaServerImpl) Start() error { serviceGraph := inject.Graph{} serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) + serviceGraph.Provide(&inject.Object{Value: g.cfg}) serviceGraph.Provide(&inject.Object{Value: dashboards.NewProvisioningService()}) serviceGraph.Provide(&inject.Object{Value: api.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)}) serviceGraph.Provide(&inject.Object{Value: api.HTTPServer{}}) @@ -138,8 +141,8 @@ func (g *GrafanaServerImpl) Start() error { return g.startHttpServer() } -func (g *GrafanaServerImpl) initLogging() { - err := setting.NewConfigContext(&setting.CommandLineArgs{ +func (g *GrafanaServerImpl) loadConfiguration() { + err := g.cfg.Load(&setting.CommandLineArgs{ Config: *configFile, HomePath: *homePath, Args: flag.Args(), @@ -151,7 +154,7 @@ func (g *GrafanaServerImpl) initLogging() { } g.log.Info("Starting "+setting.ApplicationName, "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0)) - setting.LogConfigurationInfo() + g.cfg.LogConfigSources() } func (g *GrafanaServerImpl) startHttpServer() error { diff --git a/pkg/components/imguploader/azureblobuploader_test.go b/pkg/components/imguploader/azureblobuploader_test.go index ca978f70e3d..c0c7889a155 100644 --- a/pkg/components/imguploader/azureblobuploader_test.go +++ b/pkg/components/imguploader/azureblobuploader_test.go @@ -10,7 +10,8 @@ import ( func TestUploadToAzureBlob(t *testing.T) { SkipConvey("[Integration test] for external_image_store.azure_blob", t, func() { - err := setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + err := cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) So(err, ShouldBeNil) diff --git a/pkg/components/imguploader/gcsuploader_test.go b/pkg/components/imguploader/gcsuploader_test.go index bdc21084dbf..58cb21c184c 100644 --- a/pkg/components/imguploader/gcsuploader_test.go +++ b/pkg/components/imguploader/gcsuploader_test.go @@ -10,7 +10,8 @@ import ( func TestUploadToGCS(t *testing.T) { SkipConvey("[Integration test] for external_image_store.gcs", t, func() { - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) diff --git a/pkg/components/imguploader/imguploader.go b/pkg/components/imguploader/imguploader.go index 52a31f9f606..93f69cadd46 100644 --- a/pkg/components/imguploader/imguploader.go +++ b/pkg/components/imguploader/imguploader.go @@ -3,9 +3,10 @@ package imguploader import ( "context" "fmt" - "github.com/grafana/grafana/pkg/log" "regexp" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/setting" ) @@ -24,7 +25,7 @@ func NewImageUploader() (ImageUploader, error) { switch setting.ImageUploadProvider { case "s3": - s3sec, err := setting.Cfg.GetSection("external_image_storage.s3") + s3sec, err := setting.Raw.GetSection("external_image_storage.s3") if err != nil { return nil, err } @@ -51,7 +52,7 @@ func NewImageUploader() (ImageUploader, error) { return NewS3Uploader(region, bucket, path, "public-read", accessKey, secretKey), nil case "webdav": - webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav") + webdavSec, err := setting.Raw.GetSection("external_image_storage.webdav") if err != nil { return nil, err } @@ -67,7 +68,7 @@ func NewImageUploader() (ImageUploader, error) { return NewWebdavImageUploader(url, username, password, public_url) case "gcs": - gcssec, err := setting.Cfg.GetSection("external_image_storage.gcs") + gcssec, err := setting.Raw.GetSection("external_image_storage.gcs") if err != nil { return nil, err } @@ -78,7 +79,7 @@ func NewImageUploader() (ImageUploader, error) { return NewGCSUploader(keyFile, bucketName, path), nil case "azure_blob": - azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob") + azureBlobSec, err := setting.Raw.GetSection("external_image_storage.azure_blob") if err != nil { return nil, err } diff --git a/pkg/components/imguploader/imguploader_test.go b/pkg/components/imguploader/imguploader_test.go index b272a45e7a5..570e36a47e3 100644 --- a/pkg/components/imguploader/imguploader_test.go +++ b/pkg/components/imguploader/imguploader_test.go @@ -11,14 +11,15 @@ import ( func TestImageUploaderFactory(t *testing.T) { Convey("Can create image uploader for ", t, func() { Convey("S3ImageUploader config", func() { - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) setting.ImageUploadProvider = "s3" Convey("with bucket url https://foo.bar.baz.s3-us-east-2.amazonaws.com", func() { - s3sec, err := setting.Cfg.GetSection("external_image_storage.s3") + s3sec, err := setting.Raw.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") @@ -37,7 +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") + s3sec, err := setting.Raw.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") @@ -56,7 +57,7 @@ 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") + s3sec, err := setting.Raw.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") @@ -77,13 +78,14 @@ func TestImageUploaderFactory(t *testing.T) { Convey("Webdav uploader", func() { var err error - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) setting.ImageUploadProvider = "webdav" - webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav") + webdavSec, err := cfg.Raw.GetSection("external_image_storage.webdav") So(err, ShouldBeNil) webdavSec.NewKey("url", "webdavUrl") webdavSec.NewKey("username", "username") @@ -103,13 +105,14 @@ func TestImageUploaderFactory(t *testing.T) { Convey("GCS uploader", func() { var err error - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) setting.ImageUploadProvider = "gcs" - gcpSec, err := setting.Cfg.GetSection("external_image_storage.gcs") + gcpSec, err := cfg.Raw.GetSection("external_image_storage.gcs") So(err, ShouldBeNil) gcpSec.NewKey("key_file", "/etc/secrets/project-79a52befa3f6.json") gcpSec.NewKey("bucket", "project-grafana-east") @@ -124,13 +127,14 @@ func TestImageUploaderFactory(t *testing.T) { }) Convey("AzureBlobUploader config", func() { - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) setting.ImageUploadProvider = "azure_blob" Convey("with container name", func() { - azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob") + azureBlobSec, err := cfg.Raw.GetSection("external_image_storage.azure_blob") So(err, ShouldBeNil) azureBlobSec.NewKey("account_name", "account_name") azureBlobSec.NewKey("account_key", "account_key") @@ -150,7 +154,8 @@ func TestImageUploaderFactory(t *testing.T) { Convey("Local uploader", func() { var err error - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) diff --git a/pkg/components/imguploader/s3uploader_test.go b/pkg/components/imguploader/s3uploader_test.go index b02d4676b5e..0e43740ef9b 100644 --- a/pkg/components/imguploader/s3uploader_test.go +++ b/pkg/components/imguploader/s3uploader_test.go @@ -10,7 +10,8 @@ import ( func TestUploadToS3(t *testing.T) { SkipConvey("[Integration test] for external_image_store.s3", t, func() { - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) diff --git a/pkg/metrics/settings.go b/pkg/metrics/settings.go index 5e51f85768a..c21e7279b7e 100644 --- a/pkg/metrics/settings.go +++ b/pkg/metrics/settings.go @@ -46,7 +46,7 @@ func ReadSettings(file *ini.File) *MetricSettings { } func parseGraphiteSettings(settings *MetricSettings, file *ini.File) (*graphitebridge.Config, error) { - graphiteSection, err := setting.Cfg.GetSection("metrics.graphite") + graphiteSection, err := setting.Raw.GetSection("metrics.graphite") if err != nil { return nil, nil } diff --git a/pkg/plugins/dashboard_importer_test.go b/pkg/plugins/dashboard_importer_test.go index d8460a1875c..6f31b49f99d 100644 --- a/pkg/plugins/dashboard_importer_test.go +++ b/pkg/plugins/dashboard_importer_test.go @@ -87,8 +87,8 @@ func TestDashboardImport(t *testing.T) { func pluginScenario(desc string, t *testing.T, fn func()) { Convey("Given a plugin", t, func() { - setting.Cfg = ini.Empty() - sec, _ := setting.Cfg.NewSection("plugin.test-app") + setting.Raw = ini.Empty() + sec, _ := setting.Raw.NewSection("plugin.test-app") sec.NewKey("path", "../../tests/test-app") pm := &PluginManager{} diff --git a/pkg/plugins/dashboards_test.go b/pkg/plugins/dashboards_test.go index 241e41d7bb2..c422a1431c0 100644 --- a/pkg/plugins/dashboards_test.go +++ b/pkg/plugins/dashboards_test.go @@ -14,8 +14,8 @@ import ( func TestPluginDashboards(t *testing.T) { Convey("When asking plugin dashboard info", t, func() { - setting.Cfg = ini.Empty() - sec, _ := setting.Cfg.NewSection("plugin.test-app") + setting.Raw = ini.Empty() + sec, _ := setting.Raw.NewSection("plugin.test-app") sec.NewKey("path", "../../tests/test-app") pm := &PluginManager{} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index aa4131ae06d..5096bf5cebc 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -132,7 +132,7 @@ func (pm *PluginManager) Run(ctx context.Context) error { } func checkPluginPaths() error { - for _, section := range setting.Cfg.Sections() { + for _, section := range setting.Raw.Sections() { if strings.HasPrefix(section.Name(), "plugin.") { path := section.Key("path").String() if path != "" { diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index 7566d054b7f..fa68ae4389d 100644 --- a/pkg/plugins/plugins_test.go +++ b/pkg/plugins/plugins_test.go @@ -13,7 +13,7 @@ func TestPluginScans(t *testing.T) { Convey("When scanning for plugins", t, func() { setting.StaticRootPath, _ = filepath.Abs("../../public/") - setting.Cfg = ini.Empty() + setting.Raw = ini.Empty() pm := &PluginManager{} err := pm.Init() @@ -28,8 +28,8 @@ func TestPluginScans(t *testing.T) { }) Convey("When reading app plugin definition", t, func() { - setting.Cfg = ini.Empty() - sec, _ := setting.Cfg.NewSection("plugin.nginx-app") + setting.Raw = ini.Empty() + sec, _ := setting.Raw.NewSection("plugin.nginx-app") sec.NewKey("path", "../../tests/test-app") pm := &PluginManager{} diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index ef474fd2eb2..69bc7695dea 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -16,42 +16,43 @@ import ( type CleanUpService struct { log log.Logger + Cfg *setting.Cfg `inject:""` } func init() { registry.RegisterService(&CleanUpService{}) } -func (service *CleanUpService) Init() error { - service.log = log.New("cleanup") +func (srv *CleanUpService) Init() error { + srv.log = log.New("cleanup") return nil } -func (service *CleanUpService) Run(ctx context.Context) error { - service.cleanUpTmpFiles() +func (srv *CleanUpService) Run(ctx context.Context) error { + srv.cleanUpTmpFiles() ticker := time.NewTicker(time.Minute * 10) for { select { case <-ticker.C: - service.cleanUpTmpFiles() - service.deleteExpiredSnapshots() - service.deleteExpiredDashboardVersions() - service.deleteOldLoginAttempts() + srv.cleanUpTmpFiles() + srv.deleteExpiredSnapshots() + srv.deleteExpiredDashboardVersions() + srv.deleteOldLoginAttempts() case <-ctx.Done(): return ctx.Err() } } } -func (service *CleanUpService) cleanUpTmpFiles() { - if _, err := os.Stat(setting.ImagesDir); os.IsNotExist(err) { +func (srv *CleanUpService) cleanUpTmpFiles() { + if _, err := os.Stat(srv.Cfg.ImagesDir); os.IsNotExist(err) { return } - files, err := ioutil.ReadDir(setting.ImagesDir) + files, err := ioutil.ReadDir(srv.Cfg.ImagesDir) if err != nil { - service.log.Error("Problem reading image dir", "error", err) + srv.log.Error("Problem reading image dir", "error", err) return } @@ -63,36 +64,36 @@ func (service *CleanUpService) cleanUpTmpFiles() { } for _, file := range toDelete { - fullPath := path.Join(setting.ImagesDir, file.Name()) + fullPath := path.Join(srv.Cfg.ImagesDir, file.Name()) err := os.Remove(fullPath) if err != nil { - service.log.Error("Failed to delete temp file", "file", file.Name(), "error", err) + srv.log.Error("Failed to delete temp file", "file", file.Name(), "error", err) } } - service.log.Debug("Found old rendered image to delete", "deleted", len(toDelete), "keept", len(files)) + srv.log.Debug("Found old rendered image to delete", "deleted", len(toDelete), "keept", len(files)) } -func (service *CleanUpService) deleteExpiredSnapshots() { +func (srv *CleanUpService) deleteExpiredSnapshots() { cmd := m.DeleteExpiredSnapshotsCommand{} if err := bus.Dispatch(&cmd); err != nil { - service.log.Error("Failed to delete expired snapshots", "error", err.Error()) + srv.log.Error("Failed to delete expired snapshots", "error", err.Error()) } else { - service.log.Debug("Deleted expired snapshots", "rows affected", cmd.DeletedRows) + srv.log.Debug("Deleted expired snapshots", "rows affected", cmd.DeletedRows) } } -func (service *CleanUpService) deleteExpiredDashboardVersions() { +func (srv *CleanUpService) deleteExpiredDashboardVersions() { cmd := m.DeleteExpiredVersionsCommand{} if err := bus.Dispatch(&cmd); err != nil { - service.log.Error("Failed to delete expired dashboard versions", "error", err.Error()) + srv.log.Error("Failed to delete expired dashboard versions", "error", err.Error()) } else { - service.log.Debug("Deleted old/expired dashboard versions", "rows affected", cmd.DeletedRows) + srv.log.Debug("Deleted old/expired dashboard versions", "rows affected", cmd.DeletedRows) } } -func (service *CleanUpService) deleteOldLoginAttempts() { - if setting.DisableBruteForceLoginProtection { +func (srv *CleanUpService) deleteOldLoginAttempts() { + if srv.Cfg.DisableBruteForceLoginProtection { return } @@ -100,8 +101,8 @@ func (service *CleanUpService) deleteOldLoginAttempts() { OlderThan: time.Now().Add(time.Minute * -10), } if err := bus.Dispatch(&cmd); err != nil { - service.log.Error("Problem deleting expired login attempts", "error", err.Error()) + srv.log.Error("Problem deleting expired login attempts", "error", err.Error()) } else { - service.log.Debug("Deleted expired login attempts", "rows affected", cmd.DeletedRows) + srv.log.Debug("Deleted expired login attempts", "rows affected", cmd.DeletedRows) } } diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index 37169661d73..4730ef7f0f1 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -17,8 +17,8 @@ import ( gomail "gopkg.in/mail.v2" ) -func send(msg *Message) (int, error) { - dialer, err := createDialer() +func (ns *NotificationService) send(msg *Message) (int, error) { + dialer, err := ns.createDialer() if err != nil { return 0, err } @@ -42,8 +42,8 @@ func send(msg *Message) (int, error) { return len(msg.To), nil } -func createDialer() (*gomail.Dialer, error) { - host, port, err := net.SplitHostPort(setting.Smtp.Host) +func (ns *NotificationService) createDialer() (*gomail.Dialer, error) { + host, port, err := net.SplitHostPort(ns.Cfg.Smtp.Host) if err != nil { return nil, err @@ -54,30 +54,31 @@ func createDialer() (*gomail.Dialer, error) { } tlsconfig := &tls.Config{ - InsecureSkipVerify: setting.Smtp.SkipVerify, + InsecureSkipVerify: ns.Cfg.Smtp.SkipVerify, ServerName: host, } - if setting.Smtp.CertFile != "" { - cert, err := tls.LoadX509KeyPair(setting.Smtp.CertFile, setting.Smtp.KeyFile) + if ns.Cfg.Smtp.CertFile != "" { + cert, err := tls.LoadX509KeyPair(ns.Cfg.Smtp.CertFile, ns.Cfg.Smtp.KeyFile) if err != nil { return nil, fmt.Errorf("Could not load cert or key file. error: %v", err) } tlsconfig.Certificates = []tls.Certificate{cert} } - d := gomail.NewDialer(host, iPort, setting.Smtp.User, setting.Smtp.Password) + d := gomail.NewDialer(host, iPort, ns.Cfg.Smtp.User, ns.Cfg.Smtp.Password) d.TLSConfig = tlsconfig - if setting.Smtp.EhloIdentity != "" { - d.LocalName = setting.Smtp.EhloIdentity + + if ns.Cfg.Smtp.EhloIdentity != "" { + d.LocalName = ns.Cfg.Smtp.EhloIdentity } else { d.LocalName = setting.InstanceName } return d, nil } -func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) { - if !setting.Smtp.Enabled { +func (ns *NotificationService) buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) { + if !ns.Cfg.Smtp.Enabled { return nil, m.ErrSmtpNotEnabled } @@ -121,7 +122,7 @@ func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) { return &Message{ To: cmd.To, - From: fmt.Sprintf("%s <%s>", setting.Smtp.FromName, setting.Smtp.FromAddress), + From: fmt.Sprintf("%s <%s>", ns.Cfg.Smtp.FromName, ns.Cfg.Smtp.FromAddress), Subject: subject, Body: buffer.String(), EmbededFiles: cmd.EmbededFiles, diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go index ad776057ad7..ee54e7269f7 100644 --- a/pkg/services/notifications/notifications.go +++ b/pkg/services/notifications/notifications.go @@ -28,7 +28,9 @@ func init() { } type NotificationService struct { - Bus bus.Bus `inject:""` + Bus bus.Bus `inject:""` + Cfg *setting.Cfg `inject:""` + mailQueue chan *Message webhookQueue chan *Webhook log log.Logger @@ -54,13 +56,13 @@ func (ns *NotificationService) Init() error { "Subject": subjectTemplateFunc, }) - templatePattern := filepath.Join(setting.StaticRootPath, setting.Smtp.TemplatesPattern) + templatePattern := filepath.Join(setting.StaticRootPath, ns.Cfg.Smtp.TemplatesPattern) _, err := mailTemplates.ParseGlob(templatePattern) if err != nil { return err } - if !util.IsEmail(setting.Smtp.FromAddress) { + if !util.IsEmail(ns.Cfg.Smtp.FromAddress) { return errors.New("Invalid email address for SMTP from_address config") } @@ -81,7 +83,7 @@ func (ns *NotificationService) Run(ctx context.Context) error { ns.log.Error("Failed to send webrequest ", "error", err) } case msg := <-ns.mailQueue: - num, err := send(msg) + num, err := ns.send(msg) tos := strings.Join(msg.To, "; ") info := "" if err != nil { @@ -117,7 +119,7 @@ func subjectTemplateFunc(obj map[string]interface{}, value string) string { } func (ns *NotificationService) sendEmailCommandHandlerSync(ctx context.Context, cmd *m.SendEmailCommandSync) error { - message, err := buildEmailMessage(&m.SendEmailCommand{ + message, err := ns.buildEmailMessage(&m.SendEmailCommand{ Data: cmd.Data, Info: cmd.Info, Template: cmd.Template, @@ -130,12 +132,12 @@ func (ns *NotificationService) sendEmailCommandHandlerSync(ctx context.Context, return err } - _, err = send(message) + _, err = ns.send(message) return err } func (ns *NotificationService) sendEmailCommandHandler(cmd *m.SendEmailCommand) error { - message, err := buildEmailMessage(cmd) + message, err := ns.buildEmailMessage(cmd) if err != nil { return err @@ -205,7 +207,7 @@ func (ns *NotificationService) signUpStartedHandler(evt *events.SignUpStarted) e } func (ns *NotificationService) signUpCompletedHandler(evt *events.SignUpCompleted) error { - if evt.Email == "" || !setting.Smtp.SendWelcomeEmailOnSignUp { + if evt.Email == "" || !ns.Cfg.Smtp.SendWelcomeEmailOnSignUp { return nil } diff --git a/pkg/services/notifications/notifications_test.go b/pkg/services/notifications/notifications_test.go index a86bd3b19ed..504c10c22ec 100644 --- a/pkg/services/notifications/notifications_test.go +++ b/pkg/services/notifications/notifications_test.go @@ -19,13 +19,14 @@ func TestNotifications(t *testing.T) { Convey("Given the notifications service", t, func() { setting.StaticRootPath = "../../../public/" - setting.Smtp.Enabled = true - setting.Smtp.TemplatesPattern = "emails/*.html" - setting.Smtp.FromAddress = "from@address.com" - setting.Smtp.FromName = "Grafana Admin" ns := &NotificationService{} ns.Bus = bus.New() + ns.Cfg = setting.NewCfg() + ns.Cfg.Smtp.Enabled = true + ns.Cfg.Smtp.TemplatesPattern = "emails/*.html" + ns.Cfg.Smtp.FromAddress = "from@address.com" + ns.Cfg.Smtp.FromName = "Grafana Admin" err := ns.Init() So(err, ShouldBeNil) diff --git a/pkg/services/notifications/send_email_integration_test.go b/pkg/services/notifications/send_email_integration_test.go index a9f37018a3a..201f86036d3 100644 --- a/pkg/services/notifications/send_email_integration_test.go +++ b/pkg/services/notifications/send_email_integration_test.go @@ -13,14 +13,15 @@ import ( func TestEmailIntegrationTest(t *testing.T) { SkipConvey("Given the notifications service", t, func() { setting.StaticRootPath = "../../../public/" - setting.Smtp.Enabled = true - setting.Smtp.TemplatesPattern = "emails/*.html" - setting.Smtp.FromAddress = "from@address.com" - setting.Smtp.FromName = "Grafana Admin" setting.BuildVersion = "4.0.0" ns := &NotificationService{} ns.Bus = bus.New() + ns.Cfg = setting.NewCfg() + ns.Cfg.Smtp.Enabled = true + ns.Cfg.Smtp.TemplatesPattern = "emails/*.html" + ns.Cfg.Smtp.FromAddress = "from@address.com" + ns.Cfg.Smtp.FromName = "Grafana Admin" err := ns.Init() So(err, ShouldBeNil) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index e4be3208c86..b804d8b1621 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -168,7 +168,7 @@ func getEngine() (*xorm.Engine, error) { engine.SetMaxOpenConns(DbCfg.MaxOpenConn) engine.SetMaxIdleConns(DbCfg.MaxIdleConn) engine.SetConnMaxLifetime(time.Second * time.Duration(DbCfg.ConnMaxLifetime)) - debugSql := setting.Cfg.Section("database").Key("log_queries").MustBool(false) + debugSql := setting.Raw.Section("database").Key("log_queries").MustBool(false) if !debugSql { engine.SetLogger(&xorm.DiscardLogger{}) } else { @@ -181,7 +181,7 @@ func getEngine() (*xorm.Engine, error) { } func LoadConfig() { - sec := setting.Cfg.Section("database") + sec := setting.Raw.Section("database") cfgURL := sec.Key("url").String() if len(cfgURL) != 0 { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 922eea607d1..40d7522f775 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -137,7 +137,7 @@ var ( SessionConnMaxLifetime int64 // Global setting objects. - Cfg *ini.File + Raw *ini.File ConfRootPath string IsWindows bool @@ -160,9 +160,6 @@ var ( LdapConfigFile string LdapAllowSignup = true - // SMTP email settings - Smtp SmtpSettings - // QUOTA Quota QuotaSettings @@ -187,6 +184,16 @@ var ( ImageUploadProvider string ) +type Cfg struct { + Raw *ini.File + + // SMTP email settings + Smtp SmtpSettings + + ImagesDir string + DisableBruteForceLoginProtection bool +} + type CommandLineArgs struct { Config string HomePath string @@ -228,9 +235,9 @@ func shouldRedactURLKey(s string) bool { return strings.Contains(uppercased, "DATABASE_URL") } -func applyEnvVariableOverrides() error { +func applyEnvVariableOverrides(file *ini.File) error { appliedEnvOverrides = make([]string, 0) - for _, section := range Cfg.Sections() { + for _, section := range file.Sections() { for _, key := range section.Keys() { sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1)) keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1)) @@ -264,9 +271,9 @@ func applyEnvVariableOverrides() error { return nil } -func applyCommandLineDefaultProperties(props map[string]string) { +func applyCommandLineDefaultProperties(props map[string]string, file *ini.File) { appliedCommandLineProperties = make([]string, 0) - for _, section := range Cfg.Sections() { + for _, section := range file.Sections() { for _, key := range section.Keys() { keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name()) value, exists := props[keyString] @@ -281,8 +288,8 @@ func applyCommandLineDefaultProperties(props map[string]string) { } } -func applyCommandLineProperties(props map[string]string) { - for _, section := range Cfg.Sections() { +func applyCommandLineProperties(props map[string]string, file *ini.File) { + for _, section := range file.Sections() { sectionName := section.Name() + "." if section.Name() == ini.DEFAULT_SECTION { sectionName = "" @@ -341,15 +348,15 @@ func evalEnvVarExpression(value string) string { }) } -func evalConfigValues() { - for _, section := range Cfg.Sections() { +func evalConfigValues(file *ini.File) { + for _, section := range file.Sections() { for _, key := range section.Keys() { key.SetValue(evalEnvVarExpression(key.Value())) } } } -func loadSpecifedConfigFile(configFile string) error { +func loadSpecifedConfigFile(configFile string, masterFile *ini.File) error { if configFile == "" { configFile = filepath.Join(HomePath, CustomInitPath) // return without error if custom file does not exist @@ -371,9 +378,9 @@ func loadSpecifedConfigFile(configFile string) error { continue } - defaultSec, err := Cfg.GetSection(section.Name()) + defaultSec, err := masterFile.GetSection(section.Name()) if err != nil { - defaultSec, _ = Cfg.NewSection(section.Name()) + defaultSec, _ = masterFile.NewSection(section.Name()) } defaultKey, err := defaultSec.GetKey(key.Name()) if err != nil { @@ -387,7 +394,7 @@ func loadSpecifedConfigFile(configFile string) error { return nil } -func loadConfiguration(args *CommandLineArgs) error { +func loadConfiguration(args *CommandLineArgs) (*ini.File, error) { var err error // load config defaults @@ -401,44 +408,44 @@ func loadConfiguration(args *CommandLineArgs) error { } // load defaults - Cfg, err = ini.Load(defaultConfigFile) + parsedFile, err := ini.Load(defaultConfigFile) if err != nil { fmt.Println(fmt.Sprintf("Failed to parse defaults.ini, %v", err)) os.Exit(1) - return err + return nil, err } - Cfg.BlockMode = false + parsedFile.BlockMode = false // command line props commandLineProps := getCommandLineProperties(args.Args) // load default overrides - applyCommandLineDefaultProperties(commandLineProps) + applyCommandLineDefaultProperties(commandLineProps, parsedFile) // load specified config file - err = loadSpecifedConfigFile(args.Config) + err = loadSpecifedConfigFile(args.Config, parsedFile) if err != nil { - initLogging() + initLogging(parsedFile) log.Fatal(3, err.Error()) } // apply environment overrides - err = applyEnvVariableOverrides() + err = applyEnvVariableOverrides(parsedFile) if err != nil { - return err + return nil, err } // apply command line overrides - applyCommandLineProperties(commandLineProps) + applyCommandLineProperties(commandLineProps, parsedFile) // evaluate config values containing environment variables - evalConfigValues() + evalConfigValues(parsedFile) // update data path and logging config - DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath) - initLogging() + DataPath = makeAbsolute(parsedFile.Section("paths").Key("data").String(), HomePath) + initLogging(parsedFile) - return err + return parsedFile, err } func pathExists(path string) bool { @@ -484,23 +491,33 @@ func validateStaticRootPath() error { return nil } -func NewConfigContext(args *CommandLineArgs) error { +func NewCfg() *Cfg { + return &Cfg{} +} + +func (cfg *Cfg) Load(args *CommandLineArgs) error { setHomePath(args) - err := loadConfiguration(args) + + iniFile, err := loadConfiguration(args) if err != nil { return err } + cfg.Raw = iniFile + + // Temporary keep global, to make refactor in steps + Raw = cfg.Raw + ApplicationName = "Grafana" if Enterprise { ApplicationName += " Enterprise" } - Env = Cfg.Section("").Key("app_mode").MustString("development") - InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name") - PluginsPath = makeAbsolute(Cfg.Section("paths").Key("plugins").String(), HomePath) - ProvisioningPath = makeAbsolute(Cfg.Section("paths").Key("provisioning").String(), HomePath) - server := Cfg.Section("server") + Env = iniFile.Section("").Key("app_mode").MustString("development") + InstanceName = iniFile.Section("").Key("instance_name").MustString("unknown_instance_name") + PluginsPath = makeAbsolute(iniFile.Section("paths").Key("plugins").String(), HomePath) + ProvisioningPath = makeAbsolute(iniFile.Section("paths").Key("provisioning").String(), HomePath) + server := iniFile.Section("server") AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server) Protocol = HTTP @@ -528,27 +545,28 @@ func NewConfigContext(args *CommandLineArgs) error { } // read data proxy settings - dataproxy := Cfg.Section("dataproxy") + dataproxy := iniFile.Section("dataproxy") DataProxyLogging = dataproxy.Key("logging").MustBool(false) // read security settings - security := Cfg.Section("security") + security := iniFile.Section("security") SecretKey = security.Key("secret_key").String() LogInRememberDays = security.Key("login_remember_days").MustInt() CookieUserName = security.Key("cookie_username").String() CookieRememberName = security.Key("cookie_remember_name").String() DisableGravatar = security.Key("disable_gravatar").MustBool(true) - DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false) + cfg.DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false) + DisableBruteForceLoginProtection = cfg.DisableBruteForceLoginProtection // read snapshots settings - snapshots := Cfg.Section("snapshots") + snapshots := iniFile.Section("snapshots") ExternalSnapshotUrl = snapshots.Key("external_snapshot_url").String() ExternalSnapshotName = snapshots.Key("external_snapshot_name").String() ExternalEnabled = snapshots.Key("external_enabled").MustBool(true) SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true) // read dashboard settings - dashboards := Cfg.Section("dashboards") + dashboards := iniFile.Section("dashboards") DashboardVersionsToKeep = dashboards.Key("versions_to_keep").MustInt(20) // read data source proxy white list @@ -561,7 +579,7 @@ func NewConfigContext(args *CommandLineArgs) error { AdminUser = security.Key("admin_user").String() AdminPassword = security.Key("admin_password").String() - users := Cfg.Section("users") + users := iniFile.Section("users") AllowUserSignUp = users.Key("allow_sign_up").MustBool(true) AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) @@ -575,17 +593,17 @@ func NewConfigContext(args *CommandLineArgs) error { ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false) // auth - auth := Cfg.Section("auth") + auth := iniFile.Section("auth") DisableLoginForm = auth.Key("disable_login_form").MustBool(false) DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false) // anonymous access - AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false) - AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String() - AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String() + AnonymousEnabled = iniFile.Section("auth.anonymous").Key("enabled").MustBool(false) + AnonymousOrgName = iniFile.Section("auth.anonymous").Key("org_name").String() + AnonymousOrgRole = iniFile.Section("auth.anonymous").Key("org_role").String() // auth proxy - authProxy := Cfg.Section("auth.proxy") + authProxy := iniFile.Section("auth.proxy") AuthProxyEnabled = authProxy.Key("enabled").MustBool(false) AuthProxyHeaderName = authProxy.Key("header_name").String() AuthProxyHeaderProperty = authProxy.Key("header_property").String() @@ -594,63 +612,64 @@ func NewConfigContext(args *CommandLineArgs) error { AuthProxyWhitelist = authProxy.Key("whitelist").String() // basic auth - authBasic := Cfg.Section("auth.basic") + authBasic := iniFile.Section("auth.basic") BasicAuthEnabled = authBasic.Key("enabled").MustBool(true) // global plugin settings - PluginAppsSkipVerifyTLS = Cfg.Section("plugins").Key("app_tls_skip_verify_insecure").MustBool(false) + PluginAppsSkipVerifyTLS = iniFile.Section("plugins").Key("app_tls_skip_verify_insecure").MustBool(false) // PhantomJS rendering - ImagesDir = filepath.Join(DataPath, "png") + cfg.ImagesDir = filepath.Join(DataPath, "png") + ImagesDir = cfg.ImagesDir PhantomDir = filepath.Join(HomePath, "tools/phantomjs") - analytics := Cfg.Section("analytics") + analytics := iniFile.Section("analytics") ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) CheckForUpdates = analytics.Key("check_for_updates").MustBool(true) GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String() GoogleTagManagerId = analytics.Key("google_tag_manager_id").String() - ldapSec := Cfg.Section("auth.ldap") + ldapSec := iniFile.Section("auth.ldap") LdapEnabled = ldapSec.Key("enabled").MustBool(false) LdapConfigFile = ldapSec.Key("config_file").String() LdapAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true) - alerting := Cfg.Section("alerting") + alerting := iniFile.Section("alerting") AlertingEnabled = alerting.Key("enabled").MustBool(true) ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true) - explore := Cfg.Section("explore") + explore := iniFile.Section("explore") ExploreEnabled = explore.Key("enabled").MustBool(false) - readSessionConfig() - readSmtpSettings() - readQuotaSettings() + cfg.readSessionConfig() + cfg.readSmtpSettings() + cfg.readQuotaSettings() - if VerifyEmailEnabled && !Smtp.Enabled { + if VerifyEmailEnabled && !cfg.Smtp.Enabled { log.Warn("require_email_validation is enabled but smtp is disabled") } // check old key name - GrafanaComUrl = Cfg.Section("grafana_net").Key("url").MustString("") + GrafanaComUrl = iniFile.Section("grafana_net").Key("url").MustString("") if GrafanaComUrl == "" { - GrafanaComUrl = Cfg.Section("grafana_com").Key("url").MustString("https://grafana.com") + GrafanaComUrl = iniFile.Section("grafana_com").Key("url").MustString("https://grafana.com") } - imageUploadingSection := Cfg.Section("external_image_storage") + imageUploadingSection := iniFile.Section("external_image_storage") ImageUploadProvider = imageUploadingSection.Key("provider").MustString("") return nil } -func readSessionConfig() { - sec := Cfg.Section("session") +func (cfg *Cfg) readSessionConfig() { + sec := cfg.Raw.Section("session") SessionOptions = session.Options{} SessionOptions.Provider = sec.Key("provider").In("memory", []string{"memory", "file", "redis", "mysql", "postgres", "memcache"}) SessionOptions.ProviderConfig = strings.Trim(sec.Key("provider_config").String(), "\" ") SessionOptions.CookieName = sec.Key("cookie_name").MustString("grafana_sess") SessionOptions.CookiePath = AppSubUrl SessionOptions.Secure = sec.Key("cookie_secure").MustBool() - SessionOptions.Gclifetime = Cfg.Section("session").Key("gc_interval_time").MustInt64(86400) - SessionOptions.Maxlifetime = Cfg.Section("session").Key("session_life_time").MustInt64(86400) + SessionOptions.Gclifetime = cfg.Raw.Section("session").Key("gc_interval_time").MustInt64(86400) + SessionOptions.Maxlifetime = cfg.Raw.Section("session").Key("session_life_time").MustInt64(86400) SessionOptions.IDLength = 16 if SessionOptions.Provider == "file" { @@ -662,21 +681,21 @@ func readSessionConfig() { SessionOptions.CookiePath = "/" } - SessionConnMaxLifetime = Cfg.Section("session").Key("conn_max_lifetime").MustInt64(14400) + SessionConnMaxLifetime = cfg.Raw.Section("session").Key("conn_max_lifetime").MustInt64(14400) } -func initLogging() { +func initLogging(file *ini.File) { // split on comma - LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",") + LogModes = strings.Split(file.Section("log").Key("mode").MustString("console"), ",") // also try space if len(LogModes) == 1 { - LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), " ") + LogModes = strings.Split(file.Section("log").Key("mode").MustString("console"), " ") } - LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath) - log.ReadLoggingConfig(LogModes, LogsPath, Cfg) + LogsPath = makeAbsolute(file.Section("paths").Key("logs").String(), HomePath) + log.ReadLoggingConfig(LogModes, LogsPath, file) } -func LogConfigurationInfo() { +func (cfg *Cfg) LogConfigSources() { var text bytes.Buffer for _, file := range configFiles { diff --git a/pkg/setting/setting_quota.go b/pkg/setting/setting_quota.go index 49769d9930f..c3a509219db 100644 --- a/pkg/setting/setting_quota.go +++ b/pkg/setting/setting_quota.go @@ -63,9 +63,9 @@ type QuotaSettings struct { Global *GlobalQuota } -func readQuotaSettings() { +func (cfg *Cfg) readQuotaSettings() { // set global defaults. - quota := Cfg.Section("quota") + quota := cfg.Raw.Section("quota") Quota.Enabled = quota.Key("enabled").MustBool(false) // per ORG Limits diff --git a/pkg/setting/setting_smtp.go b/pkg/setting/setting_smtp.go index 9d8b8a529a5..5df774dc691 100644 --- a/pkg/setting/setting_smtp.go +++ b/pkg/setting/setting_smtp.go @@ -16,20 +16,20 @@ type SmtpSettings struct { TemplatesPattern string } -func readSmtpSettings() { - sec := Cfg.Section("smtp") - Smtp.Enabled = sec.Key("enabled").MustBool(false) - Smtp.Host = sec.Key("host").String() - Smtp.User = sec.Key("user").String() - Smtp.Password = sec.Key("password").String() - Smtp.CertFile = sec.Key("cert_file").String() - Smtp.KeyFile = sec.Key("key_file").String() - Smtp.FromAddress = sec.Key("from_address").String() - Smtp.FromName = sec.Key("from_name").String() - Smtp.EhloIdentity = sec.Key("ehlo_identity").String() - Smtp.SkipVerify = sec.Key("skip_verify").MustBool(false) +func (cfg *Cfg) readSmtpSettings() { + sec := cfg.Raw.Section("smtp") + cfg.Smtp.Enabled = sec.Key("enabled").MustBool(false) + cfg.Smtp.Host = sec.Key("host").String() + cfg.Smtp.User = sec.Key("user").String() + cfg.Smtp.Password = sec.Key("password").String() + cfg.Smtp.CertFile = sec.Key("cert_file").String() + cfg.Smtp.KeyFile = sec.Key("key_file").String() + cfg.Smtp.FromAddress = sec.Key("from_address").String() + cfg.Smtp.FromName = sec.Key("from_name").String() + cfg.Smtp.EhloIdentity = sec.Key("ehlo_identity").String() + cfg.Smtp.SkipVerify = sec.Key("skip_verify").MustBool(false) - emails := Cfg.Section("emails") - Smtp.SendWelcomeEmailOnSignUp = emails.Key("welcome_email_on_sign_up").MustBool(false) - Smtp.TemplatesPattern = emails.Key("templates_pattern").MustString("emails/*.html") + emails := cfg.Raw.Section("emails") + cfg.Smtp.SendWelcomeEmailOnSignUp = emails.Key("welcome_email_on_sign_up").MustBool(false) + cfg.Smtp.TemplatesPattern = emails.Key("templates_pattern").MustString("emails/*.html") } diff --git a/pkg/setting/setting_test.go b/pkg/setting/setting_test.go index 2da728b7298..9de22c86811 100644 --- a/pkg/setting/setting_test.go +++ b/pkg/setting/setting_test.go @@ -15,7 +15,8 @@ func TestLoadingSettings(t *testing.T) { skipStaticRootValidation = true Convey("Given the default ini files", func() { - err := NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + cfg := NewCfg() + err := cfg.Load(&CommandLineArgs{HomePath: "../../"}) So(err, ShouldBeNil) So(AdminUser, ShouldEqual, "admin") @@ -23,7 +24,9 @@ func TestLoadingSettings(t *testing.T) { Convey("Should be able to override via environment variables", func() { os.Setenv("GF_SECURITY_ADMIN_USER", "superduper") - NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + + cfg := NewCfg() + cfg.Load(&CommandLineArgs{HomePath: "../../"}) So(AdminUser, ShouldEqual, "superduper") So(DataPath, ShouldEqual, filepath.Join(HomePath, "data")) @@ -32,21 +35,27 @@ func TestLoadingSettings(t *testing.T) { Convey("Should replace password when defined in environment", func() { os.Setenv("GF_SECURITY_ADMIN_PASSWORD", "supersecret") - NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + + cfg := NewCfg() + cfg.Load(&CommandLineArgs{HomePath: "../../"}) So(appliedEnvOverrides, ShouldContain, "GF_SECURITY_ADMIN_PASSWORD=*********") }) Convey("Should return an error when url is invalid", func() { os.Setenv("GF_DATABASE_URL", "postgres.%31://grafana:secret@postgres:5432/grafana") - err := NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + + cfg := NewCfg() + err := cfg.Load(&CommandLineArgs{HomePath: "../../"}) So(err, ShouldNotBeNil) }) Convey("Should replace password in URL when url environment is defined", func() { os.Setenv("GF_DATABASE_URL", "mysql://user:secret@localhost:3306/database") - NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + + cfg := NewCfg() + cfg.Load(&CommandLineArgs{HomePath: "../../"}) So(appliedEnvOverrides, ShouldContain, "GF_DATABASE_URL=mysql://user:-redacted-@localhost:3306/database") }) @@ -61,14 +70,16 @@ func TestLoadingSettings(t *testing.T) { Convey("Should be able to override via command line", func() { if runtime.GOOS == "windows" { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Args: []string{`cfg:paths.data=c:\tmp\data`, `cfg:paths.logs=c:\tmp\logs`}, }) So(DataPath, ShouldEqual, `c:\tmp\data`) So(LogsPath, ShouldEqual, `c:\tmp\logs`) } else { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:paths.data=/tmp/data", "cfg:paths.logs=/tmp/logs"}, }) @@ -79,7 +90,8 @@ func TestLoadingSettings(t *testing.T) { }) Convey("Should be able to override defaults via command line", func() { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Args: []string{ "cfg:default.server.domain=test2", @@ -92,7 +104,8 @@ func TestLoadingSettings(t *testing.T) { Convey("Defaults can be overridden in specified config file", func() { if runtime.GOOS == "windows" { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "tests/config-files/override_windows.ini"), Args: []string{`cfg:default.paths.data=c:\tmp\data`}, @@ -100,7 +113,8 @@ func TestLoadingSettings(t *testing.T) { So(DataPath, ShouldEqual, `c:\tmp\override`) } else { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "tests/config-files/override.ini"), Args: []string{"cfg:default.paths.data=/tmp/data"}, @@ -112,7 +126,8 @@ func TestLoadingSettings(t *testing.T) { Convey("Command line overrides specified config file", func() { if runtime.GOOS == "windows" { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "tests/config-files/override_windows.ini"), Args: []string{`cfg:paths.data=c:\tmp\data`}, @@ -120,7 +135,8 @@ func TestLoadingSettings(t *testing.T) { So(DataPath, ShouldEqual, `c:\tmp\data`) } else { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "tests/config-files/override.ini"), Args: []string{"cfg:paths.data=/tmp/data"}, @@ -133,7 +149,8 @@ func TestLoadingSettings(t *testing.T) { Convey("Can use environment variables in config values", func() { if runtime.GOOS == "windows" { os.Setenv("GF_DATA_PATH", `c:\tmp\env_override`) - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:paths.data=${GF_DATA_PATH}"}, }) @@ -141,7 +158,8 @@ func TestLoadingSettings(t *testing.T) { So(DataPath, ShouldEqual, `c:\tmp\env_override`) } else { os.Setenv("GF_DATA_PATH", "/tmp/env_override") - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:paths.data=${GF_DATA_PATH}"}, }) @@ -151,7 +169,8 @@ func TestLoadingSettings(t *testing.T) { }) Convey("instance_name default to hostname even if hostname env is empty", func() { - NewConfigContext(&CommandLineArgs{ + cfg := NewCfg() + cfg.Load(&CommandLineArgs{ HomePath: "../../", }) diff --git a/pkg/social/social.go b/pkg/social/social.go index 8f0618b7f74..adbe5a912d9 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -58,7 +58,7 @@ func NewOAuthService() { allOauthes := []string{"github", "google", "generic_oauth", "grafananet", "grafana_com"} for _, name := range allOauthes { - sec := setting.Cfg.Section("auth." + name) + sec := setting.Raw.Section("auth." + name) info := &setting.OAuthInfo{ ClientId: sec.Key("client_id").String(), ClientSecret: sec.Key("client_secret").String(), diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index 921996d155d..79b01f70c9b 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -32,7 +32,7 @@ func Init(file *ini.File) (io.Closer, error) { func parseSettings(file *ini.File) *TracingSettings { settings := &TracingSettings{} - var section, err = setting.Cfg.GetSection("tracing.jaeger") + var section, err = setting.Raw.GetSection("tracing.jaeger") if err != nil { return settings } diff --git a/pkg/tsdb/influxdb/response_parser_test.go b/pkg/tsdb/influxdb/response_parser_test.go index a517cf4d71f..d8ec6e145c7 100644 --- a/pkg/tsdb/influxdb/response_parser_test.go +++ b/pkg/tsdb/influxdb/response_parser_test.go @@ -13,7 +13,8 @@ func TestInfluxdbResponseParser(t *testing.T) { Convey("Response parser", func() { parser := &ResponseParser{} - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../../", }) diff --git a/pkg/tsdb/interval_test.go b/pkg/tsdb/interval_test.go index 1e36e5428fe..941b08dd554 100644 --- a/pkg/tsdb/interval_test.go +++ b/pkg/tsdb/interval_test.go @@ -10,7 +10,8 @@ import ( func TestInterval(t *testing.T) { Convey("Default interval ", t, func() { - setting.NewConfigContext(&setting.CommandLineArgs{ + cfg := setting.NewCfg() + cfg.Load(&setting.CommandLineArgs{ HomePath: "../../", }) From 0fc4da810fc6412c5e6c8dd7d71656db4dc80df8 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 30 Apr 2018 16:33:27 +0200 Subject: [PATCH 302/319] changelog: notes about closing #11498 [skip ci] --- CHANGELOG.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a82a8d0498..866cb216757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 5.2.0 (unreleased) + +### Minor + +* **Graph**: Show invisible highest value bucket in histogram [#11498](https://github.com/grafana/grafana/issues/11498) + + # 5.1.0 (2018-04-26) * **Folders**: Default permissions on folder are not shown as inherited in its dashboards [#11668](https://github.com/grafana/grafana/issues/11668) @@ -51,13 +58,13 @@ * **Units**: Use B/s instead Bps for Bytes per second [#9342](https://github.com/grafana/grafana/pull/9342), thx [@mayli](https://github.com/mayli) * **Units**: Radiation units [#11001](https://github.com/grafana/grafana/issues/11001), thx [@victorclaessen](https://github.com/victorclaessen) * **Units**: Timeticks unit [#11183](https://github.com/grafana/grafana/pull/11183), thx [@jtyr](https://github.com/jtyr) -* **Units**: Concentration units and "Normal cubic metre" [#11211](https://github.com/grafana/grafana/issues/11211), thx [@flopp999](https://github.com/flopp999) +* **Units**: Concentration units and "Normal cubic metre" [#11211](https://github.com/grafana/grafana/issues/11211), thx [@flopp999](https://github.com/flopp999) * **Units**: New currency - Czech koruna [#11384](https://github.com/grafana/grafana/pull/11384), thx [@Rohlik](https://github.com/Rohlik) * **Avatar**: Fix DISABLE_GRAVATAR option [#11095](https://github.com/grafana/grafana/issues/11095) * **Heatmap**: Disable log scale when using time time series buckets [#10792](https://github.com/grafana/grafana/issues/10792) * **Provisioning**: Remove `id` from json when provisioning dashboards, [#11138](https://github.com/grafana/grafana/issues/11138) -* **Prometheus**: tooltip for legend format not showing properly [#11516](https://github.com/grafana/grafana/issues/11516), thx [@svenklemm](https://github.com/svenklemm) -* **Playlist**: Empty playlists cannot be deleted [#11133](https://github.com/grafana/grafana/issues/11133), thx [@kichristensen](https://github.com/kichristensen) +* **Prometheus**: tooltip for legend format not showing properly [#11516](https://github.com/grafana/grafana/issues/11516), thx [@svenklemm](https://github.com/svenklemm) +* **Playlist**: Empty playlists cannot be deleted [#11133](https://github.com/grafana/grafana/issues/11133), thx [@kichristensen](https://github.com/kichristensen) * **Switch Orgs**: Alphabetic order in Switch Organization modal [#11556](https://github.com/grafana/grafana/issues/11556) * **Postgres**: improve `$__timeFilter` macro [#11578](https://github.com/grafana/grafana/issues/11578), thx [@svenklemm](https://github.com/svenklemm) * **Permission list**: Improved ux [#10747](https://github.com/grafana/grafana/issues/10747) From 253b2cc081dc3fe2f81da2feb22b490f09494537 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 1 May 2018 05:00:56 +0900 Subject: [PATCH 303/319] add test for prometheus table column title --- .../prometheus/specs/result_transformer.jest.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts b/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts index abcc46d7ea8..64b983fc8a7 100644 --- a/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts +++ b/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts @@ -47,6 +47,18 @@ describe('Prometheus Result Transformer', () => { { text: 'Value' }, ]); }); + + it('should column title include refId if response count is more than 2', () => { + var table = ctx.resultTransformer.transformMetricDataToTable(response.data.result, 2, "B"); + expect(table.type).toBe('table'); + expect(table.columns).toEqual([ + { text: 'Time', type: 'time' }, + { text: '__name__' }, + { text: 'instance' }, + { text: 'job' }, + { text: 'Value #B' }, + ]); + }); }); describe('When resultFormat is table and instant = true', () => { From 13e015fe3f6ec77f3e77b7d61997c98e43c67d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 May 2018 14:13:38 +0200 Subject: [PATCH 304/319] fix: improved handling of http server shutdown --- pkg/api/http_server.go | 10 ++++++++++ pkg/cmd/grafana-server/server.go | 15 +++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index fa27eabbf24..38858606579 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -60,6 +60,16 @@ func (hs *HTTPServer) Start(ctx context.Context) error { hs.log.Info("Initializing HTTP Server", "address", listenAddr, "protocol", setting.Protocol, "subUrl", setting.AppSubUrl, "socket", setting.SocketPath) hs.httpSrv = &http.Server{Addr: listenAddr, Handler: hs.macaron} + + // handle http shutdown on server context done + go func() { + <-ctx.Done() + if err := hs.httpSrv.Shutdown(context.Background()); err != nil { + hs.log.Error("Failed to shutdown server", "error", err) + } + hs.log.Info("Stopped HTTP Server") + }() + switch setting.Protocol { case setting.HTTP: err = hs.httpSrv.ListenAndServe() diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 911cf092b47..71a1560215f 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -92,6 +92,8 @@ func (g *GrafanaServerImpl) Start() error { serviceGraph.Provide(&inject.Object{Value: dashboards.NewProvisioningService()}) serviceGraph.Provide(&inject.Object{Value: api.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)}) serviceGraph.Provide(&inject.Object{Value: api.HTTPServer{}}) + + // self registered services services := registry.GetServices() // Add all services to dependency graph @@ -172,15 +174,12 @@ func (g *GrafanaServerImpl) startHttpServer() error { func (g *GrafanaServerImpl) Shutdown(code int, reason string) { g.log.Info("Shutdown started", "code", code, "reason", reason) - err := g.HttpServer.Shutdown(g.context) - if err != nil { - g.log.Error("Failed to shutdown server", "error", err) - } - + // call cancel func on root context g.shutdownFn() - err = g.childRoutines.Wait() - if err != nil && err != context.Canceled { - g.log.Error("Server shutdown completed with an error", "error", err) + + // wait for chid routines + if err := g.childRoutines.Wait(); err != nil && err != context.Canceled { + g.log.Error("Server shutdown completed", "error", err) } } From 2b93cbbf04004af09dc140ba2aa577b7ffb536c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 May 2018 14:18:10 +0200 Subject: [PATCH 305/319] --amend --- pkg/cmd/grafana-server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 71a1560215f..30bb0b2003a 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -177,7 +177,7 @@ func (g *GrafanaServerImpl) Shutdown(code int, reason string) { // call cancel func on root context g.shutdownFn() - // wait for chid routines + // wait for child routines if err := g.childRoutines.Wait(); err != nil && err != context.Canceled { g.log.Error("Server shutdown completed", "error", err) } From 3dd073f98d3fc2bc5e4b5639d226aa04cb68db0f Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 2 May 2018 09:56:53 +0200 Subject: [PATCH 306/319] fixed so all buttons are styled not just small ones, fixes #11616 --- public/sass/components/_timepicker.scss | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index 2d7a12c3d01..9b71e8e7c05 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -71,12 +71,10 @@ td { padding: 1px; } - button.btn-sm { + button { @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); - font-size: $font-size-sm; background-image: none; border: none; - padding: 5px 11px; color: $text-color; &.active span { color: $blue; @@ -86,6 +84,10 @@ color: $orange; font-weight: bold; } + &.btn-sm { + font-size: $font-size-sm; + padding: 5px 11px; + } } } @@ -103,10 +105,10 @@ } .fa-chevron-left::before { - content: "\f053"; + content: '\f053'; } .fa-chevron-right::before { - content: "\f054"; + content: '\f054'; } .glyphicon-chevron-right { From 1f21b3e23b569817e143464f12514f8fc10344c4 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 2 May 2018 10:54:00 +0200 Subject: [PATCH 307/319] remove jest it.only to not skip important tests --- public/app/features/dashboard/specs/dashboard_model.jest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/specs/dashboard_model.jest.ts b/public/app/features/dashboard/specs/dashboard_model.jest.ts index feede679018..6f0b45c9ba8 100644 --- a/public/app/features/dashboard/specs/dashboard_model.jest.ts +++ b/public/app/features/dashboard/specs/dashboard_model.jest.ts @@ -405,7 +405,7 @@ describe('DashboardModel', function() { }); }); - it.only('should move panels below down', function() { + it('should move panels below down', function() { expect(dashboard.panels[4].gridPos).toMatchObject({ x: 0, y: 9, From 6dcb9e696d46095c5e963dc9c77bd137c8115550 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 2 May 2018 11:19:22 +0200 Subject: [PATCH 308/319] changelog: add notes about closing #11625 [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 866cb216757..b977edaadf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Minor * **Graph**: Show invisible highest value bucket in histogram [#11498](https://github.com/grafana/grafana/issues/11498) - +* **Dashboard**: Enable "Save As..." if user has edit permission [#11625](https://github.com/grafana/grafana/issues/11625) # 5.1.0 (2018-04-26) From 64283408ee610e6bd1832abf37a946ca25505c81 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 2 May 2018 12:43:25 +0300 Subject: [PATCH 309/319] scroll: fix scrolling on mobile Chrome (#11710) --- public/sass/pages/_dashboard.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index cf32522df7f..aeb9e28975b 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -45,6 +45,8 @@ div.flot-text { height: calc(100% - 27px); position: relative; overflow: hidden; + // Fixes scrolling on mobile devices + overflow-y: scroll; } .panel-title-container { From de0d409a2399bcecb998a5bdca066b51dc0a7eac Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 2 May 2018 14:06:46 +0200 Subject: [PATCH 310/319] Revert "Opportunities to unindent code (unindent)" --- pkg/api/common.go | 6 +- pkg/components/dynmap/dynmap.go | 54 ++++++++------- pkg/components/simplejson/simplejson.go | 6 +- pkg/middleware/dashboard_redirect.go | 31 ++++----- pkg/tsdb/mssql/mssql.go | 23 +++---- pkg/tsdb/mysql/mysql.go | 23 +++---- pkg/tsdb/postgres/postgres.go | 23 +++---- pkg/tsdb/sql_engine.go | 89 ++++++++++++------------- 8 files changed, 131 insertions(+), 124 deletions(-) diff --git a/pkg/api/common.go b/pkg/api/common.go index cd64c57dc92..97f41ff7c72 100644 --- a/pkg/api/common.go +++ b/pkg/api/common.go @@ -99,8 +99,10 @@ func Error(status int, message string, err error) *NormalResponse { data["message"] = message } - if err != nil && setting.Env != setting.PROD { - data["error"] = err.Error() + if err != nil { + if setting.Env != setting.PROD { + data["error"] = err.Error() + } } resp := JSON(status, data) diff --git a/pkg/components/dynmap/dynmap.go b/pkg/components/dynmap/dynmap.go index 6d3546f3bc5..96effb24332 100644 --- a/pkg/components/dynmap/dynmap.go +++ b/pkg/components/dynmap/dynmap.go @@ -639,24 +639,26 @@ func (v *Value) Object() (*Object, error) { valid = true } - if !valid { - return nil, ErrNotObject - } - obj := new(Object) - obj.valid = valid - - m := make(map[string]*Value) - if valid { - for key, element := range v.data.(map[string]interface{}) { - m[key] = &Value{element, true} + obj := new(Object) + obj.valid = valid + + m := make(map[string]*Value) + + if valid { + for key, element := range v.data.(map[string]interface{}) { + m[key] = &Value{element, true} + + } } + + obj.data = v.data + obj.m = m + + return obj, nil } - obj.data = v.data - obj.m = m - - return obj, nil + return nil, ErrNotObject } // Attempts to typecast the current value into an object arrau. @@ -676,19 +678,23 @@ func (v *Value) ObjectArray() ([]*Object, error) { // Unsure if this is a good way to use slices, it's probably not var slice []*Object - if !valid { - return nil, ErrNotObjectArray - } - for _, element := range v.data.([]interface{}) { - childValue := Value{element, true} - childObject, err := childValue.Object() + if valid { - if err != nil { - return nil, ErrNotObjectArray + for _, element := range v.data.([]interface{}) { + childValue := Value{element, true} + childObject, err := childValue.Object() + + if err != nil { + return nil, ErrNotObjectArray + } + slice = append(slice, childObject) } - slice = append(slice, childObject) + + return slice, nil } - return slice, nil + + return nil, ErrNotObjectArray + } // Attempts to typecast the current value into a string. diff --git a/pkg/components/simplejson/simplejson.go b/pkg/components/simplejson/simplejson.go index 15293b0cd93..85e2f955943 100644 --- a/pkg/components/simplejson/simplejson.go +++ b/pkg/components/simplejson/simplejson.go @@ -168,8 +168,10 @@ func (j *Json) GetPath(branch ...string) *Json { // js.Get("top_level").Get("array").GetIndex(1).Get("key").Int() func (j *Json) GetIndex(index int) *Json { a, err := j.Array() - if err == nil && len(a) > index { - return &Json{a[index]} + if err == nil { + if len(a) > index { + return &Json{a[index]} + } } return &Json{nil} } diff --git a/pkg/middleware/dashboard_redirect.go b/pkg/middleware/dashboard_redirect.go index 1111929c2f6..2edf04d543e 100644 --- a/pkg/middleware/dashboard_redirect.go +++ b/pkg/middleware/dashboard_redirect.go @@ -24,12 +24,12 @@ func RedirectFromLegacyDashboardURL() macaron.Handler { return func(c *m.ReqContext) { slug := c.Params("slug") - if slug == "" { - return - } - if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { - url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) - c.Redirect(url, 301) + if slug != "" { + if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { + url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) + c.Redirect(url, 301) + return + } } } } @@ -39,16 +39,17 @@ func RedirectFromLegacyDashboardSoloURL() macaron.Handler { slug := c.Params("slug") renderRequest := c.QueryBool("render") - if slug == "" { - return - } - if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { - if renderRequest && strings.Contains(url, setting.AppSubUrl) { - url = strings.Replace(url, setting.AppSubUrl, "", 1) + if slug != "" { + if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil { + if renderRequest && strings.Contains(url, setting.AppSubUrl) { + url = strings.Replace(url, setting.AppSubUrl, "", 1) + } + + url = strings.Replace(url, "/d/", "/d-solo/", 1) + url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) + c.Redirect(url, 301) + return } - url = strings.Replace(url, "/d/", "/d-solo/", 1) - url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery) - c.Redirect(url, 301) } } } diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index 221670f1bdb..eb71259b46b 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -298,19 +298,18 @@ func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. key := elem.Value.(string) result.Series = append(result.Series, pointsBySeries[key]) - if !fillMissing { - break - } - series := pointsBySeries[key] - // fill in values from last fetched value till interval end - intervalStart := series.Points[len(series.Points)-1][1].Float64 - intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) + if fillMissing { + series := pointsBySeries[key] + // fill in values from last fetched value till interval end + intervalStart := series.Points[len(series.Points)-1][1].Float64 + intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) - // align interval start - intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval - for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { - series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) - rowCount++ + // align interval start + intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval + for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { + series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) + rowCount++ + } } } diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 57986eb7c04..7eceaffdb09 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -309,19 +309,18 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. key := elem.Value.(string) result.Series = append(result.Series, pointsBySeries[key]) - if !fillMissing { - break - } - series := pointsBySeries[key] - // fill in values from last fetched value till interval end - intervalStart := series.Points[len(series.Points)-1][1].Float64 - intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) + if fillMissing { + series := pointsBySeries[key] + // fill in values from last fetched value till interval end + intervalStart := series.Points[len(series.Points)-1][1].Float64 + intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) - // align interval start - intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval - for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { - series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) - rowCount++ + // align interval start + intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval + for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { + series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) + rowCount++ + } } } diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go index f66c09b5724..fdf09216e51 100644 --- a/pkg/tsdb/postgres/postgres.go +++ b/pkg/tsdb/postgres/postgres.go @@ -289,19 +289,18 @@ func (e PostgresQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *co key := elem.Value.(string) result.Series = append(result.Series, pointsBySeries[key]) - if !fillMissing { - break - } - series := pointsBySeries[key] - // fill in values from last fetched value till interval end - intervalStart := series.Points[len(series.Points)-1][1].Float64 - intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) + if fillMissing { + series := pointsBySeries[key] + // fill in values from last fetched value till interval end + intervalStart := series.Points[len(series.Points)-1][1].Float64 + intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) - // align interval start - intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval - for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { - series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) - rowCount++ + // align interval start + intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval + for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { + series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)}) + rowCount++ + } } } diff --git a/pkg/tsdb/sql_engine.go b/pkg/tsdb/sql_engine.go index ecf46ac689d..274e5b05dc1 100644 --- a/pkg/tsdb/sql_engine.go +++ b/pkg/tsdb/sql_engine.go @@ -141,51 +141,50 @@ func (e *DefaultSqlEngine) Query( // ConvertSqlTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds // to make native datetime types and epoch dates work in annotation and table queries. func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) { - if timeIndex < 0 { - return - } - switch value := values[timeIndex].(type) { - case time.Time: - values[timeIndex] = EpochPrecisionToMs(float64(value.UnixNano())) - case *time.Time: - if value != nil { - values[timeIndex] = EpochPrecisionToMs(float64((*value).UnixNano())) - } - case int64: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *int64: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case uint64: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *uint64: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case int32: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *int32: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case uint32: - values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) - case *uint32: - if value != nil { - values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) - } - case float64: - values[timeIndex] = EpochPrecisionToMs(value) - case *float64: - if value != nil { - values[timeIndex] = EpochPrecisionToMs(*value) - } - case float32: - values[timeIndex] = EpochPrecisionToMs(float64(value)) - case *float32: - if value != nil { - values[timeIndex] = EpochPrecisionToMs(float64(*value)) + if timeIndex >= 0 { + switch value := values[timeIndex].(type) { + case time.Time: + values[timeIndex] = EpochPrecisionToMs(float64(value.UnixNano())) + case *time.Time: + if value != nil { + values[timeIndex] = EpochPrecisionToMs(float64((*value).UnixNano())) + } + case int64: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *int64: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case uint64: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *uint64: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case int32: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *int32: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case uint32: + values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) + case *uint32: + if value != nil { + values[timeIndex] = int64(EpochPrecisionToMs(float64(*value))) + } + case float64: + values[timeIndex] = EpochPrecisionToMs(value) + case *float64: + if value != nil { + values[timeIndex] = EpochPrecisionToMs(*value) + } + case float32: + values[timeIndex] = EpochPrecisionToMs(float64(value)) + case *float32: + if value != nil { + values[timeIndex] = EpochPrecisionToMs(float64(*value)) + } } } } From 14bb7832af9a43f58cc0bf53eb7e4abe9bf44085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 May 2018 19:54:07 +0200 Subject: [PATCH 311/319] Metrics package now follows new service interface & registration (#11787) * refactoring: metrics package now follows new service interface & registration * fix: minor fix, make sure metrics service is imported, by grafana-server --- pkg/cmd/grafana-server/server.go | 3 +- pkg/metrics/init.go | 38 ----------------- pkg/metrics/metrics.go | 24 +---------- pkg/metrics/service.go | 71 ++++++++++++++++++++++++++++++++ pkg/metrics/settings.go | 58 +++++++++++--------------- 5 files changed, 96 insertions(+), 98 deletions(-) delete mode 100644 pkg/metrics/init.go create mode 100644 pkg/metrics/service.go diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 30bb0b2003a..f20195b563a 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -24,7 +24,6 @@ import ( "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/login" - "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" @@ -33,6 +32,7 @@ import ( // self registering services _ "github.com/grafana/grafana/pkg/extensions" + _ "github.com/grafana/grafana/pkg/metrics" _ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" @@ -72,7 +72,6 @@ func (g *GrafanaServerImpl) Start() error { sqlstore.NewEngine() // TODO: this should return an error sqlstore.EnsureAdminUser() - metrics.Init(g.cfg.Raw) login.Init() social.NewOAuthService() diff --git a/pkg/metrics/init.go b/pkg/metrics/init.go deleted file mode 100644 index 833b148d319..00000000000 --- a/pkg/metrics/init.go +++ /dev/null @@ -1,38 +0,0 @@ -package metrics - -import ( - "context" - - ini "gopkg.in/ini.v1" - - "github.com/grafana/grafana/pkg/log" - "github.com/grafana/grafana/pkg/metrics/graphitebridge" -) - -var metricsLogger log.Logger = log.New("metrics") - -type logWrapper struct { - logger log.Logger -} - -func (lw *logWrapper) Println(v ...interface{}) { - lw.logger.Info("graphite metric bridge", v...) -} - -func Init(file *ini.File) { - cfg := ReadSettings(file) - internalInit(cfg) -} - -func internalInit(settings *MetricSettings) { - initMetricVars(settings) - - if settings.GraphiteBridgeConfig != nil { - bridge, err := graphitebridge.NewBridge(settings.GraphiteBridgeConfig) - if err != nil { - metricsLogger.Error("failed to create graphite bridge", "error", err) - } else { - go bridge.Run(context.Background()) - } - } -} diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index e3640378f7e..83505826910 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -279,7 +279,7 @@ func init() { }, []string{"version"}) } -func initMetricVars(settings *MetricSettings) { +func initMetricVars() { prometheus.MustRegister( M_Instance_Start, M_Page_Status, @@ -316,28 +316,6 @@ func initMetricVars(settings *MetricSettings) { M_StatTotal_Playlists, M_Grafana_Version) - go instrumentationLoop(settings) -} - -func instrumentationLoop(settings *MetricSettings) chan struct{} { - M_Instance_Start.Inc() - - // set the total stats gauges before we publishing metrics - updateTotalStats() - - onceEveryDayTick := time.NewTicker(time.Hour * 24) - everyMinuteTicker := time.NewTicker(time.Minute) - defer onceEveryDayTick.Stop() - defer everyMinuteTicker.Stop() - - for { - select { - case <-onceEveryDayTick.C: - sendUsageStats() - case <-everyMinuteTicker.C: - updateTotalStats() - } - } } func updateTotalStats() { diff --git a/pkg/metrics/service.go b/pkg/metrics/service.go new file mode 100644 index 00000000000..ec38e0acfec --- /dev/null +++ b/pkg/metrics/service.go @@ -0,0 +1,71 @@ +package metrics + +import ( + "context" + "time" + + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/metrics/graphitebridge" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" +) + +var metricsLogger log.Logger = log.New("metrics") + +type logWrapper struct { + logger log.Logger +} + +func (lw *logWrapper) Println(v ...interface{}) { + lw.logger.Info("graphite metric bridge", v...) +} + +func init() { + registry.RegisterService(&InternalMetricsService{}) + initMetricVars() +} + +type InternalMetricsService struct { + Cfg *setting.Cfg `inject:""` + + enabled bool + intervalSeconds int64 + graphiteCfg *graphitebridge.Config +} + +func (im *InternalMetricsService) Init() error { + return im.readSettings() +} + +func (im *InternalMetricsService) Run(ctx context.Context) error { + // Start Graphite Bridge + if im.graphiteCfg != nil { + bridge, err := graphitebridge.NewBridge(im.graphiteCfg) + if err != nil { + metricsLogger.Error("failed to create graphite bridge", "error", err) + } else { + go bridge.Run(ctx) + } + } + + M_Instance_Start.Inc() + + // set the total stats gauges before we publishing metrics + updateTotalStats() + + onceEveryDayTick := time.NewTicker(time.Hour * 24) + everyMinuteTicker := time.NewTicker(time.Minute) + defer onceEveryDayTick.Stop() + defer everyMinuteTicker.Stop() + + for { + select { + case <-onceEveryDayTick.C: + sendUsageStats() + case <-everyMinuteTicker.C: + updateTotalStats() + case <-ctx.Done(): + return ctx.Err() + } + } +} diff --git a/pkg/metrics/settings.go b/pkg/metrics/settings.go index c21e7279b7e..58b84a7192f 100644 --- a/pkg/metrics/settings.go +++ b/pkg/metrics/settings.go @@ -1,67 +1,53 @@ package metrics import ( + "fmt" "strings" "time" "github.com/grafana/grafana/pkg/metrics/graphitebridge" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" - ini "gopkg.in/ini.v1" ) -type MetricSettings struct { - Enabled bool - IntervalSeconds int64 - GraphiteBridgeConfig *graphitebridge.Config -} - -func ReadSettings(file *ini.File) *MetricSettings { - var settings = &MetricSettings{ - Enabled: false, +func (im *InternalMetricsService) readSettings() error { + var section, err = im.Cfg.Raw.GetSection("metrics") + if err != nil { + return fmt.Errorf("Unable to find metrics config section %v", err) } - var section, err = file.GetSection("metrics") - if err != nil { - metricsLogger.Crit("Unable to find metrics config section", "error", err) + im.enabled = section.Key("enabled").MustBool(false) + im.intervalSeconds = section.Key("interval_seconds").MustInt64(10) + + if !im.enabled { return nil } - settings.Enabled = section.Key("enabled").MustBool(false) - settings.IntervalSeconds = section.Key("interval_seconds").MustInt64(10) - - if !settings.Enabled { - return settings + if err := im.parseGraphiteSettings(); err != nil { + return fmt.Errorf("Unable to parse metrics graphite section, %v", err) } - cfg, err := parseGraphiteSettings(settings, file) - if err != nil { - metricsLogger.Crit("Unable to parse metrics graphite section", "error", err) - return nil - } - - settings.GraphiteBridgeConfig = cfg - - return settings + return nil } -func parseGraphiteSettings(settings *MetricSettings, file *ini.File) (*graphitebridge.Config, error) { - graphiteSection, err := setting.Raw.GetSection("metrics.graphite") +func (im *InternalMetricsService) parseGraphiteSettings() error { + graphiteSection, err := im.Cfg.Raw.GetSection("metrics.graphite") + if err != nil { - return nil, nil + return nil } address := graphiteSection.Key("address").String() if address == "" { - return nil, nil + return nil } - cfg := &graphitebridge.Config{ + bridgeCfg := &graphitebridge.Config{ URL: address, Prefix: graphiteSection.Key("prefix").MustString("prod.grafana.%(instance_name)s"), CountersAsDelta: true, Gatherer: prometheus.DefaultGatherer, - Interval: time.Duration(settings.IntervalSeconds) * time.Second, + Interval: time.Duration(im.intervalSeconds) * time.Second, Timeout: 10 * time.Second, Logger: &logWrapper{logger: metricsLogger}, ErrorHandling: graphitebridge.ContinueOnError, @@ -74,6 +60,8 @@ func parseGraphiteSettings(settings *MetricSettings, file *ini.File) (*graphiteb prefix = "prod.grafana.%(instance_name)s." } - cfg.Prefix = strings.Replace(prefix, "%(instance_name)s", safeInstanceName, -1) - return cfg, nil + bridgeCfg.Prefix = strings.Replace(prefix, "%(instance_name)s", safeInstanceName, -1) + + im.graphiteCfg = bridgeCfg + return nil } From 764fa15e2415c9394d1064dc2256de33df691c12 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Wed, 2 May 2018 23:03:37 +0200 Subject: [PATCH 312/319] dont shadow format passed in as function parameter --- public/app/features/templating/template_srv.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index f6274a80165..e7c1dc7f102 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -179,16 +179,16 @@ export class TemplateSrv { return target; } - var variable, systemValue, value; + var variable, systemValue, value, fmt; this.regex.lastIndex = 0; return target.replace(this.regex, (match, var1, var2, fmt2, var3, fmt3) => { variable = this.index[var1 || var2 || var3]; - format = fmt2 || fmt3 || format; + fmt = fmt2 || fmt3 || format; if (scopedVars) { value = scopedVars[var1 || var2 || var3]; if (value) { - return this.formatValue(value.value, format, variable); + return this.formatValue(value.value, fmt, variable); } } @@ -198,7 +198,7 @@ export class TemplateSrv { systemValue = this.grafanaVariables[variable.current.value]; if (systemValue) { - return this.formatValue(systemValue, format, variable); + return this.formatValue(systemValue, fmt, variable); } value = variable.current.value; @@ -210,7 +210,7 @@ export class TemplateSrv { } } - var res = this.formatValue(value, format, variable); + var res = this.formatValue(value, fmt, variable); return res; }); } From 83d599670da3832982553327003dbb5f606e6bfe Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 3 May 2018 11:54:02 +0300 Subject: [PATCH 313/319] scroll: remove firefox scrollbars --- public/sass/pages/_dashboard.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index aeb9e28975b..471e90ed9cf 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -44,9 +44,8 @@ div.flot-text { padding: $panel-padding; height: calc(100% - 27px); position: relative; - overflow: hidden; // Fixes scrolling on mobile devices - overflow-y: scroll; + overflow: auto; } .panel-title-container { From d518ed5330e85d7ac192f52c6432d6f65024fbd8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 3 May 2018 15:46:21 +0200 Subject: [PATCH 314/319] changelog: add notes for ##11754, #11758, #11710 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b977edaadf0..3772812f254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ * **Graph**: Show invisible highest value bucket in histogram [#11498](https://github.com/grafana/grafana/issues/11498) * **Dashboard**: Enable "Save As..." if user has edit permission [#11625](https://github.com/grafana/grafana/issues/11625) + +# 5.1.1 (unreleased) + +* **LDAP**: LDAP login with MariaDB/MySQL database and dn>100 chars not possible [#11754](https://github.com/grafana/grafana/issues/11754) +* **Build**: AppVeyor Windows build missing version and commit info [#11758](https://github.com/grafana/grafana/issues/11758) +* **Scroll**: Scroll can't start in graphs on Chrome mobile [#11710](https://github.com/grafana/grafana/issues/11710) + # 5.1.0 (2018-04-26) * **Folders**: Default permissions on folder are not shown as inherited in its dashboards [#11668](https://github.com/grafana/grafana/issues/11668) From 8a9da4ba66f472cee8f4c10791eda18da63a3017 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 3 May 2018 18:42:14 +0200 Subject: [PATCH 315/319] changelog: notes about closing #11690 [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3772812f254..f0c59ccc310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * **Graph**: Show invisible highest value bucket in histogram [#11498](https://github.com/grafana/grafana/issues/11498) * **Dashboard**: Enable "Save As..." if user has edit permission [#11625](https://github.com/grafana/grafana/issues/11625) - +* **Prometheus**: Table columns order now changes when rearrange queries [#11690](https://github.com/grafana/grafana/issues/11690), thx [@mtanda](https://github.com/mtanda) # 5.1.1 (unreleased) From a806f542c64a61cf41d7f9fc0620b6a69100100e Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Thu, 3 May 2018 18:42:58 +0200 Subject: [PATCH 316/319] test if default variable interpolation is effective when no specific format is specified --- .../app/features/templating/specs/template_srv.jest.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/public/app/features/templating/specs/template_srv.jest.ts b/public/app/features/templating/specs/template_srv.jest.ts index 5290a883c48..59915776b4f 100644 --- a/public/app/features/templating/specs/template_srv.jest.ts +++ b/public/app/features/templating/specs/template_srv.jest.ts @@ -136,6 +136,11 @@ describe('templateSrv', function() { var target = _templateSrv.replace('this=${test:pipe}', {}); expect(target).toBe('this=value1|value2'); }); + + it('should replace ${test:pipe} with piped value and $test with globbed value', function() { + var target = _templateSrv.replace('${test:pipe},$test', {}, 'glob'); + expect(target).toBe('value1|value2,{value1,value2}'); + }); }); describe('variable with all option', function() { @@ -164,6 +169,11 @@ describe('templateSrv', function() { var target = _templateSrv.replace('this.${test:glob}.filters', {}); expect(target).toBe('this.{value1,value2}.filters'); }); + + it('should replace ${test:pipe} with piped value and $test with globbed value', function() { + var target = _templateSrv.replace('${test:pipe},$test', {}, 'glob'); + expect(target).toBe('value1|value2,{value1,value2}'); + }); }); describe('variable with all option and custom value', function() { From 4d2e6b4a34a0f675131de7776c30fe2b2b07fa3a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 3 May 2018 19:13:57 +0200 Subject: [PATCH 317/319] changelog: add notes about closing #11800 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c59ccc310..5dd958e237a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * **Graph**: Show invisible highest value bucket in histogram [#11498](https://github.com/grafana/grafana/issues/11498) * **Dashboard**: Enable "Save As..." if user has edit permission [#11625](https://github.com/grafana/grafana/issues/11625) * **Prometheus**: Table columns order now changes when rearrange queries [#11690](https://github.com/grafana/grafana/issues/11690), thx [@mtanda](https://github.com/mtanda) +* **Variables**: Fix variable interpolation when using multiple formatting types [#11800](https://github.com/grafana/grafana/issues/11800), thx [@svenklemm](https://github.com/svenklemm) # 5.1.1 (unreleased) From c897485958ff6932bb7277ed85913fad8a1172c0 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 4 May 2018 10:30:42 +0200 Subject: [PATCH 318/319] fixed text color in light theme --- public/sass/components/_timepicker.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index 9b71e8e7c05..e4d8f4555e0 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -77,7 +77,7 @@ border: none; color: $text-color; &.active span { - color: $blue; + color: $query-blue; font-weight: bold; } .text-info { @@ -88,6 +88,12 @@ font-size: $font-size-sm; padding: 5px 11px; } + &:hover { + color: $text-color-strong; + } + &[disabled] { + color: $text-color; + } } } From 8523b1e4105568f63a484f8c7d627e074b300bfa Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 4 May 2018 15:03:30 +0200 Subject: [PATCH 319/319] changelog: add notes about closing #11616 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd958e237a..1ae35bed3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * **Dashboard**: Enable "Save As..." if user has edit permission [#11625](https://github.com/grafana/grafana/issues/11625) * **Prometheus**: Table columns order now changes when rearrange queries [#11690](https://github.com/grafana/grafana/issues/11690), thx [@mtanda](https://github.com/mtanda) * **Variables**: Fix variable interpolation when using multiple formatting types [#11800](https://github.com/grafana/grafana/issues/11800), thx [@svenklemm](https://github.com/svenklemm) +* **Dashboard**: Fix date selector styling for dark/light theme in time picker control [#11616](https://github.com/grafana/grafana/issues/11616) # 5.1.1 (unreleased)