From 8d37d8f60bf5ff7c1c1ac42a2a37b4fa3a61a330 Mon Sep 17 00:00:00 2001 From: Andrew Hackmann <5140848+bossinc@users.noreply.github.com> Date: Sat, 10 Jun 2023 15:30:49 -0500 Subject: [PATCH] Azuremonitor schematize backend (#69822) * metrics schematize * metrics now use schemas * logs schema * remove unused code * fix tests --- .../azure-log-analytics-datasource.go | 26 +-- .../metrics/azuremonitor-datasource.go | 159 ++++++++++++------ .../metrics/azuremonitor-datasource_test.go | 44 ++--- pkg/tsdb/azuremonitor/metrics/migrations.go | 8 +- .../azuremonitor/metrics/migrations_test.go | 30 ++-- pkg/tsdb/azuremonitor/metrics/url-builder.go | 48 ++++-- .../azuremonitor/metrics/url-builder_test.go | 64 +++---- .../azuremonitor/time/azuremonitor-time.go | 6 +- pkg/tsdb/azuremonitor/types/types.go | 105 ++---------- 9 files changed, 244 insertions(+), 246 deletions(-) diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index 59bd9171630..73e421592b6 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -89,7 +89,7 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l resources := []string{} var resourceOrWorkspace string var queryString string - var resultFormat string + var resultFormat dataquery.ResultFormat traceExploreQuery := "" traceParentExploreQuery := "" traceLogsExploreQuery := "" @@ -103,7 +103,9 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l azureLogAnalyticsTarget := queryJSONModel.AzureLogAnalytics logger.Debug("AzureLogAnalytics", "target", azureLogAnalyticsTarget) - resultFormat = azureLogAnalyticsTarget.ResultFormat + if azureLogAnalyticsTarget.ResultFormat != nil { + resultFormat = *azureLogAnalyticsTarget.ResultFormat + } if resultFormat == "" { resultFormat = types.TimeSeries } @@ -115,14 +117,16 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l if len(azureLogAnalyticsTarget.Resources) > 0 { resources = azureLogAnalyticsTarget.Resources resourceOrWorkspace = azureLogAnalyticsTarget.Resources[0] - } else if azureLogAnalyticsTarget.Resource != "" { - resources = []string{azureLogAnalyticsTarget.Resource} - resourceOrWorkspace = azureLogAnalyticsTarget.Resource - } else { - resourceOrWorkspace = azureLogAnalyticsTarget.Workspace + } else if azureLogAnalyticsTarget.Resource != nil && *azureLogAnalyticsTarget.Resource != "" { + resources = []string{*azureLogAnalyticsTarget.Resource} + resourceOrWorkspace = *azureLogAnalyticsTarget.Resource + } else if azureLogAnalyticsTarget.Workspace != nil { + resourceOrWorkspace = *azureLogAnalyticsTarget.Workspace } - queryString = azureLogAnalyticsTarget.Query + if azureLogAnalyticsTarget.Query != nil { + queryString = *azureLogAnalyticsTarget.Query + } } if query.QueryType == string(dataquery.AzureQueryTypeAzureTraces) { @@ -205,7 +209,7 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l azureLogAnalyticsQueries = append(azureLogAnalyticsQueries, &AzureLogAnalyticsQuery{ RefID: query.RefID, - ResultFormat: resultFormat, + ResultFormat: string(resultFormat), URL: apiURL, JSON: query.JSON, TimeRange: query.TimeRange, @@ -701,7 +705,7 @@ func encodeQuery(rawQuery string) (string, error) { return base64.StdEncoding.EncodeToString(b.Bytes()), nil } -func buildTracesQuery(operationId string, parentSpanID *string, traceTypes []string, filters []types.TracesFilters, resultFormat *string, resources []string) string { +func buildTracesQuery(operationId string, parentSpanID *string, traceTypes []string, filters []dataquery.AzureTracesFilter, resultFormat *dataquery.ResultFormat, resources []string) string { types := traceTypes if len(types) == 0 { types = Tables @@ -709,7 +713,7 @@ func buildTracesQuery(operationId string, parentSpanID *string, traceTypes []str filteredTypes := make([]string, 0) // If the result format is set to trace then we filter out all events that are of the type traces as they don't make sense when visualised as a span - if resultFormat != nil && dataquery.ResultFormat(*resultFormat) == dataquery.ResultFormatTrace { + if resultFormat != nil && *resultFormat == dataquery.ResultFormatTrace { filteredTypes = slices.Filter(filteredTypes, types, func(s string) bool { return s != "traces" }) } else { filteredTypes = types diff --git a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go index e4731898a67..bd32e927fbb 100644 --- a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics" azTime "github.com/grafana/grafana/pkg/tsdb/azuremonitor/time" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" @@ -69,7 +70,7 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe for _, query := range queries { var target string - queryJSONModel := types.AzureMonitorJSONQuery{} + queryJSONModel := dataquery.AzureMonitorQuery{} err := json.Unmarshal(query.JSON, &queryJSONModel) if err != nil { return nil, fmt.Errorf("failed to decode the Azure Monitor query object from JSON: %w", err) @@ -77,49 +78,29 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe azJSONModel := queryJSONModel.AzureMonitor // Legacy: If only MetricDefinition is set, use it as namespace - if azJSONModel.MetricDefinition != "" && azJSONModel.MetricNamespace == "" { + if azJSONModel.MetricDefinition != nil && *azJSONModel.MetricDefinition != "" && + azJSONModel.MetricNamespace != nil && *azJSONModel.MetricNamespace == "" { azJSONModel.MetricNamespace = azJSONModel.MetricDefinition } azJSONModel.DimensionFilters = MigrateDimensionFilters(azJSONModel.DimensionFilters) - alias := azJSONModel.Alias - - timeGrain := azJSONModel.TimeGrain - timeGrains := azJSONModel.AllowedTimeGrainsMs - - if timeGrain == "auto" { - timeGrain, err = azTime.SetAutoTimeGrain(query.Interval.Milliseconds(), timeGrains) - if err != nil { - return nil, err - } + alias := "" + if azJSONModel.Alias != nil { + alias = *azJSONModel.Alias } - - params := url.Values{} - params.Add("api-version", AzureMonitorAPIVersion) - params.Add("timespan", fmt.Sprintf("%v/%v", query.TimeRange.From.UTC().Format(time.RFC3339), query.TimeRange.To.UTC().Format(time.RFC3339))) - params.Add("interval", timeGrain) - params.Add("aggregation", azJSONModel.Aggregation) - params.Add("metricnames", azJSONModel.MetricName) - - if azJSONModel.CustomNamespace != "" { - params.Add("metricnamespace", azJSONModel.CustomNamespace) - } else { - params.Add("metricnamespace", azJSONModel.MetricNamespace) + azureURL := "" + if queryJSONModel.Subscription != nil { + azureURL = BuildSubscriptionMetricsURL(*queryJSONModel.Subscription) } - - azureURL := BuildSubscriptionMetricsURL(queryJSONModel.Subscription) filterInBody := true - if azJSONModel.Region != "" { - params.Add("region", azJSONModel.Region) - } resourceIDs := []string{} - resourceMap := map[string]types.AzureMonitorResource{} + resourceMap := map[string]dataquery.AzureMonitorResource{} if hasOne, resourceGroup, resourceName := hasOneResource(queryJSONModel); hasOne { ub := urlBuilder{ - ResourceURI: azJSONModel.ResourceURI, + ResourceURI: azJSONModel.ResourceUri, // Alternative, used to reconstruct resource URI if it's not present - DefaultSubscription: dsInfo.Settings.SubscriptionId, + DefaultSubscription: &dsInfo.Settings.SubscriptionId, Subscription: queryJSONModel.Subscription, ResourceGroup: resourceGroup, MetricNamespace: azJSONModel.MetricNamespace, @@ -128,25 +109,36 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe azureURL = ub.BuildMetricsURL() // POST requests are only supported at the subscription level filterInBody = false - resourceMap[ub.buildResourceURI()] = types.AzureMonitorResource{ResourceGroup: resourceGroup, ResourceName: resourceName} + resourceUri := ub.buildResourceURI() + if resourceUri != nil { + resourceMap[*resourceUri] = dataquery.AzureMonitorResource{ResourceGroup: resourceGroup, ResourceName: resourceName} + } } else { for _, r := range azJSONModel.Resources { ub := urlBuilder{ - DefaultSubscription: dsInfo.Settings.SubscriptionId, + DefaultSubscription: &dsInfo.Settings.SubscriptionId, Subscription: queryJSONModel.Subscription, ResourceGroup: r.ResourceGroup, MetricNamespace: azJSONModel.MetricNamespace, ResourceName: r.ResourceName, } resourceUri := ub.buildResourceURI() - resourceMap[resourceUri] = r - resourceIDs = append(resourceIDs, fmt.Sprintf("Microsoft.ResourceId eq '%s'", resourceUri)) + if resourceUri != nil { + resourceMap[*resourceUri] = r + } + resourceIDs = append(resourceIDs, fmt.Sprintf("Microsoft.ResourceId eq '%s'", *resourceUri)) } } // old model - dimension := strings.TrimSpace(azJSONModel.Dimension) - dimensionFilter := strings.TrimSpace(azJSONModel.DimensionFilter) + dimension := "" + if azJSONModel.Dimension != nil { + dimension = strings.TrimSpace(*azJSONModel.Dimension) + } + dimensionFilter := "" + if azJSONModel.DimensionFilter != nil { + dimensionFilter = strings.TrimSpace(*azJSONModel.DimensionFilter) + } dimSB := strings.Builder{} @@ -155,9 +147,9 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe } else { for i, filter := range azJSONModel.DimensionFilters { if len(filter.Filters) == 0 { - dimSB.WriteString(fmt.Sprintf("%s eq '*'", filter.Dimension)) + dimSB.WriteString(fmt.Sprintf("%s eq '*'", *filter.Dimension)) } else { - dimSB.WriteString(filter.ConstructFiltersString()) + dimSB.WriteString(types.ConstructFiltersString(filter)) } if i != len(azJSONModel.DimensionFilters)-1 { dimSB.WriteString(" and ") @@ -175,16 +167,21 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe } } - if azJSONModel.Top != "" { - params.Add("top", azJSONModel.Top) + params, err := getParams(azJSONModel, query) + if err != nil { + return nil, err } - target = params.Encode() if setting.Env == setting.Dev { logger.Debug("Azuremonitor request", "params", params) } + sub := "" + if queryJSONModel.Subscription != nil { + sub = *queryJSONModel.Subscription + } + query := &types.AzureMonitorQuery{ URL: azureURL, Target: target, @@ -194,7 +191,7 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe TimeRange: query.TimeRange, Dimensions: azJSONModel.DimensionFilters, Resources: resourceMap, - Subscription: queryJSONModel.Subscription, + Subscription: sub, } if filterString != "" { if filterInBody { @@ -209,6 +206,45 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe return azureMonitorQueries, nil } +func getParams(azJSONModel *dataquery.AzureMetricQuery, query backend.DataQuery) (url.Values, error) { + params := url.Values{} + + timeGrain := azJSONModel.TimeGrain + timeGrains := azJSONModel.AllowedTimeGrainsMs + + if timeGrain != nil && *timeGrain == "auto" { + var err error + timeGrain, err = azTime.SetAutoTimeGrain(query.Interval.Milliseconds(), timeGrains) + if err != nil { + return nil, err + } + } + params.Add("api-version", AzureMonitorAPIVersion) + params.Add("timespan", fmt.Sprintf("%v/%v", query.TimeRange.From.UTC().Format(time.RFC3339), query.TimeRange.To.UTC().Format(time.RFC3339))) + if timeGrain != nil { + params.Add("interval", *timeGrain) + } + if azJSONModel.Aggregation != nil { + params.Add("aggregation", *azJSONModel.Aggregation) + } + if azJSONModel.MetricName != nil { + params.Add("metricnames", *azJSONModel.MetricName) + } + if azJSONModel.CustomNamespace != nil && *azJSONModel.CustomNamespace != "" { + params.Add("metricnamespace", *azJSONModel.CustomNamespace) + } else if azJSONModel.MetricNamespace != nil { + params.Add("metricnamespace", *azJSONModel.MetricNamespace) + } + if azJSONModel.Region != nil && *azJSONModel.Region != "" { + params.Add("region", *azJSONModel.Region) + } + if azJSONModel.Top != nil && *azJSONModel.Top != "" { + params.Add("top", *azJSONModel.Top) + } + + return params, nil +} + func retrieveSubscriptionDetails(e *AzureMonitorDatasource, cli *http.Client, ctx context.Context, logger log.Logger, tracer tracing.Tracer, subscriptionId string, baseUrl string, dsId int64, orgId int64) { req, err := e.createRequest(ctx, logger, fmt.Sprintf("%s/subscriptions/%s", baseUrl, subscriptionId)) if err != nil { @@ -489,7 +525,20 @@ func getQueryUrl(query *types.AzureMonitorQuery, azurePortalUrl, resourceID, res continue } - switch dimension.Operator { + if dimension.Dimension == nil { + continue + } + + if dimension.Operator == nil { + filter := types.AzureMonitorDimensionFilterBackend{ + Key: *dimension.Dimension, + Operator: 0, + Values: dimensionFilters, + } + filters = append(filters, filter) + continue + } + switch *dimension.Operator { case "eq": dimensionInt = 0 case "ne": @@ -499,7 +548,7 @@ func getQueryUrl(query *types.AzureMonitorQuery, azurePortalUrl, resourceID, res } filter := types.AzureMonitorDimensionFilterBackend{ - Key: dimension.Dimension, + Key: *dimension.Dimension, Operator: dimensionInt, Values: dimensionFilters, } @@ -552,7 +601,7 @@ func getQueryUrl(query *types.AzureMonitorQuery, azurePortalUrl, resourceID, res // formatAzureMonitorLegendKey builds the legend key or timeseries name // Alias patterns like {{resourcename}} are replaced with the appropriate data values. -func formatAzureMonitorLegendKey(alias string, subscriptionId string, resource types.AzureMonitorResource, metricName string, metadataName string, +func formatAzureMonitorLegendKey(alias string, subscriptionId string, resource dataquery.AzureMonitorResource, metricName string, metadataName string, metadataValue string, namespace string, seriesID 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{} @@ -582,16 +631,16 @@ func formatAzureMonitorLegendKey(alias string, subscriptionId string, resource t } } - if metaPartName == "resourcegroup" { - return []byte(resource.ResourceGroup) + if metaPartName == "resourcegroup" && resource.ResourceGroup != nil { + return []byte(*resource.ResourceGroup) } if metaPartName == "namespace" { return []byte(namespace) } - if metaPartName == "resourcename" { - return []byte(resource.ResourceName) + if metaPartName == "resourcename" && resource.ResourceName != nil { + return []byte(*resource.ResourceName) } if metaPartName == "metric" { @@ -674,17 +723,17 @@ func extractResourceIDFromMetricsURL(url string) string { return strings.Split(url, "/providers/microsoft.insights/metrics")[0] } -func hasOneResource(query types.AzureMonitorJSONQuery) (bool, string, string) { +func hasOneResource(query dataquery.AzureMonitorQuery) (bool, *string, *string) { azJSONModel := query.AzureMonitor if len(azJSONModel.Resources) > 1 { - return false, "", "" + return false, nil, nil } if len(azJSONModel.Resources) == 1 { return true, azJSONModel.Resources[0].ResourceGroup, azJSONModel.Resources[0].ResourceName } - if azJSONModel.ResourceGroup != "" || azJSONModel.ResourceName != "" { + if (azJSONModel.ResourceGroup != nil && *azJSONModel.ResourceGroup != "") || (azJSONModel.ResourceName != nil && *azJSONModel.ResourceName != "") { // Deprecated, Resources should be used instead return true, azJSONModel.ResourceGroup, azJSONModel.ResourceName } - return false, "", "" + return false, nil, nil } diff --git a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go index 2764f3734ee..fb486d25de8 100644 --- a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go +++ b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/testdata" azTime "github.com/grafana/grafana/pkg/tsdb/azuremonitor/time" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" @@ -48,7 +49,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { expectedBodyFilter string expectedParamFilter string expectedPortalURL *string - resources map[string]types.AzureMonitorResource + resources map[string]dataquery.AzureMonitorResource }{ { name: "Parse queries from frontend and build AzureMonitor API queries", @@ -110,7 +111,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "legacy query without resourceURI and has dimensionFilter*s* property with one dimension", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "eq", Filter: &wildcardFilter}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("eq"), Filter: &wildcardFilter}}, "top": "30", }, queryInterval: duration, @@ -123,7 +124,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "legacy query without resourceURI and has dimensionFilter*s* property with two dimensions", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "eq", Filter: &wildcardFilter}, {Dimension: "tier", Operator: "eq", Filter: &wildcardFilter}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("eq"), Filter: &wildcardFilter}, {Dimension: strPtr("tier"), Operator: strPtr("eq"), Filter: &wildcardFilter}}, "top": "30", }, queryInterval: duration, @@ -148,7 +149,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "has dimensionFilter*s* property with not equals operator", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "ne", Filter: &wildcardFilter, Filters: []string{"test"}}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("ne"), Filter: &wildcardFilter, Filters: []string{"test"}}}, "top": "30", }, queryInterval: duration, @@ -161,7 +162,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "has dimensionFilter*s* property with startsWith operator", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "sw", Filter: &testFilter}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("sw"), Filter: &testFilter}}, "top": "30", }, queryInterval: duration, @@ -174,7 +175,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "correctly sets dimension operator to eq (irrespective of operator) when filter value is '*'", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "sw", Filter: &wildcardFilter}, {Dimension: "tier", Operator: "ne", Filter: &wildcardFilter}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("sw"), Filter: &wildcardFilter}, {Dimension: strPtr("tier"), Operator: strPtr("ne"), Filter: &wildcardFilter}}, "top": "30", }, queryInterval: duration, @@ -187,7 +188,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "correctly constructs target when multiple filter values are provided for the 'eq' operator", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "eq", Filter: &wildcardFilter, Filters: []string{"test", "test2"}}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("eq"), Filter: &wildcardFilter, Filters: []string{"test", "test2"}}}, "top": "30", }, queryInterval: duration, @@ -200,7 +201,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "correctly constructs target when multiple filter values are provided for ne 'eq' operator", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "ne", Filter: &wildcardFilter, Filters: []string{"test", "test2"}}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("ne"), Filter: &wildcardFilter, Filters: []string{"test", "test2"}}}, "top": "30", }, queryInterval: duration, @@ -226,7 +227,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { "timeGrain": "PT1M", "top": "10", "region": "westus", - "resources": []types.AzureMonitorResource{{ResourceGroup: "rg", ResourceName: "vm"}, {ResourceGroup: "rg2", ResourceName: "vm2"}}, + "resources": []dataquery.AzureMonitorResource{{ResourceGroup: strPtr("rg"), ResourceName: strPtr("vm")}, {ResourceGroup: strPtr("rg2"), ResourceName: strPtr("vm2")}}, }, expectedInterval: "PT1M", azureMonitorQueryTarget: "aggregation=Average&api-version=2021-05-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute%2FvirtualMachines®ion=westus×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=10", @@ -237,7 +238,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "includes a single resource as a parameter filter", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "resources": []types.AzureMonitorResource{{ResourceGroup: "rg", ResourceName: "vm"}}, + "resources": []dataquery.AzureMonitorResource{{ResourceGroup: strPtr("rg"), ResourceName: strPtr("vm")}}, }, queryInterval: duration, expectedInterval: "PT1M", @@ -248,8 +249,8 @@ func TestAzureMonitorBuildQueries(t *testing.T) { name: "includes a resource and a dimesion as filters", azureMonitorVariedProperties: map[string]interface{}{ "timeGrain": "PT1M", - "resources": []types.AzureMonitorResource{{ResourceGroup: "rg", ResourceName: "vm"}, {ResourceGroup: "rg2", ResourceName: "vm2"}}, - "dimensionFilters": []types.AzureMonitorDimensionFilter{{Dimension: "blob", Operator: "ne", Filter: &wildcardFilter, Filters: []string{"test", "test2"}}}, + "resources": []dataquery.AzureMonitorResource{{ResourceGroup: strPtr("rg"), ResourceName: strPtr("vm")}, {ResourceGroup: strPtr("rg2"), ResourceName: strPtr("vm2")}}, + "dimensionFilters": []dataquery.AzureMetricDimension{{Dimension: strPtr("blob"), Operator: strPtr("ne"), Filter: &wildcardFilter, Filters: []string{"test", "test2"}}}, "top": "30", }, queryInterval: duration, @@ -296,14 +297,14 @@ func TestAzureMonitorBuildQueries(t *testing.T) { queries, err := datasource.buildQueries(log.New("test"), tsdbQuery, dsInfo) require.NoError(t, err) - resources := map[string]types.AzureMonitorResource{} + resources := map[string]dataquery.AzureMonitorResource{} if tt.azureMonitorVariedProperties["resources"] != nil { - resourceSlice := tt.azureMonitorVariedProperties["resources"].([]types.AzureMonitorResource) + resourceSlice := tt.azureMonitorVariedProperties["resources"].([]dataquery.AzureMonitorResource) for _, resource := range resourceSlice { - resources[fmt.Sprintf("/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", resource.ResourceGroup, resource.ResourceName)] = resource + resources[fmt.Sprintf("/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", *resource.ResourceGroup, *resource.ResourceName)] = resource } } else { - resources["/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana"] = types.AzureMonitorResource{ResourceGroup: "grafanastaging", ResourceName: "grafana"} + resources["/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana"] = dataquery.AzureMonitorResource{ResourceGroup: strPtr("grafanastaging"), ResourceName: strPtr("grafana")} } azureMonitorQuery := &types.AzureMonitorQuery{ @@ -366,8 +367,9 @@ func TestCustomNamespace(t *testing.T) { } func TestAzureMonitorParseResponse(t *testing.T) { - resources := map[string]types.AzureMonitorResource{} - resources["/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana"] = types.AzureMonitorResource{ResourceGroup: "grafanastaging", ResourceName: "grafana"} + resources := map[string]dataquery.AzureMonitorResource{} + resources["/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana"] = + dataquery.AzureMonitorResource{ResourceGroup: strPtr("grafanastaging"), ResourceName: strPtr("grafana")} subscription := "12345678-aaaa-bbbb-cccc-123456789abc" tests := []struct { @@ -484,7 +486,7 @@ func TestAzureMonitorParseResponse(t *testing.T) { Params: url.Values{ "aggregation": {"Average"}, }, - Resources: map[string]types.AzureMonitorResource{"/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanatest/providers/Microsoft.Storage/storageAccounts/testblobaccount/blobServices/default/providers/Microsoft.Insights/metrics": {ResourceGroup: "grafanatest", ResourceName: "testblobaccount"}}, + Resources: map[string]dataquery.AzureMonitorResource{"/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanatest/providers/Microsoft.Storage/storageAccounts/testblobaccount/blobServices/default/providers/Microsoft.Insights/metrics": {ResourceGroup: strPtr("grafanatest"), ResourceName: strPtr("testblobaccount")}}, Subscription: subscription, }, }, @@ -681,3 +683,7 @@ func TestExtractResourceNameFromMetricsURL(t *testing.T) { require.Equal(t, expected, extractResourceNameFromMetricsURL((url))) }) } + +func strPtr(s string) *string { + return &s +} diff --git a/pkg/tsdb/azuremonitor/metrics/migrations.go b/pkg/tsdb/azuremonitor/metrics/migrations.go index a144a892862..ba814341401 100644 --- a/pkg/tsdb/azuremonitor/metrics/migrations.go +++ b/pkg/tsdb/azuremonitor/metrics/migrations.go @@ -1,11 +1,9 @@ package metrics -import ( - "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" -) +import "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" -func MigrateDimensionFilters(filters []types.AzureMonitorDimensionFilter) []types.AzureMonitorDimensionFilter { - var newFilters []types.AzureMonitorDimensionFilter +func MigrateDimensionFilters(filters []dataquery.AzureMetricDimension) []dataquery.AzureMetricDimension { + var newFilters []dataquery.AzureMetricDimension for _, filter := range filters { newFilter := filter // Ignore the deprecation check as this is a migration diff --git a/pkg/tsdb/azuremonitor/metrics/migrations_test.go b/pkg/tsdb/azuremonitor/metrics/migrations_test.go index 1ac1e29601e..7caa3ef8c87 100644 --- a/pkg/tsdb/azuremonitor/metrics/migrations_test.go +++ b/pkg/tsdb/azuremonitor/metrics/migrations_test.go @@ -5,7 +5,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" ) func TestDimensionFiltersMigration(t *testing.T) { @@ -14,38 +14,38 @@ func TestDimensionFiltersMigration(t *testing.T) { additionalTestFilter := "testFilter2" tests := []struct { name string - dimensionFilters []types.AzureMonitorDimensionFilter - expectedDimensionFilters []types.AzureMonitorDimensionFilter + dimensionFilters []dataquery.AzureMetricDimension + expectedDimensionFilters []dataquery.AzureMetricDimension }{ { name: "will return new format unchanged", - dimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filters: []string{"testFilter"}}}, - expectedDimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filters: []string{"testFilter"}}}, + dimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filters: []string{"testFilter"}}}, + expectedDimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filters: []string{"testFilter"}}}, }, { name: "correctly updates old format with wildcard", - dimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filter: &wildcard}}, - expectedDimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq"}}, + dimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filter: &wildcard}}, + expectedDimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq")}}, }, { name: "correctly updates old format with a value", - dimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filter: &testFilter}}, - expectedDimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filters: []string{testFilter}}}, + dimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filter: &testFilter}}, + expectedDimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filters: []string{testFilter}}}, }, { name: "correctly ignores wildcard if filters has a value", - dimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filter: &wildcard, Filters: []string{testFilter}}}, - expectedDimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filters: []string{testFilter}}}, + dimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filter: &wildcard, Filters: []string{testFilter}}}, + expectedDimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filters: []string{testFilter}}}, }, { name: "correctly merges values if filters has a value (ignores duplicates)", - dimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filter: &testFilter, Filters: []string{testFilter}}}, - expectedDimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filters: []string{testFilter}}}, + dimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filter: &testFilter, Filters: []string{testFilter}}}, + expectedDimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filters: []string{testFilter}}}, }, { name: "correctly merges values if filters has a value", - dimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filter: &additionalTestFilter, Filters: []string{testFilter}}}, - expectedDimensionFilters: []types.AzureMonitorDimensionFilter{{Dimension: "testDimension", Operator: "eq", Filters: []string{testFilter, additionalTestFilter}}}, + dimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filter: &additionalTestFilter, Filters: []string{testFilter}}}, + expectedDimensionFilters: []dataquery.AzureMetricDimension{{Dimension: strPtr("testDimension"), Operator: strPtr("eq"), Filters: []string{testFilter, additionalTestFilter}}}, }, } diff --git a/pkg/tsdb/azuremonitor/metrics/url-builder.go b/pkg/tsdb/azuremonitor/metrics/url-builder.go index 849465f90a4..2d38ce835a4 100644 --- a/pkg/tsdb/azuremonitor/metrics/url-builder.go +++ b/pkg/tsdb/azuremonitor/metrics/url-builder.go @@ -7,42 +7,54 @@ import ( // urlBuilder builds the URL for calling the Azure Monitor API type urlBuilder struct { - ResourceURI string + ResourceURI *string // Following fields will be used to generate a ResourceURI - DefaultSubscription string - Subscription string - ResourceGroup string - MetricNamespace string - ResourceName string + DefaultSubscription *string + Subscription *string + ResourceGroup *string + MetricNamespace *string + ResourceName *string } -func (params *urlBuilder) buildResourceURI() string { - if params.ResourceURI != "" { +func (params *urlBuilder) buildResourceURI() *string { + if params.ResourceURI != nil && *params.ResourceURI != "" { return params.ResourceURI } subscription := params.Subscription - if params.Subscription == "" { + if params.Subscription == nil || *params.Subscription == "" { subscription = params.DefaultSubscription } - metricNamespaceArray := strings.Split(params.MetricNamespace, "/") - resourceNameArray := strings.Split(params.ResourceName, "/") + if params.MetricNamespace == nil || *params.MetricNamespace == "" { + return nil + } + + metricNamespaceArray := strings.Split(*params.MetricNamespace, "/") + var resourceNameArray []string + if params.ResourceName != nil && *params.ResourceName != "" { + resourceNameArray = strings.Split(*params.ResourceName, "/") + } provider := metricNamespaceArray[0] metricNamespaceArray = metricNamespaceArray[1:] - if strings.HasPrefix(strings.ToLower(params.MetricNamespace), "microsoft.storage/storageaccounts/") && - !strings.HasSuffix(params.ResourceName, "default") { + if strings.HasPrefix(strings.ToLower(*params.MetricNamespace), "microsoft.storage/storageaccounts/") && + params.ResourceName != nil && + !strings.HasSuffix(*params.ResourceName, "default") { resourceNameArray = append(resourceNameArray, "default") } + resGroup := "" + if params.ResourceGroup != nil { + resGroup = *params.ResourceGroup + } urlArray := []string{ "/subscriptions", - subscription, + *subscription, "resourceGroups", - params.ResourceGroup, + resGroup, "providers", provider, } @@ -52,7 +64,7 @@ func (params *urlBuilder) buildResourceURI() string { } resourceURI := strings.Join(urlArray, "/") - return resourceURI + return &resourceURI } // BuildMetricsURL checks the metric properties to see which form of the url @@ -61,11 +73,11 @@ func (params *urlBuilder) BuildMetricsURL() string { resourceURI := params.ResourceURI // Prior to Grafana 9, we had a legacy query object rather than a resourceURI, so we manually create the resource URI - if resourceURI == "" { + if resourceURI == nil || *resourceURI == "" { resourceURI = params.buildResourceURI() } - return fmt.Sprintf("%s/providers/microsoft.insights/metrics", resourceURI) + return fmt.Sprintf("%s/providers/microsoft.insights/metrics", *resourceURI) } // BuildSubscriptionMetricsURL returns a URL for querying metrics for all resources in a subscription diff --git a/pkg/tsdb/azuremonitor/metrics/url-builder_test.go b/pkg/tsdb/azuremonitor/metrics/url-builder_test.go index 3bc9349b21d..e16385f6bfb 100644 --- a/pkg/tsdb/azuremonitor/metrics/url-builder_test.go +++ b/pkg/tsdb/azuremonitor/metrics/url-builder_test.go @@ -10,7 +10,7 @@ func TestURLBuilder(t *testing.T) { t.Run("AzureMonitor URL Builder", func(t *testing.T) { t.Run("when only resource uri is provided it returns resource/uri/providers/microsoft.insights/metrics", func(t *testing.T) { ub := &urlBuilder{ - ResourceURI: "/subscriptions/sub/resource/uri", + ResourceURI: strPtr("/subscriptions/sub/resource/uri"), } url := ub.BuildMetricsURL() @@ -19,11 +19,11 @@ func TestURLBuilder(t *testing.T) { t.Run("when resource uri and legacy fields are provided the legacy fields are ignored", func(t *testing.T) { ub := &urlBuilder{ - ResourceURI: "/subscriptions/sub/resource/uri", - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricNamespace: "Microsoft.NetApp/netAppAccounts/capacityPools/volumes", - ResourceName: "rn1/rn2/rn3", + ResourceURI: strPtr("/subscriptions/sub/resource/uri"), + DefaultSubscription: strPtr("default-sub"), + ResourceGroup: strPtr("rg"), + MetricNamespace: strPtr("Microsoft.NetApp/netAppAccounts/capacityPools/volumes"), + ResourceName: strPtr("rn1/rn2/rn3"), } url := ub.BuildMetricsURL() @@ -33,10 +33,10 @@ func TestURLBuilder(t *testing.T) { t.Run("Legacy URL Builder params", func(t *testing.T) { t.Run("when metric definition is in the short form", func(t *testing.T) { ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricNamespace: "Microsoft.Compute/virtualMachines", - ResourceName: "rn", + DefaultSubscription: strPtr("default-sub"), + ResourceGroup: strPtr("rg"), + MetricNamespace: strPtr("Microsoft.Compute/virtualMachines"), + ResourceName: strPtr("rn"), } url := ub.BuildMetricsURL() @@ -45,11 +45,11 @@ func TestURLBuilder(t *testing.T) { t.Run("when metric definition is in the short form and a subscription is defined", func(t *testing.T) { ub := &urlBuilder{ - DefaultSubscription: "default-sub", - Subscription: "specified-sub", - ResourceGroup: "rg", - MetricNamespace: "Microsoft.Compute/virtualMachines", - ResourceName: "rn", + DefaultSubscription: strPtr("default-sub"), + Subscription: strPtr("specified-sub"), + ResourceGroup: strPtr("rg"), + MetricNamespace: strPtr("Microsoft.Compute/virtualMachines"), + ResourceName: strPtr("rn"), } url := ub.BuildMetricsURL() @@ -58,10 +58,10 @@ func TestURLBuilder(t *testing.T) { t.Run("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func(t *testing.T) { ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricNamespace: "Microsoft.Storage/storageAccounts/blobServices", - ResourceName: "rn1/default", + DefaultSubscription: strPtr("default-sub"), + ResourceGroup: strPtr("rg"), + MetricNamespace: strPtr("Microsoft.Storage/storageAccounts/blobServices"), + ResourceName: strPtr("rn1/default"), } url := ub.BuildMetricsURL() @@ -70,10 +70,10 @@ func TestURLBuilder(t *testing.T) { t.Run("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func(t *testing.T) { ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricNamespace: "Microsoft.Storage/storageAccounts/fileServices", - ResourceName: "rn1/default", + DefaultSubscription: strPtr("default-sub"), + ResourceGroup: strPtr("rg"), + MetricNamespace: strPtr("Microsoft.Storage/storageAccounts/fileServices"), + ResourceName: strPtr("rn1/default"), } url := ub.BuildMetricsURL() @@ -82,10 +82,10 @@ func TestURLBuilder(t *testing.T) { t.Run("when metric definition is Microsoft.NetApp/netAppAccounts/capacityPools/volumes", func(t *testing.T) { ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricNamespace: "Microsoft.NetApp/netAppAccounts/capacityPools/volumes", - ResourceName: "rn1/rn2/rn3", + DefaultSubscription: strPtr("default-sub"), + ResourceGroup: strPtr("rg"), + MetricNamespace: strPtr("Microsoft.NetApp/netAppAccounts/capacityPools/volumes"), + ResourceName: strPtr("rn1/rn2/rn3"), } url := ub.BuildMetricsURL() @@ -94,13 +94,13 @@ func TestURLBuilder(t *testing.T) { t.Run("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func(t *testing.T) { ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricNamespace: "Microsoft.Storage/storageAccounts/blobServices", - ResourceName: "rn1", + DefaultSubscription: strPtr("default-sub"), + ResourceGroup: strPtr("rg"), + MetricNamespace: strPtr("Microsoft.Storage/storageAccounts/blobServices"), + ResourceName: strPtr("rn1"), } - url := ub.buildResourceURI() + url := *ub.buildResourceURI() assert.Equal(t, "/subscriptions/default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default", url) }) }) diff --git a/pkg/tsdb/azuremonitor/time/azuremonitor-time.go b/pkg/tsdb/azuremonitor/time/azuremonitor-time.go index cc6736eb93b..76e46883100 100644 --- a/pkg/tsdb/azuremonitor/time/azuremonitor-time.go +++ b/pkg/tsdb/azuremonitor/time/azuremonitor-time.go @@ -8,14 +8,14 @@ var ( // SetAutoTimeGrain tries to find the closest interval to the query's intervalMs value // if the metric has a limited set of possible intervals/time grains then use those // instead of the default list of intervals -func SetAutoTimeGrain(intervalMs int64, timeGrains []int64) (string, error) { +func SetAutoTimeGrain(intervalMs int64, timeGrains []int64) (*string, error) { autoInterval := FindClosestAllowedIntervalMS(intervalMs, timeGrains) autoTimeGrain, err := CreateISO8601DurationFromIntervalMS(autoInterval) if err != nil { - return "", err + return nil, err } - return autoTimeGrain, nil + return &autoTimeGrain, nil } // FindClosestAllowedIntervalMS is used for the auto time grain setting. diff --git a/pkg/tsdb/azuremonitor/types/types.go b/pkg/tsdb/azuremonitor/types/types.go index 50b2819849b..dd7ed3c3e10 100644 --- a/pkg/tsdb/azuremonitor/types/types.go +++ b/pkg/tsdb/azuremonitor/types/types.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana-azure-sdk-go/azcredentials" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" ) const ( @@ -83,8 +84,8 @@ type AzureMonitorQuery struct { Alias string TimeRange backend.TimeRange BodyFilter string - Dimensions []AzureMonitorDimensionFilter - Resources map[string]AzureMonitorResource + Dimensions []dataquery.AzureMetricDimension + Resources map[string]dataquery.AzureMonitorResource Subscription string } @@ -138,110 +139,38 @@ type AzureMonitorResource struct { ResourceName string `json:"resourceName"` } -// AzureMonitorJSONQuery is the frontend JSON query model for an Azure Monitor query. -type AzureMonitorJSONQuery struct { - AzureMonitor struct { - ResourceURI string `json:"resourceUri"` - // These are used to reconstruct a resource URI - MetricNamespace string `json:"metricNamespace"` - CustomNamespace string `json:"customNamespace"` - MetricName string `json:"metricName"` - Region string `json:"region"` - Resources []AzureMonitorResource `json:"resources"` - - Aggregation string `json:"aggregation"` - Alias string `json:"alias"` - DimensionFilters []AzureMonitorDimensionFilter `json:"dimensionFilters"` // new model - TimeGrain string `json:"timeGrain"` - Top string `json:"top"` - - AllowedTimeGrainsMs []int64 `json:"allowedTimeGrainsMs"` - Dimension string `json:"dimension"` // old model - DimensionFilter string `json:"dimensionFilter"` // old model - Format string `json:"format"` - - // Deprecated, MetricNamespace should be used instead - MetricDefinition string `json:"metricDefinition"` - // Deprecated: Use Resources with a single element instead - AzureMonitorResource - } `json:"azureMonitor"` - Subscription string `json:"subscription"` -} - -// AzureMonitorDimensionFilter is the model for the frontend sent for azureMonitor metric -// queries like "BlobType", "eq", "*" -type AzureMonitorDimensionFilter struct { - Dimension string `json:"dimension"` - Operator string `json:"operator"` - Filters []string `json:"filters,omitempty"` - // Deprecated: To support multiselection, filters are passed in a slice now. Also migrated in frontend. - Filter *string `json:"filter,omitempty"` -} - type AzureMonitorDimensionFilterBackend struct { Key string `json:"key"` Operator int `json:"operator"` Values []string `json:"values"` } -func (a AzureMonitorDimensionFilter) ConstructFiltersString() string { +func ConstructFiltersString(a dataquery.AzureMetricDimension) string { var filterStrings []string for _, filter := range a.Filters { - filterStrings = append(filterStrings, fmt.Sprintf("%v %v '%v'", a.Dimension, a.Operator, filter)) + dimension := "" + operator := "" + if a.Dimension != nil { + dimension = *a.Dimension + } + if a.Operator != nil { + operator = *a.Operator + } + filterStrings = append(filterStrings, fmt.Sprintf("%v %v '%v'", dimension, operator, filter)) } - if a.Operator == "eq" { + if a.Operator != nil && *a.Operator == "eq" { return strings.Join(filterStrings, " or ") - } else { - return strings.Join(filterStrings, " and ") } + return strings.Join(filterStrings, " and ") } // LogJSONQuery is the frontend JSON query model for an Azure Log Analytics query. type LogJSONQuery struct { - AzureLogAnalytics struct { - Query string `json:"query"` - ResultFormat string `json:"resultFormat"` - Resources []string `json:"resources"` - OperationId string `json:"operationId"` - - // Deprecated: Queries should be migrated to use Resource instead - Workspace string `json:"workspace"` - // Deprecated: Use Resources instead - Resource string `json:"resource"` - } `json:"azureLogAnalytics"` + AzureLogAnalytics dataquery.AzureLogsQuery `json:"azureLogAnalytics"` } type TracesJSONQuery struct { - AzureTraces struct { - // Filters for property values. - Filters []TracesFilters `json:"filters"` - - // Operation ID. Used only for Traces queries. - OperationId *string `json:"operationId"` - - // KQL query to be executed. - Query *string `json:"query"` - - // Array of resource URIs to be queried. - Resources []string `json:"resources"` - - // Specifies the format results should be returned as. - ResultFormat *string `json:"resultFormat"` - - // Types of events to filter by. - TraceTypes []string `json:"traceTypes"` - } `json:"azureTraces"` -} - -type TracesFilters struct { - // Values to filter by. - Filters []string `json:"filters"` - - // Comparison operator to use. Either equals or not equals. - Operation string `json:"operation"` - - // Property name, auto-populated based on available traces. - Property string `json:"property"` + AzureTraces dataquery.AzureTracesQuery `json:"azureTraces"` } // MetricChartDefinition is the JSON model for a metrics chart definition