From 34fa7b493cd1a38145f3ca2f0514ac4a10bea497 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Fri, 20 May 2022 16:36:42 +0100 Subject: [PATCH] AzureMonitor: Fixes metric definition for Azure Storage queue/file/blob/table resources. (#49101) * Appropriately set metric definition - Nested storage account resources (queues/blobs/tables/files) require metric definition of Microsoft.Storage/storageAccounts - Update tests accordingly * Restructure getResourceNames test - Add expect on getResource args * Update to fix issue for new query editor - Reconstruct resourceUri if the resource is a storage account - Correctly push storage namespaces as options for metric namespaces - Filter options appropriately * Fix duplicate options * Fix lint issues * Add comment explaining URI modification --- .../azure_monitor_datasource.test.ts | 17 ++++++---- .../azure_monitor/azure_monitor_datasource.ts | 31 ++++++++++++++++--- .../MetricNamespaceField.tsx | 6 ++-- .../MetricsQueryEditor/setQueryValue.ts | 18 +++++++++++ .../NewMetricsQueryEditor/dataHooks.ts | 2 +- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts index 96e1baa2cd9..da69bd5aa11 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts @@ -1,3 +1,5 @@ +import { startsWith } from 'lodash'; + import { DataSourceInstanceSettings } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; @@ -445,25 +447,28 @@ describe('AzureMonitorDatasource', () => { ], }; - beforeEach(() => { + it('should return list of Resource Names', () => { + metricDefinition = 'Microsoft.Storage/storageAccounts/blobServices'; + const validMetricDefinition = startsWith(metricDefinition, 'Microsoft.Storage/storageAccounts/') + ? 'Microsoft.Storage/storageAccounts' + : metricDefinition; ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`; expect(path).toBe( basePath + - `/${resourceGroup}/resources?$filter=resourceType eq '${metricDefinition}'&api-version=2021-04-01` + `/${resourceGroup}/resources?$filter=resourceType eq '${validMetricDefinition}'&api-version=2021-04-01` ); return Promise.resolve(response); }); - }); - - it('should return list of Resource Names', () => { - metricDefinition = 'Microsoft.Storage/storageAccounts/blobServices'; return ctx.ds .getResourceNames(subscription, resourceGroup, metricDefinition) .then((results: Array<{ text: string; value: string }>) => { expect(results.length).toEqual(1); expect(results[0].text).toEqual('storagetest/default'); expect(results[0].value).toEqual('storagetest/default'); + expect(ctx.ds.azureMonitorDatasource.getResource).toHaveBeenCalledWith( + `azuremonitor/subscriptions/${subscription}/resourceGroups/${resourceGroup}/resources?$filter=resourceType eq '${validMetricDefinition}'&api-version=2021-04-01` + ); }); }); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts index 6dd613faaef..52af47ee5a1 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts @@ -1,4 +1,4 @@ -import { filter, startsWith } from 'lodash'; +import { filter, find, startsWith } from 'lodash'; import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; @@ -207,9 +207,12 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { - return ResponseParser.parseResponseValues(result, 'name', 'properties.metricNamespaceName'); - }); + return this.getResource(url) + .then((result: AzureMonitorMetricNamespacesResponse) => { + return ResponseParser.parseResponseValues(result, 'name', 'properties.metricNamespaceName'); + }) + .then((result) => { + if (url.includes('Microsoft.Storage/storageAccounts')) { + const storageNamespaces = [ + 'Microsoft.Storage/storageAccounts', + 'Microsoft.Storage/storageAccounts/blobServices', + 'Microsoft.Storage/storageAccounts/fileServices', + 'Microsoft.Storage/storageAccounts/tableServices', + 'Microsoft.Storage/storageAccounts/queueServices', + ]; + for (const namespace of storageNamespaces) { + if (!find(result, ['value', namespace.toLowerCase()])) { + result.push({ value: namespace, text: namespace }); + } + } + } + return result; + }); } getMetricNames(query: GetMetricNamesQuery) { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx index da80fe25609..72a81ef6a69 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx @@ -31,9 +31,11 @@ const MetricNamespaceField: React.FC = ({ ); const options = useMemo(() => [...metricNamespaces, variableOptionGroup], [metricNamespaces, variableOptionGroup]); - const optionValues = metricNamespaces.map((m) => m.value).concat(variableOptionGroup.options.map((p) => p.value)); + const optionValues = metricNamespaces + .map((m) => m.value.toLowerCase()) + .concat(variableOptionGroup.options.map((p) => p.value)); const value = query.azureMonitor?.metricNamespace; - if (value && !optionValues.includes(value)) { + if (value && !optionValues.includes(value.toLowerCase())) { options.push({ label: value, value }); } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts index ae870c8e34c..a83c214380c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/setQueryValue.ts @@ -109,6 +109,23 @@ export function setMetricNamespace(query: AzureMonitorQuery, metricNamespace: st return query; } + let resourceUri = query.azureMonitor?.resourceUri; + + // Storage Account URIs need to be handled differently due to the additional storage services (blob/queue/table/file). + // When one of these namespaces is selected it does not form a part of the URI for the storage account and so must be appended. + // The 'default' path must also be appended. Without these two paths any API call will fail. + if (resourceUri && metricNamespace?.includes('Microsoft.Storage/storageAccounts')) { + const splitUri = resourceUri.split('/'); + const accountNameIndex = splitUri.findIndex((item) => item === 'storageAccounts') + 1; + const baseUri = splitUri.slice(0, accountNameIndex + 1).join('/'); + if (metricNamespace === 'Microsoft.Storage/storageAccounts') { + resourceUri = baseUri; + } else { + const subNamespace = metricNamespace.split('/')[2]; + resourceUri = `${baseUri}/${subNamespace}/default`; + } + } + return { ...query, azureMonitor: { @@ -118,6 +135,7 @@ export function setMetricNamespace(query: AzureMonitorQuery, metricNamespace: st aggregation: undefined, timeGrain: '', dimensionFilters: [], + resourceUri, }, }; } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts index b616e296a85..9743ad93de3 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts @@ -144,7 +144,7 @@ function formatOptions( const options = rawResults.map(toOption); // account for custom values that might have been set in json file like ones crafted with a template variable (ex: "cloud-datasource-resource-$Environment") - if (selectedValue && !options.find((option) => option.value === selectedValue)) { + if (selectedValue && !options.find((option) => option.value === selectedValue.toLowerCase())) { options.push({ label: selectedValue, value: selectedValue }); }