diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index 360ba0aa490..a1e9e437b66 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -3,6 +3,7 @@ package state import ( "bytes" "fmt" + "math" "strconv" "strings" "sync" @@ -110,16 +111,13 @@ func (c *cache) expandRuleLabelsAndAnnotations(alertRule *ngModels.AlertRule, la // and labels template. type templateCaptureValue struct { Labels map[string]string - Value *float64 + Value float64 } // String implements the Stringer interface to print the value of each RefID // in the template via {{ $values.A }} rather than {{ $values.A.Value }}. func (v templateCaptureValue) String() string { - if v.Value != nil { - return strconv.FormatFloat(*v.Value, 'f', -1, 64) - } - return "null" + return strconv.FormatFloat(v.Value, 'f', -1, 64) } func expandTemplate(name, text string, labels map[string]string, alertInstance eval.Result) (result string, resultErr error) { @@ -148,23 +146,31 @@ func expandTemplate(name, text string, labels map[string]string, alertInstance e Value string }{ Labels: labels, - Values: func() map[string]templateCaptureValue { - m := make(map[string]templateCaptureValue) - for k, v := range alertInstance.Values { - m[k] = templateCaptureValue{ - Labels: v.Labels, - Value: v.Value, - } - } - return m - }(), - Value: alertInstance.EvaluationString, + Values: newTemplateCaptureValues(alertInstance.Values), + Value: alertInstance.EvaluationString, }); err != nil { return "", fmt.Errorf("error executing template %v: %s", name, err.Error()) } return buffer.String(), nil } +func newTemplateCaptureValues(values map[string]eval.NumberValueCapture) map[string]templateCaptureValue { + m := make(map[string]templateCaptureValue) + for k, v := range values { + var f float64 + if v.Value != nil { + f = *v.Value + } else { + f = math.NaN() + } + m[k] = templateCaptureValue{ + Labels: v.Labels, + Value: f, + } + } + return m +} + func (c *cache) set(entry *State) { c.mtxStates.Lock() defer c.mtxStates.Unlock() diff --git a/pkg/services/ngalert/state/cache_test.go b/pkg/services/ngalert/state/cache_test.go index 946a2c6352b..9f553105ced 100644 --- a/pkg/services/ngalert/state/cache_test.go +++ b/pkg/services/ngalert/state/cache_test.go @@ -17,16 +17,16 @@ func TestTemplateCaptureValueStringer(t *testing.T) { value templateCaptureValue expected string }{{ - name: "nil value returns null", - value: templateCaptureValue{Value: nil}, - expected: "null", + name: "0 is returned as integer value", + value: templateCaptureValue{Value: 0}, + expected: "0", }, { name: "1.0 is returned as integer value", - value: templateCaptureValue{Value: ptr.Float64(1.0)}, + value: templateCaptureValue{Value: 1.0}, expected: "1", }, { name: "1.1 is returned as decimal value", - value: templateCaptureValue{Value: ptr.Float64(1.1)}, + value: templateCaptureValue{Value: 1.1}, expected: "1.1", }} @@ -46,12 +46,12 @@ func TestExpandTemplate(t *testing.T) { expected string expectedError error }{{ - name: "instance labels are expanded into $labels", + name: "labels are expanded into $labels", text: "{{ $labels.instance }} is down", labels: data.Labels{"instance": "foo"}, expected: "foo is down", }, { - name: "missing instance label returns error", + name: "missing label in $labels returns error", text: "{{ $labels.instance }} is down", labels: data.Labels{}, expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$labels.instance>: map has no entry for key \"instance\""), @@ -63,11 +63,24 @@ func TestExpandTemplate(t *testing.T) { "A": { Var: "A", Labels: data.Labels{"instance": "foo"}, - Value: ptr.Float64(10), + Value: ptr.Float64(1), }, }, }, - expected: "foo has value 10", + expected: "foo has value 1", + }, { + name: "values can be passed to template functions such as printf", + text: "{{ $values.A.Labels.instance }} has value {{ $values.A.Value | printf \"%.1f\" }}", + alertInstance: eval.Result{ + Values: map[string]eval.NumberValueCapture{ + "A": { + Var: "A", + Labels: data.Labels{"instance": "foo"}, + Value: ptr.Float64(1.1), + }, + }, + }, + expected: "foo has value 1.1", }, { name: "missing label in $values returns error", text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}", @@ -76,13 +89,26 @@ func TestExpandTemplate(t *testing.T) { "A": { Var: "A", Labels: data.Labels{}, - Value: ptr.Float64(10), + Value: ptr.Float64(1), }, }, }, expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$values.A.Labels.instance>: map has no entry for key \"instance\""), }, { - name: "value string is expanded into $value", + name: "missing value in $values is returned as NaN", + text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}", + alertInstance: eval.Result{ + Values: map[string]eval.NumberValueCapture{ + "A": { + Var: "A", + Labels: data.Labels{"instance": "foo"}, + Value: nil, + }, + }, + }, + expected: "foo has value NaN", + }, { + name: "assert value string is expanded into $value", text: "{{ $value }}", alertInstance: eval.Result{ EvaluationString: "[ var='A' labels={instance=foo} value=10 ]",