mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
This commit changes the state package so that errors encountered while expanding templates for custom labels and annotations are returned from the function. This is not used at present, but will be used in the future as we look at how to offer better feedback to users who don't have access to logs, for example our customers who use Hosted Grafana.
450 lines
14 KiB
Go
450 lines
14 KiB
Go
package template
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
func TestLabelsString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
labels Labels
|
|
expected string
|
|
}{{
|
|
name: "single label has no commas",
|
|
labels: Labels{"foo": "bar"},
|
|
expected: "foo=bar",
|
|
}, {
|
|
name: "labels are sorted in increasing order",
|
|
labels: Labels{"foo": "bar", "bar": "baz"},
|
|
expected: "bar=baz, foo=bar",
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
assert.Equal(t, test.expected, test.labels.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValueString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value Value
|
|
expected string
|
|
}{{
|
|
name: "0 is returned as integer value",
|
|
value: Value{Value: 0},
|
|
expected: "0",
|
|
}, {
|
|
name: "1.0 is returned as integer value",
|
|
value: Value{Value: 1.0},
|
|
expected: "1",
|
|
}, {
|
|
name: "1.1 is returned as decimal value",
|
|
value: Value{Value: 1.1},
|
|
expected: "1.1",
|
|
}, {
|
|
name: "1.1 is returned as decimal value, no labels",
|
|
value: Value{Labels: map[string]string{"foo": "bar"}, Value: 1.1},
|
|
expected: "1.1",
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
assert.Equal(t, test.expected, test.value.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpandError(t *testing.T) {
|
|
err := ExpandError{Tmpl: "{{", Err: errors.New("unexpected {{")}
|
|
assert.Equal(t, "failed to expand template '{{': unexpected {{", err.Error())
|
|
}
|
|
|
|
func TestExpandTemplate(t *testing.T) {
|
|
pathPrefix := "/path/prefix"
|
|
externalURL, err := url.Parse("http://localhost" + pathPrefix)
|
|
assert.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
text string
|
|
alertInstance eval.Result
|
|
labels data.Labels
|
|
expected string
|
|
expectedError error
|
|
}{{
|
|
name: "labels are expanded into $labels",
|
|
text: "{{ $labels.instance }} is down",
|
|
labels: data.Labels{"instance": "foo"},
|
|
expected: "foo is down",
|
|
}, {
|
|
name: "missing label in $labels returns [no value]",
|
|
text: "{{ $labels.instance }} is down",
|
|
labels: data.Labels{},
|
|
expected: "[no value] is down",
|
|
}, {
|
|
name: "values are expanded into $values",
|
|
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: util.Pointer(1.0),
|
|
},
|
|
},
|
|
},
|
|
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: util.Pointer(1.1),
|
|
},
|
|
},
|
|
},
|
|
expected: "foo has value 1.1",
|
|
}, {
|
|
name: "missing label in $values returns [no value]",
|
|
text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
|
|
alertInstance: eval.Result{
|
|
Values: map[string]eval.NumberValueCapture{
|
|
"A": {
|
|
Var: "A",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(1.0),
|
|
},
|
|
},
|
|
},
|
|
expected: "[no value] has value 1",
|
|
}, {
|
|
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 ]",
|
|
},
|
|
expected: "[ var='A' labels={instance=foo} value=10 ]",
|
|
}, {
|
|
name: "float64 is humanized correctly",
|
|
text: "{{ humanize $value }}",
|
|
alertInstance: eval.Result{
|
|
EvaluationString: "1234567.0",
|
|
},
|
|
expected: "1.235M",
|
|
}, {
|
|
name: "int is humanized correctly",
|
|
text: "{{ humanize $value }}",
|
|
alertInstance: eval.Result{
|
|
EvaluationString: "1234567",
|
|
},
|
|
expected: "1.235M",
|
|
}, {
|
|
name: "humanize string with error",
|
|
text: `{{ humanize $value }}`,
|
|
alertInstance: eval.Result{
|
|
EvaluationString: "invalid",
|
|
},
|
|
expectedError: errors.New(`failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}{{ humanize $value }}': error executing template __alert_test: template: __alert_test:1:79: executing "__alert_test" at <humanize $value>: error calling humanize: strconv.ParseFloat: parsing "invalid": invalid syntax`),
|
|
}, {
|
|
name: "humanize1024 float64",
|
|
text: "{{ range $key, $val := $values }}{{ humanize1024 .Value }}:{{ end }}",
|
|
alertInstance: eval.Result{
|
|
Values: map[string]eval.NumberValueCapture{
|
|
"A": {
|
|
Var: "A",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(0.0),
|
|
},
|
|
"B": {
|
|
Var: "B",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(1.0),
|
|
},
|
|
"C": {
|
|
Var: "C",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(1048576.0),
|
|
},
|
|
"D": {
|
|
Var: "D",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(.12),
|
|
},
|
|
},
|
|
},
|
|
expected: "0:1:1Mi:0.12:",
|
|
}, {
|
|
name: "humanize1024 string with error",
|
|
text: "{{ humanize1024 $value }}",
|
|
alertInstance: eval.Result{
|
|
EvaluationString: "invalid",
|
|
},
|
|
expectedError: errors.New(`failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}{{ humanize1024 $value }}': error executing template __alert_test: template: __alert_test:1:79: executing "__alert_test" at <humanize1024 $value>: error calling humanize1024: strconv.ParseFloat: parsing "invalid": invalid syntax`),
|
|
}, {
|
|
name: "humanizeDuration - seconds - float64",
|
|
text: "{{ range $key, $val := $values }}{{ humanizeDuration .Value }}:{{ end }}",
|
|
alertInstance: eval.Result{
|
|
Values: map[string]eval.NumberValueCapture{
|
|
"A": {
|
|
Var: "A",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(0.0),
|
|
},
|
|
"B": {
|
|
Var: "B",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(1.0),
|
|
},
|
|
"C": {
|
|
Var: "C",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(60.0),
|
|
},
|
|
"D": {
|
|
Var: "D",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(3600.0),
|
|
},
|
|
"E": {
|
|
Var: "E",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(86400.0),
|
|
},
|
|
"F": {
|
|
Var: "F",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(86400.0 + 3600.0),
|
|
},
|
|
"G": {
|
|
Var: "G",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(-(86400*2 + 3600*3 + 60*4 + 5.0)),
|
|
},
|
|
"H": {
|
|
Var: "H",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(899.99),
|
|
},
|
|
},
|
|
},
|
|
expected: "0s:1s:1m 0s:1h 0m 0s:1d 0h 0m 0s:1d 1h 0m 0s:-2d 3h 4m 5s:14m 59s:",
|
|
}, {
|
|
name: "humanizeDuration - string",
|
|
text: "{{ humanizeDuration $value }}",
|
|
alertInstance: eval.Result{
|
|
EvaluationString: "86400",
|
|
},
|
|
expected: "1d 0h 0m 0s",
|
|
}, {
|
|
name: "humanizeDuration - subsecond and fractional seconds - float64",
|
|
text: "{{ range $key, $val := $values }}{{ humanizeDuration .Value }}:{{ end }}",
|
|
alertInstance: eval.Result{
|
|
Values: map[string]eval.NumberValueCapture{
|
|
"A": {
|
|
Var: "A",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(.1),
|
|
},
|
|
"B": {
|
|
Var: "B",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(.0001),
|
|
},
|
|
"C": {
|
|
Var: "C",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(.12345),
|
|
},
|
|
"D": {
|
|
Var: "D",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(60.1),
|
|
},
|
|
"E": {
|
|
Var: "E",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(60.5),
|
|
},
|
|
"F": {
|
|
Var: "F",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(1.2345),
|
|
},
|
|
"G": {
|
|
Var: "G",
|
|
Labels: data.Labels{},
|
|
Value: util.Pointer(12.345),
|
|
},
|
|
},
|
|
},
|
|
expected: "100ms:100us:123.5ms:1m 0s:1m 0s:1.234s:12.35s:",
|
|
}, {
|
|
name: "humanizeDuration - subsecond - string",
|
|
text: "{{ humanizeDuration $value }}",
|
|
alertInstance: eval.Result{
|
|
EvaluationString: ".0001",
|
|
},
|
|
expected: "100us",
|
|
}, {
|
|
name: "humanizeDuration - fractional seconds - string",
|
|
text: "{{ humanizeDuration $value }}",
|
|
alertInstance: eval.Result{
|
|
EvaluationString: "1.2345",
|
|
},
|
|
expected: "1.234s",
|
|
}, {
|
|
name: "humanizeDuration - string with error",
|
|
text: `{{ humanizeDuration $value }}`,
|
|
alertInstance: eval.Result{
|
|
EvaluationString: "invalid",
|
|
},
|
|
expectedError: errors.New(`failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}{{ humanizeDuration $value }}': error executing template __alert_test: template: __alert_test:1:79: executing "__alert_test" at <humanizeDuration $value>: error calling humanizeDuration: strconv.ParseFloat: parsing "invalid": invalid syntax`),
|
|
}, {
|
|
name: "humanizePercentage - float64",
|
|
text: "{{ -0.22222 | humanizePercentage }}:{{ 0.0 | humanizePercentage }}:{{ 0.1234567 | humanizePercentage }}:{{ 1.23456 | humanizePercentage }}",
|
|
expected: "-22.22%:0%:12.35%:123.5%",
|
|
}, {
|
|
name: "humanizePercentage - string",
|
|
text: `{{ "-0.22222" | humanizePercentage }}:{{ "0.0" | humanizePercentage }}:{{ "0.1234567" | humanizePercentage }}:{{ "1.23456" | humanizePercentage }}`,
|
|
expected: "-22.22%:0%:12.35%:123.5%",
|
|
}, {
|
|
name: "humanizePercentage - string with error",
|
|
text: `{{ "invalid" | humanizePercentage }}`,
|
|
expectedError: errors.New(`failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}{{ "invalid" | humanizePercentage }}': error executing template __alert_test: template: __alert_test:1:91: executing "__alert_test" at <humanizePercentage>: error calling humanizePercentage: strconv.ParseFloat: parsing "invalid": invalid syntax`),
|
|
}, {
|
|
name: "humanizeTimestamp - float64",
|
|
text: "{{ 1435065584.128 | humanizeTimestamp }}",
|
|
expected: "2015-06-23 13:19:44.128 +0000 UTC",
|
|
}, {
|
|
name: "humanizeTimestamp - string",
|
|
text: `{{ "1435065584.128" | humanizeTimestamp }}`,
|
|
expected: "2015-06-23 13:19:44.128 +0000 UTC",
|
|
}, {
|
|
name: "title",
|
|
text: `{{ "aa bb CC" | title }}`,
|
|
expected: "Aa Bb CC",
|
|
}, {
|
|
name: "toUpper",
|
|
text: `{{ "aa bb CC" | toUpper }}`,
|
|
expected: "AA BB CC",
|
|
}, {
|
|
name: "toLower",
|
|
text: `{{ "aA bB CC" | toLower }}`,
|
|
expected: "aa bb cc",
|
|
}, {
|
|
name: "match",
|
|
text: `{{ match "a+" "aa" }} {{ match "a+" "b" }}`,
|
|
expected: "true false",
|
|
}, {
|
|
name: "regex replacement",
|
|
text: "{{ reReplaceAll \"(a)b\" \"x$1\" \"ab\" }}",
|
|
expected: "xa",
|
|
}, {
|
|
name: "pass multiple arguments to templates",
|
|
text: `{{define "x"}}{{.arg0}} {{.arg1}}{{end}}{{template "x" (args 1 "2")}}`,
|
|
expected: "1 2",
|
|
}, {
|
|
name: "pathPrefix",
|
|
text: "{{ pathPrefix }}",
|
|
expected: pathPrefix,
|
|
}, {
|
|
name: "externalURL",
|
|
text: "{{ externalURL }}",
|
|
expected: externalURL.String(),
|
|
}, {
|
|
name: "check that query, first and value don't error or panic",
|
|
text: "{{ query \"1.5\" | first | value }}",
|
|
expected: "",
|
|
}, {
|
|
name: "check that label doesn't error or panic",
|
|
text: "{{ query \"metric{instance='a'}\" | first | label \"instance\" }}",
|
|
expected: "",
|
|
}, {
|
|
name: "graphLink",
|
|
text: `{{ graphLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}`,
|
|
expected: `/explore?left={"datasource":"gdev-prometheus","queries":[{"datasource":"gdev-prometheus","expr":"up","instant":false,"range":true,"refId":"A"}],"range":{"from":"now-1h","to":"now"}}`,
|
|
}, {
|
|
name: "graphLink should escape both the expression and the datasource",
|
|
text: `{{ graphLink "{\"expr\": \"process_open_fds > 0\", \"datasource\": \"gdev prometheus\"}" }}`,
|
|
expected: `/explore?left={"datasource":"gdev+prometheus","queries":[{"datasource":"gdev+prometheus","expr":"process_open_fds+%3E+0","instant":false,"range":true,"refId":"A"}],"range":{"from":"now-1h","to":"now"}}`,
|
|
}, {
|
|
name: "check that graphLink returns an empty string when the query is not formatted correctly",
|
|
text: "{{ graphLink \"up\" }}",
|
|
expected: "",
|
|
}, {
|
|
name: "tableLink",
|
|
text: `{{ tableLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}`,
|
|
expected: `/explore?left={"datasource":"gdev-prometheus","queries":[{"datasource":"gdev-prometheus","expr":"up","instant":true,"range":false,"refId":"A"}],"range":{"from":"now-1h","to":"now"}}`,
|
|
}, {
|
|
name: "tableLink should escape both the expression and the datasource",
|
|
text: `{{ tableLink "{\"expr\": \"process_open_fds > 0\", \"datasource\": \"gdev prometheus\"}" }}`,
|
|
expected: `/explore?left={"datasource":"gdev+prometheus","queries":[{"datasource":"gdev+prometheus","expr":"process_open_fds+%3E+0","instant":true,"range":false,"refId":"A"}],"range":{"from":"now-1h","to":"now"}}`,
|
|
}, {
|
|
name: "check that tableLink returns an empty string when the query is not formatted correctly",
|
|
text: "{{ tableLink \"up\" }}",
|
|
expected: "",
|
|
}, {
|
|
name: "check that sortByLabel doesn't error or panic",
|
|
text: "{{ query \"metric{__value__='a'}\" | sortByLabel }}",
|
|
expected: "",
|
|
}, {
|
|
name: "check that strvalue returns an empty string (for now)",
|
|
text: "{{ $values.A | strvalue }}",
|
|
alertInstance: eval.Result{
|
|
Values: map[string]eval.NumberValueCapture{
|
|
"A": {
|
|
Var: "A",
|
|
Labels: data.Labels{"__value__": "foo"},
|
|
},
|
|
},
|
|
},
|
|
expected: "",
|
|
}, {
|
|
name: "check that safeHtml doesn't error or panic",
|
|
text: "{{ \"<b>\" | safeHtml }}",
|
|
expected: "<b>",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
v, err := Expand(context.Background(), "test", c.text, NewData(c.labels, c.alertInstance), externalURL, c.alertInstance.EvaluatedAt)
|
|
if c.expectedError != nil {
|
|
require.NotNil(t, err)
|
|
require.EqualError(t, c.expectedError, err.Error())
|
|
} else {
|
|
require.Nil(t, c.expectedError)
|
|
}
|
|
require.Equal(t, c.expected, v)
|
|
})
|
|
}
|
|
}
|