mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure: Restore Insights Metrics alias feature (#26098)
also fix case sensitivity for azure monitor metrics
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -192,6 +193,9 @@ func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query
|
||||
queryResult.Error = err
|
||||
return queryResult, nil
|
||||
}
|
||||
|
||||
applyInsightsMetricAlias(frame, query.Alias)
|
||||
|
||||
queryResult.Dataframes = tsdb.NewDecodedDataFrames(data.Frames{frame})
|
||||
return queryResult, nil
|
||||
}
|
||||
@@ -249,3 +253,62 @@ func (e *ApplicationInsightsDatasource) getPluginRoute(plugin *plugins.DataSourc
|
||||
|
||||
return pluginRoute, pluginRouteName, nil
|
||||
}
|
||||
|
||||
// formatApplicationInsightsLegendKey builds the legend key or timeseries name
|
||||
// Alias patterns like {{metric}} are replaced with the appropriate data values.
|
||||
func formatApplicationInsightsLegendKey(alias string, metricName string, labels data.Labels) string {
|
||||
|
||||
// Could be a collision problem if there were two keys that varied only in case, but I don't think that would happen in azure.
|
||||
lowerLabels := data.Labels{}
|
||||
for k, v := range labels {
|
||||
lowerLabels[strings.ToLower(k)] = v
|
||||
}
|
||||
keys := make([]string, 0, len(labels))
|
||||
for k := range lowerLabels {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
keys = sort.StringSlice(keys)
|
||||
|
||||
result := legendKeyFormat.ReplaceAllFunc([]byte(alias), func(in []byte) []byte {
|
||||
metaPartName := strings.Replace(string(in), "{{", "", 1)
|
||||
metaPartName = strings.Replace(metaPartName, "}}", "", 1)
|
||||
metaPartName = strings.ToLower(strings.TrimSpace(metaPartName))
|
||||
|
||||
switch metaPartName {
|
||||
case "metric":
|
||||
return []byte(metricName)
|
||||
case "dimensionname", "groupbyname":
|
||||
return []byte(keys[0])
|
||||
case "dimensionvalue", "groupbyvalue":
|
||||
return []byte(lowerLabels[keys[0]])
|
||||
}
|
||||
|
||||
if v, ok := lowerLabels[metaPartName]; ok {
|
||||
return []byte(v)
|
||||
}
|
||||
|
||||
return in
|
||||
})
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func applyInsightsMetricAlias(frame *data.Frame, alias string) {
|
||||
if alias == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, field := range frame.Fields {
|
||||
if field.Type() == data.FieldTypeTime || field.Type() == data.FieldTypeNullableTime {
|
||||
continue
|
||||
}
|
||||
|
||||
displayName := formatApplicationInsightsLegendKey(alias, field.Name, field.Labels)
|
||||
|
||||
if field.Config == nil {
|
||||
field.Config = &data.FieldConfig{}
|
||||
}
|
||||
|
||||
field.Config.DisplayName = displayName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ func TestInsightsMetricsResultToFrame(t *testing.T) {
|
||||
name string
|
||||
testFile string
|
||||
metric string
|
||||
alias string
|
||||
agg string
|
||||
dimensions []string
|
||||
expectedFrame func() *data.Frame
|
||||
@@ -99,6 +100,49 @@ func TestInsightsMetricsResultToFrame(t *testing.T) {
|
||||
}),
|
||||
)
|
||||
|
||||
return frame
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "segmented series with alias",
|
||||
testFile: "applicationinsights/4-application-insights-response-metrics-multi-segmented.json",
|
||||
metric: "traces/count",
|
||||
alias: "{{ metric }}: Country,City: {{ client/countryOrRegion }},{{ client/city }}",
|
||||
agg: "sum",
|
||||
dimensions: []string{"client/countryOrRegion", "client/city"},
|
||||
expectedFrame: func() *data.Frame {
|
||||
frame := data.NewFrame("",
|
||||
data.NewField("StartTime", nil, []time.Time{
|
||||
time.Date(2020, 6, 25, 16, 15, 32, 14e7, time.UTC),
|
||||
time.Date(2020, 6, 25, 16, 16, 0, 0, time.UTC),
|
||||
}),
|
||||
|
||||
data.NewField("traces/count", data.Labels{"client/city": "Washington", "client/countryOrRegion": "United States"}, []*float64{
|
||||
pointer.Float64(2),
|
||||
nil,
|
||||
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,Washington"}),
|
||||
|
||||
data.NewField("traces/count", data.Labels{"client/city": "Des Moines", "client/countryOrRegion": "United States"}, []*float64{
|
||||
pointer.Float64(2),
|
||||
pointer.Float64(1),
|
||||
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,Des Moines"}),
|
||||
|
||||
data.NewField("traces/count", data.Labels{"client/city": "", "client/countryOrRegion": "United States"}, []*float64{
|
||||
nil,
|
||||
pointer.Float64(11),
|
||||
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,"}),
|
||||
|
||||
data.NewField("traces/count", data.Labels{"client/city": "Chicago", "client/countryOrRegion": "United States"}, []*float64{
|
||||
nil,
|
||||
pointer.Float64(3),
|
||||
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,Chicago"}),
|
||||
|
||||
data.NewField("traces/count", data.Labels{"client/city": "Tokyo", "client/countryOrRegion": "Japan"}, []*float64{
|
||||
nil,
|
||||
pointer.Float64(1),
|
||||
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: Japan,Tokyo"}),
|
||||
)
|
||||
|
||||
return frame
|
||||
},
|
||||
},
|
||||
@@ -110,6 +154,9 @@ func TestInsightsMetricsResultToFrame(t *testing.T) {
|
||||
|
||||
frame, err := InsightsMetricsResultToFrame(res, tt.metric, tt.agg, tt.dimensions)
|
||||
require.NoError(t, err)
|
||||
|
||||
applyInsightsMetricAlias(frame, tt.alias)
|
||||
|
||||
if diff := cmp.Diff(tt.expectedFrame(), frame, data.FrameTestCompareOptions()...); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
@@ -338,6 +338,17 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s
|
||||
endIndex := strings.Index(seriesID, "/providers")
|
||||
resourceGroup := seriesID[startIndex:endIndex]
|
||||
|
||||
// Could be a collision problem if there were two keys that varied only in case, but I don't think that would happen in azure.
|
||||
lowerLabels := data.Labels{}
|
||||
for k, v := range labels {
|
||||
lowerLabels[strings.ToLower(k)] = v
|
||||
}
|
||||
keys := make([]string, 0, len(labels))
|
||||
for k := range lowerLabels {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
keys = sort.StringSlice(keys)
|
||||
|
||||
result := legendKeyFormat.ReplaceAllFunc([]byte(alias), func(in []byte) []byte {
|
||||
metaPartName := strings.Replace(string(in), "{{", "", 1)
|
||||
metaPartName = strings.Replace(metaPartName, "}}", "", 1)
|
||||
@@ -359,23 +370,15 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s
|
||||
return []byte(metricName)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(labels))
|
||||
if metaPartName == "dimensionname" || metaPartName == "dimensionvalue" {
|
||||
for k := range labels {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
keys = sort.StringSlice(keys)
|
||||
}
|
||||
|
||||
if metaPartName == "dimensionname" {
|
||||
return []byte(keys[0])
|
||||
}
|
||||
|
||||
if metaPartName == "dimensionvalue" {
|
||||
return []byte(labels[keys[0]])
|
||||
return []byte(lowerLabels[keys[0]])
|
||||
}
|
||||
|
||||
if v, ok := labels[metaPartName]; ok {
|
||||
if v, ok := lowerLabels[metaPartName]; ok {
|
||||
return []byte(v)
|
||||
}
|
||||
return in
|
||||
|
||||
@@ -374,7 +374,7 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
name: "multiple dimension time series response with label alias",
|
||||
responseFile: "7-azure-monitor-response-multi-dimension.json",
|
||||
mockQuery: &AzureMonitorQuery{
|
||||
Alias: "{{resourcegroup}} {Blob Type={{blobtype}}, Tier={{tier}}}",
|
||||
Alias: "{{resourcegroup}} {Blob Type={{blobtype}}, Tier={{Tier}}}",
|
||||
UrlComponents: map[string]string{
|
||||
"resourceName": "grafana",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user