AzureMonitor: Remove dependencies on Grafana frontend (#75595)

* remove local graf templateSrv and use @grafana/runtime

* tests broken still 0.0

* wip

* moving around tests and fixing

* template var test using mocking and stuff

* AM ds tests fixed

* fix filter tests

* all tests work

* remove comment

* Update azure_log_analytics_datasource.ts

* remove deferred util - not used

* mock instead of spying

* not calling getTemplateSrv each time

* pass range down to filter instead of doing werid stuff

* use default time range if no time range

* prettierer

* remove note
This commit is contained in:
Andrew Hackmann
2023-10-04 09:25:05 -05:00
committed by GitHub
parent 09e638cd9d
commit 3ea6d1f7cc
19 changed files with 394 additions and 357 deletions
@@ -1,7 +1,5 @@
import { DataSourceInstanceSettings } from '@grafana/data';
import { ContextSrv } from 'app/core/services/context_srv';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import Datasource from '../datasource';
import { AzureDataSourceJsonData } from '../types';
@@ -20,15 +18,12 @@ export function createContext(overrides?: DeepPartial<Context>): Context {
const instanceSettings = createMockInstanceSetttings(overrides?.instanceSettings);
return {
instanceSettings,
templateSrv: new TemplateSrv(),
templateSrv: getTemplateSrv(),
datasource: new Datasource(instanceSettings),
getResource: jest.fn(),
};
}
const contextSrv = new ContextSrv();
const timeSrv = new TimeSrv(contextSrv);
export default function createMockDatasource(overrides?: DeepPartial<Datasource>) {
// We make this a partial so we get _some_ kind of type safety when making this, rather than
// having it be any or casted immediately to Datasource
@@ -71,7 +66,6 @@ export default function createMockDatasource(overrides?: DeepPartial<Datasource>
azureLogAnalyticsDatasource: {
getKustoSchema: () => Promise.resolve(),
getDeprecatedDefaultWorkSpace: () => 'defaultWorkspaceId',
timeSrv,
},
resourcePickerData: {
getSubscriptions: () => jest.fn().mockResolvedValue([]),
@@ -1,6 +1,5 @@
import { VariableType } from '@grafana/data';
import { VariableType, VariableWithOptions } from '@grafana/data';
import { LoadingState } from '@grafana/data/src/types/data';
import { VariableWithOptions } from 'app/features/variables/types';
interface TemplateableValue {
variableName: string;
@@ -1,4 +1,18 @@
import { CustomVariableModel, initialVariableModelState, VariableHide } from 'app/features/variables/types';
import { BaseVariableModel, CustomVariableModel, LoadingState, VariableHide } from '@grafana/data';
const initialVariableModelState: BaseVariableModel = {
id: '00000000-0000-0000-0000-000000000000',
rootStateKey: null,
name: '',
type: 'query',
global: false,
index: -1,
hide: VariableHide.dontHide,
skipUrlSync: false,
state: LoadingState.NotStarted,
error: null,
description: null,
};
export const subscriptionsVariable: CustomVariableModel = {
...initialVariableModelState,
@@ -1,37 +1,43 @@
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CustomVariableModel } from '@grafana/data';
import { Context, createContext } from '../__mocks__/datasource';
import createMockQuery from '../__mocks__/query';
import { createTemplateVariables } from '../__mocks__/utils';
import { singleVariable } from '../__mocks__/variables';
import AzureMonitorDatasource from '../datasource';
import { AzureLogsQuery, AzureMonitorQuery, AzureQueryType, AzureTracesQuery } from '../types';
import FakeSchemaData from './__mocks__/schema';
import AzureLogAnalyticsDatasource from './azure_log_analytics_datasource';
const templateSrv = new TemplateSrv();
let getTempVars = () => [] as CustomVariableModel[];
let replace = () => '';
jest.mock('app/core/services/backend_srv');
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getTemplateSrv: () => templateSrv,
}));
jest.mock('@grafana/runtime', () => {
return {
__esModule: true,
...jest.requireActual('@grafana/runtime'),
getTemplateSrv: () => ({
replace: replace,
getVariables: getTempVars,
updateTimeRange: jest.fn(),
containsTemplate: jest.fn(),
}),
};
});
describe('AzureLogAnalyticsDatasource', () => {
let ctx: Context;
beforeEach(() => {
templateSrv.init([singleVariable]);
templateSrv.getVariables = jest.fn().mockReturnValue([singleVariable]);
ctx = createContext({
instanceSettings: { jsonData: { subscriptionId: 'xxx' }, url: 'http://azureloganalyticsapi' },
});
ctx.templateSrv = templateSrv;
});
describe('When performing getSchema', () => {
beforeEach(() => {
getTempVars = () => [] as CustomVariableModel[];
replace = (target?: string) => target || '';
ctx = createContext();
ctx.getResource = jest.fn().mockImplementation((path: string) => {
expect(path).toContain('metadata');
return Promise.resolve(FakeSchemaData.getlogAnalyticsFakeMetadata());
@@ -71,6 +77,13 @@ describe('AzureLogAnalyticsDatasource', () => {
});
it('should interpolate variables when making a request for a schema with a uri that contains template variables', async () => {
replace = () => 'myWorkspace/var1-foo';
ctx = createContext();
ctx.getResource = jest.fn().mockImplementation((path: string) => {
expect(path).toContain('metadata');
return Promise.resolve(FakeSchemaData.getlogAnalyticsFakeMetadata());
});
ctx.datasource.azureLogAnalyticsDatasource.getResource = ctx.getResource;
await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace/$var1');
expect(ctx.getResource).lastCalledWith('loganalytics/v1myWorkspace/var1-foo/metadata');
});
@@ -89,7 +102,15 @@ describe('AzureLogAnalyticsDatasource', () => {
});
it('should include template variables as global parameters', async () => {
getTempVars = () => [singleVariable];
ctx = createContext();
ctx.getResource = jest.fn().mockImplementation((path: string) => {
expect(path).toContain('metadata');
return Promise.resolve(FakeSchemaData.getlogAnalyticsFakeMetadata());
});
ctx.datasource.azureLogAnalyticsDatasource.getResource = ctx.getResource;
const result = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace');
expect(result.globalScalarParameters?.map((f: { name: string }) => f.name)).toEqual([`$${singleVariable.name}`]);
});
});
@@ -129,37 +150,6 @@ describe('AzureLogAnalyticsDatasource', () => {
});
});
describe('When performing targetContainsTemplate', () => {
it('should return false when no variable is being used', () => {
const query = createMockQuery();
const ds = new AzureMonitorDatasource(ctx.instanceSettings);
query.queryType = AzureQueryType.LogAnalytics;
expect(ds.targetContainsTemplate(query)).toEqual(false);
});
it('should return true when resource field is using a variable', () => {
const templateSrv = new TemplateSrv();
const query = createMockQuery();
templateSrv.init([singleVariable]);
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.LogAnalytics;
query.azureLogAnalytics = { resources: [`$${singleVariable.name}`] };
expect(ds.targetContainsTemplate(query)).toEqual(true);
});
it('should return false when a variable is used in a different part of the query', () => {
const templateSrv = new TemplateSrv();
const query = createMockQuery();
templateSrv.init([singleVariable]);
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.LogAnalytics;
query.azureResourceGraph = { query: `$${singleVariable.name}` };
expect(ds.targetContainsTemplate(query)).toEqual(false);
});
});
describe('When performing filterQuery', () => {
let laDatasource: AzureLogAnalyticsDatasource;
@@ -248,7 +238,9 @@ describe('AzureLogAnalyticsDatasource', () => {
describe('When performing interpolateVariablesInQueries for azure_log_analytics', () => {
beforeEach(() => {
templateSrv.init([]);
getTempVars = () => [] as CustomVariableModel[];
replace = (target?: string) => target || '';
ctx = createContext();
});
it('should return a query unchanged if no template variables are provided', () => {
@@ -259,14 +251,18 @@ describe('AzureLogAnalyticsDatasource', () => {
});
it('should return a logs query with any template variables replaced', () => {
const templateableProps = ['resource', 'workspace', 'query'];
const templateVariables = createTemplateVariables(templateableProps);
templateSrv.init(Array.from(templateVariables.values()).map((item) => item.templateVariable));
replace = (target?: string) => {
if (target === '$var') {
return 'template-variable';
}
return target || '';
};
ctx = createContext();
const query = createMockQuery();
const azureLogAnalytics: Partial<AzureLogsQuery> = {};
azureLogAnalytics.query = '$query';
azureLogAnalytics.workspace = '$workspace';
azureLogAnalytics.resources = ['$resource'];
azureLogAnalytics.query = '$var';
azureLogAnalytics.workspace = '$var';
azureLogAnalytics.resources = ['$var'];
query.queryType = AzureQueryType.LogAnalytics;
query.azureLogAnalytics = {
...query.azureLogAnalytics,
@@ -275,15 +271,15 @@ describe('AzureLogAnalyticsDatasource', () => {
const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {});
expect(templatedQuery[0]).toHaveProperty('datasource');
expect(templatedQuery[0].azureLogAnalytics).toMatchObject({
query: templateVariables.get('query')?.templateVariable.current.value,
workspace: templateVariables.get('workspace')?.templateVariable.current.value,
resources: [templateVariables.get('resource')?.templateVariable.current.value],
query: 'template-variable',
workspace: 'template-variable',
resources: ['template-variable'],
});
});
it('should return a logs query with multiple resources template variables replaced', () => {
const templateVariables = createTemplateVariables(['resource'], 'resource1,resource2');
templateSrv.init(Array.from(templateVariables.values()).map((item) => item.templateVariable));
replace = () => 'resource1,resource2';
ctx = createContext();
const query = createMockQuery();
const azureLogAnalytics: Partial<AzureLogsQuery> = {};
azureLogAnalytics.resources = ['$resource'];
@@ -300,16 +296,15 @@ describe('AzureLogAnalyticsDatasource', () => {
});
it('should return a traces query with any template variables replaced', () => {
const templateableProps = ['resource', 'query', 'traceTypes', 'property', 'operation', 'filter', 'operationId'];
const templateVariables = createTemplateVariables(templateableProps);
templateSrv.init(Array.from(templateVariables.values()).map((item) => item.templateVariable));
replace = (target?: string) => (target === '$var' ? 'template-variable' : target || '');
ctx = createContext();
const query = createMockQuery();
const azureTraces: Partial<AzureTracesQuery> = {};
azureTraces.resources = ['$resource'];
azureTraces.query = '$query';
azureTraces.traceTypes = ['$traceTypes'];
azureTraces.filters = [{ filters: ['$filter'], operation: 'eq', property: '$property' }];
azureTraces.operationId = '$operationId';
azureTraces.resources = ['$var'];
azureTraces.query = '$var';
azureTraces.traceTypes = ['$var'];
azureTraces.filters = [{ filters: ['$var'], operation: 'eq', property: '$var' }];
azureTraces.operationId = '$var';
query.queryType = AzureQueryType.AzureTraces;
query.azureTraces = {
...query.azureTraces,
@@ -319,23 +314,23 @@ describe('AzureLogAnalyticsDatasource', () => {
const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {});
expect(templatedQuery[0]).toHaveProperty('datasource');
expect(templatedQuery[0].azureTraces).toMatchObject({
query: templateVariables.get('query')?.templateVariable.current.value,
resources: [templateVariables.get('resource')?.templateVariable.current.value],
operationId: templateVariables.get('operationId')?.templateVariable.current.value,
traceTypes: [templateVariables.get('traceTypes')?.templateVariable.current.value],
query: 'template-variable',
resources: ['template-variable'],
operationId: 'template-variable',
traceTypes: ['template-variable'],
filters: [
{
filters: [templateVariables.get('filter')?.templateVariable.current.value],
filters: ['template-variable'],
operation: 'eq',
property: templateVariables.get('property')?.templateVariable.current.value,
property: 'template-variable',
},
],
});
});
it('should return a trace query with multiple resources template variables replaced', () => {
const templateVariables = createTemplateVariables(['resource'], 'resource1,resource2');
templateSrv.init(Array.from(templateVariables.values()).map((item) => item.templateVariable));
replace = () => 'resource1,resource2';
ctx = createContext();
const query = createMockQuery();
const azureTraces: Partial<AzureTracesQuery> = {};
azureTraces.resources = ['$resource'];
@@ -1,8 +1,7 @@
import { map } from 'lodash';
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
import { TimeSrv, getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import ResponseParser from '../azure_monitor/response_parser';
import { getAuthType, getAzureCloud, getAzurePortalUrl } from '../credentials';
@@ -33,9 +32,10 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
azureMonitorPath: string;
firstWorkspace?: string;
readonly timeSrv: TimeSrv = getTimeSrv();
constructor(private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
constructor(
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
private readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings);
this.resourcePath = `${routeNames.logAnalytics}`;
@@ -85,7 +85,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
}
private getWorkspaceList(subscription: string): Promise<AzureAPIResponse<Workspace>> {
const subscriptionId = getTemplateSrv().replace(subscription || this.defaultSubscriptionId);
const subscriptionId = this.templateSrv.replace(subscription || this.defaultSubscriptionId);
const workspaceListUrl =
this.azureMonitorPath +
@@ -101,25 +101,23 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
}
async getKustoSchema(resourceUri: string) {
const templateSrv = getTemplateSrv();
const interpolatedUri = templateSrv.replace(resourceUri, {}, interpolateVariable);
const interpolatedUri = this.templateSrv.replace(resourceUri, {}, interpolateVariable);
const metadata = await this.getMetadata(interpolatedUri);
return transformMetadataToKustoSchema(metadata, interpolatedUri, templateSrv.getVariables());
return transformMetadataToKustoSchema(metadata, interpolatedUri, this.templateSrv.getVariables());
}
applyTemplateVariables(target: AzureMonitorQuery, scopedVars: ScopedVars): AzureMonitorQuery {
let item;
if (target.queryType === AzureQueryType.LogAnalytics && target.azureLogAnalytics) {
item = target.azureLogAnalytics;
const templateSrv = getTemplateSrv();
const resources = this.expandResourcesForMultipleVariables(item.resources, scopedVars);
let workspace = templateSrv.replace(item.workspace, scopedVars);
let workspace = this.templateSrv.replace(item.workspace, scopedVars);
if (!workspace && !resources && this.firstWorkspace) {
workspace = this.firstWorkspace;
}
const query = templateSrv.replace(item.query, scopedVars, interpolateVariable);
const query = this.templateSrv.replace(item.query, scopedVars, interpolateVariable);
return {
...target,
@@ -132,23 +130,22 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
// Workspace was removed in Grafana 8, but remains for backwards compat
workspace,
dashboardTime: item.dashboardTime,
timeColumn: templateSrv.replace(item.timeColumn, scopedVars),
timeColumn: this.templateSrv.replace(item.timeColumn, scopedVars),
},
};
}
if (target.queryType === AzureQueryType.AzureTraces && target.azureTraces) {
item = target.azureTraces;
const templateSrv = getTemplateSrv();
const resources = this.expandResourcesForMultipleVariables(item.resources, scopedVars);
const query = templateSrv.replace(item.query, scopedVars, interpolateVariable);
const traceTypes = item.traceTypes?.map((t) => templateSrv.replace(t, scopedVars));
const query = this.templateSrv.replace(item.query, scopedVars, interpolateVariable);
const traceTypes = item.traceTypes?.map((t) => this.templateSrv.replace(t, scopedVars));
const filters = (item.filters ?? [])
.filter((f) => !!f.property)
.map((f) => {
const filtersReplaced = f.filters?.map((filter) => templateSrv.replace(filter ?? '', scopedVars));
const filtersReplaced = f.filters?.map((filter) => this.templateSrv.replace(filter ?? '', scopedVars));
return {
property: templateSrv.replace(f.property, scopedVars),
property: this.templateSrv.replace(f.property, scopedVars),
operation: f.operation || 'eq',
filters: filtersReplaced || [],
};
@@ -162,7 +159,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
resultFormat: item.resultFormat,
query,
resources,
operationId: templateSrv.replace(target.azureTraces?.operationId, scopedVars),
operationId: this.templateSrv.replace(target.azureTraces?.operationId, scopedVars),
filters,
traceTypes,
},
@@ -180,9 +177,8 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
return undefined;
}
const expandedResources: string[] = [];
const templateSrv = getTemplateSrv();
resources.forEach((r: string) => {
const tempVars = templateSrv.replace(r, scopedVars, 'raw');
const tempVars = this.templateSrv.replace(r, scopedVars, 'raw');
const values = tempVars.split(',');
values.forEach((value) => {
expandedResources.push(value);
@@ -1,20 +1,27 @@
import { get, set } from 'lodash';
import { DataSourceInstanceSettings } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import createMockQuery from '../__mocks__/query';
import { createTemplateVariables } from '../__mocks__/utils';
import { multiVariable, singleVariable, subscriptionsVariable } from '../__mocks__/variables';
import { multiVariable } from '../__mocks__/variables';
import AzureMonitorDatasource from '../datasource';
import { AzureAPIResponse, AzureDataSourceJsonData, AzureQueryType, Location } from '../types';
import { AzureAPIResponse, AzureDataSourceJsonData, Location } from '../types';
const templateSrv = new TemplateSrv();
let replace = () => '';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getTemplateSrv: () => templateSrv,
}));
jest.mock('@grafana/runtime', () => {
return {
__esModule: true,
...jest.requireActual('@grafana/runtime'),
getTemplateSrv: () => ({
replace: replace,
getVariables: jest.fn(),
updateTimeRange: jest.fn(),
containsTemplate: jest.fn(),
}),
};
});
interface TestContext {
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>;
@@ -79,6 +86,11 @@ describe('AzureMonitorDatasource', () => {
});
describe('applyTemplateVariables', () => {
beforeEach(() => {
replace = (target?: string) => target || '';
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
});
it('should migrate metricDefinition to metricNamespace', () => {
const query = createMockQuery({
azureMonitor: {
@@ -99,15 +111,14 @@ describe('AzureMonitorDatasource', () => {
const resourceGroup = 'cloud-datasources';
const metricNamespace = 'microsoft.insights/components';
const resourceName = 'AppInsightsTestData';
templateSrv.init([
{
id: 'resourceUri',
name: 'resourceUri',
current: {
value: `/subscriptions/${subscription}/resourceGroups/${resourceGroup}/providers/${metricNamespace}/${resourceName}`,
},
},
]);
replace = (target?: string) => {
if (target?.includes('$resourceUri')) {
return `/subscriptions/${subscription}/resourceGroups/${resourceGroup}/providers/${metricNamespace}/${resourceName}`;
}
return target || '';
};
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
const query = createMockQuery({
azureMonitor: {
resourceUri: '$resourceUri',
@@ -126,22 +137,16 @@ describe('AzureMonitorDatasource', () => {
it('expand template variables in resource groups and names', () => {
const resourceGroup = '$rg';
const resourceName = '$rn';
templateSrv.init([
{
id: 'rg',
name: 'rg',
current: {
value: `rg1,rg2`,
},
},
{
id: 'rn',
name: 'rn',
current: {
value: `rn1,rn2`,
},
},
]);
replace = (target?: string) => {
if (target?.includes('$rg')) {
return 'rg1,rg2';
}
if (target?.includes('$rn')) {
return 'rn1,rn2';
}
return target || '';
};
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
const query = createMockQuery({
azureMonitor: {
resources: [{ resourceGroup, resourceName }],
@@ -162,15 +167,14 @@ describe('AzureMonitorDatasource', () => {
it('expand template variables for a region', () => {
const region = '$reg';
templateSrv.init([
{
id: 'reg',
name: 'reg',
current: {
value: `eastus`,
},
},
]);
replace = (target?: string) => {
if (target?.includes('$reg')) {
return 'eastus';
}
return target || '';
};
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
const query = createMockQuery({
azureMonitor: {
region,
@@ -185,29 +189,20 @@ describe('AzureMonitorDatasource', () => {
});
it('should migrate legacy properties before interpolation', () => {
templateSrv.init([
{
id: 'resourcegroup',
name: 'resourcegroup',
current: {
value: `test-rg`,
},
},
{
id: 'resourcename',
name: 'resourcename',
current: {
value: `test-resource`,
},
},
{
id: 'metric',
name: 'metric',
current: {
value: `test-ns`,
},
},
]);
replace = (target?: string) => {
if (target?.includes('$resourcegroup')) {
return 'test-rg';
}
if (target?.includes('$resourcename')) {
return 'test-resource';
}
if (target?.includes('$metric')) {
return 'test-ns';
}
return target || '';
};
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
const query = createMockQuery({
azureMonitor: {
metricDefinition: '$metric',
@@ -434,15 +429,23 @@ describe('AzureMonitorDatasource', () => {
});
it('should replace a template variable for the metric name', () => {
templateSrv.init([
{
id: 'metric',
name: 'metric',
current: {
value: 'UsedCapacity',
},
},
]);
replace = (target?: string) => {
if (target?.includes('$metric')) {
return 'UsedCapacity';
}
return target || '';
};
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp';
const expected =
basePath +
'/providers/microsoft.insights/components/resource1' +
'/providers/microsoft.insights/metricdefinitions?api-version=2018-01-01';
expect(path).toBe(expected);
return Promise.resolve(response);
});
return ctx.ds.azureMonitorDatasource
.getMetricMetadata({
resourceUri:
@@ -460,7 +463,8 @@ describe('AzureMonitorDatasource', () => {
describe('When performing interpolateVariablesInQueries for azure_monitor_metrics', () => {
beforeEach(() => {
templateSrv.init([]);
replace = (target?: string) => target || '';
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
});
it('should return a query unchanged if no template variables are provided', () => {
@@ -481,7 +485,35 @@ describe('AzureMonitorDatasource', () => {
'dimensionFilters[0].filters[0]',
];
const templateVariables = createTemplateVariables(templateableProps);
templateSrv.init(Array.from(templateVariables.values()).map((item) => item.templateVariable));
replace = (target?: string) => {
if (target === '$resources0resourceGroup') {
return 'resources0resourceGroup-template-variable';
}
if (target === '$resources0resourceName') {
return 'resources0resourceName-template-variable';
}
if (target === '$metricNamespace') {
return 'metricNamespace-template-variable';
}
if (target === '$timeGrain') {
return 'timeGrain-template-variable';
}
if (target === '$aggregation') {
return 'aggregation-template-variable';
}
if (target === '$top') {
return 'top-template-variable';
}
if (target === '$dimensionFilters0dimension') {
return 'dimensionFilters0dimension-template-variable';
}
if (target === '$dimensionFilters0filters0') {
return 'dimensionFilters0filters0-template-variable';
}
return target || '';
};
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
const query = createMockQuery();
const azureMonitorQuery = {};
for (const [path, templateVariable] of templateVariables.entries()) {
@@ -712,12 +744,14 @@ describe('AzureMonitorDatasource', () => {
});
it('should return multiple resources from a template variable', () => {
const tsrv = new TemplateSrv();
tsrv.replace = jest
.fn()
.mockImplementation((value: string) => (value === `$${multiVariable.id}` ? 'foo,bar' : value ?? ''));
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
ds.azureMonitorDatasource.templateSrv = tsrv;
replace = (target?: string) => {
if (target?.includes('$reg')) {
return 'eastus';
}
return target === `$${multiVariable.id}` ? 'foo,bar' : target ?? '';
};
const ds = new AzureMonitorDatasource(ctx.instanceSettings);
//ds.azureMonitorDatasource.templateSrv = tsrv;
ds.azureMonitorDatasource.getResource = jest
.fn()
.mockImplementationOnce((path: string) => {
@@ -1074,36 +1108,6 @@ describe('AzureMonitorDatasource', () => {
});
});
describe('When performing targetContainsTemplate', () => {
it('should return false when no variable is being used', () => {
const query = createMockQuery();
query.queryType = AzureQueryType.AzureMonitor;
expect(ctx.ds.targetContainsTemplate(query)).toEqual(false);
});
it('should return true when subscriptions field is using a variable', () => {
const query = createMockQuery();
const templateSrv = new TemplateSrv();
templateSrv.init([subscriptionsVariable]);
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.AzureMonitor;
query.subscription = `$${subscriptionsVariable.name}`;
expect(ds.targetContainsTemplate(query)).toEqual(true);
});
it('should return false when a variable is used in a different part of the query', () => {
const query = createMockQuery();
const templateSrv = new TemplateSrv();
templateSrv.init([singleVariable]);
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.AzureMonitor;
query.azureLogAnalytics = { resources: [`$${singleVariable.name}`] };
expect(ds.targetContainsTemplate(query)).toEqual(false);
});
});
it('should return an empty array for a Metric that does not have dimensions', () => {
return ctx.ds.azureMonitorDatasource
.getMetricMetadata({
@@ -3,7 +3,6 @@ import { find, startsWith } from 'lodash';
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { getAuthType, getAzureCloud, getAzurePortalUrl } from '../credentials';
import TimegrainConverter from '../time_grain_converter';
@@ -49,14 +48,13 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
azurePortalUrl: string;
declare resourceGroup: string;
declare resourceName: string;
timeSrv: TimeSrv;
templateSrv: TemplateSrv;
constructor(private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
constructor(
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
private readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings);
this.timeSrv = getTimeSrv();
this.templateSrv = getTemplateSrv();
this.defaultSubscriptionId = instanceSettings.jsonData.subscriptionId;
const cloud = getAzureCloud(instanceSettings);
@@ -92,14 +90,12 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
throw new Error('Query is not a valid Azure Monitor Metrics query');
}
const templateSrv = getTemplateSrv();
// These properties need to be replaced pre-migration to ensure values are correctly interpolated
if (preMigrationQuery.resourceUri) {
preMigrationQuery.resourceUri = templateSrv.replace(preMigrationQuery.resourceUri, scopedVars);
preMigrationQuery.resourceUri = this.templateSrv.replace(preMigrationQuery.resourceUri, scopedVars);
}
if (preMigrationQuery.metricDefinition) {
preMigrationQuery.metricDefinition = templateSrv.replace(preMigrationQuery.metricDefinition, scopedVars);
preMigrationQuery.metricDefinition = this.templateSrv.replace(preMigrationQuery.metricDefinition, scopedVars);
}
// fix for timeGrainUnit which is a deprecated/removed field name
@@ -117,20 +113,23 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
throw new Error('Query is not a valid Azure Monitor Metrics query');
}
const subscriptionId = templateSrv.replace(migratedTarget.subscription || this.defaultSubscriptionId, scopedVars);
const subscriptionId = this.templateSrv.replace(
migratedTarget.subscription || this.defaultSubscriptionId,
scopedVars
);
const resources = migratedQuery.resources?.map((r) => this.replaceTemplateVariables(r, scopedVars)).flat();
const metricNamespace = templateSrv.replace(migratedQuery.metricNamespace, scopedVars);
const customNamespace = templateSrv.replace(migratedQuery.customNamespace, scopedVars);
const timeGrain = templateSrv.replace((migratedQuery.timeGrain || '').toString(), scopedVars);
const aggregation = templateSrv.replace(migratedQuery.aggregation, scopedVars);
const top = templateSrv.replace(migratedQuery.top || '', scopedVars);
const metricNamespace = this.templateSrv.replace(migratedQuery.metricNamespace, scopedVars);
const customNamespace = this.templateSrv.replace(migratedQuery.customNamespace, scopedVars);
const timeGrain = this.templateSrv.replace((migratedQuery.timeGrain || '').toString(), scopedVars);
const aggregation = this.templateSrv.replace(migratedQuery.aggregation, scopedVars);
const top = this.templateSrv.replace(migratedQuery.top || '', scopedVars);
const dimensionFilters = (migratedQuery.dimensionFilters ?? [])
.filter((f) => f.dimension && f.dimension !== 'None')
.map((f) => {
const filters = f.filters?.map((filter) => templateSrv.replace(filter ?? '', scopedVars));
const filters = f.filters?.map((filter) => this.templateSrv.replace(filter ?? '', scopedVars));
return {
dimension: templateSrv.replace(f.dimension, scopedVars),
dimension: this.templateSrv.replace(f.dimension, scopedVars),
operator: f.operator || 'eq',
filters: filters || [],
};
@@ -143,8 +142,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
customNamespace,
timeGrain,
allowedTimeGrainsMs: migratedQuery.allowedTimeGrainsMs,
metricName: templateSrv.replace(migratedQuery.metricName, scopedVars),
region: templateSrv.replace(migratedQuery.region, scopedVars),
metricName: this.templateSrv.replace(migratedQuery.metricName, scopedVars),
region: this.templateSrv.replace(migratedQuery.region, scopedVars),
aggregation: aggregation,
dimensionFilters,
top: top || '10',
@@ -1,63 +1,55 @@
import { set, get } from 'lodash';
import { backendSrv } from 'app/core/services/backend_srv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CustomVariableModel } from '@grafana/data';
import { Context, createContext } from '../__mocks__/datasource';
import createMockQuery from '../__mocks__/query';
import { createTemplateVariables } from '../__mocks__/utils';
import { multiVariable, singleVariable, subscriptionsVariable } from '../__mocks__/variables';
import AzureMonitorDatasource from '../datasource';
import { AzureQueryType } from '../types';
const templateSrv = new TemplateSrv({
getVariables: () => [subscriptionsVariable, singleVariable, multiVariable],
getVariableWithName: jest.fn(),
getFilteredVariables: jest.fn(),
let getTempVars = () => [] as CustomVariableModel[];
let replace = () => '';
jest.mock('@grafana/runtime', () => {
return {
__esModule: true,
...jest.requireActual('@grafana/runtime'),
getTemplateSrv: () => ({
replace: replace,
getVariables: getTempVars,
updateTimeRange: jest.fn(),
containsTemplate: jest.fn(),
}),
};
});
jest.mock('app/core/services/backend_srv');
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getBackendSrv: () => backendSrv,
getTemplateSrv: () => templateSrv,
}));
describe('AzureResourceGraphDatasource', () => {
const datasourceRequestMock = jest.spyOn(backendSrv, 'datasourceRequest');
beforeEach(() => {
jest.clearAllMocks();
datasourceRequestMock.mockImplementation(jest.fn());
});
let ctx: Context;
beforeEach(() => {
ctx = createContext({
instanceSettings: {
url: 'http://azureresourcegraphapi',
jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f', cloudName: 'azuremonitor' },
},
});
});
describe('When performing interpolateVariablesInQueries for azure_resource_graph', () => {
beforeEach(() => {
templateSrv.init([]);
ctx = createContext({
instanceSettings: {
url: 'http://azureresourcegraphapi',
jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f', cloudName: 'azuremonitor' },
},
});
getTempVars = () => [] as CustomVariableModel[];
replace = (target?: string) => target || '';
});
it('should return a query unchanged if no template variables are provided', () => {
const query = createMockQuery();
query.queryType = AzureQueryType.AzureResourceGraph;
const templatedQuery = ctx.datasource.azureResourceGraphDatasource.interpolateVariablesInQueries([query], {});
expect(templatedQuery[0]).toEqual(query);
const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {});
expect(templatedQuery).toEqual([query]);
});
it('should return a query with any template variables replaced', () => {
const templateableProps = ['query'];
const templateVariables = createTemplateVariables(templateableProps);
templateSrv.init(Array.from(templateVariables.values()).map((item) => item.templateVariable));
replace = () => 'query-template-variable';
const query = createMockQuery();
const azureResourceGraph = {};
for (const [path, templateVariable] of templateVariables.entries()) {
@@ -69,7 +61,7 @@ describe('AzureResourceGraphDatasource', () => {
...query.azureResourceGraph,
...azureResourceGraph,
};
const templatedQuery = ctx.datasource.azureResourceGraphDatasource.interpolateVariablesInQueries([query], {});
const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {});
expect(templatedQuery[0]).toHaveProperty('datasource');
for (const [path, templateVariable] of templateVariables.entries()) {
expect(get(templatedQuery[0].azureResourceGraph, path)).toEqual(
@@ -81,7 +73,8 @@ describe('AzureResourceGraphDatasource', () => {
describe('When applying template variables', () => {
beforeEach(() => {
templateSrv.init([subscriptionsVariable, singleVariable, multiVariable]);
getTempVars = () => [] as CustomVariableModel[];
replace = (target?: string) => target || '';
});
it('should expand single value template variable', () => {
@@ -92,6 +85,10 @@ describe('AzureResourceGraphDatasource', () => {
resultFormat: '',
},
});
getTempVars = () =>
Array.from([subscriptionsVariable, singleVariable, multiVariable].values()).map((item) => item);
replace = (target?: string | undefined) =>
target === 'Resources | $var1' ? 'Resources | var1-foo' : target || '';
expect(ctx.datasource.azureResourceGraphDatasource.applyTemplateVariables(target, {})).toEqual(
expect.objectContaining({
...target,
@@ -110,6 +107,14 @@ describe('AzureResourceGraphDatasource', () => {
resultFormat: '',
},
});
getTempVars = () =>
Array.from([subscriptionsVariable, singleVariable, multiVariable].values()).map((item) => item);
replace = (target?: string | undefined) => {
if (target === 'resources | where $__contains(name, $var3)') {
return "resources | where $__contains(name, 'var3-foo','var3-baz')";
}
return target || '';
};
expect(ctx.datasource.azureResourceGraphDatasource.applyTemplateVariables(target, {})).toEqual(
expect.objectContaining({
...target,
@@ -128,14 +133,16 @@ describe('AzureResourceGraphDatasource', () => {
const target = createMockQuery({
subscriptions: ['$subs'],
azureResourceGraph: {
query: 'resources | where $__contains(name, $var3)',
query: 'resources | where $__contains(name)',
resultFormat: '',
},
});
getTempVars = () => Array.from([subscriptionsVariable, singleVariable, multiVariable].values()).map((item) => item);
replace = (target?: string | undefined) => (target === '$subs' ? 'sub-foo,sub-baz' : target || '');
expect(ctx.datasource.azureResourceGraphDatasource.applyTemplateVariables(target, {})).toEqual(
expect.objectContaining({
azureResourceGraph: {
query: `resources | where $__contains(name, 'var3-foo','var3-baz')`,
query: `resources | where $__contains(name)`,
resultFormat: 'table',
},
queryType: 'Azure Resource Graph',
@@ -143,47 +150,4 @@ describe('AzureResourceGraphDatasource', () => {
})
);
});
describe('When performing targetContainsTemplate', () => {
it('should return false when no variable is being used', () => {
const query = createMockQuery();
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.AzureResourceGraph;
expect(ds.targetContainsTemplate(query)).toEqual(false);
});
it('should return true when resource field is using a variable', () => {
const query = createMockQuery();
const templateSrv = new TemplateSrv();
templateSrv.init([singleVariable]);
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.AzureResourceGraph;
query.azureResourceGraph = { query: `$${singleVariable.name}` };
expect(ds.targetContainsTemplate(query)).toEqual(true);
});
it('should return true when resource field is using a variable in the subscriptions field', () => {
const query = createMockQuery();
const templateSrv = new TemplateSrv();
templateSrv.init([multiVariable]);
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.AzureResourceGraph;
query.subscriptions = [multiVariable.name];
query.azureResourceGraph = { query: `$${multiVariable.name}` };
expect(ds.targetContainsTemplate(query)).toEqual(true);
});
it('should return false when a variable is used in a different part of the query', () => {
const query = createMockQuery();
const templateSrv = new TemplateSrv();
templateSrv.init([singleVariable]);
const ds = new AzureMonitorDatasource(ctx.instanceSettings, templateSrv);
query.queryType = AzureQueryType.AzureResourceGraph;
query.azureMonitor = { metricName: `$${singleVariable.name}` };
expect(ds.targetContainsTemplate(query)).toEqual(false);
});
});
});
@@ -16,15 +16,14 @@ export default class AzureResourceGraphDatasource extends DataSourceWithBackend<
}
applyTemplateVariables(target: AzureMonitorQuery, scopedVars: ScopedVars): AzureMonitorQuery {
const ts = getTemplateSrv();
const item = target.azureResourceGraph;
if (!item) {
return target;
}
const templateSrv = getTemplateSrv();
const variableNames = templateSrv.getVariables().map((v) => `$${v.name}`);
const variableNames = ts.getVariables().map((v) => `$${v.name}`);
const subscriptionVar = _.find(target.subscriptions, (sub) => _.includes(variableNames, sub));
const interpolatedSubscriptions = templateSrv
const interpolatedSubscriptions = ts
.replace(subscriptionVar, scopedVars, (v: string[] | string) => v)
.split(',')
.filter((v) => v.length > 0);
@@ -32,7 +31,7 @@ export default class AzureResourceGraphDatasource extends DataSourceWithBackend<
...interpolatedSubscriptions,
..._.filter(target.subscriptions, (sub) => !_.includes(variableNames, sub)),
];
const query = templateSrv.replace(item.query, scopedVars, interpolateVariable);
const query = ts.replace(item.query, scopedVars, interpolateVariable);
return {
...target,
@@ -34,6 +34,7 @@ const QueryEditor = ({
onChange,
onRunQuery: baseOnRunQuery,
data,
range,
}: AzureMonitorQueryEditorProps) => {
const [errorMessage, setError] = useLastError();
const onRunQuery = useMemo(() => debounce(baseOnRunQuery, 500), [baseOnRunQuery]);
@@ -66,6 +67,7 @@ const QueryEditor = ({
onChange={onQueryChange}
variableOptionGroup={variableOptionGroup}
setError={setError}
range={range}
/>
{errorMessage && (
@@ -94,6 +96,7 @@ const EditorForQueryType = ({
variableOptionGroup,
onChange,
setError,
range,
}: EditorForQueryTypeProps) => {
switch (query.queryType) {
case AzureQueryType.AzureMonitor:
@@ -141,6 +144,7 @@ const EditorForQueryType = ({
onChange={onChange}
variableOptionGroup={variableOptionGroup}
setError={setError}
range={range}
/>
);
@@ -2,7 +2,7 @@ import { cx } from '@emotion/css';
import React, { RefCallback, SyntheticEvent, useState } from 'react';
import { lastValueFrom } from 'rxjs';
import { CoreApp, DataFrame, SelectableValue, TimeRange } from '@grafana/data';
import { CoreApp, DataFrame, getDefaultTimeRange, SelectableValue, TimeRange } from '@grafana/data';
import { AccessoryButton } from '@grafana/experimental';
import {
HorizontalGroup,
@@ -24,10 +24,10 @@ export interface FilterProps {
datasource: Datasource;
propertyMap: Map<string, SelectableValue[]>;
setPropertyMap: React.Dispatch<React.SetStateAction<Map<string, Array<SelectableValue<string>>>>>;
timeRange: TimeRange;
queryTraceTypes: string[];
properties: string[];
variableOptionGroup: VariableOptionGroup;
range?: TimeRange;
}
const onFieldChange = <Key extends keyof AzureTracesFilter>(
@@ -50,11 +50,11 @@ const onFieldChange = <Key extends keyof AzureTracesFilter>(
const getTraceProperties = async (
query: AzureMonitorQuery,
datasource: Datasource,
timeRange: TimeRange,
traceTypes: string[],
propertyMap: Map<string, SelectableValue[]>,
setPropertyMap: React.Dispatch<React.SetStateAction<Map<string, Array<SelectableValue<string>>>>>,
filter?: Partial<AzureTracesFilter>
filter?: Partial<AzureTracesFilter>,
range?: TimeRange
): Promise<SelectableValue[]> => {
const { azureTraces } = query;
if (!azureTraces) {
@@ -97,7 +97,7 @@ const getTraceProperties = async (
queryType: AzureQueryType.LogAnalytics,
},
],
range: timeRange,
range: range || getDefaultTimeRange(),
})
);
if (results.data.length > 0) {
@@ -191,13 +191,13 @@ const Filter = (
datasource,
propertyMap,
setPropertyMap,
timeRange,
queryTraceTypes,
properties,
item,
onChange,
onDelete,
variableOptionGroup,
range,
} = props;
const [loading, setLoading] = useState(false);
const [values, setValues] = useState<Array<SelectableValue<string> | VariableOptionGroup>>(
@@ -215,11 +215,11 @@ const Filter = (
const promise = await getTraceProperties(
query,
datasource,
timeRange,
queryTraceTypes,
propertyMap,
setPropertyMap,
item
item,
range
);
setValues(addValueToOptions(promise, variableOptionGroup));
setLoading(false);
@@ -16,6 +16,19 @@ import { AzureMonitorQuery } from '../../types';
import Filters from './Filters';
import { setFilters } from './setQueryValue';
jest.mock('@grafana/runtime', () => {
return {
__esModule: true,
...jest.requireActual('@grafana/runtime'),
getTemplateSrv: () => ({
replace: jest.fn(),
getVariables: jest.fn(),
updateTimeRange: jest.fn(),
containsTemplate: jest.fn(),
}),
};
});
const variableOptionGroup = {
label: 'Template variables',
options: [],
@@ -1,7 +1,7 @@
import { uniq } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import { SelectableValue, TimeRange } from '@grafana/data';
import { SelectableValue } from '@grafana/data';
import { EditorList } from '@grafana/experimental';
import { Field } from '@grafana/ui';
@@ -11,7 +11,7 @@ import { makeRenderItem } from './Filter';
import { tablesSchema } from './consts';
import { setFilters } from './setQueryValue';
const Filters = ({ query, datasource, onQueryChange, variableOptionGroup }: AzureQueryEditorFieldProps) => {
const Filters = ({ query, datasource, onQueryChange, variableOptionGroup, range }: AzureQueryEditorFieldProps) => {
const { azureTraces } = query;
const queryTraceTypes = azureTraces?.traceTypes ? azureTraces.traceTypes : Object.keys(tablesSchema);
@@ -34,23 +34,9 @@ const Filters = ({ query, datasource, onQueryChange, variableOptionGroup }: Azur
const queryFilters = useMemo(() => query.azureTraces?.filters ?? [], [query.azureTraces?.filters]);
const [filters, updateFilters] = useState(queryFilters);
const timeSrv = datasource.azureLogAnalyticsDatasource.timeSrv;
const [timeRange, setTimeRange] = useState(timeSrv.timeRange());
const useTime = (time: TimeRange) => {
if (
timeRange !== null &&
(timeRange.raw.from.toString() !== time.raw.from.toString() ||
timeRange.raw.to.toString() !== time.raw.to.toString())
) {
setTimeRange({ ...time });
}
};
useTime(timeSrv.timeRange());
useEffect(() => {
setPropertyMap(new Map<string, Array<SelectableValue<string>>>());
}, [timeRange, query.azureTraces?.resources, query.azureTraces?.traceTypes, query.azureTraces?.operationId]);
}, [query.azureTraces?.resources, query.azureTraces?.traceTypes, query.azureTraces?.operationId]);
const changedFunc = (changed: Array<Partial<AzureTracesFilter>>) => {
let updateQuery = false;
@@ -82,10 +68,10 @@ const Filters = ({ query, datasource, onQueryChange, variableOptionGroup }: Azur
datasource,
propertyMap,
setPropertyMap,
timeRange,
queryTraceTypes,
properties,
variableOptionGroup,
range,
})}
/>
</Field>
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import { usePrevious } from 'react-use';
import { TimeRange } from '@grafana/data';
import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental';
import { Input } from '@grafana/ui';
@@ -25,6 +26,7 @@ interface TracesQueryEditorProps {
onChange: (newQuery: AzureMonitorQuery) => void;
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
range?: TimeRange;
}
const TracesQueryEditor = ({
@@ -34,6 +36,7 @@ const TracesQueryEditor = ({
variableOptionGroup,
onChange,
setError,
range,
}: TracesQueryEditorProps) => {
const disableRow = (row: ResourceRow, selectedRows: ResourceRowGroup) => {
if (selectedRows.length === 0) {
@@ -102,6 +105,7 @@ const TracesQueryEditor = ({
<AdvancedResourcePicker resources={resources as string[]} onChange={onChange} />
)}
selectionNotice={() => 'You may only choose items of the same resource type.'}
range={range}
/>
</EditorFieldGroup>
</EditorRow>
@@ -113,6 +117,7 @@ const TracesQueryEditor = ({
query={query}
setError={setError}
variableOptionGroup={variableOptionGroup}
range={range}
/>
<Field label="Operation ID">
<Input
@@ -133,6 +138,7 @@ const TracesQueryEditor = ({
query={query}
setError={setError}
variableOptionGroup={variableOptionGroup}
range={range}
/>
</EditorFieldGroup>
</EditorRow>
@@ -152,6 +158,7 @@ const TracesQueryEditor = ({
defaultValue={ResultFormat.Table}
setFormatAs={setFormatAs}
resultFormat={query.azureTraces?.resultFormat}
range={range}
/>
</EditorFieldGroup>
</EditorRow>
@@ -1,6 +1,28 @@
import { createMockInstanceSetttings } from './__mocks__/instanceSettings';
import createMockQuery from './__mocks__/query';
import { singleVariable } from './__mocks__/variables';
import Datasource from './datasource';
import { AzureQueryType } from './types';
jest.mock('@grafana/runtime', () => {
return {
__esModule: true,
...jest.requireActual('@grafana/runtime'),
getTemplateSrv: () => ({
replace: (target?: string) => {
if (target === '$resourceGroup') {
return 'the-resource-group';
}
return target || '';
},
getVariables: jest.fn(),
updateTimeRange: jest.fn(),
containsTemplate: (target?: string) => {
return (target || '').includes('$');
},
}),
};
});
describe('Azure Monitor Datasource', () => {
describe('interpolateVariablesInQueries()', () => {
@@ -49,4 +71,45 @@ describe('Azure Monitor Datasource', () => {
delete query.queryType;
expect(ds.filterQuery(query)).toBe(false);
});
describe('When performing targetContainsTemplate', () => {
it('should return false when no variable is being used', () => {
const query = {
...createMockQuery(),
queryType: AzureQueryType.AzureResourceGraph,
};
const ds = new Datasource(createMockInstanceSetttings());
expect(ds.targetContainsTemplate(query)).toEqual(false);
});
it('should return true when resource field is using a variable', () => {
const query = {
...createMockQuery(),
queryType: AzureQueryType.AzureResourceGraph,
azureResourceGraph: { query: '$temp-var' },
};
const ds = new Datasource(createMockInstanceSetttings());
expect(ds.targetContainsTemplate(query)).toEqual(true);
});
it('should return true when resource field is using a variable in the subscriptions field', () => {
const query = {
...createMockQuery(),
queryType: AzureQueryType.AzureResourceGraph,
subscriptions: ['$temp-var'],
};
const ds = new Datasource(createMockInstanceSetttings());
expect(ds.targetContainsTemplate(query)).toEqual(true);
});
it('should return false when a variable is used in a different part of the query', () => {
const query = {
...createMockQuery(),
queryType: AzureQueryType.AzureResourceGraph,
azureMonitor: { metricName: `$${singleVariable.name}` },
};
const ds = new Datasource(createMockInstanceSetttings());
expect(ds.targetContainsTemplate(query)).toEqual(false);
});
});
});
@@ -10,8 +10,7 @@ import {
LoadingState,
ScopedVars,
} from '@grafana/data';
import { DataSourceWithBackend } from '@grafana/runtime';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource';
@@ -6,6 +6,7 @@ import {
DataSourceSettings,
PanelData,
SelectableValue,
TimeRange,
} from '@grafana/data';
import Datasource from '../datasource';
@@ -142,6 +143,7 @@ export interface AzureQueryEditorFieldProps {
subscriptionId?: string;
variableOptionGroup: VariableOptionGroup;
schema?: EngineSchema;
range?: TimeRange;
onQueryChange: (newQuery: AzureMonitorQuery) => void;
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
@@ -1,7 +1,6 @@
import { map } from 'lodash';
import { SelectableValue } from '@grafana/data';
import { VariableWithMultiSupport } from 'app/features/variables/types';
import { SelectableValue, VariableWithMultiSupport } from '@grafana/data';
import { AzureMonitorOption, VariableOptionGroup } from '../types';
@@ -7,7 +7,7 @@ import {
MetricFindValue,
toDataFrame,
} from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import VariableEditor from './components/VariableEditor/VariableEditor';
import DataSource from './datasource';
@@ -17,12 +17,12 @@ import { GrafanaTemplateVariableQuery } from './types/templateVariables';
import messageFromError from './utils/messageFromError';
export class VariableSupport extends CustomVariableSupport<DataSource, AzureMonitorQuery> {
templateSrv = getTemplateSrv();
constructor(private readonly datasource: DataSource) {
constructor(
private readonly datasource: DataSource,
private readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super();
this.datasource = datasource;
this.templateSrv = getTemplateSrv();
}
editor = VariableEditor;
@@ -169,6 +169,6 @@ export class VariableSupport extends CustomVariableSupport<DataSource, AzureMoni
}
replaceVariable(metric: string) {
return getTemplateSrv().replace((metric || '').trim());
return this.templateSrv.replace((metric || '').trim());
}
}