Commit 149dd782 by Torkel Ödegaard

Merge branch 'built_in_template_variables'

parents 44b7a261 b475f913
......@@ -2,7 +2,9 @@ package influxdb
import (
"fmt"
"strconv"
"strings"
"time"
"regexp"
......@@ -15,22 +17,51 @@ var (
)
func (query *Query) Build(queryContext *tsdb.QueryContext) (string, error) {
var res string
if query.UseRawQuery && query.RawQuery != "" {
q := query.RawQuery
res = query.RawQuery
} else {
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
}
q = strings.Replace(q, "$timeFilter", query.renderTimeFilter(queryContext), 1)
q = strings.Replace(q, "$interval", tsdb.CalculateInterval(queryContext.TimeRange), 1)
func getDefinedInterval(query *Query, queryContext *tsdb.QueryContext) (*tsdb.Interval, error) {
defaultInterval := tsdb.CalculateInterval(queryContext.TimeRange)
return q, nil
if query.Interval == "" {
return &defaultInterval, nil
}
res := query.renderSelectors(queryContext)
res += query.renderMeasurement()
res += query.renderWhereClause()
res += query.renderTimeFilter(queryContext)
res += query.renderGroupBy(queryContext)
setInterval := strings.Replace(strings.Replace(query.Interval, "<", "", 1), ">", "", 1)
parsedSetInterval, err := time.ParseDuration(setInterval)
return res, nil
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 {
......
......@@ -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"
}
}
......
......@@ -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() {
......
......@@ -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/`)
})
})
}
......@@ -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 {
......
......@@ -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() {
......
......@@ -89,7 +89,7 @@ export function functionRenderer(part, innerExpr) {
var paramType = part.def.params[index];
if (paramType.type === 'time') {
if (value === 'auto') {
value = '$interval';
value = '$__interval';
}
}
if (paramType.quote === 'single') {
......
......@@ -191,6 +191,13 @@ class MetricsPanelCtrl extends PanelCtrl {
return this.$q.when([]);
}
// make shallow copy of scoped vars,
// and add built in variables interval and interval_ms
var scopedVars = Object.assign({}, this.panel.scopedVars, {
"__interval": {text: this.interval, value: this.interval},
"__interval_ms": {text: this.intervalMs, value: this.intervalMs},
});
var metricsQuery = {
panelId: this.panel.id,
range: this.range,
......@@ -200,7 +207,7 @@ class MetricsPanelCtrl extends PanelCtrl {
targets: this.panel.targets,
format: this.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: this.resolution,
scopedVars: this.panel.scopedVars,
scopedVars: scopedVars,
cacheTimeout: this.panel.cacheTimeout
};
......
......@@ -244,4 +244,16 @@ describe('templateSrv', function() {
expect(target).to.be('Server: All, period: 13m');
});
});
describe('built in interval variables', function() {
beforeEach(function() {
initTemplateSrv([]);
});
it('should replace $__interval_ms with interval milliseconds', function() {
var target = _templateSrv.replace('10 * $__interval_ms', {"__interval_ms": {text: "100", value: "100"}});
expect(target).to.be('10 * 100');
});
});
});
......@@ -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();
......@@ -42,6 +47,7 @@ function (angular, _, kbn) {
this._index[variable.name] = variable;
}
};
this.variableInitialized = function(variable) {
......@@ -103,10 +109,10 @@ function (angular, _, kbn) {
return this.distributeVariable(value, variable.name);
}
default: {
if (typeof value === 'string') {
return value;
if (_.isArray(value)) {
return '{' + value.join(',') + '}';
}
return '{' + value.join(',') + '}';
return value;
}
}
};
......@@ -135,7 +141,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 '<span class="template-variable">' + match + '</span>';
}
return match;
......
......@@ -197,15 +197,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
target = options.targets[i];
if (target.hide) {continue;}
var queryObj = this.queryBuilder.build(target, adhocFilters);
var queryString = templateSrv.replace(target.query || '*', options.scopedVars, 'lucene');
var queryObj = this.queryBuilder.build(target, adhocFilters, queryString);
var esQuery = angular.toJson(queryObj);
var luceneQuery = target.query || '*';
luceneQuery = templateSrv.replace(luceneQuery, options.scopedVars, 'lucene');
luceneQuery = angular.toJson(luceneQuery);
// remove inner quotes
luceneQuery = luceneQuery.substr(1, luceneQuery.length - 2);
esQuery = esQuery.replace("$lucene_query", luceneQuery);
var searchType = (queryObj.size === 0 && this.esVersion < 5) ? 'count' : 'query_then_fetch';
var header = this.getQueryHeader(searchType, options.range.from, options.range.to);
......@@ -219,7 +213,6 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
return $q.when([]);
}
payload = payload.replace(/\$interval/g, options.interval);
payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf());
payload = payload.replace(/\$timeTo/g, options.range.to.valueOf());
payload = templateSrv.replace(payload, options.scopedVars);
......
......@@ -66,7 +66,7 @@ function (queryDef) {
esAgg.format = "epoch_millis";
if (esAgg.interval === 'auto') {
esAgg.interval = "$interval";
esAgg.interval = "$__interval";
}
if (settings.missing) {
......@@ -121,7 +121,7 @@ function (queryDef) {
}
};
ElasticQueryBuilder.prototype.build = function(target, adhocFilters) {
ElasticQueryBuilder.prototype.build = function(target, adhocFilters, queryString) {
// make sure query has defaults;
target.metrics = target.metrics || [{ type: 'count', id: '1' }];
target.dsType = 'elasticsearch';
......@@ -138,7 +138,7 @@ function (queryDef) {
{
"query_string": {
"analyze_wildcard": true,
"query": '$lucene_query'
"query": queryString,
}
}
]
......
......@@ -45,7 +45,7 @@ export default class InfluxDatasource {
query(options) {
var timeFilter = this.getTimeFilter(options);
var scopedVars = options.scopedVars ? _.cloneDeep(options.scopedVars) : {};
var scopedVars = options.scopedVars;
var targets = _.cloneDeep(options.targets);
var queryTargets = [];
var queryModel;
......@@ -56,8 +56,8 @@ export default class InfluxDatasource {
queryTargets.push(target);
// build query
scopedVars.interval = {value: target.interval || options.interval};
// backward compatability
scopedVars.interval = scopedVars.__interval;
queryModel = new InfluxQuery(target, this.templateSrv, scopedVars);
return queryModel.render(true);
......
......@@ -23,7 +23,7 @@ export default class InfluxQuery {
target.resultFormat = target.resultFormat || 'time_series';
target.tags = target.tags || [];
target.groupBy = target.groupBy || [
{type: 'time', params: ['$interval']},
{type: 'time', params: ['$__interval']},
{type: 'fill', params: ['null']},
];
target.select = target.select || [[
......
......@@ -12,7 +12,7 @@ describe('InfluxQuery', function() {
}, templateSrv, {});
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($__interval) fill(null)');
});
});
......@@ -24,7 +24,7 @@ describe('InfluxQuery', function() {
}, templateSrv, {});
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") FROM "5m_avg"."cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
expect(queryText).to.be('SELECT mean("value") FROM "5m_avg"."cpu" WHERE $timeFilter GROUP BY time($__interval) fill(null)');
});
});
......@@ -43,7 +43,7 @@ describe('InfluxQuery', function() {
}, templateSrv, {});
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($__interval) fill(null)');
});
});
......@@ -58,7 +58,7 @@ describe('InfluxQuery', function() {
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server\\\\1\' AND $timeFilter'
+ ' GROUP BY time($interval)');
+ ' GROUP BY time($__interval)');
});
it('should switch regex operator with tag value is regex', function() {
......@@ -69,7 +69,7 @@ describe('InfluxQuery', function() {
}, templateSrv, {});
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($interval)');
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($__interval)');
});
});
......@@ -83,7 +83,7 @@ describe('InfluxQuery', function() {
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' +
'$timeFilter GROUP BY time($interval)');
'$timeFilter GROUP BY time($__interval)');
});
});
......@@ -97,7 +97,7 @@ describe('InfluxQuery', function() {
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' +
'$timeFilter GROUP BY time($interval)');
'$timeFilter GROUP BY time($__interval)');
});
});
......@@ -124,7 +124,7 @@ describe('InfluxQuery', function() {
var queryText = query.render();
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter ' +
'GROUP BY time($interval), "host"');
'GROUP BY time($__interval), "host"');
});
});
......@@ -148,7 +148,7 @@ describe('InfluxQuery', function() {
groupBy: [{type: 'time'}, {type: 'fill', params: ['0']}],
}, templateSrv, {});
var queryText = query.render();
expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(0)');
expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter GROUP BY time($__interval) fill(0)');
});
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment