From d9fd80737528f304ecaf162de4b48ce1dd372076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Thu, 2 Feb 2023 09:03:18 +0100 Subject: [PATCH] Elasticsearch: Fix consistent label order in alerting (#62497) elasticsearch: backend: sort label-values --- pkg/tsdb/elasticsearch/response_parser.go | 20 +++- .../elasticsearch/response_parser_test.go | 91 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/elasticsearch/response_parser.go b/pkg/tsdb/elasticsearch/response_parser.go index 76a63c12b1d..0fb4a1216ba 100644 --- a/pkg/tsdb/elasticsearch/response_parser.go +++ b/pkg/tsdb/elasticsearch/response_parser.go @@ -534,6 +534,24 @@ func trimDatapoints(queryResult backend.DataResponse, target *Query) { } } +// we sort the label's pairs by the label-key, +// and return the label-values +func getSortedLabelValues(labels data.Labels) []string { + var keys []string + for key := range labels { + keys = append(keys, key) + } + + sort.Strings(keys) + + var values []string + for _, key := range keys { + values = append(values, labels[key]) + } + + return values +} + func nameFields(queryResult backend.DataResponse, target *Query) { set := make(map[string]struct{}) frames := queryResult.Frames @@ -645,7 +663,7 @@ func getFieldName(dataField data.Field, target *Query, metricTypeCount int) stri } name := "" - for _, v := range dataField.Labels { + for _, v := range getSortedLabelValues(dataField.Labels) { name += v + " " } diff --git a/pkg/tsdb/elasticsearch/response_parser_test.go b/pkg/tsdb/elasticsearch/response_parser_test.go index 5be058c9bea..9b0dd2aca4c 100644 --- a/pkg/tsdb/elasticsearch/response_parser_test.go +++ b/pkg/tsdb/elasticsearch/response_parser_test.go @@ -1164,3 +1164,94 @@ func parseTestResponse(tsdbQueries map[string]string, responseBody string) (*bac return parseResponse(response.Responses, queries) } + +func TestLabelOrderInFieldName(t *testing.T) { + query := []byte(` + [ + { + "refId": "A", + "metrics": [{ "type": "count", "id": "1" }], + "bucketAggs": [ + { "type": "terms", "field": "f1", "id": "3" }, + { "type": "terms", "field": "f2", "id": "4" }, + { "type": "date_histogram", "field": "@timestamp", "id": "2" } + ] + } + ] + `) + + response := []byte(` + { + "responses": [ + { + "aggregations": { + "3": { + "buckets": [ + { + "key": "val3", + "4": { + "buckets": [ + { + "key": "info", + "2": {"buckets": [{ "key_as_string": "1675086600000", "key": 1675086600000, "doc_count": 5 }]} + }, + { + "key": "error", + "2": {"buckets": [{ "key_as_string": "1675086600000", "key": 1675086600000, "doc_count": 2 }]} + } + ] + } + }, + { + "key": "val2", + "4": { + "buckets": [ + { + "key": "info", + "2": {"buckets": [{ "key_as_string": "1675086600000", "key": 1675086600000, "doc_count": 6 }]} + }, + { + "key": "error", + "2": {"buckets": [{ "key_as_string": "1675086600000", "key": 1675086600000, "doc_count": 1 }]} + } + ] + } + }, + { + "key": "val1", + "4": { + "buckets": [ + { + "key": "info", + "2": {"buckets": [{ "key_as_string": "1675086600000", "key": 1675086600000, "doc_count": 6 }]} + }, + { + "key": "error", + "2": {"buckets": [{ "key_as_string": "1675086600000", "key": 1675086600000, "doc_count": 2 }]} + } + ] + } + } + ] + } + } + } + ] + } + `) + + result, err := queryDataTest(query, response) + require.NoError(t, err) + + require.Len(t, result.response.Responses, 1) + frames := result.response.Responses["A"].Frames + require.Len(t, frames, 6) + + // the important part is that the label-value is always before the level-value + requireTimeSeriesName(t, "val3 info", frames[0]) + requireTimeSeriesName(t, "val3 error", frames[1]) + requireTimeSeriesName(t, "val2 info", frames[2]) + requireTimeSeriesName(t, "val2 error", frames[3]) + requireTimeSeriesName(t, "val1 info", frames[4]) + requireTimeSeriesName(t, "val1 error", frames[5]) +}