From 982238b4bdfcac98f83f7efbb16ece247b386e12 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 21:32:21 +0200 Subject: [PATCH] [release-12.0.2] Alerting: Fix $value type when single data source is queried (#106101) Alerting: Fix $value type when single data source is queried (#106080) (cherry picked from commit faeddf334ad68b2ca42e06a5d0fbd713b2621a5a) Co-authored-by: Alexander Akhmetov --- .../ngalert/state/template/template.go | 14 ++--- .../ngalert/state/template/template_test.go | 59 +++++++++++++++++-- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/pkg/services/ngalert/state/template/template.go b/pkg/services/ngalert/state/template/template.go index 1a593f76f67..2f716931981 100644 --- a/pkg/services/ngalert/state/template/template.go +++ b/pkg/services/ngalert/state/template/template.go @@ -77,18 +77,18 @@ type Data struct { Values map[string]Value // Value is the .Value and $value variables in templates. - // For single datasource queries, this will be the numeric value of the query. - // For multiple datasource queries, this will be the evaluation string. - Value string + // For single datasource queries, this will be the numeric value of the query (float64). + // For multiple datasource queries, this will be the evaluation string (string). + Value any } func NewData(labels map[string]string, res eval.Result) Data { values := NewValues(res.Values) // By default, use the evaluation string as the Value - valueStr := res.EvaluationString + var value any = res.EvaluationString - // If there's exactly one datasource node, use its value instead + // If there's exactly one datasource node, use its numeric value instead // This makes the $value variable compatible with Prometheus templating // where $value holds the numeric value of the alert query datasourceNodeCount := 0 @@ -105,13 +105,13 @@ func NewData(labels map[string]string, res eval.Result) Data { } if datasourceNodeCount == 1 { - valueStr = datasourceNodeValue.String() + value = datasourceNodeValue.Value } return Data{ Labels: labels, Values: values, - Value: valueStr, + Value: value, } } diff --git a/pkg/services/ngalert/state/template/template_test.go b/pkg/services/ngalert/state/template/template_test.go index 780090bd96a..b8ed4cb9b1c 100644 --- a/pkg/services/ngalert/state/template/template_test.go +++ b/pkg/services/ngalert/state/template/template_test.go @@ -3,6 +3,7 @@ package template import ( "context" "errors" + "math" "net/url" "testing" @@ -109,7 +110,7 @@ func TestNewData(t *testing.T) { } data := NewData(map[string]string{}, res) - assert.Equal(t, "10", data.Value) + assert.Equal(t, 10.0, data.Value) }) t.Run("uses evaluation string when multiple datasource nodes exist", func(t *testing.T) { @@ -159,7 +160,7 @@ func TestDatasourceValueInTemplating(t *testing.T) { data := NewData(map[string]string{}, res) // In Prometheus, a nil value would be rendered as NaN - assert.Equal(t, "NaN", data.Value) + assert.True(t, math.IsNaN(data.Value.(float64))) }) t.Run("single datasource node uses query value", func(t *testing.T) { @@ -188,7 +189,7 @@ func TestDatasourceValueInTemplating(t *testing.T) { } data := NewData(map[string]string{}, res) - assert.Equal(t, "10", data.Value) + assert.Equal(t, 10.0, data.Value) }) t.Run("multiple datasource nodes uses evaluation string", func(t *testing.T) { @@ -636,8 +637,56 @@ func TestExpandTemplate(t *testing.T) { name: "check that safeHtml doesn't error or panic", text: "{{ \"\" | safeHtml }}", expected: "", - }, - } + }, { + name: "$value numeric comparison with single datasource", + text: `{{ if eq $value 1.0 }}equal{{ else }}not equal{{ end }}`, + alertInstance: eval.Result{ + Values: map[string]eval.NumberValueCapture{ + "A": { + Var: "A", + IsDatasourceNode: true, + Labels: data.Labels{"instance": "foo"}, + Value: util.Pointer(1.0), + }, + }, + }, + expected: "equal", + }, { + name: "humanize with string $value (multiple datasources)", + text: `{{ humanize $value }}`, + alertInstance: eval.Result{ + EvaluationString: "1234567.0", + Values: map[string]eval.NumberValueCapture{ + "A": { + Var: "A", + IsDatasourceNode: true, + Labels: data.Labels{"instance": "foo"}, + Value: util.Pointer(10.0), + }, + "B": { + Var: "B", + IsDatasourceNode: true, + Labels: data.Labels{"instance": "bar"}, + Value: util.Pointer(20.0), + }, + }, + }, + expected: "1.235M", + }, { + name: "humanize with numeric $value (single datasource)", + text: `{{ humanize $value }}`, + alertInstance: eval.Result{ + Values: map[string]eval.NumberValueCapture{ + "A": { + Var: "A", + IsDatasourceNode: true, + Labels: data.Labels{"instance": "foo"}, + Value: util.Pointer(1234567.0), + }, + }, + }, + expected: "1.235M", + }} for _, c := range cases { t.Run(c.name, func(t *testing.T) {