diff --git a/pkg/tsdb/azuremonitor/applicationinsights-datasource.go b/pkg/tsdb/azuremonitor/applicationinsights-datasource.go index ed533cc0338..3b7b1790115 100644 --- a/pkg/tsdb/azuremonitor/applicationinsights-datasource.go +++ b/pkg/tsdb/azuremonitor/applicationinsights-datasource.go @@ -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 + } +} diff --git a/pkg/tsdb/azuremonitor/applicationinsights-metrics_test.go b/pkg/tsdb/azuremonitor/applicationinsights-metrics_test.go index f1b52cbd062..a0944bcd1d0 100644 --- a/pkg/tsdb/azuremonitor/applicationinsights-metrics_test.go +++ b/pkg/tsdb/azuremonitor/applicationinsights-metrics_test.go @@ -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) } diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index 02ccefa77d6..7450eca712f 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -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 diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go index 5e3b80a3b66..2c07d0a3bc6 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go @@ -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", },