From 54ad5b869e686bb61993a84bae06b5996cc74dfc Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Tue, 14 Jul 2020 11:13:03 +0100 Subject: [PATCH] CloudWatch Logs: Fixes grouping of results by numeric field (#26298) * CloudWatch Logs: Fixes grouping of results by numeric field Closes #25721 --- pkg/tsdb/cloudwatch/log_query.go | 36 +++++++- pkg/tsdb/cloudwatch/log_query_test.go | 121 ++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/cloudwatch/log_query.go b/pkg/tsdb/cloudwatch/log_query.go index 0c36172f4d8..218018f65ba 100644 --- a/pkg/tsdb/cloudwatch/log_query.go +++ b/pkg/tsdb/cloudwatch/log_query.go @@ -1,10 +1,12 @@ package cloudwatch import ( + "fmt" "sort" "strconv" "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/grafana/grafana-plugin-sdk-go/data" ) @@ -108,9 +110,19 @@ func logsResultsToDataframes(response *cloudwatchlogs.GetQueryResultsOutput) (*d func groupResults(results *data.Frame, groupingFieldNames []string) ([]*data.Frame, error) { groupingFields := make([]*data.Field, 0) - for _, field := range results.Fields { + for i, field := range results.Fields { for _, groupingField := range groupingFieldNames { if field.Name == groupingField { + // convert numeric grouping field to string field + if field.Type().Numeric() { + newField, err := numericFieldToStringField(field) + if err != nil { + return nil, err + } + results.Fields[i] = newField + field = newField + } + groupingFields = append(groupingFields, field) } } @@ -153,3 +165,25 @@ func generateGroupKey(fields []*data.Field, row int) string { return groupKey } + +func numericFieldToStringField(field *data.Field) (*data.Field, error) { + if !field.Type().Numeric() { + return nil, fmt.Errorf("field is not numeric") + } + + strings := make([]*string, field.Len()) + for i := 0; i < field.Len(); i++ { + floatVal, err := field.FloatAt(i) + if err != nil { + return nil, err + } + + strVal := fmt.Sprintf("%g", floatVal) + strings[i] = aws.String(strVal) + } + + newField := data.NewField(field.Name, field.Labels, strings) + newField.Config = field.Config + + return newField, nil +} diff --git a/pkg/tsdb/cloudwatch/log_query_test.go b/pkg/tsdb/cloudwatch/log_query_test.go index 861ae629dea..3b2fa119cbd 100644 --- a/pkg/tsdb/cloudwatch/log_query_test.go +++ b/pkg/tsdb/cloudwatch/log_query_test.go @@ -356,3 +356,124 @@ func TestGroupingResults(t *testing.T) { require.NoError(t, err) assert.ElementsMatch(t, expectedGroupedFrames, groupedResults) } + +func TestGroupingResultsWithNumericField(t *testing.T) { + timeA, err := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 15:04:05.000") + require.NoError(t, err) + timeB, err := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 16:04:05.000") + require.NoError(t, err) + timeC, err := time.Parse("2006-01-02 15:04:05.000", "2020-03-02 17:04:05.000") + require.NoError(t, err) + timeVals := []*time.Time{ + &timeA, &timeA, &timeA, &timeB, &timeB, &timeB, &timeC, &timeC, &timeC, + } + timeField := data.NewField("@timestamp", data.Labels{}, timeVals) + + httpResponseField := data.NewField("httpresponse", data.Labels{}, []*float64{ + aws.Float64(400), + aws.Float64(404), + aws.Float64(500), + aws.Float64(400), + aws.Float64(404), + aws.Float64(500), + aws.Float64(400), + aws.Float64(404), + aws.Float64(500), + }) + + countField := data.NewField("count", data.Labels{}, []*string{ + aws.String("100"), + aws.String("150"), + aws.String("20"), + aws.String("34"), + aws.String("57"), + aws.String("62"), + aws.String("105"), + aws.String("200"), + aws.String("99"), + }) + + fakeDataFrame := &data.Frame{ + Name: "CloudWatchLogsResponse", + Fields: []*data.Field{ + timeField, + httpResponseField, + countField, + }, + RefID: "", + } + + groupedTimeVals := []*time.Time{ + &timeA, &timeB, &timeC, + } + groupedTimeField := data.NewField("@timestamp", data.Labels{}, groupedTimeVals) + groupedHttpResponseFieldA := data.NewField("httpresponse", data.Labels{}, []*string{ + aws.String("400"), + aws.String("400"), + aws.String("400"), + }) + + groupedCountFieldA := data.NewField("count", data.Labels{}, []*string{ + aws.String("100"), + aws.String("34"), + aws.String("105"), + }) + + groupedHttpResponseFieldB := data.NewField("httpresponse", data.Labels{}, []*string{ + aws.String("404"), + aws.String("404"), + aws.String("404"), + }) + + groupedCountFieldB := data.NewField("count", data.Labels{}, []*string{ + aws.String("150"), + aws.String("57"), + aws.String("200"), + }) + + groupedHttpResponseFieldC := data.NewField("httpresponse", data.Labels{}, []*string{ + aws.String("500"), + aws.String("500"), + aws.String("500"), + }) + + groupedCountFieldC := data.NewField("count", data.Labels{}, []*string{ + aws.String("20"), + aws.String("62"), + aws.String("99"), + }) + + expectedGroupedFrames := []*data.Frame{ + { + Name: "400", + Fields: []*data.Field{ + groupedTimeField, + groupedHttpResponseFieldA, + groupedCountFieldA, + }, + RefID: "", + }, + { + Name: "404", + Fields: []*data.Field{ + groupedTimeField, + groupedHttpResponseFieldB, + groupedCountFieldB, + }, + RefID: "", + }, + { + Name: "500", + Fields: []*data.Field{ + groupedTimeField, + groupedHttpResponseFieldC, + groupedCountFieldC, + }, + RefID: "", + }, + } + + groupedResults, err := groupResults(fakeDataFrame, []string{"httpresponse"}) + require.NoError(t, err) + assert.ElementsMatch(t, expectedGroupedFrames, groupedResults) +}