| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | package expr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2023-07-21 02:44:12 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2023-07-21 02:44:12 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2023-07-21 02:44:12 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-21 02:44:12 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/data" | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2023-07-21 02:44:12 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/expr/mathexp" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/log/logtest" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/tracing" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/featuremgmt" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 18:07:24 +08:00
										 |  |  | type expectedError struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e expectedError) Error() string { | 
					
						
							|  |  |  | 	return "expected" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestQueryError_Error(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-07-21 18:38:29 +08:00
										 |  |  | 	e := MakeQueryError("A", "", errors.New("this is an error message")) | 
					
						
							|  |  |  | 	assert.EqualError(t, e, "[sse.dataQueryError] failed to execute query [A]: this is an error message") | 
					
						
							| 
									
										
										
										
											2021-11-16 21:42:22 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-17 18:07:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestQueryError_Unwrap(t *testing.T) { | 
					
						
							|  |  |  | 	t.Run("errors.Is", func(t *testing.T) { | 
					
						
							|  |  |  | 		expectedIsErr := errors.New("expected") | 
					
						
							| 
									
										
										
										
											2023-07-21 18:38:29 +08:00
										 |  |  | 		e := MakeQueryError("A", "", expectedIsErr) | 
					
						
							| 
									
										
										
										
											2021-11-17 18:07:24 +08:00
										 |  |  | 		assert.True(t, errors.Is(e, expectedIsErr)) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("errors.As", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-07-21 18:38:29 +08:00
										 |  |  | 		e := MakeQueryError("A", "", expectedError{}) | 
					
						
							| 
									
										
										
										
											2021-11-17 18:07:24 +08:00
										 |  |  | 		var expectedAsError expectedError | 
					
						
							|  |  |  | 		assert.True(t, errors.As(e, &expectedAsError)) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-07-21 02:44:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestCheckIfSeriesNeedToBeFixed(t *testing.T) { | 
					
						
							|  |  |  | 	createFrame := func(m ...func(field *data.Field)) []*data.Frame { | 
					
						
							|  |  |  | 		f := data.NewFrame("", | 
					
						
							|  |  |  | 			data.NewField("Time", nil, []time.Time{})) | 
					
						
							|  |  |  | 		for i := 0; i < 100; i++ { | 
					
						
							|  |  |  | 			fld := data.NewField(fmt.Sprintf("fld-%d", i), nil, []*float64{}) | 
					
						
							|  |  |  | 			fld.Config = &data.FieldConfig{} | 
					
						
							|  |  |  | 			for _, change := range m { | 
					
						
							|  |  |  | 				change(fld) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			f.Fields = append(f.Fields, fld) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return []*data.Frame{f} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	withLabels := func(field *data.Field) { | 
					
						
							|  |  |  | 		field.Labels = map[string]string{ | 
					
						
							|  |  |  | 			"field": field.Name, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	withDisplayNameFromDS := func(field *data.Field) { | 
					
						
							|  |  |  | 		field.Config.DisplayNameFromDS = fmt.Sprintf("dnds-%s", field.Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	withDisplayName := func(field *data.Field) { | 
					
						
							|  |  |  | 		field.Config.DisplayName = fmt.Sprintf("dn-%s", field.Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	withoutName := func(field *data.Field) { | 
					
						
							|  |  |  | 		field.Name = "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	getLabelName := func(f func(series mathexp.Series, valueField *data.Field)) string { | 
					
						
							|  |  |  | 		s := mathexp.NewSeries("A", nil, 0) | 
					
						
							|  |  |  | 		field := &data.Field{ | 
					
						
							|  |  |  | 			Name: "Name", | 
					
						
							|  |  |  | 			Config: &data.FieldConfig{ | 
					
						
							|  |  |  | 				DisplayNameFromDS: "DisplayNameFromDS", | 
					
						
							|  |  |  | 				DisplayName:       "DisplayName", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		f(s, field) | 
					
						
							|  |  |  | 		return s.GetLabels()[nameLabelName] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		name         string | 
					
						
							|  |  |  | 		frames       []*data.Frame | 
					
						
							|  |  |  | 		expectedName string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:         "should return nil if at least one value field has labels", | 
					
						
							|  |  |  | 			frames:       createFrame(withLabels, withDisplayNameFromDS, withDisplayName), | 
					
						
							|  |  |  | 			expectedName: "", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:         "should return nil if names are empty", | 
					
						
							|  |  |  | 			frames:       createFrame(withoutName), | 
					
						
							|  |  |  | 			expectedName: "", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:         "should return patcher with DisplayNameFromDS first", | 
					
						
							|  |  |  | 			frames:       createFrame(withDisplayNameFromDS, withDisplayName), | 
					
						
							|  |  |  | 			expectedName: "DisplayNameFromDS", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "should return patcher with DisplayName if DisplayNameFromDS is not unique", | 
					
						
							|  |  |  | 			frames: func() []*data.Frame { | 
					
						
							|  |  |  | 				frames := createFrame(withDisplayNameFromDS, withDisplayName) | 
					
						
							|  |  |  | 				f := frames[0] | 
					
						
							|  |  |  | 				f.Fields[2].Config.DisplayNameFromDS = "test" | 
					
						
							|  |  |  | 				f.Fields[3].Config.DisplayNameFromDS = "test" | 
					
						
							|  |  |  | 				return frames | 
					
						
							|  |  |  | 			}(), | 
					
						
							|  |  |  | 			expectedName: "DisplayName", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:         "should return patcher with DisplayName if is empty", | 
					
						
							|  |  |  | 			frames:       createFrame(withDisplayName), | 
					
						
							|  |  |  | 			expectedName: "DisplayName", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "should return patcher with Name if DisplayName and DisplayNameFromDS are not unique", | 
					
						
							|  |  |  | 			frames: func() []*data.Frame { | 
					
						
							|  |  |  | 				frames := createFrame(withDisplayNameFromDS, withDisplayName) | 
					
						
							|  |  |  | 				f := frames[0] | 
					
						
							|  |  |  | 				f.Fields[1].Config.DisplayNameFromDS = f.Fields[2].Config.DisplayNameFromDS | 
					
						
							|  |  |  | 				f.Fields[1].Config.DisplayName = f.Fields[2].Config.DisplayName | 
					
						
							|  |  |  | 				return frames | 
					
						
							|  |  |  | 			}(), | 
					
						
							|  |  |  | 			expectedName: "Name", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:         "should return patcher with Name if DisplayName and DisplayNameFromDS are empty", | 
					
						
							|  |  |  | 			frames:       createFrame(), | 
					
						
							|  |  |  | 			expectedName: "Name", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "should return nil if all fields are not unique", | 
					
						
							|  |  |  | 			frames: func() []*data.Frame { | 
					
						
							|  |  |  | 				frames := createFrame(withDisplayNameFromDS, withDisplayName) | 
					
						
							|  |  |  | 				f := frames[0] | 
					
						
							|  |  |  | 				f.Fields[1].Config.DisplayNameFromDS = f.Fields[2].Config.DisplayNameFromDS | 
					
						
							|  |  |  | 				f.Fields[1].Config.DisplayName = f.Fields[2].Config.DisplayName | 
					
						
							|  |  |  | 				f.Fields[1].Name = f.Fields[2].Name | 
					
						
							|  |  |  | 				return frames | 
					
						
							|  |  |  | 			}(), | 
					
						
							|  |  |  | 			expectedName: "", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	supportedDatasources := []string{ | 
					
						
							|  |  |  | 		datasources.DS_GRAPHITE, | 
					
						
							|  |  |  | 		datasources.DS_TESTDATA, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, tc := range testCases { | 
					
						
							|  |  |  | 		t.Run(tc.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			for _, datasource := range supportedDatasources { | 
					
						
							|  |  |  | 				fixer := checkIfSeriesNeedToBeFixed(tc.frames, datasource) | 
					
						
							|  |  |  | 				if tc.expectedName == "" { | 
					
						
							|  |  |  | 					require.Nil(t, fixer) | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					require.Equal(t, tc.expectedName, getLabelName(fixer)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestConvertDataFramesToResults(t *testing.T) { | 
					
						
							|  |  |  | 	s := &Service{ | 
					
						
							|  |  |  | 		cfg:      setting.NewCfg(), | 
					
						
							|  |  |  | 		features: &featuremgmt.FeatureManager{}, | 
					
						
							|  |  |  | 		tracer:   tracing.InitializeTracerForTest(), | 
					
						
							|  |  |  | 		metrics:  newMetrics(nil), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should add name label if no labels and specific data source", func(t *testing.T) { | 
					
						
							|  |  |  | 		supported := []string{datasources.DS_GRAPHITE, datasources.DS_TESTDATA} | 
					
						
							|  |  |  | 		t.Run("when only field name is specified", func(t *testing.T) { | 
					
						
							|  |  |  | 			t.Run("use value field names if one frame - many series", func(t *testing.T) { | 
					
						
							|  |  |  | 				supported := []string{datasources.DS_GRAPHITE, datasources.DS_TESTDATA} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				frames := []*data.Frame{ | 
					
						
							|  |  |  | 					data.NewFrame("test", | 
					
						
							|  |  |  | 						data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), | 
					
						
							|  |  |  | 						data.NewField("test-value1", nil, []*float64{fp(2)}), | 
					
						
							|  |  |  | 						data.NewField("test-value2", nil, []*float64{fp(2)})), | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				for _, dtype := range supported { | 
					
						
							|  |  |  | 					t.Run(dtype, func(t *testing.T) { | 
					
						
							|  |  |  | 						resultType, res, err := convertDataFramesToResults(context.Background(), frames, dtype, s, &logtest.Fake{}) | 
					
						
							|  |  |  | 						require.NoError(t, err) | 
					
						
							|  |  |  | 						assert.Equal(t, "single frame series", resultType) | 
					
						
							|  |  |  | 						require.Len(t, res.Values, 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						var names []string | 
					
						
							|  |  |  | 						for _, value := range res.Values { | 
					
						
							|  |  |  | 							require.IsType(t, mathexp.Series{}, value) | 
					
						
							|  |  |  | 							lbls := value.GetLabels() | 
					
						
							|  |  |  | 							require.Contains(t, lbls, nameLabelName) | 
					
						
							|  |  |  | 							names = append(names, lbls[nameLabelName]) | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						require.EqualValues(t, []string{"test-value1", "test-value2"}, names) | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			t.Run("should use frame name if one frame - one series", func(t *testing.T) { | 
					
						
							|  |  |  | 				frames := []*data.Frame{ | 
					
						
							|  |  |  | 					data.NewFrame("test-frame1", | 
					
						
							|  |  |  | 						data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), | 
					
						
							|  |  |  | 						data.NewField("test-value1", nil, []*float64{fp(2)})), | 
					
						
							|  |  |  | 					data.NewFrame("test-frame2", | 
					
						
							|  |  |  | 						data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), | 
					
						
							|  |  |  | 						data.NewField("test-value2", nil, []*float64{fp(2)})), | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				for _, dtype := range supported { | 
					
						
							|  |  |  | 					t.Run(dtype, func(t *testing.T) { | 
					
						
							|  |  |  | 						resultType, res, err := convertDataFramesToResults(context.Background(), frames, dtype, s, &logtest.Fake{}) | 
					
						
							|  |  |  | 						require.NoError(t, err) | 
					
						
							|  |  |  | 						assert.Equal(t, "multi frame series", resultType) | 
					
						
							|  |  |  | 						require.Len(t, res.Values, 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						var names []string | 
					
						
							|  |  |  | 						for _, value := range res.Values { | 
					
						
							|  |  |  | 							require.IsType(t, mathexp.Series{}, value) | 
					
						
							|  |  |  | 							lbls := value.GetLabels() | 
					
						
							|  |  |  | 							require.Contains(t, lbls, nameLabelName) | 
					
						
							|  |  |  | 							names = append(names, lbls[nameLabelName]) | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						require.EqualValues(t, []string{"test-frame1", "test-frame2"}, names) | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		t.Run("should use fields DisplayNameFromDS when it is unique", func(t *testing.T) { | 
					
						
							|  |  |  | 			f1 := data.NewField("test-value1", nil, []*float64{fp(2)}) | 
					
						
							|  |  |  | 			f1.Config = &data.FieldConfig{DisplayNameFromDS: "test-value1"} | 
					
						
							|  |  |  | 			f2 := data.NewField("test-value2", nil, []*float64{fp(2)}) | 
					
						
							|  |  |  | 			f2.Config = &data.FieldConfig{DisplayNameFromDS: "test-value2"} | 
					
						
							|  |  |  | 			frames := []*data.Frame{ | 
					
						
							|  |  |  | 				data.NewFrame("test-frame1", | 
					
						
							|  |  |  | 					data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), | 
					
						
							|  |  |  | 					f1), | 
					
						
							|  |  |  | 				data.NewFrame("test-frame2", | 
					
						
							|  |  |  | 					data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), | 
					
						
							|  |  |  | 					f2), | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for _, dtype := range supported { | 
					
						
							|  |  |  | 				t.Run(dtype, func(t *testing.T) { | 
					
						
							|  |  |  | 					resultType, res, err := convertDataFramesToResults(context.Background(), frames, dtype, s, &logtest.Fake{}) | 
					
						
							|  |  |  | 					require.NoError(t, err) | 
					
						
							|  |  |  | 					assert.Equal(t, "multi frame series", resultType) | 
					
						
							|  |  |  | 					require.Len(t, res.Values, 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					var names []string | 
					
						
							|  |  |  | 					for _, value := range res.Values { | 
					
						
							|  |  |  | 						require.IsType(t, mathexp.Series{}, value) | 
					
						
							|  |  |  | 						lbls := value.GetLabels() | 
					
						
							|  |  |  | 						require.Contains(t, lbls, nameLabelName) | 
					
						
							|  |  |  | 						names = append(names, lbls[nameLabelName]) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					require.EqualValues(t, []string{"test-value1", "test-value2"}, names) | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } |