CloudWatch/Logs: Don't group dataframes for non time series queries (#37998)

* Don't split response when there is no time field

* Refactor grouping to separate function

* Add test
This commit is contained in:
Andrej Ocenas 2021-08-18 15:05:06 +02:00 committed by GitHub
parent a7b22a73b6
commit 60a0c5da05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 27 deletions

View File

@ -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.

View File

@ -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
}