From b475f913875b00627bf6a8d030bbffac782e0bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 Jan 2017 12:10:26 +0100 Subject: [PATCH] feat(templating): completed work on built in template variables and , closes #7190, refactors #7170 --- pkg/tsdb/influxdb/query.go | 53 +++++++++++++++---- pkg/tsdb/influxdb/query_part.go | 25 +-------- pkg/tsdb/influxdb/query_part_test.go | 25 +-------- pkg/tsdb/influxdb/query_test.go | 45 +++++++++++++++- pkg/tsdb/interval.go | 11 ++-- pkg/tsdb/interval_test.go | 8 +-- public/app/features/templating/templateSrv.js | 10 ++-- 7 files changed, 108 insertions(+), 69 deletions(-) diff --git a/pkg/tsdb/influxdb/query.go b/pkg/tsdb/influxdb/query.go index 094f98cbe53..50d155131fd 100644 --- a/pkg/tsdb/influxdb/query.go +++ b/pkg/tsdb/influxdb/query.go @@ -2,7 +2,9 @@ package influxdb import ( "fmt" + "strconv" "strings" + "time" "regexp" @@ -15,24 +17,53 @@ var ( ) func (query *Query) Build(queryContext *tsdb.QueryContext) (string, error) { + var res string + if query.UseRawQuery && query.RawQuery != "" { - q := query.RawQuery - - q = strings.Replace(q, "$timeFilter", query.renderTimeFilter(queryContext), 1) - q = strings.Replace(q, "$interval", tsdb.CalculateInterval(queryContext.TimeRange), 1) - - return q, nil + res = query.RawQuery + } else { + res = query.renderSelectors(queryContext) + res += query.renderMeasurement() + res += query.renderWhereClause() + res += query.renderTimeFilter(queryContext) + res += query.renderGroupBy(queryContext) } - res := query.renderSelectors(queryContext) - res += query.renderMeasurement() - res += query.renderWhereClause() - res += query.renderTimeFilter(queryContext) - res += query.renderGroupBy(queryContext) + interval, err := getDefinedInterval(query, queryContext) + if err != nil { + return "", err + } + res = strings.Replace(res, "$timeFilter", query.renderTimeFilter(queryContext), 1) + res = strings.Replace(res, "$interval", interval.Text, 1) + res = strings.Replace(res, "$__interval_ms", strconv.FormatInt(interval.Value.Nanoseconds()/int64(time.Millisecond), 10), 1) + res = strings.Replace(res, "$__interval", interval.Text, 1) return res, nil } +func getDefinedInterval(query *Query, queryContext *tsdb.QueryContext) (*tsdb.Interval, error) { + defaultInterval := tsdb.CalculateInterval(queryContext.TimeRange) + + if query.Interval == "" { + return &defaultInterval, nil + } + + setInterval := strings.Replace(strings.Replace(query.Interval, "<", "", 1), ">", "", 1) + parsedSetInterval, err := time.ParseDuration(setInterval) + + if err != nil { + return nil, err + } + + if strings.Contains(query.Interval, ">") { + if defaultInterval.Value > parsedSetInterval { + return &defaultInterval, nil + } + } + + return &tsdb.Interval{Value: parsedSetInterval, Text: setInterval}, nil +} + func (query *Query) renderTags() []string { var res []string for i, tag := range query.Tags { diff --git a/pkg/tsdb/influxdb/query_part.go b/pkg/tsdb/influxdb/query_part.go index 222e0b054ee..3145ff1b333 100644 --- a/pkg/tsdb/influxdb/query_part.go +++ b/pkg/tsdb/influxdb/query_part.go @@ -3,7 +3,6 @@ package influxdb import ( "fmt" "strings" - "time" "github.com/grafana/grafana/pkg/tsdb" ) @@ -93,30 +92,10 @@ func fieldRenderer(query *Query, queryContext *tsdb.QueryContext, part *QueryPar return fmt.Sprintf(`"%s"`, part.Params[0]) } -func getDefinedInterval(query *Query, queryContext *tsdb.QueryContext) string { - setInterval := strings.Replace(strings.Replace(query.Interval, "<", "", 1), ">", "", 1) - defaultInterval := tsdb.CalculateInterval(queryContext.TimeRange) - - if strings.Contains(query.Interval, ">") { - parsedDefaultInterval, err := time.ParseDuration(defaultInterval) - parsedSetInterval, err2 := time.ParseDuration(setInterval) - - if err == nil && err2 == nil && parsedDefaultInterval > parsedSetInterval { - return defaultInterval - } - } - - return setInterval -} - func functionRenderer(query *Query, queryContext *tsdb.QueryContext, part *QueryPart, innerExpr string) string { for i, param := range part.Params { - if param == "$interval" || param == "auto" { - if query.Interval != "" { - part.Params[i] = getDefinedInterval(query, queryContext) - } else { - part.Params[i] = tsdb.CalculateInterval(queryContext.TimeRange) - } + if part.Type == "time" && param == "auto" { + part.Params[i] = "$__interval" } } diff --git a/pkg/tsdb/influxdb/query_part_test.go b/pkg/tsdb/influxdb/query_part_test.go index a083754574a..b5bae2fdf37 100644 --- a/pkg/tsdb/influxdb/query_part_test.go +++ b/pkg/tsdb/influxdb/query_part_test.go @@ -42,7 +42,7 @@ func TestInfluxdbQueryPart(t *testing.T) { So(err, ShouldBeNil) res := part.Render(query, queryContext, "") - So(res, ShouldEqual, "time(200ms)") + So(res, ShouldEqual, "time($interval)") }) Convey("render time with auto", func() { @@ -50,28 +50,7 @@ func TestInfluxdbQueryPart(t *testing.T) { So(err, ShouldBeNil) res := part.Render(query, queryContext, "") - So(res, ShouldEqual, "time(200ms)") - }) - - Convey("render time interval >10s", func() { - part, err := NewQueryPart("time", []string{"$interval"}) - So(err, ShouldBeNil) - - query.Interval = ">10s" - - res := part.Render(query, queryContext, "") - So(res, ShouldEqual, "time(10s)") - }) - - Convey("render time interval >1s and higher interval calculation", func() { - part, err := NewQueryPart("time", []string{"$interval"}) - queryContext := &tsdb.QueryContext{TimeRange: tsdb.NewTimeRange("1y", "now")} - So(err, ShouldBeNil) - - query.Interval = ">1s" - - res := part.Render(query, queryContext, "") - So(res, ShouldEqual, "time(168h)") + So(res, ShouldEqual, "time($__interval)") }) Convey("render spread", func() { diff --git a/pkg/tsdb/influxdb/query_test.go b/pkg/tsdb/influxdb/query_test.go index b6af67e5e42..dd2a3a5687a 100644 --- a/pkg/tsdb/influxdb/query_test.go +++ b/pkg/tsdb/influxdb/query_test.go @@ -16,10 +16,15 @@ func TestInfluxdbQueryBuilder(t *testing.T) { qp1, _ := NewQueryPart("field", []string{"value"}) qp2, _ := NewQueryPart("mean", []string{}) - groupBy1, _ := NewQueryPart("time", []string{"$interval"}) + mathPartDivideBy100, _ := NewQueryPart("math", []string{"/ 100"}) + mathPartDivideByIntervalMs, _ := NewQueryPart("math", []string{"/ $__interval_ms"}) + + groupBy1, _ := NewQueryPart("time", []string{"$__interval"}) groupBy2, _ := NewQueryPart("tag", []string{"datacenter"}) groupBy3, _ := NewQueryPart("fill", []string{"null"}) + groupByOldInterval, _ := NewQueryPart("time", []string{"$interval"}) + tag1 := &Tag{Key: "hostname", Value: "server1", Operator: "="} tag2 := &Tag{Key: "hostname", Value: "server2", Operator: "=", Condition: "OR"} @@ -55,6 +60,43 @@ func TestInfluxdbQueryBuilder(t *testing.T) { So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "cpu" WHERE "hostname" = 'server1' OR "hostname" = 'server2' AND time > now() - 5m GROUP BY time(5s), "datacenter" fill(null)`) }) + Convey("can build query with math part", func() { + query := &Query{ + Selects: []*Select{{*qp1, *qp2, *mathPartDivideBy100}}, + Measurement: "cpu", + Interval: "5s", + } + + rawQuery, err := query.Build(queryContext) + So(err, ShouldBeNil) + So(rawQuery, ShouldEqual, `SELECT mean("value") / 100 FROM "cpu" WHERE time > now() - 5m`) + }) + + Convey("can build query with math part using $__interval_ms variable", func() { + query := &Query{ + Selects: []*Select{{*qp1, *qp2, *mathPartDivideByIntervalMs}}, + Measurement: "cpu", + Interval: "5s", + } + + rawQuery, err := query.Build(queryContext) + So(err, ShouldBeNil) + So(rawQuery, ShouldEqual, `SELECT mean("value") / 5000 FROM "cpu" WHERE time > now() - 5m`) + }) + + Convey("can build query with old $interval variable", func() { + query := &Query{ + Selects: []*Select{{*qp1, *qp2}}, + Measurement: "cpu", + Policy: "", + GroupBy: []*QueryPart{groupByOldInterval}, + } + + rawQuery, err := query.Build(queryContext) + So(err, ShouldBeNil) + So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "cpu" WHERE time > now() - 5m GROUP BY time(200ms)`) + }) + Convey("can render time range", func() { query := Query{} Convey("render from: 2h to now-1h", func() { @@ -139,4 +181,5 @@ func TestInfluxdbQueryBuilder(t *testing.T) { So(query.renderMeasurement(), ShouldEqual, ` FROM "policy"./apa/`) }) }) + } diff --git a/pkg/tsdb/interval.go b/pkg/tsdb/interval.go index 71caf122c13..aef6cc4f47b 100644 --- a/pkg/tsdb/interval.go +++ b/pkg/tsdb/interval.go @@ -12,14 +12,19 @@ var ( day time.Duration = time.Hour * 24 * 365 ) -func CalculateInterval(timerange *TimeRange) string { +type Interval struct { + Text string + Value time.Duration +} + +func CalculateInterval(timerange *TimeRange) Interval { interval := time.Duration((timerange.MustGetTo().UnixNano() - timerange.MustGetFrom().UnixNano()) / defaultRes) if interval < minInterval { - return formatDuration(minInterval) + return Interval{Text: formatDuration(minInterval), Value: interval} } - return formatDuration(roundInterval(interval)) + return Interval{Text: formatDuration(roundInterval(interval)), Value: interval} } func formatDuration(inter time.Duration) string { diff --git a/pkg/tsdb/interval_test.go b/pkg/tsdb/interval_test.go index c06e1879668..7b243b4e3ba 100644 --- a/pkg/tsdb/interval_test.go +++ b/pkg/tsdb/interval_test.go @@ -18,28 +18,28 @@ func TestInterval(t *testing.T) { tr := NewTimeRange("5m", "now") interval := CalculateInterval(tr) - So(interval, ShouldEqual, "200ms") + So(interval.Text, ShouldEqual, "200ms") }) Convey("for 15min", func() { tr := NewTimeRange("15m", "now") interval := CalculateInterval(tr) - So(interval, ShouldEqual, "500ms") + So(interval.Text, ShouldEqual, "500ms") }) Convey("for 30min", func() { tr := NewTimeRange("30m", "now") interval := CalculateInterval(tr) - So(interval, ShouldEqual, "1s") + So(interval.Text, ShouldEqual, "1s") }) Convey("for 1h", func() { tr := NewTimeRange("1h", "now") interval := CalculateInterval(tr) - So(interval, ShouldEqual, "2s") + So(interval.Text, ShouldEqual, "2s") }) Convey("Round interval", func() { diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index 52a17de3119..a2a8915429a 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -17,6 +17,11 @@ function (angular, _, kbn) { this._grafanaVariables = {}; this._adhocVariables = {}; + // default built ins + this._builtIns = {}; + this._builtIns['__interval'] = {text: '1s', value: '1s'}; + this._builtIns['__interval_ms'] = {text: '100', value: '100'}; + this.init = function(variables) { this.variables = variables; this.updateTemplateData(); @@ -43,9 +48,6 @@ function (angular, _, kbn) { this._index[variable.name] = variable; } - // default built ins - this._index['__interval'] = {text: '1s', value: '1s'}; - this._index['__interval_ms'] = {text: '100', value: '100'}; }; this.variableInitialized = function(variable) { @@ -136,7 +138,7 @@ function (angular, _, kbn) { str = _.escape(str); this._regex.lastIndex = 0; return str.replace(this._regex, function(match, g1, g2) { - if (self._index[g1 || g2]) { + if (self._index[g1 || g2] || self._builtIns[g1 || g2]) { return '' + match + ''; } return match;