From 3b9a4e6444a8c8a500f9dc88fb808c355e8c145a Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 19 Mar 2020 00:41:35 +0100 Subject: [PATCH] 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> --- .../features/datasources/azuremonitor.md | 39 +++++ .../azure_log_analytics_datasource.test.ts | 144 +++++++++++++----- .../azure_log_analytics_datasource.ts | 14 +- .../azure_monitor_datasource.test.ts | 2 +- 4 files changed, 162 insertions(+), 37 deletions(-) diff --git a/docs/sources/features/datasources/azuremonitor.md b/docs/sources/features/datasources/azuremonitor.md index 875c0b94f74..3386028ccba 100755 --- a/docs/sources/features/datasources/azuremonitor.md +++ b/docs/sources/features/datasources/azuremonitor.md @@ -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: + + +| 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 | + + + +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. diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts index 5203dbe4bdc..72b4076c95b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts @@ -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'); + }); }); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts index 903a898ea0e..dba230aaffa 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts @@ -1,7 +1,7 @@ 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 { 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); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts index bd3d64e5b43..bdd7a9fa606 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts @@ -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', () => {