Commit 88051258 by Erik Sundell Committed by GitHub

Azure Monitor: Add support for cross resource queries (#19115)

* Add new query mode picker with different states for each query. Also really simple migration script

* Populate cross resource dropdowns

* Cleanup. Handle change events

* Add multi select picker for subscriptions

* Fix markup issue

* Prepare for new query mode

* More cleanup

* Handle multiple queries both in ds and backend

* Refactoring

* Improve migration

* Add support for multiselect display name

* Use multiselect also for locations and resources

* Add more typings

* Fix migrations

* Custom multiselect built for array of options instead of variables

* Add url builder test

* fix datasource tests

* UI fixes

* Improve query editor init

* Fix brokens tests

* Cleanup

* Fix tslint issue

* Change query mode display name

* Make sure alerting works for single queries

* Friendly error for multi resources

* Add temporary typings
parent b5f0a5d5
...@@ -60,7 +60,11 @@ func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, ori ...@@ -60,7 +60,11 @@ func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, ori
if err != nil { if err != nil {
queryRes.Error = err queryRes.Error = err
} }
result.Results[query.RefID] = queryRes if val, ok := result.Results[query.RefID]; ok {
val.Series = append(result.Results[query.RefID].Series, queryRes.Series...)
} else {
result.Results[query.RefID] = queryRes
}
} }
return result, nil return result, nil
...@@ -84,11 +88,22 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * ...@@ -84,11 +88,22 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() azureMonitorTarget := query.Model.Get("azureMonitor").MustMap()
azlog.Debug("AzureMonitor", "target", azureMonitorTarget) azlog.Debug("AzureMonitor", "target", azureMonitorTarget)
queryMode := fmt.Sprintf("%v", azureMonitorTarget["queryMode"])
if queryMode == "crossResource" {
return nil, fmt.Errorf("Alerting not supported for multiple resource queries")
}
var azureMonitorData map[string]interface{}
if queryMode == "singleResource" {
azureMonitorData = azureMonitorTarget["data"].(map[string]interface{})[queryMode].(map[string]interface{})
} else {
azureMonitorData = azureMonitorTarget
}
urlComponents := map[string]string{} urlComponents := map[string]string{}
urlComponents["subscription"] = fmt.Sprintf("%v", query.Model.Get("subscription").MustString()) urlComponents["subscription"] = fmt.Sprintf("%v", query.Model.Get("subscription").MustString())
urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"]) urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorData["resourceGroup"])
urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"]) urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorData["metricDefinition"])
urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"]) urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorData["resourceName"])
ub := urlBuilder{ ub := urlBuilder{
DefaultSubscription: query.DataSource.JsonData.Get("subscriptionId").MustString(), DefaultSubscription: query.DataSource.JsonData.Get("subscriptionId").MustString(),
...@@ -100,12 +115,12 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * ...@@ -100,12 +115,12 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
azureURL := ub.Build() azureURL := ub.Build()
alias := "" alias := ""
if val, ok := azureMonitorTarget["alias"]; ok { if val, ok := azureMonitorData["alias"]; ok {
alias = fmt.Sprintf("%v", val) alias = fmt.Sprintf("%v", val)
} }
timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"]) timeGrain := fmt.Sprintf("%v", azureMonitorData["timeGrain"])
timeGrains := azureMonitorTarget["allowedTimeGrainsMs"] timeGrains := azureMonitorData["allowedTimeGrainsMs"]
if timeGrain == "auto" { if timeGrain == "auto" {
timeGrain, err = e.setAutoTimeGrain(query.IntervalMs, timeGrains) timeGrain, err = e.setAutoTimeGrain(query.IntervalMs, timeGrains)
if err != nil { if err != nil {
...@@ -117,13 +132,16 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * ...@@ -117,13 +132,16 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
params.Add("api-version", "2018-01-01") params.Add("api-version", "2018-01-01")
params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339))) params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339)))
params.Add("interval", timeGrain) params.Add("interval", timeGrain)
params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"])) params.Add("aggregation", fmt.Sprintf("%v", azureMonitorData["aggregation"]))
params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"])) params.Add("metricnames", fmt.Sprintf("%v", azureMonitorData["metricName"]))
params.Add("metricnamespace", fmt.Sprintf("%v", azureMonitorTarget["metricNamespace"]))
if val, ok := azureMonitorData["metricNamespace"]; ok {
params.Add("metricnamespace", fmt.Sprintf("%v", val))
}
dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimension"])) dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorData["dimension"]))
dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"])) dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorData["dimensionFilter"]))
if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && len(dimension) > 0 && len(dimensionFilter) > 0 && dimension != "None" { if azureMonitorData["dimension"] != nil && azureMonitorData["dimensionFilter"] != nil && len(dimension) > 0 && len(dimensionFilter) > 0 && dimension != "None" {
params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter)) params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
} }
......
...@@ -36,15 +36,20 @@ func TestAzureMonitorDatasource(t *testing.T) { ...@@ -36,15 +36,20 @@ func TestAzureMonitorDatasource(t *testing.T) {
Model: simplejson.NewFromAny(map[string]interface{}{ Model: simplejson.NewFromAny(map[string]interface{}{
"subscription": "12345678-aaaa-bbbb-cccc-123456789abc", "subscription": "12345678-aaaa-bbbb-cccc-123456789abc",
"azureMonitor": map[string]interface{}{ "azureMonitor": map[string]interface{}{
"timeGrain": "PT1M", "queryMode": "singleResource",
"aggregation": "Average", "data": map[string]interface{}{
"resourceGroup": "grafanastaging", "singleResource": map[string]interface{}{
"resourceName": "grafana", "timeGrain": "PT1M",
"metricDefinition": "Microsoft.Compute/virtualMachines", "aggregation": "Average",
"metricNamespace": "Microsoft.Compute-virtualMachines", "resourceGroup": "grafanastaging",
"metricName": "Percentage CPU", "resourceName": "grafana",
"alias": "testalias", "metricDefinition": "Microsoft.Compute/virtualMachines",
"queryType": "Azure Monitor", "metricNamespace": "Microsoft.Compute-virtualMachines",
"metricName": "Percentage CPU",
"alias": "testalias",
"queryType": "Azure Monitor",
},
},
}, },
}), }),
RefId: "A", RefId: "A",
......
...@@ -918,8 +918,8 @@ describe('AzureMonitorDatasource', () => { ...@@ -918,8 +918,8 @@ describe('AzureMonitorDatasource', () => {
'nodeapp', 'nodeapp',
'microsoft.insights/components', 'microsoft.insights/components',
'resource1', 'resource1',
'default', 'UsedCapacity',
'UsedCapacity' 'default'
) )
.then((results: any) => { .then((results: any) => {
expect(results.primaryAggType).toEqual('Total'); expect(results.primaryAggType).toEqual('Total');
...@@ -992,8 +992,8 @@ describe('AzureMonitorDatasource', () => { ...@@ -992,8 +992,8 @@ describe('AzureMonitorDatasource', () => {
'nodeapp', 'nodeapp',
'microsoft.insights/components', 'microsoft.insights/components',
'resource1', 'resource1',
'default', 'Transactions',
'Transactions' 'default'
) )
.then((results: any) => { .then((results: any) => {
expect(results.dimensions.length).toEqual(4); expect(results.dimensions.length).toEqual(4);
...@@ -1011,8 +1011,8 @@ describe('AzureMonitorDatasource', () => { ...@@ -1011,8 +1011,8 @@ describe('AzureMonitorDatasource', () => {
'nodeapp', 'nodeapp',
'microsoft.insights/components', 'microsoft.insights/components',
'resource1', 'resource1',
'default', 'FreeCapacity',
'FreeCapacity' 'default'
) )
.then((results: any) => { .then((results: any) => {
expect(results.dimensions.length).toEqual(0); expect(results.dimensions.length).toEqual(0);
......
...@@ -5,9 +5,12 @@ import SupportedNamespaces from './supported_namespaces'; ...@@ -5,9 +5,12 @@ import SupportedNamespaces from './supported_namespaces';
import TimegrainConverter from '../time_grain_converter'; import TimegrainConverter from '../time_grain_converter';
import { import {
AzureMonitorQuery, AzureMonitorQuery,
AzureMonitorQueryData,
AzureDataSourceJsonData, AzureDataSourceJsonData,
AzureMonitorMetricDefinitionsResponse, AzureMonitorMetricDefinitionsResponse,
AzureMonitorResourceGroupsResponse, AzureMonitorResourceGroupsResponse,
AzureMonitorResourceResponse,
Resource,
} from '../types'; } from '../types';
import { DataQueryRequest, DataQueryResponseData, DataSourceInstanceSettings } from '@grafana/ui'; import { DataQueryRequest, DataQueryResponseData, DataSourceInstanceSettings } from '@grafana/ui';
...@@ -47,60 +50,172 @@ export default class AzureMonitorDatasource { ...@@ -47,60 +50,172 @@ export default class AzureMonitorDatasource {
return !!this.subscriptionId && this.subscriptionId.length > 0; return !!this.subscriptionId && this.subscriptionId.length > 0;
} }
async query(options: DataQueryRequest<AzureMonitorQuery>): Promise<DataQueryResponseData[]> { buildQuery(
const queries = _.filter(options.targets, item => { options: DataQueryRequest<AzureMonitorQuery>,
return ( target: any,
item.hide !== true && {
item.azureMonitor.resourceGroup && resourceGroup,
item.azureMonitor.resourceGroup !== this.defaultDropdownValue && resourceName,
item.azureMonitor.resourceName && metricDefinition,
item.azureMonitor.resourceName !== this.defaultDropdownValue && timeGrainUnit,
item.azureMonitor.metricDefinition && timeGrain,
item.azureMonitor.metricDefinition !== this.defaultDropdownValue && metricName,
item.azureMonitor.metricName && metricNamespace,
item.azureMonitor.metricName !== this.defaultDropdownValue allowedTimeGrainsMs,
); aggregation,
}).map(target => { dimension,
const item = target.azureMonitor; dimensionFilter,
alias,
}: AzureMonitorQueryData,
subscriptionId?: string
) {
if (timeGrainUnit && timeGrain !== 'auto') {
timeGrain = TimegrainConverter.createISO8601Duration(timeGrain, timeGrainUnit);
}
// fix for timeGrainUnit which is a deprecated/removed field name const metricNamespaceParsed = this.templateSrv.replace(metricNamespace, options.scopedVars);
if (item.timeGrainUnit && item.timeGrain !== 'auto') {
item.timeGrain = TimegrainConverter.createISO8601Duration(item.timeGrain, item.timeGrainUnit); return {
} refId: target.refId,
intervalMs: options.intervalMs,
datasourceId: this.id,
subscription: this.templateSrv.replace(
subscriptionId || target.subscription || this.subscriptionId,
options.scopedVars
),
queryType: 'Azure Monitor',
type: 'timeSeriesQuery',
raw: false,
azureMonitor: {
resourceGroup: this.templateSrv.replace(resourceGroup, options.scopedVars),
resourceName: this.templateSrv.replace(resourceName, options.scopedVars),
metricDefinition: this.templateSrv.replace(metricDefinition, options.scopedVars),
timeGrain: this.templateSrv.replace((timeGrain || '').toString(), options.scopedVars),
allowedTimeGrainsMs: allowedTimeGrainsMs,
metricName: this.templateSrv.replace(metricName, options.scopedVars),
metricNamespace:
metricNamespaceParsed && metricNamespaceParsed !== this.defaultDropdownValue
? metricNamespaceParsed
: metricDefinition,
aggregation: this.templateSrv.replace(aggregation, options.scopedVars),
dimension: this.templateSrv.replace(dimension, options.scopedVars),
dimensionFilter: this.templateSrv.replace(dimensionFilter, options.scopedVars),
alias,
format: target.format,
},
};
}
const subscriptionId = this.templateSrv.replace(target.subscription || this.subscriptionId, options.scopedVars); buildSingleQuery(
const resourceGroup = this.templateSrv.replace(item.resourceGroup, options.scopedVars); options: DataQueryRequest<AzureMonitorQuery>,
const resourceName = this.templateSrv.replace(item.resourceName, options.scopedVars); target: any,
const metricNamespace = this.templateSrv.replace(item.metricNamespace, options.scopedVars); {
const metricDefinition = this.templateSrv.replace(item.metricDefinition, options.scopedVars); resourceGroup,
const timeGrain = this.templateSrv.replace((item.timeGrain || '').toString(), options.scopedVars); resourceName,
const aggregation = this.templateSrv.replace(item.aggregation, options.scopedVars); metricDefinition,
timeGrainUnit,
timeGrain,
metricName,
metricNamespace,
allowedTimeGrainsMs,
aggregation,
dimension,
dimensionFilter,
alias,
}: AzureMonitorQueryData,
queryMode: string
) {
if (timeGrainUnit && timeGrain !== 'auto') {
timeGrain = TimegrainConverter.createISO8601Duration(timeGrain, timeGrainUnit);
}
return { const metricNamespaceParsed = this.templateSrv.replace(metricNamespace, options.scopedVars);
refId: target.refId,
intervalMs: options.intervalMs, return {
datasourceId: this.id, refId: target.refId,
subscription: subscriptionId, intervalMs: options.intervalMs,
queryType: 'Azure Monitor', datasourceId: this.id,
type: 'timeSeriesQuery', subscription: this.templateSrv.replace(target.subscription || this.subscriptionId, options.scopedVars),
raw: false, queryType: 'Azure Monitor',
azureMonitor: { type: 'timeSeriesQuery',
resourceGroup: resourceGroup, raw: false,
resourceName: resourceName, azureMonitor: {
metricDefinition: metricDefinition, queryMode,
timeGrain: timeGrain, data: {
allowedTimeGrainsMs: item.allowedTimeGrainsMs, [queryMode]: {
metricName: this.templateSrv.replace(item.metricName, options.scopedVars), resourceGroup: this.templateSrv.replace(resourceGroup, options.scopedVars),
metricNamespace: resourceName: this.templateSrv.replace(resourceName, options.scopedVars),
metricNamespace && metricNamespace !== this.defaultDropdownValue ? metricNamespace : metricDefinition, metricDefinition: this.templateSrv.replace(metricDefinition, options.scopedVars),
aggregation: aggregation, timeGrain: this.templateSrv.replace((timeGrain || '').toString(), options.scopedVars),
dimension: this.templateSrv.replace(item.dimension, options.scopedVars), allowedTimeGrainsMs: allowedTimeGrainsMs,
dimensionFilter: this.templateSrv.replace(item.dimensionFilter, options.scopedVars), metricName: this.templateSrv.replace(metricName, options.scopedVars),
alias: item.alias, metricNamespace:
format: target.format, metricNamespaceParsed && metricNamespaceParsed !== this.defaultDropdownValue
? metricNamespaceParsed
: metricDefinition,
aggregation: this.templateSrv.replace(aggregation, options.scopedVars),
dimension: this.templateSrv.replace(dimension, options.scopedVars),
dimensionFilter: this.templateSrv.replace(dimensionFilter, options.scopedVars),
alias,
format: target.format,
},
}, },
}; },
}); };
}
async query(options: DataQueryRequest<any>): Promise<DataQueryResponseData[]> {
const groupedQueries: any[] = await Promise.all(
options.targets
.filter(item => {
const { data, queryMode } = item.azureMonitor;
const { resourceGroup, resourceGroups, metricDefinition, metricName } = data[queryMode];
return (
item.hide !== true &&
((resourceGroup && resourceGroup !== this.defaultDropdownValue) || resourceGroups.length) &&
metricDefinition &&
metricDefinition !== this.defaultDropdownValue &&
metricName &&
metricName !== this.defaultDropdownValue
);
})
.map(async target => {
const { data, queryMode } = target.azureMonitor;
if (queryMode === 'crossResource') {
const { resourceGroups, metricDefinition, locations } = data[queryMode];
const resources = await this.getResources(target.subscriptions).then(resources =>
resources.filter(
({ type, group, subscriptionId, location }) =>
target.subscriptions.includes(subscriptionId) &&
resourceGroups.includes(group) &&
locations.includes(location) &&
metricDefinition === type
)
);
delete data.crossResource.metricNamespace;
return resources.map(
({ type: metricDefinition, group: resourceGroup, subscriptionId, name: resourceName }) =>
this.buildQuery(
options,
target,
{
...data[queryMode],
metricDefinition,
resourceGroup,
resourceName,
},
subscriptionId
)
);
} else {
return Promise.resolve(this.buildSingleQuery(options, target, data[queryMode], queryMode));
}
})
);
const queries = _.flatten(groupedQueries);
if (!queries || queries.length === 0) { if (!queries || queries.length === 0) {
return Promise.resolve([]); return Promise.resolve([]);
...@@ -118,7 +233,7 @@ export default class AzureMonitorDatasource { ...@@ -118,7 +233,7 @@ export default class AzureMonitorDatasource {
const result: DataQueryResponseData[] = []; const result: DataQueryResponseData[] = [];
if (data.results) { if (data.results) {
Object['values'](data.results).forEach((queryRes: any) => { Object.values(data.results).forEach((queryRes: any) => {
if (!queryRes.series) { if (!queryRes.series) {
return; return;
} }
...@@ -337,12 +452,31 @@ export default class AzureMonitorDatasource { ...@@ -337,12 +452,31 @@ export default class AzureMonitorDatasource {
}); });
} }
async getResources(subscriptionIds: string[]): Promise<Resource[]> {
const responses: Resource[][] = await Promise.all(
subscriptionIds.map(subscriptionId =>
this.doRequest(`${this.baseUrl}/${subscriptionId}/resources?api-version=2018-02-01`).then(
(res: AzureMonitorResourceResponse) =>
res.data.value
.map(r => ({
...r,
group: /.*\/resourceGroups\/(.*?)\//.exec(r.id)[1],
subscriptionId,
}))
.filter(({ type }) => this.supportedMetricNamespaces.includes(type))
)
)
);
return responses.reduce((result, resources) => [...result, ...resources], []);
}
getMetricNames( getMetricNames(
subscriptionId: string, subscriptionId: string,
resourceGroup: string, resourceGroup: string,
metricDefinition: string, metricDefinition: string,
resourceName: string, resourceName: string,
metricNamespace: string metricNamespace?: string
) { ) {
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
this.baseUrl, this.baseUrl,
...@@ -350,8 +484,8 @@ export default class AzureMonitorDatasource { ...@@ -350,8 +484,8 @@ export default class AzureMonitorDatasource {
resourceGroup, resourceGroup,
metricDefinition, metricDefinition,
resourceName, resourceName,
metricNamespace, this.apiVersion,
this.apiVersion metricNamespace
); );
return this.doRequest(url).then((result: any) => { return this.doRequest(url).then((result: any) => {
...@@ -364,8 +498,8 @@ export default class AzureMonitorDatasource { ...@@ -364,8 +498,8 @@ export default class AzureMonitorDatasource {
resourceGroup: string, resourceGroup: string,
metricDefinition: string, metricDefinition: string,
resourceName: string, resourceName: string,
metricNamespace: string, metricName: string,
metricName: string metricNamespace?: string
) { ) {
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
this.baseUrl, this.baseUrl,
...@@ -373,8 +507,8 @@ export default class AzureMonitorDatasource { ...@@ -373,8 +507,8 @@ export default class AzureMonitorDatasource {
resourceGroup, resourceGroup,
metricDefinition, metricDefinition,
resourceName, resourceName,
metricNamespace, this.apiVersion,
this.apiVersion metricNamespace
); );
return this.doRequest(url).then((result: any) => { return this.doRequest(url).then((result: any) => {
......
...@@ -108,8 +108,8 @@ export default class ResponseParser { ...@@ -108,8 +108,8 @@ export default class ResponseParser {
return dimensions; return dimensions;
} }
static parseSubscriptions(result: any): Array<{ text: string; value: string }> { static parseSubscriptions(result: any): Array<{ text: string; value: string; displayName: string }> {
const list: Array<{ text: string; value: string }> = []; const list: Array<{ text: string; value: string; displayName: string }> = [];
if (!result) { if (!result) {
return list; return list;
...@@ -122,6 +122,7 @@ export default class ResponseParser { ...@@ -122,6 +122,7 @@ export default class ResponseParser {
list.push({ list.push({
text: `${_.get(result.data.value[i], textFieldName)} - ${_.get(result.data.value[i], valueFieldName)}`, text: `${_.get(result.data.value[i], textFieldName)} - ${_.get(result.data.value[i], valueFieldName)}`,
value: _.get(result.data.value[i], valueFieldName), value: _.get(result.data.value[i], valueFieldName),
displayName: _.get(result.data.value[i], textFieldName),
}); });
} }
} }
......
...@@ -9,8 +9,8 @@ describe('AzureMonitorUrlBuilder', () => { ...@@ -9,8 +9,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg', 'rg',
'Microsoft.Sql/servers/databases', 'Microsoft.Sql/servers/databases',
'rn1/rn2', 'rn1/rn2',
'default', '2017-05-01-preview',
'2017-05-01-preview' 'default'
); );
expect(url).toBe( expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' + '/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' +
...@@ -27,8 +27,8 @@ describe('AzureMonitorUrlBuilder', () => { ...@@ -27,8 +27,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg', 'rg',
'Microsoft.Sql/servers', 'Microsoft.Sql/servers',
'rn', 'rn',
'default', '2017-05-01-preview',
'2017-05-01-preview' 'default'
); );
expect(url).toBe( expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' + '/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' +
...@@ -45,8 +45,8 @@ describe('AzureMonitorUrlBuilder', () => { ...@@ -45,8 +45,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg', 'rg',
'Microsoft.Storage/storageAccounts/blobServices', 'Microsoft.Storage/storageAccounts/blobServices',
'rn1/default', 'rn1/default',
'default', '2017-05-01-preview',
'2017-05-01-preview' 'default'
); );
expect(url).toBe( expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/' + '/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/' +
...@@ -63,8 +63,8 @@ describe('AzureMonitorUrlBuilder', () => { ...@@ -63,8 +63,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg', 'rg',
'Microsoft.Storage/storageAccounts/fileServices', 'Microsoft.Storage/storageAccounts/fileServices',
'rn1/default', 'rn1/default',
'default', '2017-05-01-preview',
'2017-05-01-preview' 'default'
); );
expect(url).toBe( expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/' + '/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/' +
...@@ -81,8 +81,8 @@ describe('AzureMonitorUrlBuilder', () => { ...@@ -81,8 +81,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg', 'rg',
'Microsoft.Storage/storageAccounts/tableServices', 'Microsoft.Storage/storageAccounts/tableServices',
'rn1/default', 'rn1/default',
'default', '2017-05-01-preview',
'2017-05-01-preview' 'default'
); );
expect(url).toBe( expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/tableServices/default/' + '/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/tableServices/default/' +
...@@ -99,8 +99,8 @@ describe('AzureMonitorUrlBuilder', () => { ...@@ -99,8 +99,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg', 'rg',
'Microsoft.Storage/storageAccounts/queueServices', 'Microsoft.Storage/storageAccounts/queueServices',
'rn1/default', 'rn1/default',
'default', '2017-05-01-preview',
'2017-05-01-preview' 'default'
); );
expect(url).toBe( expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/queueServices/default/' + '/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/queueServices/default/' +
...@@ -108,4 +108,22 @@ describe('AzureMonitorUrlBuilder', () => { ...@@ -108,4 +108,22 @@ describe('AzureMonitorUrlBuilder', () => {
); );
}); });
}); });
describe('when metric namespace is missing', () => {
it('should be excluded from the query', () => {
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
'',
'sub1',
'rg',
'Microsoft.Storage/storageAccounts/queueServices',
'rn1/default',
'2017-05-01-preview'
);
expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/queueServices/default/' +
'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview'
);
});
});
}); });
...@@ -29,26 +29,24 @@ export default class UrlBuilder { ...@@ -29,26 +29,24 @@ export default class UrlBuilder {
resourceGroup: string, resourceGroup: string,
metricDefinition: string, metricDefinition: string,
resourceName: string, resourceName: string,
metricNamespace: string, apiVersion: string,
apiVersion: string metricNamespace?: string
) { ) {
const metricNameSpaceParam = metricNamespace ? `&metricnamespace=${encodeURIComponent(metricNamespace)}` : '';
if ((metricDefinition.match(/\//g) || []).length > 1) { if ((metricDefinition.match(/\//g) || []).length > 1) {
const rn = resourceName.split('/'); const rn = resourceName.split('/');
const service = metricDefinition.substring(metricDefinition.lastIndexOf('/') + 1); const service = metricDefinition.substring(metricDefinition.lastIndexOf('/') + 1);
const md = metricDefinition.substring(0, metricDefinition.lastIndexOf('/')); const md = metricDefinition.substring(0, metricDefinition.lastIndexOf('/'));
return ( return (
`${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${md}/${rn[0]}/${service}/${rn[1]}` + `${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${md}/${rn[0]}/${service}/${rn[1]}` +
`/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}&metricnamespace=${encodeURIComponent( `/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}${metricNameSpaceParam}`
metricNamespace
)}`
); );
} }
return ( return (
`${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${metricDefinition}/${resourceName}` + `${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${metricDefinition}/${resourceName}` +
`/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}&metricnamespace=${encodeURIComponent( `/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}${metricNameSpaceParam}`
metricNamespace
)}`
); );
} }
} }
import _ from 'lodash'; import _ from 'lodash';
import { migrateTargetSchema } from './migrations';
import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource'; import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource';
import AppInsightsDatasource from './app_insights/app_insights_datasource'; import AppInsightsDatasource from './app_insights/app_insights_datasource';
import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource'; import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
...@@ -42,7 +43,9 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa ...@@ -42,7 +43,9 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
const appInsightsOptions = _.cloneDeep(options); const appInsightsOptions = _.cloneDeep(options);
const azureLogAnalyticsOptions = _.cloneDeep(options); const azureLogAnalyticsOptions = _.cloneDeep(options);
azureMonitorOptions.targets = _.filter(azureMonitorOptions.targets, ['queryType', 'Azure Monitor']); azureMonitorOptions.targets = azureMonitorOptions.targets
.filter((t: any) => t.queryType === 'Azure Monitor')
.map((t: any) => migrateTargetSchema(t));
appInsightsOptions.targets = _.filter(appInsightsOptions.targets, ['queryType', 'Application Insights']); appInsightsOptions.targets = _.filter(appInsightsOptions.targets, ['queryType', 'Application Insights']);
azureLogAnalyticsOptions.targets = _.filter(azureLogAnalyticsOptions.targets, ['queryType', 'Azure Log Analytics']); azureLogAnalyticsOptions.targets = _.filter(azureLogAnalyticsOptions.targets, ['queryType', 'Azure Log Analytics']);
...@@ -163,7 +166,7 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa ...@@ -163,7 +166,7 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
resourceGroup: string, resourceGroup: string,
metricDefinition: string, metricDefinition: string,
resourceName: string, resourceName: string,
metricNamespace: string metricNamespace?: string
) { ) {
return this.azureMonitorDatasource.getMetricNames( return this.azureMonitorDatasource.getMetricNames(
subscriptionId, subscriptionId,
...@@ -188,19 +191,23 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa ...@@ -188,19 +191,23 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
resourceGroup: string, resourceGroup: string,
metricDefinition: string, metricDefinition: string,
resourceName: string, resourceName: string,
metricNamespace: string, metricName: string,
metricName: string metricNamespace?: string
) { ) {
return this.azureMonitorDatasource.getMetricMetadata( return this.azureMonitorDatasource.getMetricMetadata(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
metricDefinition, metricDefinition,
resourceName, resourceName,
metricNamespace, metricName,
metricName metricNamespace
); );
} }
getResources(subscriptions: string[]) {
return this.azureMonitorDatasource.getResources(subscriptions);
}
/* Application Insights API method */ /* Application Insights API method */
getAppInsightsMetricNames() { getAppInsightsMetricNames() {
return this.appInsightsDatasource.getMetricNames(); return this.appInsightsDatasource.getMetricNames();
......
import { AzureMonitorQueryCtrl } from './query_ctrl';
export function migrateTargetSchema(target: any) {
if (target.azureMonitor && !target.azureMonitor.data) {
const temp = { ...target.azureMonitor };
target.azureMonitor = {
queryMode: AzureMonitorQueryCtrl.defaultQueryMode,
data: {
[AzureMonitorQueryCtrl.defaultQueryMode]: temp,
},
};
}
return target;
}
import angular from 'angular';
import _ from 'lodash';
export class MultiSelectDropdownCtrl {
dropdownVisible: boolean;
highlightIndex: number;
linkText: string;
options: Array<{ selected: boolean; text: string; value: string }>;
selectedValues: Array<{ text: string; value: string }>;
initialValues: string[];
onUpdated: any;
show() {
this.highlightIndex = -1;
this.options = this.options;
this.selectedValues = this.options.filter(({ selected }) => selected);
this.dropdownVisible = true;
}
hide() {
this.dropdownVisible = false;
}
updateLinkText() {
this.linkText =
this.selectedValues.length === 1 ? this.selectedValues[0].text : `(${this.selectedValues.length}) selected`;
}
clearSelections() {
this.selectedValues = _.filter(this.options, { selected: true });
if (this.selectedValues.length > 1) {
_.each(this.options, option => {
option.selected = false;
});
} else {
_.each(this.options, option => {
option.selected = true;
});
}
this.selectionsChanged();
}
selectValue(option: any) {
if (!option) {
return;
}
option.selected = !option.selected;
this.selectionsChanged();
}
selectionsChanged() {
this.selectedValues = _.filter(this.options, { selected: true });
if (!this.selectedValues.length && this.options.length) {
this.selectedValues = this.options.slice(0, 1);
}
this.updateLinkText();
this.onUpdated({ values: this.selectedValues.map(({ value }) => value) });
}
onClickOutside() {
this.selectedValues = _.filter(this.options, { selected: true });
if (this.selectedValues.length === 0) {
this.options[0].selected = true;
this.selectionsChanged();
}
this.dropdownVisible = false;
}
init() {
if (!this.options) {
return;
}
this.options = this.options.map(o => ({
...o,
selected: this.initialValues.includes(o.value),
}));
this.selectedValues = _.filter(this.options, { selected: true });
if (!this.selectedValues.length) {
this.options = this.options.map(o => ({
...o,
selected: true,
}));
}
this.updateLinkText();
}
updateSelection() {
this.selectedValues = _.filter(this.options, { selected: true });
if (!this.selectedValues.length && this.options.length) {
this.options = this.options.map(o => ({
...o,
selected: true,
}));
this.selectedValues = _.filter(this.options, { selected: true });
this.selectionsChanged();
}
this.updateLinkText();
}
}
/** @ngInject */
export function multiSelectDropdown($window: any, $timeout: any) {
return {
scope: { onUpdated: '&', options: '=', initialValues: '=' },
templateUrl: 'public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/multi-select.directive.html',
controller: MultiSelectDropdownCtrl,
controllerAs: 'vm',
bindToController: true,
link: (scope: any, elem: any) => {
const bodyEl = angular.element($window.document.body);
const linkEl = elem.find('.variable-value-link');
const inputEl = elem.find('input');
function openDropdown() {
inputEl.css('width', Math.max(linkEl.width(), 80) + 'px');
inputEl.show();
linkEl.hide();
inputEl.focus();
$timeout(
() => {
bodyEl.on('click', () => {
bodyEl.on('click', bodyOnClick);
});
},
0,
false
);
}
function switchToLink() {
inputEl.hide();
linkEl.show();
bodyEl.off('click', bodyOnClick);
}
function bodyOnClick(e: any) {
if (elem.has(e.target).length === 0) {
scope.$apply(() => {
scope.vm.onClickOutside();
});
}
}
scope.$watch('vm.options', (newValue: any) => {
if (newValue) {
scope.vm.updateSelection(newValue);
}
});
scope.$watch('vm.dropdownVisible', (newValue: any) => {
if (newValue) {
openDropdown();
} else {
switchToLink();
}
});
scope.vm.init();
},
};
}
angular.module('grafana.directives').directive('multiSelect', multiSelectDropdown);
<div class="variable-link-wrapper">
<a ng-click="vm.show()" class="variable-value-link">
{{vm.linkText}}
<i class="fa fa-caret-down" style="font-size:12px"></i>
</a>
<input type="text" class="gf-form-input width-11" ng-model="vm.linkText" ng-change="vm.queryChanged()" ></input>
<div class="variable-value-dropdown multi" ng-if="vm.dropdownVisible">
<div class="variable-options-wrapper">
<div class="variable-options-column">
<a ng-if="vm.options.length > 1" class="variable-options-column-header" ng-class="{'many-selected': vm.selectedValues.length > 1}" bs-tooltip="'Clear selections'" data-placement="top" ng-click="vm.clearSelections()">
<span class="variable-option-icon"></span>
Selected ({{vm.selectedValues.length}})
</a>
<a class="variable-option pointer" ng-repeat="option in vm.options" ng-class="{'selected': option.selected, 'highlighted': $index === vm.highlightIndex}" ng-click="vm.selectValue(option, $event)">
<span class="variable-option-icon"></span>
<span>{{option.text}}</span>
</a>
</div>
</div>
</div>
</div>
\ No newline at end of file
...@@ -7,70 +7,138 @@ ...@@ -7,70 +7,138 @@
ng-change="ctrl.onQueryTypeChange()"></select> ng-change="ctrl.onQueryTypeChange()"></select>
</div> </div>
</div> </div>
<div class="gf-form" ng-if="ctrl.target.queryType === 'Azure Monitor' || ctrl.target.queryType === 'Azure Log Analytics'"> <div class="gf-form" ng-if="ctrl.target.queryType === 'Azure Monitor'">
<label class="gf-form-label query-keyword width-9">Query Mode</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input service-dropdown" ng-model="ctrl.target.azureMonitor.queryMode"
ng-options="f.value as f.text for f in [{value: 'singleResource', text: 'Single Resource'}, {value: 'crossResource', text: 'Multiple Resources'}]"
ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="(ctrl.target.queryType === 'Azure Monitor' && ctrl.target.azureMonitor.queryMode === 'singleResource') || ctrl.target.queryType === 'Azure Log Analytics'">
<div class="gf-form" >
<label class="gf-form-label query-keyword width-9">Subscription</label> <label class="gf-form-label query-keyword width-9">Subscription</label>
<gf-form-dropdown model="ctrl.target.subscription" allow-custom="true" lookup-text="true" <gf-form-dropdown model="ctrl.target.subscription" allow-custom="true" lookup-text="true"
get-options="ctrl.getSubscriptions()" on-change="ctrl.onSubscriptionChange()" css-class="min-width-12"> get-options="ctrl.getSubscriptions()" on-change="ctrl.onSubscriptionChange()" css-class="min-width-6">
</gf-form-dropdown> </gf-form-dropdown>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div> </div>
<div ng-if="ctrl.target.queryType === 'Azure Monitor'"> <div class="gf-form-inline" ng-if="(ctrl.target.queryType === 'Azure Monitor' && ctrl.target.azureMonitor.queryMode === 'crossResource')">
<div class="gf-form-inline"> <div class="gf-form">
<div class="gf-form"> <label class="gf-form-label query-keyword width-9">Subscriptions</label>
<label class="gf-form-label query-keyword width-9">Resource Group</label> <multi-select ng-if="ctrl.subscriptionValues.length"
<gf-form-dropdown model="ctrl.target.azureMonitor.resourceGroup" allow-custom="true" lookup-text="true" initial-values="ctrl.target.subscriptions"
get-options="ctrl.getResourceGroups($query)" on-change="ctrl.onResourceGroupChange()" css-class="min-width-12"> options="ctrl.subscriptionValues"
</gf-form-dropdown> on-updated="ctrl.onSubscriptionsChange(values)">
</div> </multi-select>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Namespace</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.metricDefinition" allow-custom="true" lookup-text="true"
get-options="ctrl.getMetricDefinitions($query)" on-change="ctrl.onMetricDefinitionChange()" css-class="min-width-20">
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Name</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.resourceName" allow-custom="true" lookup-text="true"
get-options="ctrl.getResourceNames($query)" on-change="ctrl.onResourceNameChange()" css-class="min-width-12">
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div> </div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.target.queryType === 'Azure Monitor' && ctrl.target.azureMonitor.queryMode === 'singleResource'">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Group</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].resourceGroup" allow-custom="true" lookup-text="true"
get-options="ctrl.getResourceGroups($query)" on-change="ctrl.onResourceGroupChange()" css-class="min-width-12">
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Namespace</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].metricDefinition" allow-custom="true" lookup-text="true"
get-options="ctrl.getMetricDefinitions($query)" on-change="ctrl.onMetricDefinitionChange()" css-class="min-width-20">
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Name</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].resourceName" allow-custom="true" lookup-text="true"
get-options="ctrl.getResourceNames($query)" on-change="ctrl.onResourceNameChange()" css-class="min-width-12">
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.target.azureMonitor.queryMode === 'crossResource'">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Locations</label>
<multi-select ng-if="ctrl.locations.length"
initial-values="ctrl.target.azureMonitor.data.crossResource.locations"
options="ctrl.locations"
on-updated="ctrl.onLocationsChange(values)">
</multi-select>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.target.azureMonitor.queryMode === 'crossResource'">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Groups</label>
<multi-select class="az-multi-picker"
initial-values="ctrl.target.azureMonitor.data.crossResource.resourceGroups"
options="ctrl.resourceGroups"
on-updated="ctrl.onCrossResourceGroupChange(values)">
</multi-select>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.target.azureMonitor.queryMode === 'crossResource'">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Type</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.data.crossResource.metricDefinition" allow-custom="true" lookup-text="true"
get-options="ctrl.getCrossResourceMetricDefinitions($query)" on-change="ctrl.onCrossResourceMetricDefinitionChange()" css-class="min-width-12">
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form" ng-if="ctrl.target.azureMonitor.queryMode === 'singleResource'">
<label class="gf-form-label query-keyword width-9">Metric Namespace</label> <label class="gf-form-label query-keyword width-9">Metric Namespace</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.metricNamespace" allow-custom="true" lookup-text="true" <gf-form-dropdown model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].metricNamespace" allow-custom="true" lookup-text="true"
get-options="ctrl.getMetricNamespaces($query)" on-change="ctrl.onMetricNamespacesChange()" css-class="min-width-12"> get-options="ctrl.getMetricNamespaces($query)" on-change="ctrl.onMetricNamespacesChange()" css-class="min-width-12">
</gf-form-dropdown> </gf-form-dropdown>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-9">Metric</label> <label class="gf-form-label query-keyword width-9">Metric</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.metricName" allow-custom="true" lookup-text="true" <gf-form-dropdown ng-if="ctrl.target.azureMonitor.queryMode === 'singleResource'" model="ctrl.target.azureMonitor.data.singleResource.metricName" allow-custom="true" lookup-text="true"
get-options="ctrl.getMetricNames($query)" on-change="ctrl.onMetricNameChange()" css-class="min-width-12"> get-options="ctrl.getMetricNames($query)" on-change="ctrl.onMetricNameChange()" css-class="min-width-12">
</gf-form-dropdown> </gf-form-dropdown>
<gf-form-dropdown ng-if="ctrl.target.azureMonitor.queryMode === 'crossResource'" model="ctrl.target.azureMonitor.data.crossResource.metricName" allow-custom="true" lookup-text="true"
get-options="ctrl.getCrossResourceMetricNames($query)" on-change="ctrl.onCrossResourceMetricNameChange()" css-class="min-width-12">
</gf-form-dropdown>
</div> </div>
<div class="gf-form gf-form--grow aggregation-dropdown-wrapper"> <div class="gf-form aggregation-dropdown-wrapper">
<label class="gf-form-label query-keyword width-9">Aggregation</label> <label class="gf-form-label query-keyword width-9">Aggregation</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent"> <div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input width-11" ng-model="ctrl.target.azureMonitor.aggregation" ng-options="f as f for f in ctrl.target.azureMonitor.aggOptions" <select class="gf-form-input width-11" ng-model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].aggregation" ng-options="f as f for f in ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].aggOptions"
ng-change="ctrl.refresh()"></select> ng-change="ctrl.refresh()"></select>
</div> </div>
</div> </div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div> </div>
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-9">Time Grain</label> <label class="gf-form-label query-keyword width-9">Time Grain</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent timegrainunit-dropdown-wrapper"> <div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent timegrainunit-dropdown-wrapper">
<select class="gf-form-input" ng-model="ctrl.target.azureMonitor.timeGrain" ng-options="f.value as f.text for f in ctrl.target.azureMonitor.timeGrains" <select class="gf-form-input" ng-model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].timeGrain" ng-options="f.value as f.text for f in ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].timeGrains"
ng-change="ctrl.refresh()"></select> ng-change="ctrl.refresh()"></select>
</div> </div>
</div> </div>
<div class="gf-form" ng-show="ctrl.target.azureMonitor.timeGrain.trim() === 'auto'"> <div class="gf-form" ng-show="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].timeGrain.trim() === 'auto'">
<label class="gf-form-label">Auto Interval</label> <label class="gf-form-label">Auto Interval</label>
<label class="gf-form-label">{{ctrl.getAutoInterval()}}</label> <label class="gf-form-label">{{ctrl.getAutoInterval()}}</label>
</div> </div>
...@@ -78,17 +146,17 @@ ...@@ -78,17 +146,17 @@
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div> </div>
<div class="gf-form-inline" ng-show="ctrl.target.azureMonitor.dimensions.length > 0"> <div class="gf-form-inline" ng-show="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].dimensions.length > 0">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-9">Dimension</label> <label class="gf-form-label query-keyword width-9">Dimension</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent"> <div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input min-width-12" ng-model="ctrl.target.azureMonitor.dimension" ng-options="f.value as f.text for f in ctrl.target.azureMonitor.dimensions" <select class="gf-form-input min-width-12" ng-model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].dimension" ng-options="f.value as f.text for f in ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].dimensions"
ng-change="ctrl.refresh()"></select> ng-change="ctrl.refresh()"></select>
</div> </div>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-3">eq</label> <label class="gf-form-label query-keyword width-3">eq</label>
<input type="text" class="gf-form-input width-17" ng-model="ctrl.target.azureMonitor.dimensionFilter" <input type="text" class="gf-form-input width-17" ng-model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].dimensionFilter"
spellcheck="false" placeholder="auto" ng-blur="ctrl.refresh()"> spellcheck="false" placeholder="auto" ng-blur="ctrl.refresh()">
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
...@@ -98,7 +166,7 @@ ...@@ -98,7 +166,7 @@
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-9">Legend Format</label> <label class="gf-form-label query-keyword width-9">Legend Format</label>
<input type="text" class="gf-form-input width-30" ng-model="ctrl.target.azureMonitor.alias" spellcheck="false" <input type="text" class="gf-form-input width-30" ng-model="ctrl.target.azureMonitor.data[ctrl.target.azureMonitor.queryMode].alias" spellcheck="false"
placeholder="alias patterns (see help for more info)" ng-blur="ctrl.refresh()"> placeholder="alias patterns (see help for more info)" ng-blur="ctrl.refresh()">
</div> </div>
......
...@@ -36,11 +36,11 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -36,11 +36,11 @@ describe('AzureMonitorQueryCtrl', () => {
}); });
it('should set query parts to select', () => { it('should set query parts to select', () => {
expect(queryCtrl.target.azureMonitor.resourceGroup).toBe('select'); expect(queryCtrl.target.azureMonitor.data.singleResource.resourceGroup).toBe('select');
expect(queryCtrl.target.azureMonitor.metricDefinition).toBe('select'); expect(queryCtrl.target.azureMonitor.data.singleResource.metricDefinition).toBe('select');
expect(queryCtrl.target.azureMonitor.resourceName).toBe('select'); expect(queryCtrl.target.azureMonitor.data.singleResource.resourceName).toBe('select');
expect(queryCtrl.target.azureMonitor.metricNamespace).toBe('select'); expect(queryCtrl.target.azureMonitor.data.singleResource.metricNamespace).toBe('select');
expect(queryCtrl.target.azureMonitor.metricName).toBe('select'); expect(queryCtrl.target.azureMonitor.data.singleResource.metricName).toBe('select');
expect(queryCtrl.target.appInsights.groupBy).toBe('none'); expect(queryCtrl.target.appInsights.groupBy).toBe('none');
}); });
}); });
...@@ -76,7 +76,7 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -76,7 +76,7 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => { beforeEach(() => {
queryCtrl.target.subscription = 'sub1'; queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test'; queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.datasource.getMetricDefinitions = function(subscriptionId: any, query: any) { queryCtrl.datasource.getMetricDefinitions = function(subscriptionId: any, query: any) {
expect(subscriptionId).toBe('sub1'); expect(subscriptionId).toBe('sub1');
expect(query).toBe('test'); expect(query).toBe('test');
...@@ -94,7 +94,7 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -94,7 +94,7 @@ describe('AzureMonitorQueryCtrl', () => {
describe('and resource group has no value', () => { describe('and resource group has no value', () => {
beforeEach(() => { beforeEach(() => {
queryCtrl.target.azureMonitor.resourceGroup = 'select'; queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'select';
}); });
it('should return without making a call to datasource', () => { it('should return without making a call to datasource', () => {
...@@ -109,8 +109,8 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -109,8 +109,8 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => { beforeEach(() => {
queryCtrl.target.subscription = 'sub1'; queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test'; queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines'; queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.datasource.getResourceNames = function( queryCtrl.datasource.getResourceNames = function(
subscriptionId: any, subscriptionId: any,
resourceGroup: any, resourceGroup: any,
...@@ -133,8 +133,8 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -133,8 +133,8 @@ describe('AzureMonitorQueryCtrl', () => {
describe('and resourceGroup and metricDefinition do not have values', () => { describe('and resourceGroup and metricDefinition do not have values', () => {
beforeEach(() => { beforeEach(() => {
queryCtrl.target.azureMonitor.resourceGroup = 'select'; queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'select';
queryCtrl.target.azureMonitor.metricDefinition = 'select'; queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'select';
}); });
it('should return without making a call to datasource', () => { it('should return without making a call to datasource', () => {
...@@ -149,10 +149,10 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -149,10 +149,10 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => { beforeEach(() => {
queryCtrl.target.subscription = 'sub1'; queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test'; queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines'; queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.resourceName = 'test'; queryCtrl.target.azureMonitor.data.singleResource.resourceName = 'test';
queryCtrl.target.azureMonitor.metricNamespace = 'test'; queryCtrl.target.azureMonitor.data.singleResource.metricNamespace = 'test';
queryCtrl.datasource.getMetricNames = function( queryCtrl.datasource.getMetricNames = function(
subscriptionId: any, subscriptionId: any,
resourceGroup: any, resourceGroup: any,
...@@ -179,10 +179,10 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -179,10 +179,10 @@ describe('AzureMonitorQueryCtrl', () => {
describe('and resourceGroup, metricDefinition, resourceName and metricNamespace do not have values', () => { describe('and resourceGroup, metricDefinition, resourceName and metricNamespace do not have values', () => {
beforeEach(() => { beforeEach(() => {
queryCtrl.target.azureMonitor.resourceGroup = 'select'; queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'select';
queryCtrl.target.azureMonitor.metricDefinition = 'select'; queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'select';
queryCtrl.target.azureMonitor.resourceName = 'select'; queryCtrl.target.azureMonitor.data.singleResource.resourceName = 'select';
queryCtrl.target.azureMonitor.metricNamespace = 'select'; queryCtrl.target.azureMonitor.data.singleResource.metricNamespace = 'select';
}); });
it('should return without making a call to datasource', () => { it('should return without making a call to datasource', () => {
...@@ -201,18 +201,18 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -201,18 +201,18 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => { beforeEach(() => {
queryCtrl.target.subscription = 'sub1'; queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test'; queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines'; queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.resourceName = 'test'; queryCtrl.target.azureMonitor.data.singleResource.resourceName = 'test';
queryCtrl.target.azureMonitor.metricNamespace = 'test'; queryCtrl.target.azureMonitor.data.singleResource.metricNamespace = 'test';
queryCtrl.target.azureMonitor.metricName = 'Percentage CPU'; queryCtrl.target.azureMonitor.data.singleResource.metricName = 'Percentage CPU';
queryCtrl.datasource.getMetricMetadata = function( queryCtrl.datasource.getMetricMetadata = function(
subscription: any, subscription: any,
resourceGroup: any, resourceGroup: any,
metricDefinition: any, metricDefinition: any,
resourceName: any, resourceName: any,
metricNamespace: any, metricName: any,
metricName: any metricNamespace: any
) { ) {
expect(subscription).toBe('sub1'); expect(subscription).toBe('sub1');
expect(resourceGroup).toBe('test'); expect(resourceGroup).toBe('test');
...@@ -226,9 +226,9 @@ describe('AzureMonitorQueryCtrl', () => { ...@@ -226,9 +226,9 @@ describe('AzureMonitorQueryCtrl', () => {
it('should set the options and default selected value for the Aggregations dropdown', () => { it('should set the options and default selected value for the Aggregations dropdown', () => {
queryCtrl.onMetricNameChange().then(() => { queryCtrl.onMetricNameChange().then(() => {
expect(queryCtrl.target.azureMonitor.aggregation).toBe('Average'); expect(queryCtrl.target.azureMonitor.data.singleResource.aggregation).toBe('Average');
expect(queryCtrl.target.azureMonitor.aggOptions).toBe(['Average', 'Total']); expect(queryCtrl.target.azureMonitor.data.singleResource.aggOptions).toBe(['Average', 'Total']);
expect(queryCtrl.target.azureMonitor.timeGrains).toBe(['PT1M', 'P1D']); expect(queryCtrl.target.azureMonitor.data.singleResource.timeGrains).toBe(['PT1M', 'P1D']);
}); });
}); });
}); });
......
import _ from 'lodash'; import _ from 'lodash';
import { QueryCtrl } from 'app/plugins/sdk'; import { QueryCtrl } from 'app/plugins/sdk';
// import './css/query_editor.css';
import TimegrainConverter from './time_grain_converter';
import './editor/editor_component';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
import { auto } from 'angular'; import { auto } from 'angular';
import { DataFrame } from '@grafana/data'; import { DataFrame } from '@grafana/data';
import { Resource } from './types';
import { migrateTargetSchema } from './migrations';
import TimegrainConverter from './time_grain_converter';
import './editor/editor_component';
import './multi-select.directive';
export interface ResultFormat { export interface ResultFormat {
text: string; text: string;
value: string; value: string;
} }
interface AzureMonitor {
resourceGroup: string;
resourceGroups: string[];
resourceName: string;
metricDefinition: string;
metricNamespace: string;
metricName: string;
dimensionFilter: string;
timeGrain: string;
timeGrainUnit: string;
timeGrains: Option[];
allowedTimeGrainsMs: number[];
dimensions: any[];
dimension: any;
aggregation: string;
aggOptions: string[];
locations: string[];
queryMode: string;
}
interface Option {
value: string;
text: string;
displayName?: string;
}
export class AzureMonitorQueryCtrl extends QueryCtrl { export class AzureMonitorQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html'; static templateUrl = 'partials/query.editor.html';
static defaultQueryMode = 'singleResource';
defaultDropdownValue = 'select'; defaultDropdownValue = 'select';
...@@ -23,21 +53,10 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -23,21 +53,10 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
refId: string; refId: string;
queryType: string; queryType: string;
subscription: string; subscription: string;
subscriptions: string[];
azureMonitor: { azureMonitor: {
resourceGroup: string; queryMode: string;
resourceName: string; data: { [queryMode: string]: AzureMonitor };
metricDefinition: string;
metricNamespace: string;
metricName: string;
dimensionFilter: string;
timeGrain: string;
timeGrainUnit: string;
timeGrains: Array<{ text: string; value: string }>;
allowedTimeGrainsMs: number[];
dimensions: any[];
dimension: any;
aggregation: string;
aggOptions: string[];
}; };
azureLogAnalytics: { azureLogAnalytics: {
query: string; query: string;
...@@ -63,14 +82,30 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -63,14 +82,30 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
defaults = { defaults = {
queryType: 'Azure Monitor', queryType: 'Azure Monitor',
subscriptions: new Array<string>(),
azureMonitor: { azureMonitor: {
resourceGroup: this.defaultDropdownValue, queryMode: 'singleResource',
metricDefinition: this.defaultDropdownValue, data: {
resourceName: this.defaultDropdownValue, singleResource: {
metricNamespace: this.defaultDropdownValue, resourceGroups: new Array<string>(),
metricName: this.defaultDropdownValue, resourceGroup: this.defaultDropdownValue,
dimensionFilter: '*', metricDefinition: this.defaultDropdownValue,
timeGrain: 'auto', metricNamespace: this.defaultDropdownValue,
metricName: this.defaultDropdownValue,
resourceName: this.defaultDropdownValue,
dimensionFilter: '*',
timeGrain: 'auto',
},
crossResource: {
resourceGroups: new Array<string>(),
locations: new Array<string>(),
metricDefinition: this.defaultDropdownValue,
resourceName: this.defaultDropdownValue,
metricName: this.defaultDropdownValue,
dimensionFilter: '*',
timeGrain: 'auto',
},
},
}, },
azureLogAnalytics: { azureLogAnalytics: {
query: [ query: [
...@@ -108,12 +143,17 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -108,12 +143,17 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
showLastQuery: boolean; showLastQuery: boolean;
lastQuery: string; lastQuery: string;
lastQueryError?: string; lastQueryError?: string;
subscriptions: Array<{ text: string; value: string }>; subscriptions: Option[];
subscriptionValues: string[];
resources: Resource[];
locations: Option[];
resourceGroups: Option[];
/** @ngInject */ /** @ngInject */
constructor($scope: any, $injector: auto.IInjectorService, private templateSrv: TemplateSrv) { constructor($scope: any, $injector: auto.IInjectorService, private templateSrv: TemplateSrv) {
super($scope, $injector); super($scope, $injector);
this.target = migrateTargetSchema(this.target);
_.defaultsDeep(this.target, this.defaults); _.defaultsDeep(this.target, this.defaults);
this.migrateTimeGrains(); this.migrateTimeGrains();
...@@ -125,12 +165,35 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -125,12 +165,35 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope);
this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope);
this.resultFormats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }]; this.resultFormats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }];
this.getSubscriptions(); this.resources = new Array<Resource>();
this.subscriptionValues = [];
this.init();
if (this.target.queryType === 'Azure Log Analytics') { if (this.target.queryType === 'Azure Log Analytics') {
this.getWorkspaces(); this.getWorkspaces();
} }
} }
async init() {
const subscriptions = await this.getSubscriptions();
this.datasource.getResources(subscriptions.map((s: Option) => s.value)).then(async (resources: Resource[]) => {
if (!this.target.subscriptions.length) {
this.target.subscriptions = this.subscriptions.map(s => s.value);
}
this.resources = resources;
this.updateLocations();
this.updateCrossResourceGroups();
});
}
updateLocations() {
this.locations = this.getLocations().map(l => ({ text: l, value: l }));
}
updateCrossResourceGroups() {
this.resourceGroups = this.getCrossResourceGroups().map(rg => ({ text: rg, value: rg }));
}
onDataReceived(dataList: DataFrame[]) { onDataReceived(dataList: DataFrame[]) {
this.lastQueryError = undefined; this.lastQueryError = undefined;
this.lastQuery = ''; this.lastQuery = '';
...@@ -170,24 +233,28 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -170,24 +233,28 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
} }
migrateTimeGrains() { migrateTimeGrains() {
if (this.target.azureMonitor.timeGrainUnit) { const { queryMode } = this.target.azureMonitor;
if (this.target.azureMonitor.timeGrain !== 'auto') { if (this.target.azureMonitor.data[queryMode].timeGrainUnit) {
this.target.azureMonitor.timeGrain = TimegrainConverter.createISO8601Duration( if (this.target.azureMonitor.data[queryMode].timeGrain !== 'auto') {
this.target.azureMonitor.timeGrain, this.target.azureMonitor.data[queryMode].timeGrain = TimegrainConverter.createISO8601Duration(
this.target.azureMonitor.timeGrainUnit this.target.azureMonitor.data[queryMode].timeGrain,
this.target.azureMonitor.data[queryMode].timeGrainUnit
); );
} }
delete this.target.azureMonitor.timeGrainUnit; delete this.target.azureMonitor.data[queryMode].timeGrainUnit;
this.onMetricNameChange(); this.onMetricNameChange();
} }
if ( if (
this.target.azureMonitor.timeGrains && this.target.azureMonitor.data[queryMode].timeGrains &&
this.target.azureMonitor.timeGrains.length > 0 && this.target.azureMonitor.data[queryMode].timeGrains.length > 0 &&
(!this.target.azureMonitor.allowedTimeGrainsMs || this.target.azureMonitor.allowedTimeGrainsMs.length === 0) (!this.target.azureMonitor.data[queryMode].allowedTimeGrainsMs ||
this.target.azureMonitor.data[queryMode].allowedTimeGrainsMs.length === 0)
) { ) {
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(this.target.azureMonitor.timeGrains); this.target.azureMonitor.data[queryMode].allowedTimeGrainsMs = this.convertTimeGrainsToMs(
this.target.azureMonitor.data[queryMode].timeGrains
);
} }
} }
...@@ -197,15 +264,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -197,15 +264,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
} }
async migrateToDefaultNamespace() { async migrateToDefaultNamespace() {
const { queryMode } = this.target.azureMonitor;
if ( if (
this.target.azureMonitor.metricNamespace && this.target.azureMonitor.data[queryMode].metricNamespace &&
this.target.azureMonitor.metricNamespace !== this.defaultDropdownValue && this.target.azureMonitor.data[queryMode].metricNamespace !== this.defaultDropdownValue &&
this.target.azureMonitor.metricDefinition this.target.azureMonitor.data[queryMode].metricDefinition
) { ) {
return; return;
} }
this.target.azureMonitor.metricNamespace = this.target.azureMonitor.metricDefinition; this.target.azureMonitor.data[queryMode].metricNamespace = this.target.azureMonitor.data[
queryMode
].metricDefinition;
} }
replace(variable: string) { replace(variable: string) {
...@@ -218,13 +288,19 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -218,13 +288,19 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
} }
} }
getSubscriptions() { async getSubscriptions() {
if (!this.datasource.azureMonitorDatasource.isConfigured()) { if (!this.datasource.azureMonitorDatasource.isConfigured()) {
return; return;
} }
return this.datasource.azureMonitorDatasource.getSubscriptions().then((subs: any) => { return this.datasource.azureMonitorDatasource.getSubscriptions().then((subs: any) => {
this.subscriptions = subs; this.subscriptions = subs;
this.subscriptionValues = subs.map((s: Option) => ({ value: s.value, text: s.displayName }));
if (!this.target.subscriptions.length) {
this.target.subscriptions = subs.map((s: Option) => s.value);
}
if (!this.target.subscription && this.target.queryType === 'Azure Monitor') { if (!this.target.subscription && this.target.queryType === 'Azure Monitor') {
this.target.subscription = this.datasource.azureMonitorDatasource.subscriptionId; this.target.subscription = this.datasource.azureMonitorDatasource.subscriptionId;
} else if (!this.target.subscription && this.target.queryType === 'Azure Log Analytics') { } else if (!this.target.subscription && this.target.queryType === 'Azure Log Analytics') {
...@@ -244,16 +320,36 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -244,16 +320,36 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
return this.getWorkspaces(); return this.getWorkspaces();
} }
const { queryMode } = this.target.azureMonitor;
if (this.target.queryType === 'Azure Monitor') { if (this.target.queryType === 'Azure Monitor') {
this.target.azureMonitor.resourceGroup = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].resourceGroup = this.defaultDropdownValue;
this.target.azureMonitor.metricDefinition = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].metricDefinition = this.defaultDropdownValue;
this.target.azureMonitor.resourceName = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].resourceName = this.defaultDropdownValue;
this.target.azureMonitor.metricName = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = ''; this.target.azureMonitor.data[queryMode].aggregation = '';
this.target.azureMonitor.timeGrains = []; this.target.azureMonitor.data[queryMode].timeGrains = [];
this.target.azureMonitor.timeGrain = ''; this.target.azureMonitor.data[queryMode].timeGrain = '';
this.target.azureMonitor.dimensions = []; this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.dimension = ''; this.target.azureMonitor.data[queryMode].dimension = '';
}
}
async onSubscriptionsChange(values: any) {
if (!_.isEqual(this.target.subscriptions.sort(), values.sort())) {
this.target.subscriptions = values;
this.resources = await this.datasource.getResources(this.target.subscriptions);
const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.data[queryMode].resourceGroup = this.defaultDropdownValue;
this.target.azureMonitor.data[queryMode].metricDefinition = this.defaultDropdownValue;
this.target.azureMonitor.data[queryMode].resourceName = this.defaultDropdownValue;
this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.data[queryMode].aggregation = '';
this.target.azureMonitor.data[queryMode].timeGrains = [];
this.target.azureMonitor.data[queryMode].timeGrain = '';
this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.data[queryMode].dimension = '';
this.updateLocations();
this.updateCrossResourceGroups();
} }
} }
...@@ -270,29 +366,70 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -270,29 +366,70 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
.catch(this.handleQueryCtrlError.bind(this)); .catch(this.handleQueryCtrlError.bind(this));
} }
getCrossResourceGroups() {
if (this.target.queryType !== 'Azure Monitor' || !this.datasource.azureMonitorDatasource.isConfigured()) {
return [];
}
return this.resources
.filter(({ location, subscriptionId }) => {
if (this.target.azureMonitor.data.crossResource.locations.length) {
return (
this.target.azureMonitor.data.crossResource.locations.includes(location) &&
this.target.subscriptions.includes(subscriptionId)
);
}
return this.target.subscriptions.includes(subscriptionId);
})
.reduce((options, { group }: Resource) => (options.some(o => o === group) ? options : [...options, group]), []);
}
async getCrossResourceMetricDefinitions(query: any) {
const { locations, resourceGroups } = this.target.azureMonitor.data.crossResource;
return this.resources
.filter(({ location, group }) => locations.includes(location) && resourceGroups.includes(group))
.reduce(
(options: Option[], { type }: Resource) =>
options.some(o => o.value === type) ? options : [...options, { text: type, value: type }],
[]
);
}
getLocations() {
return this.resources
.filter(({ subscriptionId }) => this.target.subscriptions.includes(subscriptionId))
.reduce(
(options: string[], { location }: Resource) =>
options.some(o => o === location) ? options : [...options, location],
[]
);
}
getMetricDefinitions(query: any) { getMetricDefinitions(query: any) {
const { queryMode } = this.target.azureMonitor;
if ( if (
this.target.queryType !== 'Azure Monitor' || this.target.queryType !== 'Azure Monitor' ||
!this.target.azureMonitor.resourceGroup || !this.target.azureMonitor.data[queryMode].resourceGroup ||
this.target.azureMonitor.resourceGroup === this.defaultDropdownValue this.target.azureMonitor.data[queryMode].resourceGroup === this.defaultDropdownValue
) { ) {
return; return;
} }
return this.datasource return this.datasource
.getMetricDefinitions( .getMetricDefinitions(
this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId), this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId),
this.replace(this.target.azureMonitor.resourceGroup) this.replace(this.target.azureMonitor.data[queryMode].resourceGroup)
) )
.catch(this.handleQueryCtrlError.bind(this)); .catch(this.handleQueryCtrlError.bind(this));
} }
getResourceNames(query: any) { getResourceNames(query: any) {
const { queryMode } = this.target.azureMonitor;
if ( if (
this.target.queryType !== 'Azure Monitor' || this.target.queryType !== 'Azure Monitor' ||
!this.target.azureMonitor.resourceGroup || !this.target.azureMonitor.data[queryMode].resourceGroup ||
this.target.azureMonitor.resourceGroup === this.defaultDropdownValue || this.target.azureMonitor.data[queryMode].resourceGroup === this.defaultDropdownValue ||
!this.target.azureMonitor.metricDefinition || !this.target.azureMonitor.data[queryMode].metricDefinition ||
this.target.azureMonitor.metricDefinition === this.defaultDropdownValue this.target.azureMonitor.data[queryMode].metricDefinition === this.defaultDropdownValue
) { ) {
return; return;
} }
...@@ -300,21 +437,22 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -300,21 +437,22 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
return this.datasource return this.datasource
.getResourceNames( .getResourceNames(
this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId), this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId),
this.replace(this.target.azureMonitor.resourceGroup), this.replace(this.target.azureMonitor.data[queryMode].resourceGroup),
this.replace(this.target.azureMonitor.metricDefinition) this.replace(this.target.azureMonitor.data[queryMode].metricDefinition)
) )
.catch(this.handleQueryCtrlError.bind(this)); .catch(this.handleQueryCtrlError.bind(this));
} }
getMetricNamespaces() { getMetricNamespaces() {
const { queryMode } = this.target.azureMonitor;
if ( if (
this.target.queryType !== 'Azure Monitor' || this.target.queryType !== 'Azure Monitor' ||
!this.target.azureMonitor.resourceGroup || !this.target.azureMonitor.data[queryMode].resourceGroup ||
this.target.azureMonitor.resourceGroup === this.defaultDropdownValue || this.target.azureMonitor.data[queryMode].resourceGroup === this.defaultDropdownValue ||
!this.target.azureMonitor.metricDefinition || !this.target.azureMonitor.data[queryMode].metricDefinition ||
this.target.azureMonitor.metricDefinition === this.defaultDropdownValue || this.target.azureMonitor.data[queryMode].metricDefinition === this.defaultDropdownValue ||
!this.target.azureMonitor.resourceName || !this.target.azureMonitor.data[queryMode].resourceName ||
this.target.azureMonitor.resourceName === this.defaultDropdownValue this.target.azureMonitor.data[queryMode].resourceName === this.defaultDropdownValue
) { ) {
return; return;
} }
...@@ -322,24 +460,50 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -322,24 +460,50 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
return this.datasource return this.datasource
.getMetricNamespaces( .getMetricNamespaces(
this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId), this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId),
this.replace(this.target.azureMonitor.resourceGroup), this.replace(this.target.azureMonitor.data[queryMode].resourceGroup),
this.replace(this.target.azureMonitor.metricDefinition), this.replace(this.target.azureMonitor.data[queryMode].metricDefinition),
this.replace(this.target.azureMonitor.resourceName) this.replace(this.target.azureMonitor.data[queryMode].resourceName)
) )
.catch(this.handleQueryCtrlError.bind(this)); .catch(this.handleQueryCtrlError.bind(this));
} }
async getCrossResourceMetricNames() {
const { locations, resourceGroups, metricDefinition } = this.target.azureMonitor.data.crossResource;
const resources = this.resources.filter(
({ type, location, name, group }) =>
resourceGroups.includes(group) && type === metricDefinition && locations.includes(location)
);
const uniqueResources = _.uniqBy(resources, ({ subscriptionId, name, type, group }: Resource) =>
[subscriptionId, name, locations, group].join()
);
const responses = await Promise.all(
uniqueResources.map(({ subscriptionId, group, type, name }) =>
this.datasource
.getMetricNames(subscriptionId, group, type, name)
.then((metrics: any) => metrics.map((m: any) => ({ ...m, subscriptionIds: [subscriptionId] })), [
{ text: this.defaultDropdownValue, value: this.defaultDropdownValue },
])
)
);
return _.uniqBy(responses.reduce((result, resources) => [...result, ...resources], []), ({ value }) => value);
}
getMetricNames() { getMetricNames() {
const { queryMode } = this.target.azureMonitor;
if ( if (
this.target.queryType !== 'Azure Monitor' || this.target.queryType !== 'Azure Monitor' ||
!this.target.azureMonitor.resourceGroup || !this.target.azureMonitor.data[queryMode].resourceGroup ||
this.target.azureMonitor.resourceGroup === this.defaultDropdownValue || this.target.azureMonitor.data[queryMode].resourceGroup === this.defaultDropdownValue ||
!this.target.azureMonitor.metricDefinition || !this.target.azureMonitor.data[queryMode].metricDefinition ||
this.target.azureMonitor.metricDefinition === this.defaultDropdownValue || this.target.azureMonitor.data[queryMode].metricDefinition === this.defaultDropdownValue ||
!this.target.azureMonitor.resourceName || !this.target.azureMonitor.data[queryMode].resourceName ||
this.target.azureMonitor.resourceName === this.defaultDropdownValue || this.target.azureMonitor.data[queryMode].resourceName === this.defaultDropdownValue ||
!this.target.azureMonitor.metricNamespace || !this.target.azureMonitor.data[queryMode].metricNamespace ||
this.target.azureMonitor.metricNamespace === this.defaultDropdownValue this.target.azureMonitor.data[queryMode].metricNamespace === this.defaultDropdownValue
) { ) {
return; return;
} }
...@@ -347,87 +511,168 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -347,87 +511,168 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
return this.datasource return this.datasource
.getMetricNames( .getMetricNames(
this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId), this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId),
this.replace(this.target.azureMonitor.resourceGroup), this.replace(this.target.azureMonitor.data[queryMode].resourceGroup),
this.replace(this.target.azureMonitor.metricDefinition), this.replace(this.target.azureMonitor.data[queryMode].metricDefinition),
this.replace(this.target.azureMonitor.resourceName), this.replace(this.target.azureMonitor.data[queryMode].resourceName),
this.replace(this.target.azureMonitor.metricNamespace) this.replace(this.target.azureMonitor.data[queryMode].metricNamespace)
) )
.catch(this.handleQueryCtrlError.bind(this)); .catch(this.handleQueryCtrlError.bind(this));
} }
onResourceGroupChange() { onResourceGroupChange() {
this.target.azureMonitor.metricDefinition = this.defaultDropdownValue; const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.resourceName = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].metricDefinition = this.defaultDropdownValue;
this.target.azureMonitor.metricNamespace = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].resourceName = this.defaultDropdownValue;
this.target.azureMonitor.metricName = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].metricNamespace = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = ''; this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.timeGrains = []; this.target.azureMonitor.data[queryMode].aggregation = '';
this.target.azureMonitor.timeGrain = ''; this.target.azureMonitor.data[queryMode].timeGrains = [];
this.target.azureMonitor.dimensions = []; this.target.azureMonitor.data[queryMode].timeGrain = '';
this.target.azureMonitor.dimension = ''; this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.data[queryMode].dimension = '';
this.refresh(); this.refresh();
} }
onCrossResourceGroupChange(values: string[]) {
if (!_.isEqual(this.target.azureMonitor.data.crossResource.resourceGroups.sort(), values.sort())) {
this.target.azureMonitor.data.crossResource.resourceGroups = values;
const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.data[queryMode].metricDefinition = '';
this.target.azureMonitor.data[queryMode].metricName = '';
this.refresh();
}
}
onCrossResourceMetricDefinitionChange() {
const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.data[queryMode].aggregation = '';
this.target.azureMonitor.data[queryMode].timeGrains = [];
this.target.azureMonitor.data[queryMode].timeGrain = '';
this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.data[queryMode].dimension = '';
this.refresh();
}
async onLocationsChange(values: string[]) {
if (!_.isEqual(this.target.azureMonitor.data.crossResource.locations.sort(), values.sort())) {
this.target.azureMonitor.data.crossResource.locations = values;
const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.data[queryMode].metricDefinition = '';
this.target.azureMonitor.data[queryMode].resourceGroup = '';
this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.data[queryMode].aggregation = '';
this.target.azureMonitor.data[queryMode].timeGrains = [];
this.target.azureMonitor.data[queryMode].timeGrain = '';
this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.data[queryMode].dimension = '';
this.updateCrossResourceGroups();
this.refresh();
}
}
onMetricDefinitionChange() { onMetricDefinitionChange() {
this.target.azureMonitor.resourceName = this.defaultDropdownValue; const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.metricNamespace = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].resourceName = this.defaultDropdownValue;
this.target.azureMonitor.metricName = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].metricNamespace = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = ''; this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.timeGrains = []; this.target.azureMonitor.data[queryMode].aggregation = '';
this.target.azureMonitor.timeGrain = ''; this.target.azureMonitor.data[queryMode].timeGrains = [];
this.target.azureMonitor.dimensions = []; this.target.azureMonitor.data[queryMode].timeGrain = '';
this.target.azureMonitor.dimension = ''; this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.data[queryMode].dimension = '';
} }
onResourceNameChange() { onResourceNameChange() {
this.target.azureMonitor.metricNamespace = this.defaultDropdownValue; const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.metricName = this.defaultDropdownValue; this.target.azureMonitor.data[queryMode].metricNamespace = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = ''; this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.timeGrains = []; this.target.azureMonitor.data[queryMode].aggregation = '';
this.target.azureMonitor.timeGrain = ''; this.target.azureMonitor.data[queryMode].timeGrains = [];
this.target.azureMonitor.dimensions = []; this.target.azureMonitor.data[queryMode].timeGrain = '';
this.target.azureMonitor.dimension = ''; this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.data[queryMode].dimension = '';
this.refresh(); this.refresh();
} }
onMetricNamespacesChange() { onMetricNamespacesChange() {
this.target.azureMonitor.metricName = this.defaultDropdownValue; const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.dimensions = []; this.target.azureMonitor.data[queryMode].metricName = this.defaultDropdownValue;
this.target.azureMonitor.dimension = ''; this.target.azureMonitor.data[queryMode].dimensions = [];
this.target.azureMonitor.data[queryMode].dimension = '';
}
setMetricMetadata(metadata: any) {
const { queryMode } = this.target.azureMonitor;
this.target.azureMonitor.data[queryMode].aggOptions = metadata.supportedAggTypes || [metadata.primaryAggType];
this.target.azureMonitor.data[queryMode].aggregation = metadata.primaryAggType;
this.target.azureMonitor.data[queryMode].timeGrains = [{ text: 'auto', value: 'auto' }].concat(
metadata.supportedTimeGrains
);
this.target.azureMonitor.data[queryMode].timeGrain = 'auto';
this.target.azureMonitor.data[queryMode].allowedTimeGrainsMs = this.convertTimeGrainsToMs(
metadata.supportedTimeGrains || []
);
this.target.azureMonitor.data[queryMode].dimensions = metadata.dimensions;
if (metadata.dimensions.length > 0) {
this.target.azureMonitor.data[queryMode].dimension = metadata.dimensions[0].value;
}
return this.refresh();
} }
onMetricNameChange() { onCrossResourceMetricNameChange() {
if (!this.target.azureMonitor.metricName || this.target.azureMonitor.metricName === this.defaultDropdownValue) { const { queryMode } = this.target.azureMonitor;
if (
!this.target.azureMonitor.data[queryMode].metricName ||
this.target.azureMonitor.data[queryMode].metricName === this.defaultDropdownValue
) {
return; return;
} }
const { resourceGroups, metricDefinition, metricName } = this.target.azureMonitor.data[queryMode];
const resource = this.resources.find(
({ type, group }) => type === metricDefinition && resourceGroups.includes(group)
);
return this.datasource return this.datasource
.getMetricMetadata( .getMetricMetadata(
this.replace(this.target.subscription), this.replace(this.target.subscriptions[0]),
this.replace(this.target.azureMonitor.resourceGroup), resource.group,
this.replace(this.target.azureMonitor.metricDefinition), metricDefinition,
this.replace(this.target.azureMonitor.resourceName), resource.name,
this.replace(this.target.azureMonitor.metricNamespace), metricName
this.replace(this.target.azureMonitor.metricName)
) )
.then((metadata: any) => { .then(this.setMetricMetadata.bind(this))
this.target.azureMonitor.aggOptions = metadata.supportedAggTypes || [metadata.primaryAggType]; .then(() => this.refresh())
this.target.azureMonitor.aggregation = metadata.primaryAggType; .catch(this.handleQueryCtrlError.bind(this));
this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains); }
this.target.azureMonitor.timeGrain = 'auto';
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(metadata.supportedTimeGrains || []); onMetricNameChange() {
const { queryMode } = this.target.azureMonitor;
if (
!this.target.azureMonitor.data[queryMode].metricName ||
this.target.azureMonitor.data[queryMode].metricName === this.defaultDropdownValue
) {
return;
}
this.target.azureMonitor.dimensions = metadata.dimensions; return this.datasource
if (metadata.dimensions.length > 0) { .getMetricMetadata(
this.target.azureMonitor.dimension = metadata.dimensions[0].value; this.replace(this.target.subscription),
} this.replace(this.target.azureMonitor.data[queryMode].resourceGroup),
return this.refresh(); this.replace(this.target.azureMonitor.data[queryMode].metricDefinition),
}) this.replace(this.target.azureMonitor.data[queryMode].resourceName),
this.replace(this.target.azureMonitor.data[queryMode].metricName),
this.replace(this.target.azureMonitor.data[queryMode].metricNamespace)
)
.then(this.setMetricMetadata.bind(this))
.catch(this.handleQueryCtrlError.bind(this)); .catch(this.handleQueryCtrlError.bind(this));
} }
convertTimeGrainsToMs(timeGrains: Array<{ text: string; value: string }>) { convertTimeGrainsToMs(timeGrains: Option[]) {
const allowedTimeGrainsMs: number[] = []; const allowedTimeGrainsMs: number[] = [];
timeGrains.forEach((tg: any) => { timeGrains.forEach((tg: any) => {
if (tg.value !== 'auto') { if (tg.value !== 'auto') {
...@@ -438,10 +683,11 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { ...@@ -438,10 +683,11 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
} }
getAutoInterval() { getAutoInterval() {
if (this.target.azureMonitor.timeGrain === 'auto') { const { queryMode } = this.target.azureMonitor;
if (this.target.azureMonitor.data[queryMode].timeGrain === 'auto') {
return TimegrainConverter.findClosestTimeGrain( return TimegrainConverter.findClosestTimeGrain(
this.templateSrv.getBuiltInIntervalValue(), this.templateSrv.getBuiltInIntervalValue(),
_.map(this.target.azureMonitor.timeGrains, o => _.map(this.target.azureMonitor.data[queryMode].timeGrains, o =>
TimegrainConverter.createKbnUnitFromISO8601Duration(o.value) TimegrainConverter.createKbnUnitFromISO8601Duration(o.value)
) || ['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d'] ) || ['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d']
); );
......
...@@ -3,6 +3,7 @@ import { DataQuery, DataSourceJsonData } from '@grafana/ui'; ...@@ -3,6 +3,7 @@ import { DataQuery, DataSourceJsonData } from '@grafana/ui';
export interface AzureMonitorQuery extends DataQuery { export interface AzureMonitorQuery extends DataQuery {
format: string; format: string;
subscription: string; subscription: string;
subscriptions: string[];
azureMonitor: AzureMetricQuery; azureMonitor: AzureMetricQuery;
azureLogAnalytics: AzureLogsQuery; azureLogAnalytics: AzureLogsQuery;
// appInsights: any; // appInsights: any;
...@@ -26,9 +27,9 @@ export interface AzureDataSourceJsonData extends DataSourceJsonData { ...@@ -26,9 +27,9 @@ export interface AzureDataSourceJsonData extends DataSourceJsonData {
// App Insights // App Insights
appInsightsAppId?: string; appInsightsAppId?: string;
} }
export interface AzureMonitorQueryData {
export interface AzureMetricQuery {
resourceGroup: string; resourceGroup: string;
resourceGroups: string[];
resourceName: string; resourceName: string;
metricDefinition: string; metricDefinition: string;
metricNamespace: string; metricNamespace: string;
...@@ -41,6 +42,12 @@ export interface AzureMetricQuery { ...@@ -41,6 +42,12 @@ export interface AzureMetricQuery {
dimension: string; dimension: string;
dimensionFilter: string; dimensionFilter: string;
alias: string; alias: string;
locations: string[];
}
export interface AzureMetricQuery extends AzureMonitorQueryData {
queryMode: string;
data: { [queryMode: string]: AzureMonitorQueryData };
} }
export interface AzureLogsQuery { export interface AzureLogsQuery {
...@@ -67,6 +74,24 @@ export interface AzureMonitorResourceGroupsResponse { ...@@ -67,6 +74,24 @@ export interface AzureMonitorResourceGroupsResponse {
statusText: string; statusText: string;
} }
export interface Resource {
id: string;
name: string;
type: string;
location: string;
kind: string;
subscriptionId: string;
group: string;
}
export interface AzureMonitorResourceResponse {
data: {
value: Resource[];
status: number;
statusText: string;
};
}
// Azure Log Analytics types // Azure Log Analytics types
export interface KustoSchema { export interface KustoSchema {
Databases: { [key: string]: KustoDatabase }; Databases: { [key: string]: KustoDatabase };
......
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