Azure Monitor: Add support to migrate some queries with template variables (#51881)

This commit is contained in:
Andres Martinez Gotor 2022-07-07 14:44:53 +02:00 committed by GitHub
parent f6f017edc2
commit 438c76252a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 319 additions and 113 deletions

View File

@ -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<AzureM
declare resourceGroup: string;
declare resourceName: string;
timeSrv: TimeSrv;
templateSrv: TemplateSrv;
constructor(private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
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<AzureM
const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl(
this.resourcePath,
this.apiPreviewVersion,
this.replaceTemplateVariables(query)
this.replaceTemplateVariables(query),
this.templateSrv
);
return this.getResource(url)
.then((result: AzureMonitorMetricNamespacesResponse) => {
@ -273,7 +276,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
this.resourcePath,
this.apiVersion,
this.replaceTemplateVariables(query)
this.replaceTemplateVariables(query),
this.templateSrv
);
return this.getResource(url).then((result: AzureMonitorMetricNamesResponse) => {
return ResponseParser.parseResponseValues(result, 'name.localizedValue', 'name.value');
@ -285,7 +289,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
this.resourcePath,
this.apiVersion,
this.replaceTemplateVariables(query)
this.replaceTemplateVariables(query),
this.templateSrv
);
return this.getResource(url).then((result: AzureMonitorMetricsMetadataResponse) => {
return ResponseParser.parseMetadata(result, metricName);

View File

@ -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'

View File

@ -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 (

View File

@ -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.

View File

@ -70,7 +70,7 @@ export default class Datasource extends DataSourceWithBackend<AzureMonitorQuery,
for (const baseTarget of options.targets) {
// Migrate old query structures
const target = datasourceMigrations(baseTarget);
const target = datasourceMigrations(baseTarget, this.templateSrv);
// Skip hidden or invalid queries or ones without properties
if (!target.queryType || target.hide || !hasQueryForType(target)) {

View File

@ -1,7 +1,22 @@
import { getTemplateSrv } from '@grafana/runtime';
import { AzureMetricDimension, AzureMonitorQuery, AzureQueryType } from '../types';
import migrateQuery from './migrateQuery';
let replaceMock = jest.fn().mockImplementation((s: string) => s);
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
getTemplateSrv: () => ({
replace: replaceMock,
}),
};
});
let templateSrv = getTemplateSrv();
const azureMonitorQueryV7 = {
appInsights: { dimension: [], metricName: 'select', timeGrain: 'auto' },
azureLogAnalytics: {
@ -82,7 +97,7 @@ const modernMetricsQuery: AzureMonitorQuery = {
describe('AzureMonitor: migrateQuery', () => {
it('modern queries should not change', () => {
const result = migrateQuery(modernMetricsQuery);
const result = migrateQuery(modernMetricsQuery, templateSrv);
// MUST use .toBe because we want to assert that the identity of unmigrated queries remains the same
expect(modernMetricsQuery).toBe(result);
@ -90,7 +105,7 @@ describe('AzureMonitor: migrateQuery', () => {
describe('migrating from a v7 query to the latest query version', () => {
it('should build a resource uri', () => {
const result = migrateQuery(azureMonitorQueryV7);
const result = migrateQuery(azureMonitorQueryV7, templateSrv);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
@ -104,7 +119,7 @@ describe('AzureMonitor: migrateQuery', () => {
describe('migrating from a v8 query to the latest query version', () => {
it('should build a resource uri', () => {
const result = migrateQuery(azureMonitorQueryV8);
const result = migrateQuery(azureMonitorQueryV8, templateSrv);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
@ -114,6 +129,20 @@ describe('AzureMonitor: migrateQuery', () => {
})
);
});
it('should not build a resource uri with an unsupported template variable', () => {
replaceMock = jest.fn().mockImplementation((s: string) => s.replace('$ns', 'Microsoft.Storage/storageAccounts'));
templateSrv = getTemplateSrv();
const query = {
...azureMonitorQueryV8,
azureMonitor: {
...azureMonitorQueryV8,
metricDefinition: '$ns',
},
};
const result = migrateQuery(query, templateSrv);
expect(result.azureMonitor?.resourceUri).toBeUndefined();
});
});
describe('migrating from a v9 query to the latest query version', () => {
@ -121,7 +150,7 @@ describe('AzureMonitor: migrateQuery', () => {
const dimensionFilters: AzureMetricDimension[] = [
{ dimension: 'TestDimension', operator: 'eq', filters: ['testFilter'] },
];
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } });
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } }, templateSrv);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
@ -132,7 +161,7 @@ describe('AzureMonitor: migrateQuery', () => {
});
it('correctly updates old filter containing wildcard', () => {
const dimensionFilters: AzureMetricDimension[] = [{ dimension: 'TestDimension', operator: 'eq', filter: '*' }];
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } });
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } }, templateSrv);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
@ -145,7 +174,7 @@ describe('AzureMonitor: migrateQuery', () => {
});
it('correctly updates old filter containing value', () => {
const dimensionFilters: AzureMetricDimension[] = [{ dimension: 'TestDimension', operator: 'eq', filter: 'test' }];
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } });
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } }, templateSrv);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
@ -160,7 +189,7 @@ describe('AzureMonitor: migrateQuery', () => {
const dimensionFilters: AzureMetricDimension[] = [
{ dimension: 'TestDimension', operator: 'eq', filter: '*', filters: ['testFilter'] },
];
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } });
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } }, templateSrv);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
@ -179,7 +208,7 @@ describe('AzureMonitor: migrateQuery', () => {
const dimensionFilters: AzureMetricDimension[] = [
{ dimension: 'TestDimension', operator: 'eq', filter: 'testFilter', filters: ['testFilter'] },
];
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } });
const result = migrateQuery({ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } }, templateSrv);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({

View File

@ -1,3 +1,5 @@
import { TemplateSrv } from '@grafana/runtime';
import UrlBuilder from '../azure_monitor/url_builder';
import { setKustoQuery } from '../components/LogsQueryEditor/setQueryValue';
import {
@ -9,7 +11,7 @@ import { AzureMetricDimension, AzureMonitorQuery, AzureQueryType } from '../type
const OLD_DEFAULT_DROPDOWN_VALUE = 'select';
export default function migrateQuery(query: AzureMonitorQuery): AzureMonitorQuery {
export default function migrateQuery(query: AzureMonitorQuery, templateSrv: TemplateSrv): AzureMonitorQuery {
let workingQuery = query;
// The old angular controller also had a `migrateApplicationInsightsKeys` migraiton that
@ -21,7 +23,7 @@ export default function migrateQuery(query: AzureMonitorQuery): AzureMonitorQuer
workingQuery = migrateLogAnalyticsToFromTimes(workingQuery);
workingQuery = migrateToDefaultNamespace(workingQuery);
workingQuery = migrateDimensionToDimensionFilter(workingQuery);
workingQuery = migrateResourceUri(workingQuery);
workingQuery = migrateResourceUri(workingQuery, templateSrv);
workingQuery = migrateDimensionFilterToArray(workingQuery);
return workingQuery;
@ -96,7 +98,7 @@ function migrateDimensionToDimensionFilter(query: AzureMonitorQuery): AzureMonit
// Azure Monitor metric queries prior to Grafana version 9 did not include a `resourceUri`.
// The resourceUri was previously constructed with the subscription id, resource group,
// metric definition (a.k.a. resource type), and the resource name.
function migrateResourceUri(query: AzureMonitorQuery): AzureMonitorQuery {
function migrateResourceUri(query: AzureMonitorQuery, templateSrv: TemplateSrv): AzureMonitorQuery {
const azureMonitorQuery = query.azureMonitor;
if (!azureMonitorQuery || azureMonitorQuery.resourceUri) {
@ -109,11 +111,28 @@ function migrateResourceUri(query: AzureMonitorQuery): AzureMonitorQuery {
return query;
}
if (metricDefinition.includes('$') || resourceName.includes('$')) {
const metricDefinitionArray = metricDefinition.split('/');
if (metricDefinitionArray.some((p) => templateSrv.replace(p).split('/').length > 2)) {
// If a metric definition includes template variable with a subresource e.g.
// Microsoft.Storage/storageAccounts/libraries, it's not possible to generate a valid
// resource URI
return query;
}
const resourceUri = UrlBuilder.buildResourceUri(subscription, resourceGroup, metricDefinition, resourceName);
const resourceNameArray = resourceName.split('/');
if (resourceNameArray.some((p) => templateSrv.replace(p).split('/').length > 1)) {
// If a resource name includes template variable with a subresource e.g.
// abc123/def456, it's not possible to generate a valid resource URI
return query;
}
const resourceUri = UrlBuilder.buildResourceUri(
subscription,
resourceGroup,
metricDefinition,
resourceName,
templateSrv
);
return {
...query,
@ -163,7 +182,7 @@ function migrateDimensionFilterToArray(query: AzureMonitorQuery): AzureMonitorQu
// datasource.ts also contains some migrations, which have been moved to here. Unsure whether
// they should also do all the other migrations...
export function datasourceMigrations(query: AzureMonitorQuery): AzureMonitorQuery {
export function datasourceMigrations(query: AzureMonitorQuery, templateSrv: TemplateSrv): AzureMonitorQuery {
let workingQuery = query;
if (!workingQuery.queryType) {
@ -175,7 +194,7 @@ export function datasourceMigrations(query: AzureMonitorQuery): AzureMonitorQuer
if (workingQuery.queryType === AzureQueryType.AzureMonitor && workingQuery.azureMonitor) {
workingQuery = migrateDimensionToDimensionFilter(workingQuery);
workingQuery = migrateResourceUri(workingQuery);
workingQuery = migrateResourceUri(workingQuery, templateSrv);
workingQuery = migrateDimensionFilterToArray(workingQuery);
}