mirror of https://github.com/grafana/grafana
Alerting template functions (#39261)
* Alerting: (wip) add template funcs * Alerting: (wip) numeric template functions * Alerting: (wip) template functions * Test for the "args" function * Alerting: (wip) Documentation for template functions * Alerting: template functions - refactor * code review changes * disable linter error * Use Prometheus implementation of TemplateExpander * Update docs/sources/alerting/unified-alerting/alerting-rules/create-grafana-managed-rule.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * change templateCaptureValue to support using template functions * Update pkg/services/ngalert/state/template.go Co-authored-by: gotjosh <josue.abreu@gmail.com> * Test and documentation added for reReplaceAll template function * complete missing functions, documentation and tests * Use the alert instance's evaluation time for expanding the template * strvalue graphlink and tablelink functions * delete duplicate test * make strvalue return an empty string Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: gotjosh <josue.abreu@gmail.com>pull/39981/head
parent
3cd7b11eb4
commit
562cd9e44e
@ -1,126 +0,0 @@ |
||||
package state |
||||
|
||||
import ( |
||||
"errors" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data" |
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval" |
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
ptr "github.com/xorcare/pointer" |
||||
) |
||||
|
||||
func TestTemplateCaptureValueStringer(t *testing.T) { |
||||
cases := []struct { |
||||
name string |
||||
value templateCaptureValue |
||||
expected string |
||||
}{{ |
||||
name: "0 is returned as integer value", |
||||
value: templateCaptureValue{Value: 0}, |
||||
expected: "0", |
||||
}, { |
||||
name: "1.0 is returned as integer value", |
||||
value: templateCaptureValue{Value: 1.0}, |
||||
expected: "1", |
||||
}, { |
||||
name: "1.1 is returned as decimal value", |
||||
value: templateCaptureValue{Value: 1.1}, |
||||
expected: "1.1", |
||||
}} |
||||
|
||||
for _, c := range cases { |
||||
t.Run(c.name, func(t *testing.T) { |
||||
assert.Equal(t, c.expected, c.value.String()) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestExpandTemplate(t *testing.T) { |
||||
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 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\""), |
||||
}, { |
||||
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: ptr.Float64(1), |
||||
}, |
||||
}, |
||||
}, |
||||
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 }}", |
||||
alertInstance: eval.Result{ |
||||
Values: map[string]eval.NumberValueCapture{ |
||||
"A": { |
||||
Var: "A", |
||||
Labels: data.Labels{}, |
||||
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: "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 ]", |
||||
}} |
||||
|
||||
for _, c := range cases { |
||||
t.Run(c.name, func(t *testing.T) { |
||||
v, err := expandTemplate("test", c.text, c.labels, c.alertInstance) |
||||
require.Equal(t, c.expectedError, err) |
||||
require.Equal(t, c.expected, v) |
||||
}) |
||||
} |
||||
} |
||||
@ -0,0 +1,89 @@ |
||||
package state |
||||
|
||||
import ( |
||||
"context" |
||||
"math" |
||||
"net/url" |
||||
"strconv" |
||||
"time" |
||||
|
||||
text_template "text/template" |
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval" |
||||
"github.com/prometheus/common/model" |
||||
"github.com/prometheus/prometheus/pkg/timestamp" |
||||
"github.com/prometheus/prometheus/promql" |
||||
"github.com/prometheus/prometheus/template" |
||||
) |
||||
|
||||
// templateCaptureValue represents each value in .Values in the annotations
|
||||
// and labels template.
|
||||
type templateCaptureValue struct { |
||||
Labels map[string]string |
||||
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 { |
||||
return strconv.FormatFloat(v.Value, 'f', -1, 64) |
||||
} |
||||
|
||||
func expandTemplate(name, text string, labels map[string]string, alertInstance eval.Result, externalURL *url.URL) (result string, resultErr error) { |
||||
name = "__alert_" + name |
||||
text = "{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}" + text |
||||
data := struct { |
||||
Labels map[string]string |
||||
Values map[string]templateCaptureValue |
||||
Value string |
||||
}{ |
||||
Labels: labels, |
||||
Values: newTemplateCaptureValues(alertInstance.Values), |
||||
Value: alertInstance.EvaluationString, |
||||
} |
||||
|
||||
expander := template.NewTemplateExpander( |
||||
context.TODO(), // This context is only used with the `query()` function - which we don't support yet.
|
||||
text, |
||||
name, |
||||
data, |
||||
model.Time(timestamp.FromTime(alertInstance.EvaluatedAt)), |
||||
func(context.Context, string, time.Time) (promql.Vector, error) { |
||||
return nil, nil |
||||
}, |
||||
externalURL, |
||||
[]string{"missingkey=error"}, |
||||
) |
||||
|
||||
expander.Funcs(text_template.FuncMap{ |
||||
// These three functions are no-ops for now.
|
||||
"strvalue": func(value templateCaptureValue) string { |
||||
return "" |
||||
}, |
||||
"graphLink": func() string { |
||||
return "" |
||||
}, |
||||
"tableLink": func() string { |
||||
return "" |
||||
}, |
||||
}) |
||||
|
||||
return expander.Expand() |
||||
} |
||||
|
||||
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 |
||||
} |
||||
@ -0,0 +1,400 @@ |
||||
package state |
||||
|
||||
import ( |
||||
"errors" |
||||
"net/url" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data" |
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval" |
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
ptr "github.com/xorcare/pointer" |
||||
) |
||||
|
||||
func TestTemplateCaptureValueStringer(t *testing.T) { |
||||
cases := []struct { |
||||
name string |
||||
value templateCaptureValue |
||||
expected string |
||||
}{{ |
||||
name: "0 is returned as integer value", |
||||
value: templateCaptureValue{Value: 0}, |
||||
expected: "0", |
||||
}, { |
||||
name: "1.0 is returned as integer value", |
||||
value: templateCaptureValue{Value: 1.0}, |
||||
expected: "1", |
||||
}, { |
||||
name: "1.1 is returned as decimal value", |
||||
value: templateCaptureValue{Value: 1.1}, |
||||
expected: "1.1", |
||||
}} |
||||
|
||||
for _, c := range cases { |
||||
t.Run(c.name, func(t *testing.T) { |
||||
assert.Equal(t, c.expected, c.value.String()) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
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 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\""), |
||||
}, { |
||||
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: ptr.Float64(1), |
||||
}, |
||||
}, |
||||
}, |
||||
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 }}", |
||||
alertInstance: eval.Result{ |
||||
Values: map[string]eval.NumberValueCapture{ |
||||
"A": { |
||||
Var: "A", |
||||
Labels: data.Labels{}, |
||||
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: "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(`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: ptr.Float64(0.0), |
||||
}, |
||||
"B": { |
||||
Var: "B", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(1.0), |
||||
}, |
||||
"C": { |
||||
Var: "C", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(1048576.0), |
||||
}, |
||||
"D": { |
||||
Var: "D", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(.12), |
||||
}, |
||||
}, |
||||
}, |
||||
expected: "0:1:1Mi:0.12:", |
||||
}, { |
||||
name: "humanize1024 string with error", |
||||
text: "{{ humanize1024 $value }}", |
||||
alertInstance: eval.Result{ |
||||
EvaluationString: "invalid", |
||||
}, |
||||
expectedError: errors.New(`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: ptr.Float64(0), |
||||
}, |
||||
"B": { |
||||
Var: "B", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(1), |
||||
}, |
||||
"C": { |
||||
Var: "C", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(60), |
||||
}, |
||||
"D": { |
||||
Var: "D", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(3600), |
||||
}, |
||||
"E": { |
||||
Var: "E", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(86400), |
||||
}, |
||||
"F": { |
||||
Var: "F", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(86400 + 3600), |
||||
}, |
||||
"G": { |
||||
Var: "G", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(-(86400*2 + 3600*3 + 60*4 + 5)), |
||||
}, |
||||
"H": { |
||||
Var: "H", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(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: ptr.Float64(.1), |
||||
}, |
||||
"B": { |
||||
Var: "B", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(.0001), |
||||
}, |
||||
"C": { |
||||
Var: "C", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(.12345), |
||||
}, |
||||
"D": { |
||||
Var: "D", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(60.1), |
||||
}, |
||||
"E": { |
||||
Var: "E", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(60.5), |
||||
}, |
||||
"F": { |
||||
Var: "F", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(1.2345), |
||||
}, |
||||
"G": { |
||||
Var: "G", |
||||
Labels: data.Labels{}, |
||||
Value: ptr.Float64(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(`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(`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: "check that graphLink returns an empty string", |
||||
text: "{{ graphLink \"up\" }}", |
||||
expected: "", |
||||
}, { |
||||
name: "check that tableLink returns an empty string", |
||||
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 := expandTemplate("test", c.text, c.labels, c.alertInstance, externalURL) |
||||
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) |
||||
}) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue