diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa818ba1d..17f92f05f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ stream Also allows for grouping by measurement. The breaking change is that the group ID format has changed to allow for the measurement name. - [#759](https://github.com/influxdata/kapacitor/pull/759): Add mechanism for token based subscription auth. +- [#745](https://github.com/influxdata/kapacitor/pull/745): Add if function for tick script, for example: `if("value" > 6, 1, 2)`. ### Bugfixes diff --git a/tick/stateful/functions.go b/tick/stateful/functions.go index 7de86958bc..26d5587d3d 100644 --- a/tick/stateful/functions.go +++ b/tick/stateful/functions.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "reflect" "strconv" "time" @@ -87,6 +88,9 @@ func init() { // Humanize functions statelessFuncs["humanBytes"] = &humanBytes{} + + // Conditionals + statelessFuncs["if"] = &ifFunc{} } // Return set of built-in Funcs @@ -624,3 +628,33 @@ func (*humanBytes) Call(args ...interface{}) (v interface{}, err error) { } return } + +type ifFunc struct { +} + +func (*ifFunc) Reset() { + +} + +func (*ifFunc) Call(args ...interface{}) (interface{}, error) { + if len(args) != 3 { + return nil, errors.New("if expects exactly three arguments") + } + + var condition bool + var isBool bool + + if condition, isBool = args[0].(bool); !isBool { + return nil, fmt.Errorf("first argument to if must be a condition with type of bool - got %T", args[0]) + } + + if reflect.TypeOf(args[1]) != reflect.TypeOf(args[2]) { + return nil, fmt.Errorf("Multiple return types isn't supported - second argument is %T and third argument is %T", args[1], args[2]) + } + + if condition { + return args[1], nil + } + + return args[2], nil +} diff --git a/tick/stateful/functions_test.go b/tick/stateful/functions_test.go index 28b8950d60..4d0396a598 100644 --- a/tick/stateful/functions_test.go +++ b/tick/stateful/functions_test.go @@ -385,6 +385,7 @@ func Test_String(t *testing.T) { t.Errorf("unexpected error from call to string() got %s exp %s", got, expErr) } } + func Test_Duration(t *testing.T) { f := &duration{} @@ -485,3 +486,63 @@ func Test_Duration(t *testing.T) { } } } + +func Test_If(t *testing.T) { + f := &ifFunc{} + + testCases := []struct { + args []interface{} + exp interface{} + err error + }{ + // Error cases + { + args: []interface{}{true}, + err: errors.New("if expects exactly three arguments"), + }, + { + args: []interface{}{true, 6}, + err: errors.New("if expects exactly three arguments"), + }, + { + args: []interface{}{12, 6, false}, + err: errors.New("first argument to if must be a condition with type of bool - got int"), + }, + + // Simple if + { + args: []interface{}{true, 1, 2}, + exp: 1, + }, + { + args: []interface{}{false, 1, 2}, + exp: 2, + }, + + // multiple types + { + args: []interface{}{false, 1, "1"}, + err: errors.New("Multiple return types isn't supported - second argument is int and third argument is string"), + }, + } + + for _, tc := range testCases { + result, err := f.Call(tc.args...) + if tc.err != nil { + if err == nil { + t.Errorf("expected error from if(%v)\ngot: nil\nexp: exp %s", tc.args, tc.err) + } else if got, exp := err.Error(), tc.err.Error(); got != exp { + t.Errorf("unexpected error from if(%v)\ngot: %s\nexp: %s", tc.args, got, exp) + } + continue + } else if err != nil { + t.Errorf("unexpected error from if(%v) %s", tc.args, err) + continue + } + + if result != tc.exp { + t.Errorf("unexpected result from if(%v)\ngot: %+v\nexp: %+v", tc.args, result, tc.exp) + } + + } +}