Commit 3b9a4e64 by Daniel Lee Committed by GitHub

AzureMonitor: support workspaces function for template variables (#22882)

* azuremonitor: adds support for workspaces query macro...

...for Azure Logs template variable queries

* docs: azure logs workspaces templating function

* Update docs/sources/features/datasources/azuremonitor.md

Co-Authored-By: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* docs: convert list into table

* docs: fixes prettier formatting problem

Prettier adds a slash before dollar signs in markdown. Disabling it
for this table with a prettier comment.

https://prettier.io/docs/en/ignore.html

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
parent ec9167e9
......@@ -274,6 +274,45 @@ There are also some Grafana variables that can be used in Azure Log Analytics qu
- `$__interval` - Grafana calculates the minimum time grain that can be used to group by time in queries. More details on how it works [here]({{< relref "../../reference/templating.md#interval-variables" >}}). It returns a time grain like `5m` or `1h` that can be used in the bin function. E.g. `summarize count() by bin(TimeGenerated, $__interval)`
### Templating with Variables for Azure Log Analytics
Any Log Analytics query that returns a list of values can be used in the `Query` field in the Variable edit view. There is also one Grafana function for Log Analytics that returns a list of workspaces.
Refer to the [Variables]({{< relref "../../reference/templating.md" >}}) documentation for an introduction to the templating feature and the different
types of template variables.
| Name | Description |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| _workspaces()_ | Returns a list of workspaces for the default subscription. |
| _workspaces(12345678-aaaa-bbbb-cccc-123456789aaa)_ | Returns a list of workspaces for the specified subscription (the parameter can be quoted or unquoted). |
Example variable queries:
<!-- prettier-ignore-start -->
| Query | Description |
| --------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| _subscriptions()_ | Returns a list of Azure subscriptions |
| _workspaces()_ | Returns a list of workspaces for default subscription |
| _workspaces("12345678-aaaa-bbbb-cccc-123456789aaa")_ | Returns a list of workspaces for a specified subscription |
| _workspaces("$subscription")_ | With template variable for the subscription parameter |
| _workspace("myWorkspace").Heartbeat \| distinct Computer_ | Returns a list of Virtual Machines |
| _workspace("$workspace").Heartbeat \| distinct Computer_ | Returns a list of Virtual Machines with template variable |
| _workspace("$workspace").Perf \| distinct ObjectName_ | Returns a list of objects from the Perf table |
| _workspace("$workspace").Perf \| where ObjectName == "$object" \| distinct CounterName_ | Returns a list of metric names from the Perf table |
<!-- prettier-ignore-end -->
Example of a time series query using variables:
```
Perf
| where ObjectName == "$object" and CounterName == "$metric"
| where TimeGenerated >= $__timeFrom() and TimeGenerated <= $__timeTo()
| where $__contains(Computer, $computer)
| summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer
| order by TimeGenerated asc
```
### Azure Log Analytics Alerting
Not implemented yet.
......
......@@ -2,7 +2,7 @@ import AzureMonitorDatasource from '../datasource';
import FakeSchemaData from './__mocks__/schema';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { KustoSchema } from '../types';
import { KustoSchema, AzureLogsVariable } from '../types';
import { toUtc } from '@grafana/data';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
......@@ -283,53 +283,129 @@ describe('AzureLogAnalyticsDatasource', () => {
});
describe('When performing metricFindQuery', () => {
const tableResponseWithOneColumn = {
tables: [
{
name: 'PrimaryResult',
columns: [
{
name: 'Category',
type: 'string',
},
],
rows: [['Administrative'], ['Policy']],
},
],
};
let queryResults: AzureLogsVariable[];
const workspaceResponse = {
const workspacesResponse = {
value: [
{
name: 'aworkspace',
name: 'workspace1',
properties: {
source: 'Azure',
customerId: 'abc1b44e-3e57-4410-b027-6cc0ae6dee67',
customerId: 'eeee4fde-1aaa-4d60-9974-eeee562ffaa1',
},
},
{
name: 'workspace2',
properties: {
customerId: 'eeee4fde-1aaa-4d60-9974-eeee562ffaa2',
},
},
],
};
let queryResults: any[];
describe('and is the workspaces() macro', () => {
beforeEach(async () => {
datasourceRequestMock.mockImplementation((options: { url: string }) => {
expect(options.url).toContain('xxx');
return Promise.resolve({ data: workspacesResponse, status: 200 });
});
beforeEach(async () => {
datasourceRequestMock.mockImplementation((options: { url: string }) => {
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
return Promise.resolve({ data: workspaceResponse, status: 200 });
} else {
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
}
queryResults = await ctx.ds.metricFindQuery('workspaces()');
});
queryResults = await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
it('should return a list of workspaces', () => {
expect(queryResults.length).toBe(2);
expect(queryResults[0].text).toBe('workspace1');
expect(queryResults[0].value).toBe('eeee4fde-1aaa-4d60-9974-eeee562ffaa1');
expect(queryResults[1].text).toBe('workspace2');
expect(queryResults[1].value).toBe('eeee4fde-1aaa-4d60-9974-eeee562ffaa2');
});
});
it('should return a list of categories in the correct format', () => {
expect(queryResults.length).toBe(2);
expect(queryResults[0].text).toBe('Administrative');
expect(queryResults[0].value).toBe('Administrative');
expect(queryResults[1].text).toBe('Policy');
expect(queryResults[1].value).toBe('Policy');
describe('and is the workspaces() macro with the subscription parameter', () => {
beforeEach(async () => {
datasourceRequestMock.mockImplementation((options: { url: string }) => {
expect(options.url).toContain('11112222-eeee-4949-9b2d-9106972f9123');
return Promise.resolve({ data: workspacesResponse, status: 200 });
});
queryResults = await ctx.ds.metricFindQuery('workspaces(11112222-eeee-4949-9b2d-9106972f9123)');
});
it('should return a list of workspaces', () => {
expect(queryResults.length).toBe(2);
expect(queryResults[0].text).toBe('workspace1');
expect(queryResults[0].value).toBe('eeee4fde-1aaa-4d60-9974-eeee562ffaa1');
expect(queryResults[1].text).toBe('workspace2');
expect(queryResults[1].value).toBe('eeee4fde-1aaa-4d60-9974-eeee562ffaa2');
});
});
describe('and is the workspaces() macro with the subscription parameter quoted', () => {
beforeEach(async () => {
datasourceRequestMock.mockImplementation((options: { url: string }) => {
expect(options.url).toContain('11112222-eeee-4949-9b2d-9106972f9123');
return Promise.resolve({ data: workspacesResponse, status: 200 });
});
queryResults = await ctx.ds.metricFindQuery('workspaces("11112222-eeee-4949-9b2d-9106972f9123")');
});
it('should return a list of workspaces', () => {
expect(queryResults.length).toBe(2);
expect(queryResults[0].text).toBe('workspace1');
expect(queryResults[0].value).toBe('eeee4fde-1aaa-4d60-9974-eeee562ffaa1');
expect(queryResults[1].text).toBe('workspace2');
expect(queryResults[1].value).toBe('eeee4fde-1aaa-4d60-9974-eeee562ffaa2');
});
});
describe('and is a custom query', () => {
const tableResponseWithOneColumn = {
tables: [
{
name: 'PrimaryResult',
columns: [
{
name: 'Category',
type: 'string',
},
],
rows: [['Administrative'], ['Policy']],
},
],
};
const workspaceResponse = {
value: [
{
name: 'aworkspace',
properties: {
source: 'Azure',
customerId: 'abc1b44e-3e57-4410-b027-6cc0ae6dee67',
},
},
],
};
beforeEach(async () => {
datasourceRequestMock.mockImplementation((options: { url: string }) => {
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
return Promise.resolve({ data: workspaceResponse, status: 200 });
} else {
return Promise.resolve({ data: tableResponseWithOneColumn, status: 200 });
}
});
queryResults = await ctx.ds.metricFindQuery('workspace("aworkspace").AzureActivity | distinct Category');
});
it('should return a list of categories in the correct format', () => {
expect(queryResults.length).toBe(2);
expect(queryResults[0].text).toBe('Administrative');
expect(queryResults[0].value).toBe('Administrative');
expect(queryResults[1].text).toBe('Policy');
expect(queryResults[1].value).toBe('Policy');
});
});
});
......
import _ from 'lodash';
import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder';
import ResponseParser from './response_parser';
import { AzureMonitorQuery, AzureDataSourceJsonData } from '../types';
import { AzureMonitorQuery, AzureDataSourceJsonData, AzureLogsVariable } from '../types';
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
......@@ -47,7 +47,7 @@ export default class AzureLogAnalyticsDatasource {
}
}
getWorkspaces(subscription: string) {
getWorkspaces(subscription: string): Promise<AzureLogsVariable[]> {
const subscriptionId = this.templateSrv.replace(subscription || this.subscriptionId);
const workspaceListUrl =
......@@ -118,6 +118,16 @@ export default class AzureLogAnalyticsDatasource {
}
metricFindQuery(query: string) {
const workspacesQuery = query.match(/^workspaces\(\)/i);
if (workspacesQuery) {
return this.getWorkspaces(this.subscriptionId);
}
const workspacesQueryWithSub = query.match(/^workspaces\(["']?([^\)]+?)["']?\)/i);
if (workspacesQueryWithSub) {
return this.getWorkspaces((workspacesQueryWithSub[1] || '').trim());
}
return this.getDefaultOrFirstWorkspace().then((workspace: any) => {
const queries: any[] = this.buildQuery(query, null, workspace);
......
......@@ -160,7 +160,7 @@ describe('AzureMonitorDatasource', () => {
};
beforeEach(() => {
datasourceRequestMock.mockImplementation((options: { url: string }) => Promise.resolve(response));
datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
});
it('should return a list of subscriptions', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment