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 204c0d1f2db..6a80f8484df 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,7 +1,7 @@ import { find, startsWith } from 'lodash'; import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; -import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; +import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { resourceTypeDisplayNames, supportedMetricNamespaces } from '../azureMetadata'; @@ -42,11 +42,13 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend) { super(instanceSettings); this.timeSrv = getTimeSrv(); + this.templateSrv = getTemplateSrv(); this.defaultSubscriptionId = instanceSettings.jsonData.subscriptionId; const cloud = getAzureCloud(instanceSettings); @@ -244,7 +246,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { @@ -273,7 +276,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { return ResponseParser.parseResponseValues(result, 'name.localizedValue', 'name.value'); @@ -285,7 +289,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { return ResponseParser.parseMetadata(result, metricName); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts index 0e3a01293ef..a7103ef1046 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts @@ -1,33 +1,96 @@ +import { getTemplateSrv } from '@grafana/runtime'; + import UrlBuilder from './url_builder'; +let replaceMock = jest.fn().mockImplementation((s: string) => s); + +jest.mock('@grafana/runtime', () => { + const original = jest.requireActual('@grafana/runtime'); + return { + ...original, + getTemplateSrv: () => ({ + replace: replaceMock, + }), + }; +}); + describe('AzureMonitorUrlBuilder', () => { + let templateSrv = getTemplateSrv(); describe('buildResourceUri', () => { it('builds a resource uri when the required properties are provided', () => { - expect(UrlBuilder.buildResourceUri('sub', 'group', 'Microsoft.NetApp/netAppAccounts', 'name')).toEqual( - '/subscriptions/sub/resourceGroups/group/providers/Microsoft.NetApp/netAppAccounts/name' - ); + expect( + UrlBuilder.buildResourceUri('sub', 'group', 'Microsoft.NetApp/netAppAccounts', 'name', templateSrv) + ).toEqual('/subscriptions/sub/resourceGroups/group/providers/Microsoft.NetApp/netAppAccounts/name'); }); it('builds a resource uri correctly when a template variable is used as namespace', () => { - expect(UrlBuilder.buildResourceUri('sub', 'group', '$ns', 'name')).toEqual( + expect(UrlBuilder.buildResourceUri('sub', 'group', '$ns', 'name', templateSrv)).toEqual( '/subscriptions/sub/resourceGroups/group/providers/$ns/name' ); }); it('builds a resource uri correctly when the namespace includes a storage sub-resource', () => { expect( - UrlBuilder.buildResourceUri('sub', 'group', 'Microsoft.Storage/storageAccounts/tableServices', 'name') + UrlBuilder.buildResourceUri( + 'sub', + 'group', + 'Microsoft.Storage/storageAccounts/tableServices', + 'name', + templateSrv + ) ).toEqual( '/subscriptions/sub/resourceGroups/group/providers/Microsoft.Storage/storageAccounts/name/tableServices/default' ); }); + + describe('when using template variables', () => { + replaceMock = jest + .fn() + .mockImplementation((s: string) => + s + .replace('$ns', 'Microsoft.Storage/storageAccounts') + .replace('$ns2', 'tableServices') + .replace('$rs', 'name') + .replace('$rs2', 'default') + ); + templateSrv = getTemplateSrv(); + + it('builds a resource uri without specifying a subresource (default)', () => { + expect(UrlBuilder.buildResourceUri('sub', 'group', '$ns/tableServices', 'name', templateSrv)).toEqual( + '/subscriptions/sub/resourceGroups/group/providers/$ns/name/tableServices/default' + ); + }); + + it('builds a resource uri specifying a subresource (default)', () => { + expect(UrlBuilder.buildResourceUri('sub', 'group', '$ns/tableServices', 'name/default', templateSrv)).toEqual( + '/subscriptions/sub/resourceGroups/group/providers/$ns/name/tableServices/default' + ); + }); + + it('builds a resource uri specifying a resource template variable', () => { + expect(UrlBuilder.buildResourceUri('sub', 'group', '$ns/tableServices', '$rs/default', templateSrv)).toEqual( + '/subscriptions/sub/resourceGroups/group/providers/$ns/$rs/tableServices/default' + ); + }); + + it('builds a resource uri specifying multiple template variables', () => { + expect(UrlBuilder.buildResourceUri('sub', 'group', '$ns/$ns2', '$rs/$rs2', templateSrv)).toEqual( + '/subscriptions/sub/resourceGroups/group/providers/$ns/$rs/$ns2/$rs2' + ); + }); + }); }); describe('when a resource uri is provided', () => { it('builds a getMetricNamesnamespace url', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { - resourceUri: '/subscriptions/sub/resource-uri/resource', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( + '', + '2017-05-01-preview', + { + resourceUri: '/subscriptions/sub/resource-uri/resource', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' ); @@ -36,10 +99,15 @@ describe('AzureMonitorUrlBuilder', () => { describe('when a resource uri and metric namespace is provided', () => { it('builds a getMetricNames url', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - resourceUri: '/subscriptions/sub/resource-uri/resource', - metricNamespace: 'Microsoft.Sql/servers', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + resourceUri: '/subscriptions/sub/resource-uri/resource', + metricNamespace: 'Microsoft.Sql/servers', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=Microsoft.Sql%2Fservers' ); @@ -49,12 +117,17 @@ describe('AzureMonitorUrlBuilder', () => { describe('Legacy query object', () => { describe('when metric definition is Microsoft.NetApp/netAppAccounts/capacityPools/volumes', () => { it('should build the getMetricNamespaces url in the even longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', - resourceName: 'rn1/rn2/rn3', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', + resourceName: 'rn1/rn2/rn3', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/' + 'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' @@ -64,12 +137,17 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers/databases', () => { it('should build the getMetricNamespaces url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Sql/servers/databases', - resourceName: 'rn1/rn2', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers/databases', + resourceName: 'rn1/rn2', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' + 'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' @@ -79,12 +157,17 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers', () => { it('should build the getMetricNamespaces url in the shorter format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Sql/servers', - resourceName: 'rn', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers', + resourceName: 'rn', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' + 'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' @@ -94,13 +177,18 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.NetApp/netAppAccounts/capacityPools/volumes and the metricNamespace is default', () => { it('should build the getMetricNames url in the even longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', - resourceName: 'rn1/rn2/rn3', - metricNamespace: 'default', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', + resourceName: 'rn1/rn2/rn3', + metricNamespace: 'default', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -110,13 +198,18 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers/databases and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Sql/servers/databases', - resourceName: 'rn1/rn2', - metricNamespace: 'default', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers/databases', + resourceName: 'rn1/rn2', + metricNamespace: 'default', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -126,13 +219,18 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers and the metricNamespace is default', () => { it('should build the getMetricNames url in the shorter format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Sql/servers', - resourceName: 'rn', - metricNamespace: 'default', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers', + resourceName: 'rn', + metricNamespace: 'default', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -142,13 +240,18 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/blobServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Storage/storageAccounts/blobServices', - resourceName: 'rn1/default', - metricNamespace: 'default', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/blobServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -158,13 +261,18 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/fileServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Storage/storageAccounts/fileServices', - resourceName: 'rn1/default', - metricNamespace: 'default', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/fileServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -174,13 +282,18 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/tableServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Storage/storageAccounts/tableServices', - resourceName: 'rn1/default', - metricNamespace: 'default', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/tableServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/tableServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -190,13 +303,18 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/queueServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { - subscription: 'sub1', - resourceGroup: 'rg', - metricDefinition: 'Microsoft.Storage/storageAccounts/queueServices', - resourceName: 'rn1/default', - metricNamespace: 'default', - }); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( + '', + '2017-05-01-preview', + { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/queueServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }, + templateSrv + ); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/queueServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts index 51dd5a80b35..d52df58b96e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts @@ -1,3 +1,5 @@ +import { TemplateSrv } from '@grafana/runtime'; + import { GetMetricNamespacesQuery, GetMetricNamesQuery } from '../types'; export default class UrlBuilder { @@ -5,40 +7,65 @@ export default class UrlBuilder { subscriptionId: string, resourceGroup: string, metricDefinition: string, - resourceName: string + resourceName: string, + templateSrv: TemplateSrv ) { + const metricDefinitionProcessed = templateSrv.replace(metricDefinition); const metricDefinitionArray = metricDefinition.split('/'); + const resourceNameProcessed = templateSrv.replace(resourceName); const resourceNameArray = resourceName.split('/'); const provider = metricDefinitionArray.shift(); const urlArray = ['/subscriptions', subscriptionId, 'resourceGroups', resourceGroup, 'providers', provider]; - if (metricDefinition.startsWith('Microsoft.Storage/storageAccounts/') && resourceNameArray.at(-1) !== 'default') { + + if ( + metricDefinitionProcessed.startsWith('Microsoft.Storage/storageAccounts/') && + !resourceNameProcessed.endsWith('default') + ) { resourceNameArray.push('default'); } - if (metricDefinitionArray.length > 0) { - for (const i in metricDefinitionArray) { - urlArray.push(metricDefinitionArray[i]); - urlArray.push(resourceNameArray[i]); - } - } else { - urlArray.push(resourceNameArray[0]); + + if (resourceNameArray.length > metricDefinitionArray.length) { + const parentResource = resourceNameArray.shift(); + urlArray.push(parentResource); + } + + for (const i in metricDefinitionArray) { + urlArray.push(metricDefinitionArray[i]); + urlArray.push(resourceNameArray[i]); } return urlArray.join('/'); } - static buildAzureMonitorGetMetricNamespacesUrl(baseUrl: string, apiVersion: string, query: GetMetricNamespacesQuery) { + static buildAzureMonitorGetMetricNamespacesUrl( + baseUrl: string, + apiVersion: string, + query: GetMetricNamespacesQuery, + templateSrv: TemplateSrv + ) { let resourceUri: string; if ('resourceUri' in query) { resourceUri = query.resourceUri; } else { const { subscription, resourceGroup, metricDefinition, resourceName } = query; - resourceUri = UrlBuilder.buildResourceUri(subscription, resourceGroup, metricDefinition, resourceName); + resourceUri = UrlBuilder.buildResourceUri( + subscription, + resourceGroup, + metricDefinition, + resourceName, + templateSrv + ); } return `${baseUrl}${resourceUri}/providers/microsoft.insights/metricNamespaces?api-version=${apiVersion}`; } - static buildAzureMonitorGetMetricNamesUrl(baseUrl: string, apiVersion: string, query: GetMetricNamesQuery) { + static buildAzureMonitorGetMetricNamesUrl( + baseUrl: string, + apiVersion: string, + query: GetMetricNamesQuery, + templateSrv: TemplateSrv + ) { let resourceUri: string; const { metricNamespace } = query; @@ -46,7 +73,13 @@ export default class UrlBuilder { resourceUri = query.resourceUri; } else { const { subscription, resourceGroup, metricDefinition, resourceName } = query; - resourceUri = UrlBuilder.buildResourceUri(subscription, resourceGroup, metricDefinition, resourceName); + resourceUri = UrlBuilder.buildResourceUri( + subscription, + resourceGroup, + metricDefinition, + resourceName, + templateSrv + ); } return ( diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts index 560d734cbf6..9ed96bb1103 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts @@ -2,6 +2,8 @@ import deepEqual from 'fast-deep-equal'; import { defaults } from 'lodash'; import { useEffect, useMemo } from 'react'; +import { getTemplateSrv } from '@grafana/runtime'; + import { AzureMonitorQuery, AzureQueryType } from '../../types'; import migrateQuery from '../../utils/migrateQuery'; @@ -12,7 +14,7 @@ const DEFAULT_QUERY = { const prepareQuery = (query: AzureMonitorQuery) => { // Note: _.defaults does not apply default values deeply. const withDefaults = defaults({}, query, DEFAULT_QUERY); - const migratedQuery = migrateQuery(withDefaults); + const migratedQuery = migrateQuery(withDefaults, getTemplateSrv()); // If we didn't make any changes to the object, then return the original object to keep the // identity the same, and not trigger any other useEffects or anything. diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts index f5adb0410cd..5ff8d9334cb 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts @@ -70,7 +70,7 @@ export default class Datasource extends DataSourceApi