From cadc6088db6451f9db7e6adb8f8d5cdeba2d0492 Mon Sep 17 00:00:00 2001 From: Shirley <4163034+fridgepoet@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:21:13 +0200 Subject: [PATCH] CloudWatch: move QueryData input parsing types to separate package (#57165) * CloudWatch: move parse request types separate package * Move metric query constants, unexport metricDataQuery json decoding type * Unexport isSearchExpression --- .../cloudwatch/metric_data_input_builder.go | 6 +- .../metric_data_input_builder_test.go | 6 +- .../cloudwatch/metric_data_query_builder.go | 15 +-- .../metric_data_query_builder_test.go | 49 ++++---- .../{ => models}/cloudwatch_query.go | 55 +++++--- .../{ => models}/cloudwatch_query_test.go | 48 +++---- pkg/tsdb/cloudwatch/models/query_error.go | 12 ++ .../cloudwatch/{ => models}/request_parser.go | 117 +++++++++--------- .../{ => models}/request_parser_test.go | 28 ++--- pkg/tsdb/cloudwatch/models/types.go | 22 ++++ pkg/tsdb/cloudwatch/response_parser.go | 41 +++--- pkg/tsdb/cloudwatch/response_parser_test.go | 45 +++---- pkg/tsdb/cloudwatch/time_series_query.go | 3 +- pkg/tsdb/cloudwatch/time_series_query_test.go | 42 ++++--- pkg/tsdb/cloudwatch/types.go | 58 --------- 15 files changed, 280 insertions(+), 267 deletions(-) rename pkg/tsdb/cloudwatch/{ => models}/cloudwatch_query.go (73%) rename pkg/tsdb/cloudwatch/{ => models}/cloudwatch_query_test.go (83%) create mode 100644 pkg/tsdb/cloudwatch/models/query_error.go rename pkg/tsdb/cloudwatch/{ => models}/request_parser.go (74%) rename pkg/tsdb/cloudwatch/{ => models}/request_parser_test.go (97%) create mode 100644 pkg/tsdb/cloudwatch/models/types.go delete mode 100644 pkg/tsdb/cloudwatch/types.go diff --git a/pkg/tsdb/cloudwatch/metric_data_input_builder.go b/pkg/tsdb/cloudwatch/metric_data_input_builder.go index a18a036f969..5244cbdd0d3 100644 --- a/pkg/tsdb/cloudwatch/metric_data_input_builder.go +++ b/pkg/tsdb/cloudwatch/metric_data_input_builder.go @@ -5,11 +5,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" ) func (e *cloudWatchExecutor) buildMetricDataInput(startTime time.Time, endTime time.Time, - queries []*cloudWatchQuery) (*cloudwatch.GetMetricDataInput, error) { + queries []*models.CloudWatchQuery) (*cloudwatch.GetMetricDataInput, error) { metricDataInput := &cloudwatch.GetMetricDataInput{ StartTime: aws.Time(startTime), EndTime: aws.Time(endTime), @@ -27,7 +29,7 @@ func (e *cloudWatchExecutor) buildMetricDataInput(startTime time.Time, endTime t for _, query := range queries { metricDataQuery, err := e.buildMetricDataQuery(query) if err != nil { - return nil, &queryError{err, query.RefId} + return nil, &models.QueryError{Err: err, RefID: query.RefId} } metricDataInput.MetricDataQueries = append(metricDataInput.MetricDataQueries, metricDataQuery) } diff --git a/pkg/tsdb/cloudwatch/metric_data_input_builder_test.go b/pkg/tsdb/cloudwatch/metric_data_input_builder_test.go index 2fa1aaf4359..64413627971 100644 --- a/pkg/tsdb/cloudwatch/metric_data_input_builder_test.go +++ b/pkg/tsdb/cloudwatch/metric_data_input_builder_test.go @@ -6,9 +6,11 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" ) func TestMetricDataInputBuilder(t *testing.T) { @@ -34,7 +36,7 @@ func TestMetricDataInputBuilder(t *testing.T) { from := now.Add(time.Hour * -2) to := now.Add(time.Hour * -1) - mdi, err := executor.buildMetricDataInput(from, to, []*cloudWatchQuery{query}) + mdi, err := executor.buildMetricDataInput(from, to, []*models.CloudWatchQuery{query}) assert.NoError(t, err) require.NotNil(t, mdi) diff --git a/pkg/tsdb/cloudwatch/metric_data_query_builder.go b/pkg/tsdb/cloudwatch/metric_data_query_builder.go index 1cff741d788..39bd8d21c3a 100644 --- a/pkg/tsdb/cloudwatch/metric_data_query_builder.go +++ b/pkg/tsdb/cloudwatch/metric_data_query_builder.go @@ -9,9 +9,10 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" ) -func (e *cloudWatchExecutor) buildMetricDataQuery(query *cloudWatchQuery) (*cloudwatch.MetricDataQuery, error) { +func (e *cloudWatchExecutor) buildMetricDataQuery(query *models.CloudWatchQuery) (*cloudwatch.MetricDataQuery, error) { mdq := &cloudwatch.MetricDataQuery{ Id: aws.String(query.Id), ReturnData: aws.Bool(query.ReturnData), @@ -21,16 +22,16 @@ func (e *cloudWatchExecutor) buildMetricDataQuery(query *cloudWatchQuery) (*clou mdq.Label = &query.Label } - switch query.getGMDAPIMode() { - case GMDApiModeMathExpression: + switch query.GetGMDAPIMode() { + case models.GMDApiModeMathExpression: mdq.Period = aws.Int64(int64(query.Period)) mdq.Expression = aws.String(query.Expression) - case GMDApiModeSQLExpression: + case models.GMDApiModeSQLExpression: mdq.Period = aws.Int64(int64(query.Period)) mdq.Expression = aws.String(query.SqlExpression) - case GMDApiModeInferredSearchExpression: + case models.GMDApiModeInferredSearchExpression: mdq.Expression = aws.String(buildSearchExpression(query, query.Statistic)) - case GMDApiModeMetricStat: + case models.GMDApiModeMetricStat: mdq.MetricStat = &cloudwatch.MetricStat{ Metric: &cloudwatch.Metric{ Namespace: aws.String(query.Namespace), @@ -58,7 +59,7 @@ func (e *cloudWatchExecutor) buildMetricDataQuery(query *cloudWatchQuery) (*clou return mdq, nil } -func buildSearchExpression(query *cloudWatchQuery, stat string) string { +func buildSearchExpression(query *models.CloudWatchQuery, stat string) string { knownDimensions := make(map[string][]string) dimensionNames := []string{} dimensionNamesWithoutKnownValues := []string{} diff --git a/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go b/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go index 4b8c5cd92da..2815f5a7d10 100644 --- a/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go +++ b/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,8 +14,8 @@ func TestMetricDataQueryBuilder(t *testing.T) { t.Run("should use metric stat", func(t *testing.T) { executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) query := getBaseQuery() - query.MetricEditorMode = MetricEditorModeBuilder - query.MetricQueryType = MetricQueryTypeSearch + query.MetricEditorMode = models.MetricEditorModeBuilder + query.MetricQueryType = models.MetricQueryTypeSearch mdq, err := executor.buildMetricDataQuery(query) require.NoError(t, err) require.Empty(t, mdq.Expression) @@ -25,8 +26,8 @@ func TestMetricDataQueryBuilder(t *testing.T) { t.Run("should use custom built expression", func(t *testing.T) { executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) query := getBaseQuery() - query.MetricEditorMode = MetricEditorModeBuilder - query.MetricQueryType = MetricQueryTypeSearch + query.MetricEditorMode = models.MetricEditorModeBuilder + query.MetricQueryType = models.MetricQueryTypeSearch query.MatchExact = false mdq, err := executor.buildMetricDataQuery(query) require.NoError(t, err) @@ -37,8 +38,8 @@ func TestMetricDataQueryBuilder(t *testing.T) { t.Run("should use sql expression", func(t *testing.T) { executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) query := getBaseQuery() - query.MetricEditorMode = MetricEditorModeRaw - query.MetricQueryType = MetricQueryTypeQuery + query.MetricEditorMode = models.MetricEditorModeRaw + query.MetricQueryType = models.MetricQueryTypeQuery query.SqlExpression = `SELECT SUM(CPUUTilization) FROM "AWS/EC2"` mdq, err := executor.buildMetricDataQuery(query) require.NoError(t, err) @@ -49,8 +50,8 @@ func TestMetricDataQueryBuilder(t *testing.T) { t.Run("should use user defined math expression", func(t *testing.T) { executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) query := getBaseQuery() - query.MetricEditorMode = MetricEditorModeRaw - query.MetricQueryType = MetricQueryTypeSearch + query.MetricEditorMode = models.MetricEditorModeRaw + query.MetricQueryType = models.MetricQueryTypeSearch query.Expression = `SUM(x+y)` mdq, err := executor.buildMetricDataQuery(query) require.NoError(t, err) @@ -61,8 +62,8 @@ func TestMetricDataQueryBuilder(t *testing.T) { t.Run("should set period in user defined expression", func(t *testing.T) { executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures()) query := getBaseQuery() - query.MetricEditorMode = MetricEditorModeRaw - query.MetricQueryType = MetricQueryTypeSearch + query.MetricEditorMode = models.MetricEditorModeRaw + query.MetricQueryType = models.MetricQueryTypeSearch query.MatchExact = false query.Expression = `SUM([a,b])` mdq, err := executor.buildMetricDataQuery(query) @@ -116,7 +117,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { const matchExact = true t.Run("Query has three dimension values for a given dimension key", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -132,7 +133,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has three dimension values for two given dimension keys", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -149,7 +150,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("No OR operator was added if a star was used for dimension value", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -165,7 +166,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has one dimension key with a * value", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -181,7 +182,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has three dimension values for two given dimension keys, and one value is a star", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -198,7 +199,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has a dimension key with a space", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/Kafka", MetricName: "CpuUser", Dimensions: map[string][]string{ @@ -214,7 +215,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has a custom namespace contains spaces", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "Test-API Cache by Minute", MetricName: "CpuUser", Dimensions: map[string][]string{ @@ -235,7 +236,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { const matchExact = false t.Run("Query has three dimension values for a given dimension key", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -251,7 +252,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has three dimension values for two given dimension keys", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -268,7 +269,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has one dimension key with a * value", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -284,7 +285,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("query has three dimension values for two given dimension keys, and one value is a star", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -302,7 +303,7 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) t.Run("Query has invalid characters in dimension values", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ @@ -318,8 +319,8 @@ func TestMetricDataQueryBuilder(t *testing.T) { }) } -func getBaseQuery() *cloudWatchQuery { - query := &cloudWatchQuery{ +func getBaseQuery() *models.CloudWatchQuery { + query := &models.CloudWatchQuery{ Namespace: "AWS/EC2", MetricName: "CPUUtilization", Dimensions: map[string][]string{ diff --git a/pkg/tsdb/cloudwatch/cloudwatch_query.go b/pkg/tsdb/cloudwatch/models/cloudwatch_query.go similarity index 73% rename from pkg/tsdb/cloudwatch/cloudwatch_query.go rename to pkg/tsdb/cloudwatch/models/cloudwatch_query.go index cc7f56003e7..77e0cec2224 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch_query.go +++ b/pkg/tsdb/cloudwatch/models/cloudwatch_query.go @@ -1,4 +1,4 @@ -package cloudwatch +package models import ( "encoding/json" @@ -10,7 +10,30 @@ import ( "github.com/grafana/grafana/pkg/tsdb/cloudwatch/cwlog" ) -type cloudWatchQuery struct { +type ( + MetricEditorMode uint32 + MetricQueryType uint32 + GMDApiMode uint32 +) + +const ( + MetricEditorModeBuilder MetricEditorMode = iota + MetricEditorModeRaw +) + +const ( + MetricQueryTypeSearch MetricQueryType = iota + MetricQueryTypeQuery +) + +const ( + GMDApiModeMetricStat GMDApiMode = iota + GMDApiModeInferredSearchExpression + GMDApiModeMathExpression + GMDApiModeSQLExpression +) + +type CloudWatchQuery struct { RefId string Region string Id string @@ -27,13 +50,13 @@ type cloudWatchQuery struct { MatchExact bool UsedExpression string TimezoneUTCOffset string - MetricQueryType metricQueryType - MetricEditorMode metricEditorMode + MetricQueryType MetricQueryType + MetricEditorMode MetricEditorMode } -func (q *cloudWatchQuery) getGMDAPIMode() gmdApiMode { +func (q *CloudWatchQuery) GetGMDAPIMode() GMDApiMode { if q.MetricQueryType == MetricQueryTypeSearch && q.MetricEditorMode == MetricEditorModeBuilder { - if q.isInferredSearchExpression() { + if q.IsInferredSearchExpression() { return GMDApiModeInferredSearchExpression } return GMDApiModeMetricStat @@ -43,23 +66,23 @@ func (q *cloudWatchQuery) getGMDAPIMode() gmdApiMode { return GMDApiModeSQLExpression } - cwlog.Warn("Could not resolve CloudWatch metric query type. Falling back to metric stat.", "query", q) + cwlog.Warn("could not resolve CloudWatch metric query type. Falling back to metric stat.", "query", q) return GMDApiModeMetricStat } -func (q *cloudWatchQuery) isMathExpression() bool { - return q.MetricQueryType == MetricQueryTypeSearch && q.MetricEditorMode == MetricEditorModeRaw && !q.isUserDefinedSearchExpression() +func (q *CloudWatchQuery) IsMathExpression() bool { + return q.MetricQueryType == MetricQueryTypeSearch && q.MetricEditorMode == MetricEditorModeRaw && !q.IsUserDefinedSearchExpression() } -func (q *cloudWatchQuery) isSearchExpression() bool { - return q.MetricQueryType == MetricQueryTypeSearch && (q.isUserDefinedSearchExpression() || q.isInferredSearchExpression()) +func (q *CloudWatchQuery) isSearchExpression() bool { + return q.MetricQueryType == MetricQueryTypeSearch && (q.IsUserDefinedSearchExpression() || q.IsInferredSearchExpression()) } -func (q *cloudWatchQuery) isUserDefinedSearchExpression() bool { +func (q *CloudWatchQuery) IsUserDefinedSearchExpression() bool { return q.MetricQueryType == MetricQueryTypeSearch && q.MetricEditorMode == MetricEditorModeRaw && strings.Contains(q.Expression, "SEARCH(") } -func (q *cloudWatchQuery) isInferredSearchExpression() bool { +func (q *CloudWatchQuery) IsInferredSearchExpression() bool { if q.MetricQueryType != MetricQueryTypeSearch || q.MetricEditorMode != MetricEditorModeBuilder { return false } @@ -84,7 +107,7 @@ func (q *cloudWatchQuery) isInferredSearchExpression() bool { return false } -func (q *cloudWatchQuery) isMultiValuedDimensionExpression() bool { +func (q *CloudWatchQuery) IsMultiValuedDimensionExpression() bool { if q.MetricQueryType != MetricQueryTypeSearch || q.MetricEditorMode != MetricEditorModeBuilder { return false } @@ -104,8 +127,8 @@ func (q *cloudWatchQuery) isMultiValuedDimensionExpression() bool { return false } -func (q *cloudWatchQuery) buildDeepLink(startTime time.Time, endTime time.Time, dynamicLabelEnabled bool) (string, error) { - if q.isMathExpression() || q.MetricQueryType == MetricQueryTypeQuery { +func (q *CloudWatchQuery) BuildDeepLink(startTime time.Time, endTime time.Time, dynamicLabelEnabled bool) (string, error) { + if q.IsMathExpression() || q.MetricQueryType == MetricQueryTypeQuery { return "", nil } diff --git a/pkg/tsdb/cloudwatch/cloudwatch_query_test.go b/pkg/tsdb/cloudwatch/models/cloudwatch_query_test.go similarity index 83% rename from pkg/tsdb/cloudwatch/cloudwatch_query_test.go rename to pkg/tsdb/cloudwatch/models/cloudwatch_query_test.go index 7fe0a10d1e3..b9a29702128 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch_query_test.go +++ b/pkg/tsdb/cloudwatch/models/cloudwatch_query_test.go @@ -1,4 +1,4 @@ -package cloudwatch +package models import ( "testing" @@ -13,7 +13,7 @@ func TestCloudWatchQuery(t *testing.T) { t.Run("is not generated for MetricQueryTypeQuery", func(t *testing.T) { startTime := time.Now() endTime := startTime.Add(2 * time.Hour) - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -28,7 +28,7 @@ func TestCloudWatchQuery(t *testing.T) { MetricEditorMode: MetricEditorModeBuilder, } - deepLink, err := query.buildDeepLink(startTime, endTime, false) + deepLink, err := query.BuildDeepLink(startTime, endTime, false) require.NoError(t, err) assert.Empty(t, deepLink) }) @@ -36,7 +36,7 @@ func TestCloudWatchQuery(t *testing.T) { t.Run("does not include label in case dynamic label is diabled", func(t *testing.T) { startTime := time.Now() endTime := startTime.Add(2 * time.Hour) - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -52,7 +52,7 @@ func TestCloudWatchQuery(t *testing.T) { MetricEditorMode: MetricEditorModeBuilder, } - deepLink, err := query.buildDeepLink(startTime, endTime, false) + deepLink, err := query.BuildDeepLink(startTime, endTime, false) require.NoError(t, err) assert.NotContains(t, deepLink, "label") }) @@ -60,7 +60,7 @@ func TestCloudWatchQuery(t *testing.T) { t.Run("includes label in case dynamic label is enabled and it's a metric stat query", func(t *testing.T) { startTime := time.Now() endTime := startTime.Add(2 * time.Hour) - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -76,7 +76,7 @@ func TestCloudWatchQuery(t *testing.T) { MetricEditorMode: MetricEditorModeBuilder, } - deepLink, err := query.buildDeepLink(startTime, endTime, false) + deepLink, err := query.BuildDeepLink(startTime, endTime, false) require.NoError(t, err) assert.NotContains(t, deepLink, "label") }) @@ -84,7 +84,7 @@ func TestCloudWatchQuery(t *testing.T) { t.Run("includes label in case dynamic label is enabled and it's a math expression query", func(t *testing.T) { startTime := time.Now() endTime := startTime.Add(2 * time.Hour) - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Statistic: "Average", @@ -97,14 +97,14 @@ func TestCloudWatchQuery(t *testing.T) { MetricEditorMode: MetricEditorModeRaw, } - deepLink, err := query.buildDeepLink(startTime, endTime, false) + deepLink, err := query.BuildDeepLink(startTime, endTime, false) require.NoError(t, err) assert.NotContains(t, deepLink, "label") }) }) t.Run("SEARCH(someexpression) was specified in the query editor", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "SEARCH(someexpression)", @@ -114,11 +114,11 @@ func TestCloudWatchQuery(t *testing.T) { } assert.True(t, query.isSearchExpression(), "Expected a search expression") - assert.False(t, query.isMathExpression(), "Expected not math expression") + assert.False(t, query.IsMathExpression(), "Expected not math expression") }) t.Run("No expression, no multi dimension key values and no * was used", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -132,11 +132,11 @@ func TestCloudWatchQuery(t *testing.T) { } assert.False(t, query.isSearchExpression(), "Expected not a search expression") - assert.False(t, query.isMathExpression(), "Expected not math expressions") + assert.False(t, query.IsMathExpression(), "Expected not math expressions") }) t.Run("No expression but multi dimension key values exist", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -149,11 +149,11 @@ func TestCloudWatchQuery(t *testing.T) { } assert.True(t, query.isSearchExpression(), "Expected a search expression") - assert.False(t, query.isMathExpression(), "Expected not math expressions") + assert.False(t, query.IsMathExpression(), "Expected not math expressions") }) t.Run("No expression but dimension values has *", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -167,11 +167,11 @@ func TestCloudWatchQuery(t *testing.T) { } assert.True(t, query.isSearchExpression(), "Expected a search expression") - assert.False(t, query.isMathExpression(), "Expected not math expression") + assert.False(t, query.IsMathExpression(), "Expected not math expression") }) t.Run("Query has a multi-valued dimension", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -185,11 +185,11 @@ func TestCloudWatchQuery(t *testing.T) { } assert.True(t, query.isSearchExpression(), "Expected a search expression") - assert.True(t, query.isMultiValuedDimensionExpression(), "Expected a multi-valued dimension expression") + assert.True(t, query.IsMultiValuedDimensionExpression(), "Expected a multi-valued dimension expression") }) t.Run("No dimensions were added", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -202,18 +202,18 @@ func TestCloudWatchQuery(t *testing.T) { t.Run("Match exact is false", func(t *testing.T) { query.MatchExact = false assert.True(t, query.isSearchExpression(), "Expected a search expression") - assert.False(t, query.isMathExpression(), "Expected not math expression") + assert.False(t, query.IsMathExpression(), "Expected not math expression") }) t.Run("Match exact is true", func(t *testing.T) { query.MatchExact = true assert.False(t, query.isSearchExpression(), "Exxpected not search expression") - assert.False(t, query.isMathExpression(), "Expected not math expression") + assert.False(t, query.IsMathExpression(), "Expected not math expression") }) }) t.Run("Match exact is", func(t *testing.T) { - query := &cloudWatchQuery{ + query := &CloudWatchQuery{ RefId: "A", Region: "us-east-1", Expression: "", @@ -227,6 +227,6 @@ func TestCloudWatchQuery(t *testing.T) { } assert.True(t, query.isSearchExpression(), "Expected search expression") - assert.False(t, query.isMathExpression(), "Expected not math expression") + assert.False(t, query.IsMathExpression(), "Expected not math expression") }) } diff --git a/pkg/tsdb/cloudwatch/models/query_error.go b/pkg/tsdb/cloudwatch/models/query_error.go new file mode 100644 index 00000000000..8ad21fff1a7 --- /dev/null +++ b/pkg/tsdb/cloudwatch/models/query_error.go @@ -0,0 +1,12 @@ +package models + +import "fmt" + +type QueryError struct { + Err error + RefID string +} + +func (e *QueryError) Error() string { + return fmt.Sprintf("error parsing query %q, %s", e.RefID, e.Err) +} diff --git a/pkg/tsdb/cloudwatch/request_parser.go b/pkg/tsdb/cloudwatch/models/request_parser.go similarity index 74% rename from pkg/tsdb/cloudwatch/request_parser.go rename to pkg/tsdb/cloudwatch/models/request_parser.go index b9b0fe882dd..7543234433b 100644 --- a/pkg/tsdb/cloudwatch/request_parser.go +++ b/pkg/tsdb/cloudwatch/models/request_parser.go @@ -1,4 +1,4 @@ -package cloudwatch +package models import ( "encoding/json" @@ -13,12 +13,15 @@ import ( "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/cwlog" ) +const timeSeriesQuery = "timeSeriesQuery" + var validMetricDataID = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`) -type QueryJson struct { +type metricsDataQuery struct { Datasource map[string]string `json:"datasource,omitempty"` Dimensions map[string]interface{} `json:"dimensions,omitempty"` Expression string `json:"expression,omitempty"` @@ -28,7 +31,7 @@ type QueryJson struct { MaxDataPoints int `json:"maxDataPoints,omitempty"` MetricEditorMode *int `json:"metricEditorMode,omitempty"` MetricName string `json:"metricName,omitempty"` - MetricQueryType metricQueryType `json:"metricQueryType,omitempty"` + MetricQueryType MetricQueryType `json:"metricQueryType,omitempty"` Namespace string `json:"namespace,omitempty"` Period string `json:"period,omitempty"` RefId string `json:"refId,omitempty"` @@ -42,44 +45,44 @@ type QueryJson struct { Alias string `json:"alias,omitempty"` } -// parseQueries parses the json queries and returns a map of cloudWatchQueries by region. The cloudWatchQuery has a 1 to 1 mapping to a query editor row -func parseQueries(queries []backend.DataQuery, startTime time.Time, endTime time.Time, dynamicLabelsEnabled bool) (map[string][]*cloudWatchQuery, error) { - requestQueries := make(map[string][]*cloudWatchQuery) +// ParseQueries parses the json queries and returns a map of cloudWatchQueries by region. The cloudWatchQuery has a 1 to 1 mapping to a query editor row +func ParseQueries(queries []backend.DataQuery, startTime time.Time, endTime time.Time, dynamicLabelsEnabled bool) (map[string][]*CloudWatchQuery, error) { + result := make(map[string][]*CloudWatchQuery) migratedQueries, err := migrateLegacyQuery(queries, dynamicLabelsEnabled) if err != nil { return nil, err } for _, query := range migratedQueries { - var model QueryJson - err := json.Unmarshal(query.JSON, &model) + var metricsDataQuery metricsDataQuery + err := json.Unmarshal(query.JSON, &metricsDataQuery) if err != nil { - return nil, &queryError{err: err, RefID: query.RefID} + return nil, &QueryError{Err: err, RefID: query.RefID} } - queryType := model.QueryType + queryType := metricsDataQuery.QueryType if queryType != timeSeriesQuery && queryType != "" { continue } - if model.MatchExact == nil { + if metricsDataQuery.MatchExact == nil { trueBooleanValue := true - model.MatchExact = &trueBooleanValue + metricsDataQuery.MatchExact = &trueBooleanValue } refID := query.RefID - query, err := parseRequestQuery(model, refID, startTime, endTime) + query, err := parseRequestQuery(metricsDataQuery, refID, startTime, endTime) if err != nil { - return nil, &queryError{err: err, RefID: refID} + return nil, &QueryError{Err: err, RefID: refID} } - if _, exist := requestQueries[query.Region]; !exist { - requestQueries[query.Region] = []*cloudWatchQuery{} + if _, exist := result[query.Region]; !exist { + result[query.Region] = []*CloudWatchQuery{} } - requestQueries[query.Region] = append(requestQueries[query.Region], query) + result[query.Region] = append(result[query.Region], query) } - return requestQueries, nil + return result, nil } // migrateLegacyQuery is also done in the frontend, so this should only ever be needed for alerting queries @@ -87,7 +90,7 @@ func migrateLegacyQuery(queries []backend.DataQuery, dynamicLabelsEnabled bool) migratedQueries := []*backend.DataQuery{} for _, q := range queries { query := q - var queryJson *QueryJson + var queryJson *metricsDataQuery err := json.Unmarshal(query.JSON, &queryJson) if err != nil { return nil, err @@ -114,7 +117,7 @@ func migrateLegacyQuery(queries []backend.DataQuery, dynamicLabelsEnabled bool) // migrateStatisticsToStatistic migrates queries that has a `statistics` field to use the `statistic` field instead. // In case the query used more than one stat, the first stat in the slice will be used in the statistic field // Read more here https://github.com/grafana/grafana/issues/30629 -func migrateStatisticsToStatistic(queryJson *QueryJson) error { +func migrateStatisticsToStatistic(queryJson *metricsDataQuery) error { // If there's not a statistic property in the json, we know it's the legacy format and then it has to be migrated if queryJson.Statistic == nil { if queryJson.Statistics == nil { @@ -139,7 +142,7 @@ var aliasPatterns = map[string]string{ var legacyAliasRegexp = regexp.MustCompile(`{{\s*(.+?)\s*}}`) -func migrateAliasToDynamicLabel(queryJson *QueryJson) { +func migrateAliasToDynamicLabel(queryJson *metricsDataQuery) { fullAliasField := queryJson.Alias if fullAliasField != "" { @@ -158,33 +161,33 @@ func migrateAliasToDynamicLabel(queryJson *QueryJson) { queryJson.Label = &fullAliasField } -func parseRequestQuery(model QueryJson, refId string, startTime time.Time, endTime time.Time) (*cloudWatchQuery, error) { - cwlog.Debug("Parsing request query", "query", model) - cloudWatchQuery := cloudWatchQuery{ - Alias: model.Alias, +func parseRequestQuery(dataQuery metricsDataQuery, refId string, startTime time.Time, endTime time.Time) (*CloudWatchQuery, error) { + cwlog.Debug("Parsing request query", "query", dataQuery) + result := CloudWatchQuery{ + Alias: dataQuery.Alias, Label: "", MatchExact: true, Statistic: "", ReturnData: true, UsedExpression: "", RefId: refId, - Id: model.Id, - Region: model.Region, - Namespace: model.Namespace, - MetricName: model.MetricName, - MetricQueryType: model.MetricQueryType, - SqlExpression: model.SqlExpression, - TimezoneUTCOffset: model.TimezoneUTCOffset, - Expression: model.Expression, + Id: dataQuery.Id, + Region: dataQuery.Region, + Namespace: dataQuery.Namespace, + MetricName: dataQuery.MetricName, + MetricQueryType: dataQuery.MetricQueryType, + SqlExpression: dataQuery.SqlExpression, + TimezoneUTCOffset: dataQuery.TimezoneUTCOffset, + Expression: dataQuery.Expression, } reNumber := regexp.MustCompile(`^\d+$`) - dimensions, err := parseDimensions(model.Dimensions) + dimensions, err := parseDimensions(dataQuery.Dimensions) if err != nil { return nil, fmt.Errorf("failed to parse dimensions: %v", err) } - cloudWatchQuery.Dimensions = dimensions + result.Dimensions = dimensions - p := model.Period + p := dataQuery.Period var period int if strings.ToLower(p) == "auto" || p == "" { deltaInSeconds := endTime.Sub(startTime).Seconds() @@ -211,55 +214,55 @@ func parseRequestQuery(model QueryJson, refId string, startTime time.Time, endTi period = int(d.Seconds()) } } - cloudWatchQuery.Period = period + result.Period = period - if model.Id == "" { + if dataQuery.Id == "" { // Why not just use refId if id is not specified in the frontend? When specifying an id in the editor, // and alphabetical must be used. The id must be unique, so if an id like for example a, b or c would be used, // it would likely collide with some ref id. That's why the `query` prefix is used. suffix := refId if !validMetricDataID.MatchString(suffix) { - uuid := uuid.NewString() - suffix = strings.Replace(uuid, "-", "", -1) + newUUID := uuid.NewString() + suffix = strings.Replace(newUUID, "-", "", -1) } - cloudWatchQuery.Id = fmt.Sprintf("query%s", suffix) + result.Id = fmt.Sprintf("query%s", suffix) } - if model.Hide != nil { - cloudWatchQuery.ReturnData = !*model.Hide + if dataQuery.Hide != nil { + result.ReturnData = !*dataQuery.Hide } - if model.QueryType == "" { + if dataQuery.QueryType == "" { // If no type is provided we assume we are called by alerting service, which requires to return data! // Note, this is sort of a hack, but the official Grafana interfaces do not carry the information // who (which service) called the TsdbQueryEndpoint.Query(...) function. - cloudWatchQuery.ReturnData = true + result.ReturnData = true } - if model.MetricEditorMode == nil && len(model.Expression) > 0 { + if dataQuery.MetricEditorMode == nil && len(dataQuery.Expression) > 0 { // this should only ever happen if this is an alerting query that has not yet been migrated in the frontend - cloudWatchQuery.MetricEditorMode = MetricEditorModeRaw + result.MetricEditorMode = MetricEditorModeRaw } else { - if model.MetricEditorMode != nil { - cloudWatchQuery.MetricEditorMode = metricEditorMode(*model.MetricEditorMode) + if dataQuery.MetricEditorMode != nil { + result.MetricEditorMode = MetricEditorMode(*dataQuery.MetricEditorMode) } else { - cloudWatchQuery.MetricEditorMode = metricEditorMode(0) + result.MetricEditorMode = MetricEditorMode(0) } } - if model.Statistic != nil { - cloudWatchQuery.Statistic = *model.Statistic + if dataQuery.Statistic != nil { + result.Statistic = *dataQuery.Statistic } - if model.MatchExact != nil { - cloudWatchQuery.MatchExact = *model.MatchExact + if dataQuery.MatchExact != nil { + result.MatchExact = *dataQuery.MatchExact } - if model.Label != nil { - cloudWatchQuery.Label = *model.Label + if dataQuery.Label != nil { + result.Label = *dataQuery.Label } - return &cloudWatchQuery, nil + return &result, nil } func getRetainedPeriods(timeSince time.Duration) []int { diff --git a/pkg/tsdb/cloudwatch/request_parser_test.go b/pkg/tsdb/cloudwatch/models/request_parser_test.go similarity index 97% rename from pkg/tsdb/cloudwatch/request_parser_test.go rename to pkg/tsdb/cloudwatch/models/request_parser_test.go index 3d913caf3ab..28f62abd076 100644 --- a/pkg/tsdb/cloudwatch/request_parser_test.go +++ b/pkg/tsdb/cloudwatch/models/request_parser_test.go @@ -1,4 +1,4 @@ -package cloudwatch +package models import ( "encoding/json" @@ -15,7 +15,7 @@ func TestQueryJSON(t *testing.T) { jsonString := []byte(`{ "type": "timeSeriesQuery" }`) - var res QueryJson + var res metricsDataQuery err := json.Unmarshal(jsonString, &res) require.NoError(t, err) assert.Equal(t, "timeSeriesQuery", res.QueryType) @@ -49,7 +49,7 @@ func TestRequestParser(t *testing.T) { migratedQuery := migratedQueries[0] assert.Equal(t, "A", migratedQuery.RefID) - var model QueryJson + var model metricsDataQuery err = json.Unmarshal(migratedQuery.JSON, &model) require.NoError(t, err) assert.Equal(t, "Average", *model.Statistic) @@ -57,7 +57,7 @@ func TestRequestParser(t *testing.T) { }) t.Run("New dimensions structure", func(t *testing.T) { - query := QueryJson{ + query := metricsDataQuery{ RefId: "ref1", Region: "us-east-1", Namespace: "ec2", @@ -91,7 +91,7 @@ func TestRequestParser(t *testing.T) { }) t.Run("Old dimensions structure (backwards compatibility)", func(t *testing.T) { - query := QueryJson{ + query := metricsDataQuery{ RefId: "ref1", Region: "us-east-1", Namespace: "ec2", @@ -125,7 +125,7 @@ func TestRequestParser(t *testing.T) { }) t.Run("Period defined in the editor by the user is being used when time range is short", func(t *testing.T) { - query := QueryJson{ + query := metricsDataQuery{ RefId: "ref1", Region: "us-east-1", Namespace: "ec2", @@ -147,7 +147,7 @@ func TestRequestParser(t *testing.T) { }) t.Run("Period is parsed correctly if not defined by user", func(t *testing.T) { - query := QueryJson{ + query := metricsDataQuery{ RefId: "ref1", Region: "us-east-1", Namespace: "ec2", @@ -278,7 +278,7 @@ func TestRequestParser(t *testing.T) { require.NoError(t, err) assert.Equal(t, MetricQueryTypeSearch, res.MetricQueryType) assert.Equal(t, MetricEditorModeBuilder, res.MetricEditorMode) - assert.Equal(t, GMDApiModeMetricStat, res.getGMDAPIMode()) + assert.Equal(t, GMDApiModeMetricStat, res.GetGMDAPIMode()) }) t.Run("and an expression is specified it should be metric search builder", func(t *testing.T) { @@ -288,7 +288,7 @@ func TestRequestParser(t *testing.T) { require.NoError(t, err) assert.Equal(t, MetricQueryTypeSearch, res.MetricQueryType) assert.Equal(t, MetricEditorModeRaw, res.MetricEditorMode) - assert.Equal(t, GMDApiModeMathExpression, res.getGMDAPIMode()) + assert.Equal(t, GMDApiModeMathExpression, res.GetGMDAPIMode()) }) }) @@ -299,7 +299,7 @@ func TestRequestParser(t *testing.T) { require.NoError(t, err) assert.Equal(t, MetricQueryTypeSearch, res.MetricQueryType) assert.Equal(t, MetricEditorModeRaw, res.MetricEditorMode) - assert.Equal(t, GMDApiModeMathExpression, res.getGMDAPIMode()) + assert.Equal(t, GMDApiModeMathExpression, res.GetGMDAPIMode()) }) }) @@ -363,9 +363,9 @@ func TestRequestParser(t *testing.T) { }) } -func getBaseJsonQuery() QueryJson { +func getBaseJsonQuery() metricsDataQuery { average := "Average" - return QueryJson{ + return metricsDataQuery{ RefId: "ref1", Region: "us-east-1", Namespace: "ec2", @@ -396,7 +396,7 @@ func Test_migrateAliasToDynamicLabel_single_query_preserves_old_alias_and_create average := "Average" false := false - queryToMigrate := QueryJson{ + queryToMigrate := metricsDataQuery{ Region: "us-east-1", Namespace: "ec2", MetricName: "CPUUtilization", @@ -411,7 +411,7 @@ func Test_migrateAliasToDynamicLabel_single_query_preserves_old_alias_and_create migrateAliasToDynamicLabel(&queryToMigrate) - expected := QueryJson{ + expected := metricsDataQuery{ Alias: tc.inputAlias, Dimensions: map[string]interface{}{ "InstanceId": []interface{}{"test"}, diff --git a/pkg/tsdb/cloudwatch/models/types.go b/pkg/tsdb/cloudwatch/models/types.go new file mode 100644 index 00000000000..a3835e078dc --- /dev/null +++ b/pkg/tsdb/cloudwatch/models/types.go @@ -0,0 +1,22 @@ +package models + +type cloudWatchLink struct { + View string `json:"view"` + Stacked bool `json:"stacked"` + Title string `json:"title"` + Start string `json:"start"` + End string `json:"end"` + Region string `json:"region"` + Metrics []interface{} `json:"metrics"` +} + +type metricExpression struct { + Expression string `json:"expression"` + Label string `json:"label,omitempty"` +} + +type metricStatMeta struct { + Stat string `json:"stat"` + Period int `json:"period"` + Label string `json:"label,omitempty"` +} diff --git a/pkg/tsdb/cloudwatch/response_parser.go b/pkg/tsdb/cloudwatch/response_parser.go index 37d5627de11..993fb4e2894 100644 --- a/pkg/tsdb/cloudwatch/response_parser.go +++ b/pkg/tsdb/cloudwatch/response_parser.go @@ -11,12 +11,13 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" ) func (e *cloudWatchExecutor) parseResponse(startTime time.Time, endTime time.Time, metricDataOutputs []*cloudwatch.GetMetricDataOutput, - queries []*cloudWatchQuery) ([]*responseWrapper, error) { + queries []*models.CloudWatchQuery) ([]*responseWrapper, error) { aggregatedResponse := aggregateResponse(metricDataOutputs) - queriesById := map[string]*cloudWatchQuery{} + queriesById := map[string]*models.CloudWatchQuery{} for _, query := range queries { queriesById[query.Id] = query } @@ -87,7 +88,7 @@ func aggregateResponse(getMetricDataOutputs []*cloudwatch.GetMetricDataOutput) m return responseByID } -func getLabels(cloudwatchLabel string, query *cloudWatchQuery) data.Labels { +func getLabels(cloudwatchLabel string, query *models.CloudWatchQuery) data.Labels { dims := make([]string, 0, len(query.Dimensions)) for k := range query.Dimensions { dims = append(dims, k) @@ -112,19 +113,19 @@ func getLabels(cloudwatchLabel string, query *cloudWatchQuery) data.Labels { } func buildDataFrames(startTime time.Time, endTime time.Time, aggregatedResponse queryRowResponse, - query *cloudWatchQuery, dynamicLabelEnabled bool) (data.Frames, error) { + query *models.CloudWatchQuery, dynamicLabelEnabled bool) (data.Frames, error) { frames := data.Frames{} for _, metric := range aggregatedResponse.Metrics { label := *metric.Label - deepLink, err := query.buildDeepLink(startTime, endTime, dynamicLabelEnabled) + deepLink, err := query.BuildDeepLink(startTime, endTime, dynamicLabelEnabled) if err != nil { return nil, err } // In case a multi-valued dimension is used and the cloudwatch query yields no values, create one empty time // series for each dimension value. Use that dimension value to expand the alias field - if len(metric.Values) == 0 && query.isMultiValuedDimensionExpression() { + if len(metric.Values) == 0 && query.IsMultiValuedDimensionExpression() { series := 0 multiValuedDimension := "" for key, values := range query.Dimensions { @@ -221,45 +222,45 @@ func buildDataFrames(startTime time.Time, endTime time.Time, aggregatedResponse return frames, nil } -func formatAlias(query *cloudWatchQuery, stat string, dimensions map[string]string, label string) string { +func formatAlias(query *models.CloudWatchQuery, stat string, dimensions map[string]string, label string) string { region := query.Region namespace := query.Namespace metricName := query.MetricName period := strconv.Itoa(query.Period) - if query.isUserDefinedSearchExpression() { + if query.IsUserDefinedSearchExpression() { pIndex := strings.LastIndex(query.Expression, ",") period = strings.Trim(query.Expression[pIndex+1:], " )") sIndex := strings.LastIndex(query.Expression[:pIndex], ",") stat = strings.Trim(query.Expression[sIndex+1:pIndex], " '") } - if len(query.Alias) == 0 && query.isMathExpression() { + if len(query.Alias) == 0 && query.IsMathExpression() { return query.Id } - if len(query.Alias) == 0 && query.isInferredSearchExpression() && !query.isMultiValuedDimensionExpression() { + if len(query.Alias) == 0 && query.IsInferredSearchExpression() && !query.IsMultiValuedDimensionExpression() { return label } - if len(query.Alias) == 0 && query.MetricQueryType == MetricQueryTypeQuery { + if len(query.Alias) == 0 && query.MetricQueryType == models.MetricQueryTypeQuery { return label } // common fields - data := map[string]string{ + commonFields := map[string]string{ "region": region, "period": period, } if len(label) != 0 { - data["label"] = label + commonFields["label"] = label } // since the SQL query string is not (yet) parsed, we don't know what namespace, metric, statistic and labels it's using at this point - if query.MetricQueryType != MetricQueryTypeQuery { - data["namespace"] = namespace - data["metric"] = metricName - data["stat"] = stat + if query.MetricQueryType != models.MetricQueryTypeQuery { + commonFields["namespace"] = namespace + commonFields["metric"] = metricName + commonFields["stat"] = stat for k, v := range dimensions { - data[k] = v + commonFields[k] = v } } @@ -267,7 +268,7 @@ func formatAlias(query *cloudWatchQuery, stat string, dimensions map[string]stri labelName := strings.Replace(string(in), "{{", "", 1) labelName = strings.Replace(labelName, "}}", "", 1) labelName = strings.TrimSpace(labelName) - if val, exists := data[labelName]; exists { + if val, exists := commonFields[labelName]; exists { return []byte(val) } @@ -293,7 +294,7 @@ func createDataLinks(link string) []data.DataLink { return dataLinks } -func createMeta(query *cloudWatchQuery) *data.FrameMeta { +func createMeta(query *models.CloudWatchQuery) *data.FrameMeta { return &data.FrameMeta{ ExecutedQueryString: query.UsedExpression, Custom: fmt.Sprintf(`{ diff --git a/pkg/tsdb/cloudwatch/response_parser_test.go b/pkg/tsdb/cloudwatch/response_parser_test.go index 24663140e41..9cb2ff2980f 100644 --- a/pkg/tsdb/cloudwatch/response_parser_test.go +++ b/pkg/tsdb/cloudwatch/response_parser_test.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -148,7 +149,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, } - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ RefId: "refId1", Region: "us-east-1", Namespace: "AWS/ApplicationELB", @@ -160,8 +161,8 @@ func TestCloudWatchResponseParser(t *testing.T) { Statistic: "Average", Period: 60, Alias: "{{LoadBalancer}} Expanded", - MetricQueryType: MetricQueryTypeSearch, - MetricEditorMode: MetricEditorModeBuilder, + MetricQueryType: models.MetricQueryTypeSearch, + MetricEditorMode: models.MetricEditorModeBuilder, } frames, err := buildDataFrames(startTime, endTime, *response, query, false) require.NoError(t, err) @@ -211,7 +212,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, }} - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ RefId: "refId1", Region: "us-east-1", Namespace: "AWS/ApplicationELB", @@ -223,8 +224,8 @@ func TestCloudWatchResponseParser(t *testing.T) { Statistic: "Average", Period: 60, Alias: "{{LoadBalancer}} Expanded", - MetricQueryType: MetricQueryTypeSearch, - MetricEditorMode: MetricEditorModeBuilder, + MetricQueryType: models.MetricQueryTypeSearch, + MetricEditorMode: models.MetricEditorModeBuilder, } frames, err := buildDataFrames(startTime, endTime, *response, query, false) require.NoError(t, err) @@ -275,7 +276,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, } - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ RefId: "refId1", Region: "us-east-1", Namespace: "AWS/ApplicationELB", @@ -287,8 +288,8 @@ func TestCloudWatchResponseParser(t *testing.T) { Statistic: "Average", Period: 60, Alias: "{{LoadBalancer}} Expanded", - MetricQueryType: MetricQueryTypeSearch, - MetricEditorMode: MetricEditorModeBuilder, + MetricQueryType: models.MetricQueryTypeSearch, + MetricEditorMode: models.MetricEditorModeBuilder, } frames, err := buildDataFrames(startTime, endTime, *response, query, false) require.NoError(t, err) @@ -314,7 +315,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, }, } - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ RefId: "refId1", Region: "us-east-1", Namespace: "AWS/ApplicationELB", @@ -325,8 +326,8 @@ func TestCloudWatchResponseParser(t *testing.T) { Statistic: "Average", Period: 60, Alias: "{{LoadBalancer}} Expanded", - MetricQueryType: MetricQueryTypeSearch, - MetricEditorMode: MetricEditorModeBuilder, + MetricQueryType: models.MetricQueryTypeSearch, + MetricEditorMode: models.MetricEditorModeBuilder, } frames, err := buildDataFrames(startTime, endTime, *response, query, false) require.NoError(t, err) @@ -354,7 +355,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, } - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ RefId: "refId1", Region: "us-east-1", Namespace: "AWS/ApplicationELB", @@ -367,8 +368,8 @@ func TestCloudWatchResponseParser(t *testing.T) { Statistic: "Average", Period: 60, Alias: "{{LoadBalancer}} Expanded {{InstanceType}} - {{Resource}}", - MetricQueryType: MetricQueryTypeSearch, - MetricEditorMode: MetricEditorModeBuilder, + MetricQueryType: models.MetricQueryTypeSearch, + MetricEditorMode: models.MetricEditorModeBuilder, } frames, err := buildDataFrames(startTime, endTime, *response, query, false) require.NoError(t, err) @@ -394,7 +395,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, } - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ RefId: "refId1", Region: "us-east-1", Namespace: "AWS/ApplicationELB", @@ -407,8 +408,8 @@ func TestCloudWatchResponseParser(t *testing.T) { Statistic: "Average", Period: 60, Alias: "{{LoadBalancer}} {{InstanceType}} {{metric}} {{namespace}} {{stat}} {{region}} {{period}}", - MetricQueryType: MetricQueryTypeQuery, - MetricEditorMode: MetricEditorModeRaw, + MetricQueryType: models.MetricQueryTypeQuery, + MetricEditorMode: models.MetricEditorModeRaw, } frames, err := buildDataFrames(startTime, endTime, *response, query, false) require.NoError(t, err) @@ -444,7 +445,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, } - query := &cloudWatchQuery{ + query := &models.CloudWatchQuery{ RefId: "refId1", Region: "us-east-1", Namespace: "AWS/ApplicationELB", @@ -456,8 +457,8 @@ func TestCloudWatchResponseParser(t *testing.T) { Statistic: "Average", Period: 60, Alias: "{{namespace}}_{{metric}}_{{stat}}", - MetricQueryType: MetricQueryTypeSearch, - MetricEditorMode: MetricEditorModeBuilder, + MetricQueryType: models.MetricQueryTypeSearch, + MetricEditorMode: models.MetricEditorModeBuilder, } frames, err := buildDataFrames(startTime, endTime, *response, query, false) require.NoError(t, err) @@ -485,7 +486,7 @@ func TestCloudWatchResponseParser(t *testing.T) { }, } - frames, err := buildDataFrames(startTime, endTime, *response, &cloudWatchQuery{}, true) + frames, err := buildDataFrames(startTime, endTime, *response, &models.CloudWatchQuery{}, true) assert.NoError(t, err) require.Len(t, frames, 1) diff --git a/pkg/tsdb/cloudwatch/time_series_query.go b/pkg/tsdb/cloudwatch/time_series_query.go index 45b6c2c7ed3..4a2b3825ac9 100644 --- a/pkg/tsdb/cloudwatch/time_series_query.go +++ b/pkg/tsdb/cloudwatch/time_series_query.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/cwlog" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "golang.org/x/sync/errgroup" ) @@ -30,7 +31,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *ba return nil, fmt.Errorf("invalid time range: start time must be before end time") } - requestQueriesByRegion, err := parseQueries(req.Queries, startTime, endTime, e.features.IsEnabled(featuremgmt.FlagCloudWatchDynamicLabels)) + requestQueriesByRegion, err := models.ParseQueries(req.Queries, startTime, endTime, e.features.IsEnabled(featuremgmt.FlagCloudWatchDynamicLabels)) if err != nil { return nil, err } diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index c1863773507..08403336362 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -13,7 +13,9 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -143,16 +145,16 @@ type queryDimensions struct { } type queryParameters struct { - MetricQueryType metricQueryType `json:"metricQueryType"` - MetricEditorMode metricEditorMode `json:"metricEditorMode"` - Dimensions queryDimensions `json:"dimensions"` - Expression string `json:"expression"` - Alias string `json:"alias"` - Label *string `json:"label"` - Statistic string `json:"statistic"` - Period string `json:"period"` - MatchExact bool `json:"matchExact"` - MetricName string `json:"metricName"` + MetricQueryType models.MetricQueryType `json:"metricQueryType"` + MetricEditorMode models.MetricEditorMode `json:"metricEditorMode"` + Dimensions queryDimensions `json:"dimensions"` + Expression string `json:"expression"` + Alias string `json:"alias"` + Label *string `json:"label"` + Statistic string `json:"statistic"` + Period string `json:"period"` + MatchExact bool `json:"matchExact"` + MetricName string `json:"metricName"` } var queryId = "query id" @@ -161,11 +163,11 @@ func newTestQuery(t testing.TB, p queryParameters) json.RawMessage { t.Helper() tsq := struct { - Type string `json:"type"` - MetricQueryType metricQueryType `json:"metricQueryType"` - MetricEditorMode metricEditorMode `json:"metricEditorMode"` - Namespace string `json:"namespace"` - MetricName string `json:"metricName"` + Type string `json:"type"` + MetricQueryType models.MetricQueryType `json:"metricQueryType"` + MetricEditorMode models.MetricEditorMode `json:"metricEditorMode"` + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` Dimensions struct { InstanceID []string `json:"InstanceId,omitempty"` } `json:"dimensions"` @@ -315,8 +317,8 @@ func Test_QueryData_response_data_frame_names(t *testing.T) { t.Run("where user defines search expression and alias is defined, then frame name prioritizes period and stat from expression over input", func(t *testing.T) { query := newTestQuery(t, queryParameters{ - MetricQueryType: MetricQueryTypeSearch, // contributes to isUserDefinedSearchExpression = true - MetricEditorMode: MetricEditorModeRaw, // contributes to isUserDefinedSearchExpression = true + MetricQueryType: models.MetricQueryTypeSearch, // contributes to isUserDefinedSearchExpression = true + MetricEditorMode: models.MetricEditorModeRaw, // contributes to isUserDefinedSearchExpression = true Alias: "{{period}} {{stat}}", Expression: `SEARCH('{AWS/EC2,InstanceId} MetricName="CPUUtilization"', 'Average', 300)`, // period 300 and stat 'Average' parsed from this expression Statistic: "Maximum", // stat parsed from expression takes precedence over 'Maximum' @@ -340,8 +342,8 @@ func Test_QueryData_response_data_frame_names(t *testing.T) { t.Run("where no alias is provided and query is math expression, then frame name is queryId", func(t *testing.T) { query := newTestQuery(t, queryParameters{ - MetricQueryType: MetricQueryTypeSearch, - MetricEditorMode: MetricEditorModeRaw, + MetricQueryType: models.MetricQueryTypeSearch, + MetricEditorMode: models.MetricEditorModeRaw, }) resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{ @@ -361,7 +363,7 @@ func Test_QueryData_response_data_frame_names(t *testing.T) { t.Run("where no alias provided and query type is MetricQueryTypeQuery, then frame name is label", func(t *testing.T) { query := newTestQuery(t, queryParameters{ - MetricQueryType: MetricQueryTypeQuery, + MetricQueryType: models.MetricQueryTypeQuery, }) resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{ diff --git a/pkg/tsdb/cloudwatch/types.go b/pkg/tsdb/cloudwatch/types.go deleted file mode 100644 index c32967fd341..00000000000 --- a/pkg/tsdb/cloudwatch/types.go +++ /dev/null @@ -1,58 +0,0 @@ -package cloudwatch - -import ( - "fmt" -) - -type queryError struct { - err error - RefID string -} - -func (e *queryError) Error() string { - return fmt.Sprintf("error parsing query %q, %s", e.RefID, e.err) -} - -type cloudWatchLink struct { - View string `json:"view"` - Stacked bool `json:"stacked"` - Title string `json:"title"` - Start string `json:"start"` - End string `json:"end"` - Region string `json:"region"` - Metrics []interface{} `json:"metrics"` -} - -type metricExpression struct { - Expression string `json:"expression"` - Label string `json:"label,omitempty"` -} - -type metricStatMeta struct { - Stat string `json:"stat"` - Period int `json:"period"` - Label string `json:"label,omitempty"` -} - -type metricQueryType uint32 - -const ( - MetricQueryTypeSearch metricQueryType = iota - MetricQueryTypeQuery -) - -type metricEditorMode uint32 - -const ( - MetricEditorModeBuilder metricEditorMode = iota - MetricEditorModeRaw -) - -type gmdApiMode uint32 - -const ( - GMDApiModeMetricStat gmdApiMode = iota - GMDApiModeInferredSearchExpression - GMDApiModeMathExpression - GMDApiModeSQLExpression -)