From c346aca26d9183fe3245db5be1c81fcf2c340f8e Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 11 Apr 2017 16:30:20 -0700 Subject: [PATCH 01/96] allow setting the database --- .../plugins/datasource/influxdb/datasource.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 98c7ba87bd2..ddce4d8e8f9 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -157,7 +157,7 @@ export default class InfluxDatasource { return false; }; - metricFindQuery(query) { + metricFindQuery(query: string, db?: string) { var interpolated = this.templateSrv.replace(query, null, 'regex'); return this._seriesQuery(interpolated) @@ -176,10 +176,10 @@ export default class InfluxDatasource { return this.metricFindQuery(query); } - _seriesQuery(query) { + _seriesQuery(query: string, db?: string) { if (!query) { return this.$q.when({results: []}); } - return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'}); + return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'}, db); } serializeParams(params) { @@ -207,19 +207,19 @@ export default class InfluxDatasource { }); } - _influxRequest(method, url, data) { - var self = this; - - var currentUrl = self.urls.shift(); - self.urls.push(currentUrl); + _influxRequest(method: string, url: string, data: any, db?: string) { + var currentUrl = this.urls.shift(); + this.urls.push(currentUrl); var params: any = { - u: self.username, - p: self.password, + u: this.username, + p: this.password, }; - if (self.database) { - params.db = self.database; + if (db) { + params.db = db; + } else if (this.database) { + params.db = this.database; } if (method === 'GET') { @@ -241,8 +241,8 @@ export default class InfluxDatasource { if (this.basicAuth || this.withCredentials) { options.withCredentials = true; } - if (self.basicAuth) { - options.headers.Authorization = self.basicAuth; + if (this.basicAuth) { + options.headers.Authorization = this.basicAuth; } return this.backendSrv.datasourceRequest(options).then(result => { From 7009184d6eec4f1612ca036ad32f68b33cdafa3d Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 12 Apr 2017 09:02:06 -0700 Subject: [PATCH 02/96] pass database parameter in the options --- .../plugins/datasource/influxdb/datasource.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index ddce4d8e8f9..37a28d45142 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -157,10 +157,10 @@ export default class InfluxDatasource { return false; }; - metricFindQuery(query: string, db?: string) { + metricFindQuery(query: string, options?: any) { var interpolated = this.templateSrv.replace(query, null, 'regex'); - return this._seriesQuery(interpolated) + return this._seriesQuery(interpolated, options) .then(_.curry(this.responseParser.parse)(query)); } @@ -176,10 +176,10 @@ export default class InfluxDatasource { return this.metricFindQuery(query); } - _seriesQuery(query: string, db?: string) { + _seriesQuery(query: string, options?: any) { if (!query) { return this.$q.when({results: []}); } - return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'}, db); + return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'}, options); } serializeParams(params) { @@ -207,7 +207,7 @@ export default class InfluxDatasource { }); } - _influxRequest(method: string, url: string, data: any, db?: string) { + _influxRequest(method: string, url: string, data: any, options?: any) { var currentUrl = this.urls.shift(); this.urls.push(currentUrl); @@ -216,8 +216,8 @@ export default class InfluxDatasource { p: this.password, }; - if (db) { - params.db = db; + if (options && options.database) { + params.db = options.database; } else if (this.database) { params.db = this.database; } @@ -227,7 +227,7 @@ export default class InfluxDatasource { data = null; } - var options: any = { + var req: any = { method: method, url: currentUrl + url, params: params, @@ -237,15 +237,15 @@ export default class InfluxDatasource { paramSerializer: this.serializeParams, }; - options.headers = options.headers || {}; + req.headers = req.headers || {}; if (this.basicAuth || this.withCredentials) { - options.withCredentials = true; + req.withCredentials = true; } if (this.basicAuth) { - options.headers.Authorization = this.basicAuth; + req.headers.Authorization = this.basicAuth; } - return this.backendSrv.datasourceRequest(options).then(result => { + return this.backendSrv.datasourceRequest(req).then(result => { return result.data; }, function(err) { if (err.status !== 0 || err.status >= 300) { From 16819da08d9a353fc6599d72836d36019d8ca5ef Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 18 Apr 2017 09:27:25 -0700 Subject: [PATCH 03/96] pass the options along with a _seriesQuery --- public/app/plugins/datasource/influxdb/datasource.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 37a28d45142..929c673433b 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -85,7 +85,7 @@ export default class InfluxDatasource { // replace templated variables allQueries = this.templateSrv.replace(allQueries, scopedVars); - return this._seriesQuery(allQueries).then((data): any => { + return this._seriesQuery(allQueries, options).then((data): any => { if (!data || !data.results) { return []; } @@ -131,7 +131,7 @@ export default class InfluxDatasource { var query = options.annotation.query.replace('$timeFilter', timeFilter); query = this.templateSrv.replace(query, null, 'regex'); - return this._seriesQuery(query).then(data => { + return this._seriesQuery(query, options).then(data => { if (!data || !data.results || !data.results[0]) { throw { message: 'No results in response from InfluxDB' }; } @@ -167,13 +167,13 @@ export default class InfluxDatasource { getTagKeys(options) { var queryBuilder = new InfluxQueryBuilder({measurement: '', tags: []}, this.database); var query = queryBuilder.buildExploreQuery('TAG_KEYS'); - return this.metricFindQuery(query); + return this.metricFindQuery(query, options); } getTagValues(options) { var queryBuilder = new InfluxQueryBuilder({measurement: '', tags: []}, this.database); var query = queryBuilder.buildExploreQuery('TAG_VALUES', options.key); - return this.metricFindQuery(query); + return this.metricFindQuery(query, options); } _seriesQuery(query: string, options?: any) { From 15e84a1c692f5daf4984da15aa2dad696b257fad Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 26 Apr 2017 15:41:10 -0700 Subject: [PATCH 04/96] use targets[0] as the options --- public/app/plugins/datasource/influxdb/datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index ec472898583..ac18992b16d 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -85,7 +85,7 @@ export default class InfluxDatasource { // replace templated variables allQueries = this.templateSrv.replace(allQueries, scopedVars); - return this._seriesQuery(allQueries, options).then((data): any => { + return this._seriesQuery(allQueries, targets[0]).then((data): any => { if (!data || !data.results) { return []; } From 26e62b4d0690b19962e23a56840ad1cad6d226e3 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 1 May 2017 20:28:56 -0700 Subject: [PATCH 05/96] use the original options parameter --- public/app/plugins/datasource/influxdb/datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index ac18992b16d..ec472898583 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -85,7 +85,7 @@ export default class InfluxDatasource { // replace templated variables allQueries = this.templateSrv.replace(allQueries, scopedVars); - return this._seriesQuery(allQueries, targets[0]).then((data): any => { + return this._seriesQuery(allQueries, options).then((data): any => { if (!data || !data.results) { return []; } From 967efea520d61bc6a9cf2d0cfa29c90ce018a658 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 22 Sep 2017 13:28:53 +0200 Subject: [PATCH 06/96] fix merge issue --- public/app/plugins/datasource/influxdb/datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 9e568866579..bab2d597a67 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -211,7 +211,7 @@ export default class InfluxDatasource { if (this.username) { params.u = this.username; - params.u = this.password; + params.p = this.password; } if (options && options.database) { From 4440133f4dd9b4ca19ea540815683ff0428a7944 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 18 Oct 2017 23:33:10 +0200 Subject: [PATCH 07/96] Add a setting to allow DB queries --- pkg/api/pluginproxy/ds_proxy.go | 4 +++- public/app/plugins/datasource/influxdb/datasource.ts | 5 +++++ .../app/plugins/datasource/influxdb/partials/config.html | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index faac8c03c62..72309c5c855 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -166,7 +166,9 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) { func (proxy *DataSourceProxy) validateRequest() error { if proxy.ds.Type == m.DS_INFLUXDB { if proxy.ctx.Query("db") != proxy.ds.Database { - return errors.New("Datasource is not configured to allow this database") + if(!proxy.ds.JsonData.Get("allowDatabaseQuery").MustBool(false)) { + return errors.New("Datasource is not configured to allow this database"); + } } } diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 7b53d0e1d97..80294396395 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -17,6 +17,7 @@ export default class InfluxDatasource { basicAuth: any; withCredentials: any; interval: any; + allowDatabaseQuery: boolean; supportAnnotations: boolean; supportMetrics: boolean; responseParser: any; @@ -35,6 +36,7 @@ export default class InfluxDatasource { this.basicAuth = instanceSettings.basicAuth; this.withCredentials = instanceSettings.withCredentials; this.interval = (instanceSettings.jsonData || {}).timeInterval; + this.allowDatabaseQuery = (instanceSettings.jsonData || {}).allowDatabaseQuery === true; this.supportAnnotations = true; this.supportMetrics = true; this.responseParser = new ResponseParser(); @@ -214,6 +216,9 @@ export default class InfluxDatasource { if (options && options.database) { params.db = options.database; + if (params.db !== this.database && !this.allowDatabaseQuery) { + return this.$q.reject( { message: 'This datasource does not allow changing database' } ); + } } else if (this.database) { params.db = this.database; } diff --git a/public/app/plugins/datasource/influxdb/partials/config.html b/public/app/plugins/datasource/influxdb/partials/config.html index 9cb6f5ba749..4b861083928 100644 --- a/public/app/plugins/datasource/influxdb/partials/config.html +++ b/public/app/plugins/datasource/influxdb/partials/config.html @@ -23,10 +23,16 @@ + +
- Min time interval + Min time interval A lower limit for the auto group by time interval. Recommended to be set to write frequency, From fb9c714a9a0e68b8572b94852d880afd7b7919a2 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 18 Oct 2017 23:49:33 +0200 Subject: [PATCH 08/96] run go fmt --- pkg/api/pluginproxy/ds_proxy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 72309c5c855..200a1a02bcc 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -166,8 +166,8 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) { func (proxy *DataSourceProxy) validateRequest() error { if proxy.ds.Type == m.DS_INFLUXDB { if proxy.ctx.Query("db") != proxy.ds.Database { - if(!proxy.ds.JsonData.Get("allowDatabaseQuery").MustBool(false)) { - return errors.New("Datasource is not configured to allow this database"); + if !proxy.ds.JsonData.Get("allowDatabaseQuery").MustBool(false) { + return errors.New("Datasource is not configured to allow this database") } } } 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 09/96] 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 10/96] 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 11/96] 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 e037ef21f790f6ea4aea3c3e0ee29e66375e636c Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 26 Feb 2018 10:21:24 +0100 Subject: [PATCH 12/96] 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 cae9c28f7031ff686a78b5c0a910c9532d375b8b Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 15 Mar 2018 00:03:47 +0100 Subject: [PATCH 14/96] fix lint problems --- public/sass/_variables.scss | 29 +- public/sass/base/_forms.scss | 44 +- public/sass/base/_grafana_icons.scss | 123 +- public/sass/base/_normalize.scss | 20 +- public/sass/base/_reboot.scss | 8 +- public/sass/base/font-awesome/_core.scss | 3 +- public/sass/base/font-awesome/_mixins.scss | 7 +- public/sass/base/font-awesome/_path.scss | 19 +- public/sass/base/font-awesome/_variables.scss | 1576 ++++++++--------- public/sass/components/_drop.scss | 12 +- public/sass/components/_filter-list.scss | 6 +- public/sass/components/_footer.scss | 4 +- public/sass/components/_json_explorer.scss | 6 +- public/sass/components/_jsontree.scss | 4 +- .../components/_panel_gettingstarted.scss | 4 +- public/sass/components/_row.scss | 2 +- public/sass/components/_shortcuts.scss | 2 +- public/sass/components/_switch.scss | 8 +- public/sass/components/_tabs.scss | 9 +- public/sass/components/_timepicker.scss | 4 +- public/sass/grafana.dark.scss | 6 +- public/sass/mixins/_drop_element.scss | 52 +- public/sass/mixins/_forms.scss | 3 +- public/sass/mixins/_mixins.scss | 63 +- public/sass/pages/_login.scss | 2 +- public/sass/pages/_playlist.scss | 4 +- public/sass/utils/_validation.scss | 2 +- public/test/core/utils/version_specs.ts | 36 +- public/test/jest-shim.ts | 3 +- public/test/mocks/backend_srv.ts | 5 +- public/test/specs/helpers.ts | 22 +- .../bradfitz/gomemcache/memcache/memcache.go | 2 +- .../github.com/hashicorp/go-plugin/client.go | 2 +- .../hashicorp/go-plugin/rpc_client.go | 2 +- .../hashicorp/go-plugin/rpc_server.go | 2 +- .../sergi/go-diff/diffmatchpatch/diff.go | 26 +- .../sergi/go-diff/diffmatchpatch/patch.go | 4 +- vendor/golang.org/x/net/http2/transport.go | 4 +- vendor/golang.org/x/text/language/gen.go | 2 +- vendor/golang.org/x/text/language/lookup.go | 56 +- vendor/golang.org/x/text/language/tables.go | 6 +- vendor/golang.org/x/text/unicode/cldr/cldr.go | 2 +- .../golang.org/x/text/unicode/cldr/resolve.go | 4 +- .../golang.org/x/text/unicode/cldr/slice.go | 2 +- .../x/text/unicode/norm/maketables.go | 2 +- vendor/gopkg.in/macaron.v1/context.go | 2 +- 46 files changed, 1144 insertions(+), 1062 deletions(-) diff --git a/public/sass/_variables.scss b/public/sass/_variables.scss index 5a5f495470d..f46cacb0dd1 100644 --- a/public/sass/_variables.scss +++ b/public/sass/_variables.scss @@ -53,9 +53,9 @@ $enable-flex: true; // Typography // ------------------------- -$font-family-sans-serif: 'Roboto', Helvetica, Arial, sans-serif; -$font-family-serif: Georgia, 'Times New Roman', Times, serif; -$font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace; +$font-family-sans-serif: "Roboto", Helvetica, Arial, sans-serif; +$font-family-serif: Georgia, "Times New Roman", Times, serif; +$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-family-base: $font-family-sans-serif !default; $font-size-root: 14px !default; @@ -90,7 +90,7 @@ $lead-font-size: 1.25rem !default; $lead-font-weight: 300 !default; $headings-margin-bottom: ($spacer / 2) !default; -$headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; +$headings-font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; $headings-font-weight: 400 !default; $headings-line-height: 1.1 !default; @@ -152,9 +152,16 @@ $input-padding-y-sm: 4px !default; $input-padding-x-lg: 20px !default; $input-padding-y-lg: 10px !default; -$input-height: (($font-size-base * $line-height-base) + ($input-padding-y * 2)) !default; -$input-height-lg: ( ($font-size-lg * $line-height-lg) + ($input-padding-y-lg * 2)) !default; -$input-height-sm: ( ($font-size-sm * $line-height-sm) + ($input-padding-y-sm * 2)) !default; +$input-height: (($font-size-base * $line-height-base) + ($input-padding-y * 2)) + !default; +$input-height-lg: ( + ($font-size-lg * $line-height-lg) + ($input-padding-y-lg * 2) + ) + !default; +$input-height-sm: ( + ($font-size-sm * $line-height-sm) + ($input-padding-y-sm * 2) + ) + !default; $form-group-margin-bottom: $spacer-y !default; $gf-form-margin: 0.2rem; @@ -214,9 +221,9 @@ $panel-padding: 0px 10px 5px 10px; $tabs-padding: 10px 15px 9px; $external-services: ( - github: (bgColor: #464646, borderColor: #393939, icon: ''), - google: (bgColor: #e84d3c, borderColor: #b83e31, icon: ''), - grafanacom: (bgColor: inherit, borderColor: #393939, icon: ''), - oauth: (bgColor: inherit, borderColor: #393939, icon: '') + github: (bgColor: #464646, borderColor: #393939, icon: ""), + google: (bgColor: #e84d3c, borderColor: #b83e31, icon: ""), + grafanacom: (bgColor: inherit, borderColor: #393939, icon: ""), + oauth: (bgColor: inherit, borderColor: #393939, icon: "") ) !default; diff --git a/public/sass/base/_forms.scss b/public/sass/base/_forms.scss index 239bb8d4e1e..3197eb57991 100644 --- a/public/sass/base/_forms.scss +++ b/public/sass/base/_forms.scss @@ -58,19 +58,19 @@ textarea { } // Reset width of input images, buttons, radios, checkboxes -input[type='file'], -input[type='image'], -input[type='submit'], -input[type='reset'], -input[type='button'], -input[type='radio'], -input[type='checkbox'] { +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { width: auto; // Override of generic input selector } // Set the height of select and file controls to match text inputs select, -input[type='file'] { +input[type="file"] { height: $input-height; /* In IE7, the height of the select element cannot be changed by height, only font-size */ line-height: $input-height; } @@ -90,19 +90,19 @@ select[size] { // Focus for select, file, radio, and checkbox select:focus, -input[type='file']:focus, -input[type='radio']:focus, -input[type='checkbox']:focus { +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { @include tab-focus(); } // not a big fan of number fields -input[type='number']::-webkit-outer-spin-button, -input[type='number']::-webkit-inner-spin-button { +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } -input[type='number'] { +input[type="number"] { -moz-appearance: textfield; } // Placeholder @@ -155,15 +155,15 @@ textarea[readonly] { } // Explicitly reset the colors here -input[type='radio'][disabled], -input[type='checkbox'][disabled], -input[type='radio'][readonly], -input[type='checkbox'][readonly] { +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { cursor: $cursor-disabled; background-color: transparent; } -input[type='text'].input-fluid { +input[type="text"].input-fluid { width: 100%; box-sizing: border-box; padding: 10px; @@ -172,7 +172,7 @@ input[type='text'].input-fluid { height: 100%; } -input[type='checkbox'].cr1 { +input[type="checkbox"].cr1 { display: none; } @@ -194,7 +194,7 @@ label.cr1 { cursor: pointer; } -input[type='checkbox'].cr1:checked + label { +input[type="checkbox"].cr1:checked + label { background: url($checkboxImageUrl) 0px -18px no-repeat; } @@ -203,7 +203,7 @@ input[type='checkbox'].cr1:checked + label { display: block; overflow: hidden; padding-right: 10px; - input[type='text'] { + input[type="text"] { width: 100%; padding: 5px 6px; height: 100%; diff --git a/public/sass/base/_grafana_icons.scss b/public/sass/base/_grafana_icons.scss index 73574ff9efc..55e57f6d087 100644 --- a/public/sass/base/_grafana_icons.scss +++ b/public/sass/base/_grafana_icons.scss @@ -1,17 +1,18 @@ @font-face { - font-family: 'grafana-icons'; - src: url('../fonts/grafana-icons.eot?okx5td'); - src: url('../fonts/grafana-icons.eot?okx5td#iefix') format('embedded-opentype'), - url('../fonts/grafana-icons.ttf?okx5td') format('truetype'), - url('../fonts/grafana-icons.woff?okx5td') format('woff'), - url('../fonts/grafana-icons.svg?okx5td#grafana-icons') format('svg'); + font-family: "grafana-icons"; + src: url("../fonts/grafana-icons.eot?okx5td"); + src: url("../fonts/grafana-icons.eot?okx5td#iefix") + format("embedded-opentype"), + url("../fonts/grafana-icons.ttf?okx5td") format("truetype"), + url("../fonts/grafana-icons.woff?okx5td") format("woff"), + url("../fonts/grafana-icons.svg?okx5td#grafana-icons") format("svg"); font-weight: normal; font-style: normal; } .icon-gf { /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'grafana-icons' !important; + font-family: "grafana-icons" !important; speak: none; font-style: normal; font-weight: normal; @@ -36,165 +37,165 @@ } .icon-gf-raintank_wordmark:before { - content: '\e600'; + content: "\e600"; } .micon-gf-raintank_icn:before { - content: '\e601'; + content: "\e601"; } .icon-gf-raintank_r-icn:before { - content: '\e905'; + content: "\e905"; } .icon-gf-check-alt:before { - content: '\e603'; + content: "\e603"; } .icon-gf-check:before { - content: '\e604'; + content: "\e604"; } .icon-gf-collector:before { - content: '\e605'; + content: "\e605"; } .icon-gf-dashboard:before { - content: '\e606'; + content: "\e606"; } .icon-gf-panel:before { - content: '\e904'; + content: "\e904"; } .icon-gf-datasources:before { - content: '\e607'; + content: "\e607"; } .icon-gf-endpoint-tiny:before { - content: '\e608'; + content: "\e608"; } .icon-gf-endpoint:before { - content: '\e609'; + content: "\e609"; } .icon-gf-page:before { - content: '\e908'; + content: "\e908"; } .icon-gf-filter:before { - content: '\e60a'; + content: "\e60a"; } .icon-gf-status:before { - content: '\e60b'; + content: "\e60b"; } .icon-gf-monitoring:before { - content: '\e60c'; + content: "\e60c"; } .icon-gf-monitoring-tiny:before { - content: '\e620'; + content: "\e620"; } .icon-gf-jump-to-dashboard:before { - content: '\e60d'; + content: "\e60d"; } .icon-gf-warn, .icon-gf-warning:before { - content: '\e60e'; + content: "\e60e"; } .icon-gf-nodata:before { - content: '\e60f'; + content: "\e60f"; } .icon-gf-critical:before { - content: '\e610'; + content: "\e610"; } .icon-gf-crit:before { - content: '\e610'; + content: "\e610"; } .icon-gf-online:before { - content: '\e611'; + content: "\e611"; } .icon-gf-event-error:before { - content: '\e623'; + content: "\e623"; } .icon-gf-event:before { - content: '\e624'; + content: "\e624"; } .icon-gf-sadface:before { - content: '\e907'; + content: "\e907"; } .icon-gf-private-collector:before { - content: '\e612'; + content: "\e612"; } .icon-gf-alert:before { - content: '\e61f'; + content: "\e61f"; } .icon-gf-alert-disabled:before { - content: '\e621'; + content: "\e621"; } .icon-gf-refresh:before { - content: '\e613'; + content: "\e613"; } .icon-gf-save:before { - content: '\e614'; + content: "\e614"; } .icon-gf-share:before { - content: '\e616'; + content: "\e616"; } .icon-gf-star:before { - content: '\e617'; + content: "\e617"; } .icon-gf-search:before { - content: '\e618'; + content: "\e618"; } .icon-gf-settings:before { - content: '\e615'; + content: "\e615"; } .icon-gf-add:before { - content: '\e619'; + content: "\e619"; } .icon-gf-remove:before { - content: '\e61a'; + content: "\e61a"; } .icon-gf-video:before { - content: '\e61b'; + content: "\e61b"; } .icon-gf-bulk_action:before { - content: '\e61c'; + content: "\e61c"; } .icon-gf-grabber:before { - content: '\e90b'; + content: "\e90b"; } .icon-gf-users:before { - content: '\e622'; + content: "\e622"; } .icon-gf-globe:before { - content: '\e61d'; + content: "\e61d"; } .icon-gf-snapshot:before { - content: '\e61e'; + content: "\e61e"; } .icon-gf-play-grafana-icon:before { - content: '\e629'; + content: "\e629"; } .icon-gf-grafana-icon:before { - content: '\e625'; + content: "\e625"; } .icon-gf-email:before { - content: '\e628'; + content: "\e628"; } .icon-gf-stopwatch:before { - content: '\e626'; + content: "\e626"; } .icon-gf-skull:before { - content: '\e900'; + content: "\e900"; } .icon-gf-probe:before { - content: '\e901'; + content: "\e901"; } .icon-gf-apps:before { - content: '\e902'; + content: "\e902"; } .icon-gf-scale:before { - content: '\e906'; + content: "\e906"; } .icon-gf-pending:before { - content: '\e909'; + content: "\e909"; } .icon-gf-verified:before { - content: '\e90a'; + content: "\e90a"; } .icon-gf-worldping:before { - content: '\e627'; + content: "\e627"; } .icon-gf-grafana_wordmark:before { - content: '\e903'; + content: "\e903"; } diff --git a/public/sass/base/_normalize.scss b/public/sass/base/_normalize.scss index fff6ad95d7c..057f5c63602 100644 --- a/public/sass/base/_normalize.scss +++ b/public/sass/base/_normalize.scss @@ -291,9 +291,9 @@ select { // button, -html input[type='button'], -// 1 input[type='reset'], -input[type='submit'] { +html input[type="button"], +// 1 input[type="reset"], +input[type="submit"] { -webkit-appearance: button; // 2 cursor: pointer; // 3 } @@ -334,8 +334,8 @@ input { // 2. Remove excess padding in IE 8/9/10. // -input[type='checkbox'], -input[type='radio'] { +input[type="checkbox"], +input[type="radio"] { box-sizing: border-box; // 1 padding: 0; // 2 } @@ -346,8 +346,8 @@ input[type='radio'] { // decrement button to change from `default` to `text`. // -input[type='number']::-webkit-inner-spin-button, -input[type='number']::-webkit-outer-spin-button { +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { height: auto; } @@ -355,7 +355,7 @@ input[type='number']::-webkit-outer-spin-button { // Address `appearance` set to `searchfield` in Safari and Chrome. // -input[type='search'] { +input[type="search"] { -webkit-appearance: textfield; } @@ -365,8 +365,8 @@ input[type='search'] { // padding (and `textfield` appearance). // -input[type='search']::-webkit-search-cancel-button, -input[type='search']::-webkit-search-decoration { +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } diff --git a/public/sass/base/_reboot.scss b/public/sass/base/_reboot.scss index ccbb17a07b6..e34bdfdb9ed 100644 --- a/public/sass/base/_reboot.scss +++ b/public/sass/base/_reboot.scss @@ -87,7 +87,7 @@ body { // might still respond to pointer events. // // Credit: https://github.com/suitcss/base -[tabindex='-1']:focus { +[tabindex="-1"]:focus { outline: none !important; } @@ -214,7 +214,7 @@ img { // for traditionally non-focusable elements with role="button" // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile -[role='button'] { +[role="button"] { cursor: pointer; } @@ -231,7 +231,7 @@ img { a, area, button, -[role='button'], +[role="button"], input, label, select, @@ -320,7 +320,7 @@ legend { // border: 0; } -input[type='search'] { +input[type="search"] { // This overrides the extra rounded corners on search inputs in iOS so that our // `.form-control` class can properly style them. Note that this cannot simply // be added to `.form-control` as it's not specific enough. For details, see diff --git a/public/sass/base/font-awesome/_core.scss b/public/sass/base/font-awesome/_core.scss index a5411f108af..9940b63463d 100644 --- a/public/sass/base/font-awesome/_core.scss +++ b/public/sass/base/font-awesome/_core.scss @@ -3,7 +3,8 @@ .#{$fa-css-prefix} { display: inline-block; - font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration + font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} + FontAwesome; // shortening font declaration font-size: inherit; // can't have font-size inherit on line above, so need to override text-rendering: auto; // optimizelegibility throws things off #1094 -webkit-font-smoothing: antialiased; diff --git a/public/sass/base/font-awesome/_mixins.scss b/public/sass/base/font-awesome/_mixins.scss index f2a92c1e922..19771009107 100644 --- a/public/sass/base/font-awesome/_mixins.scss +++ b/public/sass/base/font-awesome/_mixins.scss @@ -3,7 +3,8 @@ @mixin fa-icon() { display: inline-block; - font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration + font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} + FontAwesome; // shortening font declaration font-size: inherit; // can't have font-size inherit on line above, so need to override text-rendering: auto; // optimizelegibility throws things off #1094 -webkit-font-smoothing: antialiased; @@ -11,14 +12,14 @@ } @mixin fa-icon-rotate($degrees, $rotation) { - -ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})'; + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; -webkit-transform: rotate($degrees); -ms-transform: rotate($degrees); transform: rotate($degrees); } @mixin fa-icon-flip($horiz, $vert, $rotation) { - -ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)'; + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; -webkit-transform: scale($horiz, $vert); -ms-transform: scale($horiz, $vert); transform: scale($horiz, $vert); diff --git a/public/sass/base/font-awesome/_path.scss b/public/sass/base/font-awesome/_path.scss index d452de2170a..0316afa161d 100644 --- a/public/sass/base/font-awesome/_path.scss +++ b/public/sass/base/font-awesome/_path.scss @@ -2,13 +2,18 @@ * -------------------------- */ @font-face { - font-family: 'FontAwesome'; - src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); - src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), - url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), - url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), - url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), - url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); + font-family: "FontAwesome"; + src: url("#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}"); + src: url("#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}") + format("embedded-opentype"), + url("#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}") + format("woff2"), + url("#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}") + format("woff"), + url("#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}") + format("truetype"), + url("#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular") + format("svg"); // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts font-weight: normal; font-style: normal; diff --git a/public/sass/base/font-awesome/_variables.scss b/public/sass/base/font-awesome/_variables.scss index 1e91e831e66..a4bd3cd87b9 100644 --- a/public/sass/base/font-awesome/_variables.scss +++ b/public/sass/base/font-awesome/_variables.scss @@ -1,799 +1,799 @@ // Variables // -------------------------- -$fa-font-path: '../fonts' !default; +$fa-font-path: "../fonts" !default; $fa-font-size-base: 14px !default; $fa-line-height-base: 1 !default; //$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default; // for referencing Bootstrap CDN font files directly $fa-css-prefix: fa !default; -$fa-version: '4.7.0' !default; +$fa-version: "4.7.0" !default; $fa-border-color: #eee !default; $fa-inverse: #fff !default; $fa-li-width: (30em / 14) !default; -$fa-var-500px: '\f26e'; -$fa-var-address-book: '\f2b9'; -$fa-var-address-book-o: '\f2ba'; -$fa-var-address-card: '\f2bb'; -$fa-var-address-card-o: '\f2bc'; -$fa-var-adjust: '\f042'; -$fa-var-adn: '\f170'; -$fa-var-align-center: '\f037'; -$fa-var-align-justify: '\f039'; -$fa-var-align-left: '\f036'; -$fa-var-align-right: '\f038'; -$fa-var-amazon: '\f270'; -$fa-var-ambulance: '\f0f9'; -$fa-var-american-sign-language-interpreting: '\f2a3'; -$fa-var-anchor: '\f13d'; -$fa-var-android: '\f17b'; -$fa-var-angellist: '\f209'; -$fa-var-angle-double-down: '\f103'; -$fa-var-angle-double-left: '\f100'; -$fa-var-angle-double-right: '\f101'; -$fa-var-angle-double-up: '\f102'; -$fa-var-angle-down: '\f107'; -$fa-var-angle-left: '\f104'; -$fa-var-angle-right: '\f105'; -$fa-var-angle-up: '\f106'; -$fa-var-apple: '\f179'; -$fa-var-archive: '\f187'; -$fa-var-area-chart: '\f1fe'; -$fa-var-arrow-circle-down: '\f0ab'; -$fa-var-arrow-circle-left: '\f0a8'; -$fa-var-arrow-circle-o-down: '\f01a'; -$fa-var-arrow-circle-o-left: '\f190'; -$fa-var-arrow-circle-o-right: '\f18e'; -$fa-var-arrow-circle-o-up: '\f01b'; -$fa-var-arrow-circle-right: '\f0a9'; -$fa-var-arrow-circle-up: '\f0aa'; -$fa-var-arrow-down: '\f063'; -$fa-var-arrow-left: '\f060'; -$fa-var-arrow-right: '\f061'; -$fa-var-arrow-up: '\f062'; -$fa-var-arrows: '\f047'; -$fa-var-arrows-alt: '\f0b2'; -$fa-var-arrows-h: '\f07e'; -$fa-var-arrows-v: '\f07d'; -$fa-var-asl-interpreting: '\f2a3'; -$fa-var-assistive-listening-systems: '\f2a2'; -$fa-var-asterisk: '\f069'; -$fa-var-at: '\f1fa'; -$fa-var-audio-description: '\f29e'; -$fa-var-automobile: '\f1b9'; -$fa-var-backward: '\f04a'; -$fa-var-balance-scale: '\f24e'; -$fa-var-ban: '\f05e'; -$fa-var-bandcamp: '\f2d5'; -$fa-var-bank: '\f19c'; -$fa-var-bar-chart: '\f080'; -$fa-var-bar-chart-o: '\f080'; -$fa-var-barcode: '\f02a'; -$fa-var-bars: '\f0c9'; -$fa-var-bath: '\f2cd'; -$fa-var-bathtub: '\f2cd'; -$fa-var-battery: '\f240'; -$fa-var-battery-0: '\f244'; -$fa-var-battery-1: '\f243'; -$fa-var-battery-2: '\f242'; -$fa-var-battery-3: '\f241'; -$fa-var-battery-4: '\f240'; -$fa-var-battery-empty: '\f244'; -$fa-var-battery-full: '\f240'; -$fa-var-battery-half: '\f242'; -$fa-var-battery-quarter: '\f243'; -$fa-var-battery-three-quarters: '\f241'; -$fa-var-bed: '\f236'; -$fa-var-beer: '\f0fc'; -$fa-var-behance: '\f1b4'; -$fa-var-behance-square: '\f1b5'; -$fa-var-bell: '\f0f3'; -$fa-var-bell-o: '\f0a2'; -$fa-var-bell-slash: '\f1f6'; -$fa-var-bell-slash-o: '\f1f7'; -$fa-var-bicycle: '\f206'; -$fa-var-binoculars: '\f1e5'; -$fa-var-birthday-cake: '\f1fd'; -$fa-var-bitbucket: '\f171'; -$fa-var-bitbucket-square: '\f172'; -$fa-var-bitcoin: '\f15a'; -$fa-var-black-tie: '\f27e'; -$fa-var-blind: '\f29d'; -$fa-var-bluetooth: '\f293'; -$fa-var-bluetooth-b: '\f294'; -$fa-var-bold: '\f032'; -$fa-var-bolt: '\f0e7'; -$fa-var-bomb: '\f1e2'; -$fa-var-book: '\f02d'; -$fa-var-bookmark: '\f02e'; -$fa-var-bookmark-o: '\f097'; -$fa-var-braille: '\f2a1'; -$fa-var-briefcase: '\f0b1'; -$fa-var-btc: '\f15a'; -$fa-var-bug: '\f188'; -$fa-var-building: '\f1ad'; -$fa-var-building-o: '\f0f7'; -$fa-var-bullhorn: '\f0a1'; -$fa-var-bullseye: '\f140'; -$fa-var-bus: '\f207'; -$fa-var-buysellads: '\f20d'; -$fa-var-cab: '\f1ba'; -$fa-var-calculator: '\f1ec'; -$fa-var-calendar: '\f073'; -$fa-var-calendar-check-o: '\f274'; -$fa-var-calendar-minus-o: '\f272'; -$fa-var-calendar-o: '\f133'; -$fa-var-calendar-plus-o: '\f271'; -$fa-var-calendar-times-o: '\f273'; -$fa-var-camera: '\f030'; -$fa-var-camera-retro: '\f083'; -$fa-var-car: '\f1b9'; -$fa-var-caret-down: '\f0d7'; -$fa-var-caret-left: '\f0d9'; -$fa-var-caret-right: '\f0da'; -$fa-var-caret-square-o-down: '\f150'; -$fa-var-caret-square-o-left: '\f191'; -$fa-var-caret-square-o-right: '\f152'; -$fa-var-caret-square-o-up: '\f151'; -$fa-var-caret-up: '\f0d8'; -$fa-var-cart-arrow-down: '\f218'; -$fa-var-cart-plus: '\f217'; -$fa-var-cc: '\f20a'; -$fa-var-cc-amex: '\f1f3'; -$fa-var-cc-diners-club: '\f24c'; -$fa-var-cc-discover: '\f1f2'; -$fa-var-cc-jcb: '\f24b'; -$fa-var-cc-mastercard: '\f1f1'; -$fa-var-cc-paypal: '\f1f4'; -$fa-var-cc-stripe: '\f1f5'; -$fa-var-cc-visa: '\f1f0'; -$fa-var-certificate: '\f0a3'; -$fa-var-chain: '\f0c1'; -$fa-var-chain-broken: '\f127'; -$fa-var-check: '\f00c'; -$fa-var-check-circle: '\f058'; -$fa-var-check-circle-o: '\f05d'; -$fa-var-check-square: '\f14a'; -$fa-var-check-square-o: '\f046'; -$fa-var-chevron-circle-down: '\f13a'; -$fa-var-chevron-circle-left: '\f137'; -$fa-var-chevron-circle-right: '\f138'; -$fa-var-chevron-circle-up: '\f139'; -$fa-var-chevron-down: '\f078'; -$fa-var-chevron-left: '\f053'; -$fa-var-chevron-right: '\f054'; -$fa-var-chevron-up: '\f077'; -$fa-var-child: '\f1ae'; -$fa-var-chrome: '\f268'; -$fa-var-circle: '\f111'; -$fa-var-circle-o: '\f10c'; -$fa-var-circle-o-notch: '\f1ce'; -$fa-var-circle-thin: '\f1db'; -$fa-var-clipboard: '\f0ea'; -$fa-var-clock-o: '\f017'; -$fa-var-clone: '\f24d'; -$fa-var-close: '\f00d'; -$fa-var-cloud: '\f0c2'; -$fa-var-cloud-download: '\f0ed'; -$fa-var-cloud-upload: '\f0ee'; -$fa-var-cny: '\f157'; -$fa-var-code: '\f121'; -$fa-var-code-fork: '\f126'; -$fa-var-codepen: '\f1cb'; -$fa-var-codiepie: '\f284'; -$fa-var-coffee: '\f0f4'; -$fa-var-cog: '\f013'; -$fa-var-cogs: '\f085'; -$fa-var-columns: '\f0db'; -$fa-var-comment: '\f075'; -$fa-var-comment-o: '\f0e5'; -$fa-var-commenting: '\f27a'; -$fa-var-commenting-o: '\f27b'; -$fa-var-comments: '\f086'; -$fa-var-comments-o: '\f0e6'; -$fa-var-compass: '\f14e'; -$fa-var-compress: '\f066'; -$fa-var-connectdevelop: '\f20e'; -$fa-var-contao: '\f26d'; -$fa-var-copy: '\f0c5'; -$fa-var-copyright: '\f1f9'; -$fa-var-creative-commons: '\f25e'; -$fa-var-credit-card: '\f09d'; -$fa-var-credit-card-alt: '\f283'; -$fa-var-crop: '\f125'; -$fa-var-crosshairs: '\f05b'; -$fa-var-css3: '\f13c'; -$fa-var-cube: '\f1b2'; -$fa-var-cubes: '\f1b3'; -$fa-var-cut: '\f0c4'; -$fa-var-cutlery: '\f0f5'; -$fa-var-dashboard: '\f0e4'; -$fa-var-dashcube: '\f210'; -$fa-var-database: '\f1c0'; -$fa-var-deaf: '\f2a4'; -$fa-var-deafness: '\f2a4'; -$fa-var-dedent: '\f03b'; -$fa-var-delicious: '\f1a5'; -$fa-var-desktop: '\f108'; -$fa-var-deviantart: '\f1bd'; -$fa-var-diamond: '\f219'; -$fa-var-digg: '\f1a6'; -$fa-var-dollar: '\f155'; -$fa-var-dot-circle-o: '\f192'; -$fa-var-download: '\f019'; -$fa-var-dribbble: '\f17d'; -$fa-var-drivers-license: '\f2c2'; -$fa-var-drivers-license-o: '\f2c3'; -$fa-var-dropbox: '\f16b'; -$fa-var-drupal: '\f1a9'; -$fa-var-edge: '\f282'; -$fa-var-edit: '\f044'; -$fa-var-eercast: '\f2da'; -$fa-var-eject: '\f052'; -$fa-var-ellipsis-h: '\f141'; -$fa-var-ellipsis-v: '\f142'; -$fa-var-empire: '\f1d1'; -$fa-var-envelope: '\f0e0'; -$fa-var-envelope-o: '\f003'; -$fa-var-envelope-open: '\f2b6'; -$fa-var-envelope-open-o: '\f2b7'; -$fa-var-envelope-square: '\f199'; -$fa-var-envira: '\f299'; -$fa-var-eraser: '\f12d'; -$fa-var-etsy: '\f2d7'; -$fa-var-eur: '\f153'; -$fa-var-euro: '\f153'; -$fa-var-exchange: '\f0ec'; -$fa-var-exclamation: '\f12a'; -$fa-var-exclamation-circle: '\f06a'; -$fa-var-exclamation-triangle: '\f071'; -$fa-var-expand: '\f065'; -$fa-var-expeditedssl: '\f23e'; -$fa-var-external-link: '\f08e'; -$fa-var-external-link-square: '\f14c'; -$fa-var-eye: '\f06e'; -$fa-var-eye-slash: '\f070'; -$fa-var-eyedropper: '\f1fb'; -$fa-var-fa: '\f2b4'; -$fa-var-facebook: '\f09a'; -$fa-var-facebook-f: '\f09a'; -$fa-var-facebook-official: '\f230'; -$fa-var-facebook-square: '\f082'; -$fa-var-fast-backward: '\f049'; -$fa-var-fast-forward: '\f050'; -$fa-var-fax: '\f1ac'; -$fa-var-feed: '\f09e'; -$fa-var-female: '\f182'; -$fa-var-fighter-jet: '\f0fb'; -$fa-var-file: '\f15b'; -$fa-var-file-archive-o: '\f1c6'; -$fa-var-file-audio-o: '\f1c7'; -$fa-var-file-code-o: '\f1c9'; -$fa-var-file-excel-o: '\f1c3'; -$fa-var-file-image-o: '\f1c5'; -$fa-var-file-movie-o: '\f1c8'; -$fa-var-file-o: '\f016'; -$fa-var-file-pdf-o: '\f1c1'; -$fa-var-file-photo-o: '\f1c5'; -$fa-var-file-picture-o: '\f1c5'; -$fa-var-file-powerpoint-o: '\f1c4'; -$fa-var-file-sound-o: '\f1c7'; -$fa-var-file-text: '\f15c'; -$fa-var-file-text-o: '\f0f6'; -$fa-var-file-video-o: '\f1c8'; -$fa-var-file-word-o: '\f1c2'; -$fa-var-file-zip-o: '\f1c6'; -$fa-var-files-o: '\f0c5'; -$fa-var-film: '\f008'; -$fa-var-filter: '\f0b0'; -$fa-var-fire: '\f06d'; -$fa-var-fire-extinguisher: '\f134'; -$fa-var-firefox: '\f269'; -$fa-var-first-order: '\f2b0'; -$fa-var-flag: '\f024'; -$fa-var-flag-checkered: '\f11e'; -$fa-var-flag-o: '\f11d'; -$fa-var-flash: '\f0e7'; -$fa-var-flask: '\f0c3'; -$fa-var-flickr: '\f16e'; -$fa-var-floppy-o: '\f0c7'; -$fa-var-folder: '\f07b'; -$fa-var-folder-o: '\f114'; -$fa-var-folder-open: '\f07c'; -$fa-var-folder-open-o: '\f115'; -$fa-var-font: '\f031'; -$fa-var-font-awesome: '\f2b4'; -$fa-var-fonticons: '\f280'; -$fa-var-fort-awesome: '\f286'; -$fa-var-forumbee: '\f211'; -$fa-var-forward: '\f04e'; -$fa-var-foursquare: '\f180'; -$fa-var-free-code-camp: '\f2c5'; -$fa-var-frown-o: '\f119'; -$fa-var-futbol-o: '\f1e3'; -$fa-var-gamepad: '\f11b'; -$fa-var-gavel: '\f0e3'; -$fa-var-gbp: '\f154'; -$fa-var-ge: '\f1d1'; -$fa-var-gear: '\f013'; -$fa-var-gears: '\f085'; -$fa-var-genderless: '\f22d'; -$fa-var-get-pocket: '\f265'; -$fa-var-gg: '\f260'; -$fa-var-gg-circle: '\f261'; -$fa-var-gift: '\f06b'; -$fa-var-git: '\f1d3'; -$fa-var-git-square: '\f1d2'; -$fa-var-github: '\f09b'; -$fa-var-github-alt: '\f113'; -$fa-var-github-square: '\f092'; -$fa-var-gitlab: '\f296'; -$fa-var-gittip: '\f184'; -$fa-var-glass: '\f000'; -$fa-var-glide: '\f2a5'; -$fa-var-glide-g: '\f2a6'; -$fa-var-globe: '\f0ac'; -$fa-var-google: '\f1a0'; -$fa-var-google-plus: '\f0d5'; -$fa-var-google-plus-circle: '\f2b3'; -$fa-var-google-plus-official: '\f2b3'; -$fa-var-google-plus-square: '\f0d4'; -$fa-var-google-wallet: '\f1ee'; -$fa-var-graduation-cap: '\f19d'; -$fa-var-gratipay: '\f184'; -$fa-var-grav: '\f2d6'; -$fa-var-group: '\f0c0'; -$fa-var-h-square: '\f0fd'; -$fa-var-hacker-news: '\f1d4'; -$fa-var-hand-grab-o: '\f255'; -$fa-var-hand-lizard-o: '\f258'; -$fa-var-hand-o-down: '\f0a7'; -$fa-var-hand-o-left: '\f0a5'; -$fa-var-hand-o-right: '\f0a4'; -$fa-var-hand-o-up: '\f0a6'; -$fa-var-hand-paper-o: '\f256'; -$fa-var-hand-peace-o: '\f25b'; -$fa-var-hand-pointer-o: '\f25a'; -$fa-var-hand-rock-o: '\f255'; -$fa-var-hand-scissors-o: '\f257'; -$fa-var-hand-spock-o: '\f259'; -$fa-var-hand-stop-o: '\f256'; -$fa-var-handshake-o: '\f2b5'; -$fa-var-hard-of-hearing: '\f2a4'; -$fa-var-hashtag: '\f292'; -$fa-var-hdd-o: '\f0a0'; -$fa-var-header: '\f1dc'; -$fa-var-headphones: '\f025'; -$fa-var-heart: '\f004'; -$fa-var-heart-o: '\f08a'; -$fa-var-heartbeat: '\f21e'; -$fa-var-history: '\f1da'; -$fa-var-home: '\f015'; -$fa-var-hospital-o: '\f0f8'; -$fa-var-hotel: '\f236'; -$fa-var-hourglass: '\f254'; -$fa-var-hourglass-1: '\f251'; -$fa-var-hourglass-2: '\f252'; -$fa-var-hourglass-3: '\f253'; -$fa-var-hourglass-end: '\f253'; -$fa-var-hourglass-half: '\f252'; -$fa-var-hourglass-o: '\f250'; -$fa-var-hourglass-start: '\f251'; -$fa-var-houzz: '\f27c'; -$fa-var-html5: '\f13b'; -$fa-var-i-cursor: '\f246'; -$fa-var-id-badge: '\f2c1'; -$fa-var-id-card: '\f2c2'; -$fa-var-id-card-o: '\f2c3'; -$fa-var-ils: '\f20b'; -$fa-var-image: '\f03e'; -$fa-var-imdb: '\f2d8'; -$fa-var-inbox: '\f01c'; -$fa-var-indent: '\f03c'; -$fa-var-industry: '\f275'; -$fa-var-info: '\f129'; -$fa-var-info-circle: '\f05a'; -$fa-var-inr: '\f156'; -$fa-var-instagram: '\f16d'; -$fa-var-institution: '\f19c'; -$fa-var-internet-explorer: '\f26b'; -$fa-var-intersex: '\f224'; -$fa-var-ioxhost: '\f208'; -$fa-var-italic: '\f033'; -$fa-var-joomla: '\f1aa'; -$fa-var-jpy: '\f157'; -$fa-var-jsfiddle: '\f1cc'; -$fa-var-key: '\f084'; -$fa-var-keyboard-o: '\f11c'; -$fa-var-krw: '\f159'; -$fa-var-language: '\f1ab'; -$fa-var-laptop: '\f109'; -$fa-var-lastfm: '\f202'; -$fa-var-lastfm-square: '\f203'; -$fa-var-leaf: '\f06c'; -$fa-var-leanpub: '\f212'; -$fa-var-legal: '\f0e3'; -$fa-var-lemon-o: '\f094'; -$fa-var-level-down: '\f149'; -$fa-var-level-up: '\f148'; -$fa-var-life-bouy: '\f1cd'; -$fa-var-life-buoy: '\f1cd'; -$fa-var-life-ring: '\f1cd'; -$fa-var-life-saver: '\f1cd'; -$fa-var-lightbulb-o: '\f0eb'; -$fa-var-line-chart: '\f201'; -$fa-var-link: '\f0c1'; -$fa-var-linkedin: '\f0e1'; -$fa-var-linkedin-square: '\f08c'; -$fa-var-linode: '\f2b8'; -$fa-var-linux: '\f17c'; -$fa-var-list: '\f03a'; -$fa-var-list-alt: '\f022'; -$fa-var-list-ol: '\f0cb'; -$fa-var-list-ul: '\f0ca'; -$fa-var-location-arrow: '\f124'; -$fa-var-lock: '\f023'; -$fa-var-long-arrow-down: '\f175'; -$fa-var-long-arrow-left: '\f177'; -$fa-var-long-arrow-right: '\f178'; -$fa-var-long-arrow-up: '\f176'; -$fa-var-low-vision: '\f2a8'; -$fa-var-magic: '\f0d0'; -$fa-var-magnet: '\f076'; -$fa-var-mail-forward: '\f064'; -$fa-var-mail-reply: '\f112'; -$fa-var-mail-reply-all: '\f122'; -$fa-var-male: '\f183'; -$fa-var-map: '\f279'; -$fa-var-map-marker: '\f041'; -$fa-var-map-o: '\f278'; -$fa-var-map-pin: '\f276'; -$fa-var-map-signs: '\f277'; -$fa-var-mars: '\f222'; -$fa-var-mars-double: '\f227'; -$fa-var-mars-stroke: '\f229'; -$fa-var-mars-stroke-h: '\f22b'; -$fa-var-mars-stroke-v: '\f22a'; -$fa-var-maxcdn: '\f136'; -$fa-var-meanpath: '\f20c'; -$fa-var-medium: '\f23a'; -$fa-var-medkit: '\f0fa'; -$fa-var-meetup: '\f2e0'; -$fa-var-meh-o: '\f11a'; -$fa-var-mercury: '\f223'; -$fa-var-microchip: '\f2db'; -$fa-var-microphone: '\f130'; -$fa-var-microphone-slash: '\f131'; -$fa-var-minus: '\f068'; -$fa-var-minus-circle: '\f056'; -$fa-var-minus-square: '\f146'; -$fa-var-minus-square-o: '\f147'; -$fa-var-mixcloud: '\f289'; -$fa-var-mobile: '\f10b'; -$fa-var-mobile-phone: '\f10b'; -$fa-var-modx: '\f285'; -$fa-var-money: '\f0d6'; -$fa-var-moon-o: '\f186'; -$fa-var-mortar-board: '\f19d'; -$fa-var-motorcycle: '\f21c'; -$fa-var-mouse-pointer: '\f245'; -$fa-var-music: '\f001'; -$fa-var-navicon: '\f0c9'; -$fa-var-neuter: '\f22c'; -$fa-var-newspaper-o: '\f1ea'; -$fa-var-object-group: '\f247'; -$fa-var-object-ungroup: '\f248'; -$fa-var-odnoklassniki: '\f263'; -$fa-var-odnoklassniki-square: '\f264'; -$fa-var-opencart: '\f23d'; -$fa-var-openid: '\f19b'; -$fa-var-opera: '\f26a'; -$fa-var-optin-monster: '\f23c'; -$fa-var-outdent: '\f03b'; -$fa-var-pagelines: '\f18c'; -$fa-var-paint-brush: '\f1fc'; -$fa-var-paper-plane: '\f1d8'; -$fa-var-paper-plane-o: '\f1d9'; -$fa-var-paperclip: '\f0c6'; -$fa-var-paragraph: '\f1dd'; -$fa-var-paste: '\f0ea'; -$fa-var-pause: '\f04c'; -$fa-var-pause-circle: '\f28b'; -$fa-var-pause-circle-o: '\f28c'; -$fa-var-paw: '\f1b0'; -$fa-var-paypal: '\f1ed'; -$fa-var-pencil: '\f040'; -$fa-var-pencil-square: '\f14b'; -$fa-var-pencil-square-o: '\f044'; -$fa-var-percent: '\f295'; -$fa-var-phone: '\f095'; -$fa-var-phone-square: '\f098'; -$fa-var-photo: '\f03e'; -$fa-var-picture-o: '\f03e'; -$fa-var-pie-chart: '\f200'; -$fa-var-pied-piper: '\f2ae'; -$fa-var-pied-piper-alt: '\f1a8'; -$fa-var-pied-piper-pp: '\f1a7'; -$fa-var-pinterest: '\f0d2'; -$fa-var-pinterest-p: '\f231'; -$fa-var-pinterest-square: '\f0d3'; -$fa-var-plane: '\f072'; -$fa-var-play: '\f04b'; -$fa-var-play-circle: '\f144'; -$fa-var-play-circle-o: '\f01d'; -$fa-var-plug: '\f1e6'; -$fa-var-plus: '\f067'; -$fa-var-plus-circle: '\f055'; -$fa-var-plus-square: '\f0fe'; -$fa-var-plus-square-o: '\f196'; -$fa-var-podcast: '\f2ce'; -$fa-var-power-off: '\f011'; -$fa-var-print: '\f02f'; -$fa-var-product-hunt: '\f288'; -$fa-var-puzzle-piece: '\f12e'; -$fa-var-qq: '\f1d6'; -$fa-var-qrcode: '\f029'; -$fa-var-question: '\f128'; -$fa-var-question-circle: '\f059'; -$fa-var-question-circle-o: '\f29c'; -$fa-var-quora: '\f2c4'; -$fa-var-quote-left: '\f10d'; -$fa-var-quote-right: '\f10e'; -$fa-var-ra: '\f1d0'; -$fa-var-random: '\f074'; -$fa-var-ravelry: '\f2d9'; -$fa-var-rebel: '\f1d0'; -$fa-var-recycle: '\f1b8'; -$fa-var-reddit: '\f1a1'; -$fa-var-reddit-alien: '\f281'; -$fa-var-reddit-square: '\f1a2'; -$fa-var-refresh: '\f021'; -$fa-var-registered: '\f25d'; -$fa-var-remove: '\f00d'; -$fa-var-renren: '\f18b'; -$fa-var-reorder: '\f0c9'; -$fa-var-repeat: '\f01e'; -$fa-var-reply: '\f112'; -$fa-var-reply-all: '\f122'; -$fa-var-resistance: '\f1d0'; -$fa-var-retweet: '\f079'; -$fa-var-rmb: '\f157'; -$fa-var-road: '\f018'; -$fa-var-rocket: '\f135'; -$fa-var-rotate-left: '\f0e2'; -$fa-var-rotate-right: '\f01e'; -$fa-var-rouble: '\f158'; -$fa-var-rss: '\f09e'; -$fa-var-rss-square: '\f143'; -$fa-var-rub: '\f158'; -$fa-var-ruble: '\f158'; -$fa-var-rupee: '\f156'; -$fa-var-s15: '\f2cd'; -$fa-var-safari: '\f267'; -$fa-var-save: '\f0c7'; -$fa-var-scissors: '\f0c4'; -$fa-var-scribd: '\f28a'; -$fa-var-search: '\f002'; -$fa-var-search-minus: '\f010'; -$fa-var-search-plus: '\f00e'; -$fa-var-sellsy: '\f213'; -$fa-var-send: '\f1d8'; -$fa-var-send-o: '\f1d9'; -$fa-var-server: '\f233'; -$fa-var-share: '\f064'; -$fa-var-share-alt: '\f1e0'; -$fa-var-share-alt-square: '\f1e1'; -$fa-var-share-square: '\f14d'; -$fa-var-share-square-o: '\f045'; -$fa-var-shekel: '\f20b'; -$fa-var-sheqel: '\f20b'; -$fa-var-shield: '\f132'; -$fa-var-ship: '\f21a'; -$fa-var-shirtsinbulk: '\f214'; -$fa-var-shopping-bag: '\f290'; -$fa-var-shopping-basket: '\f291'; -$fa-var-shopping-cart: '\f07a'; -$fa-var-shower: '\f2cc'; -$fa-var-sign-in: '\f090'; -$fa-var-sign-language: '\f2a7'; -$fa-var-sign-out: '\f08b'; -$fa-var-signal: '\f012'; -$fa-var-signing: '\f2a7'; -$fa-var-simplybuilt: '\f215'; -$fa-var-sitemap: '\f0e8'; -$fa-var-skyatlas: '\f216'; -$fa-var-skype: '\f17e'; -$fa-var-slack: '\f198'; -$fa-var-sliders: '\f1de'; -$fa-var-slideshare: '\f1e7'; -$fa-var-smile-o: '\f118'; -$fa-var-snapchat: '\f2ab'; -$fa-var-snapchat-ghost: '\f2ac'; -$fa-var-snapchat-square: '\f2ad'; -$fa-var-snowflake-o: '\f2dc'; -$fa-var-soccer-ball-o: '\f1e3'; -$fa-var-sort: '\f0dc'; -$fa-var-sort-alpha-asc: '\f15d'; -$fa-var-sort-alpha-desc: '\f15e'; -$fa-var-sort-amount-asc: '\f160'; -$fa-var-sort-amount-desc: '\f161'; -$fa-var-sort-asc: '\f0de'; -$fa-var-sort-desc: '\f0dd'; -$fa-var-sort-down: '\f0dd'; -$fa-var-sort-numeric-asc: '\f162'; -$fa-var-sort-numeric-desc: '\f163'; -$fa-var-sort-up: '\f0de'; -$fa-var-soundcloud: '\f1be'; -$fa-var-space-shuttle: '\f197'; -$fa-var-spinner: '\f110'; -$fa-var-spoon: '\f1b1'; -$fa-var-spotify: '\f1bc'; -$fa-var-square: '\f0c8'; -$fa-var-square-o: '\f096'; -$fa-var-stack-exchange: '\f18d'; -$fa-var-stack-overflow: '\f16c'; -$fa-var-star: '\f005'; -$fa-var-star-half: '\f089'; -$fa-var-star-half-empty: '\f123'; -$fa-var-star-half-full: '\f123'; -$fa-var-star-half-o: '\f123'; -$fa-var-star-o: '\f006'; -$fa-var-steam: '\f1b6'; -$fa-var-steam-square: '\f1b7'; -$fa-var-step-backward: '\f048'; -$fa-var-step-forward: '\f051'; -$fa-var-stethoscope: '\f0f1'; -$fa-var-sticky-note: '\f249'; -$fa-var-sticky-note-o: '\f24a'; -$fa-var-stop: '\f04d'; -$fa-var-stop-circle: '\f28d'; -$fa-var-stop-circle-o: '\f28e'; -$fa-var-street-view: '\f21d'; -$fa-var-strikethrough: '\f0cc'; -$fa-var-stumbleupon: '\f1a4'; -$fa-var-stumbleupon-circle: '\f1a3'; -$fa-var-subscript: '\f12c'; -$fa-var-subway: '\f239'; -$fa-var-suitcase: '\f0f2'; -$fa-var-sun-o: '\f185'; -$fa-var-superpowers: '\f2dd'; -$fa-var-superscript: '\f12b'; -$fa-var-support: '\f1cd'; -$fa-var-table: '\f0ce'; -$fa-var-tablet: '\f10a'; -$fa-var-tachometer: '\f0e4'; -$fa-var-tag: '\f02b'; -$fa-var-tags: '\f02c'; -$fa-var-tasks: '\f0ae'; -$fa-var-taxi: '\f1ba'; -$fa-var-telegram: '\f2c6'; -$fa-var-television: '\f26c'; -$fa-var-tencent-weibo: '\f1d5'; -$fa-var-terminal: '\f120'; -$fa-var-text-height: '\f034'; -$fa-var-text-width: '\f035'; -$fa-var-th: '\f00a'; -$fa-var-th-large: '\f009'; -$fa-var-th-list: '\f00b'; -$fa-var-themeisle: '\f2b2'; -$fa-var-thermometer: '\f2c7'; -$fa-var-thermometer-0: '\f2cb'; -$fa-var-thermometer-1: '\f2ca'; -$fa-var-thermometer-2: '\f2c9'; -$fa-var-thermometer-3: '\f2c8'; -$fa-var-thermometer-4: '\f2c7'; -$fa-var-thermometer-empty: '\f2cb'; -$fa-var-thermometer-full: '\f2c7'; -$fa-var-thermometer-half: '\f2c9'; -$fa-var-thermometer-quarter: '\f2ca'; -$fa-var-thermometer-three-quarters: '\f2c8'; -$fa-var-thumb-tack: '\f08d'; -$fa-var-thumbs-down: '\f165'; -$fa-var-thumbs-o-down: '\f088'; -$fa-var-thumbs-o-up: '\f087'; -$fa-var-thumbs-up: '\f164'; -$fa-var-ticket: '\f145'; -$fa-var-times: '\f00d'; -$fa-var-times-circle: '\f057'; -$fa-var-times-circle-o: '\f05c'; -$fa-var-times-rectangle: '\f2d3'; -$fa-var-times-rectangle-o: '\f2d4'; -$fa-var-tint: '\f043'; -$fa-var-toggle-down: '\f150'; -$fa-var-toggle-left: '\f191'; -$fa-var-toggle-off: '\f204'; -$fa-var-toggle-on: '\f205'; -$fa-var-toggle-right: '\f152'; -$fa-var-toggle-up: '\f151'; -$fa-var-trademark: '\f25c'; -$fa-var-train: '\f238'; -$fa-var-transgender: '\f224'; -$fa-var-transgender-alt: '\f225'; -$fa-var-trash: '\f1f8'; -$fa-var-trash-o: '\f014'; -$fa-var-tree: '\f1bb'; -$fa-var-trello: '\f181'; -$fa-var-tripadvisor: '\f262'; -$fa-var-trophy: '\f091'; -$fa-var-truck: '\f0d1'; -$fa-var-try: '\f195'; -$fa-var-tty: '\f1e4'; -$fa-var-tumblr: '\f173'; -$fa-var-tumblr-square: '\f174'; -$fa-var-turkish-lira: '\f195'; -$fa-var-tv: '\f26c'; -$fa-var-twitch: '\f1e8'; -$fa-var-twitter: '\f099'; -$fa-var-twitter-square: '\f081'; -$fa-var-umbrella: '\f0e9'; -$fa-var-underline: '\f0cd'; -$fa-var-undo: '\f0e2'; -$fa-var-universal-access: '\f29a'; -$fa-var-university: '\f19c'; -$fa-var-unlink: '\f127'; -$fa-var-unlock: '\f09c'; -$fa-var-unlock-alt: '\f13e'; -$fa-var-unsorted: '\f0dc'; -$fa-var-upload: '\f093'; -$fa-var-usb: '\f287'; -$fa-var-usd: '\f155'; -$fa-var-user: '\f007'; -$fa-var-user-circle: '\f2bd'; -$fa-var-user-circle-o: '\f2be'; -$fa-var-user-md: '\f0f0'; -$fa-var-user-o: '\f2c0'; -$fa-var-user-plus: '\f234'; -$fa-var-user-secret: '\f21b'; -$fa-var-user-times: '\f235'; -$fa-var-users: '\f0c0'; -$fa-var-vcard: '\f2bb'; -$fa-var-vcard-o: '\f2bc'; -$fa-var-venus: '\f221'; -$fa-var-venus-double: '\f226'; -$fa-var-venus-mars: '\f228'; -$fa-var-viacoin: '\f237'; -$fa-var-viadeo: '\f2a9'; -$fa-var-viadeo-square: '\f2aa'; -$fa-var-video-camera: '\f03d'; -$fa-var-vimeo: '\f27d'; -$fa-var-vimeo-square: '\f194'; -$fa-var-vine: '\f1ca'; -$fa-var-vk: '\f189'; -$fa-var-volume-control-phone: '\f2a0'; -$fa-var-volume-down: '\f027'; -$fa-var-volume-off: '\f026'; -$fa-var-volume-up: '\f028'; -$fa-var-warning: '\f071'; -$fa-var-wechat: '\f1d7'; -$fa-var-weibo: '\f18a'; -$fa-var-weixin: '\f1d7'; -$fa-var-whatsapp: '\f232'; -$fa-var-wheelchair: '\f193'; -$fa-var-wheelchair-alt: '\f29b'; -$fa-var-wifi: '\f1eb'; -$fa-var-wikipedia-w: '\f266'; -$fa-var-window-close: '\f2d3'; -$fa-var-window-close-o: '\f2d4'; -$fa-var-window-maximize: '\f2d0'; -$fa-var-window-minimize: '\f2d1'; -$fa-var-window-restore: '\f2d2'; -$fa-var-windows: '\f17a'; -$fa-var-won: '\f159'; -$fa-var-wordpress: '\f19a'; -$fa-var-wpbeginner: '\f297'; -$fa-var-wpexplorer: '\f2de'; -$fa-var-wpforms: '\f298'; -$fa-var-wrench: '\f0ad'; -$fa-var-xing: '\f168'; -$fa-var-xing-square: '\f169'; -$fa-var-y-combinator: '\f23b'; -$fa-var-y-combinator-square: '\f1d4'; -$fa-var-yahoo: '\f19e'; -$fa-var-yc: '\f23b'; -$fa-var-yc-square: '\f1d4'; -$fa-var-yelp: '\f1e9'; -$fa-var-yen: '\f157'; -$fa-var-yoast: '\f2b1'; -$fa-var-youtube: '\f167'; -$fa-var-youtube-play: '\f16a'; -$fa-var-youtube-square: '\f166'; +$fa-var-500px: "\f26e"; +$fa-var-address-book: "\f2b9"; +$fa-var-address-book-o: "\f2ba"; +$fa-var-address-card: "\f2bb"; +$fa-var-address-card-o: "\f2bc"; +$fa-var-adjust: "\f042"; +$fa-var-adn: "\f170"; +$fa-var-align-center: "\f037"; +$fa-var-align-justify: "\f039"; +$fa-var-align-left: "\f036"; +$fa-var-align-right: "\f038"; +$fa-var-amazon: "\f270"; +$fa-var-ambulance: "\f0f9"; +$fa-var-american-sign-language-interpreting: "\f2a3"; +$fa-var-anchor: "\f13d"; +$fa-var-android: "\f17b"; +$fa-var-angellist: "\f209"; +$fa-var-angle-double-down: "\f103"; +$fa-var-angle-double-left: "\f100"; +$fa-var-angle-double-right: "\f101"; +$fa-var-angle-double-up: "\f102"; +$fa-var-angle-down: "\f107"; +$fa-var-angle-left: "\f104"; +$fa-var-angle-right: "\f105"; +$fa-var-angle-up: "\f106"; +$fa-var-apple: "\f179"; +$fa-var-archive: "\f187"; +$fa-var-area-chart: "\f1fe"; +$fa-var-arrow-circle-down: "\f0ab"; +$fa-var-arrow-circle-left: "\f0a8"; +$fa-var-arrow-circle-o-down: "\f01a"; +$fa-var-arrow-circle-o-left: "\f190"; +$fa-var-arrow-circle-o-right: "\f18e"; +$fa-var-arrow-circle-o-up: "\f01b"; +$fa-var-arrow-circle-right: "\f0a9"; +$fa-var-arrow-circle-up: "\f0aa"; +$fa-var-arrow-down: "\f063"; +$fa-var-arrow-left: "\f060"; +$fa-var-arrow-right: "\f061"; +$fa-var-arrow-up: "\f062"; +$fa-var-arrows: "\f047"; +$fa-var-arrows-alt: "\f0b2"; +$fa-var-arrows-h: "\f07e"; +$fa-var-arrows-v: "\f07d"; +$fa-var-asl-interpreting: "\f2a3"; +$fa-var-assistive-listening-systems: "\f2a2"; +$fa-var-asterisk: "\f069"; +$fa-var-at: "\f1fa"; +$fa-var-audio-description: "\f29e"; +$fa-var-automobile: "\f1b9"; +$fa-var-backward: "\f04a"; +$fa-var-balance-scale: "\f24e"; +$fa-var-ban: "\f05e"; +$fa-var-bandcamp: "\f2d5"; +$fa-var-bank: "\f19c"; +$fa-var-bar-chart: "\f080"; +$fa-var-bar-chart-o: "\f080"; +$fa-var-barcode: "\f02a"; +$fa-var-bars: "\f0c9"; +$fa-var-bath: "\f2cd"; +$fa-var-bathtub: "\f2cd"; +$fa-var-battery: "\f240"; +$fa-var-battery-0: "\f244"; +$fa-var-battery-1: "\f243"; +$fa-var-battery-2: "\f242"; +$fa-var-battery-3: "\f241"; +$fa-var-battery-4: "\f240"; +$fa-var-battery-empty: "\f244"; +$fa-var-battery-full: "\f240"; +$fa-var-battery-half: "\f242"; +$fa-var-battery-quarter: "\f243"; +$fa-var-battery-three-quarters: "\f241"; +$fa-var-bed: "\f236"; +$fa-var-beer: "\f0fc"; +$fa-var-behance: "\f1b4"; +$fa-var-behance-square: "\f1b5"; +$fa-var-bell: "\f0f3"; +$fa-var-bell-o: "\f0a2"; +$fa-var-bell-slash: "\f1f6"; +$fa-var-bell-slash-o: "\f1f7"; +$fa-var-bicycle: "\f206"; +$fa-var-binoculars: "\f1e5"; +$fa-var-birthday-cake: "\f1fd"; +$fa-var-bitbucket: "\f171"; +$fa-var-bitbucket-square: "\f172"; +$fa-var-bitcoin: "\f15a"; +$fa-var-black-tie: "\f27e"; +$fa-var-blind: "\f29d"; +$fa-var-bluetooth: "\f293"; +$fa-var-bluetooth-b: "\f294"; +$fa-var-bold: "\f032"; +$fa-var-bolt: "\f0e7"; +$fa-var-bomb: "\f1e2"; +$fa-var-book: "\f02d"; +$fa-var-bookmark: "\f02e"; +$fa-var-bookmark-o: "\f097"; +$fa-var-braille: "\f2a1"; +$fa-var-briefcase: "\f0b1"; +$fa-var-btc: "\f15a"; +$fa-var-bug: "\f188"; +$fa-var-building: "\f1ad"; +$fa-var-building-o: "\f0f7"; +$fa-var-bullhorn: "\f0a1"; +$fa-var-bullseye: "\f140"; +$fa-var-bus: "\f207"; +$fa-var-buysellads: "\f20d"; +$fa-var-cab: "\f1ba"; +$fa-var-calculator: "\f1ec"; +$fa-var-calendar: "\f073"; +$fa-var-calendar-check-o: "\f274"; +$fa-var-calendar-minus-o: "\f272"; +$fa-var-calendar-o: "\f133"; +$fa-var-calendar-plus-o: "\f271"; +$fa-var-calendar-times-o: "\f273"; +$fa-var-camera: "\f030"; +$fa-var-camera-retro: "\f083"; +$fa-var-car: "\f1b9"; +$fa-var-caret-down: "\f0d7"; +$fa-var-caret-left: "\f0d9"; +$fa-var-caret-right: "\f0da"; +$fa-var-caret-square-o-down: "\f150"; +$fa-var-caret-square-o-left: "\f191"; +$fa-var-caret-square-o-right: "\f152"; +$fa-var-caret-square-o-up: "\f151"; +$fa-var-caret-up: "\f0d8"; +$fa-var-cart-arrow-down: "\f218"; +$fa-var-cart-plus: "\f217"; +$fa-var-cc: "\f20a"; +$fa-var-cc-amex: "\f1f3"; +$fa-var-cc-diners-club: "\f24c"; +$fa-var-cc-discover: "\f1f2"; +$fa-var-cc-jcb: "\f24b"; +$fa-var-cc-mastercard: "\f1f1"; +$fa-var-cc-paypal: "\f1f4"; +$fa-var-cc-stripe: "\f1f5"; +$fa-var-cc-visa: "\f1f0"; +$fa-var-certificate: "\f0a3"; +$fa-var-chain: "\f0c1"; +$fa-var-chain-broken: "\f127"; +$fa-var-check: "\f00c"; +$fa-var-check-circle: "\f058"; +$fa-var-check-circle-o: "\f05d"; +$fa-var-check-square: "\f14a"; +$fa-var-check-square-o: "\f046"; +$fa-var-chevron-circle-down: "\f13a"; +$fa-var-chevron-circle-left: "\f137"; +$fa-var-chevron-circle-right: "\f138"; +$fa-var-chevron-circle-up: "\f139"; +$fa-var-chevron-down: "\f078"; +$fa-var-chevron-left: "\f053"; +$fa-var-chevron-right: "\f054"; +$fa-var-chevron-up: "\f077"; +$fa-var-child: "\f1ae"; +$fa-var-chrome: "\f268"; +$fa-var-circle: "\f111"; +$fa-var-circle-o: "\f10c"; +$fa-var-circle-o-notch: "\f1ce"; +$fa-var-circle-thin: "\f1db"; +$fa-var-clipboard: "\f0ea"; +$fa-var-clock-o: "\f017"; +$fa-var-clone: "\f24d"; +$fa-var-close: "\f00d"; +$fa-var-cloud: "\f0c2"; +$fa-var-cloud-download: "\f0ed"; +$fa-var-cloud-upload: "\f0ee"; +$fa-var-cny: "\f157"; +$fa-var-code: "\f121"; +$fa-var-code-fork: "\f126"; +$fa-var-codepen: "\f1cb"; +$fa-var-codiepie: "\f284"; +$fa-var-coffee: "\f0f4"; +$fa-var-cog: "\f013"; +$fa-var-cogs: "\f085"; +$fa-var-columns: "\f0db"; +$fa-var-comment: "\f075"; +$fa-var-comment-o: "\f0e5"; +$fa-var-commenting: "\f27a"; +$fa-var-commenting-o: "\f27b"; +$fa-var-comments: "\f086"; +$fa-var-comments-o: "\f0e6"; +$fa-var-compass: "\f14e"; +$fa-var-compress: "\f066"; +$fa-var-connectdevelop: "\f20e"; +$fa-var-contao: "\f26d"; +$fa-var-copy: "\f0c5"; +$fa-var-copyright: "\f1f9"; +$fa-var-creative-commons: "\f25e"; +$fa-var-credit-card: "\f09d"; +$fa-var-credit-card-alt: "\f283"; +$fa-var-crop: "\f125"; +$fa-var-crosshairs: "\f05b"; +$fa-var-css3: "\f13c"; +$fa-var-cube: "\f1b2"; +$fa-var-cubes: "\f1b3"; +$fa-var-cut: "\f0c4"; +$fa-var-cutlery: "\f0f5"; +$fa-var-dashboard: "\f0e4"; +$fa-var-dashcube: "\f210"; +$fa-var-database: "\f1c0"; +$fa-var-deaf: "\f2a4"; +$fa-var-deafness: "\f2a4"; +$fa-var-dedent: "\f03b"; +$fa-var-delicious: "\f1a5"; +$fa-var-desktop: "\f108"; +$fa-var-deviantart: "\f1bd"; +$fa-var-diamond: "\f219"; +$fa-var-digg: "\f1a6"; +$fa-var-dollar: "\f155"; +$fa-var-dot-circle-o: "\f192"; +$fa-var-download: "\f019"; +$fa-var-dribbble: "\f17d"; +$fa-var-drivers-license: "\f2c2"; +$fa-var-drivers-license-o: "\f2c3"; +$fa-var-dropbox: "\f16b"; +$fa-var-drupal: "\f1a9"; +$fa-var-edge: "\f282"; +$fa-var-edit: "\f044"; +$fa-var-eercast: "\f2da"; +$fa-var-eject: "\f052"; +$fa-var-ellipsis-h: "\f141"; +$fa-var-ellipsis-v: "\f142"; +$fa-var-empire: "\f1d1"; +$fa-var-envelope: "\f0e0"; +$fa-var-envelope-o: "\f003"; +$fa-var-envelope-open: "\f2b6"; +$fa-var-envelope-open-o: "\f2b7"; +$fa-var-envelope-square: "\f199"; +$fa-var-envira: "\f299"; +$fa-var-eraser: "\f12d"; +$fa-var-etsy: "\f2d7"; +$fa-var-eur: "\f153"; +$fa-var-euro: "\f153"; +$fa-var-exchange: "\f0ec"; +$fa-var-exclamation: "\f12a"; +$fa-var-exclamation-circle: "\f06a"; +$fa-var-exclamation-triangle: "\f071"; +$fa-var-expand: "\f065"; +$fa-var-expeditedssl: "\f23e"; +$fa-var-external-link: "\f08e"; +$fa-var-external-link-square: "\f14c"; +$fa-var-eye: "\f06e"; +$fa-var-eye-slash: "\f070"; +$fa-var-eyedropper: "\f1fb"; +$fa-var-fa: "\f2b4"; +$fa-var-facebook: "\f09a"; +$fa-var-facebook-f: "\f09a"; +$fa-var-facebook-official: "\f230"; +$fa-var-facebook-square: "\f082"; +$fa-var-fast-backward: "\f049"; +$fa-var-fast-forward: "\f050"; +$fa-var-fax: "\f1ac"; +$fa-var-feed: "\f09e"; +$fa-var-female: "\f182"; +$fa-var-fighter-jet: "\f0fb"; +$fa-var-file: "\f15b"; +$fa-var-file-archive-o: "\f1c6"; +$fa-var-file-audio-o: "\f1c7"; +$fa-var-file-code-o: "\f1c9"; +$fa-var-file-excel-o: "\f1c3"; +$fa-var-file-image-o: "\f1c5"; +$fa-var-file-movie-o: "\f1c8"; +$fa-var-file-o: "\f016"; +$fa-var-file-pdf-o: "\f1c1"; +$fa-var-file-photo-o: "\f1c5"; +$fa-var-file-picture-o: "\f1c5"; +$fa-var-file-powerpoint-o: "\f1c4"; +$fa-var-file-sound-o: "\f1c7"; +$fa-var-file-text: "\f15c"; +$fa-var-file-text-o: "\f0f6"; +$fa-var-file-video-o: "\f1c8"; +$fa-var-file-word-o: "\f1c2"; +$fa-var-file-zip-o: "\f1c6"; +$fa-var-files-o: "\f0c5"; +$fa-var-film: "\f008"; +$fa-var-filter: "\f0b0"; +$fa-var-fire: "\f06d"; +$fa-var-fire-extinguisher: "\f134"; +$fa-var-firefox: "\f269"; +$fa-var-first-order: "\f2b0"; +$fa-var-flag: "\f024"; +$fa-var-flag-checkered: "\f11e"; +$fa-var-flag-o: "\f11d"; +$fa-var-flash: "\f0e7"; +$fa-var-flask: "\f0c3"; +$fa-var-flickr: "\f16e"; +$fa-var-floppy-o: "\f0c7"; +$fa-var-folder: "\f07b"; +$fa-var-folder-o: "\f114"; +$fa-var-folder-open: "\f07c"; +$fa-var-folder-open-o: "\f115"; +$fa-var-font: "\f031"; +$fa-var-font-awesome: "\f2b4"; +$fa-var-fonticons: "\f280"; +$fa-var-fort-awesome: "\f286"; +$fa-var-forumbee: "\f211"; +$fa-var-forward: "\f04e"; +$fa-var-foursquare: "\f180"; +$fa-var-free-code-camp: "\f2c5"; +$fa-var-frown-o: "\f119"; +$fa-var-futbol-o: "\f1e3"; +$fa-var-gamepad: "\f11b"; +$fa-var-gavel: "\f0e3"; +$fa-var-gbp: "\f154"; +$fa-var-ge: "\f1d1"; +$fa-var-gear: "\f013"; +$fa-var-gears: "\f085"; +$fa-var-genderless: "\f22d"; +$fa-var-get-pocket: "\f265"; +$fa-var-gg: "\f260"; +$fa-var-gg-circle: "\f261"; +$fa-var-gift: "\f06b"; +$fa-var-git: "\f1d3"; +$fa-var-git-square: "\f1d2"; +$fa-var-github: "\f09b"; +$fa-var-github-alt: "\f113"; +$fa-var-github-square: "\f092"; +$fa-var-gitlab: "\f296"; +$fa-var-gittip: "\f184"; +$fa-var-glass: "\f000"; +$fa-var-glide: "\f2a5"; +$fa-var-glide-g: "\f2a6"; +$fa-var-globe: "\f0ac"; +$fa-var-google: "\f1a0"; +$fa-var-google-plus: "\f0d5"; +$fa-var-google-plus-circle: "\f2b3"; +$fa-var-google-plus-official: "\f2b3"; +$fa-var-google-plus-square: "\f0d4"; +$fa-var-google-wallet: "\f1ee"; +$fa-var-graduation-cap: "\f19d"; +$fa-var-gratipay: "\f184"; +$fa-var-grav: "\f2d6"; +$fa-var-group: "\f0c0"; +$fa-var-h-square: "\f0fd"; +$fa-var-hacker-news: "\f1d4"; +$fa-var-hand-grab-o: "\f255"; +$fa-var-hand-lizard-o: "\f258"; +$fa-var-hand-o-down: "\f0a7"; +$fa-var-hand-o-left: "\f0a5"; +$fa-var-hand-o-right: "\f0a4"; +$fa-var-hand-o-up: "\f0a6"; +$fa-var-hand-paper-o: "\f256"; +$fa-var-hand-peace-o: "\f25b"; +$fa-var-hand-pointer-o: "\f25a"; +$fa-var-hand-rock-o: "\f255"; +$fa-var-hand-scissors-o: "\f257"; +$fa-var-hand-spock-o: "\f259"; +$fa-var-hand-stop-o: "\f256"; +$fa-var-handshake-o: "\f2b5"; +$fa-var-hard-of-hearing: "\f2a4"; +$fa-var-hashtag: "\f292"; +$fa-var-hdd-o: "\f0a0"; +$fa-var-header: "\f1dc"; +$fa-var-headphones: "\f025"; +$fa-var-heart: "\f004"; +$fa-var-heart-o: "\f08a"; +$fa-var-heartbeat: "\f21e"; +$fa-var-history: "\f1da"; +$fa-var-home: "\f015"; +$fa-var-hospital-o: "\f0f8"; +$fa-var-hotel: "\f236"; +$fa-var-hourglass: "\f254"; +$fa-var-hourglass-1: "\f251"; +$fa-var-hourglass-2: "\f252"; +$fa-var-hourglass-3: "\f253"; +$fa-var-hourglass-end: "\f253"; +$fa-var-hourglass-half: "\f252"; +$fa-var-hourglass-o: "\f250"; +$fa-var-hourglass-start: "\f251"; +$fa-var-houzz: "\f27c"; +$fa-var-html5: "\f13b"; +$fa-var-i-cursor: "\f246"; +$fa-var-id-badge: "\f2c1"; +$fa-var-id-card: "\f2c2"; +$fa-var-id-card-o: "\f2c3"; +$fa-var-ils: "\f20b"; +$fa-var-image: "\f03e"; +$fa-var-imdb: "\f2d8"; +$fa-var-inbox: "\f01c"; +$fa-var-indent: "\f03c"; +$fa-var-industry: "\f275"; +$fa-var-info: "\f129"; +$fa-var-info-circle: "\f05a"; +$fa-var-inr: "\f156"; +$fa-var-instagram: "\f16d"; +$fa-var-institution: "\f19c"; +$fa-var-internet-explorer: "\f26b"; +$fa-var-intersex: "\f224"; +$fa-var-ioxhost: "\f208"; +$fa-var-italic: "\f033"; +$fa-var-joomla: "\f1aa"; +$fa-var-jpy: "\f157"; +$fa-var-jsfiddle: "\f1cc"; +$fa-var-key: "\f084"; +$fa-var-keyboard-o: "\f11c"; +$fa-var-krw: "\f159"; +$fa-var-language: "\f1ab"; +$fa-var-laptop: "\f109"; +$fa-var-lastfm: "\f202"; +$fa-var-lastfm-square: "\f203"; +$fa-var-leaf: "\f06c"; +$fa-var-leanpub: "\f212"; +$fa-var-legal: "\f0e3"; +$fa-var-lemon-o: "\f094"; +$fa-var-level-down: "\f149"; +$fa-var-level-up: "\f148"; +$fa-var-life-bouy: "\f1cd"; +$fa-var-life-buoy: "\f1cd"; +$fa-var-life-ring: "\f1cd"; +$fa-var-life-saver: "\f1cd"; +$fa-var-lightbulb-o: "\f0eb"; +$fa-var-line-chart: "\f201"; +$fa-var-link: "\f0c1"; +$fa-var-linkedin: "\f0e1"; +$fa-var-linkedin-square: "\f08c"; +$fa-var-linode: "\f2b8"; +$fa-var-linux: "\f17c"; +$fa-var-list: "\f03a"; +$fa-var-list-alt: "\f022"; +$fa-var-list-ol: "\f0cb"; +$fa-var-list-ul: "\f0ca"; +$fa-var-location-arrow: "\f124"; +$fa-var-lock: "\f023"; +$fa-var-long-arrow-down: "\f175"; +$fa-var-long-arrow-left: "\f177"; +$fa-var-long-arrow-right: "\f178"; +$fa-var-long-arrow-up: "\f176"; +$fa-var-low-vision: "\f2a8"; +$fa-var-magic: "\f0d0"; +$fa-var-magnet: "\f076"; +$fa-var-mail-forward: "\f064"; +$fa-var-mail-reply: "\f112"; +$fa-var-mail-reply-all: "\f122"; +$fa-var-male: "\f183"; +$fa-var-map: "\f279"; +$fa-var-map-marker: "\f041"; +$fa-var-map-o: "\f278"; +$fa-var-map-pin: "\f276"; +$fa-var-map-signs: "\f277"; +$fa-var-mars: "\f222"; +$fa-var-mars-double: "\f227"; +$fa-var-mars-stroke: "\f229"; +$fa-var-mars-stroke-h: "\f22b"; +$fa-var-mars-stroke-v: "\f22a"; +$fa-var-maxcdn: "\f136"; +$fa-var-meanpath: "\f20c"; +$fa-var-medium: "\f23a"; +$fa-var-medkit: "\f0fa"; +$fa-var-meetup: "\f2e0"; +$fa-var-meh-o: "\f11a"; +$fa-var-mercury: "\f223"; +$fa-var-microchip: "\f2db"; +$fa-var-microphone: "\f130"; +$fa-var-microphone-slash: "\f131"; +$fa-var-minus: "\f068"; +$fa-var-minus-circle: "\f056"; +$fa-var-minus-square: "\f146"; +$fa-var-minus-square-o: "\f147"; +$fa-var-mixcloud: "\f289"; +$fa-var-mobile: "\f10b"; +$fa-var-mobile-phone: "\f10b"; +$fa-var-modx: "\f285"; +$fa-var-money: "\f0d6"; +$fa-var-moon-o: "\f186"; +$fa-var-mortar-board: "\f19d"; +$fa-var-motorcycle: "\f21c"; +$fa-var-mouse-pointer: "\f245"; +$fa-var-music: "\f001"; +$fa-var-navicon: "\f0c9"; +$fa-var-neuter: "\f22c"; +$fa-var-newspaper-o: "\f1ea"; +$fa-var-object-group: "\f247"; +$fa-var-object-ungroup: "\f248"; +$fa-var-odnoklassniki: "\f263"; +$fa-var-odnoklassniki-square: "\f264"; +$fa-var-opencart: "\f23d"; +$fa-var-openid: "\f19b"; +$fa-var-opera: "\f26a"; +$fa-var-optin-monster: "\f23c"; +$fa-var-outdent: "\f03b"; +$fa-var-pagelines: "\f18c"; +$fa-var-paint-brush: "\f1fc"; +$fa-var-paper-plane: "\f1d8"; +$fa-var-paper-plane-o: "\f1d9"; +$fa-var-paperclip: "\f0c6"; +$fa-var-paragraph: "\f1dd"; +$fa-var-paste: "\f0ea"; +$fa-var-pause: "\f04c"; +$fa-var-pause-circle: "\f28b"; +$fa-var-pause-circle-o: "\f28c"; +$fa-var-paw: "\f1b0"; +$fa-var-paypal: "\f1ed"; +$fa-var-pencil: "\f040"; +$fa-var-pencil-square: "\f14b"; +$fa-var-pencil-square-o: "\f044"; +$fa-var-percent: "\f295"; +$fa-var-phone: "\f095"; +$fa-var-phone-square: "\f098"; +$fa-var-photo: "\f03e"; +$fa-var-picture-o: "\f03e"; +$fa-var-pie-chart: "\f200"; +$fa-var-pied-piper: "\f2ae"; +$fa-var-pied-piper-alt: "\f1a8"; +$fa-var-pied-piper-pp: "\f1a7"; +$fa-var-pinterest: "\f0d2"; +$fa-var-pinterest-p: "\f231"; +$fa-var-pinterest-square: "\f0d3"; +$fa-var-plane: "\f072"; +$fa-var-play: "\f04b"; +$fa-var-play-circle: "\f144"; +$fa-var-play-circle-o: "\f01d"; +$fa-var-plug: "\f1e6"; +$fa-var-plus: "\f067"; +$fa-var-plus-circle: "\f055"; +$fa-var-plus-square: "\f0fe"; +$fa-var-plus-square-o: "\f196"; +$fa-var-podcast: "\f2ce"; +$fa-var-power-off: "\f011"; +$fa-var-print: "\f02f"; +$fa-var-product-hunt: "\f288"; +$fa-var-puzzle-piece: "\f12e"; +$fa-var-qq: "\f1d6"; +$fa-var-qrcode: "\f029"; +$fa-var-question: "\f128"; +$fa-var-question-circle: "\f059"; +$fa-var-question-circle-o: "\f29c"; +$fa-var-quora: "\f2c4"; +$fa-var-quote-left: "\f10d"; +$fa-var-quote-right: "\f10e"; +$fa-var-ra: "\f1d0"; +$fa-var-random: "\f074"; +$fa-var-ravelry: "\f2d9"; +$fa-var-rebel: "\f1d0"; +$fa-var-recycle: "\f1b8"; +$fa-var-reddit: "\f1a1"; +$fa-var-reddit-alien: "\f281"; +$fa-var-reddit-square: "\f1a2"; +$fa-var-refresh: "\f021"; +$fa-var-registered: "\f25d"; +$fa-var-remove: "\f00d"; +$fa-var-renren: "\f18b"; +$fa-var-reorder: "\f0c9"; +$fa-var-repeat: "\f01e"; +$fa-var-reply: "\f112"; +$fa-var-reply-all: "\f122"; +$fa-var-resistance: "\f1d0"; +$fa-var-retweet: "\f079"; +$fa-var-rmb: "\f157"; +$fa-var-road: "\f018"; +$fa-var-rocket: "\f135"; +$fa-var-rotate-left: "\f0e2"; +$fa-var-rotate-right: "\f01e"; +$fa-var-rouble: "\f158"; +$fa-var-rss: "\f09e"; +$fa-var-rss-square: "\f143"; +$fa-var-rub: "\f158"; +$fa-var-ruble: "\f158"; +$fa-var-rupee: "\f156"; +$fa-var-s15: "\f2cd"; +$fa-var-safari: "\f267"; +$fa-var-save: "\f0c7"; +$fa-var-scissors: "\f0c4"; +$fa-var-scribd: "\f28a"; +$fa-var-search: "\f002"; +$fa-var-search-minus: "\f010"; +$fa-var-search-plus: "\f00e"; +$fa-var-sellsy: "\f213"; +$fa-var-send: "\f1d8"; +$fa-var-send-o: "\f1d9"; +$fa-var-server: "\f233"; +$fa-var-share: "\f064"; +$fa-var-share-alt: "\f1e0"; +$fa-var-share-alt-square: "\f1e1"; +$fa-var-share-square: "\f14d"; +$fa-var-share-square-o: "\f045"; +$fa-var-shekel: "\f20b"; +$fa-var-sheqel: "\f20b"; +$fa-var-shield: "\f132"; +$fa-var-ship: "\f21a"; +$fa-var-shirtsinbulk: "\f214"; +$fa-var-shopping-bag: "\f290"; +$fa-var-shopping-basket: "\f291"; +$fa-var-shopping-cart: "\f07a"; +$fa-var-shower: "\f2cc"; +$fa-var-sign-in: "\f090"; +$fa-var-sign-language: "\f2a7"; +$fa-var-sign-out: "\f08b"; +$fa-var-signal: "\f012"; +$fa-var-signing: "\f2a7"; +$fa-var-simplybuilt: "\f215"; +$fa-var-sitemap: "\f0e8"; +$fa-var-skyatlas: "\f216"; +$fa-var-skype: "\f17e"; +$fa-var-slack: "\f198"; +$fa-var-sliders: "\f1de"; +$fa-var-slideshare: "\f1e7"; +$fa-var-smile-o: "\f118"; +$fa-var-snapchat: "\f2ab"; +$fa-var-snapchat-ghost: "\f2ac"; +$fa-var-snapchat-square: "\f2ad"; +$fa-var-snowflake-o: "\f2dc"; +$fa-var-soccer-ball-o: "\f1e3"; +$fa-var-sort: "\f0dc"; +$fa-var-sort-alpha-asc: "\f15d"; +$fa-var-sort-alpha-desc: "\f15e"; +$fa-var-sort-amount-asc: "\f160"; +$fa-var-sort-amount-desc: "\f161"; +$fa-var-sort-asc: "\f0de"; +$fa-var-sort-desc: "\f0dd"; +$fa-var-sort-down: "\f0dd"; +$fa-var-sort-numeric-asc: "\f162"; +$fa-var-sort-numeric-desc: "\f163"; +$fa-var-sort-up: "\f0de"; +$fa-var-soundcloud: "\f1be"; +$fa-var-space-shuttle: "\f197"; +$fa-var-spinner: "\f110"; +$fa-var-spoon: "\f1b1"; +$fa-var-spotify: "\f1bc"; +$fa-var-square: "\f0c8"; +$fa-var-square-o: "\f096"; +$fa-var-stack-exchange: "\f18d"; +$fa-var-stack-overflow: "\f16c"; +$fa-var-star: "\f005"; +$fa-var-star-half: "\f089"; +$fa-var-star-half-empty: "\f123"; +$fa-var-star-half-full: "\f123"; +$fa-var-star-half-o: "\f123"; +$fa-var-star-o: "\f006"; +$fa-var-steam: "\f1b6"; +$fa-var-steam-square: "\f1b7"; +$fa-var-step-backward: "\f048"; +$fa-var-step-forward: "\f051"; +$fa-var-stethoscope: "\f0f1"; +$fa-var-sticky-note: "\f249"; +$fa-var-sticky-note-o: "\f24a"; +$fa-var-stop: "\f04d"; +$fa-var-stop-circle: "\f28d"; +$fa-var-stop-circle-o: "\f28e"; +$fa-var-street-view: "\f21d"; +$fa-var-strikethrough: "\f0cc"; +$fa-var-stumbleupon: "\f1a4"; +$fa-var-stumbleupon-circle: "\f1a3"; +$fa-var-subscript: "\f12c"; +$fa-var-subway: "\f239"; +$fa-var-suitcase: "\f0f2"; +$fa-var-sun-o: "\f185"; +$fa-var-superpowers: "\f2dd"; +$fa-var-superscript: "\f12b"; +$fa-var-support: "\f1cd"; +$fa-var-table: "\f0ce"; +$fa-var-tablet: "\f10a"; +$fa-var-tachometer: "\f0e4"; +$fa-var-tag: "\f02b"; +$fa-var-tags: "\f02c"; +$fa-var-tasks: "\f0ae"; +$fa-var-taxi: "\f1ba"; +$fa-var-telegram: "\f2c6"; +$fa-var-television: "\f26c"; +$fa-var-tencent-weibo: "\f1d5"; +$fa-var-terminal: "\f120"; +$fa-var-text-height: "\f034"; +$fa-var-text-width: "\f035"; +$fa-var-th: "\f00a"; +$fa-var-th-large: "\f009"; +$fa-var-th-list: "\f00b"; +$fa-var-themeisle: "\f2b2"; +$fa-var-thermometer: "\f2c7"; +$fa-var-thermometer-0: "\f2cb"; +$fa-var-thermometer-1: "\f2ca"; +$fa-var-thermometer-2: "\f2c9"; +$fa-var-thermometer-3: "\f2c8"; +$fa-var-thermometer-4: "\f2c7"; +$fa-var-thermometer-empty: "\f2cb"; +$fa-var-thermometer-full: "\f2c7"; +$fa-var-thermometer-half: "\f2c9"; +$fa-var-thermometer-quarter: "\f2ca"; +$fa-var-thermometer-three-quarters: "\f2c8"; +$fa-var-thumb-tack: "\f08d"; +$fa-var-thumbs-down: "\f165"; +$fa-var-thumbs-o-down: "\f088"; +$fa-var-thumbs-o-up: "\f087"; +$fa-var-thumbs-up: "\f164"; +$fa-var-ticket: "\f145"; +$fa-var-times: "\f00d"; +$fa-var-times-circle: "\f057"; +$fa-var-times-circle-o: "\f05c"; +$fa-var-times-rectangle: "\f2d3"; +$fa-var-times-rectangle-o: "\f2d4"; +$fa-var-tint: "\f043"; +$fa-var-toggle-down: "\f150"; +$fa-var-toggle-left: "\f191"; +$fa-var-toggle-off: "\f204"; +$fa-var-toggle-on: "\f205"; +$fa-var-toggle-right: "\f152"; +$fa-var-toggle-up: "\f151"; +$fa-var-trademark: "\f25c"; +$fa-var-train: "\f238"; +$fa-var-transgender: "\f224"; +$fa-var-transgender-alt: "\f225"; +$fa-var-trash: "\f1f8"; +$fa-var-trash-o: "\f014"; +$fa-var-tree: "\f1bb"; +$fa-var-trello: "\f181"; +$fa-var-tripadvisor: "\f262"; +$fa-var-trophy: "\f091"; +$fa-var-truck: "\f0d1"; +$fa-var-try: "\f195"; +$fa-var-tty: "\f1e4"; +$fa-var-tumblr: "\f173"; +$fa-var-tumblr-square: "\f174"; +$fa-var-turkish-lira: "\f195"; +$fa-var-tv: "\f26c"; +$fa-var-twitch: "\f1e8"; +$fa-var-twitter: "\f099"; +$fa-var-twitter-square: "\f081"; +$fa-var-umbrella: "\f0e9"; +$fa-var-underline: "\f0cd"; +$fa-var-undo: "\f0e2"; +$fa-var-universal-access: "\f29a"; +$fa-var-university: "\f19c"; +$fa-var-unlink: "\f127"; +$fa-var-unlock: "\f09c"; +$fa-var-unlock-alt: "\f13e"; +$fa-var-unsorted: "\f0dc"; +$fa-var-upload: "\f093"; +$fa-var-usb: "\f287"; +$fa-var-usd: "\f155"; +$fa-var-user: "\f007"; +$fa-var-user-circle: "\f2bd"; +$fa-var-user-circle-o: "\f2be"; +$fa-var-user-md: "\f0f0"; +$fa-var-user-o: "\f2c0"; +$fa-var-user-plus: "\f234"; +$fa-var-user-secret: "\f21b"; +$fa-var-user-times: "\f235"; +$fa-var-users: "\f0c0"; +$fa-var-vcard: "\f2bb"; +$fa-var-vcard-o: "\f2bc"; +$fa-var-venus: "\f221"; +$fa-var-venus-double: "\f226"; +$fa-var-venus-mars: "\f228"; +$fa-var-viacoin: "\f237"; +$fa-var-viadeo: "\f2a9"; +$fa-var-viadeo-square: "\f2aa"; +$fa-var-video-camera: "\f03d"; +$fa-var-vimeo: "\f27d"; +$fa-var-vimeo-square: "\f194"; +$fa-var-vine: "\f1ca"; +$fa-var-vk: "\f189"; +$fa-var-volume-control-phone: "\f2a0"; +$fa-var-volume-down: "\f027"; +$fa-var-volume-off: "\f026"; +$fa-var-volume-up: "\f028"; +$fa-var-warning: "\f071"; +$fa-var-wechat: "\f1d7"; +$fa-var-weibo: "\f18a"; +$fa-var-weixin: "\f1d7"; +$fa-var-whatsapp: "\f232"; +$fa-var-wheelchair: "\f193"; +$fa-var-wheelchair-alt: "\f29b"; +$fa-var-wifi: "\f1eb"; +$fa-var-wikipedia-w: "\f266"; +$fa-var-window-close: "\f2d3"; +$fa-var-window-close-o: "\f2d4"; +$fa-var-window-maximize: "\f2d0"; +$fa-var-window-minimize: "\f2d1"; +$fa-var-window-restore: "\f2d2"; +$fa-var-windows: "\f17a"; +$fa-var-won: "\f159"; +$fa-var-wordpress: "\f19a"; +$fa-var-wpbeginner: "\f297"; +$fa-var-wpexplorer: "\f2de"; +$fa-var-wpforms: "\f298"; +$fa-var-wrench: "\f0ad"; +$fa-var-xing: "\f168"; +$fa-var-xing-square: "\f169"; +$fa-var-y-combinator: "\f23b"; +$fa-var-y-combinator-square: "\f1d4"; +$fa-var-yahoo: "\f19e"; +$fa-var-yc: "\f23b"; +$fa-var-yc-square: "\f1d4"; +$fa-var-yelp: "\f1e9"; +$fa-var-yen: "\f157"; +$fa-var-yoast: "\f2b1"; +$fa-var-youtube: "\f167"; +$fa-var-youtube-play: "\f16a"; +$fa-var-youtube-square: "\f166"; diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index 8d9d4fc6b7d..6568414ed88 100644 --- a/public/sass/components/_drop.scss +++ b/public/sass/components/_drop.scss @@ -5,13 +5,13 @@ $useDropShadow: false; $attachmentOffset: 0%; $easing: cubic-bezier(0, 0, 0.265, 1); -@include drop-theme('error', $popover-error-bg, $popover-color); -@include drop-theme('popover', $popover-bg, $popover-color, $popover-border-color); -@include drop-theme('help', $popover-help-bg, $popover-help-color); +@include drop-theme("error", $popover-error-bg, $popover-color); +@include drop-theme("popover", $popover-bg, $popover-color, $popover-border-color); +@include drop-theme("help", $popover-help-bg, $popover-help-color); -@include drop-animation-scale('drop', 'help', $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale('drop', 'error', $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale('drop', 'popover', $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale("drop", "help", $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale("drop", "error", $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale("drop", "popover", $attachmentOffset: $attachmentOffset, $easing: $easing); .drop-element { z-index: 10000; diff --git a/public/sass/components/_filter-list.scss b/public/sass/components/_filter-list.scss index 90d0a21c539..7713aa05ac2 100644 --- a/public/sass/components/_filter-list.scss +++ b/public/sass/components/_filter-list.scss @@ -67,17 +67,17 @@ text-transform: uppercase; &.online { - background-image: url('/img/online.svg'); + background-image: url("/img/online.svg"); color: $online; } &.warn { - background-image: url('/img/warn-tiny.svg'); + background-image: url("/img/warn-tiny.svg"); color: $warn; } &.critical { - background-image: url('/img/critical.svg'); + background-image: url("/img/critical.svg"); color: $critical; } } diff --git a/public/sass/components/_footer.scss b/public/sass/components/_footer.scss index cec6f820118..8b7d64e47fe 100644 --- a/public/sass/components/_footer.scss +++ b/public/sass/components/_footer.scss @@ -25,7 +25,7 @@ display: inline-block; padding-right: 2px; &::after { - content: ' | '; + content: " | "; padding-left: 2px; } } @@ -33,7 +33,7 @@ li:last-child { &::after { padding-left: 0; - content: ''; + content: ""; } } } diff --git a/public/sass/components/_json_explorer.scss b/public/sass/components/_json_explorer.scss index aa212cd2dab..2b1be8bd4f5 100644 --- a/public/sass/components/_json_explorer.scss +++ b/public/sass/components/_json_explorer.scss @@ -21,10 +21,10 @@ display: none; } &.json-formatter-object::after { - content: 'No properties'; + content: "No properties"; } &.json-formatter-array::after { - content: '[]'; + content: "[]"; } } } @@ -87,7 +87,7 @@ &::after { display: inline-block; transition: transform $json-explorer-rotate-time ease-in; - content: '►'; + content: "►"; } } diff --git a/public/sass/components/_jsontree.scss b/public/sass/components/_jsontree.scss index 0a0497a0627..665deda0f12 100644 --- a/public/sass/components/_jsontree.scss +++ b/public/sass/components/_jsontree.scss @@ -35,12 +35,12 @@ json-tree { color: $variable; padding: 5px 10px 5px 15px; &::after { - content: ':'; + content: ":"; } } json-node.expandable { &::before { - content: '\25b6'; + content: "\25b6"; position: absolute; left: 0px; font-size: 8px; diff --git a/public/sass/components/_panel_gettingstarted.scss b/public/sass/components/_panel_gettingstarted.scss index 7b935e3707d..1fb3eda1834 100644 --- a/public/sass/components/_panel_gettingstarted.scss +++ b/public/sass/components/_panel_gettingstarted.scss @@ -52,7 +52,7 @@ $path-position: $marker-size-half - ($path-height / 2); &::after { right: -50%; - content: ''; + content: ""; display: block; position: absolute; z-index: 1; @@ -105,7 +105,7 @@ $path-position: $marker-size-half - ($path-height / 2); // change icon to check .icon-gf::before { - content: '\e604'; + content: "\e604"; } } .progress-text { diff --git a/public/sass/components/_row.scss b/public/sass/components/_row.scss index 9cd564a4edf..3c1465a30bc 100644 --- a/public/sass/components/_row.scss +++ b/public/sass/components/_row.scss @@ -69,7 +69,7 @@ cursor: move; width: 1rem; height: 100%; - background: url('../img/grab_dark.svg') no-repeat 50% 50%; + background: url("../img/grab_dark.svg") no-repeat 50% 50%; background-size: 8px; visibility: hidden; position: absolute; diff --git a/public/sass/components/_shortcuts.scss b/public/sass/components/_shortcuts.scss index 83e112648cf..b5f61872585 100644 --- a/public/sass/components/_shortcuts.scss +++ b/public/sass/components/_shortcuts.scss @@ -33,7 +33,7 @@ text-align: center; margin-right: 0.3rem; padding: 3px 5px; - font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; line-height: 10px; color: #555; vertical-align: middle; diff --git a/public/sass/components/_switch.scss b/public/sass/components/_switch.scss index 11ab2e3554a..c7eb1914103 100644 --- a/public/sass/components/_switch.scss +++ b/public/sass/components/_switch.scss @@ -64,8 +64,8 @@ } input + label::before { - font-family: 'FontAwesome'; - content: '\f096'; // square-o + font-family: "FontAwesome"; + content: "\f096"; // square-o color: $text-color-weak; transition: transform 0.4s; backface-visibility: hidden; @@ -73,11 +73,11 @@ } input + label::after { - content: '\f046'; // check-square-o + content: "\f046"; // check-square-o color: $orange; text-shadow: $text-shadow-strong; - font-family: 'FontAwesome'; + font-family: "FontAwesome"; transition: transform 0.4s; transform: rotateY(180deg); backface-visibility: hidden; diff --git a/public/sass/components/_tabs.scss b/public/sass/components/_tabs.scss index 44a116cd0a4..197d5892652 100644 --- a/public/sass/components/_tabs.scss +++ b/public/sass/components/_tabs.scss @@ -44,13 +44,18 @@ &::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% + ); } } } diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index b9e39ae9e04..2d7a12c3d01 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -103,10 +103,10 @@ } .fa-chevron-left::before { - content: '\f053'; + content: "\f053"; } .fa-chevron-right::before { - content: '\f054'; + content: "\f054"; } .glyphicon-chevron-right { diff --git a/public/sass/grafana.dark.scss b/public/sass/grafana.dark.scss index f7f5163f36f..53193d213e6 100644 --- a/public/sass/grafana.dark.scss +++ b/public/sass/grafana.dark.scss @@ -1,3 +1,3 @@ -@import 'variables'; -@import 'variables.dark'; -@import 'grafana'; +@import "variables"; +@import "variables.dark"; +@import "grafana"; diff --git a/public/sass/mixins/_drop_element.scss b/public/sass/mixins/_drop_element.scss index eb354b219fc..e7e53382e0e 100644 --- a/public/sass/mixins/_drop_element.scss +++ b/public/sass/mixins/_drop_element.scss @@ -15,7 +15,7 @@ border: 1px solid $border-color; &:before { - content: ''; + content: ""; display: block; position: absolute; width: 0; @@ -88,7 +88,8 @@ left: $popover-arrow-size * 2; } - &.drop-element-attached-top.drop-element-attached-left.drop-target-attached-middle .drop-content { + &.drop-element-attached-top.drop-element-attached-left.drop-target-attached-middle + .drop-content { margin-top: $popover-arrow-size; &:before { @@ -98,7 +99,8 @@ } } - &.drop-element-attached-top.drop-element-attached-right.drop-target-attached-middle .drop-content { + &.drop-element-attached-top.drop-element-attached-right.drop-target-attached-middle + .drop-content { margin-top: $popover-arrow-size; &:before { @@ -108,7 +110,8 @@ } } - &.drop-element-attached-bottom.drop-element-attached-left.drop-target-attached-middle .drop-content { + &.drop-element-attached-bottom.drop-element-attached-left.drop-target-attached-middle + .drop-content { margin-bottom: $popover-arrow-size; &:before { @@ -118,7 +121,8 @@ } } - &.drop-element-attached-bottom.drop-element-attached-right.drop-target-attached-middle .drop-content { + &.drop-element-attached-bottom.drop-element-attached-right.drop-target-attached-middle + .drop-content { margin-bottom: $popover-arrow-size; &:before { @@ -129,7 +133,8 @@ } // Top and bottom corners - &.drop-element-attached-top.drop-element-attached-left.drop-target-attached-bottom .drop-content { + &.drop-element-attached-top.drop-element-attached-left.drop-target-attached-bottom + .drop-content { margin-top: $popover-arrow-size; &:before { @@ -139,7 +144,8 @@ } } - &.drop-element-attached-top.drop-element-attached-right.drop-target-attached-bottom .drop-content { + &.drop-element-attached-top.drop-element-attached-right.drop-target-attached-bottom + .drop-content { margin-top: $popover-arrow-size; &:before { @@ -149,7 +155,8 @@ } } - &.drop-element-attached-bottom.drop-element-attached-left.drop-target-attached-top .drop-content { + &.drop-element-attached-bottom.drop-element-attached-left.drop-target-attached-top + .drop-content { margin-bottom: $popover-arrow-size; &:before { @@ -159,7 +166,8 @@ } } - &.drop-element-attached-bottom.drop-element-attached-right.drop-target-attached-top .drop-content { + &.drop-element-attached-bottom.drop-element-attached-right.drop-target-attached-top + .drop-content { margin-bottom: $popover-arrow-size; &:before { @@ -170,7 +178,8 @@ } // Side corners - &.drop-element-attached-top.drop-element-attached-right.drop-target-attached-left .drop-content { + &.drop-element-attached-top.drop-element-attached-right.drop-target-attached-left + .drop-content { margin-right: $popover-arrow-size; &:before { @@ -180,7 +189,8 @@ } } - &.drop-element-attached-top.drop-element-attached-left.drop-target-attached-right .drop-content { + &.drop-element-attached-top.drop-element-attached-left.drop-target-attached-right + .drop-content { margin-left: $popover-arrow-size; &:before { @@ -190,7 +200,8 @@ } } - &.drop-element-attached-bottom.drop-element-attached-right.drop-target-attached-left .drop-content { + &.drop-element-attached-bottom.drop-element-attached-right.drop-target-attached-left + .drop-content { margin-right: $popover-arrow-size; &:before { @@ -200,7 +211,8 @@ } } - &.drop-element-attached-bottom.drop-element-attached-left.drop-target-attached-right .drop-content { + &.drop-element-attached-bottom.drop-element-attached-left.drop-target-attached-right + .drop-content { margin-left: $popover-arrow-size; &:before { @@ -212,7 +224,7 @@ } } -@mixin drop-animation-scale($themePrefix: 'drop', $themeName: 'default', $attachmentOffset: 0, $easing: 'linear') { +@mixin drop-animation-scale($themePrefix: "drop", $themeName: "default", $attachmentOffset: 0, $easing: "linear") { .#{$themePrefix}-element.#{$themePrefix}-#{$themeName} { transform: translateZ(0); transition: opacity 100ms; @@ -235,16 +247,20 @@ } } // Centers and middles - &.#{$themePrefix}-element-attached-bottom.#{$themePrefix}-element-attached-center .#{$themePrefix}-content { + &.#{$themePrefix}-element-attached-bottom.#{$themePrefix}-element-attached-center + .#{$themePrefix}-content { transform-origin: 50% calc(100% + #{$attachmentOffset}); } - &.#{$themePrefix}-element-attached-top.#{$themePrefix}-element-attached-center .#{$themePrefix}-content { + &.#{$themePrefix}-element-attached-top.#{$themePrefix}-element-attached-center + .#{$themePrefix}-content { transform-origin: 50% (-$attachmentOffset); } - &.#{$themePrefix}-element-attached-right.#{$themePrefix}-element-attached-middle .#{$themePrefix}-content { + &.#{$themePrefix}-element-attached-right.#{$themePrefix}-element-attached-middle + .#{$themePrefix}-content { transform-origin: calc(100% + #{$attachmentOffset}) 50%; } - &.#{$themePrefix}-element-attached-left.#{$themePrefix}-element-attached-middle .#{$themePrefix}-content { + &.#{$themePrefix}-element-attached-left.#{$themePrefix}-element-attached-middle + .#{$themePrefix}-content { transform-origin: -($attachmentOffset 50%); } // Top and bottom corners diff --git a/public/sass/mixins/_forms.scss b/public/sass/mixins/_forms.scss index 2f163e0f46b..ce488f0f636 100644 --- a/public/sass/mixins/_forms.scss +++ b/public/sass/mixins/_forms.scss @@ -41,7 +41,8 @@ &:focus { border-color: $input-border-focus; outline: none; - $shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px $input-box-shadow-focus; + $shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), + 0 0 5px $input-box-shadow-focus; @include box-shadow($shadow); } } diff --git a/public/sass/mixins/_mixins.scss b/public/sass/mixins/_mixins.scss index 2e3c2fdcdd8..f3be6af56ba 100644 --- a/public/sass/mixins/_mixins.scss +++ b/public/sass/mixins/_mixins.scss @@ -1,6 +1,6 @@ @mixin clearfix() { &::after { - content: ''; + content: ""; display: table; clear: both; } @@ -265,10 +265,20 @@ // Add an alphatransparency value to any background or border color (via Elyse Holladay) #translucent { @mixin background($color: $white, $alpha: 1) { - background-color: hsla(hue($color), saturation($color), lightness($color), $alpha); + background-color: hsla( + hue($color), + saturation($color), + lightness($color), + $alpha + ); } @mixin border($color: $white, $alpha: 1) { - border-color: hsla(hue($color), saturation($color), lightness($color), $alpha); + border-color: hsla( + hue($color), + saturation($color), + lightness($color), + $alpha + ); @include background-clip(padding-box); } } @@ -284,37 +294,66 @@ // Gradients @mixin gradient-horizontal($startColor: #555, $endColor: #333) { background-color: $endColor; - background-image: linear-gradient(to right, $startColor, $endColor); // Standard, IE10 + background-image: linear-gradient( + to right, + $startColor, + $endColor + ); // Standard, IE10 background-repeat: repeat-x; } @mixin gradient-vertical($startColor: #555, $endColor: #333) { background-color: mix($startColor, $endColor, 60%); - background-image: linear-gradient(to bottom, $startColor, $endColor); // Standard, IE10 + background-image: linear-gradient( + to bottom, + $startColor, + $endColor + ); // Standard, IE10 background-repeat: repeat-x; } @mixin gradient-directional($startColor: #555, $endColor: #333, $deg: 45deg) { background-color: $endColor; background-repeat: repeat-x; - background-image: linear-gradient($deg, $startColor, $endColor); // Standard, IE10 + background-image: linear-gradient( + $deg, + $startColor, + $endColor + ); // Standard, IE10 } @mixin gradient-horizontal-three-colors($startColor: #00b3ee, $midColor: #7a43b6, $colorStop: 50%, $endColor: #c3325f) { background-color: mix($midColor, $endColor, 80%); - background-image: linear-gradient(to right, $startColor, $midColor $colorStop, $endColor); + background-image: linear-gradient( + to right, + $startColor, + $midColor $colorStop, + $endColor + ); background-repeat: no-repeat; } @mixin gradient-vertical-three-colors($startColor: #00b3ee, $midColor: #7a43b6, $colorStop: 50%, $endColor: #c3325f) { background-color: mix($midColor, $endColor, 80%); - background-image: linear-gradient($startColor, $midColor $colorStop, $endColor); + background-image: linear-gradient( + $startColor, + $midColor $colorStop, + $endColor + ); background-repeat: no-repeat; } @mixin gradient-radial($innerColor: #555, $outerColor: #333) { background-color: $outerColor; - background-image: -webkit-gradient(radial, center center, 0, center center, 460, from($innerColor), to($outerColor)); + background-image: -webkit-gradient( + radial, + center center, + 0, + center center, + 460, + from($innerColor), + to($outerColor) + ); background-image: -webkit-radial-gradient(circle, $innerColor, $outerColor); background-image: -moz-radial-gradient(circle, $innerColor, $outerColor); background-image: -o-radial-gradient(circle, $innerColor, $outerColor); @@ -341,7 +380,11 @@ @mixin left-brand-border-gradient() { border: none; - border-image: linear-gradient(rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%); + border-image: linear-gradient( + rgba(255, 213, 0, 1) 0%, + rgba(255, 68, 0, 1) 99%, + rgba(255, 68, 0, 1) 100% + ); border-image-slice: 1; border-style: solid; border-top: 0; diff --git a/public/sass/pages/_login.scss b/public/sass/pages/_login.scss index ce1e9eb4ea1..8622eec4e99 100644 --- a/public/sass/pages/_login.scss +++ b/public/sass/pages/_login.scss @@ -371,7 +371,7 @@ select:-webkit-autofill:focus { left: 0; right: 0; height: 100%; - content: ''; + content: ""; display: block; } diff --git a/public/sass/pages/_playlist.scss b/public/sass/pages/_playlist.scss index 37fa7f17e20..5dd1c92cbd2 100644 --- a/public/sass/pages/_playlist.scss +++ b/public/sass/pages/_playlist.scss @@ -84,11 +84,11 @@ background-color: $list-item-bg; margin-bottom: 4px; .search-result-icon:before { - content: '\f009'; + content: "\f009"; } &.search-item-dash-home .search-result-icon:before { - content: '\f015'; + content: "\f015"; } } diff --git a/public/sass/utils/_validation.scss b/public/sass/utils/_validation.scss index c074d847490..86b7c008bfd 100644 --- a/public/sass/utils/_validation.scss +++ b/public/sass/utils/_validation.scss @@ -1,4 +1,4 @@ -input[type='text'].ng-dirty.ng-invalid { +input[type="text"].ng-dirty.ng-invalid { } input.validation-error, diff --git a/public/test/core/utils/version_specs.ts b/public/test/core/utils/version_specs.ts index 9abc4f326fc..a057c8e16bd 100644 --- a/public/test/core/utils/version_specs.ts +++ b/public/test/core/utils/version_specs.ts @@ -1,8 +1,8 @@ -import { describe, beforeEach, it, expect } from 'test/lib/common'; +import {describe, beforeEach, it, expect} from 'test/lib/common'; -import { SemVersion, isVersionGtOrEq } from 'app/core/utils/version'; +import {SemVersion, isVersionGtOrEq} from 'app/core/utils/version'; -describe('SemVersion', () => { +describe("SemVersion", () => { let version = '1.0.0-alpha.1'; describe('parsing', () => { @@ -23,13 +23,13 @@ describe('SemVersion', () => { it('should detect greater version properly', () => { let semver = new SemVersion(version); let cases = [ - { value: '3.4.5', expected: true }, - { value: '3.4.4', expected: true }, - { value: '3.4.6', expected: false }, - { value: '4', expected: false }, - { value: '3.5', expected: false }, + {value: '3.4.5', expected: true}, + {value: '3.4.4', expected: true}, + {value: '3.4.6', expected: false}, + {value: '4', expected: false}, + {value: '3.5', expected: false}, ]; - cases.forEach(testCase => { + cases.forEach((testCase) => { expect(semver.isGtOrEq(testCase.value)).to.be(testCase.expected); }); }); @@ -38,16 +38,16 @@ describe('SemVersion', () => { describe('isVersionGtOrEq', () => { it('should compare versions properly (a >= b)', () => { let cases = [ - { values: ['3.4.5', '3.4.5'], expected: true }, - { values: ['3.4.5', '3.4.4'], expected: true }, - { values: ['3.4.5', '3.4.6'], expected: false }, - { values: ['3.4', '3.4.0'], expected: true }, - { values: ['3', '3.0.0'], expected: true }, - { values: ['3.1.1-beta1', '3.1'], expected: true }, - { values: ['3.4.5', '4'], expected: false }, - { values: ['3.4.5', '3.5'], expected: false }, + {values: ['3.4.5', '3.4.5'], expected: true}, + {values: ['3.4.5', '3.4.4'] , expected: true}, + {values: ['3.4.5', '3.4.6'], expected: false}, + {values: ['3.4', '3.4.0'], expected: true}, + {values: ['3', '3.0.0'], expected: true}, + {values: ['3.1.1-beta1', '3.1'], expected: true}, + {values: ['3.4.5', '4'], expected: false}, + {values: ['3.4.5', '3.5'], expected: false}, ]; - cases.forEach(testCase => { + cases.forEach((testCase) => { expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).to.be(testCase.expected); }); }); diff --git a/public/test/jest-shim.ts b/public/test/jest-shim.ts index f9af0c4a3f3..80c4bb3d21b 100644 --- a/public/test/jest-shim.ts +++ b/public/test/jest-shim.ts @@ -1,5 +1,6 @@ declare var global: NodeJS.Global; -(global).requestAnimationFrame = callback => { +(global).requestAnimationFrame = (callback) => { setTimeout(callback, 0); }; + diff --git a/public/test/mocks/backend_srv.ts b/public/test/mocks/backend_srv.ts index 32c04866395..666de593722 100644 --- a/public/test/mocks/backend_srv.ts +++ b/public/test/mocks/backend_srv.ts @@ -1,5 +1,8 @@ export class BackendSrvMock { search: any; - constructor() {} + constructor() { + } + } + diff --git a/public/test/specs/helpers.ts b/public/test/specs/helpers.ts index b92e853d8f8..8e83915362f 100644 --- a/public/test/specs/helpers.ts +++ b/public/test/specs/helpers.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; import config from 'app/core/config'; import * as dateMath from 'app/core/utils/datemath'; -import { angularMocks, sinon } from '../lib/common'; -import { PanelModel } from 'app/features/dashboard/panel_model'; +import {angularMocks, sinon} from '../lib/common'; +import {PanelModel} from 'app/features/dashboard/panel_model'; export function ControllerTestContext() { var self = this; @@ -42,8 +42,8 @@ export function ControllerTestContext() { self.$location = $location; self.$browser = $browser; self.$q = $q; - self.panel = new PanelModel({ type: 'test' }); - self.dashboard = { meta: {} }; + self.panel = new PanelModel({type: 'test'}); + self.dashboard = {meta: {}}; $rootScope.appEvent = sinon.spy(); $rootScope.onAppEvent = sinon.spy(); @@ -53,14 +53,14 @@ export function ControllerTestContext() { $rootScope.colors.push('#' + i); } - config.panels['test'] = { info: {} }; + config.panels['test'] = {info: {}}; self.ctrl = $controller( Ctrl, - { $scope: self.scope }, + {$scope: self.scope}, { panel: self.panel, dashboard: self.dashboard, - } + }, ); }); }; @@ -72,7 +72,7 @@ export function ControllerTestContext() { self.$browser = $browser; self.scope.contextSrv = {}; self.scope.panel = {}; - self.scope.dashboard = { meta: {} }; + self.scope.dashboard = {meta: {}}; self.scope.dashboardMeta = {}; self.scope.dashboardViewState = new DashboardViewStateStub(); self.scope.appEvent = sinon.spy(); @@ -131,7 +131,7 @@ export function DashboardViewStateStub() { export function TimeSrvStub() { this.init = sinon.spy(); - this.time = { from: 'now-1h', to: 'now' }; + this.time = {from: 'now-1h', to: 'now'}; this.timeRange = function(parse) { if (parse === false) { return this.time; @@ -159,7 +159,7 @@ export function ContextSrvStub() { export function TemplateSrvStub() { this.variables = []; - this.templateSettings = { interpolate: /\[\[([\s\S]+?)\]\]/g }; + this.templateSettings = {interpolate: /\[\[([\s\S]+?)\]\]/g}; this.data = {}; this.replace = function(text) { return _.template(text, this.templateSettings)(this.data); @@ -188,7 +188,7 @@ var allDeps = { TimeSrvStub: TimeSrvStub, ControllerTestContext: ControllerTestContext, ServiceTestContext: ServiceTestContext, - DashboardViewStateStub: DashboardViewStateStub, + DashboardViewStateStub: DashboardViewStateStub }; // for legacy diff --git a/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go b/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go index 8508063bc35..b98a7653467 100644 --- a/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go +++ b/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go @@ -457,7 +457,7 @@ func (c *Client) GetMulti(keys []string) (map[string]*Item, error) { } var err error - for range keyMap { + for _ = range keyMap { if ge := <-ch; ge != nil { err = ge } diff --git a/vendor/github.com/hashicorp/go-plugin/client.go b/vendor/github.com/hashicorp/go-plugin/client.go index de03690703f..b912826b200 100644 --- a/vendor/github.com/hashicorp/go-plugin/client.go +++ b/vendor/github.com/hashicorp/go-plugin/client.go @@ -567,7 +567,7 @@ func (c *Client) Start() (addr net.Addr, err error) { // so they don't block since it is an io.Pipe defer func() { go func() { - for range linesCh { + for _ = range linesCh { } }() }() diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_client.go b/vendor/github.com/hashicorp/go-plugin/rpc_client.go index 4d99d42c7e1..f30a4b1d387 100644 --- a/vendor/github.com/hashicorp/go-plugin/rpc_client.go +++ b/vendor/github.com/hashicorp/go-plugin/rpc_client.go @@ -75,7 +75,7 @@ func NewRPCClient(conn io.ReadWriteCloser, plugins map[string]Plugin) (*RPCClien // Connect stdout, stderr streams stdstream := make([]net.Conn, 2) - for i := range stdstream { + for i, _ := range stdstream { stdstream[i], err = mux.Open() if err != nil { mux.Close() diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_server.go b/vendor/github.com/hashicorp/go-plugin/rpc_server.go index 168ef7dd944..5bb18dd5db1 100644 --- a/vendor/github.com/hashicorp/go-plugin/rpc_server.go +++ b/vendor/github.com/hashicorp/go-plugin/rpc_server.go @@ -78,7 +78,7 @@ func (s *RPCServer) ServeConn(conn io.ReadWriteCloser) { // Connect the stdstreams (in, out, err) stdstream := make([]net.Conn, 2) - for i := range stdstream { + for i, _ := range stdstream { stdstream[i], err = mux.Accept() if err != nil { mux.Close() diff --git a/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go b/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go index 1857f93f226..82ad7bc8f1c 100644 --- a/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go +++ b/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go @@ -85,7 +85,7 @@ func (dmp *DiffMatchPatch) diffMainRunes(text1, text2 []rune, checklines bool, d // Restore the prefix and suffix. if len(commonprefix) != 0 { - diffs = append([]Diff{{DiffEqual, string(commonprefix)}}, diffs...) + diffs = append([]Diff{Diff{DiffEqual, string(commonprefix)}}, diffs...) } if len(commonsuffix) != 0 { diffs = append(diffs, Diff{DiffEqual, string(commonsuffix)}) @@ -122,16 +122,16 @@ func (dmp *DiffMatchPatch) diffCompute(text1, text2 []rune, checklines bool, dea } // Shorter text is inside the longer text (speedup). return []Diff{ - {op, string(longtext[:i])}, - {DiffEqual, string(shorttext)}, - {op, string(longtext[i+len(shorttext):])}, + Diff{op, string(longtext[:i])}, + Diff{DiffEqual, string(shorttext)}, + Diff{op, string(longtext[i+len(shorttext):])}, } } else if len(shorttext) == 1 { // Single character string. // After the previous speedup, the character can't be an equality. return []Diff{ - {DiffDelete, string(text1)}, - {DiffInsert, string(text2)}, + Diff{DiffDelete, string(text1)}, + Diff{DiffInsert, string(text2)}, } // Check to see if the problem can be split in two. } else if hm := dmp.diffHalfMatch(text1, text2); hm != nil { @@ -145,7 +145,7 @@ func (dmp *DiffMatchPatch) diffCompute(text1, text2 []rune, checklines bool, dea diffsA := dmp.diffMainRunes(text1A, text2A, checklines, deadline) diffsB := dmp.diffMainRunes(text1B, text2B, checklines, deadline) // Merge the results. - return append(diffsA, append([]Diff{{DiffEqual, string(midCommon)}}, diffsB...)...) + return append(diffsA, append([]Diff{Diff{DiffEqual, string(midCommon)}}, diffsB...)...) } else if checklines && len(text1) > 100 && len(text2) > 100 { return dmp.diffLineMode(text1, text2, deadline) } @@ -330,8 +330,8 @@ func (dmp *DiffMatchPatch) diffBisect(runes1, runes2 []rune, deadline time.Time) } // Diff took too long and hit the deadline or number of diffs equals number of characters, no commonality at all. return []Diff{ - {DiffDelete, string(runes1)}, - {DiffInsert, string(runes2)}, + Diff{DiffDelete, string(runes1)}, + Diff{DiffInsert, string(runes2)}, } } @@ -673,7 +673,7 @@ func (dmp *DiffMatchPatch) DiffCleanupSemantic(diffs []Diff) []Diff { insPoint := equalities.data diffs = append( diffs[:insPoint], - append([]Diff{{DiffDelete, lastequality}}, diffs[insPoint:]...)...) + append([]Diff{Diff{DiffDelete, lastequality}}, diffs[insPoint:]...)...) // Change second copy to insert. diffs[insPoint+1].Type = DiffInsert @@ -726,7 +726,7 @@ func (dmp *DiffMatchPatch) DiffCleanupSemantic(diffs []Diff) []Diff { // Overlap found. Insert an equality and trim the surrounding edits. diffs = append( diffs[:pointer], - append([]Diff{{DiffEqual, insertion[:overlapLength1]}}, diffs[pointer:]...)...) + append([]Diff{Diff{DiffEqual, insertion[:overlapLength1]}}, diffs[pointer:]...)...) diffs[pointer-1].Text = deletion[0 : len(deletion)-overlapLength1] @@ -955,7 +955,7 @@ func (dmp *DiffMatchPatch) DiffCleanupEfficiency(diffs []Diff) []Diff { // Duplicate record. diffs = append(diffs[:insPoint], - append([]Diff{{DiffDelete, lastequality}}, diffs[insPoint:]...)...) + append([]Diff{Diff{DiffDelete, lastequality}}, diffs[insPoint:]...)...) // Change second copy to insert. diffs[insPoint+1].Type = DiffInsert @@ -1028,7 +1028,7 @@ func (dmp *DiffMatchPatch) DiffCleanupMerge(diffs []Diff) []Diff { if x > 0 && diffs[x-1].Type == DiffEqual { diffs[x-1].Text += string(textInsert[:commonlength]) } else { - diffs = append([]Diff{{DiffEqual, string(textInsert[:commonlength])}}, diffs...) + diffs = append([]Diff{Diff{DiffEqual, string(textInsert[:commonlength])}}, diffs...) pointer++ } textInsert = textInsert[commonlength:] diff --git a/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go b/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go index 1708a96fbed..223c43c4268 100644 --- a/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go +++ b/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go @@ -93,7 +93,7 @@ func (dmp *DiffMatchPatch) PatchAddContext(patch Patch, text string) Patch { // Add the prefix. prefix := text[max(0, patch.Start2-padding):patch.Start2] if len(prefix) != 0 { - patch.diffs = append([]Diff{{DiffEqual, prefix}}, patch.diffs...) + patch.diffs = append([]Diff{Diff{DiffEqual, prefix}}, patch.diffs...) } // Add the suffix. suffix := text[patch.Start2+patch.Length1 : min(len(text), patch.Start2+patch.Length1+padding)] @@ -336,7 +336,7 @@ func (dmp *DiffMatchPatch) PatchAddPadding(patches []Patch) string { // Add some padding on start of first diff. if len(patches[0].diffs) == 0 || patches[0].diffs[0].Type != DiffEqual { // Add nullPadding equality. - patches[0].diffs = append([]Diff{{DiffEqual, nullPadding}}, patches[0].diffs...) + patches[0].diffs = append([]Diff{Diff{DiffEqual, nullPadding}}, patches[0].diffs...) patches[0].Start1 -= paddingLength // Should be 0. patches[0].Start2 -= paddingLength // Should be 0. patches[0].Length1 += paddingLength diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index a3fe975f049..e6b321f4bb6 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -321,9 +321,7 @@ func (noCachedConnError) Error() string { return "http2: no cached c // or its equivalent renamed type in net/http2's h2_bundle.go. Both types // may coexist in the same running program. func isNoCachedConnError(err error) bool { - _, ok := err.(interface { - IsHTTP2NoCachedConnError() - }) + _, ok := err.(interface{ IsHTTP2NoCachedConnError() }) return ok } diff --git a/vendor/golang.org/x/text/language/gen.go b/vendor/golang.org/x/text/language/gen.go index fea288d4621..302f1940aaf 100644 --- a/vendor/golang.org/x/text/language/gen.go +++ b/vendor/golang.org/x/text/language/gen.go @@ -1050,7 +1050,7 @@ func (b *builder) writeRegion() { m49Index := [9]int16{} fromM49 := []uint16{} m49 := []int{} - for k := range fromM49map { + for k, _ := range fromM49map { m49 = append(m49, int(k)) } sort.Ints(m49) diff --git a/vendor/golang.org/x/text/language/lookup.go b/vendor/golang.org/x/text/language/lookup.go index 96d16dac9d3..1d80ac37082 100644 --- a/vendor/golang.org/x/text/language/lookup.go +++ b/vendor/golang.org/x/text/language/lookup.go @@ -344,39 +344,39 @@ var ( // grandfatheredMap holds a mapping from legacy and grandfathered tags to // their base language or index to more elaborate tag. grandfatheredMap = map[[maxLen]byte]int16{ - {'a', 'r', 't', '-', 'l', 'o', 'j', 'b', 'a', 'n'}: _jbo, // art-lojban - {'i', '-', 'a', 'm', 'i'}: _ami, // i-ami - {'i', '-', 'b', 'n', 'n'}: _bnn, // i-bnn - {'i', '-', 'h', 'a', 'k'}: _hak, // i-hak - {'i', '-', 'k', 'l', 'i', 'n', 'g', 'o', 'n'}: _tlh, // i-klingon - {'i', '-', 'l', 'u', 'x'}: _lb, // i-lux - {'i', '-', 'n', 'a', 'v', 'a', 'j', 'o'}: _nv, // i-navajo - {'i', '-', 'p', 'w', 'n'}: _pwn, // i-pwn - {'i', '-', 't', 'a', 'o'}: _tao, // i-tao - {'i', '-', 't', 'a', 'y'}: _tay, // i-tay - {'i', '-', 't', 's', 'u'}: _tsu, // i-tsu - {'n', 'o', '-', 'b', 'o', 'k'}: _nb, // no-bok - {'n', 'o', '-', 'n', 'y', 'n'}: _nn, // no-nyn - {'s', 'g', 'n', '-', 'b', 'e', '-', 'f', 'r'}: _sfb, // sgn-BE-FR - {'s', 'g', 'n', '-', 'b', 'e', '-', 'n', 'l'}: _vgt, // sgn-BE-NL - {'s', 'g', 'n', '-', 'c', 'h', '-', 'd', 'e'}: _sgg, // sgn-CH-DE - {'z', 'h', '-', 'g', 'u', 'o', 'y', 'u'}: _cmn, // zh-guoyu - {'z', 'h', '-', 'h', 'a', 'k', 'k', 'a'}: _hak, // zh-hakka - {'z', 'h', '-', 'm', 'i', 'n', '-', 'n', 'a', 'n'}: _nan, // zh-min-nan - {'z', 'h', '-', 'x', 'i', 'a', 'n', 'g'}: _hsn, // zh-xiang + [maxLen]byte{'a', 'r', 't', '-', 'l', 'o', 'j', 'b', 'a', 'n'}: _jbo, // art-lojban + [maxLen]byte{'i', '-', 'a', 'm', 'i'}: _ami, // i-ami + [maxLen]byte{'i', '-', 'b', 'n', 'n'}: _bnn, // i-bnn + [maxLen]byte{'i', '-', 'h', 'a', 'k'}: _hak, // i-hak + [maxLen]byte{'i', '-', 'k', 'l', 'i', 'n', 'g', 'o', 'n'}: _tlh, // i-klingon + [maxLen]byte{'i', '-', 'l', 'u', 'x'}: _lb, // i-lux + [maxLen]byte{'i', '-', 'n', 'a', 'v', 'a', 'j', 'o'}: _nv, // i-navajo + [maxLen]byte{'i', '-', 'p', 'w', 'n'}: _pwn, // i-pwn + [maxLen]byte{'i', '-', 't', 'a', 'o'}: _tao, // i-tao + [maxLen]byte{'i', '-', 't', 'a', 'y'}: _tay, // i-tay + [maxLen]byte{'i', '-', 't', 's', 'u'}: _tsu, // i-tsu + [maxLen]byte{'n', 'o', '-', 'b', 'o', 'k'}: _nb, // no-bok + [maxLen]byte{'n', 'o', '-', 'n', 'y', 'n'}: _nn, // no-nyn + [maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'f', 'r'}: _sfb, // sgn-BE-FR + [maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'n', 'l'}: _vgt, // sgn-BE-NL + [maxLen]byte{'s', 'g', 'n', '-', 'c', 'h', '-', 'd', 'e'}: _sgg, // sgn-CH-DE + [maxLen]byte{'z', 'h', '-', 'g', 'u', 'o', 'y', 'u'}: _cmn, // zh-guoyu + [maxLen]byte{'z', 'h', '-', 'h', 'a', 'k', 'k', 'a'}: _hak, // zh-hakka + [maxLen]byte{'z', 'h', '-', 'm', 'i', 'n', '-', 'n', 'a', 'n'}: _nan, // zh-min-nan + [maxLen]byte{'z', 'h', '-', 'x', 'i', 'a', 'n', 'g'}: _hsn, // zh-xiang // Grandfathered tags with no modern replacement will be converted as // follows: - {'c', 'e', 'l', '-', 'g', 'a', 'u', 'l', 'i', 's', 'h'}: -1, // cel-gaulish - {'e', 'n', '-', 'g', 'b', '-', 'o', 'e', 'd'}: -2, // en-GB-oed - {'i', '-', 'd', 'e', 'f', 'a', 'u', 'l', 't'}: -3, // i-default - {'i', '-', 'e', 'n', 'o', 'c', 'h', 'i', 'a', 'n'}: -4, // i-enochian - {'i', '-', 'm', 'i', 'n', 'g', 'o'}: -5, // i-mingo - {'z', 'h', '-', 'm', 'i', 'n'}: -6, // zh-min + [maxLen]byte{'c', 'e', 'l', '-', 'g', 'a', 'u', 'l', 'i', 's', 'h'}: -1, // cel-gaulish + [maxLen]byte{'e', 'n', '-', 'g', 'b', '-', 'o', 'e', 'd'}: -2, // en-GB-oed + [maxLen]byte{'i', '-', 'd', 'e', 'f', 'a', 'u', 'l', 't'}: -3, // i-default + [maxLen]byte{'i', '-', 'e', 'n', 'o', 'c', 'h', 'i', 'a', 'n'}: -4, // i-enochian + [maxLen]byte{'i', '-', 'm', 'i', 'n', 'g', 'o'}: -5, // i-mingo + [maxLen]byte{'z', 'h', '-', 'm', 'i', 'n'}: -6, // zh-min // CLDR-specific tag. - {'r', 'o', 'o', 't'}: 0, // root - {'e', 'n', '-', 'u', 's', '-', 'p', 'o', 's', 'i', 'x'}: -7, // en_US_POSIX" + [maxLen]byte{'r', 'o', 'o', 't'}: 0, // root + [maxLen]byte{'e', 'n', '-', 'u', 's', '-', 'p', 'o', 's', 'i', 'x'}: -7, // en_US_POSIX" } altTagIndex = [...]uint8{0, 17, 31, 45, 61, 74, 86, 102} diff --git a/vendor/golang.org/x/text/language/tables.go b/vendor/golang.org/x/text/language/tables.go index a28524e1d72..b738d457b5d 100644 --- a/vendor/golang.org/x/text/language/tables.go +++ b/vendor/golang.org/x/text/language/tables.go @@ -3348,9 +3348,9 @@ var regionToGroups = [358]uint8{ // Size: 18 bytes, 3 elements var paradigmLocales = [3][3]uint16{ - 0: {0x139, 0x0, 0x7b}, - 1: {0x13e, 0x0, 0x1f}, - 2: {0x3c0, 0x41, 0xee}, + 0: [3]uint16{0x139, 0x0, 0x7b}, + 1: [3]uint16{0x13e, 0x0, 0x1f}, + 2: [3]uint16{0x3c0, 0x41, 0xee}, } type mutualIntelligibility struct { diff --git a/vendor/golang.org/x/text/unicode/cldr/cldr.go b/vendor/golang.org/x/text/unicode/cldr/cldr.go index 19b8cefd706..2197f8ac268 100644 --- a/vendor/golang.org/x/text/unicode/cldr/cldr.go +++ b/vendor/golang.org/x/text/unicode/cldr/cldr.go @@ -110,7 +110,7 @@ func (cldr *CLDR) Supplemental() *SupplementalData { func (cldr *CLDR) Locales() []string { loc := []string{"root"} hasRoot := false - for l := range cldr.locale { + for l, _ := range cldr.locale { if l == "root" { hasRoot = true continue diff --git a/vendor/golang.org/x/text/unicode/cldr/resolve.go b/vendor/golang.org/x/text/unicode/cldr/resolve.go index c6919216b80..691b5903fe4 100644 --- a/vendor/golang.org/x/text/unicode/cldr/resolve.go +++ b/vendor/golang.org/x/text/unicode/cldr/resolve.go @@ -289,7 +289,7 @@ var distinguishing = map[string][]string{ "mzone": nil, "from": nil, "to": nil, - "type": { + "type": []string{ "abbreviationFallback", "default", "mapping", @@ -527,7 +527,7 @@ func (cldr *CLDR) inheritSlice(enc, v, parent reflect.Value) (res reflect.Value, } } keys := make([]string, 0, len(index)) - for k := range index { + for k, _ := range index { keys = append(keys, k) } sort.Strings(keys) diff --git a/vendor/golang.org/x/text/unicode/cldr/slice.go b/vendor/golang.org/x/text/unicode/cldr/slice.go index ea5f31a3903..388c983ff13 100644 --- a/vendor/golang.org/x/text/unicode/cldr/slice.go +++ b/vendor/golang.org/x/text/unicode/cldr/slice.go @@ -83,7 +83,7 @@ func (s Slice) Group(fn func(e Elem) string) []Slice { m[key] = append(m[key], vi) } keys := []string{} - for k := range m { + for k, _ := range m { keys = append(keys, k) } sort.Strings(keys) diff --git a/vendor/golang.org/x/text/unicode/norm/maketables.go b/vendor/golang.org/x/text/unicode/norm/maketables.go index f66778d450a..338c395ee6f 100644 --- a/vendor/golang.org/x/text/unicode/norm/maketables.go +++ b/vendor/golang.org/x/text/unicode/norm/maketables.go @@ -241,7 +241,7 @@ func compactCCC() { m[c.ccc] = 0 } cccs := []int{} - for v := range m { + for v, _ := range m { cccs = append(cccs, int(v)) } sort.Ints(cccs) diff --git a/vendor/gopkg.in/macaron.v1/context.go b/vendor/gopkg.in/macaron.v1/context.go index 0f86e0d41ec..94a8c45d7da 100644 --- a/vendor/gopkg.in/macaron.v1/context.go +++ b/vendor/gopkg.in/macaron.v1/context.go @@ -270,7 +270,7 @@ func (ctx *Context) SetParams(name, val string) { // ReplaceAllParams replace all current params with given params func (ctx *Context) ReplaceAllParams(params Params) { - ctx.params = params + ctx.params = params; } // ParamsEscape returns escapred params result. From 1ce3e49e72d92e44fc6e4fe8ab4446a93d6ae262 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 15 Mar 2018 00:05:15 +0100 Subject: [PATCH 15/96] fix lint problems --- .../core/components/Login/LoginBackground.tsx | 16 +++++----- .../app/core/components/PasswordStrength.tsx | 15 +++++---- .../components/colorpicker/ColorPalette.tsx | 4 +-- .../colorpicker/ColorPickerPopover.tsx | 31 ++++++++----------- .../components/colorpicker/SpectrumPicker.tsx | 24 +++++++------- .../core/components/search/SearchResult.tsx | 31 ++++++++++++------- .../app/core/specs/PasswordStrength.jest.tsx | 13 +++++--- public/app/core/utils/kbn.ts | 12 +++---- .../dashboard/dashgrid/DashboardPanel.tsx | 15 +++++---- 9 files changed, 86 insertions(+), 75 deletions(-) diff --git a/public/app/core/components/Login/LoginBackground.tsx b/public/app/core/components/Login/LoginBackground.tsx index fb554845240..83e228ab6e0 100644 --- a/public/app/core/components/Login/LoginBackground.tsx +++ b/public/app/core/components/Login/LoginBackground.tsx @@ -4,14 +4,10 @@ const xCount = 50; const yCount = 50; function Cell({ x, y, flipIndex }) { - const index = y * xCount + x; + const index = (y * xCount) + x; const bgColor1 = getColor(x, y); return ( -
+
); } @@ -35,7 +31,7 @@ export default class LoginBackground extends Component { } flipElements() { - const elementIndexToFlip = getRandomInt(0, xCount * yCount - 1); + const elementIndexToFlip = getRandomInt(0, (xCount * yCount) - 1); this.setState(prevState => { return { ...prevState, @@ -61,7 +57,9 @@ export default class LoginBackground extends Component { return (
{Array.from(Array(xCount)).map((el2, x) => { - return ; + return ( + + ); })}
); @@ -1238,5 +1236,5 @@ function getColor(x, y) { // let randY = getRandomInt(0, y); // let randIndex = randY * xCount + randX; - return colors[(y * xCount + x) % colors.length]; + return colors[(y*xCount + x) % colors.length]; } diff --git a/public/app/core/components/PasswordStrength.tsx b/public/app/core/components/PasswordStrength.tsx index 4830ebdb61b..8f92b18445c 100644 --- a/public/app/core/components/PasswordStrength.tsx +++ b/public/app/core/components/PasswordStrength.tsx @@ -5,27 +5,28 @@ export interface IProps { } export class PasswordStrength extends React.Component { + constructor(props) { super(props); } render() { const { password } = this.props; - let strengthText = 'strength: strong like a bull.'; - let strengthClass = 'password-strength-good'; + let strengthText = "strength: strong like a bull."; + let strengthClass = "password-strength-good"; if (!password) { return null; } if (password.length <= 8) { - strengthText = 'strength: you can do better.'; - strengthClass = 'password-strength-ok'; + strengthText = "strength: you can do better."; + strengthClass = "password-strength-ok"; } if (password.length < 4) { - strengthText = 'strength: weak sauce.'; - strengthClass = 'password-strength-bad'; + strengthText = "strength: weak sauce."; + strengthClass = "password-strength-bad"; } return ( @@ -35,3 +36,5 @@ export class PasswordStrength extends React.Component { ); } } + + diff --git a/public/app/core/components/colorpicker/ColorPalette.tsx b/public/app/core/components/colorpicker/ColorPalette.tsx index 812827996c7..07b25a32046 100644 --- a/public/app/core/components/colorpicker/ColorPalette.tsx +++ b/public/app/core/components/colorpicker/ColorPalette.tsx @@ -29,8 +29,7 @@ export class ColorPalette extends React.Component { key={paletteColor} className={'pointer fa ' + cssClass} style={{ color: paletteColor }} - onClick={this.onColorSelect(paletteColor)} - > + onClick={this.onColorSelect(paletteColor)}>  
); @@ -42,3 +41,4 @@ export class ColorPalette extends React.Component { ); } } + diff --git a/public/app/core/components/colorpicker/ColorPickerPopover.tsx b/public/app/core/components/colorpicker/ColorPickerPopover.tsx index 4ac4161a160..360c3fdd5c4 100644 --- a/public/app/core/components/colorpicker/ColorPickerPopover.tsx +++ b/public/app/core/components/colorpicker/ColorPickerPopover.tsx @@ -19,7 +19,7 @@ export class ColorPickerPopover extends React.Component { this.state = { tab: 'palette', color: this.props.color || DEFAULT_COLOR, - colorString: this.props.color || DEFAULT_COLOR, + colorString: this.props.color || DEFAULT_COLOR }; } @@ -32,7 +32,7 @@ export class ColorPickerPopover extends React.Component { if (newColor.isValid()) { this.setState({ color: newColor.toString(), - colorString: newColor.toString(), + colorString: newColor.toString() }); this.props.onColorSelect(color); } @@ -50,7 +50,7 @@ export class ColorPickerPopover extends React.Component { onColorStringChange(e) { let colorString = e.target.value; this.setState({ - colorString: colorString, + colorString: colorString }); let newColor = tinycolor(colorString); @@ -71,11 +71,11 @@ export class ColorPickerPopover extends React.Component { componentDidMount() { this.pickerNavElem.find('li:first').addClass('active'); - this.pickerNavElem.on('show', e => { + this.pickerNavElem.on('show', (e) => { // use href attr (#name => name) let tab = e.target.hash.slice(1); this.setState({ - tab: tab, + tab: tab }); }); } @@ -97,24 +97,19 @@ export class ColorPickerPopover extends React.Component {
-
{currentTab}
+
+ {currentTab} +
- + +
); diff --git a/public/app/core/components/colorpicker/SpectrumPicker.tsx b/public/app/core/components/colorpicker/SpectrumPicker.tsx index 7242c094cde..eef04545308 100644 --- a/public/app/core/components/colorpicker/SpectrumPicker.tsx +++ b/public/app/core/components/colorpicker/SpectrumPicker.tsx @@ -29,17 +29,14 @@ export class SpectrumPicker extends React.Component { } componentDidMount() { - let spectrumOptions = _.assignIn( - { - flat: true, - showAlpha: true, - showButtons: false, - color: this.props.color, - appendTo: this.elem, - move: this.onSpectrumMove, - }, - this.props.options - ); + let spectrumOptions = _.assignIn({ + flat: true, + showAlpha: true, + showButtons: false, + color: this.props.color, + appendTo: this.elem, + move: this.onSpectrumMove, + }, this.props.options); this.elem.spectrum(spectrumOptions); this.elem.spectrum('show'); @@ -67,6 +64,9 @@ export class SpectrumPicker extends React.Component { } render() { - return
; + return ( +
+ ); } } + diff --git a/public/app/core/components/search/SearchResult.tsx b/public/app/core/components/search/SearchResult.tsx index 5ab4bba8edb..6d6b001cc1d 100644 --- a/public/app/core/components/search/SearchResult.tsx +++ b/public/app/core/components/search/SearchResult.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import classNames from 'classnames'; -import { observer } from 'mobx-react'; -import { store } from 'app/stores/store'; +import React from "react"; +import classNames from "classnames"; +import { observer } from "mobx-react"; +import { store } from "app/stores/store"; export interface SearchResultProps { search: any; @@ -13,7 +13,7 @@ export class SearchResult extends React.Component { super(props); this.state = { - search: store.search, + search: store.search }; store.search.query(); @@ -56,20 +56,29 @@ export class SearchResultSection extends React.Component { render() { let collapseClassNames = classNames({ fa: true, - 'fa-plus': !this.props.section.expanded, - 'fa-minus': this.props.section.expanded, - 'search-section__header__toggle': true, + "fa-plus": !this.props.section.expanded, + "fa-minus": this.props.section.expanded, + "search-section__header__toggle": true }); return (
- - {this.props.section.title} + + + {this.props.section.title} +
{this.props.section.expanded && ( -
{this.props.section.items.map(this.renderItem)}
+
+ {this.props.section.items.map(this.renderItem)} +
)}
); diff --git a/public/app/core/specs/PasswordStrength.jest.tsx b/public/app/core/specs/PasswordStrength.jest.tsx index 1bd52ee6d50..a0a2df69029 100644 --- a/public/app/core/specs/PasswordStrength.jest.tsx +++ b/public/app/core/specs/PasswordStrength.jest.tsx @@ -1,21 +1,24 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import {shallow} from 'enzyme'; -import { PasswordStrength } from '../components/PasswordStrength'; +import {PasswordStrength} from '../components/PasswordStrength'; describe('PasswordStrength', () => { + it('should have class bad if length below 4', () => { const wrapper = shallow(); - expect(wrapper.find('.password-strength-bad')).toHaveLength(1); + expect(wrapper.find(".password-strength-bad")).toHaveLength(1); }); it('should have class ok if length below 8', () => { const wrapper = shallow(); - expect(wrapper.find('.password-strength-ok')).toHaveLength(1); + expect(wrapper.find(".password-strength-ok")).toHaveLength(1); }); it('should have class good if length above 8', () => { const wrapper = shallow(); - expect(wrapper.find('.password-strength-good')).toHaveLength(1); + expect(wrapper.find(".password-strength-good")).toHaveLength(1); }); + }); + diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index c2bcf1ba70e..3b78ccfc001 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -133,12 +133,12 @@ kbn.secondsToHms = function(seconds) { kbn.secondsToHhmmss = function(seconds) { var strings = []; - var numhours = Math.floor(seconds / 3600); - var numminutes = Math.floor((seconds % 3600) / 60); - var numseconds = Math.floor((seconds % 3600) % 60); - numhours > 9 ? strings.push('' + numhours) : strings.push('0' + numhours); - numminutes > 9 ? strings.push('' + numminutes) : strings.push('0' + numminutes); - numseconds > 9 ? strings.push('' + numseconds) : strings.push('0' + numseconds); + var numhours = Math.floor(seconds/3600); + var numminutes = Math.floor((seconds%3600)/60); + var numseconds = Math.floor((seconds%3600)%60); + numhours > 9 ? strings.push(''+numhours) : strings.push('0'+numhours); + numminutes > 9 ? strings.push(''+numminutes) : strings.push('0'+numminutes); + numseconds > 9 ? strings.push(''+numseconds) : strings.push('0'+numseconds); return strings.join(':'); }; diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index f135d94431a..27fe64d4660 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { PanelModel } from '../panel_model'; -import { PanelContainer } from './PanelContainer'; -import { AttachedPanel } from './PanelLoader'; -import { DashboardRow } from './DashboardRow'; -import { AddPanelPanel } from './AddPanelPanel'; +import {PanelModel} from '../panel_model'; +import {PanelContainer} from './PanelContainer'; +import {AttachedPanel} from './PanelLoader'; +import {DashboardRow} from './DashboardRow'; +import {AddPanelPanel} from './AddPanelPanel'; export interface DashboardPanelProps { panel: PanelModel; @@ -46,6 +46,9 @@ export class DashboardPanel extends React.Component { return ; } - return
(this.element = element)} className="panel-height-helper" />; + return ( +
this.element = element} className="panel-height-helper" /> + ); } } + From 1080c113f6ea576cee65cf7bd66dd2c3f3e66287 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 15 Mar 2018 00:07:20 +0100 Subject: [PATCH 16/96] fix lint problems --- .../components/EmptyListCTA/EmptyListCTA.tsx | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx index c32edee156d..1583303dfa1 100644 --- a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx +++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx @@ -1,37 +1,34 @@ import React, { Component } from 'react'; export interface IProps { - model: any; + model: any; } class EmptyListCTA extends Component { - render() { - const { - title, - buttonIcon, - buttonLink, - buttonTitle, - proTip, - proTipLink, - proTipLinkTitle, - proTipTarget, - } = this.props.model; - return ( -
-
{title}
- - - {buttonTitle} - -
- ProTip: {proTip} - - {proTipLinkTitle} - -
-
- ); - } + render() { + const { + title, + buttonIcon, + buttonLink, + buttonTitle, + proTip, + proTipLink, + proTipLinkTitle, + proTipTarget + } = this.props.model; + return ( +
+
{title}
+ {buttonTitle} +
+ ProTip: {proTip} + {proTipLinkTitle} +
+
+ ); + } } export default EmptyListCTA; From 3aed867b4ba094d7315db3333096b16b85fb9b51 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 15 Mar 2018 10:35:50 +0100 Subject: [PATCH 17/96] fix merge error --- public/app/plugins/datasource/influxdb/datasource.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 19df26b9e63..90337fdc3f2 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -228,10 +228,10 @@ export default class InfluxDatasource { } _influxRequest(method: string, url: string, data: any, options?: any) { - var currentUrl = this.urls.shift(); + const currentUrl = this.urls.shift(); this.urls.push(currentUrl); - var params: any = {}; + let params: any = {}; if (this.username) { params.u = this.username; @@ -252,7 +252,7 @@ export default class InfluxDatasource { data = null; } - var req: any = { + let req: any = { method: method, url: currentUrl + url, params: params, @@ -270,7 +270,7 @@ export default class InfluxDatasource { req.headers.Authorization = this.basicAuth; } - return this.backendSrv.datasourceRequest(options).then( + return this.backendSrv.datasourceRequest(req).then( result => { return result.data; }, From ad88e5398c663feb853e7fb1bc8bb2dacd0b9380 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 15 Mar 2018 12:57:09 +0100 Subject: [PATCH 18/96] remove --- pkg/api/pluginproxy/ds_proxy.go | 4 +--- .../plugins/datasource/influxdb/datasource.ts | 5 ----- .../datasource/influxdb/partials/config.html | 20 +++++++++++++------ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 0b0cd4ee79f..b861a344c75 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -180,9 +180,7 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) { func (proxy *DataSourceProxy) validateRequest() error { if proxy.ds.Type == m.DS_INFLUXDB { if proxy.ctx.Query("db") != proxy.ds.Database { - if !proxy.ds.JsonData.Get("allowDatabaseQuery").MustBool(false) { - return errors.New("Datasource is not configured to allow this database") - } + return errors.New("Datasource is not configured to allow this database") } } diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 90337fdc3f2..8b213ca0f36 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -16,7 +16,6 @@ export default class InfluxDatasource { basicAuth: any; withCredentials: any; interval: any; - allowDatabaseQuery: boolean; supportAnnotations: boolean; supportMetrics: boolean; responseParser: any; @@ -35,7 +34,6 @@ export default class InfluxDatasource { this.basicAuth = instanceSettings.basicAuth; this.withCredentials = instanceSettings.withCredentials; this.interval = (instanceSettings.jsonData || {}).timeInterval; - this.allowDatabaseQuery = (instanceSettings.jsonData || {}).allowDatabaseQuery === true; this.supportAnnotations = true; this.supportMetrics = true; this.responseParser = new ResponseParser(); @@ -240,9 +238,6 @@ export default class InfluxDatasource { if (options && options.database) { params.db = options.database; - if (params.db !== this.database && !this.allowDatabaseQuery) { - return this.$q.reject({ message: 'This datasource does not allow changing database' }); - } } else if (this.database) { params.db = this.database; } diff --git a/public/app/plugins/datasource/influxdb/partials/config.html b/public/app/plugins/datasource/influxdb/partials/config.html index 4b861083928..a70a1de98a4 100644 --- a/public/app/plugins/datasource/influxdb/partials/config.html +++ b/public/app/plugins/datasource/influxdb/partials/config.html @@ -23,16 +23,24 @@
- + +
+
+
Database Access
+

+ Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows + switching the database in the query. For example: + SHOW MEASUREMENTS ON _internal or SELECT * FROM "_internal".."database" LIMIT 10 +

+ To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. +

+
+
- Min time interval + Min time interval A lower limit for the auto group by time interval. Recommended to be set to write frequency, From a04c4ba4541e8641b2ef07f37d280f20e7aa4472 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 15 Mar 2018 13:01:17 +0100 Subject: [PATCH 19/96] allow any database for influx proxy --- pkg/api/pluginproxy/ds_proxy.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index b861a344c75..4b84c40643c 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -178,12 +178,6 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) { } func (proxy *DataSourceProxy) validateRequest() error { - if proxy.ds.Type == m.DS_INFLUXDB { - if proxy.ctx.Query("db") != proxy.ds.Database { - return errors.New("Datasource is not configured to allow this database") - } - } - if !checkWhiteList(proxy.ctx, proxy.targetUrl.Host) { return errors.New("Target url is not a valid target") } From 8b076d921f9b18f9f8bca173c12a2203c044415e Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 3 Apr 2018 15:20:39 +0200 Subject: [PATCH 20/96] fixes for avatar on adding permission and size for gicon --- public/app/core/components/Permissions/AddPermissions.tsx | 2 +- .../components/Permissions/DisabledPermissionsListItem.tsx | 2 +- .../app/core/components/Permissions/PermissionsListItem.tsx | 2 +- public/app/stores/PermissionsStore/PermissionsStore.ts | 6 +++++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/public/app/core/components/Permissions/AddPermissions.tsx b/public/app/core/components/Permissions/AddPermissions.tsx index 07ccfdbbef5..cfdcebf3063 100644 --- a/public/app/core/components/Permissions/AddPermissions.tsx +++ b/public/app/core/components/Permissions/AddPermissions.tsx @@ -39,7 +39,7 @@ class AddPermissions extends Component { permissions.newItem.setUser(null, null); return; } - return permissions.newItem.setUser(user.id, user.login); + return permissions.newItem.setUser(user.id, user.login, user.avatarUrl); } teamPicked(team: Team) { diff --git a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx index adc2bec3d81..5e473c25869 100644 --- a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx +++ b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx @@ -13,7 +13,7 @@ export default class DisabledPermissionListItem extends Component { return ( - + {item.name} diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index 1bec7003f1f..f1d96aaf358 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -28,6 +28,7 @@ function ItemDescription({ item }) { } export default observer(({ item, removeItem, permissionChanged, itemIndex, folderInfo }) => { + console.log(item); const handleRemoveItem = evt => { evt.preventDefault(); removeItem(itemIndex); @@ -38,7 +39,6 @@ export default observer(({ item, removeItem, permissionChanged, itemIndex, folde }; const inheritedFromRoot = item.dashboardId === -1 && folderInfo && folderInfo.id === 0; - console.log(item.name); return ( diff --git a/public/app/stores/PermissionsStore/PermissionsStore.ts b/public/app/stores/PermissionsStore/PermissionsStore.ts index a96ff36a376..8899931255f 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.ts @@ -30,6 +30,7 @@ export const NewPermissionsItem = types ), userId: types.maybe(types.number), userLogin: types.maybe(types.string), + userAvatarUrl: types.maybe(types.string), teamId: types.maybe(types.number), team: types.maybe(types.string), permission: types.optional(types.number, 1), @@ -50,9 +51,10 @@ export const NewPermissionsItem = types }, })) .actions(self => ({ - setUser(userId: number, userLogin: string) { + setUser(userId: number, userLogin: string, userAvatarUrl: string) { self.userId = userId; self.userLogin = userLogin; + self.userAvatarUrl = userAvatarUrl; self.teamId = null; self.team = null; }, @@ -121,6 +123,7 @@ export const PermissionsStore = types teamId: undefined, userLogin: undefined, userId: undefined, + userAvatarUrl: undefined, role: undefined, }; switch (self.newItem.type) { @@ -131,6 +134,7 @@ export const PermissionsStore = types case aclTypeValues.USER.value: item.userLogin = self.newItem.userLogin; item.userId = self.newItem.userId; + item.userAvatarUrl = self.newItem.userAvatarUrl; break; case aclTypeValues.VIEWER.value: case aclTypeValues.EDITOR.value: From ebad863f95a419a8330e0ec4fc03904faed353cf Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 4 Apr 2018 15:50:45 +0200 Subject: [PATCH 21/96] permission: generate team avatar url with default --- pkg/api/dashboard_permission.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/api/dashboard_permission.go b/pkg/api/dashboard_permission.go index 2f85d6f55e8..342eaf556c6 100644 --- a/pkg/api/dashboard_permission.go +++ b/pkg/api/dashboard_permission.go @@ -30,7 +30,10 @@ func GetDashboardPermissionList(c *m.ReqContext) Response { for _, perm := range acl { perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail) - perm.TeamAvatarUrl = dtos.GetGravatarUrl(perm.TeamEmail) + + if perm.TeamId > 0 { + perm.TeamAvatarUrl = dtos.GetGravatarUrlWithDefault(perm.TeamEmail, perm.Team) + } if perm.Slug != "" { perm.Url = m.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug) } From 8f94cecf0f4fbc9347dd2baec55196b972545307 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 4 Apr 2018 15:51:19 +0200 Subject: [PATCH 22/96] permissions: return user and team avatar in folder permissions api --- pkg/api/folder_permission.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go index 0d0904c99ea..d19ec848ab2 100644 --- a/pkg/api/folder_permission.go +++ b/pkg/api/folder_permission.go @@ -33,6 +33,12 @@ func GetFolderPermissionList(c *m.ReqContext) Response { perm.FolderId = folder.Id perm.DashboardId = 0 + perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail) + + if perm.TeamId > 0 { + perm.TeamAvatarUrl = dtos.GetGravatarUrlWithDefault(perm.TeamEmail, perm.Team) + } + if perm.Slug != "" { perm.Url = m.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug) } From b363e160d99f3e1684cb79ba6c6bbdebf1123c02 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 6 Apr 2018 09:43:59 +0200 Subject: [PATCH 23/96] added icons for viewer and editor, fixed add permission team avatar --- .../components/Permissions/AddPermissions.tsx | 2 +- .../Permissions/PermissionsListItem.tsx | 8 ++++++-- .../PermissionsStore/PermissionsStore.ts | 6 +++++- public/img/icons_dark_theme/icon_editor.svg | 19 +++++++++++++++++++ public/img/icons_dark_theme/icon_viewer.svg | 17 +++++++++++++++++ public/img/icons_light_theme/icon_editor.svg | 19 +++++++++++++++++++ public/img/icons_light_theme/icon_viewer.svg | 17 +++++++++++++++++ public/sass/base/_icons.scss | 8 ++++++++ 8 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 public/img/icons_dark_theme/icon_editor.svg create mode 100644 public/img/icons_dark_theme/icon_viewer.svg create mode 100644 public/img/icons_light_theme/icon_editor.svg create mode 100644 public/img/icons_light_theme/icon_viewer.svg diff --git a/public/app/core/components/Permissions/AddPermissions.tsx b/public/app/core/components/Permissions/AddPermissions.tsx index cfdcebf3063..4dcd07ffb48 100644 --- a/public/app/core/components/Permissions/AddPermissions.tsx +++ b/public/app/core/components/Permissions/AddPermissions.tsx @@ -48,7 +48,7 @@ class AddPermissions extends Component { permissions.newItem.setTeam(null, null); return; } - return permissions.newItem.setTeam(team.id, team.name); + return permissions.newItem.setTeam(team.id, team.name, team.avatarUrl); } permissionPicked(permission: OptionWithDescription) { diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index f1d96aaf358..f0da7b31881 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -8,13 +8,18 @@ const setClassNameHelper = inherited => { }; function ItemAvatar({ item }) { + console.log(item); if (item.userAvatarUrl) { return ; } if (item.teamAvatarUrl) { return ; } - return ; + if (item.role === 'Editor') { + return ; + } + + return ; } function ItemDescription({ item }) { @@ -28,7 +33,6 @@ function ItemDescription({ item }) { } export default observer(({ item, removeItem, permissionChanged, itemIndex, folderInfo }) => { - console.log(item); const handleRemoveItem = evt => { evt.preventDefault(); removeItem(itemIndex); diff --git a/public/app/stores/PermissionsStore/PermissionsStore.ts b/public/app/stores/PermissionsStore/PermissionsStore.ts index 8899931255f..7761ecc13c7 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.ts @@ -31,6 +31,7 @@ export const NewPermissionsItem = types userId: types.maybe(types.number), userLogin: types.maybe(types.string), userAvatarUrl: types.maybe(types.string), + teamAvatarUrl: types.maybe(types.string), teamId: types.maybe(types.number), team: types.maybe(types.string), permission: types.optional(types.number, 1), @@ -58,11 +59,12 @@ export const NewPermissionsItem = types self.teamId = null; self.team = null; }, - setTeam(teamId: number, team: string) { + setTeam(teamId: number, team: string, teamAvatarUrl: string) { self.userId = null; self.userLogin = null; self.teamId = teamId; self.team = team; + self.teamAvatarUrl = teamAvatarUrl; }, setPermission(permission: number) { self.permission = permission; @@ -124,12 +126,14 @@ export const PermissionsStore = types userLogin: undefined, userId: undefined, userAvatarUrl: undefined, + teamAvatarUrl: undefined, role: undefined, }; switch (self.newItem.type) { case aclTypeValues.GROUP.value: item.team = self.newItem.team; item.teamId = self.newItem.teamId; + item.teamAvatarUrl = self.newItem.teamAvatarUrl; break; case aclTypeValues.USER.value: item.userLogin = self.newItem.userLogin; diff --git a/public/img/icons_dark_theme/icon_editor.svg b/public/img/icons_dark_theme/icon_editor.svg new file mode 100644 index 00000000000..00c60902fbc --- /dev/null +++ b/public/img/icons_dark_theme/icon_editor.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/public/img/icons_dark_theme/icon_viewer.svg b/public/img/icons_dark_theme/icon_viewer.svg new file mode 100644 index 00000000000..aec3e6b7e5b --- /dev/null +++ b/public/img/icons_dark_theme/icon_viewer.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/public/img/icons_light_theme/icon_editor.svg b/public/img/icons_light_theme/icon_editor.svg new file mode 100644 index 00000000000..a6581072a17 --- /dev/null +++ b/public/img/icons_light_theme/icon_editor.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/public/img/icons_light_theme/icon_viewer.svg b/public/img/icons_light_theme/icon_viewer.svg new file mode 100644 index 00000000000..85d9b7109f4 --- /dev/null +++ b/public/img/icons_light_theme/icon_viewer.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/public/sass/base/_icons.scss b/public/sass/base/_icons.scss index c701cc1249e..bf66d4dc68d 100644 --- a/public/sass/base/_icons.scss +++ b/public/sass/base/_icons.scss @@ -120,6 +120,10 @@ background-image: url('../img/icons_#{$theme-name}_theme/icon_data_sources.svg'); } +.gicon-editor { + background-image: url('../img/icons_#{$theme-name}_theme/icon_editor.svg'); +} + .gicon-folder-new { background-image: url('../img/icons_#{$theme-name}_theme/icon_add_folder.svg'); } @@ -180,6 +184,10 @@ background-image: url('../img/icons_#{$theme-name}_theme/icon_variable.svg'); } +.gicon-viewer { + background-image: url('../img/icons_#{$theme-name}_theme/icon_viewer.svg'); +} + .gicon-zoom-out { background-image: url('../img/icons_#{$theme-name}_theme/icon_zoom_out.svg'); } From c7cd754a94781076d2b481bd825d8aae83826d64 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 6 Apr 2018 15:02:32 +0200 Subject: [PATCH 24/96] migrating to ts --- .../core/directives/value_select_dropdown.js | 283 ---------------- .../core/directives/value_select_dropdown.ts | 303 ++++++++++++++++++ 2 files changed, 303 insertions(+), 283 deletions(-) delete mode 100644 public/app/core/directives/value_select_dropdown.js create mode 100644 public/app/core/directives/value_select_dropdown.ts diff --git a/public/app/core/directives/value_select_dropdown.js b/public/app/core/directives/value_select_dropdown.js deleted file mode 100644 index a2bae1c34d3..00000000000 --- a/public/app/core/directives/value_select_dropdown.js +++ /dev/null @@ -1,283 +0,0 @@ -define([ - 'angular', - 'lodash', - '../core_module', -], -function (angular, _, coreModule) { - 'use strict'; - - coreModule.default.controller('ValueSelectDropdownCtrl', function($q) { - var vm = this; - - vm.show = function() { - vm.oldVariableText = vm.variable.current.text; - vm.highlightIndex = -1; - - vm.options = vm.variable.options; - vm.selectedValues = _.filter(vm.options, {selected: true}); - - vm.tags = _.map(vm.variable.tags, function(value) { - var tag = { text: value, selected: false }; - _.each(vm.variable.current.tags, function(tagObj) { - if (tagObj.text === value) { - tag = tagObj; - } - }); - return tag; - }); - - vm.search = { - query: '', - options: vm.options.slice(0, Math.min(vm.options.length, 1000)) - }; - - vm.dropdownVisible = true; - }; - - vm.updateLinkText = function() { - var current = vm.variable.current; - - if (current.tags && current.tags.length) { - // filer out values that are in selected tags - var selectedAndNotInTag = _.filter(vm.variable.options, function(option) { - if (!option.selected) { return false; } - for (var i = 0; i < current.tags.length; i++) { - var tag = current.tags[i]; - if (_.indexOf(tag.values, option.value) !== -1) { - return false; - } - } - return true; - }); - - // convert values to text - var currentTexts = _.map(selectedAndNotInTag, 'text'); - - // join texts - vm.linkText = currentTexts.join(' + '); - if (vm.linkText.length > 0) { - vm.linkText += ' + '; - } - } else { - vm.linkText = vm.variable.current.text; - } - }; - - vm.clearSelections = function() { - _.each(vm.options, function(option) { - option.selected = false; - }); - - vm.selectionsChanged(false); - }; - - vm.selectTag = function(tag) { - tag.selected = !tag.selected; - var tagValuesPromise; - if (!tag.values) { - tagValuesPromise = vm.variable.getValuesForTag(tag.text); - } else { - tagValuesPromise = $q.when(tag.values); - } - - tagValuesPromise.then(function(values) { - tag.values = values; - tag.valuesText = values.join(' + '); - _.each(vm.options, function(option) { - if (_.indexOf(tag.values, option.value) !== -1) { - option.selected = tag.selected; - } - }); - - vm.selectionsChanged(false); - }); - }; - - vm.keyDown = function (evt) { - if (evt.keyCode === 27) { - vm.hide(); - } - if (evt.keyCode === 40) { - vm.moveHighlight(1); - } - if (evt.keyCode === 38) { - vm.moveHighlight(-1); - } - if (evt.keyCode === 13) { - if (vm.search.options.length === 0) { - vm.commitChanges(); - } else { - vm.selectValue(vm.search.options[vm.highlightIndex], {}, true, false); - } - } - if (evt.keyCode === 32) { - vm.selectValue(vm.search.options[vm.highlightIndex], {}, false, false); - } - }; - - vm.moveHighlight = function(direction) { - vm.highlightIndex = (vm.highlightIndex + direction) % vm.search.options.length; - }; - - vm.selectValue = function(option, event, commitChange, excludeOthers) { - if (!option) { return; } - - option.selected = vm.variable.multi ? !option.selected: true; - - commitChange = commitChange || false; - excludeOthers = excludeOthers || false; - - var setAllExceptCurrentTo = function(newValue) { - _.each(vm.options, function(other) { - if (option !== other) { other.selected = newValue; } - }); - }; - - // commit action (enter key), should not deselect it - if (commitChange) { - option.selected = true; - } - - if (option.text === 'All' || excludeOthers) { - setAllExceptCurrentTo(false); - commitChange = true; - } - else if (!vm.variable.multi) { - setAllExceptCurrentTo(false); - commitChange = true; - } else if (event.ctrlKey || event.metaKey || event.shiftKey) { - commitChange = true; - setAllExceptCurrentTo(false); - } - - vm.selectionsChanged(commitChange); - }; - - vm.selectionsChanged = function(commitChange) { - vm.selectedValues = _.filter(vm.options, {selected: true}); - - if (vm.selectedValues.length > 1) { - if (vm.selectedValues[0].text === 'All') { - vm.selectedValues[0].selected = false; - vm.selectedValues = vm.selectedValues.slice(1, vm.selectedValues.length); - } - } - - // validate selected tags - _.each(vm.tags, function(tag) { - if (tag.selected) { - _.each(tag.values, function(value) { - if (!_.find(vm.selectedValues, {value: value})) { - tag.selected = false; - } - }); - } - }); - - vm.selectedTags = _.filter(vm.tags, {selected: true}); - vm.variable.current.value = _.map(vm.selectedValues, 'value'); - vm.variable.current.text = _.map(vm.selectedValues, 'text').join(' + '); - vm.variable.current.tags = vm.selectedTags; - - if (!vm.variable.multi) { - vm.variable.current.value = vm.selectedValues[0].value; - } - - if (commitChange) { - vm.commitChanges(); - } - }; - - vm.commitChanges = function() { - // if we have a search query and no options use that - if (vm.search.options.length === 0 && vm.search.query.length > 0) { - vm.variable.current = {text: vm.search.query, value: vm.search.query}; - } - else if (vm.selectedValues.length === 0) { - // make sure one option is selected - vm.options[0].selected = true; - vm.selectionsChanged(false); - } - - vm.dropdownVisible = false; - vm.updateLinkText(); - - if (vm.variable.current.text !== vm.oldVariableText) { - vm.onUpdated(); - } - }; - - vm.queryChanged = function() { - vm.highlightIndex = -1; - vm.search.options = _.filter(vm.options, function(option) { - return option.text.toLowerCase().indexOf(vm.search.query.toLowerCase()) !== -1; - }); - - vm.search.options = vm.search.options.slice(0, Math.min(vm.search.options.length, 1000)); - }; - - vm.init = function() { - vm.selectedTags = vm.variable.current.tags || []; - vm.updateLinkText(); - }; - - }); - - coreModule.default.directive('valueSelectDropdown', function($compile, $window, $timeout, $rootScope) { - return { - scope: { variable: "=", onUpdated: "&"}, - templateUrl: 'public/app/partials/valueSelectDropdown.html', - controller: 'ValueSelectDropdownCtrl', - controllerAs: 'vm', - bindToController: true, - link: function(scope, elem) { - var bodyEl = angular.element($window.document.body); - var linkEl = elem.find('.variable-value-link'); - var inputEl = elem.find('input'); - - function openDropdown() { - inputEl.css('width', Math.max(linkEl.width(), 80) + 'px'); - - inputEl.show(); - linkEl.hide(); - - inputEl.focus(); - $timeout(function() { bodyEl.on('click', bodyOnClick); }, 0, false); - } - - function switchToLink() { - inputEl.hide(); - linkEl.show(); - bodyEl.off('click', bodyOnClick); - } - - function bodyOnClick (e) { - if (elem.has(e.target).length === 0) { - scope.$apply(function() { - scope.vm.commitChanges(); - }); - } - } - - scope.$watch('vm.dropdownVisible', function(newValue) { - if (newValue) { - openDropdown(); - } else { - switchToLink(); - } - }); - - var cleanUp = $rootScope.$on('template-variable-value-updated', function() { - scope.vm.updateLinkText(); - }); - - scope.$on("$destroy", function() { - cleanUp(); - }); - - scope.vm.init(); - }, - }; - }); - -}); diff --git a/public/app/core/directives/value_select_dropdown.ts b/public/app/core/directives/value_select_dropdown.ts new file mode 100644 index 00000000000..628b1480a4f --- /dev/null +++ b/public/app/core/directives/value_select_dropdown.ts @@ -0,0 +1,303 @@ +import angular from 'angular'; +import _ from 'lodash'; +import coreModule from '../core_module'; + +export class ValueSelectDropdownCtrl { + dropdownVisible: any; + highlightIndex: any; + linkText: any; + oldVariableText: any; + options: any; + search: any; + selectedTags: any; + selectedValues: any; + tags: any; + variable: any; + + hide: any; + onUpdated: any; + + constructor(private $q) {} + + show() { + this.oldVariableText = this.variable.current.text; + this.highlightIndex = -1; + + this.options = this.variable.options; + this.selectedValues = _.filter(this.options, { selected: true }); + + this.tags = _.map(this.variable.tags, function(value) { + let tag = { text: value, selected: false }; + _.each(this.variable.current.tags, function(tagObj) { + if (tagObj.text === value) { + tag = tagObj; + } + }); + return tag; + }); + + this.search = { + query: '', + options: this.options.slice(0, Math.min(this.options.length, 1000)), + }; + + this.dropdownVisible = true; + } + + updateLinkText() { + let current = this.variable.current; + + if (current.tags && current.tags.length) { + // filer out values that are in selected tags + let selectedAndNotInTag = _.filter(this.variable.options, function(option) { + if (!option.selected) { + return false; + } + for (let i = 0; i < current.tags.length; i++) { + let tag = current.tags[i]; + if (_.indexOf(tag.values, option.value) !== -1) { + return false; + } + } + return true; + }); + + // convert values to text + let currentTexts = _.map(selectedAndNotInTag, 'text'); + + // join texts + this.linkText = currentTexts.join(' + '); + if (this.linkText.length > 0) { + this.linkText += ' + '; + } + } else { + this.linkText = this.variable.current.text; + } + } + + clearSelections() { + _.each(this.options, function(option) { + option.selected = false; + }); + + this.selectionsChanged(false); + } + + selectTag(tag) { + tag.selected = !tag.selected; + let tagValuesPromise; + if (!tag.values) { + tagValuesPromise = this.variable.getValuesForTag(tag.text); + } else { + tagValuesPromise = this.$q.when(tag.values); + } + + tagValuesPromise.then(function(values) { + tag.values = values; + tag.valuesText = values.join(' + '); + _.each(this.options, function(option) { + if (_.indexOf(tag.values, option.value) !== -1) { + option.selected = tag.selected; + } + }); + + this.selectionsChanged(false); + }); + } + + keyDown(evt) { + if (evt.keyCode === 27) { + this.hide(); + } + if (evt.keyCode === 40) { + this.moveHighlight(1); + } + if (evt.keyCode === 38) { + this.moveHighlight(-1); + } + if (evt.keyCode === 13) { + if (this.search.options.length === 0) { + this.commitChanges(); + } else { + this.selectValue(this.search.options[this.highlightIndex], {}, true, false); + } + } + if (evt.keyCode === 32) { + this.selectValue(this.search.options[this.highlightIndex], {}, false, false); + } + } + + moveHighlight(direction) { + this.highlightIndex = (this.highlightIndex + direction) % this.search.options.length; + } + + selectValue(option, event, commitChange, excludeOthers) { + if (!option) { + return; + } + + option.selected = this.variable.multi ? !option.selected : true; + + commitChange = commitChange || false; + excludeOthers = excludeOthers || false; + + let setAllExceptCurrentTo = function(newValue) { + _.each(this.options, function(other) { + if (option !== other) { + other.selected = newValue; + } + }); + }; + + // commit action (enter key), should not deselect it + if (commitChange) { + option.selected = true; + } + + if (option.text === 'All' || excludeOthers) { + setAllExceptCurrentTo(false); + commitChange = true; + } else if (!this.variable.multi) { + setAllExceptCurrentTo(false); + commitChange = true; + } else if (event.ctrlKey || event.metaKey || event.shiftKey) { + commitChange = true; + setAllExceptCurrentTo(false); + } + + this.selectionsChanged(commitChange); + } + + selectionsChanged(commitChange) { + this.selectedValues = _.filter(this.options, { selected: true }); + + if (this.selectedValues.length > 1) { + if (this.selectedValues[0].text === 'All') { + this.selectedValues[0].selected = false; + this.selectedValues = this.selectedValues.slice(1, this.selectedValues.length); + } + } + + // validate selected tags + _.each(this.tags, function(tag) { + if (tag.selected) { + _.each(tag.values, function(value) { + if (!_.find(this.selectedValues, { value: value })) { + tag.selected = false; + } + }); + } + }); + + this.selectedTags = _.filter(this.tags, { selected: true }); + this.variable.current.value = _.map(this.selectedValues, 'value'); + this.variable.current.text = _.map(this.selectedValues, 'text').join(' + '); + this.variable.current.tags = this.selectedTags; + + if (!this.variable.multi) { + this.variable.current.value = this.selectedValues[0].value; + } + + if (commitChange) { + this.commitChanges(); + } + } + + commitChanges() { + // if we have a search query and no options use that + if (this.search.options.length === 0 && this.search.query.length > 0) { + this.variable.current = { text: this.search.query, value: this.search.query }; + } else if (this.selectedValues.length === 0) { + // make sure one option is selected + this.options[0].selected = true; + this.selectionsChanged(false); + } + + this.dropdownVisible = false; + this.updateLinkText(); + + if (this.variable.current.text !== this.oldVariableText) { + this.onUpdated(); + } + } + + queryChanged() { + this.highlightIndex = -1; + this.search.options = _.filter(this.options, function(option) { + return option.text.toLowerCase().indexOf(this.search.query.toLowerCase()) !== -1; + }); + + this.search.options = this.search.options.slice(0, Math.min(this.search.options.length, 1000)); + } + + init() { + this.selectedTags = this.variable.current.tags || []; + this.updateLinkText(); + } +} + +export function valueSelectDropdown($compile, $window, $timeout, $rootScope) { + return { + scope: { variable: '=', onUpdated: '&' }, + templateUrl: 'public/app/partials/valueSelectDropdown.html', + controller: 'ValueSelectDropdownCtrl', + controllerAs: 'vm', + bindToController: true, + link: function(scope, elem) { + let bodyEl = angular.element($window.document.body); + let linkEl = elem.find('.variable-value-link'); + let inputEl = elem.find('input'); + + function openDropdown() { + inputEl.css('width', Math.max(linkEl.width(), 80) + 'px'); + + inputEl.show(); + linkEl.hide(); + + inputEl.focus(); + $timeout( + function() { + bodyEl.on('click', bodyOnClick); + }, + 0, + false + ); + } + + function switchToLink() { + inputEl.hide(); + linkEl.show(); + bodyEl.off('click', bodyOnClick); + } + + function bodyOnClick(e) { + if (elem.has(e.target).length === 0) { + scope.$apply(function() { + scope.vm.commitChanges(); + }); + } + } + + scope.$watch('vm.dropdownVisible', function(newValue) { + if (newValue) { + openDropdown(); + } else { + switchToLink(); + } + }); + + let cleanUp = $rootScope.$on('template-variable-value-updated', function() { + scope.vm.updateLinkText(); + }); + + scope.$on('$destroy', function() { + cleanUp(); + }); + + scope.vm.init(); + }, + }; +} + +coreModule.controller('ValueSelectDropdownCtrl', ValueSelectDropdownCtrl); +coreModule.directive('valueSelectDropdown', valueSelectDropdown); From 113bfb3d3eafeaad1453d371e3cab889b9d13f70 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 9 Apr 2018 13:28:32 +0200 Subject: [PATCH 25/96] don't convert to uint64 --- pkg/tsdb/postgres/macros.go | 12 ++++++------ pkg/tsdb/postgres/macros_test.go | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 23daeebec5a..f96b3896041 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -83,11 +83,11 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, 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], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("extract(epoch from %s) BETWEEN %d AND %d", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeFrom": - return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__timeTo": - return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) @@ -114,11 +114,11 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__unixEpochTo": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil default: return "", fmt.Errorf("Unknown macro %v", name) } diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index b18acced963..1485f8a261f 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -85,5 +85,12 @@ func TestMacroEngine(t *testing.T) { So(sql, ShouldEqual, "select 18446744066914187038") }) + timeRange := &tsdb.TimeRange{From: "-315622800000", To: "315529200000"} // 1960-1980 + Convey("interpolate __timeFilter function before epoch", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "WHERE extract(epoch from time_column) BETWEEN -315622800 AND 315529200") + }) }) } From 7d6c8aa61299d48f2308b56d057c45a3feb35f38 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 9 Apr 2018 13:34:35 +0200 Subject: [PATCH 26/96] add mssql and mysql --- pkg/tsdb/mssql/macros.go | 12 ++++++------ pkg/tsdb/mysql/macros.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index 9d41cd03255..6fd1550b048 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -82,11 +82,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeFrom": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__timeTo": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) @@ -113,11 +113,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__unixEpochTo": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil default: return "", fmt.Errorf("Unknown macro %v", name) } diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index a292f209429..92f6968f12a 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -77,11 +77,11 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= FROM_UNIXTIME(%d) AND %s <= FROM_UNIXTIME(%d)", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= FROM_UNIXTIME(%d) AND %s <= FROM_UNIXTIME(%d)", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeFrom": - return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__timeTo": - return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) @@ -108,11 +108,11 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__unixEpochTo": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil default: return "", fmt.Errorf("Unknown macro %v", name) } From 920a0c4fec5149fc1bc3b42646a909bc036178dd Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 9 Apr 2018 13:49:13 +0200 Subject: [PATCH 27/96] skip mssql fix --- pkg/tsdb/mssql/macros.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index 6fd1550b048..9d41cd03255 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -82,11 +82,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeFrom": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__timeTo": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) @@ -113,11 +113,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil case "__unixEpochTo": - return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil default: return "", fmt.Errorf("Unknown macro %v", name) } From 1c9ebd5bd8c46cb5b32e7cbe0f94d4f5bded1447 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 9 Apr 2018 14:01:09 +0200 Subject: [PATCH 28/96] fix test --- pkg/tsdb/postgres/macros_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index 1485f8a261f..838a73ce240 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -85,7 +85,7 @@ func TestMacroEngine(t *testing.T) { So(sql, ShouldEqual, "select 18446744066914187038") }) - timeRange := &tsdb.TimeRange{From: "-315622800000", To: "315529200000"} // 1960-1980 + timeRange = &tsdb.TimeRange{From: "-315622800000", To: "315529200000"} // 1960-1980 Convey("interpolate __timeFilter function before epoch", func() { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) From 77b8ccd7f542bec40d1a80bb00ca772ee04ced15 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 9 Apr 2018 17:01:58 +0200 Subject: [PATCH 29/96] removed console.log --- public/app/core/components/Permissions/PermissionsListItem.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index f0da7b31881..229c774c639 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -8,7 +8,6 @@ const setClassNameHelper = inherited => { }; function ItemAvatar({ item }) { - console.log(item); if (item.userAvatarUrl) { return ; } From a314890f895498c2fc7a1bfe333c47c1e5c7f3fa Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 10:32:30 +0200 Subject: [PATCH 30/96] tsdb: add support for more data types when converting sql time column to epoch (ms) --- pkg/tsdb/sql_engine.go | 30 +++++- pkg/tsdb/sql_engine_test.go | 182 +++++++++++++++++++++++++++++++----- pkg/tsdb/time_range.go | 5 + 3 files changed, 192 insertions(+), 25 deletions(-) diff --git a/pkg/tsdb/sql_engine.go b/pkg/tsdb/sql_engine.go index 16370a4ea7f..0f35cadf4d6 100644 --- a/pkg/tsdb/sql_engine.go +++ b/pkg/tsdb/sql_engine.go @@ -135,16 +135,16 @@ func (e *DefaultSqlEngine) Query( return result, nil } -// ConvertTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds +// 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.Unix())) + values[timeIndex] = EpochPrecisionToMs(float64(value.UnixNano())) case *time.Time: if value != nil { - values[timeIndex] = EpochPrecisionToMs(float64((*value).Unix())) + values[timeIndex] = EpochPrecisionToMs(float64((*value).UnixNano())) } case int64: values[timeIndex] = int64(EpochPrecisionToMs(float64(value))) @@ -152,12 +152,36 @@ func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) { 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)) + } } } } diff --git a/pkg/tsdb/sql_engine_test.go b/pkg/tsdb/sql_engine_test.go index 48aac2c4d45..f8856489230 100644 --- a/pkg/tsdb/sql_engine_test.go +++ b/pkg/tsdb/sql_engine_test.go @@ -9,37 +9,175 @@ import ( func TestSqlEngine(t *testing.T) { Convey("SqlEngine", t, func() { - Convey("Given row values with time columns when converting them", func() { - dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC) - fixtures := make([]interface{}, 8) - fixtures[0] = dt - fixtures[1] = dt.Unix() * 1000 - fixtures[2] = dt.Unix() - fixtures[3] = float64(dt.Unix() * 1000) - fixtures[4] = float64(dt.Unix()) + dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC) - var nilDt *time.Time - var nilInt64 *int64 - var nilFloat64 *float64 - fixtures[5] = nilDt - fixtures[6] = nilInt64 - fixtures[7] = nilFloat64 + Convey("Given row values with time.Time as time columns", func() { + var nilPointer *time.Time + + fixtures := make([]interface{}, 3) + fixtures[0] = dt + fixtures[1] = &dt + fixtures[2] = nilPointer for i := range fixtures { ConvertSqlTimeColumnToEpochMs(fixtures, i) } - Convey("Should convert sql time columns to epoch time in ms ", func() { - expected := float64(dt.Unix() * 1000) + Convey("When converting them should return epoch time with millisecond precision ", func() { + expected := float64(dt.UnixNano() / 1e6) So(fixtures[0].(float64), ShouldEqual, expected) - So(fixtures[1].(int64), ShouldEqual, expected) - So(fixtures[2].(int64), ShouldEqual, expected) - So(fixtures[3].(float64), ShouldEqual, expected) - So(fixtures[4].(float64), ShouldEqual, expected) + So(fixtures[1].(float64), ShouldEqual, expected) + So(fixtures[2], ShouldBeNil) + }) + }) - So(fixtures[5], ShouldBeNil) + Convey("Given row values with int64 as time columns", func() { + tSeconds := dt.Unix() + tMilliseconds := dt.UnixNano() / 1e6 + tNanoSeconds := dt.UnixNano() + var nilPointer *int64 + + fixtures := make([]interface{}, 7) + fixtures[0] = tSeconds + fixtures[1] = &tSeconds + fixtures[2] = tMilliseconds + fixtures[3] = &tMilliseconds + fixtures[4] = tNanoSeconds + fixtures[5] = &tNanoSeconds + fixtures[6] = nilPointer + + for i := range fixtures { + ConvertSqlTimeColumnToEpochMs(fixtures, i) + } + + Convey("When converting them should return epoch time with millisecond precision ", func() { + So(fixtures[0].(int64), ShouldEqual, tSeconds*1e3) + So(fixtures[1].(int64), ShouldEqual, tSeconds*1e3) + So(fixtures[2].(int64), ShouldEqual, tMilliseconds) + So(fixtures[3].(int64), ShouldEqual, tMilliseconds) + So(fixtures[4].(int64), ShouldEqual, tMilliseconds) + So(fixtures[5].(int64), ShouldEqual, tMilliseconds) So(fixtures[6], ShouldBeNil) - So(fixtures[7], ShouldBeNil) + }) + }) + + Convey("Given row values with uin64 as time columns", func() { + tSeconds := uint64(dt.Unix()) + tMilliseconds := uint64(dt.UnixNano() / 1e6) + tNanoSeconds := uint64(dt.UnixNano()) + var nilPointer *uint64 + + fixtures := make([]interface{}, 7) + fixtures[0] = tSeconds + fixtures[1] = &tSeconds + fixtures[2] = tMilliseconds + fixtures[3] = &tMilliseconds + fixtures[4] = tNanoSeconds + fixtures[5] = &tNanoSeconds + fixtures[6] = nilPointer + + for i := range fixtures { + ConvertSqlTimeColumnToEpochMs(fixtures, i) + } + + Convey("When converting them should return epoch time with millisecond precision ", func() { + So(fixtures[0].(int64), ShouldEqual, tSeconds*1e3) + So(fixtures[1].(int64), ShouldEqual, tSeconds*1e3) + So(fixtures[2].(int64), ShouldEqual, tMilliseconds) + So(fixtures[3].(int64), ShouldEqual, tMilliseconds) + So(fixtures[4].(int64), ShouldEqual, tMilliseconds) + So(fixtures[5].(int64), ShouldEqual, tMilliseconds) + So(fixtures[6], ShouldBeNil) + }) + }) + + Convey("Given row values with int32 as time columns", func() { + tSeconds := int32(dt.Unix()) + var nilInt *int32 + + fixtures := make([]interface{}, 3) + fixtures[0] = tSeconds + fixtures[1] = &tSeconds + fixtures[2] = nilInt + + for i := range fixtures { + ConvertSqlTimeColumnToEpochMs(fixtures, i) + } + + Convey("When converting them should return epoch time with millisecond precision ", func() { + So(fixtures[0].(int64), ShouldEqual, dt.Unix()*1e3) + So(fixtures[1].(int64), ShouldEqual, dt.Unix()*1e3) + So(fixtures[2], ShouldBeNil) + }) + }) + + Convey("Given row values with uint32 as time columns", func() { + tSeconds := uint32(dt.Unix()) + var nilInt *uint32 + + fixtures := make([]interface{}, 3) + fixtures[0] = tSeconds + fixtures[1] = &tSeconds + fixtures[2] = nilInt + + for i := range fixtures { + ConvertSqlTimeColumnToEpochMs(fixtures, i) + } + + Convey("When converting them should return epoch time with millisecond precision ", func() { + So(fixtures[0].(int64), ShouldEqual, dt.Unix()*1e3) + So(fixtures[1].(int64), ShouldEqual, dt.Unix()*1e3) + So(fixtures[2], ShouldBeNil) + }) + }) + + Convey("Given row values with float64 as time columns", func() { + tSeconds := float64(dt.Unix()) + tMilliseconds := float64(dt.UnixNano() / 1e6) + tNanoSeconds := float64(dt.UnixNano()) + var nilPointer *float64 + + fixtures := make([]interface{}, 7) + fixtures[0] = tSeconds + fixtures[1] = &tSeconds + fixtures[2] = tMilliseconds + fixtures[3] = &tMilliseconds + fixtures[4] = tNanoSeconds + fixtures[5] = &tNanoSeconds + fixtures[6] = nilPointer + + for i := range fixtures { + ConvertSqlTimeColumnToEpochMs(fixtures, i) + } + + Convey("When converting them should return epoch time with millisecond precision ", func() { + So(fixtures[0].(float64), ShouldEqual, tSeconds*1e3) + So(fixtures[1].(float64), ShouldEqual, tSeconds*1e3) + So(fixtures[2].(float64), ShouldEqual, tMilliseconds) + So(fixtures[3].(float64), ShouldEqual, tMilliseconds) + So(fixtures[4].(float64), ShouldEqual, tMilliseconds) + So(fixtures[5].(float64), ShouldEqual, tMilliseconds) + So(fixtures[6], ShouldBeNil) + }) + }) + + Convey("Given row values with float32 as time columns", func() { + tSeconds := float32(dt.Unix()) + var nilInt *float32 + + fixtures := make([]interface{}, 3) + fixtures[0] = tSeconds + fixtures[1] = &tSeconds + fixtures[2] = nilInt + + for i := range fixtures { + ConvertSqlTimeColumnToEpochMs(fixtures, i) + } + + Convey("When converting them should return epoch time with millisecond precision ", func() { + So(fixtures[0].(float64), ShouldEqual, float32(dt.Unix()*1e3)) + So(fixtures[1].(float64), ShouldEqual, float32(dt.Unix()*1e3)) + So(fixtures[2], ShouldBeNil) }) }) }) diff --git a/pkg/tsdb/time_range.go b/pkg/tsdb/time_range.go index fd0cb3f8e82..47076eb3c6b 100644 --- a/pkg/tsdb/time_range.go +++ b/pkg/tsdb/time_range.go @@ -96,5 +96,10 @@ func EpochPrecisionToMs(value float64) float64 { return float64(value * 1e3) } + s := strconv.FormatFloat(value, 'f', -1, 64) + if len(s) == 19 { + return float64(value / 1e6) + } + return float64(value) } From 5c120c2c11e9a85dff1f0e9953014a07734a6cb1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 10:58:45 +0200 Subject: [PATCH 31/96] mysql: mysql tests should use a db server with UTC To get rid of issues involving date/time when testing. Also, makes it possible to run mysql integration tests for both grafana config db and tsdb at the same time using GRAFANA_TEST_DB=mysql go test ./pkg/... --- docker/blocks/mysql_tests/Dockerfile | 3 + docker/blocks/mysql_tests/dashboard.json | 158 +++++++++++++----- docker/blocks/mysql_tests/docker-compose.yaml | 6 +- docker/blocks/mysql_tests/setup.sql | 2 + pkg/services/sqlstore/sqlstore.go | 11 +- pkg/tsdb/mysql/mysql_test.go | 38 +++-- 6 files changed, 156 insertions(+), 62 deletions(-) create mode 100644 docker/blocks/mysql_tests/Dockerfile create mode 100644 docker/blocks/mysql_tests/setup.sql diff --git a/docker/blocks/mysql_tests/Dockerfile b/docker/blocks/mysql_tests/Dockerfile new file mode 100644 index 00000000000..fa91fa3c023 --- /dev/null +++ b/docker/blocks/mysql_tests/Dockerfile @@ -0,0 +1,3 @@ +FROM mysql:latest +ADD setup.sql /docker-entrypoint-initdb.d +CMD ["mysqld"] \ No newline at end of file diff --git a/docker/blocks/mysql_tests/dashboard.json b/docker/blocks/mysql_tests/dashboard.json index 3ab08a7da35..53f313315bd 100644 --- a/docker/blocks/mysql_tests/dashboard.json +++ b/docker/blocks/mysql_tests/dashboard.json @@ -7,14 +7,6 @@ "type": "datasource", "pluginId": "mysql", "pluginName": "MySQL" - }, - { - "name": "DS_MSSQL_TEST", - "label": "MSSQL Test", - "description": "", - "type": "datasource", - "pluginId": "mssql", - "pluginName": "Microsoft SQL Server" } ], "__requires": [ @@ -30,12 +22,6 @@ "name": "Graph", "version": "5.0.0" }, - { - "type": "datasource", - "id": "mssql", - "name": "Microsoft SQL Server", - "version": "1.0.0" - }, { "type": "datasource", "id": "mysql", @@ -114,7 +100,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1521715720483, + "iteration": 1523320712115, "links": [], "panels": [ { @@ -349,7 +335,7 @@ { "alias": "Time", "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "time_sec", + "pattern": "time", "type": "date" }, { @@ -457,7 +443,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -536,7 +526,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -615,7 +609,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -694,7 +692,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -773,7 +775,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -852,7 +858,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -941,7 +951,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1034,7 +1048,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1123,7 +1141,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1204,7 +1226,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1293,7 +1319,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1374,7 +1404,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1463,7 +1497,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1544,7 +1582,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1634,14 +1676,18 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, "bars": true, "dashLength": 10, "dashes": false, - "datasource": "${DS_MSSQL_TEST}", + "datasource": "${DS_MYSQL_TEST}", "fill": 1, "gridPos": { "h": 8, @@ -1717,7 +1763,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1807,7 +1857,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1890,7 +1944,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1980,7 +2038,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2063,7 +2125,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2153,7 +2219,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2236,7 +2306,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } } ], "refresh": false, @@ -2315,8 +2389,8 @@ ] }, "time": { - "from": "2018-03-15T11:30:00.000Z", - "to": "2018-03-15T12:55:01.000Z" + "from": "2018-03-15T12:30:00.000Z", + "to": "2018-03-15T13:55:01.000Z" }, "timepicker": { "refresh_intervals": [ @@ -2346,5 +2420,5 @@ "timezone": "", "title": "MySQL Data Source Test", "uid": "Hmf8FDkmz", - "version": 9 + "version": 12 } \ No newline at end of file diff --git a/docker/blocks/mysql_tests/docker-compose.yaml b/docker/blocks/mysql_tests/docker-compose.yaml index 3c59b66b5ac..035a6167017 100644 --- a/docker/blocks/mysql_tests/docker-compose.yaml +++ b/docker/blocks/mysql_tests/docker-compose.yaml @@ -1,5 +1,6 @@ mysqltests: - image: mysql:latest + build: + context: blocks/mysql_tests environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: grafana_tests @@ -7,7 +8,4 @@ MYSQL_PASSWORD: password ports: - "3306:3306" - volumes: - - /etc/localtime:/etc/localtime:ro - - /etc/timezone:/etc/timezone:ro tmpfs: /var/lib/mysql:rw diff --git a/docker/blocks/mysql_tests/setup.sql b/docker/blocks/mysql_tests/setup.sql new file mode 100644 index 00000000000..be917a1c542 --- /dev/null +++ b/docker/blocks/mysql_tests/setup.sql @@ -0,0 +1,2 @@ +CREATE DATABASE grafana_ds_tests; +GRANT ALL PRIVILEGES ON grafana_ds_tests.* TO 'grafana'; diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 6aace350193..21069eab01a 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -258,7 +258,7 @@ func InitTestDB(t *testing.T) *xorm.Engine { // x.ShowSQL() if err != nil { - t.Fatalf("Failed to init in memory sqllite3 db %v", err) + t.Fatalf("Failed to init test database: %v", err) } sqlutil.CleanDB(x) @@ -269,3 +269,12 @@ func InitTestDB(t *testing.T) *xorm.Engine { return x } + +func IsTestDbMySql() bool { + if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { + return db == dbMySql + } + + return false +} + diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 750704c9965..b8d7fc8d42b 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -3,25 +3,35 @@ package mysql import ( "fmt" "math/rand" + "strings" "testing" "time" "github.com/go-xorm/xorm" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" "github.com/grafana/grafana/pkg/tsdb" . "github.com/smartystreets/goconvey/convey" ) -// To run this test, remove the Skip from SkipConvey -// and set up a MySQL db named grafana_tests and a user/password grafana/password +// To run this test, set runMySqlTests=true +// and set up 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 // once you've created a datasource for the test server/database. func TestMySQL(t *testing.T) { - SkipConvey("MySQL", t, func() { + // change to true to run the MySQL tests + runMySqlTests := false + // runMySqlTests := true + + if !(sqlstore.IsTestDbMySql() || runMySqlTests) { + t.Skip() + } + + Convey("MySQL", t, func() { x := InitMySQLTestDB(t) endpoint := &MysqlQueryEndpoint{ @@ -35,7 +45,7 @@ func TestMySQL(t *testing.T) { sess := x.NewSession() defer sess.Close() - fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.Local) + fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC) Convey("Given a table with different native data types", func() { if exists, err := sess.IsTableExist("mysql_types"); err != nil || exists { @@ -121,9 +131,8 @@ func TestMySQL(t *testing.T) { So(column[7].(float64), ShouldEqual, 1.11) So(column[8].(float64), ShouldEqual, 2.22) So(*column[9].(*float32), ShouldEqual, 3.33) - _, offset := time.Now().Zone() - So(column[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second)) - So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second)) + So(column[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now()) + So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now()) So(column[12].(string), ShouldEqual, "11:11:11") So(column[13].(int64), ShouldEqual, 2018) So(*column[14].(*[]byte), ShouldHaveSameTypeAs, []byte{1}) @@ -137,8 +146,7 @@ func TestMySQL(t *testing.T) { So(column[22].(string), ShouldEqual, "longblob") So(column[23].(string), ShouldEqual, "val2") So(column[24].(string), ShouldEqual, "a,b") - So(column[25].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().Format("2006-01-02T00:00:00Z")) - So(column[26].(float64), ShouldEqual, float64(1514764861000)) + So(column[25].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().UTC().Format("2006-01-02T00:00:00Z")) So(column[27], ShouldEqual, nil) So(column[28], ShouldEqual, nil) So(column[29], ShouldEqual, "") @@ -647,16 +655,16 @@ func TestMySQL(t *testing.T) { } func InitMySQLTestDB(t *testing.T) *xorm.Engine { - x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr+"&parseTime=true") - x.DatabaseTZ = time.Local - x.TZLocation = time.Local - - // x.ShowSQL() - + x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, strings.Replace(sqlutil.TestDB_Mysql.ConnStr, "/grafana_tests", "/grafana_ds_tests", 1)) if err != nil { t.Fatalf("Failed to init mysql db %v", err) } + x.DatabaseTZ = time.UTC + x.TZLocation = time.UTC + + // x.ShowSQL() + return x } From af626466240fac57130bbb608d9db78b8816e4ed Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 11:01:43 +0200 Subject: [PATCH 32/96] mysql: fix precision for time columns in time series query mode --- pkg/tsdb/mysql/mysql.go | 14 +- pkg/tsdb/mysql/mysql_test.go | 295 ++++++++++++++++++++++++++++++++--- 2 files changed, 279 insertions(+), 30 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 483974c55a4..83027d4b210 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -8,7 +8,6 @@ import ( "math" "reflect" "strconv" - "time" "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" @@ -239,15 +238,18 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. return err } + // converts column named time to unix timestamp in milliseconds to make + // native mysql datetime types and epoch dates work in + // annotation and table queries. + tsdb.ConvertSqlTimeColumnToEpochMs(values, timeIndex) + switch columnValue := values[timeIndex].(type) { case int64: - timestamp = float64(columnValue * 1000) + timestamp = float64(columnValue) case float64: - timestamp = columnValue * 1000 - case time.Time: - timestamp = float64(columnValue.UnixNano() / 1e6) + timestamp = columnValue default: - return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue) + return fmt.Errorf("Invalid type for column time/time_sec, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue) } if metricIndex >= 0 { diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index b8d7fc8d42b..530c8fb6c8a 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -147,6 +147,7 @@ func TestMySQL(t *testing.T) { So(column[23].(string), ShouldEqual, "val2") So(column[24].(string), ShouldEqual, "a,b") So(column[25].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().UTC().Format("2006-01-02T00:00:00Z")) + So(column[26].(float64), ShouldEqual, float64(1.514764861123456*1e12)) So(column[27], ShouldEqual, nil) So(column[28], ShouldEqual, nil) So(column[29], ShouldEqual, "") @@ -185,10 +186,8 @@ func TestMySQL(t *testing.T) { }) } - for _, s := range series { - _, err = sess.Insert(s) - So(err, ShouldBeNil) - } + _, err = sess.InsertMulti(series) + So(err, ShouldBeNil) Convey("When doing a metric query using timeGroup", func() { query := &tsdb.TsdbQuery{ @@ -309,10 +308,19 @@ func TestMySQL(t *testing.T) { Convey("Given a table with metrics having multiple values and measurements", func() { type metric_values struct { - Time time.Time - Measurement string - ValueOne int64 `xorm:"integer 'valueOne'"` - ValueTwo int64 `xorm:"integer 'valueTwo'"` + Time time.Time `xorm:"datetime 'time' not null"` + TimeNullable *time.Time `xorm:"datetime 'timeNullable' null"` + TimeInt64 int64 `xorm:"bigint(20) 'timeInt64' not null"` + TimeInt64Nullable *int64 `xorm:"bigint(20) 'timeInt64Nullable' null"` + TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"` + TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"` + TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"` + TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"` + TimeFloat32 float32 `xorm:"double 'timeFloat32' not null"` + TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"` + Measurement string + ValueOne int64 `xorm:"integer 'valueOne'"` + ValueTwo int64 `xorm:"integer 'valueTwo'"` } if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist { @@ -327,26 +335,265 @@ func TestMySQL(t *testing.T) { return rand.Int63n(max-min) + min } + var tInitial time.Time + series := []*metric_values{} - for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) { - series = append(series, &metric_values{ - Time: t, - Measurement: "Metric A", - ValueOne: rnd(0, 100), - ValueTwo: rnd(0, 100), - }) - series = append(series, &metric_values{ - Time: t, - Measurement: "Metric B", - ValueOne: rnd(0, 100), - ValueTwo: rnd(0, 100), - }) + for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) { + if i == 0 { + tInitial = t + } + tSeconds := t.Unix() + tSecondsInt32 := int32(tSeconds) + tSecondsFloat32 := float32(tSeconds) + tMilliseconds := tSeconds * 1e3 + tMillisecondsFloat := float64(tMilliseconds) + t2 := t + first := metric_values{ + Time: t, + TimeNullable: &t2, + TimeInt64: tMilliseconds, + TimeInt64Nullable: &(tMilliseconds), + TimeFloat64: tMillisecondsFloat, + TimeFloat64Nullable: &tMillisecondsFloat, + TimeInt32: tSecondsInt32, + TimeInt32Nullable: &tSecondsInt32, + TimeFloat32: tSecondsFloat32, + TimeFloat32Nullable: &tSecondsFloat32, + Measurement: "Metric A", + ValueOne: rnd(0, 100), + ValueTwo: rnd(0, 100), + } + second := first + second.Measurement = "Metric B" + second.ValueOne = rnd(0, 100) + second.ValueTwo = rnd(0, 100) + + series = append(series, &first) + series = append(series, &second) } - for _, s := range series { - _, err := sess.Insert(s) + _, err = sess.InsertMulti(series) + So(err, ShouldBeNil) + + Convey("When doing a metric query using time as time column should return metric with time in milliseconds", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": `SELECT time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) So(err, ShouldBeNil) - } + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6)) + }) + + Convey("When doing a metric query using time (nullable) as time column should return metric with time in milliseconds", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": `SELECT timeNullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3) + }) Convey("When doing a metric query grouping by time and select metric column should return correct series", func() { query := &tsdb.TsdbQuery{ From 1783c534fd45cc8a679b262b0c7245404ae4c880 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 11:03:58 +0200 Subject: [PATCH 33/96] postgres: fix precision for time columns in time series query mode --- pkg/tsdb/postgres/postgres.go | 12 +- pkg/tsdb/postgres/postgres_test.go | 244 ++++++++++++++++++++++++++--- 2 files changed, 229 insertions(+), 27 deletions(-) diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go index 5f6b56ebcf1..e17cc783f38 100644 --- a/pkg/tsdb/postgres/postgres.go +++ b/pkg/tsdb/postgres/postgres.go @@ -7,7 +7,6 @@ import ( "math" "net/url" "strconv" - "time" "github.com/go-xorm/core" "github.com/grafana/grafana/pkg/components/null" @@ -219,13 +218,16 @@ func (e PostgresQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *co return err } + // converts column named time to unix timestamp in milliseconds to make + // native mysql datetime types and epoch dates work in + // annotation and table queries. + tsdb.ConvertSqlTimeColumnToEpochMs(values, timeIndex) + switch columnValue := values[timeIndex].(type) { case int64: - timestamp = float64(columnValue * 1000) + timestamp = float64(columnValue) case float64: - timestamp = columnValue * 1000 - case time.Time: - timestamp = float64(columnValue.UnixNano() / 1e6) + timestamp = columnValue default: return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue) } diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index 3f2203ac7a4..43e8419a329 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -156,8 +156,7 @@ func TestPostgres(t *testing.T) { }) } - for _, s := range series { - _, err = sess.Insert(s) + _, err = sess.InsertMulti(series) So(err, ShouldBeNil) } @@ -280,10 +279,18 @@ func TestPostgres(t *testing.T) { Convey("Given a table with metrics having multiple values and measurements", func() { type metric_values struct { - Time time.Time - Measurement string - ValueOne int64 `xorm:"integer 'valueOne'"` - ValueTwo int64 `xorm:"integer 'valueTwo'"` + Time time.Time + TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"` + TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"` + TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"` + TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"` + TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"` + TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"` + TimeFloat32 float32 `xorm:"double 'timeFloat32' not null"` + TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"` + Measurement string + ValueOne int64 `xorm:"integer 'valueOne'"` + ValueTwo int64 `xorm:"integer 'valueTwo'"` } if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist { @@ -298,27 +305,220 @@ func TestPostgres(t *testing.T) { return rand.Int63n(max-min) + min } + var tInitial time.Time + series := []*metric_values{} - for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) { - series = append(series, &metric_values{ - Time: t, - Measurement: "Metric A", - ValueOne: rnd(0, 100), - ValueTwo: rnd(0, 100), - }) - series = append(series, &metric_values{ - Time: t, - Measurement: "Metric B", - ValueOne: rnd(0, 100), - ValueTwo: rnd(0, 100), - }) + for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) { + if i == 0 { + tInitial = t + } + tSeconds := t.Unix() + tSecondsInt32 := int32(tSeconds) + tSecondsFloat32 := float32(tSeconds) + tMilliseconds := tSeconds * 1e3 + tMillisecondsFloat := float64(tMilliseconds) + first := metric_values{ + Time: t, + TimeInt64: tMilliseconds, + TimeInt64Nullable: &(tMilliseconds), + TimeFloat64: tMillisecondsFloat, + TimeFloat64Nullable: &tMillisecondsFloat, + TimeInt32: tSecondsInt32, + TimeInt32Nullable: &tSecondsInt32, + TimeFloat32: tSecondsFloat32, + TimeFloat32Nullable: &tSecondsFloat32, + Measurement: "Metric A", + ValueOne: rnd(0, 100), + ValueTwo: rnd(0, 100), + } + second := first + second.Measurement = "Metric B" + second.ValueOne = rnd(0, 100) + second.ValueTwo = rnd(0, 100) + + series = append(series, &first) + series = append(series, &second) } - for _, s := range series { - _, err := sess.Insert(s) + _, 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, } + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3) + }) + Convey("When doing a metric query grouping by time and select metric column should return correct series", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ @@ -473,7 +673,7 @@ func TestPostgres(t *testing.T) { columns := queryResult.Tables[0].Rows[0] //Should be in milliseconds - So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000)) + So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6)) }) Convey("When doing an annotation query with a time column in epoch second format should return ms", func() { From 0317ecbf0d8b595ec2ec08f05df4d2d2128b406f Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 11:08:30 +0200 Subject: [PATCH 34/96] postgres: support running multiple postgres integration tests Makes it possible to run mysql integration tests for both grafana config db and tsdb at the same time using GRAFANA_TEST_DB=postgres go test ./pkg/... --- docker/blocks/postgres_tests/Dockerfile | 3 + docker/blocks/postgres_tests/dashboard.json | 134 +++++++++++++++--- .../blocks/postgres_tests/docker-compose.yaml | 3 +- docker/blocks/postgres_tests/setup.sql | 3 + pkg/services/sqlstore/sqlstore.go | 7 + pkg/tsdb/postgres/postgres_test.go | 37 +++-- 6 files changed, 149 insertions(+), 38 deletions(-) create mode 100644 docker/blocks/postgres_tests/Dockerfile create mode 100644 docker/blocks/postgres_tests/setup.sql diff --git a/docker/blocks/postgres_tests/Dockerfile b/docker/blocks/postgres_tests/Dockerfile new file mode 100644 index 00000000000..afe4d199651 --- /dev/null +++ b/docker/blocks/postgres_tests/Dockerfile @@ -0,0 +1,3 @@ +FROM postgres:latest +ADD setup.sql /docker-entrypoint-initdb.d +CMD ["postgres"] \ No newline at end of file diff --git a/docker/blocks/postgres_tests/dashboard.json b/docker/blocks/postgres_tests/dashboard.json index eea95863716..9efbe90bdfe 100644 --- a/docker/blocks/postgres_tests/dashboard.json +++ b/docker/blocks/postgres_tests/dashboard.json @@ -100,7 +100,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1521725946837, + "iteration": 1523320929325, "links": [], "panels": [ { @@ -443,7 +443,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -522,7 +526,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -601,7 +609,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -680,7 +692,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -759,7 +775,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -838,7 +858,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -927,7 +951,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1008,7 +1036,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1097,7 +1129,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1178,7 +1214,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1267,7 +1307,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1348,7 +1392,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1437,7 +1485,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1518,7 +1570,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1608,7 +1664,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1691,7 +1751,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1781,7 +1845,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1864,7 +1932,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1954,7 +2026,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2037,7 +2113,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2127,7 +2207,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2210,7 +2294,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } } ], "refresh": false, diff --git a/docker/blocks/postgres_tests/docker-compose.yaml b/docker/blocks/postgres_tests/docker-compose.yaml index 44b66e8e558..f5ce0a5a3d3 100644 --- a/docker/blocks/postgres_tests/docker-compose.yaml +++ b/docker/blocks/postgres_tests/docker-compose.yaml @@ -1,5 +1,6 @@ postgrestest: - image: postgres:latest + build: + context: blocks/postgres_tests environment: POSTGRES_USER: grafanatest POSTGRES_PASSWORD: grafanatest diff --git a/docker/blocks/postgres_tests/setup.sql b/docker/blocks/postgres_tests/setup.sql new file mode 100644 index 00000000000..b182b7c292d --- /dev/null +++ b/docker/blocks/postgres_tests/setup.sql @@ -0,0 +1,3 @@ +CREATE DATABASE grafanadstest; +REVOKE CONNECT ON DATABASE grafanadstest FROM PUBLIC; +GRANT CONNECT ON DATABASE grafanadstest TO grafanatest; \ No newline at end of file diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 21069eab01a..782318fa188 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -278,3 +278,10 @@ func IsTestDbMySql() bool { return false } +func IsTestDbPostgres() bool { + if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { + return db == dbPostgres + } + + return false +} diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index 43e8419a329..d35ba2b3209 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -3,26 +3,36 @@ package postgres import ( "fmt" "math/rand" + "strings" "testing" "time" "github.com/go-xorm/xorm" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" "github.com/grafana/grafana/pkg/tsdb" _ "github.com/lib/pq" . "github.com/smartystreets/goconvey/convey" ) -// To run this test, remove the Skip from SkipConvey -// and set up a PostgreSQL db named grafanatest and a user/password grafanatest/grafanatest! +// To run this test, set runMySqlTests=true +// and set up 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 // once you've created a datasource for the test server/database. func TestPostgres(t *testing.T) { - SkipConvey("PostgreSQL", t, func() { + // change to true to run the MySQL tests + runPostgresTests := false + // runPostgresTests := true + + if !(sqlstore.IsTestDbPostgres() || runPostgresTests) { + t.Skip() + } + + Convey("PostgreSQL", t, func() { x := InitPostgresTestDB(t) endpoint := &PostgresQueryEndpoint{ @@ -157,8 +167,7 @@ func TestPostgres(t *testing.T) { } _, err = sess.InsertMulti(series) - So(err, ShouldBeNil) - } + So(err, ShouldBeNil) Convey("When doing a metric query using timeGroup", func() { query := &tsdb.TsdbQuery{ @@ -451,7 +460,7 @@ func TestPostgres(t *testing.T) { So(len(queryResult.Series), ShouldEqual, 1) 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() { query := &tsdb.TsdbQuery{ @@ -473,7 +482,7 @@ func TestPostgres(t *testing.T) { So(len(queryResult.Series), ShouldEqual, 1) 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() { query := &tsdb.TsdbQuery{ @@ -486,7 +495,7 @@ func TestPostgres(t *testing.T) { RefId: "A", }, }, - } + } resp, err := endpoint.Query(nil, nil, query) So(err, ShouldBeNil) @@ -508,7 +517,7 @@ func TestPostgres(t *testing.T) { RefId: "A", }, }, - } + } resp, err := endpoint.Query(nil, nil, query) So(err, ShouldBeNil) @@ -826,16 +835,16 @@ func TestPostgres(t *testing.T) { } func InitPostgresTestDB(t *testing.T) *xorm.Engine { - x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr) + x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, strings.Replace(sqlutil.TestDB_Postgres.ConnStr, "dbname=grafanatest", "dbname=grafanadstest", 1)) + if err != nil { + t.Fatalf("Failed to init postgres db %v", err) + } + x.DatabaseTZ = time.UTC x.TZLocation = time.UTC // x.ShowSQL() - if err != nil { - t.Fatalf("Failed to init postgres db %v", err) - } - return x } From 9d84e6f31f8c828564cf04874b8ce2d88294588d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 11:10:56 +0200 Subject: [PATCH 35/96] mssql: fix precision for time columns in time series query mode --- docker/blocks/mssql_tests/dashboard.json | 148 ++++++++++--- pkg/tsdb/mssql/mssql.go | 15 +- pkg/tsdb/mssql/mssql_test.go | 258 ++++++++++++++++++++--- 3 files changed, 358 insertions(+), 63 deletions(-) diff --git a/docker/blocks/mssql_tests/dashboard.json b/docker/blocks/mssql_tests/dashboard.json index 20e3907b48b..80994254093 100644 --- a/docker/blocks/mssql_tests/dashboard.json +++ b/docker/blocks/mssql_tests/dashboard.json @@ -100,7 +100,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1521715844826, + "iteration": 1523320861623, "links": [], "panels": [ { @@ -443,7 +443,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -522,7 +526,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -601,7 +609,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -680,7 +692,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -759,7 +775,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -838,7 +858,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -927,7 +951,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1026,7 +1054,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1115,7 +1147,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1196,7 +1232,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1285,7 +1325,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1366,7 +1410,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1455,7 +1503,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1536,7 +1588,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1619,7 +1675,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1702,7 +1762,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1792,7 +1856,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1875,7 +1943,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -1965,7 +2037,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2048,7 +2124,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2138,7 +2218,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2221,7 +2305,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2311,7 +2399,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -2394,7 +2486,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } } ], "refresh": false, @@ -2504,5 +2600,5 @@ "timezone": "", "title": "Microsoft SQL Server Data Source Test", "uid": "GlAqcPgmz", - "version": 57 + "version": 58 } \ No newline at end of file diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index 2638fd8bb40..3a440859f9f 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -8,8 +8,6 @@ import ( "strconv" "strings" - "time" - "math" _ "github.com/denisenkom/go-mssqldb" @@ -231,15 +229,18 @@ func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core. return err } + // converts column named time to unix timestamp in milliseconds to make + // native mysql datetime types and epoch dates work in + // annotation and table queries. + tsdb.ConvertSqlTimeColumnToEpochMs(values, timeIndex) + switch columnValue := values[timeIndex].(type) { case int64: - timestamp = float64(columnValue * 1000) + timestamp = float64(columnValue) case float64: - timestamp = columnValue * 1000 - case time.Time: - timestamp = (float64(columnValue.Unix()) * 1000) + float64(columnValue.Nanosecond()/1e6) // in case someone is trying to map times beyond 2262 :D + timestamp = columnValue default: - return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp") + return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue) } if metricIndex >= 0 { diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index 4bd1e3a8ad7..dc527d09bd9 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -188,10 +188,8 @@ func TestMSSQL(t *testing.T) { }) } - for _, s := range series { - _, err = sess.Insert(s) - So(err, ShouldBeNil) - } + _, err = sess.InsertMulti(series) + So(err, ShouldBeNil) Convey("When doing a metric query using timeGroup", func() { query := &tsdb.TsdbQuery{ @@ -312,10 +310,18 @@ func TestMSSQL(t *testing.T) { Convey("Given a table with metrics having multiple values and measurements", func() { type metric_values struct { - Time time.Time - Measurement string - ValueOne int64 `xorm:"integer 'valueOne'"` - ValueTwo int64 `xorm:"integer 'valueTwo'"` + Time time.Time + TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"` + TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"` + TimeFloat64 float64 `xorm:"float 'timeFloat64' not null"` + TimeFloat64Nullable *float64 `xorm:"float 'timeFloat64Nullable' null"` + TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"` + TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"` + TimeFloat32 float32 `xorm:"float(11) 'timeFloat32' not null"` + TimeFloat32Nullable *float32 `xorm:"float(11) 'timeFloat32Nullable' null"` + Measurement string + ValueOne int64 `xorm:"integer 'valueOne'"` + ValueTwo int64 `xorm:"integer 'valueTwo'"` } if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist { @@ -330,26 +336,219 @@ func TestMSSQL(t *testing.T) { return rand.Int63n(max-min) + min } + var tInitial time.Time + series := []*metric_values{} - for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) { - series = append(series, &metric_values{ - Time: t, - Measurement: "Metric A", - ValueOne: rnd(0, 100), - ValueTwo: rnd(0, 100), - }) - series = append(series, &metric_values{ - Time: t, - Measurement: "Metric B", - ValueOne: rnd(0, 100), - ValueTwo: rnd(0, 100), - }) + for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) { + if i == 0 { + tInitial = t + } + tSeconds := t.Unix() + tSecondsInt32 := int32(tSeconds) + tSecondsFloat32 := float32(tSeconds) + tMilliseconds := tSeconds * 1e3 + tMillisecondsFloat := float64(tMilliseconds) + first := metric_values{ + Time: t, + TimeInt64: tMilliseconds, + TimeInt64Nullable: &(tMilliseconds), + TimeFloat64: tMillisecondsFloat, + TimeFloat64Nullable: &tMillisecondsFloat, + TimeInt32: tSecondsInt32, + TimeInt32Nullable: &tSecondsInt32, + TimeFloat32: tSecondsFloat32, + TimeFloat32Nullable: &tSecondsFloat32, + Measurement: "Metric A", + ValueOne: rnd(0, 100), + ValueTwo: rnd(0, 100), + } + second := first + second.Measurement = "Metric B" + second.ValueOne = rnd(0, 100) + second.ValueTwo = rnd(0, 100) + + series = append(series, &first) + series = append(series, &second) } - for _, s := range series { - _, err = sess.Insert(s) + _, 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) So(err, ShouldBeNil) - } + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + 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() { + 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`, + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 1) + So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3) + }) Convey("When doing a metric query grouping by time and select metric column should return correct series", func() { query := &tsdb.TsdbQuery{ @@ -476,7 +675,6 @@ func TestMSSQL(t *testing.T) { resp, err := endpoint.Query(nil, nil, query) queryResult := resp.Results["A"] So(err, ShouldBeNil) - fmt.Println("query", "sql", queryResult.Meta) So(queryResult.Error, ShouldBeNil) So(len(queryResult.Series), ShouldEqual, 4) @@ -696,7 +894,7 @@ func TestMSSQL(t *testing.T) { columns := queryResult.Tables[0].Rows[0] //Should be in milliseconds - So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000)) + So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6)) }) Convey("When doing an annotation query with a time column in epoch second format should return ms", func() { @@ -850,15 +1048,15 @@ func TestMSSQL(t *testing.T) { func InitMSSQLTestDB(t *testing.T) *xorm.Engine { x, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1)) + if err != nil { + t.Fatalf("Failed to init mssql db %v", err) + } + x.DatabaseTZ = time.UTC x.TZLocation = time.UTC // x.ShowSQL() - if err != nil { - t.Fatalf("Failed to init mssql db %v", err) - } - return x } From 5f67d4268d68c4a3469bff97f5535480ba898405 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 10 Apr 2018 15:48:51 +0200 Subject: [PATCH 36/96] added @ngInject --- public/app/core/directives/value_select_dropdown.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/core/directives/value_select_dropdown.ts b/public/app/core/directives/value_select_dropdown.ts index 628b1480a4f..78f46b5aa1b 100644 --- a/public/app/core/directives/value_select_dropdown.ts +++ b/public/app/core/directives/value_select_dropdown.ts @@ -17,6 +17,7 @@ export class ValueSelectDropdownCtrl { hide: any; onUpdated: any; + /** @ngInject */ constructor(private $q) {} show() { @@ -236,6 +237,7 @@ export class ValueSelectDropdownCtrl { } } +/** @ngInject */ export function valueSelectDropdown($compile, $window, $timeout, $rootScope) { return { scope: { variable: '=', onUpdated: '&' }, From 97f67ddcb87581f579359ea62f8f510fce9c917a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 16:36:00 +0200 Subject: [PATCH 37/96] tsdb: improved floating point support when converting sql time column to epoch (ms) --- pkg/tsdb/sql_engine_test.go | 15 +++++++++------ pkg/tsdb/time_range.go | 12 ++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pkg/tsdb/sql_engine_test.go b/pkg/tsdb/sql_engine_test.go index f8856489230..4c6951a0196 100644 --- a/pkg/tsdb/sql_engine_test.go +++ b/pkg/tsdb/sql_engine_test.go @@ -1,6 +1,7 @@ package tsdb import ( + "fmt" "testing" "time" @@ -9,7 +10,7 @@ import ( func TestSqlEngine(t *testing.T) { Convey("SqlEngine", t, func() { - dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC) + dt := time.Date(2018, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC) Convey("Given row values with time.Time as time columns", func() { var nilPointer *time.Time @@ -24,7 +25,7 @@ func TestSqlEngine(t *testing.T) { } Convey("When converting them should return epoch time with millisecond precision ", func() { - expected := float64(dt.UnixNano() / 1e6) + expected := float64(dt.UnixNano()) / float64(time.Millisecond) So(fixtures[0].(float64), ShouldEqual, expected) So(fixtures[1].(float64), ShouldEqual, expected) So(fixtures[2], ShouldBeNil) @@ -132,8 +133,8 @@ func TestSqlEngine(t *testing.T) { }) Convey("Given row values with float64 as time columns", func() { - tSeconds := float64(dt.Unix()) - tMilliseconds := float64(dt.UnixNano() / 1e6) + tSeconds := float64(dt.UnixNano()) / float64(time.Second) + tMilliseconds := float64(dt.UnixNano()) / float64(time.Millisecond) tNanoSeconds := float64(dt.UnixNano()) var nilPointer *float64 @@ -151,10 +152,12 @@ func TestSqlEngine(t *testing.T) { } Convey("When converting them should return epoch time with millisecond precision ", func() { - So(fixtures[0].(float64), ShouldEqual, tSeconds*1e3) - So(fixtures[1].(float64), ShouldEqual, tSeconds*1e3) + So(fixtures[0].(float64), ShouldEqual, tMilliseconds) + 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) diff --git a/pkg/tsdb/time_range.go b/pkg/tsdb/time_range.go index 47076eb3c6b..ed5b77d61de 100644 --- a/pkg/tsdb/time_range.go +++ b/pkg/tsdb/time_range.go @@ -92,14 +92,14 @@ func (tr *TimeRange) ParseTo() (time.Time, error) { // EpochPrecisionToMs converts epoch precision to millisecond, if needed. // Only seconds to milliseconds supported right now func EpochPrecisionToMs(value float64) float64 { - if int64(value)/1e10 == 0 { - return float64(value * 1e3) + s := strconv.FormatFloat(value, 'e', -1, 64) + if strings.HasSuffix(s, "e+09") { + return value * float64(1e3) } - s := strconv.FormatFloat(value, 'f', -1, 64) - if len(s) == 19 { - return float64(value / 1e6) + if strings.HasSuffix(s, "e+18") { + return value / float64(time.Millisecond) } - return float64(value) + return value } From 6cb891dca84c444362fb97b861894339f299ef20 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 16:37:39 +0200 Subject: [PATCH 38/96] mysql: use a datetime column with microsecond precision in test --- pkg/tsdb/mysql/mysql_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 530c8fb6c8a..827ebfa9555 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -309,7 +309,7 @@ func TestMySQL(t *testing.T) { Convey("Given a table with metrics having multiple values and measurements", func() { type metric_values struct { Time time.Time `xorm:"datetime 'time' not null"` - TimeNullable *time.Time `xorm:"datetime 'timeNullable' null"` + TimeNullable *time.Time `xorm:"datetime(6) 'timeNullable' null"` TimeInt64 int64 `xorm:"bigint(20) 'timeInt64' not null"` TimeInt64Nullable *int64 `xorm:"bigint(20) 'timeInt64Nullable' null"` TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"` From be4b715aad729c93683223f52351a18811c34110 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 10 Apr 2018 16:59:48 +0200 Subject: [PATCH 39/96] docker: change mysql container so that it uses utc --- docker/blocks/mysql/dashboard.json | 39 ++++++++++++++++--------- docker/blocks/mysql/docker-compose.yaml | 3 -- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/docker/blocks/mysql/dashboard.json b/docker/blocks/mysql/dashboard.json index e2b791f82e6..dba7847cc72 100644 --- a/docker/blocks/mysql/dashboard.json +++ b/docker/blocks/mysql/dashboard.json @@ -2,7 +2,7 @@ "__inputs": [ { "name": "DS_MYSQL", - "label": "Mysql", + "label": "MySQL", "description": "", "type": "datasource", "pluginId": "mysql", @@ -20,19 +20,19 @@ "type": "panel", "id": "graph", "name": "Graph", - "version": "" + "version": "5.0.0" }, { "type": "datasource", "id": "mysql", "name": "MySQL", - "version": "1.0.0" + "version": "5.0.0" }, { "type": "panel", "id": "table", "name": "Table", - "version": "" + "version": "5.0.0" } ], "annotations": { @@ -53,7 +53,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1518602729468, + "iteration": 1523372133566, "links": [], "panels": [ { @@ -118,7 +118,7 @@ ], "thresholds": [], "timeFrom": null, - "timeShift": "1h", + "timeShift": null, "title": "Average logins / $summarize", "tooltip": { "shared": true, @@ -150,7 +150,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -204,7 +208,7 @@ ], "thresholds": [], "timeFrom": null, - "timeShift": "1h", + "timeShift": null, "title": "Average payments started/ended / $summarize", "tooltip": { "shared": true, @@ -236,7 +240,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "aliasColors": {}, @@ -284,7 +292,7 @@ ], "thresholds": [], "timeFrom": null, - "timeShift": "1h", + "timeShift": null, "title": "Max CPU / $summarize", "tooltip": { "shared": true, @@ -316,7 +324,11 @@ "min": null, "show": true } - ] + ], + "yaxis": { + "align": false, + "alignLevel": null + } }, { "columns": [], @@ -369,7 +381,7 @@ "target": "" } ], - "timeShift": "1h", + "timeShift": null, "title": "Values", "transform": "table", "type": "table" @@ -428,7 +440,6 @@ "auto_count": 5, "auto_min": "10s", "current": { - "selected": true, "text": "1m", "value": "1m" }, @@ -545,5 +556,5 @@ "timezone": "", "title": "Grafana Fake Data Gen - MySQL", "uid": "DGsCac3kz", - "version": 6 + "version": 8 } \ No newline at end of file diff --git a/docker/blocks/mysql/docker-compose.yaml b/docker/blocks/mysql/docker-compose.yaml index f7881e66539..53ff9da62a7 100644 --- a/docker/blocks/mysql/docker-compose.yaml +++ b/docker/blocks/mysql/docker-compose.yaml @@ -7,9 +7,6 @@ MYSQL_PASSWORD: password ports: - "3306:3306" - volumes: - - /etc/localtime:/etc/localtime:ro - - /etc/timezone:/etc/timezone:ro command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --innodb_monitor_enable=all] fake-mysql-data: From 82aa6cf46b64b404683a332b536dad64702fbe07 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 11 Apr 2018 10:26:05 +0200 Subject: [PATCH 40/96] converted functions to arrow functions --- .../core/directives/value_select_dropdown.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/public/app/core/directives/value_select_dropdown.ts b/public/app/core/directives/value_select_dropdown.ts index 78f46b5aa1b..2b8d5de5ad8 100644 --- a/public/app/core/directives/value_select_dropdown.ts +++ b/public/app/core/directives/value_select_dropdown.ts @@ -27,9 +27,9 @@ export class ValueSelectDropdownCtrl { this.options = this.variable.options; this.selectedValues = _.filter(this.options, { selected: true }); - this.tags = _.map(this.variable.tags, function(value) { + this.tags = _.map(this.variable.tags, value => { let tag = { text: value, selected: false }; - _.each(this.variable.current.tags, function(tagObj) { + _.each(this.variable.current.tags, tagObj => { if (tagObj.text === value) { tag = tagObj; } @@ -50,7 +50,7 @@ export class ValueSelectDropdownCtrl { if (current.tags && current.tags.length) { // filer out values that are in selected tags - let selectedAndNotInTag = _.filter(this.variable.options, function(option) { + let selectedAndNotInTag = _.filter(this.variable.options, option => { if (!option.selected) { return false; } @@ -77,7 +77,7 @@ export class ValueSelectDropdownCtrl { } clearSelections() { - _.each(this.options, function(option) { + _.each(this.options, option => { option.selected = false; }); @@ -93,10 +93,10 @@ export class ValueSelectDropdownCtrl { tagValuesPromise = this.$q.when(tag.values); } - tagValuesPromise.then(function(values) { + tagValuesPromise.then(values => { tag.values = values; tag.valuesText = values.join(' + '); - _.each(this.options, function(option) { + _.each(this.options, option => { if (_.indexOf(tag.values, option.value) !== -1) { option.selected = tag.selected; } @@ -143,7 +143,7 @@ export class ValueSelectDropdownCtrl { excludeOthers = excludeOthers || false; let setAllExceptCurrentTo = function(newValue) { - _.each(this.options, function(other) { + _.each(this.options, other => { if (option !== other) { other.selected = newValue; } @@ -180,9 +180,9 @@ export class ValueSelectDropdownCtrl { } // validate selected tags - _.each(this.tags, function(tag) { + _.each(this.tags, tag => { if (tag.selected) { - _.each(tag.values, function(value) { + _.each(tag.values, value => { if (!_.find(this.selectedValues, { value: value })) { tag.selected = false; } @@ -224,7 +224,7 @@ export class ValueSelectDropdownCtrl { queryChanged() { this.highlightIndex = -1; - this.search.options = _.filter(this.options, function(option) { + this.search.options = _.filter(this.options, option => { return option.text.toLowerCase().indexOf(this.search.query.toLowerCase()) !== -1; }); @@ -280,7 +280,7 @@ export function valueSelectDropdown($compile, $window, $timeout, $rootScope) { } } - scope.$watch('vm.dropdownVisible', function(newValue) { + scope.$watch('vm.dropdownVisible', newValue => { if (newValue) { openDropdown(); } else { @@ -288,11 +288,11 @@ export function valueSelectDropdown($compile, $window, $timeout, $rootScope) { } }); - let cleanUp = $rootScope.$on('template-variable-value-updated', function() { + let cleanUp = $rootScope.$on('template-variable-value-updated', () => { scope.vm.updateLinkText(); }); - scope.$on('$destroy', function() { + scope.$on('$destroy', () => { cleanUp(); }); From 229486015d91133c23083b493c5fe202884c178a Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 12 Apr 2018 10:44:00 +0200 Subject: [PATCH 41/96] added styling to fontawesome icons so they have same size as the other icons --- public/sass/components/_dashboard_settings.scss | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/public/sass/components/_dashboard_settings.scss b/public/sass/components/_dashboard_settings.scss index 11d943eb13c..6be7fab53e5 100644 --- a/public/sass/components/_dashboard_settings.scss +++ b/public/sass/components/_dashboard_settings.scss @@ -3,6 +3,20 @@ height: 100%; display: flex; flex-direction: row; + + //fix for fontawesome icons + .fa-lock { + &::before { + font-size: 20px; + text-align: center; + padding-left: 1px; + } + } + .fa-history { + &::before { + font-size: 17px; + } + } } .dashboard-page--settings-opening { From f5586b1270ebd92e5fc2ebb8a22f1c1bbc186f43 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 12 Apr 2018 18:53:12 +0200 Subject: [PATCH 42/96] tsdb: sql data sources should handle time ranges before epoch start correctly --- pkg/tsdb/mssql/macros.go | 12 +- pkg/tsdb/mssql/macros_test.go | 271 +++++++++++++++++++++---------- pkg/tsdb/mysql/macros.go | 12 +- pkg/tsdb/mysql/macros_test.go | 233 ++++++++++++++++++-------- pkg/tsdb/postgres/macros.go | 12 +- pkg/tsdb/postgres/macros_test.go | 218 ++++++++++++++++++------- pkg/tsdb/time_range.go | 16 ++ 7 files changed, 546 insertions(+), 228 deletions(-) diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index 9d41cd03255..f53e06131be 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -82,11 +82,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__timeFrom": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil case "__timeTo": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) @@ -113,11 +113,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil case "__unixEpochTo": - return fmt.Sprintf("%d", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsSecondsEpoch())), nil default: return "", fmt.Errorf("Unknown macro %v", name) } diff --git a/pkg/tsdb/mssql/macros_test.go b/pkg/tsdb/mssql/macros_test.go index 12a9b0d82be..ae0d4f67d2b 100644 --- a/pkg/tsdb/mssql/macros_test.go +++ b/pkg/tsdb/mssql/macros_test.go @@ -1,6 +1,8 @@ package mssql import ( + "fmt" + "strconv" "testing" "time" @@ -13,112 +15,213 @@ import ( func TestMacroEngine(t *testing.T) { Convey("MacroEngine", t, func() { engine := &MsSqlMacroEngine{} - timeRange := &tsdb.TimeRange{From: "5m", To: "now"} query := &tsdb.Query{ Model: simplejson.New(), } - Convey("interpolate __time function", func() { - sql, err := engine.Interpolate(query, nil, "select $__time(time_column)") - So(err, ShouldBeNil) + 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) + to := from.Add(5 * time.Minute) + timeRange := tsdb.NewFakeTimeRange("5m", "now", to) - So(sql, ShouldEqual, "select time_column AS time") + Convey("interpolate __time function", func() { + sql, err := engine.Interpolate(query, nil, "select $__time(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "select time_column AS time") + }) + + Convey("interpolate __timeEpoch function", func() { + sql, err := engine.Interpolate(query, nil, "select $__timeEpoch(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "select DATEDIFF(second, '1970-01-01', time_column) AS time") + }) + + Convey("interpolate __timeEpoch function wrapped in aggregation", func() { + sql, err := engine.Interpolate(query, nil, "select min($__timeEpoch(time_column))") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "select min(DATEDIFF(second, '1970-01-01', time_column) AS time)") + }) + + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column >= DATEADD(s, %d, '1970-01-01') AND time_column <= DATEADD(s, %d, '1970-01-01')", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeGroup function", func() { + sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300") + }) + + Convey("interpolate __timeGroup function with spaces around arguments", func() { + sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300") + }) + + Convey("interpolate __timeGroup function with fill (value = NULL)", func() { + _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', NULL)") + + fill := query.Model.Get("fill").MustBool() + fillNull := query.Model.Get("fillNull").MustBool() + fillInterval := query.Model.Get("fillInterval").MustInt() + + So(err, ShouldBeNil) + So(fill, ShouldBeTrue) + So(fillNull, ShouldBeTrue) + So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) + }) + + Convey("interpolate __timeGroup function with fill (value = float)", func() { + _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', 1.5)") + + fill := query.Model.Get("fill").MustBool() + fillValue := query.Model.Get("fillValue").MustFloat64() + fillInterval := query.Model.Get("fillInterval").MustInt() + + So(err, ShouldBeNil) + So(fill, ShouldBeTrue) + So(fillValue, ShouldEqual, 1.5) + So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) + }) + + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select DATEADD(second, %d, '1970-01-01')", from.Unix())) + }) + + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select DATEADD(second, %d, '1970-01-01')", to.Unix())) + }) + + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.Unix(), to.Unix())) + }) + + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) + + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) - Convey("interpolate __timeEpoch function", func() { - sql, err := engine.Interpolate(query, nil, "select $__timeEpoch(time_column)") - So(err, ShouldBeNil) + Convey("Given a time range between 1960-02-01 07:00 and 1965-02-03 08:00", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC) + to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) - So(sql, ShouldEqual, "select DATEDIFF(second, '1970-01-01', time_column) AS time") + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column >= DATEADD(s, %d, '1970-01-01') AND time_column <= DATEADD(s, %d, '1970-01-01')", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select DATEADD(second, %d, '1970-01-01')", from.Unix())) + }) + + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select DATEADD(second, %d, '1970-01-01')", to.Unix())) + }) + + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.Unix(), to.Unix())) + }) + + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) + + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) - Convey("interpolate __timeEpoch function wrapped in aggregation", func() { - sql, err := engine.Interpolate(query, nil, "select min($__timeEpoch(time_column))") - So(err, ShouldBeNil) + Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC) + to := time.Date(1980, 2, 3, 8, 0, 0, 0, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) - So(sql, ShouldEqual, "select min(DATEDIFF(second, '1970-01-01', time_column) AS time)") - }) + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) - Convey("interpolate __timeFilter function", func() { - sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column >= DATEADD(s, %d, '1970-01-01') AND time_column <= DATEADD(s, %d, '1970-01-01')", from.Unix(), to.Unix())) + }) - So(sql, ShouldEqual, "WHERE time_column >= DATEADD(s, 18446744066914186738, '1970-01-01') AND time_column <= DATEADD(s, 18446744066914187038, '1970-01-01')") - }) + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) - Convey("interpolate __timeGroup function", func() { - sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") - So(err, ShouldBeNil) + So(sql, ShouldEqual, fmt.Sprintf("select DATEADD(second, %d, '1970-01-01')", from.Unix())) + }) - So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300") - }) + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) - Convey("interpolate __timeGroup function with spaces around arguments", func() { - sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") - So(err, ShouldBeNil) + So(sql, ShouldEqual, fmt.Sprintf("select DATEADD(second, %d, '1970-01-01')", to.Unix())) + }) - So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300") - }) + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)") + So(err, ShouldBeNil) - Convey("interpolate __timeGroup function with fill (value = NULL)", func() { - _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', NULL)") + So(sql, ShouldEqual, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.Unix(), to.Unix())) + }) - fill := query.Model.Get("fill").MustBool() - fillNull := query.Model.Get("fillNull").MustBool() - fillInterval := query.Model.Get("fillInterval").MustInt() + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) - So(err, ShouldBeNil) - So(fill, ShouldBeTrue) - So(fillNull, ShouldBeTrue) - So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) - }) + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) - Convey("interpolate __timeGroup function with fill (value = float)", func() { - _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', 1.5)") + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) - fill := query.Model.Get("fill").MustBool() - fillValue := query.Model.Get("fillValue").MustFloat64() - fillInterval := query.Model.Get("fillInterval").MustInt() - - So(err, ShouldBeNil) - So(fill, ShouldBeTrue) - So(fillValue, ShouldEqual, 1.5) - So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) - }) - - Convey("interpolate __timeFrom function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select DATEADD(second, 18446744066914186738, '1970-01-01')") - }) - - Convey("interpolate __timeTo function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select DATEADD(second, 18446744066914187038, '1970-01-01')") - }) - - Convey("interpolate __unixEpochFilter function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select time_column >= 18446744066914186738 AND time_column <= 18446744066914187038") - }) - - Convey("interpolate __unixEpochFrom function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914186738") - }) - - Convey("interpolate __unixEpochTo function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914187038") + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) }) } diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index 92f6968f12a..d4a08bd6241 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -77,11 +77,11 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= FROM_UNIXTIME(%d) AND %s <= FROM_UNIXTIME(%d)", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= FROM_UNIXTIME(%d) AND %s <= FROM_UNIXTIME(%d)", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__timeFrom": - return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil case "__timeTo": - return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) @@ -108,11 +108,11 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil case "__unixEpochTo": - return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsSecondsEpoch())), nil default: return "", fmt.Errorf("Unknown macro %v", name) } diff --git a/pkg/tsdb/mysql/macros_test.go b/pkg/tsdb/mysql/macros_test.go index a89ba16ab78..66ec143eac8 100644 --- a/pkg/tsdb/mysql/macros_test.go +++ b/pkg/tsdb/mysql/macros_test.go @@ -1,7 +1,10 @@ package mysql import ( + "fmt" + "strconv" "testing" + "time" "github.com/grafana/grafana/pkg/tsdb" . "github.com/smartystreets/goconvey/convey" @@ -11,79 +14,179 @@ func TestMacroEngine(t *testing.T) { Convey("MacroEngine", t, func() { engine := &MySqlMacroEngine{} query := &tsdb.Query{} - timeRange := &tsdb.TimeRange{From: "5m", To: "now"} - Convey("interpolate __time function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__time(time_column)") - So(err, ShouldBeNil) + 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) + to := from.Add(5 * time.Minute) + timeRange := tsdb.NewFakeTimeRange("5m", "now", to) - So(sql, ShouldEqual, "select UNIX_TIMESTAMP(time_column) as time_sec") + Convey("interpolate __time function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__time(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "select UNIX_TIMESTAMP(time_column) as time_sec") + }) + + Convey("interpolate __time function wrapped in aggregation", func() { + sql, err := engine.Interpolate(query, timeRange, "select min($__time(time_column))") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "select min(UNIX_TIMESTAMP(time_column) as time_sec)") + }) + + Convey("interpolate __timeGroup function", func() { + + sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "GROUP BY cast(cast(UNIX_TIMESTAMP(time_column)/(300) as signed)*300 as signed)") + }) + + Convey("interpolate __timeGroup function with spaces around arguments", func() { + + sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "GROUP BY cast(cast(UNIX_TIMESTAMP(time_column)/(300) as signed)*300 as signed)") + }) + + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column >= FROM_UNIXTIME(%d) AND time_column <= FROM_UNIXTIME(%d)", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", from.Unix())) + }) + + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", to.Unix())) + }) + + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) + }) + + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) + + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) - Convey("interpolate __time function wrapped in aggregation", func() { - sql, err := engine.Interpolate(query, timeRange, "select min($__time(time_column))") - So(err, ShouldBeNil) + Convey("Given a time range between 1960-02-01 07:00 and 1965-02-03 08:00", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC) + to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) - So(sql, ShouldEqual, "select min(UNIX_TIMESTAMP(time_column) as time_sec)") + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column >= FROM_UNIXTIME(%d) AND time_column <= FROM_UNIXTIME(%d)", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", from.Unix())) + }) + + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", to.Unix())) + }) + + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) + }) + + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) + + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) - Convey("interpolate __timeFilter function", func() { - sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) + Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC) + to := time.Date(1980, 2, 3, 8, 0, 0, 0, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) - So(sql, ShouldEqual, "WHERE time_column >= FROM_UNIXTIME(18446744066914186738) AND time_column <= FROM_UNIXTIME(18446744066914187038)") + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column >= FROM_UNIXTIME(%d) AND time_column <= FROM_UNIXTIME(%d)", from.Unix(), to.Unix())) + }) + + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", from.Unix())) + }) + + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", to.Unix())) + }) + + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) + }) + + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) + + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) - - Convey("interpolate __timeFrom function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select FROM_UNIXTIME(18446744066914186738)") - }) - - Convey("interpolate __timeGroup function", func() { - - sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "GROUP BY cast(cast(UNIX_TIMESTAMP(time_column)/(300) as signed)*300 as signed)") - }) - - Convey("interpolate __timeGroup function with spaces around arguments", func() { - - sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "GROUP BY cast(cast(UNIX_TIMESTAMP(time_column)/(300) as signed)*300 as signed)") - }) - - Convey("interpolate __timeTo function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select FROM_UNIXTIME(18446744066914187038)") - }) - - Convey("interpolate __unixEpochFilter function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(18446744066914186738)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914186738 >= 18446744066914186738 AND 18446744066914186738 <= 18446744066914187038") - }) - - Convey("interpolate __unixEpochFrom function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914186738") - }) - - Convey("interpolate __unixEpochTo function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914187038") - }) - }) } diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index f96b3896041..b28b7bd36da 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -83,11 +83,11 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, 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], int64(m.TimeRange.GetFromAsMsEpoch()/1000), int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("extract(epoch from %s) BETWEEN %d AND %d", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__timeFrom": - return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil case "__timeTo": - return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) @@ -114,11 +114,11 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil case "__unixEpochTo": - return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsMsEpoch()/1000)), nil + return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsSecondsEpoch())), nil default: return "", fmt.Errorf("Unknown macro %v", name) } diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index 838a73ce240..f441690a429 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -1,7 +1,10 @@ package postgres import ( + "fmt" + "strconv" "testing" + "time" "github.com/grafana/grafana/pkg/tsdb" . "github.com/smartystreets/goconvey/convey" @@ -11,86 +14,179 @@ func TestMacroEngine(t *testing.T) { Convey("MacroEngine", t, func() { engine := &PostgresMacroEngine{} query := &tsdb.Query{} - timeRange := &tsdb.TimeRange{From: "5m", To: "now"} - Convey("interpolate __time function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__time(time_column)") - So(err, ShouldBeNil) + 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) + to := from.Add(5 * time.Minute) + timeRange := tsdb.NewFakeTimeRange("5m", "now", to) - So(sql, ShouldEqual, "select time_column AS \"time\"") + Convey("interpolate __time function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__time(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "select time_column AS \"time\"") + }) + + Convey("interpolate __time function wrapped in aggregation", func() { + sql, err := engine.Interpolate(query, timeRange, "select min($__time(time_column))") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "select min(time_column AS \"time\")") + }) + + Convey("interpolate __timeFilter function", func() { + 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())) + }) + + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", from.Unix())) + }) + + Convey("interpolate __timeGroup function", func() { + + sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time") + }) + + Convey("interpolate __timeGroup function with spaces between args", func() { + + sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time") + }) + + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", to.Unix())) + }) + + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) + }) + + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) + + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) - Convey("interpolate __time function wrapped in aggregation", func() { - sql, err := engine.Interpolate(query, timeRange, "select min($__time(time_column))") - So(err, ShouldBeNil) + Convey("Given a time range between 1960-02-01 07:00 and 1965-02-03 08:00", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC) + to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) - So(sql, ShouldEqual, "select min(time_column AS \"time\")") + Convey("interpolate __timeFilter function", func() { + 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())) + }) + + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", from.Unix())) + }) + + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", to.Unix())) + }) + + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) + }) + + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) + + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) - Convey("interpolate __timeFilter function", func() { - sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) + Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC) + to := time.Date(1980, 2, 3, 8, 0, 0, 0, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) - So(sql, ShouldEqual, "WHERE extract(epoch from time_column) BETWEEN 18446744066914186738 AND 18446744066914187038") - }) + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) - Convey("interpolate __timeFrom function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(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, "select to_timestamp(18446744066914186738)") - }) + Convey("interpolate __timeFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)") + So(err, ShouldBeNil) - Convey("interpolate __timeGroup function", func() { + So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", from.Unix())) + }) - sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") - So(err, ShouldBeNil) + Convey("interpolate __timeTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") + So(err, ShouldBeNil) - So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time") - }) + So(sql, ShouldEqual, fmt.Sprintf("select to_timestamp(%d)", to.Unix())) + }) - Convey("interpolate __timeGroup function with spaces between args", func() { + Convey("interpolate __unixEpochFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)") + So(err, ShouldBeNil) - sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") - So(err, ShouldBeNil) + So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) + }) - So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time") - }) + Convey("interpolate __unixEpochFrom function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") + So(err, ShouldBeNil) - Convey("interpolate __timeTo function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)") - So(err, ShouldBeNil) + So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix())) + }) - So(sql, ShouldEqual, "select to_timestamp(18446744066914187038)") - }) + Convey("interpolate __unixEpochTo function", func() { + sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") + So(err, ShouldBeNil) - Convey("interpolate __unixEpochFilter function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(18446744066914186738)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914186738 >= 18446744066914186738 AND 18446744066914186738 <= 18446744066914187038") - }) - - Convey("interpolate __unixEpochFrom function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914186738") - }) - - Convey("interpolate __unixEpochTo function", func() { - sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "select 18446744066914187038") - }) - - timeRange = &tsdb.TimeRange{From: "-315622800000", To: "315529200000"} // 1960-1980 - Convey("interpolate __timeFilter function before epoch", func() { - sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") - So(err, ShouldBeNil) - - So(sql, ShouldEqual, "WHERE extract(epoch from time_column) BETWEEN -315622800 AND 315529200") + So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix())) + }) }) }) } diff --git a/pkg/tsdb/time_range.go b/pkg/tsdb/time_range.go index fd0cb3f8e82..5ebad362bf7 100644 --- a/pkg/tsdb/time_range.go +++ b/pkg/tsdb/time_range.go @@ -15,6 +15,14 @@ func NewTimeRange(from, to string) *TimeRange { } } +func NewFakeTimeRange(from, to string, now time.Time) *TimeRange { + return &TimeRange{ + From: from, + To: to, + now: now, + } +} + type TimeRange struct { From string To string @@ -25,10 +33,18 @@ func (tr *TimeRange) GetFromAsMsEpoch() int64 { return tr.MustGetFrom().UnixNano() / int64(time.Millisecond) } +func (tr *TimeRange) GetFromAsSecondsEpoch() int64 { + return tr.GetFromAsMsEpoch() / 1000 +} + func (tr *TimeRange) GetToAsMsEpoch() int64 { return tr.MustGetTo().UnixNano() / int64(time.Millisecond) } +func (tr *TimeRange) GetToAsSecondsEpoch() int64 { + return tr.GetToAsMsEpoch() / 1000 +} + func (tr *TimeRange) MustGetFrom() time.Time { if res, err := tr.ParseFrom(); err != nil { return time.Unix(0, 0) From a86ee304ff9d7f30f044eebdf673c13fcbc0a7b9 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 12 Apr 2018 19:08:35 +0200 Subject: [PATCH 43/96] tsdb: remove unnecessary type casts in sql data sources macro engines --- pkg/tsdb/mssql/macros.go | 12 ++++++------ pkg/tsdb/mysql/macros.go | 12 ++++++------ pkg/tsdb/postgres/macros.go | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index f53e06131be..bb9489cd654 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -82,11 +82,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], m.TimeRange.GetFromAsSecondsEpoch(), args[0], m.TimeRange.GetToAsSecondsEpoch()), nil case "__timeFrom": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__timeTo": - return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", m.TimeRange.GetToAsSecondsEpoch()), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) @@ -113,11 +113,11 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], m.TimeRange.GetFromAsSecondsEpoch(), args[0], m.TimeRange.GetToAsSecondsEpoch()), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil + return fmt.Sprintf("%d", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__unixEpochTo": - return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%d", m.TimeRange.GetToAsSecondsEpoch()), nil default: return "", fmt.Errorf("Unknown macro %v", name) } diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index d4a08bd6241..fadcbe4edbc 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -77,11 +77,11 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= FROM_UNIXTIME(%d) AND %s <= FROM_UNIXTIME(%d)", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%s >= FROM_UNIXTIME(%d) AND %s <= FROM_UNIXTIME(%d)", args[0], m.TimeRange.GetFromAsSecondsEpoch(), args[0], m.TimeRange.GetToAsSecondsEpoch()), nil case "__timeFrom": - return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil + return fmt.Sprintf("FROM_UNIXTIME(%d)", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__timeTo": - return fmt.Sprintf("FROM_UNIXTIME(%d)", int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("FROM_UNIXTIME(%d)", m.TimeRange.GetToAsSecondsEpoch()), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) @@ -108,11 +108,11 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], m.TimeRange.GetFromAsSecondsEpoch(), args[0], m.TimeRange.GetToAsSecondsEpoch()), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil + return fmt.Sprintf("%d", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__unixEpochTo": - return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%d", m.TimeRange.GetToAsSecondsEpoch()), nil default: return "", fmt.Errorf("Unknown macro %v", name) } diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index b28b7bd36da..bd0ac0cc620 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -83,11 +83,11 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, 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], int64(m.TimeRange.GetFromAsSecondsEpoch()), int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("extract(epoch from %s) BETWEEN %d AND %d", args[0], m.TimeRange.GetFromAsSecondsEpoch(), m.TimeRange.GetToAsSecondsEpoch()), nil case "__timeFrom": - return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil + return fmt.Sprintf("to_timestamp(%d)", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__timeTo": - return fmt.Sprintf("to_timestamp(%d)", int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("to_timestamp(%d)", m.TimeRange.GetToAsSecondsEpoch()), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) @@ -114,11 +114,11 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], int64(m.TimeRange.GetFromAsSecondsEpoch()), args[0], int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], m.TimeRange.GetFromAsSecondsEpoch(), args[0], m.TimeRange.GetToAsSecondsEpoch()), nil case "__unixEpochFrom": - return fmt.Sprintf("%d", int64(m.TimeRange.GetFromAsSecondsEpoch())), nil + return fmt.Sprintf("%d", m.TimeRange.GetFromAsSecondsEpoch()), nil case "__unixEpochTo": - return fmt.Sprintf("%d", int64(m.TimeRange.GetToAsSecondsEpoch())), nil + return fmt.Sprintf("%d", m.TimeRange.GetToAsSecondsEpoch()), nil default: return "", fmt.Errorf("Unknown macro %v", name) } From a43e7c7b3fe323ea6cb2407a44d865bdcd9541af Mon Sep 17 00:00:00 2001 From: Tobias Wolf Date: Thu, 12 Apr 2018 21:14:58 +0200 Subject: [PATCH 44/96] =?UTF-8?q?Add=20another=20URL=20param=20=C2=ABinact?= =?UTF-8?q?ive=C2=BB=20which=20works=20like=20=C2=ABkiosk=C2=BB=20but=20wi?= =?UTF-8?q?th=20title?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #11228 --- public/app/core/components/grafana_app.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index 798a40cb1bf..01218c40529 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -117,6 +117,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop appEvents.emit('toggle-kiosk-mode'); } + // check for 'inactive' url param for clean looks like kiosk, but with title + if (data.params.inactive) { + body.addClass('user-activity-low'); + + // for some reason, with this class it looks cleanest + body.addClass('sidemenu-open'); + } + // close all drops for (let drop of Drop.drops) { drop.destroy(); From f143cb655a5e8c691083b1ab66fd763264d547cb Mon Sep 17 00:00:00 2001 From: Tobias Wolf Date: Thu, 12 Apr 2018 21:38:28 +0200 Subject: [PATCH 45/96] Mention the ?inactive parameter in the docs --- docs/sources/reference/playlist.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/playlist.md b/docs/sources/reference/playlist.md index 5a6bf921334..182e69eebd0 100644 --- a/docs/sources/reference/playlist.md +++ b/docs/sources/reference/playlist.md @@ -49,7 +49,7 @@ Click the back button to rewind to the previous Dashboard in the Playlist. In TV mode the top navbar, row & panel controls will all fade to transparent. This happens automatically after one minute of user inactivity but can also be toggled manually -with the `d v` sequence shortcut. Any mouse movement or keyboard action will +with the `d v` sequence shortcut, or by appending the parameter `?inactive` to the dashboard URL. Any mouse movement or keyboard action will restore navbar & controls. Another feature is the kiosk mode - in kiosk mode the navbar is completely hidden/removed from view. This can be enabled with the `d k` From c431875f28962355db566309798c0729f6cb4cda Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 13 Apr 2018 13:50:15 +0200 Subject: [PATCH 46/96] permissions sorting fixed + icon same size as avatrs --- .../Permissions/DisabledPermissionsListItem.tsx | 2 +- .../components/Permissions/PermissionsListItem.tsx | 4 ++-- .../app/stores/PermissionsStore/PermissionsStore.ts | 12 ++++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx index 5e473c25869..5e2497d983e 100644 --- a/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx +++ b/public/app/core/components/Permissions/DisabledPermissionsListItem.tsx @@ -13,7 +13,7 @@ export default class DisabledPermissionListItem extends Component { return ( - + {item.name} diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index 229c774c639..ee1108a6998 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -15,10 +15,10 @@ function ItemAvatar({ item }) { return ; } if (item.role === 'Editor') { - return ; + return ; } - return ; + return ; } function ItemDescription({ item }) { diff --git a/public/app/stores/PermissionsStore/PermissionsStore.ts b/public/app/stores/PermissionsStore/PermissionsStore.ts index 7761ecc13c7..833d1bdaac7 100644 --- a/public/app/stores/PermissionsStore/PermissionsStore.ts +++ b/public/app/stores/PermissionsStore/PermissionsStore.ts @@ -155,6 +155,8 @@ export const PermissionsStore = types try { yield updateItems(self, updatedItems); self.items.push(newItem); + let sortedItems = self.items.sort((a, b) => b.sortRank - a.sortRank || a.name.localeCompare(b.name)); + self.items = sortedItems; resetNewTypeInternal(); } catch {} yield Promise.resolve(); @@ -214,9 +216,11 @@ const updateItems = (self, items) => { }; const prepareServerResponse = (response, dashboardId: number, isFolder: boolean, isInRoot: boolean) => { - return response.map(item => { - return prepareItem(item, dashboardId, isFolder, isInRoot); - }); + return response + .map(item => { + return prepareItem(item, dashboardId, isFolder, isInRoot); + }) + .sort((a, b) => b.sortRank - a.sortRank || a.name.localeCompare(b.name)); }; const prepareItem = (item, dashboardId: number, isFolder: boolean, isInRoot: boolean) => { @@ -233,7 +237,7 @@ const prepareItem = (item, dashboardId: number, isFolder: boolean, isInRoot: boo item.icon = 'fa fa-fw fa-street-view'; item.name = item.role; item.sortRank = 30; - if (item.role === 'Viewer') { + if (item.role === 'Editor') { item.sortRank += 1; } } From 9ad8a77a21badc331c5467221eb63da91978c9a2 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 13 Apr 2018 14:53:36 +0200 Subject: [PATCH 47/96] ordered user orgs alphabeticaly fixes #11556 --- pkg/services/sqlstore/user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index f42ff5fb2ed..db7e851435c 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -333,6 +333,7 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error { sess.Join("INNER", "org", "org_user.org_id=org.id") sess.Where("org_user.user_id=?", query.UserId) sess.Cols("org.name", "org_user.role", "org_user.org_id") + sess.OrderBy("org.name") err := sess.Find(&query.Result) return err } From 91fb2e07ce9b7bf9ee433fd08e3d53506e20a53d Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 13 Apr 2018 18:40:14 +0200 Subject: [PATCH 48/96] pkg: fix codespell issues --- pkg/api/metrics.go | 2 +- pkg/api/static/static.go | 2 +- pkg/components/dashdiffs/formatter_json.go | 2 +- pkg/components/dynmap/dynmap_test.go | 4 ++-- pkg/middleware/recovery.go | 2 +- pkg/models/dashboards.go | 2 +- pkg/models/folders.go | 2 +- pkg/plugins/plugins_test.go | 2 +- pkg/plugins/queries.go | 2 +- pkg/services/alerting/engine_test.go | 8 ++++---- pkg/services/alerting/notifiers/hipchat.go | 2 +- pkg/services/alerting/notifiers/slack.go | 2 +- pkg/services/alerting/notifiers/teams.go | 4 ++-- pkg/services/alerting/notifiers/telegram_test.go | 2 +- pkg/services/alerting/result_handler.go | 2 +- pkg/services/alerting/scheduler.go | 6 +++--- pkg/services/guardian/guardian.go | 2 +- pkg/services/provisioning/dashboards/config_reader.go | 2 +- pkg/services/provisioning/datasources/config_reader.go | 2 +- pkg/services/sqlstore/alert_notification_test.go | 2 +- pkg/tsdb/opentsdb/opentsdb_test.go | 2 +- pkg/tsdb/postgres/macros.go | 2 +- 22 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 5c06d652b70..c1b8ffe595e 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -75,7 +75,7 @@ func GetTestDataScenarios(c *m.ReqContext) Response { return JSON(200, &result) } -// Genereates a index out of range error +// Generates a index out of range error func GenerateError(c *m.ReqContext) Response { var array []string return JSON(200, array[20]) diff --git a/pkg/api/static/static.go b/pkg/api/static/static.go index 7a61c85b4f3..2a35dd11fa6 100644 --- a/pkg/api/static/static.go +++ b/pkg/api/static/static.go @@ -48,7 +48,7 @@ type StaticOptions struct { // Expires defines which user-defined function to use for producing a HTTP Expires Header // https://developers.google.com/speed/docs/insights/LeverageBrowserCaching AddHeaders func(ctx *macaron.Context) - // FileSystem is the interface for supporting any implmentation of file system. + // FileSystem is the interface for supporting any implementation of file system. FileSystem http.FileSystem } diff --git a/pkg/components/dashdiffs/formatter_json.go b/pkg/components/dashdiffs/formatter_json.go index 3a9ddcc4ee3..488a345d492 100644 --- a/pkg/components/dashdiffs/formatter_json.go +++ b/pkg/components/dashdiffs/formatter_json.go @@ -22,7 +22,7 @@ const ( ) var ( - // changeTypeToSymbol is used for populating the terminating characer in + // changeTypeToSymbol is used for populating the terminating character in // the diff changeTypeToSymbol = map[ChangeType]string{ ChangeNil: "", diff --git a/pkg/components/dynmap/dynmap_test.go b/pkg/components/dynmap/dynmap_test.go index cc002ea06e0..1dacee163f1 100644 --- a/pkg/components/dynmap/dynmap_test.go +++ b/pkg/components/dynmap/dynmap_test.go @@ -76,10 +76,10 @@ func TestFirst(t *testing.T) { assert.True(s == "fallback", "must get string return fallback") s, err = j.GetString("name") - assert.True(s == "anton" && err == nil, "name shoud match") + assert.True(s == "anton" && err == nil, "name should match") s, err = j.GetString("address", "street") - assert.True(s == "Street 42" && err == nil, "street shoud match") + assert.True(s == "Street 42" && err == nil, "street should match") //log.Println("s: ", s.String()) _, err = j.GetNumber("age") diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go index ec289387aa4..456bc91354e 100644 --- a/pkg/middleware/recovery.go +++ b/pkg/middleware/recovery.go @@ -35,7 +35,7 @@ var ( slash = []byte("/") ) -// stack returns a nicely formated stack frame, skipping skip frames +// stack returns a nicely formatted stack frame, skipping skip frames func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 8cd2b01811c..6393595abb3 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -157,7 +157,7 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard { return dash } -// GetDashboardModel turns the command into the savable model +// GetDashboardModel turns the command into the saveable model func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard { dash := NewDashboardFromJson(cmd.Dashboard) userId := cmd.UserId diff --git a/pkg/models/folders.go b/pkg/models/folders.go index c61620a11fc..0c876edcfd7 100644 --- a/pkg/models/folders.go +++ b/pkg/models/folders.go @@ -32,7 +32,7 @@ type Folder struct { HasAcl bool } -// GetDashboardModel turns the command into the savable model +// GetDashboardModel turns the command into the saveable model func (cmd *CreateFolderCommand) GetDashboardModel(orgId int64, userId int64) *Dashboard { dashFolder := NewDashboardFolder(strings.TrimSpace(cmd.Title)) dashFolder.OrgId = orgId diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index 4d3ccb4502b..00329b4a8a1 100644 --- a/pkg/plugins/plugins_test.go +++ b/pkg/plugins/plugins_test.go @@ -12,7 +12,7 @@ import ( func TestPluginScans(t *testing.T) { - Convey("When scaning for plugins", t, func() { + Convey("When scanning for plugins", t, func() { setting.StaticRootPath, _ = filepath.Abs("../../public/") setting.Cfg = ini.Empty() err := initPlugins(context.Background()) diff --git a/pkg/plugins/queries.go b/pkg/plugins/queries.go index 5ae1825a88f..5bd412d2cc9 100644 --- a/pkg/plugins/queries.go +++ b/pkg/plugins/queries.go @@ -37,7 +37,7 @@ func GetPluginSettings(orgId int64) (map[string]*m.PluginSettingInfoDTO, error) // if it's included in app check app settings if pluginDef.IncludedInAppId != "" { - // app componets are by default disabled + // app components are by default disabled opt.Enabled = false if appSettings, ok := pluginMap[pluginDef.IncludedInAppId]; ok { diff --git a/pkg/services/alerting/engine_test.go b/pkg/services/alerting/engine_test.go index 64f954c6dd5..63108bbb9aa 100644 --- a/pkg/services/alerting/engine_test.go +++ b/pkg/services/alerting/engine_test.go @@ -10,7 +10,7 @@ import ( ) type FakeEvalHandler struct { - SuccessCallID int // 0 means never sucess + SuccessCallID int // 0 means never success CallNb int } @@ -87,7 +87,7 @@ func TestEngineProcessJob(t *testing.T) { Convey("Should trigger as many retries as needed", func() { - Convey("never sucess -> max retries number", func() { + Convey("never success -> max retries number", func() { expectedAttempts := alertMaxAttempts evalHandler := NewFakeEvalHandler(0) engine.evalHandler = evalHandler @@ -96,7 +96,7 @@ func TestEngineProcessJob(t *testing.T) { So(evalHandler.CallNb, ShouldEqual, expectedAttempts) }) - Convey("always sucess -> never retry", func() { + Convey("always success -> never retry", func() { expectedAttempts := 1 evalHandler := NewFakeEvalHandler(1) engine.evalHandler = evalHandler @@ -105,7 +105,7 @@ func TestEngineProcessJob(t *testing.T) { So(evalHandler.CallNb, ShouldEqual, expectedAttempts) }) - Convey("some errors before sucess -> some retries", func() { + Convey("some errors before success -> some retries", func() { expectedAttempts := int(math.Ceil(float64(alertMaxAttempts) / 2)) evalHandler := NewFakeEvalHandler(expectedAttempts) engine.evalHandler = evalHandler diff --git a/pkg/services/alerting/notifiers/hipchat.go b/pkg/services/alerting/notifiers/hipchat.go index f1f63d42a04..58e1b7bd71e 100644 --- a/pkg/services/alerting/notifiers/hipchat.go +++ b/pkg/services/alerting/notifiers/hipchat.go @@ -111,7 +111,7 @@ func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error { } message := "" - if evalContext.Rule.State != models.AlertStateOK { //dont add message when going back to alert state ok. + if evalContext.Rule.State != models.AlertStateOK { //don't add message when going back to alert state ok. message += " " + evalContext.Rule.Message } diff --git a/pkg/services/alerting/notifiers/slack.go b/pkg/services/alerting/notifiers/slack.go index e051a71740a..a8139b62726 100644 --- a/pkg/services/alerting/notifiers/slack.go +++ b/pkg/services/alerting/notifiers/slack.go @@ -129,7 +129,7 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { } message := this.Mention - if evalContext.Rule.State != m.AlertStateOK { //dont add message when going back to alert state ok. + if evalContext.Rule.State != m.AlertStateOK { //don't add message when going back to alert state ok. message += " " + evalContext.Rule.Message } image_url := "" diff --git a/pkg/services/alerting/notifiers/teams.go b/pkg/services/alerting/notifiers/teams.go index 9a9e93dbc47..7f62340d0e1 100644 --- a/pkg/services/alerting/notifiers/teams.go +++ b/pkg/services/alerting/notifiers/teams.go @@ -13,7 +13,7 @@ func init() { alerting.RegisterNotifier(&alerting.NotifierPlugin{ Type: "teams", Name: "Microsoft Teams", - Description: "Sends notifications using Incomming Webhook connector to Microsoft Teams", + Description: "Sends notifications using Incoming Webhook connector to Microsoft Teams", Factory: NewTeamsNotifier, OptionsTemplate: `

Teams settings

@@ -76,7 +76,7 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error { } message := this.Mention - if evalContext.Rule.State != m.AlertStateOK { //dont add message when going back to alert state ok. + if evalContext.Rule.State != m.AlertStateOK { //don't add message when going back to alert state ok. message += " " + evalContext.Rule.Message } else { message += " " // summary must not be empty diff --git a/pkg/services/alerting/notifiers/telegram_test.go b/pkg/services/alerting/notifiers/telegram_test.go index 05be787dced..98c8d884ad0 100644 --- a/pkg/services/alerting/notifiers/telegram_test.go +++ b/pkg/services/alerting/notifiers/telegram_test.go @@ -100,7 +100,7 @@ func TestTelegramNotifier(t *testing.T) { So(caption, ShouldContainSubstring, "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise ") }) - Convey("Metrics should be skipped if they dont fit", func() { + Convey("Metrics should be skipped if they don't fit", func() { evalContext := alerting.NewEvalContext(nil, &alerting.Rule{ Name: "This is an alarm", Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I ", diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 8f9deb758a6..5d95e090c9e 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -56,7 +56,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { if err := bus.Dispatch(cmd); err != nil { if err == m.ErrCannotChangeStateOnPausedAlert { - handler.log.Error("Cannot change state on alert thats pause", "error", err) + handler.log.Error("Cannot change state on alert that's paused", "error", err) return err } diff --git a/pkg/services/alerting/scheduler.go b/pkg/services/alerting/scheduler.go index 151f802ec15..b0a3f8303c4 100644 --- a/pkg/services/alerting/scheduler.go +++ b/pkg/services/alerting/scheduler.go @@ -58,7 +58,7 @@ func (s *SchedulerImpl) Tick(tickTime time.Time, execQueue chan *Job) { if job.OffsetWait && now%job.Offset == 0 { job.OffsetWait = false - s.enque(job, execQueue) + s.enqueue(job, execQueue) continue } @@ -66,13 +66,13 @@ func (s *SchedulerImpl) Tick(tickTime time.Time, execQueue chan *Job) { if job.Offset > 0 { job.OffsetWait = true } else { - s.enque(job, execQueue) + s.enqueue(job, execQueue) } } } } -func (s *SchedulerImpl) enque(job *Job, execQueue chan *Job) { +func (s *SchedulerImpl) enqueue(job *Job, execQueue chan *Job) { s.log.Debug("Scheduler: Putting job on to exec queue", "name", job.Rule.Name, "id", job.Rule.Id) execQueue <- job } diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index 811b38cac86..6e13817b902 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -113,7 +113,7 @@ func (g *dashboardGuardianImpl) checkAcl(permission m.PermissionType, acl []*m.D return false, err } - // evalute team rules + // evaluate team rules for _, p := range acl { for _, ug := range teams { if ug.Id == p.TeamId && p.Permission >= permission { diff --git a/pkg/services/provisioning/dashboards/config_reader.go b/pkg/services/provisioning/dashboards/config_reader.go index 9030ba609b9..8ac79df0fac 100644 --- a/pkg/services/provisioning/dashboards/config_reader.go +++ b/pkg/services/provisioning/dashboards/config_reader.go @@ -58,7 +58,7 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) { files, err := ioutil.ReadDir(cr.path) if err != nil { - cr.log.Error("cant read dashboard provisioning files from directory", "path", cr.path) + cr.log.Error("can't read dashboard provisioning files from directory", "path", cr.path) return dashboards, nil } diff --git a/pkg/services/provisioning/datasources/config_reader.go b/pkg/services/provisioning/datasources/config_reader.go index 58ed5472a6b..4b8931f0ed3 100644 --- a/pkg/services/provisioning/datasources/config_reader.go +++ b/pkg/services/provisioning/datasources/config_reader.go @@ -19,7 +19,7 @@ func (cr *configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) files, err := ioutil.ReadDir(path) if err != nil { - cr.log.Error("cant read datasource provisioning files from directory", "path", path) + cr.log.Error("can't read datasource provisioning files from directory", "path", path) return datasources, nil } diff --git a/pkg/services/sqlstore/alert_notification_test.go b/pkg/services/sqlstore/alert_notification_test.go index d37062fb58f..761114978a8 100644 --- a/pkg/services/sqlstore/alert_notification_test.go +++ b/pkg/services/sqlstore/alert_notification_test.go @@ -21,7 +21,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { } err := GetAlertNotifications(cmd) - fmt.Printf("errror %v", err) + fmt.Printf("error %v", err) So(err, ShouldBeNil) So(cmd.Result, ShouldBeNil) }) diff --git a/pkg/tsdb/opentsdb/opentsdb_test.go b/pkg/tsdb/opentsdb/opentsdb_test.go index 094deb9e8ec..fe03599f54d 100644 --- a/pkg/tsdb/opentsdb/opentsdb_test.go +++ b/pkg/tsdb/opentsdb/opentsdb_test.go @@ -35,7 +35,7 @@ func TestOpenTsdbExecutor(t *testing.T) { }) - Convey("Build metric with downsampling diabled", func() { + Convey("Build metric with downsampling disabled", func() { query := &tsdb.Query{ Model: simplejson.New(), diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 23daeebec5a..dee25592623 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -79,7 +79,7 @@ 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 + // don't 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) } From 3424fa94c29a1a59ba59bb100e363aa53321bd59 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 13 Apr 2018 18:44:49 +0200 Subject: [PATCH 49/96] scripts: fix codespell issues --- scripts/build/build_container.sh | 2 +- scripts/build/publish.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/build_container.sh b/scripts/build/build_container.sh index f130e2f94db..16406993e75 100755 --- a/scripts/build/build_container.sh +++ b/scripts/build/build_container.sh @@ -12,6 +12,6 @@ if [[ -e ~/docker/centos.tar ]]; then else docker build --rm=false --tag "grafana/buildcontainer" ./scripts/build/ - # save docker container so we dont have to recreate it next run + # save docker container so we don't have to recreate it next run docker save grafana/buildcontainer > ~/docker/centos.tar; fi diff --git a/scripts/build/publish.go b/scripts/build/publish.go index 7e88c06f67f..500cb0d48ab 100644 --- a/scripts/build/publish.go +++ b/scripts/build/publish.go @@ -125,7 +125,7 @@ func postRequest(url string, obj interface{}, desc string) { } else { log.Printf("Action: %s \t Failed - Status: %v", desc, res.Status) log.Printf("Resp: %s", body) - log.Fatalf("Quiting") + log.Fatalf("Quitting") } } } From 3fb204cc0deb56fad483f5367287591d5896b1a7 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 13 Apr 2018 19:02:28 +0200 Subject: [PATCH 50/96] CHANGELOG.md: fix codespell issues --- CHANGELOG.md | 70 ++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 170d366cb24..dd83280584c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,7 @@ * **Dashboards**: Changing templated value from dropdown is causing unsaved changes [#11063](https://github.com/grafana/grafana/issues/11063) * **Prometheus**: Fixes bundled Prometheus 2.0 dashboard [#11016](https://github.com/grafana/grafana/issues/11016), thx [@roidelapluie](https://github.com/roidelapluie) * **Sidemenu**: Profile menu "invisible" when gravatar is disabled [#11097](https://github.com/grafana/grafana/issues/11097) -* **Dashboard**: Fixes a bug with resizeable handles for panels [#11103](https://github.com/grafana/grafana/issues/11103) +* **Dashboard**: Fixes a bug with resizable handles for panels [#11103](https://github.com/grafana/grafana/issues/11103) * **Alerting**: Telegram inline image mode fails when caption too long [#10975](https://github.com/grafana/grafana/issues/10975) * **Alerting**: Fixes silent failing validation [#11145](https://github.com/grafana/grafana/pull/11145) * **OAuth**: Only use jwt token if it contains an email address [#11127](https://github.com/grafana/grafana/pull/11127) @@ -121,7 +121,7 @@ Grafana v5.0 is going to be the biggest and most foundational release Grafana ha ### New Major Features - **Dashboards** Dashboard folders, [#1611](https://github.com/grafana/grafana/issues/1611) - **Teams** User groups (teams) implemented. Can be used in folder & dashboard permission list. -- **Dashboard grid**: Panels are now layed out in a two dimensional grid (with x, y, w, h). [#9093](https://github.com/grafana/grafana/issues/9093). +- **Dashboard grid**: Panels are now laid out in a two dimensional grid (with x, y, w, h). [#9093](https://github.com/grafana/grafana/issues/9093). - **Templating**: Vertical repeat direction for panel repeats. - **UX**: Major update to page header and navigation - **Dashboard settings**: Combine dashboard settings views into one with side menu, [#9750](https://github.com/grafana/grafana/issues/9750) @@ -155,7 +155,7 @@ Dashboard panels and rows are positioned using a gridPos object `{x: 0, y: 0, w: * **Dashboard history**: New config file option versions_to_keep sets how many versions per dashboard to store, [#9671](https://github.com/grafana/grafana/issues/9671) * **Dashboard as cfg**: Load dashboards from file into Grafana on startup/change [#9654](https://github.com/grafana/grafana/issues/9654) [#5269](https://github.com/grafana/grafana/issues/5269) * **Prometheus**: Grafana can now send alerts to Prometheus Alertmanager while firing [#7481](https://github.com/grafana/grafana/issues/7481), thx [@Thib17](https://github.com/Thib17) and [@mtanda](https://github.com/mtanda) -* **Table**: Support multiple table formated queries in table panel [#9170](https://github.com/grafana/grafana/issues/9170), thx [@davkal](https://github.com/davkal) +* **Table**: Support multiple table formatted queries in table panel [#9170](https://github.com/grafana/grafana/issues/9170), thx [@davkal](https://github.com/davkal) * **Security**: Protect against brute force (frequent) login attempts [#7616](https://github.com/grafana/grafana/issues/7616) ## Minor @@ -177,7 +177,7 @@ Dashboard panels and rows are positioned using a gridPos object `{x: 0, y: 0, w: * **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand) * **Singlestat**: suppress error when result contains no datapoints [#9636](https://github.com/grafana/grafana/issues/9636), thx [@utkarshcmu](https://github.com/utkarshcmu) * **Postgres/MySQL**: Control quoting in SQL-queries when using template variables [#9030](https://github.com/grafana/grafana/issues/9030), thanks [@svenklemm](https://github.com/svenklemm) -* **Pagerduty**: Pagerduty dont auto resolve incidents by default anymore. [#10222](https://github.com/grafana/grafana/issues/10222) +* **Pagerduty**: Pagerduty don't auto resolve incidents by default anymore. [#10222](https://github.com/grafana/grafana/issues/10222) * **Cloudwatch**: Fix for multi-valued templated queries. [#9903](https://github.com/grafana/grafana/issues/9903) ## Tech @@ -255,7 +255,7 @@ The following properties have been deprecated and will be removed in a future re * **Annotations**: Add support for creating annotations from graph panel [#8197](https://github.com/grafana/grafana/pull/8197) * **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) @@ -292,7 +292,7 @@ The following properties have been deprecated and will be removed in a future re * **Graphite**: Fix for Grafana internal metrics to Graphite sending NaN values [#9279](https://github.com/grafana/grafana/issues/9279) * **HTTP API**: Fix for HEAD method requests [#9307](https://github.com/grafana/grafana/issues/9307) * **Templating**: Fix for duplicate template variable queries when refresh is set to time range change [#9185](https://github.com/grafana/grafana/issues/9185) -* **Metrics**: dont write NaN values to graphite [#9279](https://github.com/grafana/grafana/issues/9279) +* **Metrics**: don't write NaN values to graphite [#9279](https://github.com/grafana/grafana/issues/9279) # 4.5.1 (2017-09-15) @@ -329,12 +329,12 @@ The following properties have been deprecated and will be removed in a future re ### Breaking change * **InfluxDB/Elasticsearch**: The panel & data source option named "Group by time interval" is now named "Min time interval" and does now always define a lower limit for the auto group by time. Without having to use `>` prefix (that prefix still works). This should in theory have close to zero actual impact on existing dashboards. It does mean that if you used this setting to define a hard group by time interval of, say "1d", if you zoomed to a time range wide enough the time range could increase above the "1d" range as the setting is now always considered a lower limit. -* **Elasticsearch**: Elasticsearch metric queries without date histogram now return table formated data making table panel much easier to use for this use case. Should not break/change existing dashboards with stock panels but external panel plugins can be affected. +* **Elasticsearch**: Elasticsearch metric queries without date histogram now return table formatted data making table panel much easier to use for this use case. Should not break/change existing dashboards with stock panels but external panel plugins can be affected. ## 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 @@ -346,7 +346,7 @@ The following properties have been deprecated and will be removed in a future re ## Bug Fixes -* **Search**: Fix for issue that casued search view to hide when you clicked starred or tags filters, fixes [#8981](https://github.com/grafana/grafana/issues/8981) +* **Search**: Fix for issue that caused search view to hide when you clicked starred or tags filters, fixes [#8981](https://github.com/grafana/grafana/issues/8981) * **Modals**: ESC key now closes modal again, fixes [#8981](https://github.com/grafana/grafana/issues/8988), thx [@j-white](https://github.com/j-white) # 4.4.2 (2017-08-01) @@ -685,12 +685,12 @@ due to too many connections/file handles on the data source backend. This proble ### Enhancements * **Login**: Adds option to disable username/password logins, closes [#4674](https://github.com/grafana/grafana/issues/4674) * **SingleStat**: Add seriename as option in singlestat panel, closes [#4740](https://github.com/grafana/grafana/issues/4740) -* **Localization**: Week start day now dependant on browser locale setting, closes [#3003](https://github.com/grafana/grafana/issues/3003) +* **Localization**: Week start day now dependent on browser locale setting, closes [#3003](https://github.com/grafana/grafana/issues/3003) * **Templating**: Update panel repeats for variables that change on time refresh, closes [#5021](https://github.com/grafana/grafana/issues/5021) * **Templating**: Add support for numeric and alphabetical sorting of variable values, closes [#2839](https://github.com/grafana/grafana/issues/2839) * **Elasticsearch**: Support to set Precision Threshold for Unique Count metric, closes [#4689](https://github.com/grafana/grafana/issues/4689) * **Navigation**: Add search to org swithcer, closes [#2609](https://github.com/grafana/grafana/issues/2609) -* **Database**: Allow database config using one propertie, closes [#5456](https://github.com/grafana/grafana/pull/5456) +* **Database**: Allow database config using one property, closes [#5456](https://github.com/grafana/grafana/pull/5456) * **Graphite**: Add support for groupByNodes, closes [#5613](https://github.com/grafana/grafana/pull/5613) * **Influxdb**: Add support for elapsed(), closes [#5827](https://github.com/grafana/grafana/pull/5827) * **OpenTSDB**: Add support for explicitTags for OpenTSDB>=2.3, closes [#6360](https://github.com/grafana/grafana/pull/6361) @@ -757,7 +757,7 @@ due to too many connections/file handles on the data source backend. This proble * **Datasource**: Pending data source requests are cancelled before new ones are issues (Graphite & Prometheus), closes [#5321](https://github.com/grafana/grafana/issues/5321) ### Breaking changes -* **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log ouput. +* **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log output. * **Graphite** : The Graph panel no longer have a Graphite PNG option. closes [#5367](https://github.com/grafana/grafana/issues/5367) ### Bug fixes @@ -775,7 +775,7 @@ due to too many connections/file handles on the data source backend. This proble * **Annotations**: Annotations can now use a template variable as data source, closes [#5054](https://github.com/grafana/grafana/issues/5054) * **Time picker**: Fixed issue timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078) * **CloudWatch**: Support for Multiple Account by AssumeRole, closes [#3522](https://github.com/grafana/grafana/issues/3522) -* **Singlestat**: Fixed alignment and minium height issue, fixes [#5113](https://github.com/grafana/grafana/issues/5113), fixes [#4679](https://github.com/grafana/grafana/issues/4679) +* **Singlestat**: Fixed alignment and minimum height issue, fixes [#5113](https://github.com/grafana/grafana/issues/5113), fixes [#4679](https://github.com/grafana/grafana/issues/4679) * **Share modal**: Fixed link when using grafana under dashboard sub url, fixes [#5109](https://github.com/grafana/grafana/issues/5109) * **Prometheus**: Fixed bug in query editor that caused it not to load when reloading page, fixes [#5107](https://github.com/grafana/grafana/issues/5107) * **Elasticsearch**: Fixed bug when template variable query returns numeric values, fixes [#5097](https://github.com/grafana/grafana/issues/5097), fixes [#5088](https://github.com/grafana/grafana/issues/5088) @@ -792,7 +792,7 @@ due to too many connections/file handles on the data source backend. This proble * **Graph**: Fixed broken PNG rendering in graph panel, fixes [#5025](https://github.com/grafana/grafana/issues/5025) * **Graph**: Fixed broken xaxis on graph panel, fixes [#5024](https://github.com/grafana/grafana/issues/5024) -* **Influxdb**: Fixes crash when hiding middle serie, fixes [#5005](https://github.com/grafana/grafana/issues/5005) +* **Influxdb**: Fixes crash when hiding middle series, fixes [#5005](https://github.com/grafana/grafana/issues/5005) # 3.0.1 Stable (2016-05-11) @@ -804,7 +804,7 @@ due to too many connections/file handles on the data source backend. This proble ### Bug fixes * **Dashboard title**: Fixed max dashboard title width (media query) for large screens, fixes [#4859](https://github.com/grafana/grafana/issues/4859) * **Annotations**: Fixed issue with entering annotation edit view, fixes [#4857](https://github.com/grafana/grafana/issues/4857) -* **Remove query**: Fixed issue with removing query for data sources without collapsable query editors, fixes [#4856](https://github.com/grafana/grafana/issues/4856) +* **Remove query**: Fixed issue with removing query for data sources without collapsible query editors, fixes [#4856](https://github.com/grafana/grafana/issues/4856) * **Graphite PNG**: Fixed issue graphite png rendering option, fixes [#4864](https://github.com/grafana/grafana/issues/4864) * **InfluxDB**: Fixed issue missing plus group by iconn, fixes [#4862](https://github.com/grafana/grafana/issues/4862) * **Graph**: Fixes missing line mode for thresholds, fixes [#4902](https://github.com/grafana/grafana/pull/4902) @@ -820,11 +820,11 @@ due to too many connections/file handles on the data source backend. This proble ### Bug fixes * **InfluxDB 0.12**: Fixed issue templating and `show tag values` query only returning tags for first measurement, fixes [#4726](https://github.com/grafana/grafana/issues/4726) -* **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755) +* **Templating**: Fixed issue with regex formatting when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755) * **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736) * **Dashlist**: Fixed issue dashboard list panel and caching tags, fixes [#4768](https://github.com/grafana/grafana/issues/4768) * **Graph**: Fixed issue with unneeded scrollbar in legend for Firefox, fixes [#4760](https://github.com/grafana/grafana/issues/4760) -* **Table panel**: Fixed issue table panel formating string array properties, fixes [#4791](https://github.com/grafana/grafana/issues/4791) +* **Table panel**: Fixed issue table panel formatting string array properties, fixes [#4791](https://github.com/grafana/grafana/issues/4791) * **grafana-cli**: Improve error message when failing to install plugins due to corrupt response, fixes [#4651](https://github.com/grafana/grafana/issues/4651) * **Singlestat**: Fixes prefix an postfix for gauges, fixes [#4812](https://github.com/grafana/grafana/issues/4812) * **Singlestat**: Fixes auto-refresh on change for some options, fixes [#4809](https://github.com/grafana/grafana/issues/4809) @@ -916,7 +916,7 @@ slack channel (link to slack channel in readme). ### Bug fixes * **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794) * **InfluxDB**: Fix for InfluxDB and table panel when using Format As Table and having group by time, fixes [#3928](https://github.com/grafana/grafana/issues/3928) -* **Panel Time shift**: Fix for panel time range and using dashboard times liek `Today` and `This Week`, fixes [#3941](https://github.com/grafana/grafana/issues/3941) +* **Panel Time shift**: Fix for panel time range and using dashboard times like `Today` and `This Week`, fixes [#3941](https://github.com/grafana/grafana/issues/3941) * **Row repeat**: Repeated rows will now appear next to each other and not by the bottom of the dashboard, fixes [#3942](https://github.com/grafana/grafana/issues/3942) * **Png renderer**: Fix for phantomjs path on windows, fixes [#3657](https://github.com/grafana/grafana/issues/3657) @@ -940,7 +940,7 @@ slack channel (link to slack channel in readme). ### Bug Fixes * **metric editors**: Fix for clicking typeahead auto dropdown option, fixes [#3428](https://github.com/grafana/grafana/issues/3428) * **influxdb**: Fixed issue showing Group By label only on first query, fixes [#3453](https://github.com/grafana/grafana/issues/3453) -* **logging**: Add more verbose info logging for http reqeusts, closes [#3405](https://github.com/grafana/grafana/pull/3405) +* **logging**: Add more verbose info logging for http requests, closes [#3405](https://github.com/grafana/grafana/pull/3405) # 2.6.0-Beta1 (2015-12-04) @@ -967,7 +967,7 @@ slack channel (link to slack channel in readme). **New Feature: Mix data sources** - A built in data source is now available named `-- Mixed --`, When picked in the metrics tab, -it allows you to add queries of differnet data source types & instances to the same graph/panel! +it allows you to add queries of different data source types & instances to the same graph/panel! [Issue #436](https://github.com/grafana/grafana/issues/436) **New Feature: Elasticsearch Metrics Query Editor and Viz Support** @@ -1006,7 +1006,7 @@ it allows you to add queries of differnet data source types & instances to the s - [Issue #2564](https://github.com/grafana/grafana/issues/2564). Templating: Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url) - [Issue #2620](https://github.com/grafana/grafana/issues/2620). Graph: multi series tooltip did no highlight correct point when stacking was enabled and series were of different resolution - [Issue #2636](https://github.com/grafana/grafana/issues/2636). InfluxDB: Do no show template vars in dropdown for tag keys and group by keys -- [Issue #2604](https://github.com/grafana/grafana/issues/2604). InfluxDB: More alias options, can now use `$[0-9]` syntax to reference part of a measurement name (seperated by dots) +- [Issue #2604](https://github.com/grafana/grafana/issues/2604). InfluxDB: More alias options, can now use `$[0-9]` syntax to reference part of a measurement name (separated by dots) **Breaking Changes** - Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that @@ -1088,7 +1088,7 @@ Grunt & Watch tasks: - [Issue #1826](https://github.com/grafana/grafana/issues/1826). User role 'Viewer' are now prohibited from entering edit mode (and doing other transient dashboard edits). A new role `Read Only Editor` will replace the old Viewer behavior - [Issue #1928](https://github.com/grafana/grafana/issues/1928). HTTP API: GET /api/dashboards/db/:slug response changed property `model` to `dashboard` to match the POST request nameing - Backend render URL changed from `/render/dashboard/solo` `render/dashboard-solo/` (in order to have consistent dashboard url `/dashboard/:type/:slug`) -- Search HTTP API response has changed (simplified), tags list moved to seperate HTTP resource URI +- Search HTTP API response has changed (simplified), tags list moved to separate HTTP resource URI - Datasource HTTP api breaking change, ADD datasource is now POST /api/datasources/, update is now PUT /api/datasources/:id **Fixes** @@ -1105,7 +1105,7 @@ Grunt & Watch tasks: # 2.0.2 (2015-04-22) **Fixes** -- [Issue #1832](https://github.com/grafana/grafana/issues/1832). Graph Panel + Legend Table mode: Many series casued zero height graph, now legend will never reduce the height of the graph below 50% of row height. +- [Issue #1832](https://github.com/grafana/grafana/issues/1832). Graph Panel + Legend Table mode: Many series caused zero height graph, now legend will never reduce the height of the graph below 50% of row height. - [Issue #1846](https://github.com/grafana/grafana/issues/1846). Snapshots: Fixed issue with snapshoting dashboards with an interval template variable - [Issue #1848](https://github.com/grafana/grafana/issues/1848). Panel timeshift: You can now use panel timeshift without a relative time override @@ -1147,7 +1147,7 @@ Grunt & Watch tasks: **Fixes** - [Issue #1649](https://github.com/grafana/grafana/issues/1649). HTTP API: grafana /render calls nows with api keys -- [Issue #1667](https://github.com/grafana/grafana/issues/1667). Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while) +- [Issue #1667](https://github.com/grafana/grafana/issues/1667). Datasource proxy & session timeout fix (caused 401 Unauthorized error after a while) - [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards - [Issue #1703](https://github.com/grafana/grafana/issues/1703). Unsaved changes: Do not show for users with role `Viewer` - [Issue #1675](https://github.com/grafana/grafana/issues/1675). Data source proxy: Fixed issue with Gzip enabled and data source proxy @@ -1160,14 +1160,14 @@ Grunt & Watch tasks: **Important Note** -Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated backend server. Please read the [Documentation](http://docs.grafana.org) for more detailed about this SIGNIFCANT change to Grafana +Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated backend server. Please read the [Documentation](http://docs.grafana.org) for more detailed about this SIGNIFICANT change to Grafana **New features** - [Issue #1623](https://github.com/grafana/grafana/issues/1623). Share Dashboard: Dashboard snapshot sharing (dash and data snapshot), save to local or save to public snapshot dashboard snapshots.raintank.io site - [Issue #1622](https://github.com/grafana/grafana/issues/1622). Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site -- [Issue #718](https://github.com/grafana/grafana/issues/718). Dashboard: When saving a dashboard and another user has made changes inbetween the user is promted with a warning if he really wants to overwrite the other's changes +- [Issue #718](https://github.com/grafana/grafana/issues/718). Dashboard: When saving a dashboard and another user has made changes in between the user is promted with a warning if he really wants to overwrite the other's changes - [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views -- [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, usefull when you want to ignore last minute because it contains incomplete data +- [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, useful when you want to ignore last minute because it contains incomplete data - [Issue #171](https://github.com/grafana/grafana/issues/171). Panel: Different time periods, panels can override dashboard relative time and/or add a time shift - [Issue #1488](https://github.com/grafana/grafana/issues/1488). Dashboard: Clone dashboard / Save as - [Issue #1458](https://github.com/grafana/grafana/issues/1458). User: persisted user option for dark or light theme (no longer an option on a dashboard) @@ -1198,7 +1198,7 @@ Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated **OpenTSDB breaking change** - [Issue #1438](https://github.com/grafana/grafana/issues/1438). OpenTSDB: Automatic downsample interval passed to OpenTSDB (depends on timespan and graph width) -- NOTICE, Downsampling is now enabled by default, so if you have not picked a downsample aggregator in your metric query do so or your graphs will be missleading +- NOTICE, Downsampling is now enabled by default, so if you have not picked a downsample aggregator in your metric query do so or your graphs will be misleading - This will make Grafana a lot quicker for OpenTSDB users when viewing large time spans without having to change the downsample interval manually. **Tech** @@ -1229,7 +1229,7 @@ Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated - [Issue #1114](https://github.com/grafana/grafana/issues/1114). Graphite: Lexer fix, allow equal sign (=) in metric paths - [Issue #1136](https://github.com/grafana/grafana/issues/1136). Graph: Fix to legend value Max and negative values - [Issue #1150](https://github.com/grafana/grafana/issues/1150). SinglestatPanel: Fixed absolute drilldown link issue -- [Issue #1123](https://github.com/grafana/grafana/issues/1123). Firefox: Workaround for Firefox bug, casued input text fields to not be selectable and not have placeable cursor +- [Issue #1123](https://github.com/grafana/grafana/issues/1123). Firefox: Workaround for Firefox bug, caused input text fields to not be selectable and not have placeable cursor - [Issue #1108](https://github.com/grafana/grafana/issues/1108). Graph: Fix for tooltip series order when series draw order was changed with zindex property # 1.9.0-rc1 (2014-11-17) @@ -1306,7 +1306,7 @@ Read this [blog post](https://grafana.com/blog/2014/09/11/grafana-1.8.0-rc1-rele - [Issue #234](https://github.com/grafana/grafana/issues/234). Templating: Interval variable type for time intervals summarize/group by parameter, included "auto" option, and auto step counts option. - [Issue #262](https://github.com/grafana/grafana/issues/262). Templating: Ability to use template variables for function parameters via custom variable type, can be used as parameter for movingAverage or scaleToSeconds for example - [Issue #312](https://github.com/grafana/grafana/issues/312). Templating: Can now use template variables in panel titles -- [Issue #613](https://github.com/grafana/grafana/issues/613). Templating: Full support for InfluxDB, filter by part of series names, extract series substrings, nested queries, multipe where clauses! +- [Issue #613](https://github.com/grafana/grafana/issues/613). Templating: Full support for InfluxDB, filter by part of series names, extract series substrings, nested queries, multiple where clauses! - Template variables can be initialized from url, with var-my_varname=value, breaking change, before it was just my_varname. - Templating and url state sync has some issues that are not solved for this release, see [Issue #772](https://github.com/grafana/grafana/issues/772) for more details. @@ -1395,7 +1395,7 @@ Read this [blog post](https://grafana.com/blog/2014/09/11/grafana-1.8.0-rc1-rele - [Issue #136](https://github.com/grafana/grafana/issues/136). Graph: New legend display option "Align as table" - [Issue #556](https://github.com/grafana/grafana/issues/556). Graph: New legend display option "Right side", will show legend to the right of the graph - [Issue #604](https://github.com/grafana/grafana/issues/604). Graph: New axis format, 'bps' (SI unit in steps of 1000) useful for network gear metics -- [Issue #626](https://github.com/grafana/grafana/issues/626). Graph: Downscale y axis to more precise unit, value of 0.1 for seconds format will be formated as 100 ms. Thanks @kamaradclimber +- [Issue #626](https://github.com/grafana/grafana/issues/626). Graph: Downscale y axis to more precise unit, value of 0.1 for seconds format will be formatted as 100 ms. Thanks @kamaradclimber - [Issue #618](https://github.com/grafana/grafana/issues/618). OpenTSDB: Series alias option to override metric name returned from opentsdb. Thanks @heldr **Documentation** @@ -1425,13 +1425,13 @@ Read this [blog post](https://grafana.com/blog/2014/09/11/grafana-1.8.0-rc1-rele - [Issue #522](https://github.com/grafana/grafana/issues/522). Series names and column name typeahead cache fix - [Issue #504](https://github.com/grafana/grafana/issues/504). Fixed influxdb issue with raw query that caused wrong value column detection - [Issue #526](https://github.com/grafana/grafana/issues/526). Default property that marks which datasource is default in config.js is now optional -- [Issue #342](https://github.com/grafana/grafana/issues/342). Auto-refresh caused 2 refreshes (and hence mulitple queries) each time (at least in firefox) +- [Issue #342](https://github.com/grafana/grafana/issues/342). Auto-refresh caused 2 refreshes (and hence multiple queries) each time (at least in firefox) # 1.6.0 (2014-06-16) #### New features or improvements - [Issue #427](https://github.com/grafana/grafana/issues/427). New Y-axis formater for metric values that represent seconds, Thanks @jippi -- [Issue #390](https://github.com/grafana/grafana/issues/390). Allow special characters in serie names (influxdb datasource), Thanks @majst01 +- [Issue #390](https://github.com/grafana/grafana/issues/390). Allow special characters in series names (influxdb datasource), Thanks @majst01 - [Issue #428](https://github.com/grafana/grafana/issues/428). Refactoring of filterSrv, Thanks @Tetha - [Issue #445](https://github.com/grafana/grafana/issues/445). New config for playlist feature. Set playlist_timespan to set default playlist interval, Thanks @rmca - [Issue #461](https://github.com/grafana/grafana/issues/461). New graphite function definition added isNonNull, Thanks @tmonk42 @@ -1452,13 +1452,13 @@ Read this [blog post](https://grafana.com/blog/2014/09/11/grafana-1.8.0-rc1-rele - [Issue #475](https://github.com/grafana/grafana/issues/475). Add panel icon and Row edit button is replaced by the Row edit menu - New graphs now have a default empty query - Add Row button now creates a row with default height of 250px (no longer opens dashboard settings modal) -- Clean up of config.sample.js, graphiteUrl removed (still works, but depricated, removed in future) +- Clean up of config.sample.js, graphiteUrl removed (still works, but deprecated, removed in future) Use datasources config instead. panel_names removed from config.js. Use plugins.panels to add custom panels - Graphite panel is now renamed graph (Existing dashboards will still work) #### Fixes - [Issue #126](https://github.com/grafana/grafana/issues/126). Graphite query lexer change, can now handle regex parameters for aliasSub function -- [Issue #447](https://github.com/grafana/grafana/issues/447). Filter option loading when having muliple nested filters now works better. Options are now reloaded correctly and there are no multiple renders/refresh inbetween. +- [Issue #447](https://github.com/grafana/grafana/issues/447). Filter option loading when having muliple nested filters now works better. Options are now reloaded correctly and there are no multiple renders/refresh in between. - [Issue #412](https://github.com/grafana/grafana/issues/412). After a filter option is changed and a nested template param is reloaded, if the current value exists after the options are reloaded the current selected value is kept. - [Issue #460](https://github.com/grafana/grafana/issues/460). Legend Current value did not display when value was zero - [Issue #328](https://github.com/grafana/grafana/issues/328). Fix to series toggling bug that caused annotations to be hidden when toggling/hiding series. From 9a11b574ca994e1ca7eb13f9da54afc56e99b542 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 13 Apr 2018 19:07:13 +0200 Subject: [PATCH 51/96] blocks: fix codespell issues --- docker/blocks/graphite/files/carbon.conf | 2 +- docker/blocks/graphite1/conf/opt/graphite/conf/carbon.amqp.conf | 2 +- docker/blocks/graphite1/conf/opt/graphite/conf/carbon.conf | 2 +- docker/blocks/graphite1/conf/opt/graphite/conf/dashboard.conf | 2 +- docker/blocks/smtp/bootstrap.sh | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/blocks/graphite/files/carbon.conf b/docker/blocks/graphite/files/carbon.conf index 50762b3fff5..fc03aba6398 100644 --- a/docker/blocks/graphite/files/carbon.conf +++ b/docker/blocks/graphite/files/carbon.conf @@ -38,7 +38,7 @@ CACHE_QUERY_PORT = 7002 LOG_UPDATES = False -# Enable AMQP if you want to receve metrics using an amqp broker +# Enable AMQP if you want to receive metrics using an amqp broker # ENABLE_AMQP = False # Verbose means a line will be logged for every metric received diff --git a/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.amqp.conf b/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.amqp.conf index fc36328b25f..f8a53a61115 100644 --- a/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.amqp.conf +++ b/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.amqp.conf @@ -41,7 +41,7 @@ PICKLE_RECEIVER_PORT = 2004 CACHE_QUERY_INTERFACE = 0.0.0.0 CACHE_QUERY_PORT = 7002 -# Enable AMQP if you want to receve metrics using you amqp broker +# Enable AMQP if you want to receive metrics using you amqp broker ENABLE_AMQP = True # Verbose means a line will be logged for every metric received diff --git a/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.conf b/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.conf index 3e10dcec9cf..6741932da37 100644 --- a/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.conf +++ b/docker/blocks/graphite1/conf/opt/graphite/conf/carbon.conf @@ -265,7 +265,7 @@ WHISPER_FALLOCATE_CREATE = True # CARBON_METRIC_PREFIX = carbon # CARBON_METRIC_INTERVAL = 60 -# Enable AMQP if you want to receve metrics using an amqp broker +# Enable AMQP if you want to receive metrics using an amqp broker # ENABLE_AMQP = False # Verbose means a line will be logged for every metric received diff --git a/docker/blocks/graphite1/conf/opt/graphite/conf/dashboard.conf b/docker/blocks/graphite1/conf/opt/graphite/conf/dashboard.conf index 2e1b0bc4db3..f558b273f57 100644 --- a/docker/blocks/graphite1/conf/opt/graphite/conf/dashboard.conf +++ b/docker/blocks/graphite1/conf/opt/graphite/conf/dashboard.conf @@ -30,7 +30,7 @@ give_completer_focus = shift-space # pertain only to specific metric types. # # The dashboard presents only metrics that fall into specified naming schemes -# defined in this file. This creates a simpler, more targetted view of the +# defined in this file. This creates a simpler, more targeted view of the # data. The general form for defining a naming scheme is as follows: # #[Metric Type] diff --git a/docker/blocks/smtp/bootstrap.sh b/docker/blocks/smtp/bootstrap.sh index a78f9d6dc16..27f6a2c3ef8 100755 --- a/docker/blocks/smtp/bootstrap.sh +++ b/docker/blocks/smtp/bootstrap.sh @@ -22,6 +22,6 @@ log() { log $RUN_CMD $RUN_CMD -# Exit immidiately in case of any errors or when we have interactive terminal +# Exit immediately in case of any errors or when we have interactive terminal if [[ $? != 0 ]] || test -t 0; then exit $?; fi log From 298ece0a02cf56df770e6088e9528f47ff5ffddb Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 13 Apr 2018 19:10:13 +0200 Subject: [PATCH 52/96] conf: fix codespell issues --- conf/sample.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/sample.ini b/conf/sample.ini index 1af5bbdb62b..9f0c2a73c25 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -64,7 +64,7 @@ #################################### Database #################################### [database] # You can configure the database connection by specifying type, host, name, user and password -# as seperate properties or as on string using the url propertie. +# as separate properties or as on string using the url properties. # Either "mysql", "postgres" or "sqlite3", it's your choice ;type = sqlite3 From e5e6bc56c84f8a397dd872785e07592e0f1c895d Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Fri, 13 Apr 2018 19:48:37 +0200 Subject: [PATCH 53/96] public: fix codespell issues --- .../core/components/json_explorer/helpers.ts | 4 ++-- .../components/json_explorer/json_explorer.ts | 2 +- .../app/core/directives/dropdown_typeahead.js | 4 ++-- public/app/core/utils/css_loader.ts | 2 +- public/app/core/utils/kbn.ts | 20 +++++++++---------- public/app/features/alerting/alert_def.ts | 2 +- .../alerting/specs/threshold_mapper_specs.ts | 6 +++--- .../features/annotations/events_processing.ts | 2 +- .../app/features/dashboard/dashboard_model.ts | 4 ++-- .../app/features/dashboard/history/history.ts | 2 +- .../specs/dashboard_import_ctrl.jest.ts | 4 ++-- .../dashboard/specs/time_srv_specs.ts | 4 ++-- .../app/features/dashboard/view_state_srv.ts | 4 ++-- public/app/features/org/partials/newOrg.html | 2 +- .../app/features/panel/metrics_panel_ctrl.ts | 2 +- .../templating/datasource_variable.ts | 2 +- .../templating/specs/adhoc_variable.jest.ts | 2 +- .../templating/specs/template_srv.jest.ts | 2 +- .../app/features/templating/template_srv.ts | 2 +- .../testdata/dashboards/graph_last_1h.json | 4 ++-- .../elasticsearch/elastic_response.ts | 2 +- .../partials/annotations.editor.html | 2 +- .../elasticsearch/specs/datasource_specs.ts | 4 ++-- .../datasource/graphite/add_graphite_func.ts | 2 +- .../graphite/specs/query_ctrl_specs.ts | 6 +++--- .../plugins/datasource/influxdb/datasource.ts | 2 +- .../datasource/influxdb/influx_query.ts | 2 +- .../influxdb/partials/annotations.editor.html | 2 +- .../influxdb/specs/query_builder.jest.ts | 2 +- .../mssql/partials/annotations.editor.html | 2 +- .../mysql/partials/annotations.editor.html | 2 +- .../postgres/img/postgresql_logo.svg | 4 ++-- .../postgres/partials/annotations.editor.html | 2 +- .../plugins/panel/graph/jquery.flot.events.js | 4 ++-- public/app/plugins/panel/graph/legend.ts | 2 +- .../panel/graph/series_overrides_ctrl.ts | 2 +- .../panel/table/specs/transformers.jest.ts | 4 ++-- .../app/plugins/panel/table/transformers.ts | 2 +- public/dashboards/scripted_templated.js | 2 +- 39 files changed, 63 insertions(+), 63 deletions(-) diff --git a/public/app/core/components/json_explorer/helpers.ts b/public/app/core/components/json_explorer/helpers.ts index 5b053792d73..c445e1b0667 100644 --- a/public/app/core/components/json_explorer/helpers.ts +++ b/public/app/core/components/json_explorer/helpers.ts @@ -2,7 +2,7 @@ // Licence MIT, Copyright (c) 2015 Mohsen Azimi /* - * Escapes `"` charachters from string + * Escapes `"` characters from string */ function escapeString(str: string): string { return str.replace('"', '"'); @@ -100,7 +100,7 @@ export function cssClass(className: string): string { } /* - * Creates a new DOM element wiht given type and class + * Creates a new DOM element with given type and class * TODO: move me to helpers */ export function createElement(type: string, className?: string, content?: Element | string): Element { diff --git a/public/app/core/components/json_explorer/json_explorer.ts b/public/app/core/components/json_explorer/json_explorer.ts index 9cc1b53bc82..790ed442d5c 100644 --- a/public/app/core/components/json_explorer/json_explorer.ts +++ b/public/app/core/components/json_explorer/json_explorer.ts @@ -146,7 +146,7 @@ export class JsonExplorer { } /* - * did we recieve a key argument? + * did we receive a key argument? * This means that the formatter was called as a sub formatter of a parent formatter */ private get hasKey(): boolean { diff --git a/public/app/core/directives/dropdown_typeahead.js b/public/app/core/directives/dropdown_typeahead.js index 25772b4638a..9b677c95697 100644 --- a/public/app/core/directives/dropdown_typeahead.js +++ b/public/app/core/directives/dropdown_typeahead.js @@ -108,7 +108,7 @@ function (_, $, coreModule) { $input.val(''); $button.show(); $button.focus(); - // clicking the function dropdown menu wont + // clicking the function dropdown menu won't // work if you remove class at once setTimeout(function() { elem.removeClass('open'); @@ -222,7 +222,7 @@ function (_, $, coreModule) { $input.val(''); $button.show(); $button.focus(); - // clicking the function dropdown menu wont + // clicking the function dropdown menu won't // work if you remove class at once setTimeout(function() { elem.removeClass('open'); diff --git a/public/app/core/utils/css_loader.ts b/public/app/core/utils/css_loader.ts index 42f59a9c27c..ba8623df842 100644 --- a/public/app/core/utils/css_loader.ts +++ b/public/app/core/utils/css_loader.ts @@ -67,7 +67,7 @@ export function fetch(load): any { return ''; } - // dont reload styles loaded in the head + // don't reload styles loaded in the head for (var i = 0; i < linkHrefs.length; i++) { if (load.address === linkHrefs[i]) { return ''; diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index dcb04a3e38e..8c3e3e72dda 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -620,13 +620,13 @@ kbn.valueFormats.ms = function(size, decimals, scaledDecimals) { // Less than 1 min return kbn.toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' s'); } else if (Math.abs(size) < 3600000) { - // Less than 1 hour, devide in minutes + // Less than 1 hour, divide in minutes return kbn.toFixedScaled(size / 60000, decimals, scaledDecimals, 5, ' min'); } else if (Math.abs(size) < 86400000) { - // Less than one day, devide in hours + // Less than one day, divide in hours return kbn.toFixedScaled(size / 3600000, decimals, scaledDecimals, 7, ' hour'); } else if (Math.abs(size) < 31536000000) { - // Less than one year, devide in days + // Less than one year, divide in days return kbn.toFixedScaled(size / 86400000, decimals, scaledDecimals, 8, ' day'); } @@ -638,15 +638,15 @@ kbn.valueFormats.s = function(size, decimals, scaledDecimals) { return ''; } - // Less than 1 µs, devide in ns + // Less than 1 µs, divide in ns if (Math.abs(size) < 0.000001) { return kbn.toFixedScaled(size * 1e9, decimals, scaledDecimals - decimals, -9, ' ns'); } - // Less than 1 ms, devide in µs + // Less than 1 ms, divide in µs if (Math.abs(size) < 0.001) { return kbn.toFixedScaled(size * 1e6, decimals, scaledDecimals - decimals, -6, ' µs'); } - // Less than 1 second, devide in ms + // Less than 1 second, divide in ms if (Math.abs(size) < 1) { return kbn.toFixedScaled(size * 1e3, decimals, scaledDecimals - decimals, -3, ' ms'); } @@ -654,16 +654,16 @@ kbn.valueFormats.s = function(size, decimals, scaledDecimals) { if (Math.abs(size) < 60) { return kbn.toFixed(size, decimals) + ' s'; } else if (Math.abs(size) < 3600) { - // Less than 1 hour, devide in minutes + // Less than 1 hour, divide in minutes return kbn.toFixedScaled(size / 60, decimals, scaledDecimals, 1, ' min'); } else if (Math.abs(size) < 86400) { - // Less than one day, devide in hours + // Less than one day, divide in hours return kbn.toFixedScaled(size / 3600, decimals, scaledDecimals, 4, ' hour'); } else if (Math.abs(size) < 604800) { - // Less than one week, devide in days + // Less than one week, divide in days return kbn.toFixedScaled(size / 86400, decimals, scaledDecimals, 5, ' day'); } else if (Math.abs(size) < 31536000) { - // Less than one year, devide in week + // Less than one year, divide in week return kbn.toFixedScaled(size / 604800, decimals, scaledDecimals, 6, ' week'); } diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/alert_def.ts index d86461780ba..797a67abfd8 100644 --- a/public/app/features/alerting/alert_def.ts +++ b/public/app/features/alerting/alert_def.ts @@ -124,7 +124,7 @@ function joinEvalMatches(matches, separator: string) { } function getAlertAnnotationInfo(ah) { - // backward compatability, can be removed in grafana 5.x + // backward compatibility, can be removed in grafana 5.x // old way stored evalMatches in data property directly, // new way stores it in evalMatches property on new data object diff --git a/public/app/features/alerting/specs/threshold_mapper_specs.ts b/public/app/features/alerting/specs/threshold_mapper_specs.ts index 3b284776b8d..1d68fce7050 100644 --- a/public/app/features/alerting/specs/threshold_mapper_specs.ts +++ b/public/app/features/alerting/specs/threshold_mapper_specs.ts @@ -4,7 +4,7 @@ import { ThresholdMapper } from '../threshold_mapper'; describe('ThresholdMapper', () => { describe('with greater than evaluator', () => { - it('can mapp query conditions to thresholds', () => { + it('can map query conditions to thresholds', () => { var panel: any = { type: 'graph', alert: { @@ -25,7 +25,7 @@ describe('ThresholdMapper', () => { }); describe('with outside range evaluator', () => { - it('can mapp query conditions to thresholds', () => { + it('can map query conditions to thresholds', () => { var panel: any = { type: 'graph', alert: { @@ -49,7 +49,7 @@ describe('ThresholdMapper', () => { }); describe('with inside range evaluator', () => { - it('can mapp query conditions to thresholds', () => { + it('can map query conditions to thresholds', () => { var panel: any = { type: 'graph', alert: { diff --git a/public/app/features/annotations/events_processing.ts b/public/app/features/annotations/events_processing.ts index 040bf6425c1..667285d7d43 100644 --- a/public/app/features/annotations/events_processing.ts +++ b/public/app/features/annotations/events_processing.ts @@ -56,7 +56,7 @@ function isStartOfRegion(event): boolean { export function dedupAnnotations(annotations) { let dedup = []; - // Split events by annotationId property existance + // Split events by annotationId property existence let events = _.partition(annotations, 'id'); let eventsById = _.groupBy(events[0], 'id'); diff --git a/public/app/features/dashboard/dashboard_model.ts b/public/app/features/dashboard/dashboard_model.ts index 3fa8ed9973a..9130cb7e806 100644 --- a/public/app/features/dashboard/dashboard_model.ts +++ b/public/app/features/dashboard/dashboard_model.ts @@ -129,7 +129,7 @@ export class DashboardModel { this.meta = meta; } - // cleans meta data and other non peristent state + // cleans meta data and other non persistent state getSaveModelClone() { // make clone var copy: any = {}; @@ -606,7 +606,7 @@ export class DashboardModel { if (panel.gridPos.x + panel.gridPos.w * 2 <= GRID_COLUMN_COUNT) { newPanel.gridPos.x += panel.gridPos.w; } else { - // add bellow + // add below newPanel.gridPos.y += panel.gridPos.h; } diff --git a/public/app/features/dashboard/history/history.ts b/public/app/features/dashboard/history/history.ts index d9f0c087438..be6ad5af1ba 100644 --- a/public/app/features/dashboard/history/history.ts +++ b/public/app/features/dashboard/history/history.ts @@ -133,7 +133,7 @@ export class HistoryListCtrl { return this.historySrv .getHistoryList(this.dashboard, options) .then(revisions => { - // set formated dates & default values + // set formatted dates & default values for (let rev of revisions) { rev.createdDateString = this.formatDate(rev.created); rev.ageString = this.formatBasicDate(rev.created); diff --git a/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts b/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts index 1cb59ef5bac..737eb360461 100644 --- a/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts +++ b/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts @@ -56,7 +56,7 @@ describe('DashboardImportCtrl', function() { }); }); - describe('when specifing grafana.com url', function() { + describe('when specifying grafana.com url', function() { beforeEach(function() { ctx.ctrl.gnetUrl = 'http://grafana.com/dashboards/123'; // setup api mock @@ -73,7 +73,7 @@ describe('DashboardImportCtrl', function() { }); }); - describe('when specifing dashbord id', function() { + describe('when specifying dashboard id', function() { beforeEach(function() { ctx.ctrl.gnetUrl = '2342'; // setup api mock diff --git a/public/app/features/dashboard/specs/time_srv_specs.ts b/public/app/features/dashboard/specs/time_srv_specs.ts index ca75f0ffcf9..6e180679ff2 100644 --- a/public/app/features/dashboard/specs/time_srv_specs.ts +++ b/public/app/features/dashboard/specs/time_srv_specs.ts @@ -44,7 +44,7 @@ describe('timeSrv', function() { expect(time.raw.to).to.be('now'); }); - it('should handle formated dates', function() { + it('should handle formatted dates', function() { ctx.$location.search({ from: '20140410T052010', to: '20140520T031022' }); ctx.service.init(_dashboard); var time = ctx.service.timeRange(true); @@ -52,7 +52,7 @@ describe('timeSrv', function() { expect(time.to.valueOf()).to.equal(new Date('2014-05-20T03:10:22Z').getTime()); }); - it('should handle formated dates without time', function() { + it('should handle formatted dates without time', function() { ctx.$location.search({ from: '20140410', to: '20140520' }); ctx.service.init(_dashboard); var time = ctx.service.timeRange(true); diff --git a/public/app/features/dashboard/view_state_srv.ts b/public/app/features/dashboard/view_state_srv.ts index fa471b89989..1ed2d61df71 100644 --- a/public/app/features/dashboard/view_state_srv.ts +++ b/public/app/features/dashboard/view_state_srv.ts @@ -38,7 +38,7 @@ export class DashboardViewState { }); // this marks changes to location during this digest cycle as not to add history item - // dont want url changes like adding orgId to add browser history + // don't want url changes like adding orgId to add browser history $location.replace(); this.update(this.getQueryStringState()); } @@ -196,7 +196,7 @@ export class DashboardViewState { this.oldTimeRange = ctrl.range; this.fullscreenPanel = panelScope; - // Firefox doesn't return scrollTop postion properly if 'dash-scroll' is emitted after setViewMode() + // Firefox doesn't return scrollTop position properly if 'dash-scroll' is emitted after setViewMode() this.$scope.appEvent('dash-scroll', { animate: false, pos: 0 }); this.dashboard.setViewMode(ctrl.panel, true, ctrl.editMode); this.$scope.appEvent('panel-fullscreen-enter', { panelId: ctrl.panel.id }); diff --git a/public/app/features/org/partials/newOrg.html b/public/app/features/org/partials/newOrg.html index 424c55d6eb7..9777107c31a 100644 --- a/public/app/features/org/partials/newOrg.html +++ b/public/app/features/org/partials/newOrg.html @@ -5,7 +5,7 @@ New Organization -

Each organization contains their own dashboards, data sources and configuration, and cannot be shared between orgs. While users may belong to more than one, mutiple organization are most frequently used in multi-tenant deployments.

+

Each organization contains their own dashboards, data sources and configuration, and cannot be shared between orgs. While users may belong to more than one, multiple organization are most frequently used in multi-tenant deployments.

diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 177f0c7bf00..9e9598e1732 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -73,7 +73,7 @@ class MetricsPanelCtrl extends PanelCtrl { if (this.panel.snapshotData) { this.updateTimeRange(); var data = this.panel.snapshotData; - // backward compatability + // backward compatibility if (!_.isArray(data)) { data = data.data; } diff --git a/public/app/features/templating/datasource_variable.ts b/public/app/features/templating/datasource_variable.ts index 0c5b226c372..4c326a94e3b 100644 --- a/public/app/features/templating/datasource_variable.ts +++ b/public/app/features/templating/datasource_variable.ts @@ -29,7 +29,7 @@ export class DatasourceVariable implements Variable { getSaveModel() { assignModelProperties(this.model, this, this.defaults); - // dont persist options + // don't persist options this.model.options = []; return this.model; } diff --git a/public/app/features/templating/specs/adhoc_variable.jest.ts b/public/app/features/templating/specs/adhoc_variable.jest.ts index 863c8401c50..a7b20e8d029 100644 --- a/public/app/features/templating/specs/adhoc_variable.jest.ts +++ b/public/app/features/templating/specs/adhoc_variable.jest.ts @@ -2,7 +2,7 @@ import { AdhocVariable } from '../adhoc_variable'; describe('AdhocVariable', function() { describe('when serializing to url', function() { - it('should set return key value and op seperated by pipe', function() { + it('should set return key value and op separated by pipe', function() { var variable = new AdhocVariable({ filters: [ { key: 'key1', operator: '=', value: 'value1' }, diff --git a/public/app/features/templating/specs/template_srv.jest.ts b/public/app/features/templating/specs/template_srv.jest.ts index f28fbf9ac64..5290a883c48 100644 --- a/public/app/features/templating/specs/template_srv.jest.ts +++ b/public/app/features/templating/specs/template_srv.jest.ts @@ -282,7 +282,7 @@ describe('templateSrv', function() { }); }); - describe('can hightlight variables in string', function() { + describe('can highlight variables in string', function() { beforeEach(function() { initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]); }); diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 5b31072d140..f6274a80165 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -204,7 +204,7 @@ export class TemplateSrv { value = variable.current.value; if (this.isAllValue(value)) { value = this.getAllValue(variable); - // skip formating of custom all values + // skip formatting of custom all values if (variable.allValue) { return value; } diff --git a/public/app/plugins/app/testdata/dashboards/graph_last_1h.json b/public/app/plugins/app/testdata/dashboards/graph_last_1h.json index c56d9e9216f..5a4459cd62c 100644 --- a/public/app/plugins/app/testdata/dashboards/graph_last_1h.json +++ b/public/app/plugins/app/testdata/dashboards/graph_last_1h.json @@ -392,7 +392,7 @@ "thresholds": [], "timeFrom": null, "timeShift": null, - "title": "2 yaxis and axis lables", + "title": "2 yaxis and axis labels", "tooltip": { "msResolution": false, "shared": true, @@ -894,7 +894,7 @@ "thresholds": [], "timeFrom": null, "timeShift": null, - "title": "Legend Table Single Series Should Take Minium Height", + "title": "Legend Table Single Series Should Take Minimum Height", "tooltip": { "shared": true, "sort": 0, diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.ts b/public/app/plugins/datasource/elasticsearch/elastic_response.ts index ede5cb0ba3a..a378ab8b55f 100644 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.ts +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.ts @@ -175,7 +175,7 @@ export class ElasticResponse { } // This is quite complex - // neeed to recurise down the nested buckets to build series + // need to recurise down the nested buckets to build series processBuckets(aggs, target, seriesList, table, props, depth) { var bucket, aggDef, esAgg, aggId; var maxDepth = target.bucketAggs.length - 1; diff --git a/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html b/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html index d4e1e7d1b1c..a2e903f231c 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html +++ b/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html @@ -27,7 +27,7 @@
- Title (depricated) + Title (deprecated)
diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index 629621b8e60..558bccf3d0f 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -53,7 +53,7 @@ describe('ElasticDatasource', function() { }); }); - describe('When issueing metric query with interval pattern', function() { + describe('When issuing metric query with interval pattern', function() { var requestOptions, parts, header; beforeEach(function() { @@ -98,7 +98,7 @@ describe('ElasticDatasource', function() { }); }); - describe('When issueing document query', function() { + describe('When issuing document query', function() { var requestOptions, parts, header; beforeEach(function() { diff --git a/public/app/plugins/datasource/graphite/add_graphite_func.ts b/public/app/plugins/datasource/graphite/add_graphite_func.ts index 6e64b5d12d0..444d30b5453 100644 --- a/public/app/plugins/datasource/graphite/add_graphite_func.ts +++ b/public/app/plugins/datasource/graphite/add_graphite_func.ts @@ -68,7 +68,7 @@ export function graphiteAddFunc($compile) { }); $input.blur(function() { - // clicking the function dropdown menu wont + // clicking the function dropdown menu won't // work if you remove class at once setTimeout(function() { $input.val(''); diff --git a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts index f8b70b05940..b4f7718930f 100644 --- a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts @@ -97,7 +97,7 @@ describe('GraphiteQueryCtrl', function() { }); }); - describe('when initalizing target without metric expression and only function', function() { + describe('when initializing target without metric expression and only function', function() { beforeEach(function() { ctx.ctrl.target.target = 'asPercent(#A, #B)'; ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([])); @@ -130,7 +130,7 @@ describe('GraphiteQueryCtrl', function() { }); }); - describe('when initalizing target without metric expression and function with series-ref', function() { + describe('when initializing target without metric expression and function with series-ref', function() { beforeEach(function() { ctx.ctrl.target.target = 'asPercent(metric.node.count, #A)'; ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([])); @@ -146,7 +146,7 @@ describe('GraphiteQueryCtrl', function() { }); }); - describe('when getting altSegments and metricFindQuery retuns empty array', function() { + describe('when getting altSegments and metricFindQuery returns empty array', function() { beforeEach(function() { ctx.ctrl.target.target = 'test.count'; ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([])); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 1eff9bfa527..4439ca7beaf 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -54,7 +54,7 @@ export default class InfluxDatasource { queryTargets.push(target); - // backward compatability + // backward compatibility scopedVars.interval = scopedVars.__interval; queryModel = new InfluxQuery(target, this.templateSrv, scopedVars); diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 656647b4413..2ef74170068 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -230,7 +230,7 @@ export default class InfluxQuery { for (i = 0; i < this.groupByParts.length; i++) { var part = this.groupByParts[i]; if (i > 0) { - // for some reason fill has no seperator + // for some reason fill has no separator groupBySection += part.def.type === 'fill' ? ' ' : ', '; } groupBySection += part.render(''); diff --git a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html index 2f54ff28275..48991426c1e 100644 --- a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/annotations.editor.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 54/96] 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 55/96] 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 56/96] 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 57/96] 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 58/96] 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 59/96] 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 60/96] 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 61/96] 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 62/96] 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 63/96] 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 64/96] 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 9337972a0fdc11c2ed1d1e0cc89178f0c8182c76 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 16 Apr 2018 11:06:23 +0200 Subject: [PATCH 65/96] 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 66/96] 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 67/96] 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 68/96] 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 69/96] 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 8d963e27332a6a80fba7f42a3c0726a40f1608f3 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 16 Apr 2018 15:45:55 +0200 Subject: [PATCH 70/96] 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 71/96] 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 72/96] 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 @@