mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 03:34:15 -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]>;
|
||||
};
|
||||
|
||||
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
|
||||
// having it be any or casted immediately to Datasource
|
||||
const _mockDatasource: DeepPartial<Datasource> = {
|
||||
@ -16,6 +16,7 @@ export default function createMockDatasource() {
|
||||
return true;
|
||||
},
|
||||
getSubscriptions: jest.fn().mockResolvedValueOnce([]),
|
||||
defaultSubscriptionId: 'subscriptionId',
|
||||
},
|
||||
|
||||
getAzureLogAnalyticsWorkspaces: jest.fn().mockResolvedValueOnce([]),
|
||||
@ -34,12 +35,14 @@ export default function createMockDatasource() {
|
||||
|
||||
azureLogAnalyticsDatasource: {
|
||||
getKustoSchema: () => Promise.resolve(),
|
||||
getDeprecatedDefaultWorkSpace: () => 'defaultWorkspaceId',
|
||||
},
|
||||
resourcePickerData: {
|
||||
getResourcePickerData: () => ({}),
|
||||
getResourcesForResourceGroup: () => ({}),
|
||||
getResourceURIFromWorkspace: () => '',
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
|
||||
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', () => {
|
||||
const response = {
|
||||
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 { 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> {
|
||||
const path = `${this.resourcePath}/metrics/metadata`;
|
||||
return this.getResource(path)
|
||||
|
@ -2,7 +2,7 @@ import AzureMonitorDatasource from '../datasource';
|
||||
import AzureLogAnalyticsDatasource from './azure_log_analytics_datasource';
|
||||
import FakeSchemaData from './__mocks__/schema';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { AzureLogsVariable, AzureMonitorQuery, DatasourceValidationResult } from '../types';
|
||||
import { AzureMonitorQuery, DatasourceValidationResult } from '../types';
|
||||
import { toUtc } from '@grafana/data';
|
||||
|
||||
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', () => {
|
||||
const tableResponse = {
|
||||
tables: [
|
||||
|
@ -8,13 +8,7 @@ import {
|
||||
AzureQueryType,
|
||||
DatasourceValidationResult,
|
||||
} from '../types';
|
||||
import {
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
ScopedVars,
|
||||
DataSourceInstanceSettings,
|
||||
MetricFindValue,
|
||||
} from '@grafana/data';
|
||||
import { DataQueryRequest, DataQueryResponse, ScopedVars, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
||||
import { Observable, from } from 'rxjs';
|
||||
import { mergeMap } from 'rxjs/operators';
|
||||
@ -218,60 +212,12 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, optionalOptions?: unknown): Promise<MetricFindValue[]> {
|
||||
// workspaces() - Get workspaces in the default subscription
|
||||
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[]>;
|
||||
/*
|
||||
In 7.5.x it used to be possible to set a default workspace id in the config on the auth page.
|
||||
This has been deprecated, however is still used by a few legacy template queries.
|
||||
*/
|
||||
getDeprecatedDefaultWorkSpace() {
|
||||
return this.instanceSettings.jsonData.logAnalyticsDefaultWorkspace;
|
||||
}
|
||||
|
||||
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', () => {
|
||||
const response = {
|
||||
value: [
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
AzureQueryType,
|
||||
DatasourceValidationResult,
|
||||
} from '../types';
|
||||
import { DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
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 }>> {
|
||||
if (!this.isConfigured()) {
|
||||
return [];
|
||||
|
@ -14,6 +14,7 @@ interface LogsQueryEditorProps {
|
||||
onChange: (newQuery: AzureMonitorQuery) => void;
|
||||
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
||||
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
|
||||
hideFormatAs?: boolean;
|
||||
}
|
||||
|
||||
const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
||||
@ -23,6 +24,7 @@ const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
||||
variableOptionGroup,
|
||||
onChange,
|
||||
setError,
|
||||
hideFormatAs,
|
||||
}) => {
|
||||
const migrationError = useMigrations(datasource, query, onChange);
|
||||
|
||||
@ -48,14 +50,16 @@ const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
||||
setError={setError}
|
||||
/>
|
||||
|
||||
<FormatAsField
|
||||
query={query}
|
||||
datasource={datasource}
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
{!hideFormatAs && (
|
||||
<FormatAsField
|
||||
query={query}
|
||||
datasource={datasource}
|
||||
subscriptionId={subscriptionId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{migrationError && <Alert title={migrationError.title}>{migrationError.message}</Alert>}
|
||||
</div>
|
||||
|
@ -27,7 +27,6 @@ function parseResourceDetails(resourceURI: string) {
|
||||
const ResourceField: React.FC<AzureQueryEditorFieldProps> = ({ query, datasource, onQueryChange }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { resource } = query.azureLogAnalytics ?? {};
|
||||
|
||||
const [pickerIsOpen, setPickerIsOpen] = useState(false);
|
||||
|
||||
const handleOpenPicker = useCallback(() => {
|
||||
@ -61,7 +60,7 @@ const ResourceField: React.FC<AzureQueryEditorFieldProps> = ({ query, datasource
|
||||
</Modal>
|
||||
|
||||
<Field label="Resource">
|
||||
<Button variant="secondary" onClick={handleOpenPicker}>
|
||||
<Button variant="secondary" onClick={handleOpenPicker} type="button">
|
||||
<ResourceLabel resource={resource} datasource={datasource} />
|
||||
</Button>
|
||||
</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 { getAzureCloud } from './credentials';
|
||||
import migrateAnnotation from './utils/migrateAnnotation';
|
||||
|
||||
import { VariableSupport } from './variables';
|
||||
export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||
annotations = {
|
||||
prepareAnnotation: migrateAnnotation,
|
||||
@ -71,6 +71,8 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
||||
this.pseudoDatasource[AzureQueryType.ApplicationInsights] = this.appInsightsDatasource;
|
||||
this.pseudoDatasource[AzureQueryType.InsightsAnalytics] = this.insightsAnalyticsDatasource;
|
||||
}
|
||||
|
||||
this.variables = new VariableSupport(this);
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<AzureMonitorQuery>): Observable<DataQueryResponse> {
|
||||
@ -133,29 +135,6 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
||||
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> {
|
||||
const promises: Array<Promise<DatasourceValidationResult>> = [];
|
||||
|
||||
@ -307,6 +286,9 @@ function hasQueryForType(query: AzureMonitorQuery): boolean {
|
||||
case AzureQueryType.AzureResourceGraph:
|
||||
return !!query.azureResourceGraph;
|
||||
|
||||
case AzureQueryType.GrafanaTemplateVariableFn:
|
||||
return !!query.grafanaTemplateVariableFn;
|
||||
|
||||
case AzureQueryType.ApplicationInsights:
|
||||
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 { GrafanaTemplateVariableQuery } from './templateVariables';
|
||||
|
||||
export enum AzureQueryType {
|
||||
AzureMonitor = 'Azure Monitor',
|
||||
@ -6,6 +7,7 @@ export enum AzureQueryType {
|
||||
InsightsAnalytics = 'Insights Analytics',
|
||||
LogAnalytics = 'Azure Log Analytics',
|
||||
AzureResourceGraph = 'Azure Resource Graph',
|
||||
GrafanaTemplateVariableFn = 'Grafana Template Variable Function',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,6 +25,7 @@ export interface AzureMonitorQuery extends DataQuery {
|
||||
azureMonitor?: AzureMetricQuery;
|
||||
azureLogAnalytics?: AzureLogsQuery;
|
||||
azureResourceGraph?: AzureResourceGraphQuery;
|
||||
grafanaTemplateVariableFn?: GrafanaTemplateVariableQuery;
|
||||
|
||||
/** @deprecated App Insights/Insights Analytics deprecated in v8 */
|
||||
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;
|
||||
/** @deprecated Azure Logs credentials */
|
||||
logAnalyticsSubscriptionId?: string;
|
||||
/** @deprecated Azure Logs credentials */
|
||||
logAnalyticsDefaultWorkspace?: string;
|
||||
|
||||
// App Insights
|
||||
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