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:
+51
-27
@@ -270,33 +270,9 @@ func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel
|
|||||||
|
|
||||||
dataFrame.Name = query.RefID
|
dataFrame.Name = query.RefID
|
||||||
dataFrame.RefID = query.RefID
|
dataFrame.RefID = query.RefID
|
||||||
var dataFrames data.Frames
|
dataFrames, err := groupResponseFrame(dataFrame, model.Get("statsGroups").MustStringArray())
|
||||||
|
if err != nil {
|
||||||
// When a query of the form "stats ... by ..." is made, we want to return
|
return retryer.FuncError, fmt.Errorf("failed to group dataframe response: %v", err)
|
||||||
// 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}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
responseChannel <- &backend.QueryDataResponse{
|
responseChannel <- &backend.QueryDataResponse{
|
||||||
@@ -317,6 +293,54 @@ func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel
|
|||||||
}, maxAttempts, minRetryDelay, maxRetryDelay)
|
}, 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.
|
// Service quotas client factory.
|
||||||
//
|
//
|
||||||
// Stubbable by tests.
|
// Stubbable by tests.
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user