diff --git a/pkg/tsdb/prometheus/time_series_query.go b/pkg/tsdb/prometheus/time_series_query.go index 2b95756db07..64cd8d77847 100644 --- a/pkg/tsdb/prometheus/time_series_query.go +++ b/pkg/tsdb/prometheus/time_series_query.go @@ -18,7 +18,7 @@ import ( "github.com/prometheus/common/model" ) -// Internal interval and range variables +//Internal interval and range variables const ( varInterval = "$__interval" varIntervalMs = "$__interval_ms" @@ -28,6 +28,17 @@ const ( varRateInterval = "$__rate_interval" ) +//Internal interval and range variables with {} syntax +//Repetitive code, we should have functionality to unify these +const ( + varIntervalAlt = "${__interval}" + varIntervalMsAlt = "${__interval_ms}" + varRangeAlt = "${__range}" + varRangeSAlt = "${__range_s}" + varRangeMsAlt = "${__range_ms}" + varRateIntervalAlt = "${__rate_interval}" +) + type TimeSeriesQueryType string const ( @@ -153,6 +164,12 @@ func (s *Service) parseTimeSeriesQuery(queryContext *backend.QueryDataRequest, d if queryInterval == varInterval || queryInterval == varIntervalMs || queryInterval == varRateInterval { queryInterval = "" } + //If we are using variable or interval/step with {} syntax, we will replace it with calculated interval + //Repetitive code, we should have functionality to unify these + if queryInterval == varIntervalAlt || queryInterval == varIntervalMsAlt || queryInterval == varRateIntervalAlt { + queryInterval = "" + } + minInterval, err := intervalv2.GetIntervalFrom(dsInfo.TimeInterval, queryInterval, model.IntervalMS, 15*time.Second) if err != nil { return nil, err @@ -166,7 +183,7 @@ func (s *Service) parseTimeSeriesQuery(queryContext *backend.QueryDataRequest, d adjustedInterval = calculatedInterval.Value } - if queryInterval == varRateInterval { + if queryInterval == varRateInterval || queryInterval == varRateIntervalAlt { // Rate interval is final and is not affected by resolution interval = calculateRateInterval(adjustedInterval, dsInfo.TimeInterval, s.intervalCalculator) } else { @@ -264,6 +281,14 @@ func interpolateVariables(expr string, interval time.Duration, timeRange time.Du expr = strings.ReplaceAll(expr, varRangeS, strconv.FormatInt(rangeSRounded, 10)) expr = strings.ReplaceAll(expr, varRange, strconv.FormatInt(rangeSRounded, 10)+"s") expr = strings.ReplaceAll(expr, varRateInterval, intervalv2.FormatDuration(calculateRateInterval(interval, timeInterval, intervalCalculator))) + + // Repetitive code, we should have functionality to unify these + expr = strings.ReplaceAll(expr, varIntervalMsAlt, strconv.FormatInt(int64(interval/time.Millisecond), 10)) + expr = strings.ReplaceAll(expr, varIntervalAlt, intervalv2.FormatDuration(interval)) + expr = strings.ReplaceAll(expr, varRangeMsAlt, strconv.FormatInt(rangeMs, 10)) + expr = strings.ReplaceAll(expr, varRangeSAlt, strconv.FormatInt(rangeSRounded, 10)) + expr = strings.ReplaceAll(expr, varRangeAlt, strconv.FormatInt(rangeSRounded, 10)+"s") + expr = strings.ReplaceAll(expr, varRateIntervalAlt, intervalv2.FormatDuration(calculateRateInterval(interval, timeInterval, intervalCalculator))) return expr } diff --git a/pkg/tsdb/prometheus/time_series_query_test.go b/pkg/tsdb/prometheus/time_series_query_test.go index 10992a03841..36aa40e045c 100644 --- a/pkg/tsdb/prometheus/time_series_query_test.go +++ b/pkg/tsdb/prometheus/time_series_query_test.go @@ -205,6 +205,25 @@ func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) { require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", models[0].Expr) }) + t.Run("parsing query model with ${__interval} variable", func(t *testing.T) { + timeRange := backend.TimeRange{ + From: now, + To: now.Add(48 * time.Hour), + } + + query := queryContext(`{ + "expr": "rate(ALERTS{job=\"test\" [${__interval}]})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + }`, timeRange) + + dsInfo := &DatasourceInfo{} + models, err := service.parseTimeSeriesQuery(query, dsInfo) + require.NoError(t, err) + require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", models[0].Expr) + }) + t.Run("parsing query model with $__interval_ms variable", func(t *testing.T) { timeRange := backend.TimeRange{ From: now, @@ -243,6 +262,25 @@ func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) { require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", models[0].Expr) }) + t.Run("parsing query model with ${__interval_ms} and ${__interval} variable", func(t *testing.T) { + timeRange := backend.TimeRange{ + From: now, + To: now.Add(48 * time.Hour), + } + + query := queryContext(`{ + "expr": "rate(ALERTS{job=\"test\" [${__interval_ms}]}) + rate(ALERTS{job=\"test\" [${__interval}]})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + }`, timeRange) + + dsInfo := &DatasourceInfo{} + models, err := service.parseTimeSeriesQuery(query, dsInfo) + require.NoError(t, err) + require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", models[0].Expr) + }) + t.Run("parsing query model with $__range variable", func(t *testing.T) { timeRange := backend.TimeRange{ From: now, @@ -281,6 +319,25 @@ func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) { require.Equal(t, "rate(ALERTS{job=\"test\" [172800]})", models[0].Expr) }) + t.Run("parsing query model with ${__range_s} variable", func(t *testing.T) { + timeRange := backend.TimeRange{ + From: now, + To: now.Add(48 * time.Hour), + } + + query := queryContext(`{ + "expr": "rate(ALERTS{job=\"test\" [${__range_s}s]})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + }`, timeRange) + + dsInfo := &DatasourceInfo{} + models, err := service.parseTimeSeriesQuery(query, dsInfo) + require.NoError(t, err) + require.Equal(t, "rate(ALERTS{job=\"test\" [172800s]})", models[0].Expr) + }) + t.Run("parsing query model with $__range_s variable below 0.5s", func(t *testing.T) { timeRange := backend.TimeRange{ From: now,