import React from 'react';
import { getTemplateSrv } from '@grafana/runtime';
import { AzureMetricDimension, AzureMonitorErrorish, 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();
let setErrorMock = jest.fn();
const azureMonitorQueryV7 = {
appInsights: { dimension: [], metricName: 'select', timeGrain: 'auto' },
azureLogAnalytics: {
query:
'//change this example to create your own time series query\n
//the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full chart’s time range, choose the datetime column here\n| summarize count() by , bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
resultFormat: 'time_series',
workspace: 'mock-workspace-id',
},
azureMonitor: {
aggregation: 'Average',
allowedTimeGrainsMs: [60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000],
dimensionFilters: [{ dimension: 'dependency/success', filter: '', operator: 'eq' }],
metricDefinition: 'microsoft.insights/components',
metricName: 'dependencies/duration',
metricNamespace: 'microsoft.insights/components',
resourceGroup: 'cloud-datasources',
resourceName: 'AppInsightsTestData',
timeGrain: 'auto',
top: '10',
},
insightsAnalytics: {
query: '',
resultFormat: 'time_series',
},
queryType: AzureQueryType.AzureMonitor,
refId: 'A',
subscription: '44693801-6ee6-49de-9b2d-9106972f9572',
};
const azureMonitorQueryV8 = {
azureMonitor: {
aggregation: 'Average',
dimensionFilters: [],
metricDefinition: 'microsoft.insights/components',
metricName: 'dependencies/duration',
metricNamespace: 'microsoft.insights/components',
resourceGroup: 'cloud-datasources',
resourceName: 'AppInsightsTestData',
timeGrain: 'auto',
},
datasource: {
type: 'grafana-azure-monitor-datasource',
uid: 'sD-ZuB87k',
},
queryType: AzureQueryType.AzureMonitor,
refId: 'A',
subscription: '44693801-6ee6-49de-9b2d-9106972f9572',
};
const modernMetricsQuery: AzureMonitorQuery = {
azureLogAnalytics: {
query:
'//change this example to create your own time series query\n //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full chart’s time range, choose the datetime column here\n| summarize count() by , bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
resultFormat: 'time_series',
workspace: 'mock-workspace-id',
},
azureMonitor: {
aggregation: 'Average',
alias: '{{ dimensionvalue }}',
allowedTimeGrainsMs: [60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000],
dimensionFilters: [{ dimension: 'dependency/success', filters: ['*'], operator: 'eq' }],
metricDefinition: 'microsoft.insights/components',
metricName: 'dependencies/duration',
metricNamespace: 'microsoft.insights/components',
resourceGroup: 'cloud-datasources',
resourceName: 'AppInsightsTestData',
resourceUri:
'/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/microsoft.insights/components/AppInsightsTestData',
timeGrain: 'PT5M',
top: '10',
},
azureResourceGraph: { resultFormat: 'table' },
queryType: AzureQueryType.AzureMonitor,
refId: 'A',
subscription: '44693801-6ee6-49de-9b2d-9106972f9572',
subscriptions: ['44693801-6ee6-49de-9b2d-9106972f9572'],
};
describe('AzureMonitor: migrateQuery', () => {
it('modern queries should not change', () => {
const result = migrateQuery(modernMetricsQuery, templateSrv, setErrorMock);
// MUST use .toBe because we want to assert that the identity of unmigrated queries remains the same
expect(modernMetricsQuery).toBe(result);
});
describe('migrating from a v7 query to the latest query version', () => {
it('should build a resource uri', () => {
const result = migrateQuery(azureMonitorQueryV7, templateSrv, setErrorMock);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
resourceUri:
'/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/microsoft.insights/components/AppInsightsTestData',
}),
})
);
});
});
describe('migrating from a v8 query to the latest query version', () => {
it('should build a resource uri', () => {
const result = migrateQuery(azureMonitorQueryV8, templateSrv, setErrorMock);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
resourceUri:
'/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/microsoft.insights/components/AppInsightsTestData',
}),
})
);
});
it('should not build a resource uri with an unsupported namespace template variable', () => {
replaceMock = jest
.fn()
.mockImplementation((s: string) => s.replace('$ns', 'Microsoft.Storage/storageAccounts/tableServices'));
setErrorMock = jest
.fn()
.mockImplementation((errorSource: string, error: AzureMonitorErrorish) => 'Template Var error');
const errorElement = React.createElement(
'div',
null,
`Failed to create resource URI. Validate the metric definition template variable against supported cases `,
React.createElement(
'a',
{
href: 'https://grafana.com/docs/grafana/latest/datasources/azuremonitor/template-variables/',
},
'here.'
)
);
templateSrv = getTemplateSrv();
const query = {
...azureMonitorQueryV8,
azureMonitor: {
...azureMonitorQueryV8.azureMonitor,
metricDefinition: '$ns',
},
};
const result = migrateQuery(query, templateSrv, setErrorMock);
expect(result.azureMonitor?.resourceUri).toBeUndefined();
expect(setErrorMock).toHaveBeenCalledWith('Resource URI migration', errorElement);
});
it('should not build a resource uri with unsupported resource name template variable', () => {
replaceMock = jest.fn().mockImplementation((s: string) => s.replace('$resource', 'resource/default'));
setErrorMock = jest
.fn()
.mockImplementation((errorSource: string, error: AzureMonitorErrorish) => 'Template Var error');
const errorElement = React.createElement(
'div',
null,
`Failed to create resource URI. Validate the resource name template variable against supported cases `,
React.createElement(
'a',
{
href: 'https://grafana.com/docs/grafana/latest/datasources/azuremonitor/template-variables/',
},
'here.'
)
);
templateSrv = getTemplateSrv();
const query = {
...azureMonitorQueryV8,
azureMonitor: {
...azureMonitorQueryV8.azureMonitor,
resourceName: '$resource',
},
};
const result = migrateQuery(query, templateSrv, setErrorMock);
expect(result.azureMonitor?.resourceUri).toBeUndefined();
expect(setErrorMock).toHaveBeenCalledWith('Resource URI migration', errorElement);
});
});
describe('migrating from a v9 query to the latest query version', () => {
it('will not change valid dimension filters', () => {
const dimensionFilters: AzureMetricDimension[] = [
{ dimension: 'TestDimension', operator: 'eq', filters: ['testFilter'] },
];
const result = migrateQuery(
{ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } },
templateSrv,
setErrorMock
);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
dimensionFilters,
}),
})
);
});
it('correctly updates old filter containing wildcard', () => {
const dimensionFilters: AzureMetricDimension[] = [{ dimension: 'TestDimension', operator: 'eq', filter: '*' }];
const result = migrateQuery(
{ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } },
templateSrv,
setErrorMock
);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
dimensionFilters: [
{ dimension: dimensionFilters[0].dimension, operator: dimensionFilters[0].operator, filters: ['*'] },
],
}),
})
);
});
it('correctly updates old filter containing value', () => {
const dimensionFilters: AzureMetricDimension[] = [{ dimension: 'TestDimension', operator: 'eq', filter: 'test' }];
const result = migrateQuery(
{ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } },
templateSrv,
setErrorMock
);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
dimensionFilters: [
{ dimension: dimensionFilters[0].dimension, operator: dimensionFilters[0].operator, filters: ['test'] },
],
}),
})
);
});
it('correctly ignores wildcard if filters has a value', () => {
const dimensionFilters: AzureMetricDimension[] = [
{ dimension: 'TestDimension', operator: 'eq', filter: '*', filters: ['testFilter'] },
];
const result = migrateQuery(
{ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } },
templateSrv,
setErrorMock
);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
dimensionFilters: [
{
dimension: dimensionFilters[0].dimension,
operator: dimensionFilters[0].operator,
filters: ['testFilter'],
},
],
}),
})
);
});
it('correctly ignores duplicates', () => {
const dimensionFilters: AzureMetricDimension[] = [
{ dimension: 'TestDimension', operator: 'eq', filter: 'testFilter', filters: ['testFilter'] },
];
const result = migrateQuery(
{ ...azureMonitorQueryV8, azureMonitor: { dimensionFilters } },
templateSrv,
setErrorMock
);
expect(result).toMatchObject(
expect.objectContaining({
azureMonitor: expect.objectContaining({
dimensionFilters: [
{
dimension: dimensionFilters[0].dimension,
operator: dimensionFilters[0].operator,
filters: ['testFilter'],
},
],
}),
})
);
});
});
});