0f53eab992
* 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>
(cherry picked from commit bab78a9e64)
Co-authored-by: Erik Sundell <erik.sundell@grafana.com>
150 lines
3.9 KiB
Go
150 lines
3.9 KiB
Go
package cloudwatch
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type cloudWatchQuery struct {
|
|
RefId string
|
|
Region string
|
|
Id string
|
|
Namespace string
|
|
MetricName string
|
|
Statistic string
|
|
Expression string
|
|
SqlExpression string
|
|
ReturnData bool
|
|
Dimensions map[string][]string
|
|
Period int
|
|
Alias string
|
|
MatchExact bool
|
|
UsedExpression string
|
|
MetricQueryType metricQueryType
|
|
MetricEditorMode metricEditorMode
|
|
}
|
|
|
|
func (q *cloudWatchQuery) getGMDAPIMode() gmdApiMode {
|
|
if q.MetricQueryType == MetricQueryTypeSearch && q.MetricEditorMode == MetricEditorModeBuilder {
|
|
if q.isInferredSearchExpression() {
|
|
return GMDApiModeInferredSearchExpression
|
|
}
|
|
return GMDApiModeMetricStat
|
|
} else if q.MetricQueryType == MetricQueryTypeSearch && q.MetricEditorMode == MetricEditorModeRaw {
|
|
return GMDApiModeMathExpression
|
|
} else if q.MetricQueryType == MetricQueryTypeQuery {
|
|
return GMDApiModeSQLExpression
|
|
}
|
|
|
|
plog.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) isSearchExpression() bool {
|
|
return q.MetricQueryType == MetricQueryTypeSearch && (q.isUserDefinedSearchExpression() || q.isInferredSearchExpression())
|
|
}
|
|
|
|
func (q *cloudWatchQuery) isUserDefinedSearchExpression() bool {
|
|
return q.MetricQueryType == MetricQueryTypeSearch && q.MetricEditorMode == MetricEditorModeRaw && strings.Contains(q.Expression, "SEARCH(")
|
|
}
|
|
|
|
func (q *cloudWatchQuery) isInferredSearchExpression() bool {
|
|
if q.MetricQueryType != MetricQueryTypeSearch || q.MetricEditorMode != MetricEditorModeBuilder {
|
|
return false
|
|
}
|
|
|
|
if len(q.Dimensions) == 0 {
|
|
return !q.MatchExact
|
|
}
|
|
if !q.MatchExact {
|
|
return true
|
|
}
|
|
|
|
for _, values := range q.Dimensions {
|
|
if len(values) > 1 {
|
|
return true
|
|
}
|
|
for _, v := range values {
|
|
if v == "*" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (q *cloudWatchQuery) isMultiValuedDimensionExpression() bool {
|
|
if q.MetricQueryType != MetricQueryTypeSearch || q.MetricEditorMode != MetricEditorModeBuilder {
|
|
return false
|
|
}
|
|
|
|
for _, values := range q.Dimensions {
|
|
for _, v := range values {
|
|
if v == "*" {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if len(values) > 1 {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (q *cloudWatchQuery) buildDeepLink(startTime time.Time, endTime time.Time) (string, error) {
|
|
if q.isMathExpression() || q.MetricQueryType == MetricQueryTypeQuery {
|
|
return "", nil
|
|
}
|
|
|
|
link := &cloudWatchLink{
|
|
Title: q.RefId,
|
|
View: "timeSeries",
|
|
Stacked: false,
|
|
Region: q.Region,
|
|
Start: startTime.UTC().Format(time.RFC3339),
|
|
End: endTime.UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
if q.isSearchExpression() {
|
|
link.Metrics = []interface{}{&metricExpression{Expression: q.UsedExpression}}
|
|
} else {
|
|
metricStat := []interface{}{q.Namespace, q.MetricName}
|
|
for dimensionKey, dimensionValues := range q.Dimensions {
|
|
metricStat = append(metricStat, dimensionKey, dimensionValues[0])
|
|
}
|
|
metricStat = append(metricStat, &metricStatMeta{
|
|
Stat: q.Statistic,
|
|
Period: q.Period,
|
|
})
|
|
link.Metrics = []interface{}{metricStat}
|
|
}
|
|
|
|
linkProps, err := json.Marshal(link)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not marshal link: %w", err)
|
|
}
|
|
|
|
url, err := url.Parse(fmt.Sprintf(`https://%s.console.aws.amazon.com/cloudwatch/deeplink.js`, q.Region))
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to parse CloudWatch console deep link")
|
|
}
|
|
|
|
fragment := url.Query()
|
|
fragment.Set("graph", string(linkProps))
|
|
|
|
query := url.Query()
|
|
query.Set("region", q.Region)
|
|
url.RawQuery = query.Encode()
|
|
|
|
return fmt.Sprintf(`%s#metricsV2:%s`, url.String(), fragment.Encode()), nil
|
|
}
|