From 0a1c2a7024d9e1940e8df434a37d22998a8ab0a9 Mon Sep 17 00:00:00 2001 From: Victor Azevedo Date: Thu, 25 May 2017 11:43:29 -0400 Subject: [PATCH 01/34] use fielddata_fields in elasticsearch 2.x queries Resolves issue #8467 --- .../app/plugins/datasource/elasticsearch/query_builder.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.js b/public/app/plugins/datasource/elasticsearch/query_builder.js index ecbbb9c7e48..62a7b379c8e 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.js +++ b/public/app/plugins/datasource/elasticsearch/query_builder.js @@ -119,8 +119,12 @@ function (queryDef) { query.fields = ["*", "_source"]; } - query.script_fields = {}, - query.docvalue_fields = [this.timeField]; + query.script_fields = {}; + if (this.esVersion < 5) { + query.fielddata_fields = [this.timeField]; + } else { + query.docvalue_fields = [this.timeField]; + } return query; }; From 09267bbfe8f1ab724a3880dd53876a26387e9a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 25 May 2017 20:11:53 +0200 Subject: [PATCH 02/34] docs: elasticsearch, added info about size property in templating query --- docs/sources/features/datasources/elasticsearch.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/features/datasources/elasticsearch.md b/docs/sources/features/datasources/elasticsearch.md index 6462d026366..25cdb98c8c5 100644 --- a/docs/sources/features/datasources/elasticsearch.md +++ b/docs/sources/features/datasources/elasticsearch.md @@ -92,9 +92,10 @@ The Elasticsearch data source supports two types of queries you can use in the * Query | Description ------------ | ------------- *{"find": "fields", "type": "keyword"} | Returns a list of field names with the index type `keyword`. -*{"find": "terms", "field": "@hostname"}* | Returns a list of values for a field using term aggregation. Query will user current dashboard time range as time range for query. +*{"find": "terms", "field": "@hostname", "size": 1000}* | Returns a list of values for a field using term aggregation. Query will user current dashboard time range as time range for query. *{"find": "terms", "field": "@hostname", "query": ''}* | Returns a list of values for a field using term aggregation & and a specified lucene query filter. Query will use current dashboard time range as time range for query. +There is a default size limit of 500 on terms queries. Set the size property in your query to set a custom limit. You can use other variables inside the query. Example query definition for a variable named `$host`. ``` From 60d5d5fb1530d0dcbc5dad63bc6c2009973c40f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 25 May 2017 20:37:01 +0200 Subject: [PATCH 03/34] docs: updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index acec4e8cdf9..0480e9b971c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * **InfluxDB**: Fixed issue with query editor not showing ALIAS BY input field when in text editor mode [#8459](https://github.com/grafana/grafana/issues/8459) * **Graph Log Scale**: Fixed issue with log scale going below x-axis [#8244](https://github.com/grafana/grafana/issues/8244) * **Playlist**: Fixed dashboard play order issue [#7688](https://github.com/grafana/grafana/issues/7688) +* **Elasticsearch**: Fixed table query issue with ES 2.x [#8467](https://github.com/grafana/grafana/issues/8467), thx [@goldeelox](https://github.com/goldeelox) # 4.3.1 (2017-05-23) From 8422697199ffa21cda664644d7af459ad4efc3cc Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Fri, 26 May 2017 08:35:32 -0400 Subject: [PATCH 04/34] centralize oauth http calls, validate response status (#8470) --- pkg/api/login_oauth.go | 7 +++++ pkg/social/common.go | 26 ++++++++++++++++ pkg/social/generic_oauth.go | 53 ++++++++++++--------------------- pkg/social/github_oauth.go | 48 +++++++++++++---------------- pkg/social/google_oauth.go | 14 +++++---- pkg/social/grafana_com_oauth.go | 13 ++++---- public/views/500.html | 40 +++++++++---------------- 7 files changed, 100 insertions(+), 101 deletions(-) diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 5fbd9fea709..939e9b163e6 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -28,6 +28,7 @@ var ( ErrEmailNotAllowed = errors.New("Required email domain not fulfilled") ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter") ErrUsersQuotaReached = errors.New("Users quota reached") + ErrNoEmail = errors.New("Login provider didn't return an email address") ) func GenStateString() string { @@ -134,6 +135,12 @@ func OAuthLogin(ctx *middleware.Context) { ctx.Logger.Debug("OAuthLogin got user info", "userInfo", userInfo) + // validate that we got at least an email address + if userInfo.Email == "" { + redirectWithError(ctx, ErrNoEmail) + return + } + // validate that the email is allowed to login to grafana if !connect.IsEmailAllowed(userInfo.Email) { redirectWithError(ctx, ErrEmailNotAllowed) diff --git a/pkg/social/common.go b/pkg/social/common.go index 7bce5d2ae8f..a5b8973306f 100644 --- a/pkg/social/common.go +++ b/pkg/social/common.go @@ -2,7 +2,11 @@ package social import ( "fmt" + "io/ioutil" + "net/http" "strings" + + "github.com/grafana/grafana/pkg/log" ) func isEmailAllowed(email string, allowedDomains []string) bool { @@ -18,3 +22,25 @@ func isEmailAllowed(email string, allowedDomains []string) bool { return valid } + +func HttpGet(client *http.Client, url string) ([]byte, error) { + r, err := client.Get(url) + if err != nil { + return nil, err + } + + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + + if r.StatusCode >= 300 { + return nil, fmt.Errorf(string(body)) + } + + log.Trace("HTTP GET %s: %s %s", url, r.Status, string(body)) + + return body, nil +} diff --git a/pkg/social/generic_oauth.go b/pkg/social/generic_oauth.go index 04b0536852a..76b2b734cd6 100644 --- a/pkg/social/generic_oauth.go +++ b/pkg/social/generic_oauth.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "net/http" "github.com/grafana/grafana/pkg/models" @@ -84,22 +83,14 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) { IsConfirmed bool `json:"is_confirmed"` } - emailsUrl := fmt.Sprintf(s.apiUrl + "/emails") - r, err := client.Get(emailsUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/emails")) if err != nil { - return "", err + return "", fmt.Errorf("Error getting email address: %s", err) } - defer r.Body.Close() - var records []Record - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return "", err - } - - err = json.Unmarshal(body, records) + err = json.Unmarshal(body, &records) if err != nil { var data struct { Values []Record `json:"values"` @@ -107,7 +98,7 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) { err = json.Unmarshal(body, &data) if err != nil { - return "", err + return "", fmt.Errorf("Error getting email address: %s", err) } records = data.Values @@ -129,18 +120,16 @@ func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error) Id int `json:"id"` } - membershipUrl := fmt.Sprintf(s.apiUrl + "/teams") - r, err := client.Get(membershipUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/teams")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting team memberships: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting team memberships: %s", err) } var ids = make([]int, len(records)) @@ -156,18 +145,16 @@ func (s *GenericOAuth) FetchOrganizations(client *http.Client) ([]string, error) Login string `json:"login"` } - url := fmt.Sprintf(s.apiUrl + "/orgs") - r, err := client.Get(url) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting organizations: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting organizations: %s", err) } var logins = make([]string, len(records)) @@ -188,16 +175,14 @@ func (s *GenericOAuth) UserInfo(client *http.Client) (*BasicUserInfo, error) { Attributes map[string][]string `json:"attributes"` } - var err error - r, err := client.Get(s.apiUrl) + body, err := HttpGet(client, s.apiUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } userInfo := &BasicUserInfo{ diff --git a/pkg/social/github_oauth.go b/pkg/social/github_oauth.go index 271a472be84..b7b8d7c8156 100644 --- a/pkg/social/github_oauth.go +++ b/pkg/social/github_oauth.go @@ -85,18 +85,16 @@ func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) { Verified bool `json:"verified"` } - emailsUrl := fmt.Sprintf(s.apiUrl + "/emails") - r, err := client.Get(emailsUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/emails")) if err != nil { - return "", err + return "", fmt.Errorf("Error getting email address: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return "", err + err = json.Unmarshal(body, &records) + if err != nil { + return "", fmt.Errorf("Error getting email address: %s", err) } var email = "" @@ -114,18 +112,16 @@ func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) Id int `json:"id"` } - membershipUrl := fmt.Sprintf(s.apiUrl + "/teams") - r, err := client.Get(membershipUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/teams")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting team memberships: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting team memberships: %s", err) } var ids = make([]int, len(records)) @@ -141,18 +137,16 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) Login string `json:"login"` } - url := fmt.Sprintf(s.apiUrl + "/orgs") - r, err := client.Get(url) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting organizations: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting organizations: %s", err) } var logins = make([]string, len(records)) @@ -170,16 +164,14 @@ func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) { Email string `json:"email"` } - var err error - r, err := client.Get(s.apiUrl) + body, err := HttpGet(client, s.apiUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } userInfo := &BasicUserInfo{ diff --git a/pkg/social/google_oauth.go b/pkg/social/google_oauth.go index d140f385b66..94a05b25140 100644 --- a/pkg/social/google_oauth.go +++ b/pkg/social/google_oauth.go @@ -2,6 +2,7 @@ package social import ( "encoding/json" + "fmt" "net/http" "github.com/grafana/grafana/pkg/models" @@ -34,16 +35,17 @@ func (s *SocialGoogle) UserInfo(client *http.Client) (*BasicUserInfo, error) { Name string `json:"name"` Email string `json:"email"` } - var err error - r, err := client.Get(s.apiUrl) + body, err := HttpGet(client, s.apiUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } + return &BasicUserInfo{ Name: data.Name, Email: data.Email, diff --git a/pkg/social/grafana_com_oauth.go b/pkg/social/grafana_com_oauth.go index 498af7f1935..dd693f18d5d 100644 --- a/pkg/social/grafana_com_oauth.go +++ b/pkg/social/grafana_com_oauth.go @@ -2,6 +2,7 @@ package social import ( "encoding/json" + "fmt" "net/http" "github.com/grafana/grafana/pkg/models" @@ -57,16 +58,14 @@ func (s *SocialGrafanaCom) UserInfo(client *http.Client) (*BasicUserInfo, error) Orgs []OrgRecord `json:"orgs"` } - var err error - r, err := client.Get(s.url + "/api/oauth2/user") + body, err := HttpGet(client, s.url+"/api/oauth2/user") if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } userInfo := &BasicUserInfo{ diff --git a/public/views/500.html b/public/views/500.html index cb2e99595f1..dca1245535f 100644 --- a/public/views/500.html +++ b/public/views/500.html @@ -4,33 +4,21 @@ - Grafana - Error - - - - - + + - - - + - - - -
- - -

