From a0bb01103eebfa7e0d94201db5536b295c4f4529 Mon Sep 17 00:00:00 2001 From: Bernard Duggan Date: Thu, 30 May 2019 17:58:29 +1000 Subject: [PATCH] MySQL/Postgres/MSSQL: Add parsing for day, weeks and year intervals in macros (#13086) closes #11431 --- pkg/components/gtime/gtime.go | 28 ++++++++++++++++++++++++ pkg/components/gtime/gtime_test.go | 34 ++++++++++++++++++++++++++++++ pkg/tsdb/mssql/macros.go | 5 +++-- pkg/tsdb/mysql/macros.go | 6 +++--- pkg/tsdb/postgres/macros.go | 5 +++-- 5 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 pkg/components/gtime/gtime.go create mode 100644 pkg/components/gtime/gtime_test.go diff --git a/pkg/components/gtime/gtime.go b/pkg/components/gtime/gtime.go new file mode 100644 index 00000000000..e3e4e449f66 --- /dev/null +++ b/pkg/components/gtime/gtime.go @@ -0,0 +1,28 @@ +package gtime + +import ( + "regexp" + "strconv" + "time" +) + +// ParseInterval parses and interval with support for all units that Grafana uses. +func ParseInterval(interval string) (time.Duration, error) { + re := regexp.MustCompile(`(\d+)([wdy])`) + result := re.FindSubmatch([]byte(interval)) + + if len(result) == 3 { + num, _ := strconv.Atoi(string(result[1])) + period := string(result[2]) + + if period == `d` { + return time.Hour * 24 * time.Duration(num), nil + } else if period == `w` { + return time.Hour * 24 * 7 * time.Duration(num), nil + } else { + return time.Hour * 24 * 7 * 365 * time.Duration(num), nil + } + } else { + return time.ParseDuration(interval) + } +} diff --git a/pkg/components/gtime/gtime_test.go b/pkg/components/gtime/gtime_test.go new file mode 100644 index 00000000000..e683184023f --- /dev/null +++ b/pkg/components/gtime/gtime_test.go @@ -0,0 +1,34 @@ +package gtime + +import ( + "errors" + "fmt" + "testing" + "time" +) + +func TestParseInterval(t *testing.T) { + tcs := []struct { + interval string + duration time.Duration + err error + }{ + {interval: "1d", duration: time.Hour * 24}, + {interval: "1w", duration: time.Hour * 24 * 7}, + {interval: "1y", duration: time.Hour * 24 * 7 * 365}, + {interval: "1M", err: errors.New("time: unknown unit M in duration 1M")}, + {interval: "invalid-duration", err: errors.New("time: invalid duration invalid-duration")}, + } + + for i, tc := range tcs { + t.Run(fmt.Sprintf("testcase %d", i), func(t *testing.T) { + res, err := ParseInterval(tc.interval) + if err != nil && err.Error() != tc.err.Error() { + t.Fatalf("expected '%v' got '%v'", tc.err, err) + } + if res != tc.duration { + t.Errorf("expected %v got %v", tc.duration, res) + } + }) + } +} diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index ec3f103a4ef..c0794863efe 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/grafana/grafana/pkg/components/gtime" "github.com/grafana/grafana/pkg/tsdb" ) @@ -74,7 +75,7 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) } - interval, err := time.ParseDuration(strings.Trim(args[1], `'"`)) + interval, err := gtime.ParseInterval(strings.Trim(args[1], `'"`)) if err != nil { return "", fmt.Errorf("error parsing interval %v", args[1]) } @@ -109,7 +110,7 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) } - interval, err := time.ParseDuration(strings.Trim(args[1], `'`)) + interval, err := gtime.ParseInterval(strings.Trim(args[1], `'`)) if err != nil { return "", fmt.Errorf("error parsing interval %v", args[1]) } diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index bbd928b0563..8cd83d2f999 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -4,8 +4,8 @@ import ( "fmt" "regexp" "strings" - "time" + "github.com/grafana/grafana/pkg/components/gtime" "github.com/grafana/grafana/pkg/tsdb" ) @@ -69,7 +69,7 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval", name) } - interval, err := time.ParseDuration(strings.Trim(args[1], `'"`)) + interval, err := gtime.ParseInterval(strings.Trim(args[1], `'"`)) if err != nil { return "", fmt.Errorf("error parsing interval %v", args[1]) } @@ -104,7 +104,7 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) } - interval, err := time.ParseDuration(strings.Trim(args[1], `'`)) + interval, err := gtime.ParseInterval(strings.Trim(args[1], `'`)) if err != nil { return "", fmt.Errorf("error parsing interval %v", args[1]) } diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 2efba13d31a..f7a194e63cd 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/grafana/grafana/pkg/components/gtime" "github.com/grafana/grafana/pkg/tsdb" ) @@ -95,7 +96,7 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) } - interval, err := time.ParseDuration(strings.Trim(args[1], `'`)) + interval, err := gtime.ParseInterval(strings.Trim(args[1], `'`)) if err != nil { return "", fmt.Errorf("error parsing interval %v", args[1]) } @@ -139,7 +140,7 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) } - interval, err := time.ParseDuration(strings.Trim(args[1], `'`)) + interval, err := gtime.ParseInterval(strings.Trim(args[1], `'`)) if err != nil { return "", fmt.Errorf("error parsing interval %v", args[1]) }