mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure Monitor: Add Resource Picker to Template Variable Query Editor (#40841)
Azure Monitor: Add Resource Picker to Template Variable Query Editor - Should fix bug related broken template variables that relied on a deprecated default workspace.
This commit is contained in:
parent
7b7c193551
commit
3da4e1cdcd
@ -5,7 +5,7 @@ type DeepPartial<T> = {
|
|||||||
[P in keyof T]?: DeepPartial<T[P]>;
|
[P in keyof T]?: DeepPartial<T[P]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function createMockDatasource() {
|
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
|
// 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
|
// having it be any or casted immediately to Datasource
|
||||||
const _mockDatasource: DeepPartial<Datasource> = {
|
const _mockDatasource: DeepPartial<Datasource> = {
|
||||||
@ -16,6 +16,7 @@ export default function createMockDatasource() {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
getSubscriptions: jest.fn().mockResolvedValueOnce([]),
|
getSubscriptions: jest.fn().mockResolvedValueOnce([]),
|
||||||
|
defaultSubscriptionId: 'subscriptionId',
|
||||||
},
|
},
|
||||||
|
|
||||||
getAzureLogAnalyticsWorkspaces: jest.fn().mockResolvedValueOnce([]),
|
getAzureLogAnalyticsWorkspaces: jest.fn().mockResolvedValueOnce([]),
|
||||||
@ -34,12 +35,14 @@ export default function createMockDatasource() {
|
|||||||
|
|
||||||
azureLogAnalyticsDatasource: {
|
azureLogAnalyticsDatasource: {
|
||||||
getKustoSchema: () => Promise.resolve(),
|
getKustoSchema: () => Promise.resolve(),
|
||||||
|
getDeprecatedDefaultWorkSpace: () => 'defaultWorkspaceId',
|
||||||
},
|
},
|
||||||
resourcePickerData: {
|
resourcePickerData: {
|
||||||
getResourcePickerData: () => ({}),
|
getResourcePickerData: () => ({}),
|
||||||
getResourcesForResourceGroup: () => ({}),
|
getResourcesForResourceGroup: () => ({}),
|
||||||
getResourceURIFromWorkspace: () => '',
|
getResourceURIFromWorkspace: () => '',
|
||||||
},
|
},
|
||||||
|
...overrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockDatasource = _mockDatasource as Datasource;
|
const mockDatasource = _mockDatasource as Datasource;
|
||||||
|
@ -394,73 +394,6 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When performing metricFindQuery', () => {
|
|
||||||
describe('with a metric names query', () => {
|
|
||||||
const response = {
|
|
||||||
metrics: {
|
|
||||||
'exceptions/server': {},
|
|
||||||
'requests/count': {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.getResource = jest.fn().mockImplementation((path) => {
|
|
||||||
expect(path).toContain('/metrics/metadata');
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
|
||||||
return ctx.ds.metricFindQueryInternal('appInsightsMetricNames()').then((results: any) => {
|
|
||||||
expect(results.length).toBe(2);
|
|
||||||
expect(results[0].text).toBe('exceptions/server');
|
|
||||||
expect(results[0].value).toBe('exceptions/server');
|
|
||||||
expect(results[1].text).toBe('requests/count');
|
|
||||||
expect(results[1].value).toBe('requests/count');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with metadata group by query', () => {
|
|
||||||
const response = {
|
|
||||||
metrics: {
|
|
||||||
'exceptions/server': {
|
|
||||||
supportedAggregations: ['sum'],
|
|
||||||
supportedGroupBy: {
|
|
||||||
all: ['client/os', 'client/city', 'client/browser'],
|
|
||||||
},
|
|
||||||
defaultAggregation: 'sum',
|
|
||||||
},
|
|
||||||
'requests/count': {
|
|
||||||
supportedAggregations: ['avg', 'sum', 'total'],
|
|
||||||
supportedGroupBy: {
|
|
||||||
all: ['client/os', 'client/city', 'client/browser'],
|
|
||||||
},
|
|
||||||
defaultAggregation: 'avg',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.getResource = jest.fn().mockImplementation((path) => {
|
|
||||||
expect(path).toContain('/metrics/metadata');
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of group bys', () => {
|
|
||||||
return ctx.ds.metricFindQueryInternal('appInsightsGroupBys(requests/count)').then((results: any) => {
|
|
||||||
expect(results[0].text).toContain('client/os');
|
|
||||||
expect(results[0].value).toContain('client/os');
|
|
||||||
expect(results[1].text).toContain('client/city');
|
|
||||||
expect(results[1].value).toContain('client/city');
|
|
||||||
expect(results[2].text).toContain('client/browser');
|
|
||||||
expect(results[2].value).toContain('client/browser');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('When getting Metric Names', () => {
|
describe('When getting Metric Names', () => {
|
||||||
const response = {
|
const response = {
|
||||||
metrics: {
|
metrics: {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DataQueryRequest, DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
|
import { DataQueryRequest, DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||||
import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
|
|
||||||
@ -106,27 +106,6 @@ export default class AppInsightsDatasource extends DataSourceWithBackend<AzureMo
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is named differently than DataSourceApi.metricFindQuery
|
|
||||||
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
|
|
||||||
* And some of the azure internal data sources return null in this function, which the
|
|
||||||
* external interface does not support
|
|
||||||
*/
|
|
||||||
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> | null {
|
|
||||||
const appInsightsMetricNameQuery = query.match(/^AppInsightsMetricNames\(\)/i);
|
|
||||||
if (appInsightsMetricNameQuery) {
|
|
||||||
return this.getMetricNames();
|
|
||||||
}
|
|
||||||
|
|
||||||
const appInsightsGroupByQuery = query.match(/^AppInsightsGroupBys\(([^\)]+?)(,\s?([^,]+?))?\)/i);
|
|
||||||
if (appInsightsGroupByQuery) {
|
|
||||||
const metricName = appInsightsGroupByQuery[1];
|
|
||||||
return this.getGroupBys(getTemplateSrv().replace(metricName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
testDatasource(): Promise<DatasourceValidationResult> {
|
testDatasource(): Promise<DatasourceValidationResult> {
|
||||||
const path = `${this.resourcePath}/metrics/metadata`;
|
const path = `${this.resourcePath}/metrics/metadata`;
|
||||||
return this.getResource(path)
|
return this.getResource(path)
|
||||||
|
@ -2,7 +2,7 @@ import AzureMonitorDatasource from '../datasource';
|
|||||||
import AzureLogAnalyticsDatasource from './azure_log_analytics_datasource';
|
import AzureLogAnalyticsDatasource from './azure_log_analytics_datasource';
|
||||||
import FakeSchemaData from './__mocks__/schema';
|
import FakeSchemaData from './__mocks__/schema';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { AzureLogsVariable, AzureMonitorQuery, DatasourceValidationResult } from '../types';
|
import { AzureMonitorQuery, DatasourceValidationResult } from '../types';
|
||||||
import { toUtc } from '@grafana/data';
|
import { toUtc } from '@grafana/data';
|
||||||
|
|
||||||
const templateSrv = new TemplateSrv();
|
const templateSrv = new TemplateSrv();
|
||||||
@ -111,157 +111,6 @@ describe('AzureLogAnalyticsDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When performing metricFindQuery', () => {
|
|
||||||
let queryResults: AzureLogsVariable[];
|
|
||||||
|
|
||||||
const workspacesResponse = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'workspace1',
|
|
||||||
id: makeResourceURI('workspace-1'),
|
|
||||||
properties: {
|
|
||||||
customerId: 'eeee4fde-1aaa-4d60-9974-eeee562ffaa1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'workspace2',
|
|
||||||
id: makeResourceURI('workspace-2'),
|
|
||||||
properties: {
|
|
||||||
customerId: 'eeee4fde-1aaa-4d60-9974-eeee562ffaa2',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('and is the workspaces() macro', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
ctx.ds.azureLogAnalyticsDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
expect(path).toContain('xxx');
|
|
||||||
return Promise.resolve(workspacesResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
queryResults = await ctx.ds.metricFindQuery('workspaces()');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of workspaces', () => {
|
|
||||||
expect(queryResults).toEqual([
|
|
||||||
{ text: 'workspace1', value: makeResourceURI('workspace-1') },
|
|
||||||
{ text: 'workspace2', value: makeResourceURI('workspace-2') },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and is the workspaces() macro with the subscription parameter', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
ctx.ds.azureLogAnalyticsDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
expect(path).toContain('11112222-eeee-4949-9b2d-9106972f9123');
|
|
||||||
return Promise.resolve(workspacesResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
queryResults = await ctx.ds.metricFindQuery('workspaces(11112222-eeee-4949-9b2d-9106972f9123)');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of workspaces', () => {
|
|
||||||
expect(queryResults).toEqual([
|
|
||||||
{ text: 'workspace1', value: makeResourceURI('workspace-1') },
|
|
||||||
{ text: 'workspace2', value: makeResourceURI('workspace-2') },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and is the workspaces() macro with the subscription parameter quoted', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
ctx.ds.azureLogAnalyticsDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
expect(path).toContain('11112222-eeee-4949-9b2d-9106972f9123');
|
|
||||||
return Promise.resolve(workspacesResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
queryResults = await ctx.ds.metricFindQuery('workspaces("11112222-eeee-4949-9b2d-9106972f9123")');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of workspaces', () => {
|
|
||||||
expect(queryResults).toEqual([
|
|
||||||
{ text: 'workspace1', value: makeResourceURI('workspace-1') },
|
|
||||||
{ text: 'workspace2', value: makeResourceURI('workspace-2') },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and is a custom query', () => {
|
|
||||||
const tableResponseWithOneColumn = {
|
|
||||||
tables: [
|
|
||||||
{
|
|
||||||
name: 'PrimaryResult',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'Category',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rows: [['Administrative'], ['Policy']],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const workspaceResponse = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'aworkspace',
|
|
||||||
id: makeResourceURI('a-workspace'),
|
|
||||||
properties: {
|
|
||||||
source: 'Azure',
|
|
||||||
customerId: 'abc1b44e-3e57-4410-b027-6cc0ae6dee67',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
ctx.ds.azureLogAnalyticsDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
if (path.indexOf('OperationalInsights/workspaces?api-version=') > -1) {
|
|
||||||
return Promise.resolve(workspaceResponse);
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(tableResponseWithOneColumn);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of categories in the correct format', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
|
|
||||||
|
|
||||||
expect(results.length).toBe(2);
|
|
||||||
expect(results[0].text).toBe('Administrative');
|
|
||||||
expect(results[0].value).toBe('Administrative');
|
|
||||||
expect(results[1].text).toBe('Policy');
|
|
||||||
expect(results[1].value).toBe('Policy');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and contain options', () => {
|
|
||||||
const queryResponse = {
|
|
||||||
tables: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should substitute macros', async () => {
|
|
||||||
ctx.ds.azureLogAnalyticsDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const params = new URLSearchParams(path.split('?')[1]);
|
|
||||||
const query = params.get('query');
|
|
||||||
expect(query).toEqual(
|
|
||||||
'Perf| where TimeGenerated >= datetime(2021-01-01T05:01:00.000Z) and TimeGenerated <= datetime(2021-01-01T05:02:00.000Z)'
|
|
||||||
);
|
|
||||||
return Promise.resolve(queryResponse);
|
|
||||||
});
|
|
||||||
ctx.ds.azureLogAnalyticsDatasource.firstWorkspace = 'foo';
|
|
||||||
await ctx.ds.metricFindQuery('Perf| where TimeGenerated >= $__timeFrom() and TimeGenerated <= $__timeTo()', {
|
|
||||||
range: {
|
|
||||||
from: new Date('2021-01-01 00:01:00'),
|
|
||||||
to: new Date('2021-01-01 00:02:00'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('When performing annotationQuery', () => {
|
describe('When performing annotationQuery', () => {
|
||||||
const tableResponse = {
|
const tableResponse = {
|
||||||
tables: [
|
tables: [
|
||||||
|
@ -8,13 +8,7 @@ import {
|
|||||||
AzureQueryType,
|
AzureQueryType,
|
||||||
DatasourceValidationResult,
|
DatasourceValidationResult,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import { DataQueryRequest, DataQueryResponse, ScopedVars, DataSourceInstanceSettings } from '@grafana/data';
|
||||||
DataQueryRequest,
|
|
||||||
DataQueryResponse,
|
|
||||||
ScopedVars,
|
|
||||||
DataSourceInstanceSettings,
|
|
||||||
MetricFindValue,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
||||||
import { Observable, from } from 'rxjs';
|
import { Observable, from } from 'rxjs';
|
||||||
import { mergeMap } from 'rxjs/operators';
|
import { mergeMap } from 'rxjs/operators';
|
||||||
@ -218,60 +212,12 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* This is named differently than DataSourceApi.metricFindQuery
|
In 7.5.x it used to be possible to set a default workspace id in the config on the auth page.
|
||||||
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
|
This has been deprecated, however is still used by a few legacy template queries.
|
||||||
* And some of the azure internal data sources return null in this function, which the
|
|
||||||
* external interface does not support
|
|
||||||
*/
|
*/
|
||||||
metricFindQueryInternal(query: string, optionalOptions?: unknown): Promise<MetricFindValue[]> {
|
getDeprecatedDefaultWorkSpace() {
|
||||||
// workspaces() - Get workspaces in the default subscription
|
return this.instanceSettings.jsonData.logAnalyticsDefaultWorkspace;
|
||||||
const workspacesQuery = query.match(/^workspaces\(\)/i);
|
|
||||||
if (workspacesQuery) {
|
|
||||||
if (this.defaultSubscriptionId) {
|
|
||||||
return this.getWorkspaces(this.defaultSubscriptionId);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'No subscription ID. Specify a default subscription ID in the data source config to use workspaces() without a subscription ID'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// workspaces("abc-def-etc") - Get workspaces a specified subscription
|
|
||||||
const workspacesQueryWithSub = query.match(/^workspaces\(["']?([^\)]+?)["']?\)/i);
|
|
||||||
if (workspacesQueryWithSub) {
|
|
||||||
return this.getWorkspaces((workspacesQueryWithSub[1] || '').trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the query as KQL to the default or first workspace
|
|
||||||
return this.getFirstWorkspace().then((resourceURI) => {
|
|
||||||
if (!resourceURI) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const queries = this.buildQuery(query, optionalOptions, resourceURI);
|
|
||||||
const promises = this.doQueries(queries);
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
|
||||||
.then((results) => {
|
|
||||||
return new ResponseParser(results).parseToVariables();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (
|
|
||||||
err.error &&
|
|
||||||
err.error.data &&
|
|
||||||
err.error.data.error &&
|
|
||||||
err.error.data.error.innererror &&
|
|
||||||
err.error.data.error.innererror.innererror
|
|
||||||
) {
|
|
||||||
throw { message: err.error.data.error.innererror.innererror.message };
|
|
||||||
} else if (err.error && err.error.data && err.error.data.error) {
|
|
||||||
throw { message: err.error.data.error.message };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}) as Promise<MetricFindValue[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildQuery(query: string, options: any, workspace: string): AdhocQuery[] {
|
private buildQuery(query: string, options: any, workspace: string): AdhocQuery[] {
|
||||||
|
@ -73,358 +73,6 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When performing metricFindQuery', () => {
|
|
||||||
describe('with a subscriptions query', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{ displayName: 'Primary', subscriptionId: 'sub1' },
|
|
||||||
{ displayName: 'Secondary', subscriptionId: 'sub2' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.instanceSettings.jsonData.azureAuthType = 'msi';
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockResolvedValue(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of subscriptions', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('subscriptions()');
|
|
||||||
expect(results.length).toBe(2);
|
|
||||||
expect(results[0].text).toBe('Primary');
|
|
||||||
expect(results[0].value).toBe('sub1');
|
|
||||||
expect(results[1].text).toBe('Secondary');
|
|
||||||
expect(results[1].value).toBe('sub2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a resource groups query', () => {
|
|
||||||
const response = {
|
|
||||||
value: [{ name: 'grp1' }, { name: 'grp2' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockResolvedValue(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of resource groups', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('ResourceGroups()');
|
|
||||||
expect(results.length).toBe(2);
|
|
||||||
expect(results[0].text).toBe('grp1');
|
|
||||||
expect(results[0].value).toBe('grp1');
|
|
||||||
expect(results[1].text).toBe('grp2');
|
|
||||||
expect(results[1].value).toBe('grp2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a resource groups query that specifies a subscription id', () => {
|
|
||||||
const response = {
|
|
||||||
value: [{ name: 'grp1' }, { name: 'grp2' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
expect(path).toContain('11112222-eeee-4949-9b2d-9106972f9123');
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of resource groups', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('ResourceGroups(11112222-eeee-4949-9b2d-9106972f9123)');
|
|
||||||
expect(results.length).toBe(2);
|
|
||||||
expect(results[0].text).toBe('grp1');
|
|
||||||
expect(results[0].value).toBe('grp1');
|
|
||||||
expect(results[1].text).toBe('grp2');
|
|
||||||
expect(results[1].value).toBe('grp2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with namespaces query', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'test',
|
|
||||||
type: 'Microsoft.Network/networkInterfaces',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
|
||||||
expect(path).toBe(basePath + '/nodesapp/resources?api-version=2018-01-01');
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of namespaces', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('Namespaces(nodesapp)');
|
|
||||||
expect(results.length).toEqual(1);
|
|
||||||
expect(results[0].text).toEqual('Network interface');
|
|
||||||
expect(results[0].value).toEqual('Microsoft.Network/networkInterfaces');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with namespaces query that specifies a subscription id', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'test',
|
|
||||||
type: 'Microsoft.Network/networkInterfaces',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
|
||||||
expect(path).toBe(basePath + '/nodesapp/resources?api-version=2018-01-01');
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of namespaces', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('namespaces(11112222-eeee-4949-9b2d-9106972f9123, nodesapp)');
|
|
||||||
expect(results.length).toEqual(1);
|
|
||||||
expect(results[0].text).toEqual('Network interface');
|
|
||||||
expect(results[0].value).toEqual('Microsoft.Network/networkInterfaces');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with resource names query', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'Failure Anomalies - nodeapp',
|
|
||||||
type: 'microsoft.insights/alertrules',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'nodeapp',
|
|
||||||
type: 'microsoft.insights/components',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
|
||||||
expect(path).toBe(basePath + '/nodeapp/resources?api-version=2018-01-01');
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of resource names', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('resourceNames(nodeapp, microsoft.insights/components )');
|
|
||||||
expect(results.length).toEqual(1);
|
|
||||||
expect(results[0].text).toEqual('nodeapp');
|
|
||||||
expect(results[0].value).toEqual('nodeapp');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with resource names query and that specifies a subscription id', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'Failure Anomalies - nodeapp',
|
|
||||||
type: 'microsoft.insights/alertrules',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'nodeapp',
|
|
||||||
type: 'microsoft.insights/components',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
|
||||||
expect(path).toBe(basePath + '/nodeapp/resources?api-version=2018-01-01');
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of resource names', () => {
|
|
||||||
return ctx.ds
|
|
||||||
.metricFindQuery(
|
|
||||||
'resourceNames(11112222-eeee-4949-9b2d-9106972f9123, nodeapp, microsoft.insights/components )'
|
|
||||||
)
|
|
||||||
.then((results: any) => {
|
|
||||||
expect(results.length).toEqual(1);
|
|
||||||
expect(results[0].text).toEqual('nodeapp');
|
|
||||||
expect(results[0].value).toEqual('nodeapp');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with metric names query', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
value: 'Percentage CPU',
|
|
||||||
localizedValue: 'Percentage CPU',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
value: 'UsedCapacity',
|
|
||||||
localizedValue: 'Used capacity',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
|
||||||
expect(path).toBe(
|
|
||||||
basePath +
|
|
||||||
'/nodeapp/providers/microsoft.insights/components/rn/providers/microsoft.insights/' +
|
|
||||||
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
|
||||||
);
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of metric names', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery(
|
|
||||||
'Metricnames(nodeapp, microsoft.insights/components, rn, default)'
|
|
||||||
);
|
|
||||||
expect(results.length).toEqual(2);
|
|
||||||
expect(results[0].text).toEqual('Percentage CPU');
|
|
||||||
expect(results[0].value).toEqual('Percentage CPU');
|
|
||||||
|
|
||||||
expect(results[1].text).toEqual('Used capacity');
|
|
||||||
expect(results[1].value).toEqual('UsedCapacity');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with metric names query and specifies a subscription id', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
value: 'Percentage CPU',
|
|
||||||
localizedValue: 'Percentage CPU',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
value: 'UsedCapacity',
|
|
||||||
localizedValue: 'Used capacity',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
|
||||||
expect(path).toBe(
|
|
||||||
basePath +
|
|
||||||
'/nodeapp/providers/microsoft.insights/components/rn/providers/microsoft.insights/' +
|
|
||||||
'metricdefinitions?api-version=2018-01-01&metricnamespace=default'
|
|
||||||
);
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of metric names', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery(
|
|
||||||
'Metricnames(11112222-eeee-4949-9b2d-9106972f9123, nodeapp, microsoft.insights/components, rn, default)'
|
|
||||||
);
|
|
||||||
expect(results.length).toEqual(2);
|
|
||||||
expect(results[0].text).toEqual('Percentage CPU');
|
|
||||||
expect(results[0].value).toEqual('Percentage CPU');
|
|
||||||
|
|
||||||
expect(results[1].text).toEqual('Used capacity');
|
|
||||||
expect(results[1].value).toEqual('UsedCapacity');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with metric namespace query', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'Microsoft.Compute-virtualMachines',
|
|
||||||
properties: {
|
|
||||||
metricNamespaceName: 'Microsoft.Compute/virtualMachines',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Telegraf-mem',
|
|
||||||
properties: {
|
|
||||||
metricNamespaceName: 'Telegraf/mem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups';
|
|
||||||
expect(path).toBe(
|
|
||||||
basePath +
|
|
||||||
'/nodeapp/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview'
|
|
||||||
);
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of metric names', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery('Metricnamespace(nodeapp, Microsoft.Compute/virtualMachines, rn)');
|
|
||||||
expect(results.length).toEqual(2);
|
|
||||||
expect(results[0].text).toEqual('Microsoft.Compute-virtualMachines');
|
|
||||||
expect(results[0].value).toEqual('Microsoft.Compute/virtualMachines');
|
|
||||||
|
|
||||||
expect(results[1].text).toEqual('Telegraf-mem');
|
|
||||||
expect(results[1].value).toEqual('Telegraf/mem');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with metric namespace query and specifies a subscription id', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
name: 'Microsoft.Compute-virtualMachines',
|
|
||||||
properties: {
|
|
||||||
metricNamespaceName: 'Microsoft.Compute/virtualMachines',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Telegraf-mem',
|
|
||||||
properties: {
|
|
||||||
metricNamespaceName: 'Telegraf/mem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
|
|
||||||
const basePath = 'azuremonitor/subscriptions/11112222-eeee-4949-9b2d-9106972f9123/resourceGroups';
|
|
||||||
expect(path).toBe(
|
|
||||||
basePath +
|
|
||||||
'/nodeapp/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview'
|
|
||||||
);
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of metric namespaces', async () => {
|
|
||||||
const results = await ctx.ds.metricFindQuery(
|
|
||||||
'Metricnamespace(11112222-eeee-4949-9b2d-9106972f9123, nodeapp, Microsoft.Compute/virtualMachines, rn)'
|
|
||||||
);
|
|
||||||
expect(results.length).toEqual(2);
|
|
||||||
expect(results[0].text).toEqual('Microsoft.Compute-virtualMachines');
|
|
||||||
expect(results[0].value).toEqual('Microsoft.Compute/virtualMachines');
|
|
||||||
|
|
||||||
expect(results[1].text).toEqual('Telegraf-mem');
|
|
||||||
expect(results[1].value).toEqual('Telegraf/mem');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('When performing getSubscriptions', () => {
|
describe('When performing getSubscriptions', () => {
|
||||||
const response = {
|
const response = {
|
||||||
value: [
|
value: [
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
AzureQueryType,
|
AzureQueryType,
|
||||||
DatasourceValidationResult,
|
DatasourceValidationResult,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
|
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||||
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
|
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
@ -122,114 +122,6 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is named differently than DataSourceApi.metricFindQuery
|
|
||||||
* because it's not exposed to Grafana like the main AzureMonitorDataSource.
|
|
||||||
* And some of the azure internal data sources return null in this function, which the
|
|
||||||
* external interface does not support
|
|
||||||
*/
|
|
||||||
metricFindQueryInternal(query: string): Promise<MetricFindValue[]> | null {
|
|
||||||
const subscriptionsQuery = query.match(/^Subscriptions\(\)/i);
|
|
||||||
if (subscriptionsQuery) {
|
|
||||||
return this.getSubscriptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceGroupsQuery = query.match(/^ResourceGroups\(\)/i);
|
|
||||||
if (resourceGroupsQuery && this.defaultSubscriptionId) {
|
|
||||||
return this.getResourceGroups(this.defaultSubscriptionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceGroupsQueryWithSub = query.match(/^ResourceGroups\(([^\)]+?)(,\s?([^,]+?))?\)/i);
|
|
||||||
if (resourceGroupsQueryWithSub) {
|
|
||||||
return this.getResourceGroups(this.toVariable(resourceGroupsQueryWithSub[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const metricDefinitionsQuery = query.match(/^Namespaces\(([^\)]+?)(,\s?([^,]+?))?\)/i);
|
|
||||||
if (metricDefinitionsQuery && this.defaultSubscriptionId) {
|
|
||||||
if (!metricDefinitionsQuery[3]) {
|
|
||||||
return this.getMetricDefinitions(this.defaultSubscriptionId, this.toVariable(metricDefinitionsQuery[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const metricDefinitionsQueryWithSub = query.match(/^Namespaces\(([^,]+?),\s?([^,]+?)\)/i);
|
|
||||||
if (metricDefinitionsQueryWithSub) {
|
|
||||||
return this.getMetricDefinitions(
|
|
||||||
this.toVariable(metricDefinitionsQueryWithSub[1]),
|
|
||||||
this.toVariable(metricDefinitionsQueryWithSub[2])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceNamesQuery = query.match(/^ResourceNames\(([^,]+?),\s?([^,]+?)\)/i);
|
|
||||||
if (resourceNamesQuery && this.defaultSubscriptionId) {
|
|
||||||
const resourceGroup = this.toVariable(resourceNamesQuery[1]);
|
|
||||||
const metricDefinition = this.toVariable(resourceNamesQuery[2]);
|
|
||||||
return this.getResourceNames(this.defaultSubscriptionId, resourceGroup, metricDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceNamesQueryWithSub = query.match(/^ResourceNames\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/i);
|
|
||||||
if (resourceNamesQueryWithSub) {
|
|
||||||
const subscription = this.toVariable(resourceNamesQueryWithSub[1]);
|
|
||||||
const resourceGroup = this.toVariable(resourceNamesQueryWithSub[2]);
|
|
||||||
const metricDefinition = this.toVariable(resourceNamesQueryWithSub[3]);
|
|
||||||
return this.getResourceNames(subscription, resourceGroup, metricDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
const metricNamespaceQuery = query.match(/^MetricNamespace\(([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i);
|
|
||||||
if (metricNamespaceQuery && this.defaultSubscriptionId) {
|
|
||||||
const resourceGroup = this.toVariable(metricNamespaceQuery[1]);
|
|
||||||
const metricDefinition = this.toVariable(metricNamespaceQuery[2]);
|
|
||||||
const resourceName = this.toVariable(metricNamespaceQuery[3]);
|
|
||||||
return this.getMetricNamespaces(this.defaultSubscriptionId, resourceGroup, metricDefinition, resourceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const metricNamespaceQueryWithSub = query.match(
|
|
||||||
/^metricnamespace\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i
|
|
||||||
);
|
|
||||||
if (metricNamespaceQueryWithSub) {
|
|
||||||
const subscription = this.toVariable(metricNamespaceQueryWithSub[1]);
|
|
||||||
const resourceGroup = this.toVariable(metricNamespaceQueryWithSub[2]);
|
|
||||||
const metricDefinition = this.toVariable(metricNamespaceQueryWithSub[3]);
|
|
||||||
const resourceName = this.toVariable(metricNamespaceQueryWithSub[4]);
|
|
||||||
return this.getMetricNamespaces(subscription, resourceGroup, metricDefinition, resourceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const metricNamesQuery = query.match(/^MetricNames\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i);
|
|
||||||
if (metricNamesQuery && this.defaultSubscriptionId) {
|
|
||||||
if (metricNamesQuery[3].indexOf(',') === -1) {
|
|
||||||
const resourceGroup = this.toVariable(metricNamesQuery[1]);
|
|
||||||
const metricDefinition = this.toVariable(metricNamesQuery[2]);
|
|
||||||
const resourceName = this.toVariable(metricNamesQuery[3]);
|
|
||||||
const metricNamespace = this.toVariable(metricNamesQuery[4]);
|
|
||||||
return this.getMetricNames(
|
|
||||||
this.defaultSubscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
metricDefinition,
|
|
||||||
resourceName,
|
|
||||||
metricNamespace
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const metricNamesQueryWithSub = query.match(
|
|
||||||
/^MetricNames\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?(.+?)\)/i
|
|
||||||
);
|
|
||||||
|
|
||||||
if (metricNamesQueryWithSub) {
|
|
||||||
const subscription = this.toVariable(metricNamesQueryWithSub[1]);
|
|
||||||
const resourceGroup = this.toVariable(metricNamesQueryWithSub[2]);
|
|
||||||
const metricDefinition = this.toVariable(metricNamesQueryWithSub[3]);
|
|
||||||
const resourceName = this.toVariable(metricNamesQueryWithSub[4]);
|
|
||||||
const metricNamespace = this.toVariable(metricNamesQueryWithSub[5]);
|
|
||||||
return this.getMetricNames(subscription, resourceGroup, metricDefinition, resourceName, metricNamespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
toVariable(metric: string) {
|
|
||||||
return getTemplateSrv().replace((metric || '').trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSubscriptions(): Promise<Array<{ text: string; value: string }>> {
|
async getSubscriptions(): Promise<Array<{ text: string; value: string }>> {
|
||||||
if (!this.isConfigured()) {
|
if (!this.isConfigured()) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -14,6 +14,7 @@ interface LogsQueryEditorProps {
|
|||||||
onChange: (newQuery: AzureMonitorQuery) => void;
|
onChange: (newQuery: AzureMonitorQuery) => void;
|
||||||
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
||||||
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
|
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
|
||||||
|
hideFormatAs?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
||||||
@ -23,6 +24,7 @@ const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
|||||||
variableOptionGroup,
|
variableOptionGroup,
|
||||||
onChange,
|
onChange,
|
||||||
setError,
|
setError,
|
||||||
|
hideFormatAs,
|
||||||
}) => {
|
}) => {
|
||||||
const migrationError = useMigrations(datasource, query, onChange);
|
const migrationError = useMigrations(datasource, query, onChange);
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
|||||||
setError={setError}
|
setError={setError}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{!hideFormatAs && (
|
||||||
<FormatAsField
|
<FormatAsField
|
||||||
query={query}
|
query={query}
|
||||||
datasource={datasource}
|
datasource={datasource}
|
||||||
@ -56,6 +59,7 @@ const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
|||||||
onQueryChange={onChange}
|
onQueryChange={onChange}
|
||||||
setError={setError}
|
setError={setError}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{migrationError && <Alert title={migrationError.title}>{migrationError.message}</Alert>}
|
{migrationError && <Alert title={migrationError.title}>{migrationError.message}</Alert>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,7 +27,6 @@ function parseResourceDetails(resourceURI: string) {
|
|||||||
const ResourceField: React.FC<AzureQueryEditorFieldProps> = ({ query, datasource, onQueryChange }) => {
|
const ResourceField: React.FC<AzureQueryEditorFieldProps> = ({ query, datasource, onQueryChange }) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const { resource } = query.azureLogAnalytics ?? {};
|
const { resource } = query.azureLogAnalytics ?? {};
|
||||||
|
|
||||||
const [pickerIsOpen, setPickerIsOpen] = useState(false);
|
const [pickerIsOpen, setPickerIsOpen] = useState(false);
|
||||||
|
|
||||||
const handleOpenPicker = useCallback(() => {
|
const handleOpenPicker = useCallback(() => {
|
||||||
@ -61,7 +60,7 @@ const ResourceField: React.FC<AzureQueryEditorFieldProps> = ({ query, datasource
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Field label="Resource">
|
<Field label="Resource">
|
||||||
<Button variant="secondary" onClick={handleOpenPicker}>
|
<Button variant="secondary" onClick={handleOpenPicker} type="button">
|
||||||
<ResourceLabel resource={resource} datasource={datasource} />
|
<ResourceLabel resource={resource} datasource={datasource} />
|
||||||
</Button>
|
</Button>
|
||||||
</Field>
|
</Field>
|
||||||
|
@ -0,0 +1,163 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import VariableEditor from './VariableEditor';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import createMockDatasource from '../../__mocks__/datasource';
|
||||||
|
import { AzureMonitorQuery, AzureQueryType } from '../../types';
|
||||||
|
import { select } from 'react-select-event';
|
||||||
|
import * as ui from '@grafana/ui';
|
||||||
|
// eslint-disable-next-line lodash/import-scope
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
// Have to mock CodeEditor because it doesnt seem to work in tests???
|
||||||
|
jest.mock('@grafana/ui', () => ({
|
||||||
|
...jest.requireActual<typeof ui>('@grafana/ui'),
|
||||||
|
CodeEditor: function CodeEditor({ value, onSave }: { value: string; onSave: (newQuery: string) => void }) {
|
||||||
|
return <input data-testid="mockeditor" value={value} onChange={(event) => onSave(event.target.value)} />;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('lodash', () => ({
|
||||||
|
...jest.requireActual<typeof _>('lodash'),
|
||||||
|
debounce: jest.fn((fn: unknown) => fn),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('VariableEditor:', () => {
|
||||||
|
it('can select a query type', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
query: {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
azureLogAnalytics: {
|
||||||
|
query: 'test query',
|
||||||
|
},
|
||||||
|
subscription: 'id',
|
||||||
|
},
|
||||||
|
onChange,
|
||||||
|
datasource: createMockDatasource(),
|
||||||
|
};
|
||||||
|
render(<VariableEditor {...props} />);
|
||||||
|
await waitFor(() => screen.getByLabelText('select query type'));
|
||||||
|
expect(screen.getByLabelText('select query type')).toBeInTheDocument();
|
||||||
|
screen.getByLabelText('select query type').click();
|
||||||
|
await select(screen.getByLabelText('select query type'), 'Grafana Query Function', {
|
||||||
|
container: document.body,
|
||||||
|
});
|
||||||
|
expect(screen.queryByText('Logs')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Grafana Query Function')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
describe('log queries:', () => {
|
||||||
|
it('should render', async () => {
|
||||||
|
const props = {
|
||||||
|
query: {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
azureLogAnalytics: {
|
||||||
|
query: 'test query',
|
||||||
|
},
|
||||||
|
subscription: 'id',
|
||||||
|
},
|
||||||
|
onChange: () => {},
|
||||||
|
datasource: createMockDatasource(),
|
||||||
|
};
|
||||||
|
render(<VariableEditor {...props} />);
|
||||||
|
await waitFor(() => screen.queryByTestId('mockeditor'));
|
||||||
|
expect(screen.queryByText('Resource')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('mockeditor')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with legacy query strings', async () => {
|
||||||
|
const props = {
|
||||||
|
query: 'test query',
|
||||||
|
onChange: () => {},
|
||||||
|
datasource: createMockDatasource(),
|
||||||
|
};
|
||||||
|
render(<VariableEditor {...props} />);
|
||||||
|
await waitFor(() => screen.queryByTestId('mockeditor'));
|
||||||
|
expect(screen.queryByText('Resource')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('mockeditor')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('should call on change if the query changes', async () => {
|
||||||
|
const props = {
|
||||||
|
query: {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
azureLogAnalytics: {
|
||||||
|
query: 'test query',
|
||||||
|
},
|
||||||
|
subscription: 'id',
|
||||||
|
},
|
||||||
|
onChange: jest.fn(),
|
||||||
|
datasource: createMockDatasource(),
|
||||||
|
};
|
||||||
|
render(<VariableEditor {...props} />);
|
||||||
|
await waitFor(() => screen.queryByTestId('mockeditor'));
|
||||||
|
expect(screen.queryByTestId('mockeditor')).toBeInTheDocument();
|
||||||
|
await userEvent.type(screen.getByTestId('mockeditor'), '{backspace}');
|
||||||
|
expect(props.onChange).toHaveBeenCalledWith({
|
||||||
|
azureLogAnalytics: {
|
||||||
|
query: 'test quer',
|
||||||
|
},
|
||||||
|
queryType: 'Azure Log Analytics',
|
||||||
|
refId: 'A',
|
||||||
|
subscription: 'id',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('grafana template variable fn queries:', () => {
|
||||||
|
it('should render', async () => {
|
||||||
|
const props = {
|
||||||
|
query: {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
rawQuery: 'test query',
|
||||||
|
kind: 'SubscriptionsQuery',
|
||||||
|
},
|
||||||
|
subscription: 'id',
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
onChange: () => {},
|
||||||
|
datasource: createMockDatasource(),
|
||||||
|
};
|
||||||
|
render(<VariableEditor {...props} />);
|
||||||
|
await waitFor(() => screen.queryByText('Grafana template variable function'));
|
||||||
|
expect(screen.queryByText('Grafana template variable function')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByDisplayValue('test query')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call on change if the query changes', async () => {
|
||||||
|
const props = {
|
||||||
|
query: {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
rawQuery: 'Su',
|
||||||
|
kind: 'UnknownQuery',
|
||||||
|
},
|
||||||
|
subscription: 'subscriptionId',
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
onChange: jest.fn(),
|
||||||
|
datasource: createMockDatasource(),
|
||||||
|
debounceTime: 1,
|
||||||
|
};
|
||||||
|
render(<VariableEditor {...props} />);
|
||||||
|
await waitFor(() => screen.queryByText('Grafana template variable function'));
|
||||||
|
userEvent.type(screen.getByDisplayValue('Su'), 'bscriptions()');
|
||||||
|
expect(screen.getByDisplayValue('Subscriptions()')).toBeInTheDocument();
|
||||||
|
screen.getByDisplayValue('Subscriptions()').blur();
|
||||||
|
await waitFor(() => screen.queryByText('None'));
|
||||||
|
expect(props.onChange).toHaveBeenCalledWith({
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
rawQuery: 'Subscriptions()',
|
||||||
|
kind: 'SubscriptionsQuery',
|
||||||
|
},
|
||||||
|
subscription: 'subscriptionId',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,150 @@
|
|||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { Alert, InlineField, Input, Select } from '@grafana/ui';
|
||||||
|
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { AzureMonitorQuery, AzureQueryType } from '../../types';
|
||||||
|
import LogsQueryEditor from '../LogsQueryEditor';
|
||||||
|
import DataSource from '../../datasource';
|
||||||
|
import useLastError from '../../utils/useLastError';
|
||||||
|
import { Space } from '../Space';
|
||||||
|
import { migrateStringQueriesToObjectQueries } from '../../grafanaTemplateVariableFns';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
|
const AZURE_QUERY_VARIABLE_TYPE_OPTIONS = [
|
||||||
|
{ label: 'Grafana Query Function', value: AzureQueryType.GrafanaTemplateVariableFn },
|
||||||
|
{ label: 'Logs', value: AzureQueryType.LogAnalytics },
|
||||||
|
];
|
||||||
|
|
||||||
|
const GrafanaTemplateVariableFnInput = ({
|
||||||
|
query,
|
||||||
|
updateQuery,
|
||||||
|
datasource,
|
||||||
|
}: {
|
||||||
|
query: AzureMonitorQuery;
|
||||||
|
updateQuery: (val: AzureMonitorQuery) => void;
|
||||||
|
datasource: DataSource;
|
||||||
|
}) => {
|
||||||
|
const [inputVal, setInputVal] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
setInputVal(query.grafanaTemplateVariableFn?.rawQuery || '');
|
||||||
|
}, [query.grafanaTemplateVariableFn?.rawQuery]);
|
||||||
|
|
||||||
|
const onRunQuery = useCallback(
|
||||||
|
(newQuery: string) => {
|
||||||
|
migrateStringQueriesToObjectQueries(newQuery, { datasource }).then((updatedQuery) => {
|
||||||
|
if (updatedQuery.queryType === AzureQueryType.GrafanaTemplateVariableFn) {
|
||||||
|
updateQuery(updatedQuery);
|
||||||
|
} else {
|
||||||
|
updateQuery({
|
||||||
|
...query,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'UnknownQuery',
|
||||||
|
rawQuery: newQuery,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[datasource, query, updateQuery]
|
||||||
|
);
|
||||||
|
const debouncedRunQuery = useMemo(() => debounce(onRunQuery, 500), [onRunQuery]);
|
||||||
|
|
||||||
|
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInputVal(event.target.value);
|
||||||
|
debouncedRunQuery(event.target.value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<InlineField label="Grafana template variable function">
|
||||||
|
<Input
|
||||||
|
placeholder={'type a grafana template variable function, ex: Subscriptions()'}
|
||||||
|
value={inputVal}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
query: AzureMonitorQuery | string;
|
||||||
|
onChange: (query: AzureMonitorQuery) => void;
|
||||||
|
datasource: DataSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VariableEditor = (props: Props) => {
|
||||||
|
const defaultQuery: AzureMonitorQuery = {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
};
|
||||||
|
const [query, setQuery] = useState(defaultQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
migrateStringQueriesToObjectQueries(props.query, { datasource: props.datasource }).then((migratedQuery) => {
|
||||||
|
setQuery(migratedQuery);
|
||||||
|
});
|
||||||
|
}, [props.query, props.datasource]);
|
||||||
|
|
||||||
|
const onQueryTypeChange = (selectableValue: SelectableValue) => {
|
||||||
|
if (selectableValue.value) {
|
||||||
|
setQuery({
|
||||||
|
...query,
|
||||||
|
queryType: selectableValue.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onLogsQueryChange = (queryChange: AzureMonitorQuery) => {
|
||||||
|
setQuery(queryChange);
|
||||||
|
|
||||||
|
// only hit backend if there's something to query (prevents error when selecting the resource before pinging a query)
|
||||||
|
if (queryChange.azureLogAnalytics?.query) {
|
||||||
|
props.onChange(queryChange);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [errorMessage, setError] = useLastError();
|
||||||
|
|
||||||
|
const variableOptionGroup = {
|
||||||
|
label: 'Template Variables',
|
||||||
|
// TODO: figure out a way to filter out the current variable from the variables list
|
||||||
|
// options: props.datasource.getVariables().map((v) => ({ label: v, value: v })),
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InlineField label="Select query type">
|
||||||
|
<Select
|
||||||
|
aria-label="select query type"
|
||||||
|
onChange={onQueryTypeChange}
|
||||||
|
options={AZURE_QUERY_VARIABLE_TYPE_OPTIONS}
|
||||||
|
width={25}
|
||||||
|
value={query.queryType}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
{query.queryType === AzureQueryType.LogAnalytics && (
|
||||||
|
<>
|
||||||
|
<LogsQueryEditor
|
||||||
|
subscriptionId={query.subscription}
|
||||||
|
query={query}
|
||||||
|
datasource={props.datasource}
|
||||||
|
onChange={onLogsQueryChange}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
setError={setError}
|
||||||
|
hideFormatAs={true}
|
||||||
|
/>
|
||||||
|
{errorMessage && (
|
||||||
|
<>
|
||||||
|
<Space v={2} />
|
||||||
|
<Alert severity="error" title="An error occurred while requesting metadata from Azure Monitor">
|
||||||
|
{errorMessage}
|
||||||
|
</Alert>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{query.queryType === AzureQueryType.GrafanaTemplateVariableFn && (
|
||||||
|
<GrafanaTemplateVariableFnInput query={query} updateQuery={props.onChange} datasource={props.datasource} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VariableEditor;
|
@ -21,7 +21,7 @@ import { map } from 'rxjs/operators';
|
|||||||
import AzureResourceGraphDatasource from './azure_resource_graph/azure_resource_graph_datasource';
|
import AzureResourceGraphDatasource from './azure_resource_graph/azure_resource_graph_datasource';
|
||||||
import { getAzureCloud } from './credentials';
|
import { getAzureCloud } from './credentials';
|
||||||
import migrateAnnotation from './utils/migrateAnnotation';
|
import migrateAnnotation from './utils/migrateAnnotation';
|
||||||
|
import { VariableSupport } from './variables';
|
||||||
export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
|
export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||||
annotations = {
|
annotations = {
|
||||||
prepareAnnotation: migrateAnnotation,
|
prepareAnnotation: migrateAnnotation,
|
||||||
@ -71,6 +71,8 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
this.pseudoDatasource[AzureQueryType.ApplicationInsights] = this.appInsightsDatasource;
|
this.pseudoDatasource[AzureQueryType.ApplicationInsights] = this.appInsightsDatasource;
|
||||||
this.pseudoDatasource[AzureQueryType.InsightsAnalytics] = this.insightsAnalyticsDatasource;
|
this.pseudoDatasource[AzureQueryType.InsightsAnalytics] = this.insightsAnalyticsDatasource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.variables = new VariableSupport(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
query(options: DataQueryRequest<AzureMonitorQuery>): Observable<DataQueryResponse> {
|
query(options: DataQueryRequest<AzureMonitorQuery>): Observable<DataQueryResponse> {
|
||||||
@ -133,29 +135,6 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
return this.azureLogAnalyticsDatasource.annotationQuery(options);
|
return this.azureLogAnalyticsDatasource.annotationQuery(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async metricFindQuery(query: string, optionalOptions?: unknown) {
|
|
||||||
if (!query) {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const aiResult = this.appInsightsDatasource?.metricFindQueryInternal(query);
|
|
||||||
if (aiResult) {
|
|
||||||
return aiResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
const amResult = this.azureMonitorDatasource.metricFindQueryInternal(query);
|
|
||||||
if (amResult) {
|
|
||||||
return amResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
const alaResult = this.azureLogAnalyticsDatasource.metricFindQueryInternal(query, optionalOptions);
|
|
||||||
if (alaResult) {
|
|
||||||
return alaResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async testDatasource(): Promise<DatasourceValidationResult> {
|
async testDatasource(): Promise<DatasourceValidationResult> {
|
||||||
const promises: Array<Promise<DatasourceValidationResult>> = [];
|
const promises: Array<Promise<DatasourceValidationResult>> = [];
|
||||||
|
|
||||||
@ -307,6 +286,9 @@ function hasQueryForType(query: AzureMonitorQuery): boolean {
|
|||||||
case AzureQueryType.AzureResourceGraph:
|
case AzureQueryType.AzureResourceGraph:
|
||||||
return !!query.azureResourceGraph;
|
return !!query.azureResourceGraph;
|
||||||
|
|
||||||
|
case AzureQueryType.GrafanaTemplateVariableFn:
|
||||||
|
return !!query.grafanaTemplateVariableFn;
|
||||||
|
|
||||||
case AzureQueryType.ApplicationInsights:
|
case AzureQueryType.ApplicationInsights:
|
||||||
return !!query.appInsights;
|
return !!query.appInsights;
|
||||||
|
|
||||||
|
@ -0,0 +1,268 @@
|
|||||||
|
import { AzureMonitorQuery, AzureQueryType } from './types';
|
||||||
|
import DataSource from './datasource';
|
||||||
|
import {
|
||||||
|
AppInsightsGroupByQuery,
|
||||||
|
AppInsightsMetricNameQuery,
|
||||||
|
GrafanaTemplateVariableQuery,
|
||||||
|
MetricDefinitionsQuery,
|
||||||
|
MetricNamespaceQuery,
|
||||||
|
MetricNamesQuery,
|
||||||
|
ResourceGroupsQuery,
|
||||||
|
ResourceNamesQuery,
|
||||||
|
SubscriptionsQuery,
|
||||||
|
WorkspacesQuery,
|
||||||
|
} from './types/templateVariables';
|
||||||
|
import { isGUIDish } from './components/ResourcePicker/utils';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Grafana Template Variable Functions
|
||||||
|
ex: Subscriptions()
|
||||||
|
|
||||||
|
These are helper functions we have created and exposed to users to make the writing of template variables easier.
|
||||||
|
Due to legacy reasons, we still need to parse strings to determine if a query is a Grafana Template Variable Function
|
||||||
|
or if it's a KQL-type query
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const grafanaTemplateVariableFnMatches = (query: string) => {
|
||||||
|
return {
|
||||||
|
subscriptions: query.match(/^Subscriptions\(\)/i),
|
||||||
|
resourceGroups: query.match(/^ResourceGroups\(\)/i),
|
||||||
|
resourceGroupsWithSub: query.match(/^ResourceGroups\(([^\)]+?)(,\s?([^,]+?))?\)/i),
|
||||||
|
metricDefinitions: query.match(/^Namespaces\(([^\)]+?)(,\s?([^,]+?))?\)/i),
|
||||||
|
metricDefinitionsWithSub: query.match(/^Namespaces\(([^,]+?),\s?([^,]+?)\)/i),
|
||||||
|
resourceNames: query.match(/^ResourceNames\(([^,]+?),\s?([^,]+?)\)/i),
|
||||||
|
resourceNamesWithSub: query.match(/^ResourceNames\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/i),
|
||||||
|
metricNamespace: query.match(/^MetricNamespace\(([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i),
|
||||||
|
metricNamespaceWithSub: query.match(/^metricnamespace\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i),
|
||||||
|
metricNames: query.match(/^MetricNames\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/i),
|
||||||
|
metricNamesWithSub: query.match(/^MetricNames\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?(.+?)\)/i),
|
||||||
|
appInsightsMetricNameQuery: query.match(/^AppInsightsMetricNames\(\)/i),
|
||||||
|
appInsightsGroupByQuery: query.match(/^AppInsightsGroupBys\(([^\)]+?)(,\s?([^,]+?))?\)/i),
|
||||||
|
workspacesQuery: query.match(/^workspaces\(\)/i),
|
||||||
|
workspacesQueryWithSub: query.match(/^workspaces\(["']?([^\)]+?)["']?\)/i),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isGrafanaTemplateVariableFnQuery = (query: string) => {
|
||||||
|
const matches: Record<string, RegExpMatchArray | null> = grafanaTemplateVariableFnMatches(query);
|
||||||
|
return Object.keys(matches).some((key) => !!matches[key]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createGrafanaTemplateVariableQuery = (rawQuery: string, datasource: DataSource): AzureMonitorQuery => {
|
||||||
|
const matchesForQuery = grafanaTemplateVariableFnMatches(rawQuery);
|
||||||
|
const defaultSubscriptionId = datasource.azureMonitorDatasource.defaultSubscriptionId;
|
||||||
|
const createGrafanaTemplateVariableDetails = (): GrafanaTemplateVariableQuery => {
|
||||||
|
// deprecated app insights template variables (will most likely remove in grafana 9)
|
||||||
|
if (matchesForQuery.appInsightsMetricNameQuery) {
|
||||||
|
const queryDetails: AppInsightsMetricNameQuery = { rawQuery, kind: 'AppInsightsMetricNameQuery' };
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.appInsightsGroupByQuery) {
|
||||||
|
const queryDetails: AppInsightsGroupByQuery = {
|
||||||
|
kind: 'AppInsightsGroupByQuery',
|
||||||
|
rawQuery,
|
||||||
|
metricName: matchesForQuery.appInsightsGroupByQuery[1],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.subscriptions) {
|
||||||
|
const queryDetails: SubscriptionsQuery = {
|
||||||
|
kind: 'SubscriptionsQuery',
|
||||||
|
rawQuery,
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.resourceGroups && defaultSubscriptionId) {
|
||||||
|
const queryDetails: ResourceGroupsQuery = {
|
||||||
|
kind: 'ResourceGroupsQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.resourceGroupsWithSub) {
|
||||||
|
const queryDetails: ResourceGroupsQuery = {
|
||||||
|
kind: 'ResourceGroupsQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: matchesForQuery.resourceGroupsWithSub[1],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.metricDefinitions && defaultSubscriptionId) {
|
||||||
|
const queryDetails: MetricDefinitionsQuery = {
|
||||||
|
kind: 'MetricDefinitionsQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
resourceGroup: matchesForQuery.metricDefinitions[1],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.metricDefinitionsWithSub) {
|
||||||
|
const queryDetails: MetricDefinitionsQuery = {
|
||||||
|
kind: 'MetricDefinitionsQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: matchesForQuery.metricDefinitionsWithSub[1],
|
||||||
|
resourceGroup: matchesForQuery.metricDefinitionsWithSub[2],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.resourceNames && defaultSubscriptionId) {
|
||||||
|
const queryDetails: ResourceNamesQuery = {
|
||||||
|
kind: 'ResourceNamesQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
resourceGroup: matchesForQuery.resourceNames[1],
|
||||||
|
metricDefinition: matchesForQuery.resourceNames[2],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.resourceNamesWithSub) {
|
||||||
|
const queryDetails: ResourceNamesQuery = {
|
||||||
|
kind: 'ResourceNamesQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: matchesForQuery.resourceNamesWithSub[1],
|
||||||
|
resourceGroup: matchesForQuery.resourceNamesWithSub[2],
|
||||||
|
metricDefinition: matchesForQuery.resourceNamesWithSub[3],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.metricNamespace && defaultSubscriptionId) {
|
||||||
|
const queryDetails: MetricNamespaceQuery = {
|
||||||
|
kind: 'MetricNamespaceQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
resourceGroup: matchesForQuery.metricNamespace[1],
|
||||||
|
metricDefinition: matchesForQuery.metricNamespace[2],
|
||||||
|
resourceName: matchesForQuery.metricNamespace[3],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.metricNamespaceWithSub) {
|
||||||
|
const queryDetails: MetricNamespaceQuery = {
|
||||||
|
kind: 'MetricNamespaceQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: matchesForQuery.metricNamespaceWithSub[1],
|
||||||
|
resourceGroup: matchesForQuery.metricNamespaceWithSub[2],
|
||||||
|
metricDefinition: matchesForQuery.metricNamespaceWithSub[3],
|
||||||
|
resourceName: matchesForQuery.metricNamespaceWithSub[4],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.metricNames && defaultSubscriptionId) {
|
||||||
|
if (matchesForQuery.metricNames[3].indexOf(',') === -1) {
|
||||||
|
const queryDetails: MetricNamesQuery = {
|
||||||
|
kind: 'MetricNamesQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
resourceGroup: matchesForQuery.metricNames[1],
|
||||||
|
metricDefinition: matchesForQuery.metricNames[2],
|
||||||
|
resourceName: matchesForQuery.metricNames[3],
|
||||||
|
metricNamespace: matchesForQuery.metricNames[4],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.metricNamesWithSub) {
|
||||||
|
const queryDetails: MetricNamesQuery = {
|
||||||
|
kind: 'MetricNamesQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: matchesForQuery.metricNamesWithSub[1],
|
||||||
|
resourceGroup: matchesForQuery.metricNamesWithSub[2],
|
||||||
|
metricDefinition: matchesForQuery.metricNamesWithSub[3],
|
||||||
|
resourceName: matchesForQuery.metricNamesWithSub[4],
|
||||||
|
metricNamespace: matchesForQuery.metricNamesWithSub[5],
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.workspacesQuery && defaultSubscriptionId) {
|
||||||
|
const queryDetails: WorkspacesQuery = {
|
||||||
|
kind: 'WorkspacesQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesForQuery.workspacesQueryWithSub) {
|
||||||
|
const queryDetails: WorkspacesQuery = {
|
||||||
|
kind: 'WorkspacesQuery',
|
||||||
|
rawQuery,
|
||||||
|
subscription: (matchesForQuery.workspacesQueryWithSub[1] || '').trim(),
|
||||||
|
};
|
||||||
|
return queryDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
const queryDetails: SubscriptionsQuery = { kind: 'SubscriptionsQuery', rawQuery };
|
||||||
|
return queryDetails;
|
||||||
|
};
|
||||||
|
|
||||||
|
const query: AzureMonitorQuery = {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: createGrafanaTemplateVariableDetails(),
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
};
|
||||||
|
return query;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createLogAnalyticsTemplateVariableQuery = async (
|
||||||
|
rawQuery: string,
|
||||||
|
datasource: DataSource
|
||||||
|
): Promise<AzureMonitorQuery> => {
|
||||||
|
const defaultSubscriptionId = datasource.azureMonitorDatasource.defaultSubscriptionId;
|
||||||
|
let resource = '';
|
||||||
|
// if there's an existing query, we try to get the resourcesuri from a deprecated default workspace
|
||||||
|
// a note this is very similar logic to what is used in useMigrations but moved out of the react-hook land
|
||||||
|
if (rawQuery) {
|
||||||
|
const defaultWorkspaceId = datasource.azureLogAnalyticsDatasource.getDeprecatedDefaultWorkSpace();
|
||||||
|
if (defaultWorkspaceId) {
|
||||||
|
const isWorkspaceGUID = isGUIDish(defaultWorkspaceId);
|
||||||
|
if (isWorkspaceGUID) {
|
||||||
|
resource = await datasource.resourcePickerData.getResourceURIFromWorkspace(defaultWorkspaceId);
|
||||||
|
} else {
|
||||||
|
resource = defaultWorkspaceId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const maybeFirstWorkspace = await datasource.azureLogAnalyticsDatasource.getFirstWorkspace();
|
||||||
|
resource = maybeFirstWorkspace || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
azureLogAnalytics: {
|
||||||
|
query: rawQuery,
|
||||||
|
resource,
|
||||||
|
},
|
||||||
|
subscription: defaultSubscriptionId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const migrateStringQueriesToObjectQueries = async (
|
||||||
|
rawQuery: string | AzureMonitorQuery,
|
||||||
|
options: { datasource: DataSource }
|
||||||
|
): Promise<AzureMonitorQuery> => {
|
||||||
|
// no need to migrate already migrated queries
|
||||||
|
if (typeof rawQuery !== 'string') {
|
||||||
|
return rawQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isGrafanaTemplateVariableFnQuery(rawQuery)
|
||||||
|
? createGrafanaTemplateVariableQuery(rawQuery, options.datasource)
|
||||||
|
: createLogAnalyticsTemplateVariableQuery(rawQuery, options.datasource);
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
import { DataQuery } from '@grafana/data';
|
import { DataQuery } from '@grafana/data';
|
||||||
|
import { GrafanaTemplateVariableQuery } from './templateVariables';
|
||||||
|
|
||||||
export enum AzureQueryType {
|
export enum AzureQueryType {
|
||||||
AzureMonitor = 'Azure Monitor',
|
AzureMonitor = 'Azure Monitor',
|
||||||
@ -6,6 +7,7 @@ export enum AzureQueryType {
|
|||||||
InsightsAnalytics = 'Insights Analytics',
|
InsightsAnalytics = 'Insights Analytics',
|
||||||
LogAnalytics = 'Azure Log Analytics',
|
LogAnalytics = 'Azure Log Analytics',
|
||||||
AzureResourceGraph = 'Azure Resource Graph',
|
AzureResourceGraph = 'Azure Resource Graph',
|
||||||
|
GrafanaTemplateVariableFn = 'Grafana Template Variable Function',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,6 +25,7 @@ export interface AzureMonitorQuery extends DataQuery {
|
|||||||
azureMonitor?: AzureMetricQuery;
|
azureMonitor?: AzureMetricQuery;
|
||||||
azureLogAnalytics?: AzureLogsQuery;
|
azureLogAnalytics?: AzureLogsQuery;
|
||||||
azureResourceGraph?: AzureResourceGraphQuery;
|
azureResourceGraph?: AzureResourceGraphQuery;
|
||||||
|
grafanaTemplateVariableFn?: GrafanaTemplateVariableQuery;
|
||||||
|
|
||||||
/** @deprecated App Insights/Insights Analytics deprecated in v8 */
|
/** @deprecated App Insights/Insights Analytics deprecated in v8 */
|
||||||
appInsights?: ApplicationInsightsQuery;
|
appInsights?: ApplicationInsightsQuery;
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
export type GrafanaTemplateVariableQueryType =
|
||||||
|
| 'AppInsightsMetricNameQuery'
|
||||||
|
| 'AppInsightsGroupByQuery'
|
||||||
|
| 'SubscriptionsQuery'
|
||||||
|
| 'ResourceGroupsQuery'
|
||||||
|
| 'MetricDefinitionsQuery'
|
||||||
|
| 'ResourceNamesQuery'
|
||||||
|
| 'MetricNamespaceQuery'
|
||||||
|
| 'MetricNamesQuery'
|
||||||
|
| 'WorkspacesQuery'
|
||||||
|
| 'UnknownQuery';
|
||||||
|
|
||||||
|
interface BaseGrafanaTemplateVariableQuery {
|
||||||
|
rawQuery?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnknownQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'UnknownQuery';
|
||||||
|
}
|
||||||
|
export interface AppInsightsMetricNameQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'AppInsightsMetricNameQuery';
|
||||||
|
}
|
||||||
|
export interface AppInsightsGroupByQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'AppInsightsGroupByQuery';
|
||||||
|
metricName: string;
|
||||||
|
}
|
||||||
|
export interface SubscriptionsQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'SubscriptionsQuery';
|
||||||
|
}
|
||||||
|
export interface ResourceGroupsQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'ResourceGroupsQuery';
|
||||||
|
subscription: string;
|
||||||
|
}
|
||||||
|
export interface MetricDefinitionsQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'MetricDefinitionsQuery';
|
||||||
|
subscription: string;
|
||||||
|
resourceGroup: string;
|
||||||
|
}
|
||||||
|
export interface ResourceNamesQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'ResourceNamesQuery';
|
||||||
|
subscription: string;
|
||||||
|
resourceGroup: string;
|
||||||
|
metricDefinition: string;
|
||||||
|
}
|
||||||
|
export interface MetricNamespaceQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'MetricNamespaceQuery';
|
||||||
|
subscription: string;
|
||||||
|
resourceGroup: string;
|
||||||
|
metricDefinition: string;
|
||||||
|
resourceName: string;
|
||||||
|
}
|
||||||
|
export interface MetricNamesQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'MetricNamesQuery';
|
||||||
|
subscription: string;
|
||||||
|
resourceGroup: string;
|
||||||
|
metricDefinition: string;
|
||||||
|
resourceName: string;
|
||||||
|
metricNamespace: string;
|
||||||
|
}
|
||||||
|
export interface WorkspacesQuery extends BaseGrafanaTemplateVariableQuery {
|
||||||
|
kind: 'WorkspacesQuery';
|
||||||
|
subscription: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GrafanaTemplateVariableQuery =
|
||||||
|
| AppInsightsMetricNameQuery
|
||||||
|
| AppInsightsGroupByQuery
|
||||||
|
| SubscriptionsQuery
|
||||||
|
| ResourceGroupsQuery
|
||||||
|
| MetricDefinitionsQuery
|
||||||
|
| ResourceNamesQuery
|
||||||
|
| MetricNamespaceQuery
|
||||||
|
| MetricNamesQuery
|
||||||
|
| WorkspacesQuery
|
||||||
|
| UnknownQuery;
|
@ -64,6 +64,8 @@ export interface AzureDataSourceJsonData extends DataSourceJsonData {
|
|||||||
logAnalyticsClientId?: string;
|
logAnalyticsClientId?: string;
|
||||||
/** @deprecated Azure Logs credentials */
|
/** @deprecated Azure Logs credentials */
|
||||||
logAnalyticsSubscriptionId?: string;
|
logAnalyticsSubscriptionId?: string;
|
||||||
|
/** @deprecated Azure Logs credentials */
|
||||||
|
logAnalyticsDefaultWorkspace?: string;
|
||||||
|
|
||||||
// App Insights
|
// App Insights
|
||||||
appInsightsAppId?: string;
|
appInsightsAppId?: string;
|
||||||
|
@ -0,0 +1,478 @@
|
|||||||
|
import { DataQueryRequest, DataQueryResponseData, toDataFrame } from '@grafana/data';
|
||||||
|
import { from } from 'rxjs';
|
||||||
|
import { AzureMonitorQuery, AzureQueryType } from './types';
|
||||||
|
import { VariableSupport } from './variables';
|
||||||
|
import createMockDatasource from './__mocks__/datasource';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...((jest.requireActual('@grafana/runtime') as unknown) as object),
|
||||||
|
getTemplateSrv: () => ({
|
||||||
|
replace: (val: string) => {
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
describe('VariableSupport', () => {
|
||||||
|
describe('querying for grafana template variable fns', () => {
|
||||||
|
it('can fetch deprecated log analytics metric names', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
insightsAnalyticsDatasource: {
|
||||||
|
getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'AppInsightsMetricNameQuery',
|
||||||
|
rawQuery: 'AppInsightsMetricNames()',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch deprecated log analytics groupBys', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
insightsAnalyticsDatasource: {
|
||||||
|
getGroupBys: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'AppInsightsGroupByQuery',
|
||||||
|
rawQuery: 'AppInsightsGroupBys(metricname)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch subscriptions', (done) => {
|
||||||
|
const fakeSubscriptions = ['subscriptionId'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
getSubscriptions: jest.fn().mockResolvedValueOnce(fakeSubscriptions),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'SubscriptionsQuery',
|
||||||
|
rawQuery: 'Subscriptions()',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(fakeSubscriptions);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch resourceGroups with a default subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
azureLogAnalyticsDatasource: {
|
||||||
|
defaultSubscriptionId: 'defaultSubscriptionId',
|
||||||
|
},
|
||||||
|
getResourceGroups: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'ResourceGroupsQuery',
|
||||||
|
rawQuery: 'ResourceGroups()',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch resourceGroups with a subscriptionId arg', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
getResourceGroups: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'ResourceGroupsQuery',
|
||||||
|
rawQuery: 'ResourceGroups(sub)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch metricDefinitions with a default subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
azureLogAnalyticsDatasource: {
|
||||||
|
defaultSubscriptionId: 'defaultSubscriptionId',
|
||||||
|
},
|
||||||
|
getMetricDefinitions: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'MetricDefinitionsQuery',
|
||||||
|
rawQuery: 'Namespaces(resourceGroup)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch metricDefinitions with a subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
getMetricDefinitions: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'MetricDefinitionsQuery',
|
||||||
|
rawQuery: 'Namespaces(resourceGroup, subscriptionId)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch resourceNames with a default subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
azureLogAnalyticsDatasource: {
|
||||||
|
defaultSubscriptionId: 'defaultSubscriptionId',
|
||||||
|
},
|
||||||
|
getResourceNames: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'ResourceNamesQuery',
|
||||||
|
rawQuery: 'ResourceNames(resourceGroup, metricDefinition)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch resourceNames with a subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
getResourceNames: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'ResourceNamesQuery',
|
||||||
|
rawQuery: 'ResourceNames(subscriptionId, resourceGroup, metricDefinition)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch a metricNamespace with a default subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
azureLogAnalyticsDatasource: {
|
||||||
|
defaultSubscriptionId: 'defaultSubscriptionId',
|
||||||
|
},
|
||||||
|
getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'MetricNamespaceQuery',
|
||||||
|
rawQuery: 'metricNamespace(resourceGroup, metricDefinition, resourceName)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch a metricNamespace with a subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'MetricNamespaceQuery',
|
||||||
|
rawQuery: 'metricNamespace(subscriptionId, resourceGroup, metricDefinition, resourceName)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch metricNames with a default subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
azureLogAnalyticsDatasource: {
|
||||||
|
defaultSubscriptionId: 'defaultSubscriptionId',
|
||||||
|
},
|
||||||
|
getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'MetricNamesQuery',
|
||||||
|
rawQuery: 'metricNames(resourceGroup, metricDefinition, resourceName, metricNamespace)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch metricNames with a subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'MetricNamesQuery',
|
||||||
|
rawQuery: 'metricNames(subscription, resourceGroup, metricDefinition, resourceName, metricNamespace)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch workspaces with a default subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
azureLogAnalyticsDatasource: {
|
||||||
|
defaultSubscriptionId: 'defaultSubscriptionId',
|
||||||
|
getWorkspaces: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'WorkspacesQuery',
|
||||||
|
rawQuery: 'workspaces()',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('can fetch workspaces with a subscriptionId', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
azureLogAnalyticsDatasource: {
|
||||||
|
getWorkspaces: jest.fn().mockResolvedValueOnce(expectedResults),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
kind: 'WorkspacesQuery',
|
||||||
|
rawQuery: 'workspaces(subscriptionId)',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty array for unknown queries', (done) => {
|
||||||
|
const variableSupport = new VariableSupport(createMockDatasource());
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
queryType: AzureQueryType.GrafanaTemplateVariableFn,
|
||||||
|
grafanaTemplateVariableFn: {
|
||||||
|
rawQuery: 'nonsense',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data).toEqual([]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes on the query to the main datasource for all non-grafana template variable fns', (done) => {
|
||||||
|
const expectedResults = ['test'];
|
||||||
|
const variableSupport = new VariableSupport(
|
||||||
|
createMockDatasource({
|
||||||
|
query: () =>
|
||||||
|
from(
|
||||||
|
Promise.resolve({
|
||||||
|
data: [toDataFrame(expectedResults)],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const mockRequest = {
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
azureLogAnalytics: {
|
||||||
|
query: 'some log thing',
|
||||||
|
},
|
||||||
|
} as AzureMonitorQuery,
|
||||||
|
],
|
||||||
|
} as DataQueryRequest<AzureMonitorQuery>;
|
||||||
|
const observables = variableSupport.query(mockRequest);
|
||||||
|
observables.subscribe((result: DataQueryResponseData) => {
|
||||||
|
expect(result.data[0].source).toEqual(expectedResults);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,105 @@
|
|||||||
|
import { from, lastValueFrom, Observable } from 'rxjs';
|
||||||
|
import {
|
||||||
|
CustomVariableSupport,
|
||||||
|
DataQueryRequest,
|
||||||
|
DataQueryResponse,
|
||||||
|
MetricFindValue,
|
||||||
|
toDataFrame,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import VariableEditor from './components/VariableEditor/VariableEditor';
|
||||||
|
import DataSource from './datasource';
|
||||||
|
import { AzureQueryType, AzureMonitorQuery } from './types';
|
||||||
|
import { getTemplateSrv } from '@grafana/runtime';
|
||||||
|
import { migrateStringQueriesToObjectQueries } from './grafanaTemplateVariableFns';
|
||||||
|
import { GrafanaTemplateVariableQuery } from './types/templateVariables';
|
||||||
|
export class VariableSupport extends CustomVariableSupport<DataSource, AzureMonitorQuery> {
|
||||||
|
constructor(private readonly datasource: DataSource) {
|
||||||
|
super();
|
||||||
|
this.datasource = datasource;
|
||||||
|
this.query = this.query.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor = VariableEditor;
|
||||||
|
|
||||||
|
query(request: DataQueryRequest<AzureMonitorQuery>): Observable<DataQueryResponse> {
|
||||||
|
const promisedResults = async () => {
|
||||||
|
const queryObj = await migrateStringQueriesToObjectQueries(request.targets[0], { datasource: this.datasource });
|
||||||
|
|
||||||
|
if (queryObj.queryType === AzureQueryType.GrafanaTemplateVariableFn && queryObj.grafanaTemplateVariableFn) {
|
||||||
|
const templateVariablesResults = await this.callGrafanaTemplateVariableFn(queryObj.grafanaTemplateVariableFn);
|
||||||
|
return {
|
||||||
|
data: templateVariablesResults ? [toDataFrame(templateVariablesResults)] : [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
request.targets[0] = queryObj;
|
||||||
|
return lastValueFrom(this.datasource.query(request));
|
||||||
|
};
|
||||||
|
|
||||||
|
return from(promisedResults());
|
||||||
|
}
|
||||||
|
|
||||||
|
callGrafanaTemplateVariableFn(query: GrafanaTemplateVariableQuery): Promise<MetricFindValue[]> | null {
|
||||||
|
// deprecated app insights template variables (will most likely remove in grafana 9)
|
||||||
|
if (this.datasource.insightsAnalyticsDatasource) {
|
||||||
|
if (query.kind === 'AppInsightsMetricNameQuery') {
|
||||||
|
return this.datasource.insightsAnalyticsDatasource.getMetricNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'AppInsightsGroupByQuery') {
|
||||||
|
return this.datasource.insightsAnalyticsDatasource.getGroupBys(getTemplateSrv().replace(query.metricName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'SubscriptionsQuery') {
|
||||||
|
return this.datasource.getSubscriptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'ResourceGroupsQuery') {
|
||||||
|
return this.datasource.getResourceGroups(this.replaceVariable(query.subscription));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'MetricDefinitionsQuery') {
|
||||||
|
return this.datasource.getMetricDefinitions(
|
||||||
|
this.replaceVariable(query.subscription),
|
||||||
|
this.replaceVariable(query.resourceGroup)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'ResourceNamesQuery') {
|
||||||
|
return this.datasource.getResourceNames(
|
||||||
|
this.replaceVariable(query.subscription),
|
||||||
|
this.replaceVariable(query.resourceGroup),
|
||||||
|
this.replaceVariable(query.metricDefinition)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'MetricNamespaceQuery') {
|
||||||
|
return this.datasource.getMetricNamespaces(
|
||||||
|
this.replaceVariable(query.subscription),
|
||||||
|
this.replaceVariable(query.resourceGroup),
|
||||||
|
this.replaceVariable(query.metricDefinition),
|
||||||
|
this.replaceVariable(query.resourceName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'MetricNamesQuery') {
|
||||||
|
return this.datasource.getMetricNames(
|
||||||
|
this.replaceVariable(query.subscription),
|
||||||
|
this.replaceVariable(query.resourceGroup),
|
||||||
|
this.replaceVariable(query.metricDefinition),
|
||||||
|
this.replaceVariable(query.resourceName),
|
||||||
|
this.replaceVariable(query.metricNamespace)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.kind === 'WorkspacesQuery') {
|
||||||
|
return this.datasource.azureLogAnalyticsDatasource.getWorkspaces(this.replaceVariable(query.subscription));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceVariable(metric: string) {
|
||||||
|
return getTemplateSrv().replace((metric || '').trim());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user