grafana/pkg/tsdb/cloudwatch/metric_data_query_builder.go
Erik Sundell bab78a9e64
CloudWatch: Add support for AWS Metric Insights (#42487)
* add support for code editor and builder

* refactor cloudwatch migration

* Add tooltip to editor field (#56)

* add tooltip

* add old tooltips

* Bug bash feedback fixes (#58)

* make ASC the default option

* update sql preview whenever sql changes

* don't allow queries without aggregation

* set default value for aggregation

* use new input field

* cleanup

* pr feedback

* prevent unnecessary rerenders

* use frame error instead of main error

* remove not used snapshot

* Use dimension filter in schema picker  (#63)

* use dimension key filter in group by and schema labels

* add dimension filter also to code editor

* add tests

* fix build error

* fix strict error

* remove debug code

* fix annotation editor (#64)

* fix annotation editor

* fix broken test

* revert annotation backend change

* PR feedback (#67)

* pr feedback

* removed dimension filter from group by

* add spacing between common fields and rest

* do not generate deep link for metric queries (#70)

* update docs (#69)

Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>

* fix lint problem caused by merge conflict

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
2021-11-30 10:53:31 +01:00

139 lines
3.7 KiB
Go

package cloudwatch
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
)
func (e *cloudWatchExecutor) buildMetricDataQuery(query *cloudWatchQuery) (*cloudwatch.MetricDataQuery, error) {
mdq := &cloudwatch.MetricDataQuery{
Id: aws.String(query.Id),
ReturnData: aws.Bool(query.ReturnData),
}
switch query.getGMDAPIMode() {
case GMDApiModeMathExpression:
mdq.Period = aws.Int64(int64(query.Period))
mdq.Expression = aws.String(query.Expression)
case GMDApiModeSQLExpression:
mdq.Period = aws.Int64(int64(query.Period))
mdq.Expression = aws.String(query.SqlExpression)
case GMDApiModeInferredSearchExpression:
mdq.Expression = aws.String(buildSearchExpression(query, query.Statistic))
case GMDApiModeMetricStat:
mdq.MetricStat = &cloudwatch.MetricStat{
Metric: &cloudwatch.Metric{
Namespace: aws.String(query.Namespace),
MetricName: aws.String(query.MetricName),
Dimensions: make([]*cloudwatch.Dimension, 0),
},
Period: aws.Int64(int64(query.Period)),
}
for key, values := range query.Dimensions {
mdq.MetricStat.Metric.Dimensions = append(mdq.MetricStat.Metric.Dimensions,
&cloudwatch.Dimension{
Name: aws.String(key),
Value: aws.String(values[0]),
})
}
mdq.MetricStat.Stat = aws.String(query.Statistic)
}
if mdq.Expression != nil {
query.UsedExpression = *mdq.Expression
} else {
query.UsedExpression = ""
}
return mdq, nil
}
func buildSearchExpression(query *cloudWatchQuery, stat string) string {
knownDimensions := make(map[string][]string)
dimensionNames := []string{}
dimensionNamesWithoutKnownValues := []string{}
for key, values := range query.Dimensions {
dimensionNames = append(dimensionNames, key)
hasWildcard := false
for _, value := range values {
if value == "*" {
hasWildcard = true
break
}
}
if hasWildcard {
dimensionNamesWithoutKnownValues = append(dimensionNamesWithoutKnownValues, key)
} else {
knownDimensions[key] = values
}
}
searchTerm := fmt.Sprintf(`MetricName="%s"`, query.MetricName)
keys := []string{}
for k := range knownDimensions {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
values := escapeDoubleQuotes(knownDimensions[key])
valueExpression := join(values, " OR ", `"`, `"`)
if len(knownDimensions[key]) > 1 {
valueExpression = fmt.Sprintf(`(%s)`, valueExpression)
}
keyFilter := fmt.Sprintf(`"%s"=%s`, key, valueExpression)
searchTerm = appendSearch(searchTerm, keyFilter)
}
if query.MatchExact {
schema := fmt.Sprintf("%q", query.Namespace)
if len(dimensionNames) > 0 {
sort.Strings(dimensionNames)
schema += fmt.Sprintf(",%s", join(dimensionNames, ",", `"`, `"`))
}
return fmt.Sprintf("REMOVE_EMPTY(SEARCH('{%s} %s', '%s', %s))", schema, searchTerm, stat, strconv.Itoa(query.Period))
}
sort.Strings(dimensionNamesWithoutKnownValues)
searchTerm = appendSearch(searchTerm, join(dimensionNamesWithoutKnownValues, " ", `"`, `"`))
return fmt.Sprintf(`REMOVE_EMPTY(SEARCH('Namespace="%s" %s', '%s', %s))`, query.Namespace, searchTerm, stat, strconv.Itoa(query.Period))
}
func escapeDoubleQuotes(arr []string) []string {
result := []string{}
for _, value := range arr {
value = strings.ReplaceAll(value, `"`, `\"`)
result = append(result, value)
}
return result
}
func join(arr []string, delimiter string, valuePrefix string, valueSuffix string) string {
result := ""
for index, value := range arr {
result += valuePrefix + value + valueSuffix
if index+1 != len(arr) {
result += delimiter
}
}
return result
}
func appendSearch(target string, value string) string {
if value != "" {
if target == "" {
return value
}
return fmt.Sprintf("%v %v", target, value)
}
return target
}