diff --git a/pkg/tsdb/cloudwatch/annotation_query.go b/pkg/tsdb/cloudwatch/annotation_query.go index 5a439f5cbcd..30b33f0262a 100644 --- a/pkg/tsdb/cloudwatch/annotation_query.go +++ b/pkg/tsdb/cloudwatch/annotation_query.go @@ -28,15 +28,20 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont } var period int64 - if model.Period != "" { - p, err := strconv.ParseInt(model.Period, 10, 64) + + if model.Period != nil && *model.Period != "" { + p, err := strconv.ParseInt(*model.Period, 10, 64) if err != nil { return nil, err } period = p } - if period == 0 && !model.PrefixMatching { + prefixMatching := false + if model.PrefixMatching != nil { + prefixMatching = *model.PrefixMatching + } + if period == 0 && !prefixMatching { period = 300 } @@ -49,19 +54,23 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont } var alarmNames []*string - if model.PrefixMatching { + metricName := "" + if model.MetricName != nil { + metricName = *model.MetricName + } + if prefixMatching { params := &cloudwatch.DescribeAlarmsInput{ MaxRecords: aws.Int64(100), - ActionPrefix: aws.String(actionPrefix), - AlarmNamePrefix: aws.String(alarmNamePrefix), + ActionPrefix: actionPrefix, + AlarmNamePrefix: alarmNamePrefix, } resp, err := cli.DescribeAlarms(params) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to call cloudwatch:DescribeAlarms", err) } - alarmNames = filterAlarms(resp, model.Namespace, model.MetricName, model.Dimensions, statistic, period) + alarmNames = filterAlarms(resp, model.Namespace, metricName, model.Dimensions, statistic, period) } else { - if model.Region == "" || model.Namespace == "" || model.MetricName == "" || statistic == "" { + if model.Region == "" || model.Namespace == "" || metricName == "" || statistic == "" { return result, errors.New("invalid annotations query") } @@ -80,7 +89,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont } params := &cloudwatch.DescribeAlarmsForMetricInput{ Namespace: aws.String(model.Namespace), - MetricName: aws.String(model.MetricName), + MetricName: aws.String(metricName), Dimensions: qd, Statistic: aws.String(statistic), Period: aws.Int64(period), diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 4a59945d35a..771698f3563 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -27,21 +27,13 @@ import ( "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/clients" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" ) type DataQueryJson struct { - QueryType string `json:"type,omitempty"` - QueryMode string - PrefixMatching bool - Region string - Namespace string - MetricName string - Dimensions map[string]interface{} - Statistic *string - Period string - ActionPrefix string - AlarmNamePrefix string + dataquery.CloudWatchAnnotationQuery + Type string `json:"type,omitempty"` } type DataSource struct { @@ -179,7 +171,7 @@ func (e *cloudWatchExecutor) QueryData(ctx context.Context, req *backend.QueryDa } var result *backend.QueryDataResponse - switch model.QueryType { + switch model.Type { case annotationQuery: result, err = e.executeAnnotationQuery(req.PluginContext, model, q) case logAction: diff --git a/pkg/tsdb/cloudwatch/log_actions.go b/pkg/tsdb/cloudwatch/log_actions.go index 306ea4d6cd8..3ced6a4a436 100644 --- a/pkg/tsdb/cloudwatch/log_actions.go +++ b/pkg/tsdb/cloudwatch/log_actions.go @@ -112,7 +112,7 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, logger log.Lo } var data *data.Frame = nil - switch logsQuery.SubType { + switch logsQuery.Subtype { case "StartQuery": data, err = e.handleStartQuery(ctx, logger, logsClient, logsQuery, query.TimeRange, query.RefID) case "StopQuery": @@ -123,7 +123,7 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, logger log.Lo data, err = e.handleGetLogEvents(ctx, logsClient, logsQuery) } if err != nil { - return nil, fmt.Errorf("failed to execute log action with subtype: %s: %w", logsQuery.SubType, err) + return nil, fmt.Errorf("failed to execute log action with subtype: %s: %w", logsQuery.Subtype, err) } return data, nil @@ -214,7 +214,7 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c if logsQuery.LogGroups != nil && len(logsQuery.LogGroups) > 0 { var logGroupIdentifiers []string for _, lg := range logsQuery.LogGroups { - arn := lg.ARN + arn := lg.Arn // due to a bug in the startQuery api, we remove * from the arn, otherwise it throws an error logGroupIdentifiers = append(logGroupIdentifiers, strings.TrimSuffix(arn, "*")) } diff --git a/pkg/tsdb/cloudwatch/log_sync_query.go b/pkg/tsdb/cloudwatch/log_sync_query.go index e4a5684e6c0..ac01d654bef 100644 --- a/pkg/tsdb/cloudwatch/log_sync_query.go +++ b/pkg/tsdb/cloudwatch/log_sync_query.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" ) @@ -29,7 +30,9 @@ var executeSyncLogQuery = func(ctx context.Context, e *cloudWatchExecutor, req * } logsQuery.Subtype = "StartQuery" - logsQuery.QueryString = logsQuery.Expression + if logsQuery.Expression != nil { + logsQuery.QueryString = *logsQuery.Expression + } region := logsQuery.Region if logsQuery.Region == "" || region == defaultRegion { @@ -86,7 +89,9 @@ func (e *cloudWatchExecutor) syncQuery(ctx context.Context, logsClient cloudwatc } requestParams := models.LogsQuery{ - Region: logsQuery.Region, + CloudWatchLogsQuery: dataquery.CloudWatchLogsQuery{ + Region: logsQuery.Region, + }, QueryId: *startQueryOutput.QueryId, } diff --git a/pkg/tsdb/cloudwatch/models/cloudwatch_query.go b/pkg/tsdb/cloudwatch/models/cloudwatch_query.go index b100bd18772..e88723e5cb4 100644 --- a/pkg/tsdb/cloudwatch/models/cloudwatch_query.go +++ b/pkg/tsdb/cloudwatch/models/cloudwatch_query.go @@ -17,22 +17,23 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" ) type ( - MetricEditorMode uint32 - MetricQueryType uint32 + MetricEditorMode dataquery.MetricEditorMode + MetricQueryType dataquery.MetricQueryType GMDApiMode uint32 ) const ( - MetricEditorModeBuilder MetricEditorMode = iota - MetricEditorModeRaw + MetricEditorModeBuilder = dataquery.CloudWatchMetricsQueryMetricEditorModeN0 + MetricEditorModeRaw = dataquery.CloudWatchMetricsQueryMetricEditorModeN1 ) const ( - MetricQueryTypeSearch MetricQueryType = iota - MetricQueryTypeQuery + MetricQueryTypeSearch = dataquery.CloudWatchMetricsQueryMetricQueryTypeN0 + MetricQueryTypeQuery = dataquery.CloudWatchMetricsQueryMetricQueryTypeN1 ) const ( @@ -67,8 +68,8 @@ type CloudWatchQuery struct { MatchExact bool UsedExpression string TimezoneUTCOffset string - MetricQueryType MetricQueryType - MetricEditorMode MetricEditorMode + MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType + MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode AccountId *string } @@ -213,25 +214,9 @@ const timeSeriesQuery = "timeSeriesQuery" var validMetricDataID = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`) type metricsDataQuery struct { - Dimensions map[string]interface{} `json:"dimensions"` - Expression string `json:"expression"` - Label *string `json:"label"` - Id string `json:"id"` - MatchExact *bool `json:"matchExact"` - MetricEditorMode *MetricEditorMode `json:"metricEditorMode"` - MetricName string `json:"metricName"` - MetricQueryType MetricQueryType `json:"metricQueryType"` - Namespace string `json:"namespace"` - Period string `json:"period"` - Region string `json:"region"` - SqlExpression string `json:"sqlExpression"` - Statistic *string `json:"statistic"` - Statistics []*string `json:"statistics"` - TimezoneUTCOffset string `json:"timezoneUTCOffset"` - QueryType string `json:"type"` - Hide *bool `json:"hide"` - Alias string `json:"alias"` - AccountId *string `json:"accountId"` + dataquery.CloudWatchMetricsQuery + Type string `json:"type"` + TimezoneUTCOffset string `json:"timezoneUTCOffset"` } // ParseMetricDataQueries decodes the metric data queries json, validates, sets default values and returns an array of CloudWatchQueries. @@ -246,7 +231,7 @@ func ParseMetricDataQueries(dataQueries []backend.DataQuery, startTime time.Time return nil, &QueryError{Err: err, RefID: query.RefID} } - queryType := metricsDataQuery.QueryType + queryType := metricsDataQuery.Type if queryType != timeSeriesQuery && queryType != "" { continue } @@ -255,19 +240,35 @@ func ParseMetricDataQueries(dataQueries []backend.DataQuery, startTime time.Time } result := make([]*CloudWatchQuery, 0, len(metricDataQueries)) + for refId, mdq := range metricDataQueries { cwQuery := &CloudWatchQuery{ logger: logger, - Alias: mdq.Alias, RefId: refId, Id: mdq.Id, Region: mdq.Region, Namespace: mdq.Namespace, - MetricName: mdq.MetricName, - MetricQueryType: mdq.MetricQueryType, - SqlExpression: mdq.SqlExpression, TimezoneUTCOffset: mdq.TimezoneUTCOffset, - Expression: mdq.Expression, + } + + if mdq.Alias != nil { + cwQuery.Alias = *mdq.Alias + } + + if mdq.MetricName != nil { + cwQuery.MetricName = *mdq.MetricName + } + + if mdq.MetricQueryType != nil { + cwQuery.MetricQueryType = *mdq.MetricQueryType + } + + if mdq.SqlExpression != nil { + cwQuery.SqlExpression = *mdq.SqlExpression + } + + if mdq.Expression != nil { + cwQuery.Expression = *mdq.Expression } if err := cwQuery.validateAndSetDefaults(refId, mdq, startTime, endTime, defaultRegion, crossAccountQueryingEnabled); err != nil { @@ -337,14 +338,14 @@ func (q *CloudWatchQuery) validateAndSetDefaults(refId string, metricsDataQuery if metricsDataQuery.Hide != nil { q.ReturnData = !*metricsDataQuery.Hide } - if metricsDataQuery.QueryType == "" { + if metricsDataQuery.Type == "" { // 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. q.ReturnData = true } - if metricsDataQuery.MetricEditorMode == nil && len(metricsDataQuery.Expression) > 0 { + if metricsDataQuery.MetricEditorMode == nil && metricsDataQuery.Expression != nil && len(*metricsDataQuery.Expression) > 0 { // this should only ever happen if this is an alerting query that has not yet been migrated in the frontend q.MetricEditorMode = MetricEditorModeRaw } else { @@ -369,7 +370,7 @@ func (q *CloudWatchQuery) validateAndSetDefaults(refId string, metricsDataQuery func getStatistic(query metricsDataQuery) string { // 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 query.Statistic == nil { - return *query.Statistics[0] + return query.Statistics[0] } return *query.Statistic } @@ -389,14 +390,17 @@ func getLabel(query metricsDataQuery, dynamicLabelsEnabled bool) string { if query.Label != nil { return *query.Label } - if query.Alias == "" { + if query.Alias != nil && *query.Alias == "" { return "" } var result string if dynamicLabelsEnabled { - fullAliasField := query.Alias - matches := legacyAliasRegexp.FindAllStringSubmatch(query.Alias, -1) + fullAliasField := "" + if query.Alias != nil { + fullAliasField = *query.Alias + } + matches := legacyAliasRegexp.FindAllStringSubmatch(fullAliasField, -1) for _, groups := range matches { fullMatch := groups[0] @@ -428,7 +432,10 @@ func calculatePeriodBasedOnTimeRange(startTime, endTime time.Time) int { } func getPeriod(query metricsDataQuery, startTime, endTime time.Time) (int, error) { - periodString := query.Period + periodString := "" + if query.Period != nil { + periodString = *query.Period + } var period int var err error if strings.ToLower(periodString) == "auto" || periodString == "" { diff --git a/pkg/tsdb/cloudwatch/models/cloudwatch_query_test.go b/pkg/tsdb/cloudwatch/models/cloudwatch_query_test.go index da1ee6f8081..cc9b0f243e9 100644 --- a/pkg/tsdb/cloudwatch/models/cloudwatch_query_test.go +++ b/pkg/tsdb/cloudwatch/models/cloudwatch_query_test.go @@ -8,10 +8,12 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/kindsys" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log/logtest" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" ) @@ -294,7 +296,7 @@ func TestQueryJSON(t *testing.T) { var res metricsDataQuery err := json.Unmarshal(jsonString, &res) require.NoError(t, err) - assert.Equal(t, "timeSeriesQuery", res.QueryType) + assert.Equal(t, "timeSeriesQuery", res.Type) } func TestRequestParser(t *testing.T) { @@ -622,11 +624,11 @@ func Test_ParseMetricDataQueries_periods(t *testing.T) { } func Test_ParseMetricDataQueries_query_type_and_metric_editor_mode_and_GMD_query_api_mode(t *testing.T) { - const dummyTestEditorMode MetricEditorMode = 99 + const dummyTestEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode = 99 testCases := map[string]struct { extraDataQueryJson string - expectedMetricQueryType MetricQueryType - expectedMetricEditorMode MetricEditorMode + expectedMetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType + expectedMetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode expectedGMDApiMode GMDApiMode }{ "no metric query type, no metric editor mode, no expression": { @@ -930,16 +932,18 @@ func Test_migrateAliasToDynamicLabel_single_query_preserves_old_alias_and_create false := false queryToMigrate := metricsDataQuery{ - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Alias: tc.inputAlias, - Dimensions: map[string]interface{}{ - "InstanceId": []interface{}{"test"}, + CloudWatchMetricsQuery: dataquery.CloudWatchMetricsQuery{ + Region: "us-east-1", + Namespace: "ec2", + MetricName: kindsys.Ptr("CPUUtilization"), + Alias: kindsys.Ptr(tc.inputAlias), + Dimensions: map[string]interface{}{ + "InstanceId": []interface{}{"test"}, + }, + Statistic: &average, + Period: kindsys.Ptr("600"), + Hide: &false, }, - Statistic: &average, - Period: "600", - Hide: &false, } assert.Equal(t, tc.expectedLabel, getLabel(queryToMigrate, true)) diff --git a/pkg/tsdb/cloudwatch/models/logs_query.go b/pkg/tsdb/cloudwatch/models/logs_query.go index 14406e9baeb..f9ea0c3e0ff 100644 --- a/pkg/tsdb/cloudwatch/models/logs_query.go +++ b/pkg/tsdb/cloudwatch/models/logs_query.go @@ -1,28 +1,18 @@ package models -type LogGroup struct { - ARN string `json:"arn"` - Name string `json:"name"` - AccountID string `json:"accountId"` -} +import ( + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" +) type LogsQuery struct { - LogType string `json:"type"` - SubType string - Limit *int64 - Time int64 - StartTime *int64 - EndTime *int64 - LogGroupName string - LogGroupNames []string - LogGroups []LogGroup `json:"logGroups"` - LogGroupNamePrefix string - LogStreamName string - StartFromHead bool - Region string - QueryString string - QueryId string - StatsGroups []string - Subtype string - Expression string + dataquery.CloudWatchLogsQuery + StartTime *int64 + EndTime *int64 + Limit *int64 + LogGroupName string + LogStreamName string + QueryId string + QueryString string + StartFromHead bool + Subtype string } diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index 29728c729fc..5ee82a2ae99 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" @@ -270,16 +271,16 @@ type queryDimensions struct { } type queryParameters struct { - 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"` + MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType `json:"metricQueryType"` + MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode `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" @@ -288,11 +289,11 @@ func newTestQuery(t testing.TB, p queryParameters) json.RawMessage { t.Helper() tsq := struct { - Type string `json:"type"` - MetricQueryType models.MetricQueryType `json:"metricQueryType"` - MetricEditorMode models.MetricEditorMode `json:"metricEditorMode"` - Namespace string `json:"namespace"` - MetricName string `json:"metricName"` + Type string `json:"type"` + MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType `json:"metricQueryType"` + MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode `json:"metricEditorMode"` + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` Dimensions struct { InstanceID []string `json:"InstanceId,omitempty"` } `json:"dimensions"` diff --git a/public/app/plugins/datasource/cloudwatch/dataquery.cue b/public/app/plugins/datasource/cloudwatch/dataquery.cue index c9f9d19bfbc..b6c7ce5c646 100644 --- a/public/app/plugins/datasource/cloudwatch/dataquery.cue +++ b/public/app/plugins/datasource/cloudwatch/dataquery.cue @@ -23,7 +23,7 @@ import ( pfs.GrafanaPlugin composableKinds: DataQuery: { - maturity: "merged" + maturity: "experimental" lineage: { seqs: [