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 ...@@ -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)` - `$__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 ### Azure Log Analytics Alerting
Not implemented yet. Not implemented yet.
......
...@@ -2,7 +2,7 @@ import AzureMonitorDatasource from '../datasource'; ...@@ -2,7 +2,7 @@ import AzureMonitorDatasource from '../datasource';
import FakeSchemaData from './__mocks__/schema'; import FakeSchemaData from './__mocks__/schema';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
import { KustoSchema } from '../types'; import { KustoSchema, AzureLogsVariable } from '../types';
import { toUtc } from '@grafana/data'; import { toUtc } from '@grafana/data';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
...@@ -283,6 +283,83 @@ describe('AzureLogAnalyticsDatasource', () => { ...@@ -283,6 +283,83 @@ describe('AzureLogAnalyticsDatasource', () => {
}); });
describe('When performing metricFindQuery', () => { describe('When performing metricFindQuery', () => {
let queryResults: AzureLogsVariable[];
const workspacesResponse = {
value: [
{
name: 'workspace1',
properties: {
customerId: 'eeee4fde-1aaa-4d60-9974-eeee562ffaa1',
},
},
{
name: 'workspace2',
properties: {
customerId: 'eeee4fde-1aaa-4d60-9974-eeee562ffaa2',
},
},
],
};
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 });
});
queryResults = await ctx.ds.metricFindQuery('workspaces()');
});
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', () => {
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 = { const tableResponseWithOneColumn = {
tables: [ tables: [
{ {
...@@ -310,8 +387,6 @@ describe('AzureLogAnalyticsDatasource', () => { ...@@ -310,8 +387,6 @@ describe('AzureLogAnalyticsDatasource', () => {
], ],
}; };
let queryResults: any[];
beforeEach(async () => { beforeEach(async () => {
datasourceRequestMock.mockImplementation((options: { url: string }) => { datasourceRequestMock.mockImplementation((options: { url: string }) => {
if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) { if (options.url.indexOf('Microsoft.OperationalInsights/workspaces') > -1) {
...@@ -332,6 +407,7 @@ describe('AzureLogAnalyticsDatasource', () => { ...@@ -332,6 +407,7 @@ describe('AzureLogAnalyticsDatasource', () => {
expect(queryResults[1].value).toBe('Policy'); expect(queryResults[1].value).toBe('Policy');
}); });
}); });
});
describe('When performing annotationQuery', () => { describe('When performing annotationQuery', () => {
const tableResponse = { const tableResponse = {
......
import _ from 'lodash'; import _ from 'lodash';
import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder'; import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder';
import ResponseParser from './response_parser'; import ResponseParser from './response_parser';
import { AzureMonitorQuery, AzureDataSourceJsonData } from '../types'; import { AzureMonitorQuery, AzureDataSourceJsonData, AzureLogsVariable } from '../types';
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data'; import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
...@@ -47,7 +47,7 @@ export default class AzureLogAnalyticsDatasource { ...@@ -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 subscriptionId = this.templateSrv.replace(subscription || this.subscriptionId);
const workspaceListUrl = const workspaceListUrl =
...@@ -118,6 +118,16 @@ export default class AzureLogAnalyticsDatasource { ...@@ -118,6 +118,16 @@ export default class AzureLogAnalyticsDatasource {
} }
metricFindQuery(query: string) { 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) => { return this.getDefaultOrFirstWorkspace().then((workspace: any) => {
const queries: any[] = this.buildQuery(query, null, workspace); const queries: any[] = this.buildQuery(query, null, workspace);
......
...@@ -160,7 +160,7 @@ describe('AzureMonitorDatasource', () => { ...@@ -160,7 +160,7 @@ describe('AzureMonitorDatasource', () => {
}; };
beforeEach(() => { beforeEach(() => {
datasourceRequestMock.mockImplementation((options: { url: string }) => Promise.resolve(response)); datasourceRequestMock.mockImplementation(() => Promise.resolve(response));
}); });
it('should return a list of subscriptions', () => { 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