diff --git a/pkg/tsdb/cloudwatch/live.go b/pkg/tsdb/cloudwatch/live.go index 445fa2f1415..e8970e7e0d2 100644 --- a/pkg/tsdb/cloudwatch/live.go +++ b/pkg/tsdb/cloudwatch/live.go @@ -270,33 +270,9 @@ func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel dataFrame.Name = query.RefID dataFrame.RefID = query.RefID - var dataFrames data.Frames - - // When a query of the form "stats ... by ..." is made, we want to return - // one series per group defined in the query, but due to the format - // the query response is in, there does not seem to be a way to tell - // by the response alone if/how the results should be grouped. - // Because of this, if the frontend sees that a "stats ... by ..." query is being made - // the "statsGroups" parameter is sent along with the query to the backend so that we - // can correctly group the CloudWatch logs response. - statsGroups := model.Get("statsGroups").MustStringArray() - if len(statsGroups) > 0 && len(dataFrame.Fields) > 0 { - groupedFrames, err := groupResults(dataFrame, statsGroups) - if err != nil { - return retryer.FuncError, err - } - - dataFrames = groupedFrames - } else { - if dataFrame.Meta != nil { - dataFrame.Meta.PreferredVisualization = "logs" - } else { - dataFrame.Meta = &data.FrameMeta{ - PreferredVisualization: "logs", - } - } - - dataFrames = data.Frames{dataFrame} + dataFrames, err := groupResponseFrame(dataFrame, model.Get("statsGroups").MustStringArray()) + if err != nil { + return retryer.FuncError, fmt.Errorf("failed to group dataframe response: %v", err) } responseChannel <- &backend.QueryDataResponse{ @@ -317,6 +293,54 @@ func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel }, maxAttempts, minRetryDelay, maxRetryDelay) } +func groupResponseFrame(frame *data.Frame, statsGroups []string) (data.Frames, error) { + var dataFrames data.Frames + + // When a query of the form "stats ... by ..." is made, we want to return + // one series per group defined in the query, but due to the format + // the query response is in, there does not seem to be a way to tell + // by the response alone if/how the results should be grouped. + // Because of this, if the frontend sees that a "stats ... by ..." query is being made + // the "statsGroups" parameter is sent along with the query to the backend so that we + // can correctly group the CloudWatch logs response. + // Check if we have time field though as it makes sense to split only for time series. + if hasTimeField(frame) { + if len(statsGroups) > 0 && len(frame.Fields) > 0 { + groupedFrames, err := groupResults(frame, statsGroups) + if err != nil { + return nil, err + } + + dataFrames = groupedFrames + } else { + setPreferredVisType(frame, "logs") + dataFrames = data.Frames{frame} + } + } else { + dataFrames = data.Frames{frame} + } + return dataFrames, nil +} + +func hasTimeField(frame *data.Frame) bool { + for _, field := range frame.Fields { + if field.Type() == data.FieldTypeNullableTime { + return true + } + } + return false +} + +func setPreferredVisType(frame *data.Frame, visType data.VisType) { + if frame.Meta != nil { + frame.Meta.PreferredVisualization = visType + } else { + frame.Meta = &data.FrameMeta{ + PreferredVisualization: visType, + } + } +} + // Service quotas client factory. // // Stubbable by tests. diff --git a/pkg/tsdb/cloudwatch/live_test.go b/pkg/tsdb/cloudwatch/live_test.go new file mode 100644 index 00000000000..459ae4552eb --- /dev/null +++ b/pkg/tsdb/cloudwatch/live_test.go @@ -0,0 +1,31 @@ +package cloudwatch + +import ( + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/stretchr/testify/require" +) + +func TestGroupResponseFrame(t *testing.T) { + t.Run("Doesn't group results without time field", func(t *testing.T) { + frame := data.NewFrameOfFieldTypes("test", 0, data.FieldTypeString, data.FieldTypeInt32) + frame.AppendRow("val1", int32(10)) + frame.AppendRow("val2", int32(20)) + frame.AppendRow("val3", int32(30)) + + groupedFrame, err := groupResponseFrame(frame, []string{"something"}) + require.NoError(t, err) + require.Equal(t, 3, groupedFrame[0].Rows()) + require.Equal(t, []interface{}{"val1", "val2", "val3"}, asArray(groupedFrame[0].Fields[0])) + require.Equal(t, []interface{}{int32(10), int32(20), int32(30)}, asArray(groupedFrame[0].Fields[1])) + }) +} + +func asArray(field *data.Field) []interface{} { + var vals []interface{} + for i := 0; i < field.Len(); i++ { + vals = append(vals, field.At(i)) + } + return vals +}