Loki: Implement step editor (#69648)

* Loki: Implement step editor

* Update to keep value

* Remove console.log

* Remove white space

* Update public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx

Co-authored-by: Matias Chomicki <matyax@gmail.com>

* Import trim

* Update using of step in split queries

* Add tests

* Add tests

* Remove step interpolation

---------

Co-authored-by: Matias Chomicki <matyax@gmail.com>
This commit is contained in:
Ivana Huckova
2023-06-16 19:08:29 +03:00
committed by GitHub
co-authored by Matias Chomicki
parent 66d2214c3b
commit 82c125d450
8 changed files with 359 additions and 114 deletions
+4 -1
View File
@@ -126,7 +126,10 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) {
interval := query.Interval
timeRange := query.TimeRange.To.Sub(query.TimeRange.From)
step := calculateStep(interval, timeRange, resolution)
step, err := calculateStep(interval, timeRange, resolution, model.Step)
if err != nil {
return nil, err
}
expr := interpolateVariables(model.Expr, interval, timeRange)
+15 -6
View File
@@ -3,6 +3,8 @@ package loki
import (
"math"
"time"
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
)
// round the duration to the nearest millisecond larger-or-equal-to the duration
@@ -20,12 +22,19 @@ func durationMax(d1 time.Duration, d2 time.Duration) time.Duration {
}
}
func calculateStep(baseInterval time.Duration, timeRange time.Duration, resolution int64) time.Duration {
step := time.Duration(baseInterval.Nanoseconds() * resolution)
func calculateStep(interval time.Duration, timeRange time.Duration, resolution int64, queryStep *string) (time.Duration, error) {
// If we don't have step from query we calculate it from interval, time range and resolution
if queryStep == nil || *queryStep == "" {
step := time.Duration(interval.Nanoseconds() * resolution)
safeStep := timeRange / 11000
chosenStep := durationMax(step, safeStep)
return ceilMs(chosenStep), nil
}
safeStep := timeRange / 11000
step, err := intervalv2.ParseIntervalStringToTimeDuration(*queryStep)
if err != nil {
return step, err
}
chosenStep := durationMax(step, safeStep)
return ceilMs(chosenStep)
return time.Duration(step.Nanoseconds() * resolution), nil
}
+96 -35
View File
@@ -8,50 +8,111 @@ import (
)
func TestLokiStep(t *testing.T) {
t.Run("base case", func(t *testing.T) {
require.Equal(t, time.Second*14, calculateStep(time.Second*7, time.Second, 2))
})
t.Run("with query step", func(t *testing.T) {
t.Run("valid step in go duration format", func(t *testing.T) {
queryStep := "1m"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Minute*2, step)
})
t.Run("step should be at least 1 millisecond", func(t *testing.T) {
require.Equal(t, time.Millisecond*1, calculateStep(time.Microsecond*500, time.Second, 1))
})
t.Run("valid step as number", func(t *testing.T) {
queryStep := "30"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Minute*1, step)
})
t.Run("safeInterval should happen", func(t *testing.T) {
// safeInterval
require.Equal(t, time.Second*3, calculateStep(time.Second*2, time.Second*33000, 1))
})
// calculateStep parses a duration with support for unit that Grafana uses (e.g 1d)
t.Run("step with 1d", func(t *testing.T) {
queryStep := "1d"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Hour*48, step)
})
t.Run("step should math.Ceil in milliseconds", func(t *testing.T) {
require.Equal(t, time.Millisecond*2, calculateStep(time.Microsecond*1234, time.Second*1, 1))
})
// calculateStep parses a duration with support for unit that Grafana uses (e.g 1w)
t.Run("step with 1w", func(t *testing.T) {
queryStep := "1w"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.NoError(t, err)
require.Equal(t, time.Hour*336, step)
})
t.Run("step should math.Ceil in milliseconds, even if safeInterval happens", func(t *testing.T) {
require.Equal(t, time.Millisecond*3001, calculateStep(time.Second*2, time.Second*33001, 1))
// Returns error
t.Run("invalid step", func(t *testing.T) {
queryStep := "invalid"
step, err := calculateStep(time.Second*7, time.Second, 2, &queryStep)
require.Error(t, err)
require.Equal(t, time.Duration(0), step)
})
})
t.Run("with no query step", func(t *testing.T) {
t.Run("base case", func(t *testing.T) {
step, err := calculateStep(time.Second*7, time.Second, 2, nil)
require.NoError(t, err)
require.Equal(t, time.Second*14, step)
})
t.Run("resolution should happen", func(t *testing.T) {
require.Equal(t, time.Second*5, calculateStep(time.Second*1, time.Second*100, 5))
})
t.Run("step should be at least 1 millisecond", func(t *testing.T) {
step, err := calculateStep(time.Microsecond*500, time.Second, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Millisecond*1, step)
})
t.Run("safeInterval check should happen after resolution is used", func(t *testing.T) {
require.Equal(t, time.Second*4, calculateStep(time.Second*2, time.Second*33000, 2))
})
t.Run("safeInterval should happen", func(t *testing.T) {
// safeInterval
step, err := calculateStep(time.Second*2, time.Second*33000, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Second*3, step)
})
t.Run("survive interval=0", func(t *testing.T) {
// interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*0, time.Second*22000, 1))
})
t.Run("step should math.Ceil in milliseconds", func(t *testing.T) {
step, err := calculateStep(time.Microsecond*1234, time.Second*1, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Millisecond*2, step)
})
t.Run("survive resolution=0", func(t *testing.T) {
// resolution=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*1, time.Second*22000, 0))
})
t.Run("step should math.Ceil in milliseconds, even if safeInterval happens", func(t *testing.T) {
step, err := calculateStep(time.Second*2, time.Second*33001, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Millisecond*3001, step)
})
t.Run("survive interval=0 and resolution=0", func(t *testing.T) {
// resolution=0 and interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*0, time.Second*22000, 0))
t.Run("resolution should happen", func(t *testing.T) {
step, err := calculateStep(time.Second*1, time.Second*100, 5, nil)
require.NoError(t, err)
require.Equal(t, time.Second*5, step)
})
t.Run("safeInterval check should happen after resolution is used", func(t *testing.T) {
step, err := calculateStep(time.Second*2, time.Second*33000, 2, nil)
require.NoError(t, err)
require.Equal(t, time.Second*4, step)
})
t.Run("survive interval=0", func(t *testing.T) {
// interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
step, err := calculateStep(time.Second*0, time.Second*22000, 1, nil)
require.NoError(t, err)
require.Equal(t, time.Second*2, step)
})
t.Run("survive resolution=0", func(t *testing.T) {
// resolution=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
step, err := calculateStep(time.Second*1, time.Second*22000, 0, nil)
require.NoError(t, err)
require.Equal(t, time.Second*2, step)
})
t.Run("survive interval=0 and resolution=0", func(t *testing.T) {
// resolution=0 and interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
step, err := calculateStep(time.Second*0, time.Second*22000, 0, nil)
require.NoError(t, err)
require.Equal(t, time.Second*2, step)
})
})
}