Files
grafana/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts
Andreas Christou a4d33a0f43 AzureMonitor: Improve handling of unsupported template variable cases in URIs (#52054)
* Set error message for certain template variable combinations

- Make use of setError method from query editor
- Update Azure Monitor error type
- Add test for case 2 from https://github.com/grafana/grafana/pull/51331

* Update template variable docs

* Fix lint issues

* Update docs/sources/datasources/azuremonitor/template-variables.md

Co-authored-by: Garrett Guillotte <100453168+gguillotte-grafana@users.noreply.github.com>

* PR comment updates

Co-authored-by: Garrett Guillotte <100453168+gguillotte-grafana@users.noreply.github.com>
2022-07-14 09:28:44 +01:00

300 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full charts time range, choose the datetime column here\n| summarize count() by <group by column>, 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<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full charts time range, choose the datetime column here\n| summarize count() by <group by column>, 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'],
},
],
}),
})
);
});
});
});