diff --git a/pkg/services/ngalert/state/template/template.go b/pkg/services/ngalert/state/template/template.go index c0a92be548b..c26455fa862 100644 --- a/pkg/services/ngalert/state/template/template.go +++ b/pkg/services/ngalert/state/template/template.go @@ -4,6 +4,7 @@ import ( "context" "math" "net/url" + "sort" "strconv" "strings" "time" @@ -16,10 +17,33 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/eval" ) +type Labels map[string]string + +// String returns the labels as k=v, comma separated, in increasing order. +func (l Labels) String() string { + // sort the names of the labels in increasing order + sorted := make([]string, 0, len(l)) + for k := range l { + sorted = append(sorted, k) + } + sort.Strings(sorted) + // create the string from the sorted labels + b := strings.Builder{} + for i, k := range sorted { + b.WriteString(k) + b.WriteString("=") + b.WriteString(l[k]) + if i < len(l)-1 { + b.WriteString(", ") + } + } + return b.String() +} + // Value contains the labels and value of a Reduce, Math or Threshold // expression for a series. type Value struct { - Labels map[string]string + Labels Labels Value float64 } @@ -37,7 +61,7 @@ func NewValues(values map[string]eval.NumberValueCapture) map[string]Value { f = math.NaN() } m[k] = Value{ - Labels: v.Labels, + Labels: Labels(v.Labels), Value: f, } } diff --git a/pkg/services/ngalert/state/template/template_test.go b/pkg/services/ngalert/state/template/template_test.go index a1206281f37..f71d8e323da 100644 --- a/pkg/services/ngalert/state/template/template_test.go +++ b/pkg/services/ngalert/state/template/template_test.go @@ -14,6 +14,28 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/eval" ) +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