[[.Title]]

- -
[[.ErrorMsg]]
- -
- - + +
+ +

[[.Title]]

+ [[if .ErrorMsg]] +
[[.ErrorMsg]]
+ [[end]] +
+ From 3292a48381a28d2aca596bc60047a7da810e0044 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 26 May 2017 15:04:34 +0200 Subject: [PATCH 05/34] graph: dashes with linewidth fix. Fixes #8469 Adds series override options for dash spaces and dash length. --- public/app/core/time_series2.ts | 4 +++- .../plugins/panel/graph/series_overrides_ctrl.js | 2 ++ public/test/core/time_series_specs.js | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index 782543787d7..b8b7ef6ed26 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -87,9 +87,11 @@ export default class TimeSeries { if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); } if (override.stack !== void 0) { this.stack = override.stack; } if (override.linewidth !== void 0) { - this.lines.lineWidth = override.linewidth; + this.lines.lineWidth = this.dashes.show ? 0: override.linewidth; this.dashes.lineWidth = override.linewidth; } + if (override.dashLength !== void 0) { this.dashes.dashLength[0] = override.dashLength; } + if (override.spaceLength !== void 0) { this.dashes.dashLength[1] = override.spaceLength; } if (override.nullPointMode !== void 0) { this.nullPointMode = override.nullPointMode; } if (override.pointradius !== void 0) { this.points.radius = override.pointradius; } if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; } diff --git a/public/app/plugins/panel/graph/series_overrides_ctrl.js b/public/app/plugins/panel/graph/series_overrides_ctrl.js index bca1a9e5587..945f8340b33 100644 --- a/public/app/plugins/panel/graph/series_overrides_ctrl.js +++ b/public/app/plugins/panel/graph/series_overrides_ctrl.js @@ -101,6 +101,8 @@ define([ $scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames()); $scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]); $scope.addOverrideOption('Dashes', 'dashes', [true, false]); + $scope.addOverrideOption('Dash Length', 'dashLength', [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]); + $scope.addOverrideOption('Dash Space', 'spaceLength', [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]); $scope.addOverrideOption('Points', 'points', [true, false]); $scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]); $scope.addOverrideOption('Stack', 'stack', [true, false, 'A', 'B', 'C', 'D']); diff --git a/public/test/core/time_series_specs.js b/public/test/core/time_series_specs.js index 04efa8a2249..08434e58b81 100644 --- a/public/test/core/time_series_specs.js +++ b/public/test/core/time_series_specs.js @@ -211,6 +211,19 @@ define([ }); }); + describe('series option overrides, dashes and lineWidth', function() { + beforeEach(function() { + series.alias = 'test'; + series.applySeriesOverrides([{ alias: 'test', linewidth: 5, dashes: true }]); + }); + + it('should enable dashes, set dashes lineWidth to 5 and lines lineWidth to 0', function() { + expect(series.dashes.show).to.be(true); + expect(series.dashes.lineWidth).to.be(5); + expect(series.lines.lineWidth).to.be(0); + }); + }); + describe('series option overrides, fill below to', function() { beforeEach(function() { series.alias = 'test'; From f484b4c34700db540cea3e2cc5caf8a2c7f2865f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Reginster?= Date: Wed, 3 May 2017 17:28:01 +0200 Subject: [PATCH 06/34] Make csv export date time format configurable - Move export csv options to modal dialog --- public/app/core/utils/file_export.ts | 11 ++++--- public/app/features/dashboard/all.js | 1 + .../features/dashboard/exportCsvModalCtrl.ts | 32 +++++++++++++++++++ .../dashboard/partials/exportCsvModal.html | 32 +++++++++++++++++++ public/app/plugins/panel/graph/module.ts | 16 +++++----- 5 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 public/app/features/dashboard/exportCsvModalCtrl.ts create mode 100644 public/app/features/dashboard/partials/exportCsvModal.html diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index 24b501eee28..dd616ca77e7 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -1,20 +1,23 @@ /// import _ from 'lodash'; +import moment from 'moment'; declare var window: any; -export function exportSeriesListToCsv(seriesList) { +const DEFAULT_DATETIME_FORMAT: String = 'YYYY-MM-DDTHH:mm:ssZ'; + +export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT) { var text = 'sep=;\nSeries;Time;Value\n'; _.each(seriesList, function(series) { _.each(series.datapoints, function(dp) { - text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; + text += series.alias + ';' + moment(dp[1]).format(dateTimeFormat) + ';' + dp[0] + '\n'; }); }); saveSaveBlob(text, 'grafana_data_export.csv'); } -export function exportSeriesListToCsvColumns(seriesList) { +export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT) { var text = 'sep=;\nTime;'; // add header _.each(seriesList, function(series) { @@ -30,7 +33,7 @@ export function exportSeriesListToCsvColumns(seriesList) { var cIndex = 0; dataArr.push([]); _.each(series.datapoints, function(dp) { - dataArr[0][cIndex] = new Date(dp[1]).toISOString(); + dataArr[0][cIndex] = moment(dp[1]).format(dateTimeFormat); dataArr[sIndex][cIndex] = dp[0]; cIndex++; }); diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index c362f9cd032..3315371a78f 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -21,4 +21,5 @@ define([ './ad_hoc_filters', './row/row_ctrl', './repeat_option/repeat_option', + './exportCsvModalCtrl' ], function () {}); diff --git a/public/app/features/dashboard/exportCsvModalCtrl.ts b/public/app/features/dashboard/exportCsvModalCtrl.ts new file mode 100644 index 00000000000..0e2e2d3f232 --- /dev/null +++ b/public/app/features/dashboard/exportCsvModalCtrl.ts @@ -0,0 +1,32 @@ +/// + +import config from 'app/core/config'; +import angular from 'angular'; +import _ from 'lodash'; +import * as fileExport from 'app/core/utils/file_export'; + +const module = angular.module('grafana.controllers'); + +export class ExportCsvModalCtrl { + scope: any; + seriesList: any = []; + /** @ngInject */ + constructor(private $scope) { + this.seriesList = $scope.seriesList; + this.$scope = $scope; + $scope.asRows = true; + $scope.dateTimeFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + $scope.export = this.export.bind(this); + } + + export() { + if (this.$scope.asRows) { + fileExport.exportSeriesListToCsv(this.seriesList, this.$scope.dateTimeFormat); + } else { + fileExport.exportSeriesListToCsvColumns(this.seriesList, this.$scope.dateTimeFormat); + } + this.$scope.dismiss(); + } +} + +module.controller('ExportCsvModalCtrl', ExportCsvModalCtrl); diff --git a/public/app/features/dashboard/partials/exportCsvModal.html b/public/app/features/dashboard/partials/exportCsvModal.html new file mode 100644 index 00000000000..ae3b4dfdf7d --- /dev/null +++ b/public/app/features/dashboard/partials/exportCsvModal.html @@ -0,0 +1,32 @@ + diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index e2dce12168d..89c280ed5e5 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -11,7 +11,6 @@ import moment from 'moment'; import _ from 'lodash'; import TimeSeries from 'app/core/time_series2'; import config from 'app/core/config'; -import * as fileExport from 'app/core/utils/file_export'; import {MetricsPanelCtrl, alertTab} from 'app/plugins/sdk'; import {DataProcessor} from './data_processor'; import {axesEditorComponent} from './axes_editor'; @@ -147,8 +146,7 @@ class GraphCtrl extends MetricsPanelCtrl { } onInitPanelActions(actions) { - actions.push({text: 'Export CSV (series as rows)', click: 'ctrl.exportCsv()'}); - actions.push({text: 'Export CSV (series as columns)', click: 'ctrl.exportCsvColumns()'}); + actions.push({text: 'Export CSV', click: 'ctrl.exportCsv()'}); actions.push({text: 'Toggle legend', click: 'ctrl.toggleLegend()'}); } @@ -313,13 +311,15 @@ class GraphCtrl extends MetricsPanelCtrl { } exportCsv() { - fileExport.exportSeriesListToCsv(this.seriesList); - } + var scope = this.$scope.$new(); + scope.seriesList = this.seriesList; - exportCsvColumns() { - fileExport.exportSeriesListToCsvColumns(this.seriesList); + this.publishAppEvent('show-modal', { + src: 'public/app/features/dashboard/partials/exportCsvModal.html', + scope, + modalClass: 'modal--narrow' + }); } - } export {GraphCtrl, GraphCtrl as PanelCtrl}; From ee0d0155a572a3fbe3677e52deb8c99151706bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Reginster?= Date: Thu, 4 May 2017 15:09:48 +0200 Subject: [PATCH 07/34] Refactor to component based style --- public/app/features/dashboard/all.js | 2 +- .../features/dashboard/exportCsvModalCtrl.ts | 32 --------------- .../export_data_modal.html} | 12 +++--- .../export_data/export_data_modal.ts | 41 +++++++++++++++++++ public/app/plugins/panel/graph/module.ts | 5 +-- 5 files changed, 50 insertions(+), 42 deletions(-) delete mode 100644 public/app/features/dashboard/exportCsvModalCtrl.ts rename public/app/features/dashboard/{partials/exportCsvModal.html => export_data/export_data_modal.html} (53%) create mode 100644 public/app/features/dashboard/export_data/export_data_modal.ts diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index 3315371a78f..242d464d77b 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -17,9 +17,9 @@ define([ './upload', './import/dash_import', './export/export_modal', + './export_data/export_data_modal', './dash_list_ctrl', './ad_hoc_filters', './row/row_ctrl', './repeat_option/repeat_option', - './exportCsvModalCtrl' ], function () {}); diff --git a/public/app/features/dashboard/exportCsvModalCtrl.ts b/public/app/features/dashboard/exportCsvModalCtrl.ts deleted file mode 100644 index 0e2e2d3f232..00000000000 --- a/public/app/features/dashboard/exportCsvModalCtrl.ts +++ /dev/null @@ -1,32 +0,0 @@ -/// - -import config from 'app/core/config'; -import angular from 'angular'; -import _ from 'lodash'; -import * as fileExport from 'app/core/utils/file_export'; - -const module = angular.module('grafana.controllers'); - -export class ExportCsvModalCtrl { - scope: any; - seriesList: any = []; - /** @ngInject */ - constructor(private $scope) { - this.seriesList = $scope.seriesList; - this.$scope = $scope; - $scope.asRows = true; - $scope.dateTimeFormat = 'YYYY-MM-DDTHH:mm:ssZ'; - $scope.export = this.export.bind(this); - } - - export() { - if (this.$scope.asRows) { - fileExport.exportSeriesListToCsv(this.seriesList, this.$scope.dateTimeFormat); - } else { - fileExport.exportSeriesListToCsvColumns(this.seriesList, this.$scope.dateTimeFormat); - } - this.$scope.dismiss(); - } -} - -module.controller('ExportCsvModalCtrl', ExportCsvModalCtrl); diff --git a/public/app/features/dashboard/partials/exportCsvModal.html b/public/app/features/dashboard/export_data/export_data_modal.html similarity index 53% rename from public/app/features/dashboard/partials/exportCsvModal.html rename to public/app/features/dashboard/export_data/export_data_modal.html index ae3b4dfdf7d..ad6494f16cc 100644 --- a/public/app/features/dashboard/partials/exportCsvModal.html +++ b/public/app/features/dashboard/export_data/export_data_modal.html @@ -1,10 +1,10 @@ - diff --git a/public/app/features/dashboard/export_data/export_data_modal.ts b/public/app/features/dashboard/export_data/export_data_modal.ts new file mode 100644 index 00000000000..80b2ff7d2f2 --- /dev/null +++ b/public/app/features/dashboard/export_data/export_data_modal.ts @@ -0,0 +1,41 @@ +/// + +import angular from 'angular'; +import * as fileExport from 'app/core/utils/file_export'; +import appEvents from 'app/core/app_events'; + +export class ExportDataModalCtrl { + private data: any[]; + asRows: Boolean = true; + dateTimeFormat: String = 'YYYY-MM-DDTHH:mm:ssZ'; + /** @ngInject */ + constructor(private $scope) { } + + export() { + if (this.asRows) { + fileExport.exportSeriesListToCsv(this.data, this.dateTimeFormat); + } else { + fileExport.exportSeriesListToCsvColumns(this.data, this.dateTimeFormat); + } + this.dismiss(); + } + + dismiss() { + appEvents.emit('hide-modal'); + } +} + +export function exportDataModal() { + return { + restrict: 'E', + templateUrl: 'public/app/features/dashboard/export_data/export_data_modal.html', + controller: ExportDataModalCtrl, + controllerAs: 'ctrl', + scope: { + data: '<' // The difference to '=' is that the bound properties are not watched + }, + bindToController: true + }; +} + +angular.module('grafana.directives').directive('exportDataModal', exportDataModal); diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 89c280ed5e5..e9ca8c5c4e2 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -311,11 +311,10 @@ class GraphCtrl extends MetricsPanelCtrl { } exportCsv() { - var scope = this.$scope.$new(); + var scope = this.$scope.$new(true); scope.seriesList = this.seriesList; - this.publishAppEvent('show-modal', { - src: 'public/app/features/dashboard/partials/exportCsvModal.html', + templateHtml: '', scope, modalClass: 'modal--narrow' }); From c17d02e4963e2921a9d41881dcdb205d184b2481 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 26 May 2017 15:41:50 +0200 Subject: [PATCH 08/34] csv: remove sep metadata as only works for excel --- public/app/core/utils/file_export.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index dd616ca77e7..960be37230c 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -8,7 +8,7 @@ declare var window: any; const DEFAULT_DATETIME_FORMAT: String = 'YYYY-MM-DDTHH:mm:ssZ'; export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT) { - var text = 'sep=;\nSeries;Time;Value\n'; + var text = 'Series;Time;Value\n'; _.each(seriesList, function(series) { _.each(series.datapoints, function(dp) { text += series.alias + ';' + moment(dp[1]).format(dateTimeFormat) + ';' + dp[0] + '\n'; @@ -18,7 +18,7 @@ export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATET } export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT) { - var text = 'sep=;\nTime;'; + var text = 'Time;'; // add header _.each(seriesList, function(series) { text += series.alias + ';'; @@ -53,7 +53,7 @@ export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAUL } export function exportTableDataToCsv(table) { - var text = 'sep=;\n'; + var text = ''; // add header _.each(table.columns, function(column) { text += (column.title || column.text) + ';'; From e65f86147f63f3137bce338a89d1a125adbbde57 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 26 May 2017 15:56:44 +0200 Subject: [PATCH 09/34] changelog: note for #8058 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0480e9b971c..98f489e3ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Enhancements * **Elasticsearch**: Added filter aggregation label [#8420](https://github.com/grafana/grafana/pull/8420), thx [@tianzk](github.com/tianzk) * **Sensu**: Added option for source and handler [#8405](https://github.com/grafana/grafana/pull/8405), thx [@joemiller](github.com/joemiller) +* **CSV**: Configurable csv export datetime format [#8058](https://github.com/grafana/grafana/issues/8058), thx [@cederigo](github.com/cederigo) # 4.3.2 (upcoming patch release) From c2885430bd45f1a083e0340520497ef609eb2975 Mon Sep 17 00:00:00 2001 From: Anton Yackushev Date: Mon, 29 May 2017 09:12:54 +0300 Subject: [PATCH 10/34] fix "no formatting directive in Fatalf call" (vet) (#8487) --- scripts/build/publish.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/publish.go b/scripts/build/publish.go index ed738e2312a..7e88c06f67f 100644 --- a/scripts/build/publish.go +++ b/scripts/build/publish.go @@ -75,7 +75,7 @@ func packageWalker(path string, f os.FileInfo, err error) error { shaBytes, err := ioutil.ReadFile(path + ".sha256") if err != nil { - log.Fatalf("Failed to read sha256 file", err) + log.Fatalf("Failed to read sha256 file %v", err) } os := "" From 7004a84c30417a22e89f8c1e8a422334e6d816ee Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Fri, 26 May 2017 13:39:52 -0400 Subject: [PATCH 11/34] tweak column lengths for utf8mb4 support on older mysql --- pkg/services/sqlstore/migrations/alert_mig.go | 4 ++-- pkg/services/sqlstore/migrations/dashboard_mig.go | 4 ++-- pkg/services/sqlstore/migrations/temp_user.go | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 7b6558656f1..2a364d5f464 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -16,7 +16,7 @@ func addAlertMigrations(mg *Migrator) { {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "frequency", Type: DB_BigInt, Nullable: false}, {Name: "handler", Type: DB_BigInt, Nullable: false}, @@ -70,7 +70,7 @@ func addAlertMigrations(mg *Migrator) { mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{ {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "severity", Type: DB_Text, Nullable: false}, {Name: "execution_error", Type: DB_Text, Nullable: false}, diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go index 0ef2f3be54f..0519194ae31 100644 --- a/pkg/services/sqlstore/migrations/dashboard_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_mig.go @@ -56,7 +56,7 @@ func addDashboardMigration(mg *Migrator) { Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "data", Type: DB_Text, Nullable: false}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, @@ -127,7 +127,7 @@ func addDashboardMigration(mg *Migrator) { })) mg.AddMigration("Update dashboard table charset", NewTableCharsetMigration("dashboard", []*Column{ - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255}, {Name: "data", Type: DB_MediumText, Nullable: false}, diff --git a/pkg/services/sqlstore/migrations/temp_user.go b/pkg/services/sqlstore/migrations/temp_user.go index 5592ab7e4ad..3913b18b3d8 100644 --- a/pkg/services/sqlstore/migrations/temp_user.go +++ b/pkg/services/sqlstore/migrations/temp_user.go @@ -9,10 +9,10 @@ func addTempUserMigrations(mg *Migrator) { {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "invited_by_user_id", Type: DB_BigInt, Nullable: true}, {Name: "email_sent", Type: DB_Bool}, @@ -37,10 +37,10 @@ func addTempUserMigrations(mg *Migrator) { addTableIndicesMigrations(mg, "v1-7", tempUserV1) mg.AddMigration("Update temp_user table charset", NewTableCharsetMigration("temp_user", []*Column{ - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "remote_addr", Type: DB_Varchar, Length: 255, Nullable: true}, })) From c0d5b6140346471eb81017a688870938ad411111 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Mon, 29 May 2017 02:31:36 -0400 Subject: [PATCH 12/34] tweak column lengths for utf8mb4 support on older mysql (#8483) --- pkg/services/sqlstore/migrations/alert_mig.go | 4 ++-- pkg/services/sqlstore/migrations/dashboard_mig.go | 4 ++-- pkg/services/sqlstore/migrations/temp_user.go | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 7b6558656f1..2a364d5f464 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -16,7 +16,7 @@ func addAlertMigrations(mg *Migrator) { {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "frequency", Type: DB_BigInt, Nullable: false}, {Name: "handler", Type: DB_BigInt, Nullable: false}, @@ -70,7 +70,7 @@ func addAlertMigrations(mg *Migrator) { mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{ {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "severity", Type: DB_Text, Nullable: false}, {Name: "execution_error", Type: DB_Text, Nullable: false}, diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go index 0ef2f3be54f..0519194ae31 100644 --- a/pkg/services/sqlstore/migrations/dashboard_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_mig.go @@ -56,7 +56,7 @@ func addDashboardMigration(mg *Migrator) { Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "data", Type: DB_Text, Nullable: false}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, @@ -127,7 +127,7 @@ func addDashboardMigration(mg *Migrator) { })) mg.AddMigration("Update dashboard table charset", NewTableCharsetMigration("dashboard", []*Column{ - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255}, {Name: "data", Type: DB_MediumText, Nullable: false}, diff --git a/pkg/services/sqlstore/migrations/temp_user.go b/pkg/services/sqlstore/migrations/temp_user.go index 5592ab7e4ad..3913b18b3d8 100644 --- a/pkg/services/sqlstore/migrations/temp_user.go +++ b/pkg/services/sqlstore/migrations/temp_user.go @@ -9,10 +9,10 @@ func addTempUserMigrations(mg *Migrator) { {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "invited_by_user_id", Type: DB_BigInt, Nullable: true}, {Name: "email_sent", Type: DB_Bool}, @@ -37,10 +37,10 @@ func addTempUserMigrations(mg *Migrator) { addTableIndicesMigrations(mg, "v1-7", tempUserV1) mg.AddMigration("Update temp_user table charset", NewTableCharsetMigration("temp_user", []*Column{ - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "remote_addr", Type: DB_Varchar, Length: 255, Nullable: true}, })) From ed2092e287fa8a287270770b65848fbb12439d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 29 May 2017 08:51:56 +0200 Subject: [PATCH 13/34] docs: updated alerting docs to make query conditions easier to understand, fixes #8486 --- docs/sources/alerting/rules.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index a2688de3100..c4a3a012c46 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -52,12 +52,22 @@ Here you can specify the name of the alert rule and how often the scheduler shou ### Conditions Currently the only condition type that exists is a `Query` condition that allows you to -specify a query letter, time range and an aggregation function. The letter refers to -a query you already have added in the **Metrics** tab. The result from the query and the aggregation function is -a single value that is then used in the threshold check. The query used in an alert rule cannot -contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. +specify a query letter, time range and an aggregation function. + + +### Query condition example + +```sql +avg() OF query(A, 5m, now) IS BELOW 14 +``` + +- `avg()` Controls how the values for **each** serie should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function. +- `query(A, 5m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters defines the time range, `5m, now` means 5 minutes from now to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes from now to 2 minutes from now. This is useful if you want to ignore the last 2 minutes of data. +- `IS BELOW 14` Defines the type of threshold and the threshold value. You can click on `IS BELOW` to change the type of threshold. + +The query used in an alert rule cannot contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. For example, we have 3 conditions in the following order: -`condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)` +*condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)* so the result will be calculated as ((TRUE OR FALSE) AND TRUE) = TRUE. We plan to add other condition types in the future, like `Other Alert`, where you can include the state From 0d39852ef4aef6360143295e5677437546e1c8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 29 May 2017 10:48:38 +0200 Subject: [PATCH 14/34] fix: fixed test data fake metric query, fixes #8474 --- pkg/api/api.go | 1 + pkg/api/metrics.go | 27 +++++++++++++++++++ .../plugins/datasource/grafana/datasource.ts | 26 +++++++++++++++--- .../grafana/partials/query.editor.html | 6 ++++- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 6dcc900c16f..eb17c65ac73 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -253,6 +253,7 @@ func (hs *HttpServer) registerRoutes() { r.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics)) r.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios)) r.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData)) + r.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk)) // metrics r.Get("/metrics", wrap(GetInternalMetrics)) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index abd6527431a..e35e35cdab6 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" @@ -144,3 +145,29 @@ func GenerateSqlTestData(c *middleware.Context) Response { return Json(200, &util.DynMap{"message": "OK"}) } + +// GET /api/tsdb/testdata/random-walk +func GetTestDataRandomWalk(c *middleware.Context) Response { + from := c.Query("from") + to := c.Query("to") + intervalMs := c.QueryInt64("intervalMs") + + timeRange := tsdb.NewTimeRange(from, to) + request := &tsdb.Request{TimeRange: timeRange} + + request.Queries = append(request.Queries, &tsdb.Query{ + RefId: "A", + IntervalMs: intervalMs, + Model: simplejson.NewFromAny(&util.DynMap{ + "scenario": "random_walk", + }), + DataSource: &models.DataSource{Type: "grafana-testdata-datasource"}, + }) + + resp, err := tsdb.HandleRequest(context.Background(), request) + if err != nil { + return ApiError(500, "Metric request error", err) + } + + return Json(200, &resp) +} diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 8fb987e75e5..2960af6b062 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -8,11 +8,31 @@ class GrafanaDatasource { constructor(private backendSrv, private $q) {} query(options) { - return this.$q.when({data: []}); + return this.backendSrv.get('/api/tsdb/testdata/random-walk', { + from: options.range.from.valueOf(), + to: options.range.to.valueOf(), + intervalMs: options.intervalMs, + maxDataPoints: options.maxDataPoints, + }).then(res => { + var data = []; + + if (res.results) { + _.forEach(res.results, queryRes => { + for (let series of queryRes.series) { + data.push({ + target: series.name, + datapoints: series.points + }); + } + }); + } + + return {data: data}; + }); } - metricFindQuery() { - return this.$q.when([]); + metricFindQuery(options) { + return this.$q.when({data: []}); } annotationQuery(options) { diff --git a/public/app/plugins/datasource/grafana/partials/query.editor.html b/public/app/plugins/datasource/grafana/partials/query.editor.html index 880402573d5..91ec2bc8faa 100644 --- a/public/app/plugins/datasource/grafana/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana/partials/query.editor.html @@ -1,7 +1,11 @@
- + +
+ +
+
From beced6f3a625e600a6d30c95e87a651d250d11e1 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 29 May 2017 11:04:49 +0200 Subject: [PATCH 15/34] graph: Handle data with zeroes for log scale fixes #8446. Data with all values equal to zero, creates a max with the value of Infinite. The for loop for creating ticks then gets stuck in an infinite loop. This fix resets min and max and creates some fake ticks for the y-axis if the min and max are not finite numbers. --- public/app/plugins/panel/graph/graph.ts | 17 ++++++++++---- .../plugins/panel/graph/specs/graph_specs.ts | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index e0cec3febbf..726f8be5628 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -555,12 +555,19 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { return; } - axis.ticks = []; - var nextTick; - for (nextTick = min; nextTick <= max; nextTick *= axis.logBase) { - axis.ticks.push(nextTick); + if (Number.isFinite(min) && Number.isFinite(max)) { + axis.ticks = []; + var nextTick; + for (nextTick = min; nextTick <= max; nextTick *= axis.logBase) { + axis.ticks.push(nextTick); + } + axis.tickDecimals = decimalPlaces(min); + } else { + axis.ticks = [1, 2]; + delete axis.min; + delete axis.max; } - axis.tickDecimals = decimalPlaces(min); + } function decimalPlaces(num) { diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 161aca11e42..9595c3dd544 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -153,6 +153,29 @@ describe('grafanaGraph', function() { }); }); + graphScenario('when logBase is log 10 and data points contain only zeroes', function(ctx) { + ctx.setup(function(ctrl, data) { + ctrl.panel.yaxes[0].logBase = 10; + data[0] = new TimeSeries({ + datapoints: [[0,1],[0,2],[0,3],[0,4]], + alias: 'seriesAutoscale', + }); + data[0].yaxis = 1; + }); + + it('should not set min and max and should create some fake ticks', function() { + var axisAutoscale = ctx.plotOptions.yaxes[0]; + expect(axisAutoscale.transform(100)).to.be(2); + expect(axisAutoscale.inverseTransform(-3)).to.be(0.001); + expect(axisAutoscale.min).to.be(undefined); + expect(axisAutoscale.max).to.be(undefined); + expect(axisAutoscale.ticks.length).to.be(2); + expect(axisAutoscale.ticks[0]).to.be(1); + expect(axisAutoscale.ticks[1]).to.be(2); + expect(axisAutoscale.tickDecimals).to.be(undefined); + }); + }); + graphScenario('dashed lines options', function(ctx) { ctx.setup(function(ctrl) { ctrl.panel.lines = true; From 980b9b5ca05baa2eb414aeaa755652057f95ce3a Mon Sep 17 00:00:00 2001 From: Victor Azevedo Date: Thu, 25 May 2017 11:43:29 -0400 Subject: [PATCH 16/34] use fielddata_fields in elasticsearch 2.x queries Resolves issue #8467 --- .../app/plugins/datasource/elasticsearch/query_builder.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.js b/public/app/plugins/datasource/elasticsearch/query_builder.js index dd296c00a34..571eea14399 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.js +++ b/public/app/plugins/datasource/elasticsearch/query_builder.js @@ -118,8 +118,12 @@ function (queryDef) { query.fields = ["*", "_source"]; } - query.script_fields = {}, - query.docvalue_fields = [this.timeField]; + query.script_fields = {}; + if (this.esVersion < 5) { + query.fielddata_fields = [this.timeField]; + } else { + query.docvalue_fields = [this.timeField]; + } return query; }; From 0d865a83bc7b21b36fdae6fdf305a9dea894883a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 29 May 2017 10:48:38 +0200 Subject: [PATCH 17/34] fix: fixed test data fake metric query, fixes #8474 --- pkg/api/api.go | 1 + pkg/api/metrics.go | 27 +++++++++++++++++++ .../plugins/datasource/grafana/datasource.ts | 26 +++++++++++++++--- .../grafana/partials/query.editor.html | 6 ++++- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 6dcc900c16f..eb17c65ac73 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -253,6 +253,7 @@ func (hs *HttpServer) registerRoutes() { r.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics)) r.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios)) r.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData)) + r.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk)) // metrics r.Get("/metrics", wrap(GetInternalMetrics)) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index abd6527431a..e35e35cdab6 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" @@ -144,3 +145,29 @@ func GenerateSqlTestData(c *middleware.Context) Response { return Json(200, &util.DynMap{"message": "OK"}) } + +// GET /api/tsdb/testdata/random-walk +func GetTestDataRandomWalk(c *middleware.Context) Response { + from := c.Query("from") + to := c.Query("to") + intervalMs := c.QueryInt64("intervalMs") + + timeRange := tsdb.NewTimeRange(from, to) + request := &tsdb.Request{TimeRange: timeRange} + + request.Queries = append(request.Queries, &tsdb.Query{ + RefId: "A", + IntervalMs: intervalMs, + Model: simplejson.NewFromAny(&util.DynMap{ + "scenario": "random_walk", + }), + DataSource: &models.DataSource{Type: "grafana-testdata-datasource"}, + }) + + resp, err := tsdb.HandleRequest(context.Background(), request) + if err != nil { + return ApiError(500, "Metric request error", err) + } + + return Json(200, &resp) +} diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 8fb987e75e5..2960af6b062 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -8,11 +8,31 @@ class GrafanaDatasource { constructor(private backendSrv, private $q) {} query(options) { - return this.$q.when({data: []}); + return this.backendSrv.get('/api/tsdb/testdata/random-walk', { + from: options.range.from.valueOf(), + to: options.range.to.valueOf(), + intervalMs: options.intervalMs, + maxDataPoints: options.maxDataPoints, + }).then(res => { + var data = []; + + if (res.results) { + _.forEach(res.results, queryRes => { + for (let series of queryRes.series) { + data.push({ + target: series.name, + datapoints: series.points + }); + } + }); + } + + return {data: data}; + }); } - metricFindQuery() { - return this.$q.when([]); + metricFindQuery(options) { + return this.$q.when({data: []}); } annotationQuery(options) { diff --git a/public/app/plugins/datasource/grafana/partials/query.editor.html b/public/app/plugins/datasource/grafana/partials/query.editor.html index 880402573d5..91ec2bc8faa 100644 --- a/public/app/plugins/datasource/grafana/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana/partials/query.editor.html @@ -1,7 +1,11 @@
- + +
+ +
+
From c95162e0679eb3729f815eeb145c2e6e0be6e38f Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Mon, 29 May 2017 02:31:36 -0400 Subject: [PATCH 18/34] tweak column lengths for utf8mb4 support on older mysql (#8483) --- pkg/services/sqlstore/migrations/alert_mig.go | 4 ++-- pkg/services/sqlstore/migrations/dashboard_mig.go | 4 ++-- pkg/services/sqlstore/migrations/temp_user.go | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 7b6558656f1..2a364d5f464 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -16,7 +16,7 @@ func addAlertMigrations(mg *Migrator) { {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "frequency", Type: DB_BigInt, Nullable: false}, {Name: "handler", Type: DB_BigInt, Nullable: false}, @@ -70,7 +70,7 @@ func addAlertMigrations(mg *Migrator) { mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{ {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "severity", Type: DB_Text, Nullable: false}, {Name: "execution_error", Type: DB_Text, Nullable: false}, diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go index 0ef2f3be54f..0519194ae31 100644 --- a/pkg/services/sqlstore/migrations/dashboard_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_mig.go @@ -56,7 +56,7 @@ func addDashboardMigration(mg *Migrator) { Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "data", Type: DB_Text, Nullable: false}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, @@ -127,7 +127,7 @@ func addDashboardMigration(mg *Migrator) { })) mg.AddMigration("Update dashboard table charset", NewTableCharsetMigration("dashboard", []*Column{ - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255}, {Name: "data", Type: DB_MediumText, Nullable: false}, diff --git a/pkg/services/sqlstore/migrations/temp_user.go b/pkg/services/sqlstore/migrations/temp_user.go index 5592ab7e4ad..3913b18b3d8 100644 --- a/pkg/services/sqlstore/migrations/temp_user.go +++ b/pkg/services/sqlstore/migrations/temp_user.go @@ -9,10 +9,10 @@ func addTempUserMigrations(mg *Migrator) { {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "invited_by_user_id", Type: DB_BigInt, Nullable: true}, {Name: "email_sent", Type: DB_Bool}, @@ -37,10 +37,10 @@ func addTempUserMigrations(mg *Migrator) { addTableIndicesMigrations(mg, "v1-7", tempUserV1) mg.AddMigration("Update temp_user table charset", NewTableCharsetMigration("temp_user", []*Column{ - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "remote_addr", Type: DB_Varchar, Length: 255, Nullable: true}, })) From d318c9093fc2fcf610f38bf22550fb0f66849626 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 29 May 2017 11:04:49 +0200 Subject: [PATCH 19/34] graph: Handle data with zeroes for log scale fixes #8446. Data with all values equal to zero, creates a max with the value of Infinite. The for loop for creating ticks then gets stuck in an infinite loop. This fix resets min and max and creates some fake ticks for the y-axis if the min and max are not finite numbers. --- public/app/plugins/panel/graph/graph.ts | 17 ++++++++++---- .../plugins/panel/graph/specs/graph_specs.ts | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index e0cec3febbf..726f8be5628 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -555,12 +555,19 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { return; } - axis.ticks = []; - var nextTick; - for (nextTick = min; nextTick <= max; nextTick *= axis.logBase) { - axis.ticks.push(nextTick); + if (Number.isFinite(min) && Number.isFinite(max)) { + axis.ticks = []; + var nextTick; + for (nextTick = min; nextTick <= max; nextTick *= axis.logBase) { + axis.ticks.push(nextTick); + } + axis.tickDecimals = decimalPlaces(min); + } else { + axis.ticks = [1, 2]; + delete axis.min; + delete axis.max; } - axis.tickDecimals = decimalPlaces(min); + } function decimalPlaces(num) { diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 161aca11e42..9595c3dd544 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -153,6 +153,29 @@ describe('grafanaGraph', function() { }); }); + graphScenario('when logBase is log 10 and data points contain only zeroes', function(ctx) { + ctx.setup(function(ctrl, data) { + ctrl.panel.yaxes[0].logBase = 10; + data[0] = new TimeSeries({ + datapoints: [[0,1],[0,2],[0,3],[0,4]], + alias: 'seriesAutoscale', + }); + data[0].yaxis = 1; + }); + + it('should not set min and max and should create some fake ticks', function() { + var axisAutoscale = ctx.plotOptions.yaxes[0]; + expect(axisAutoscale.transform(100)).to.be(2); + expect(axisAutoscale.inverseTransform(-3)).to.be(0.001); + expect(axisAutoscale.min).to.be(undefined); + expect(axisAutoscale.max).to.be(undefined); + expect(axisAutoscale.ticks.length).to.be(2); + expect(axisAutoscale.ticks[0]).to.be(1); + expect(axisAutoscale.ticks[1]).to.be(2); + expect(axisAutoscale.tickDecimals).to.be(undefined); + }); + }); + graphScenario('dashed lines options', function(ctx) { ctx.setup(function(ctrl) { ctrl.panel.lines = true; From e4cb103966c087d01802960e13cf6de9e4b23b74 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Wed, 24 May 2017 04:39:40 -0700 Subject: [PATCH 20/34] remove X-Forwarded-* headers added by nginx when proxying data source & plugin requests (#8418) * remove X-Forwarded-* headers added by nginx when proxying data source & plugin requests * properly handle X-Forwarded-For --- pkg/api/dataproxy.go | 22 ++++++++++++++++++++++ pkg/api/pluginproxy/pluginproxy.go | 23 ++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 612d013e802..965653c2501 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -3,6 +3,7 @@ package api import ( "bytes" "io/ioutil" + "net" "net/http" "net/http/httputil" "net/url" @@ -62,6 +63,27 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht // clear cookie headers req.Header.Del("Cookie") req.Header.Del("Set-Cookie") + + // clear X-Forwarded Host/Port/Proto headers + req.Header.Del("X-Forwarded-Host") + req.Header.Del("X-Forwarded-Port") + req.Header.Del("X-Forwarded-Proto") + + // set X-Forwarded-For header + if req.RemoteAddr != "" { + remoteAddr, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + remoteAddr = req.RemoteAddr + } + if req.Header.Get("X-Forwarded-For") != "" { + req.Header.Set("X-Forwarded-For", req.Header.Get("X-Forwarded-For")+", "+remoteAddr) + } else { + req.Header.Set("X-Forwarded-For", remoteAddr) + } + } + + // reqBytes, _ := httputil.DumpRequestOut(req, true); + // log.Trace("Proxying datasource request: %s", string(reqBytes)) } return &httputil.ReverseProxy{Director: director, FlushInterval: time.Millisecond * 200} diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index 21d40ecb948..a5139bb69f7 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "net" "net/http" "net/http/httputil" "net/url" @@ -71,7 +72,25 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins req.Header.Del("Cookie") req.Header.Del("Set-Cookie") - //Create a HTTP header with the context in it. + // clear X-Forwarded Host/Port/Proto headers + req.Header.Del("X-Forwarded-Host") + req.Header.Del("X-Forwarded-Port") + req.Header.Del("X-Forwarded-Proto") + + // set X-Forwarded-For header + if req.RemoteAddr != "" { + remoteAddr, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + remoteAddr = req.RemoteAddr + } + if req.Header.Get("X-Forwarded-For") != "" { + req.Header.Set("X-Forwarded-For", req.Header.Get("X-Forwarded-For")+", "+remoteAddr) + } else { + req.Header.Set("X-Forwarded-For", remoteAddr) + } + } + + // Create a HTTP header with the context in it. ctxJson, err := json.Marshal(ctx.SignedInUser) if err != nil { ctx.JsonApiErr(500, "failed to marshal context to json.", err) @@ -93,6 +112,8 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins } } + // reqBytes, _ := httputil.DumpRequestOut(req, true); + // log.Trace("Proxying plugin request: %s", string(reqBytes)) } return &httputil.ReverseProxy{Director: director} From 26ec874fb1b548a54edad9e7792c25248b7f86c9 Mon Sep 17 00:00:00 2001 From: Pranay Kanwar Date: Tue, 30 May 2017 13:23:11 +0530 Subject: [PATCH 21/34] Frontend query changes corresponding #8336 (#8489) --- public/app/plugins/datasource/opentsdb/datasource.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index ecd939d8f74..f460c1a1c91 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -28,8 +28,8 @@ function (angular, _, dateMath) { _.each(options.targets, function(target) { if (!target.metric) { return; } - qs.push(convertTargetToQuery(target, options)); - }); + qs.push(convertTargetToQuery(target, options, this.tsdbVersion)); + }.bind(this)); var queries = _.compact(qs); @@ -366,7 +366,7 @@ function (angular, _, dateMath) { return label; } - function convertTargetToQuery(target, options) { + function convertTargetToQuery(target, options, tsdbVersion) { if (!target.metric || target.hide) { return null; } @@ -393,6 +393,11 @@ function (angular, _, dateMath) { if (target.counterResetValue && target.counterResetValue.length) { query.rateOptions.resetValue = parseInt(target.counterResetValue); } + + if(tsdbVersion >= 2) { + query.rateOptions.dropResets = !query.rateOptions.counterMax && + (!query.rateOptions.ResetValue || query.rateOptions.ResetValue === 0); + } } if (!target.disableDownsampling) { From cd3807055e8d5de760fd00baca9cafb2e5fdf423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 30 May 2017 13:50:03 +0200 Subject: [PATCH 22/34] chrome: an attempt to fix scroll issue with chrome, but this did not fix the problem as it seems to be caused by some interaction between graph rendering and scrolling, #8494 --- public/app/core/components/grafana_app.ts | 12 ++++++++++++ public/app/features/panel/panel_directive.ts | 3 ++- public/app/headers/common.d.ts | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index f48d941a8ba..09326de6481 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -122,6 +122,18 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { body.toggleClass('page-kiosk-mode'); }); + var ticking = false; + window.addEventListener('scroll', function(e) { + if (!ticking) { + ticking = true; + setTimeout(function() { + appEvents.emit('scroll'); + ticking = false; + }, 100); + } + ticking = true; + }); + // handle in active view state class var lastActivity = new Date().getTime(); var activeUser = true; diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 63fdba693cb..c7ccbb62eab 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -4,6 +4,7 @@ import angular from 'angular'; import $ from 'jquery'; import _ from 'lodash'; import Drop from 'tether-drop'; +import {appEvents} from 'app/core/core'; var module = angular.module('grafana.directives'); @@ -196,7 +197,7 @@ module.directive('grafanaPanel', function($rootScope, $document) { } }; - $document.on('scroll', refreshOnScroll); + appEvents.on('scroll', refreshOnScroll, scope); scope.$on('$destroy', function() { elem.off(); diff --git a/public/app/headers/common.d.ts b/public/app/headers/common.d.ts index 9ea5e96654d..314258be21e 100644 --- a/public/app/headers/common.d.ts +++ b/public/app/headers/common.d.ts @@ -72,3 +72,4 @@ declare module 'd3' { var d3: any; export default d3; } + From 812958162a69b24dff14e8dbb30758b1597c4de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 31 May 2017 10:47:04 +0200 Subject: [PATCH 23/34] revert: removed lazy loading feature, closes #8500 --- public/app/features/panel/panel_ctrl.ts | 9 --------- public/app/features/panel/panel_directive.ts | 14 -------------- 2 files changed, 23 deletions(-) diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 8de78291baa..83c79f4123b 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -35,8 +35,6 @@ export class PanelCtrl { containerHeight: any; events: Emitter; timing: any; - skippedLastRefresh: boolean; - isPanelVisible: any; constructor($scope, $injector) { this.$injector = $injector; @@ -77,13 +75,6 @@ export class PanelCtrl { } refresh() { - if (!this.isPanelVisible() && !this.dashboard.meta.soloMode && !this.dashboard.snapshot) { - this.skippedLastRefresh = true; - return; - } - - this.skippedLastRefresh = false; - this.events.emit('refresh', null); } diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 63fdba693cb..11906ab7d8d 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -185,23 +185,9 @@ module.directive('grafanaPanel', function($rootScope, $document) { elem.on('mouseenter', mouseEnter); elem.on('mouseleave', mouseLeave); - ctrl.isPanelVisible = function () { - var position = panelContainer[0].getBoundingClientRect(); - return (0 < position.top) && (position.top < window.innerHeight); - }; - - const refreshOnScroll = function () { - if (ctrl.skippedLastRefresh) { - ctrl.refresh(); - } - }; - - $document.on('scroll', refreshOnScroll); - scope.$on('$destroy', function() { elem.off(); cornerInfoElem.off(); - $document.off('scroll', refreshOnScroll); if (infoDrop) { infoDrop.destroy(); From ed4d170bcce8697bda28a65a33c630a2628d989e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 31 May 2017 10:48:16 +0200 Subject: [PATCH 24/34] build: updated version to v4.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f9a4364e33..5d028de93b0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Coding Instinct AB" }, "name": "grafana", - "version": "4.3.1", + "version": "4.3.2", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" From 6783d1000ca8a656011f82c6b2f622559e270a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 31 May 2017 14:04:02 +0200 Subject: [PATCH 25/34] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f489e3ce3..d6f8cecd7d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ * **Playlist**: Fixed dashboard play order issue [#7688](https://github.com/grafana/grafana/issues/7688) * **Elasticsearch**: Fixed table query issue with ES 2.x [#8467](https://github.com/grafana/grafana/issues/8467), thx [@goldeelox](https://github.com/goldeelox) +## Changes +* **Lazy Loading Of Panels**: Panels are no longer loaded as they are scrolled into view, this was reverted due to Chrome bug, might be reintroduced when Chrome fixes it's JS blocking behavior on scroll. [#8500](https://github.com/grafana/grafana/issues/8500) + # 4.3.1 (2017-05-23) ## Bug fixes From 1bdf82dca3882452eac6454e19d64d5e77cb1bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 31 May 2017 14:04:19 +0200 Subject: [PATCH 26/34] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f8cecd7d0..eef674b4bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * **Sensu**: Added option for source and handler [#8405](https://github.com/grafana/grafana/pull/8405), thx [@joemiller](github.com/joemiller) * **CSV**: Configurable csv export datetime format [#8058](https://github.com/grafana/grafana/issues/8058), thx [@cederigo](github.com/cederigo) -# 4.3.2 (upcoming patch release) +# 4.3.2 (2017-05-31) ## Bug fixes From e9d5e037e817b57303c4497b42b28ee9403dd65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 2 Jun 2017 14:00:42 +0200 Subject: [PATCH 27/34] ux: merge branch navbarv2, new navbar with dashboard search available on all pages, closes #6475 --- public/app/core/components/grafana_app.ts | 19 +- public/app/core/components/help/help.html | 4 +- public/app/core/components/navbar/navbar.html | 35 ++- public/app/core/components/navbar/navbar.ts | 27 +- public/app/core/components/search/search.html | 112 ++++---- public/app/core/components/search/search.ts | 5 + public/app/core/controllers/error_ctrl.js | 4 +- public/app/core/core.ts | 3 + public/app/core/nav_model_srv.ts | 209 +++++++++++++++ public/app/core/routes/routes.ts | 4 + public/app/core/services/keybindingSrv.ts | 6 +- public/app/features/admin/admin.ts | 14 +- public/app/features/admin/adminEditOrgCtrl.js | 4 +- .../app/features/admin/adminEditUserCtrl.js | 3 +- .../app/features/admin/adminListOrgsCtrl.js | 3 +- .../features/admin/admin_list_users_ctrl.ts | 4 +- .../features/admin/partials/admin_home.html | 3 +- .../app/features/admin/partials/edit_org.html | 7 +- .../features/admin/partials/edit_user.html | 7 +- .../app/features/admin/partials/new_user.html | 7 +- public/app/features/admin/partials/orgs.html | 7 +- .../app/features/admin/partials/settings.html | 3 +- public/app/features/admin/partials/stats.html | 3 +- public/app/features/admin/partials/users.html | 15 +- .../app/features/alerting/alert_list_ctrl.ts | 11 +- .../alerting/notification_edit_ctrl.ts | 5 +- .../alerting/notifications_list_ctrl.ts | 9 +- .../alerting/partials/alert_list.html | 3 +- .../alerting/partials/notification_edit.html | 7 +- .../alerting/partials/notifications_list.html | 3 +- .../features/dashboard/dashnav/dashnav.html | 75 ++---- .../app/features/dashboard/dashnav/dashnav.ts | 172 ++++++------- public/app/features/dashboard/viewStateSrv.js | 9 +- .../app/features/org/change_password_ctrl.js | 3 +- public/app/features/org/newOrgCtrl.js | 3 +- public/app/features/org/orgApiKeysCtrl.js | 3 +- public/app/features/org/orgDetailsCtrl.js | 3 +- public/app/features/org/org_users_ctrl.ts | 4 +- .../org/partials/change_password.html | 3 +- public/app/features/org/partials/newOrg.html | 3 +- .../app/features/org/partials/orgApiKeys.html | 3 +- .../app/features/org/partials/orgDetails.html | 3 +- .../app/features/org/partials/orgUsers.html | 3 +- public/app/features/org/partials/profile.html | 5 +- public/app/features/org/profile_ctrl.ts | 4 +- .../features/playlist/partials/playlist.html | 3 +- .../features/playlist/partials/playlists.html | 3 +- .../features/playlist/playlist_edit_ctrl.ts | 29 ++- public/app/features/playlist/playlist_srv.ts | 6 +- .../app/features/playlist/playlists_ctrl.ts | 14 +- .../specs/playlist_edit_ctrl_specs.ts | 6 +- public/app/features/plugins/ds_edit_ctrl.ts | 240 +++++++++--------- public/app/features/plugins/ds_list_ctrl.ts | 42 +-- .../features/plugins/partials/ds_edit.html | 3 +- .../features/plugins/partials/ds_list.html | 6 +- .../plugins/partials/plugin_edit.html | 3 +- .../plugins/partials/plugin_list.html | 3 +- .../plugins/partials/plugin_page.html | 3 +- .../app/features/plugins/plugin_edit_ctrl.ts | 19 +- .../app/features/plugins/plugin_list_ctrl.ts | 4 +- .../app/features/plugins/plugin_page_ctrl.ts | 45 +++- .../features/snapshot/partials/snapshots.html | 3 +- public/app/features/snapshot/snapshot_ctrl.ts | 11 +- .../app/features/styleguide/styleguide.html | 3 +- public/app/features/styleguide/styleguide.ts | 4 +- public/app/partials/dashboard.html | 3 +- public/app/partials/error.html | 3 +- public/sass/_old_responsive.scss | 4 +- public/sass/components/_dropdown.scss | 16 +- public/sass/components/_navbar.scss | 46 +++- public/sass/components/_search.scss | 105 +++++--- public/sass/components/_sidemenu.scss | 8 +- public/sass/components/_view_states.scss | 2 +- 73 files changed, 929 insertions(+), 550 deletions(-) create mode 100644 public/app/core/nav_model_srv.ts diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index f48d941a8ba..5a677094754 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -105,10 +105,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { if (pageClass) { body.removeClass(pageClass); } - pageClass = data.$$route.pageClass; - if (pageClass) { - body.addClass(pageClass); + + if (data.$$route) { + pageClass = data.$$route.pageClass; + if (pageClass) { + body.addClass(pageClass); + } } + $("#tooltip, .tooltip").remove(); // check for kiosk url param @@ -194,6 +198,15 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { }); } } + + // hide menus + var openMenus = body.find('.navbar-page-btn--open'); + if (openMenus.length > 0) { + if (target.parents('.navbar-page-btn--open').length === 0) { + openMenus.removeClass('navbar-page-btn--open'); + } + } + // hide sidemenu if (!ignoreSideMenuHide && !contextSrv.pinned && body.find('.sidemenu').length > 0) { if (target.parents('.sidemenu').length === 0) { diff --git a/public/app/core/components/help/help.html b/public/app/core/components/help/help.html index 3356f21d452..c07d57a0ffc 100644 --- a/public/app/core/components/help/help.html +++ b/public/app/core/components/help/help.html @@ -1,7 +1,7 @@