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
if err != nil {
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
......@@ -84,11 +88,22 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
azureMonitorTarget := query.Model.Get("azureMonitor").MustMap()
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["subscription"] = fmt.Sprintf("%v", query.Model.Get("subscription").MustString())
urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"])
urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"])
urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"])
urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorData["resourceGroup"])
urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorData["metricDefinition"])
urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorData["resourceName"])
ub := urlBuilder{
DefaultSubscription: query.DataSource.JsonData.Get("subscriptionId").MustString(),
......@@ -100,12 +115,12 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
azureURL := ub.Build()
alias := ""
if val, ok := azureMonitorTarget["alias"]; ok {
if val, ok := azureMonitorData["alias"]; ok {
alias = fmt.Sprintf("%v", val)
}
timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])
timeGrains := azureMonitorTarget["allowedTimeGrainsMs"]
timeGrain := fmt.Sprintf("%v", azureMonitorData["timeGrain"])
timeGrains := azureMonitorData["allowedTimeGrainsMs"]
if timeGrain == "auto" {
timeGrain, err = e.setAutoTimeGrain(query.IntervalMs, timeGrains)
if err != nil {
......@@ -117,13 +132,16 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
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("interval", timeGrain)
params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"]))
params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"]))
params.Add("metricnamespace", fmt.Sprintf("%v", azureMonitorTarget["metricNamespace"]))
params.Add("aggregation", fmt.Sprintf("%v", azureMonitorData["aggregation"]))
params.Add("metricnames", fmt.Sprintf("%v", azureMonitorData["metricName"]))
if val, ok := azureMonitorData["metricNamespace"]; ok {
params.Add("metricnamespace", fmt.Sprintf("%v", val))
}
dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimension"]))
dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"]))
if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && len(dimension) > 0 && len(dimensionFilter) > 0 && dimension != "None" {
dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorData["dimension"]))
dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorData["dimensionFilter"]))
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))
}
......
......@@ -36,15 +36,20 @@ func TestAzureMonitorDatasource(t *testing.T) {
Model: simplejson.NewFromAny(map[string]interface{}{
"subscription": "12345678-aaaa-bbbb-cccc-123456789abc",
"azureMonitor": map[string]interface{}{
"timeGrain": "PT1M",
"aggregation": "Average",
"resourceGroup": "grafanastaging",
"resourceName": "grafana",
"metricDefinition": "Microsoft.Compute/virtualMachines",
"metricNamespace": "Microsoft.Compute-virtualMachines",
"metricName": "Percentage CPU",
"alias": "testalias",
"queryType": "Azure Monitor",
"queryMode": "singleResource",
"data": map[string]interface{}{
"singleResource": map[string]interface{}{
"timeGrain": "PT1M",
"aggregation": "Average",
"resourceGroup": "grafanastaging",
"resourceName": "grafana",
"metricDefinition": "Microsoft.Compute/virtualMachines",
"metricNamespace": "Microsoft.Compute-virtualMachines",
"metricName": "Percentage CPU",
"alias": "testalias",
"queryType": "Azure Monitor",
},
},
},
}),
RefId: "A",
......
......@@ -918,8 +918,8 @@ describe('AzureMonitorDatasource', () => {
'nodeapp',
'microsoft.insights/components',
'resource1',
'default',
'UsedCapacity'
'UsedCapacity',
'default'
)
.then((results: any) => {
expect(results.primaryAggType).toEqual('Total');
......@@ -992,8 +992,8 @@ describe('AzureMonitorDatasource', () => {
'nodeapp',
'microsoft.insights/components',
'resource1',
'default',
'Transactions'
'Transactions',
'default'
)
.then((results: any) => {
expect(results.dimensions.length).toEqual(4);
......@@ -1011,8 +1011,8 @@ describe('AzureMonitorDatasource', () => {
'nodeapp',
'microsoft.insights/components',
'resource1',
'default',
'FreeCapacity'
'FreeCapacity',
'default'
)
.then((results: any) => {
expect(results.dimensions.length).toEqual(0);
......
......@@ -108,8 +108,8 @@ export default class ResponseParser {
return dimensions;
}
static parseSubscriptions(result: any): Array<{ text: string; value: string }> {
const list: Array<{ text: string; value: string }> = [];
static parseSubscriptions(result: any): Array<{ text: string; value: string; displayName: string }> {
const list: Array<{ text: string; value: string; displayName: string }> = [];
if (!result) {
return list;
......@@ -122,6 +122,7 @@ export default class ResponseParser {
list.push({
text: `${_.get(result.data.value[i], textFieldName)} - ${_.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', () => {
'rg',
'Microsoft.Sql/servers/databases',
'rn1/rn2',
'default',
'2017-05-01-preview'
'2017-05-01-preview',
'default'
);
expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' +
......@@ -27,8 +27,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg',
'Microsoft.Sql/servers',
'rn',
'default',
'2017-05-01-preview'
'2017-05-01-preview',
'default'
);
expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' +
......@@ -45,8 +45,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg',
'Microsoft.Storage/storageAccounts/blobServices',
'rn1/default',
'default',
'2017-05-01-preview'
'2017-05-01-preview',
'default'
);
expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/' +
......@@ -63,8 +63,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg',
'Microsoft.Storage/storageAccounts/fileServices',
'rn1/default',
'default',
'2017-05-01-preview'
'2017-05-01-preview',
'default'
);
expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/' +
......@@ -81,8 +81,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg',
'Microsoft.Storage/storageAccounts/tableServices',
'rn1/default',
'default',
'2017-05-01-preview'
'2017-05-01-preview',
'default'
);
expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/tableServices/default/' +
......@@ -99,8 +99,8 @@ describe('AzureMonitorUrlBuilder', () => {
'rg',
'Microsoft.Storage/storageAccounts/queueServices',
'rn1/default',
'default',
'2017-05-01-preview'
'2017-05-01-preview',
'default'
);
expect(url).toBe(
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/queueServices/default/' +
......@@ -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 {
resourceGroup: string,
metricDefinition: string,
resourceName: string,
metricNamespace: string,
apiVersion: string
apiVersion: string,
metricNamespace?: string
) {
const metricNameSpaceParam = metricNamespace ? `&metricnamespace=${encodeURIComponent(metricNamespace)}` : '';
if ((metricDefinition.match(/\//g) || []).length > 1) {
const rn = resourceName.split('/');
const service = metricDefinition.substring(metricDefinition.lastIndexOf('/') + 1);
const md = metricDefinition.substring(0, metricDefinition.lastIndexOf('/'));
return (
`${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${md}/${rn[0]}/${service}/${rn[1]}` +
`/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}&metricnamespace=${encodeURIComponent(
metricNamespace
)}`
`/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}${metricNameSpaceParam}`
);
}
return (
`${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${metricDefinition}/${resourceName}` +
`/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}&metricnamespace=${encodeURIComponent(
metricNamespace
)}`
`/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}${metricNameSpaceParam}`
);
}
}
import _ from 'lodash';
import { migrateTargetSchema } from './migrations';
import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource';
import AppInsightsDatasource from './app_insights/app_insights_datasource';
import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
......@@ -42,7 +43,9 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
const appInsightsOptions = _.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']);
azureLogAnalyticsOptions.targets = _.filter(azureLogAnalyticsOptions.targets, ['queryType', 'Azure Log Analytics']);
......@@ -163,7 +166,7 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
resourceGroup: string,
metricDefinition: string,
resourceName: string,
metricNamespace: string
metricNamespace?: string
) {
return this.azureMonitorDatasource.getMetricNames(
subscriptionId,
......@@ -188,19 +191,23 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
resourceGroup: string,
metricDefinition: string,
resourceName: string,
metricNamespace: string,
metricName: string
metricName: string,
metricNamespace?: string
) {
return this.azureMonitorDatasource.getMetricMetadata(
subscriptionId,
resourceGroup,
metricDefinition,
resourceName,
metricNamespace,
metricName
metricName,
metricNamespace
);
}
getResources(subscriptions: string[]) {
return this.azureMonitorDatasource.getResources(subscriptions);
}
/* Application Insights API method */
getAppInsightsMetricNames() {
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
......@@ -36,11 +36,11 @@ describe('AzureMonitorQueryCtrl', () => {
});
it('should set query parts to select', () => {
expect(queryCtrl.target.azureMonitor.resourceGroup).toBe('select');
expect(queryCtrl.target.azureMonitor.metricDefinition).toBe('select');
expect(queryCtrl.target.azureMonitor.resourceName).toBe('select');
expect(queryCtrl.target.azureMonitor.metricNamespace).toBe('select');
expect(queryCtrl.target.azureMonitor.metricName).toBe('select');
expect(queryCtrl.target.azureMonitor.data.singleResource.resourceGroup).toBe('select');
expect(queryCtrl.target.azureMonitor.data.singleResource.metricDefinition).toBe('select');
expect(queryCtrl.target.azureMonitor.data.singleResource.resourceName).toBe('select');
expect(queryCtrl.target.azureMonitor.data.singleResource.metricNamespace).toBe('select');
expect(queryCtrl.target.azureMonitor.data.singleResource.metricName).toBe('select');
expect(queryCtrl.target.appInsights.groupBy).toBe('none');
});
});
......@@ -76,7 +76,7 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => {
queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test';
queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.datasource.getMetricDefinitions = function(subscriptionId: any, query: any) {
expect(subscriptionId).toBe('sub1');
expect(query).toBe('test');
......@@ -94,7 +94,7 @@ describe('AzureMonitorQueryCtrl', () => {
describe('and resource group has no value', () => {
beforeEach(() => {
queryCtrl.target.azureMonitor.resourceGroup = 'select';
queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'select';
});
it('should return without making a call to datasource', () => {
......@@ -109,8 +109,8 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => {
queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test';
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.datasource.getResourceNames = function(
subscriptionId: any,
resourceGroup: any,
......@@ -133,8 +133,8 @@ describe('AzureMonitorQueryCtrl', () => {
describe('and resourceGroup and metricDefinition do not have values', () => {
beforeEach(() => {
queryCtrl.target.azureMonitor.resourceGroup = 'select';
queryCtrl.target.azureMonitor.metricDefinition = 'select';
queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'select';
queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'select';
});
it('should return without making a call to datasource', () => {
......@@ -149,10 +149,10 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => {
queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test';
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.resourceName = 'test';
queryCtrl.target.azureMonitor.metricNamespace = 'test';
queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.data.singleResource.resourceName = 'test';
queryCtrl.target.azureMonitor.data.singleResource.metricNamespace = 'test';
queryCtrl.datasource.getMetricNames = function(
subscriptionId: any,
resourceGroup: any,
......@@ -179,10 +179,10 @@ describe('AzureMonitorQueryCtrl', () => {
describe('and resourceGroup, metricDefinition, resourceName and metricNamespace do not have values', () => {
beforeEach(() => {
queryCtrl.target.azureMonitor.resourceGroup = 'select';
queryCtrl.target.azureMonitor.metricDefinition = 'select';
queryCtrl.target.azureMonitor.resourceName = 'select';
queryCtrl.target.azureMonitor.metricNamespace = 'select';
queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'select';
queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'select';
queryCtrl.target.azureMonitor.data.singleResource.resourceName = 'select';
queryCtrl.target.azureMonitor.data.singleResource.metricNamespace = 'select';
});
it('should return without making a call to datasource', () => {
......@@ -201,18 +201,18 @@ describe('AzureMonitorQueryCtrl', () => {
beforeEach(() => {
queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test';
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.resourceName = 'test';
queryCtrl.target.azureMonitor.metricNamespace = 'test';
queryCtrl.target.azureMonitor.metricName = 'Percentage CPU';
queryCtrl.target.azureMonitor.data.singleResource.resourceGroup = 'test';
queryCtrl.target.azureMonitor.data.singleResource.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.data.singleResource.resourceName = 'test';
queryCtrl.target.azureMonitor.data.singleResource.metricNamespace = 'test';
queryCtrl.target.azureMonitor.data.singleResource.metricName = 'Percentage CPU';
queryCtrl.datasource.getMetricMetadata = function(
subscription: any,
resourceGroup: any,
metricDefinition: any,
resourceName: any,
metricNamespace: any,
metricName: any
metricName: any,
metricNamespace: any
) {
expect(subscription).toBe('sub1');
expect(resourceGroup).toBe('test');
......@@ -226,9 +226,9 @@ describe('AzureMonitorQueryCtrl', () => {
it('should set the options and default selected value for the Aggregations dropdown', () => {
queryCtrl.onMetricNameChange().then(() => {
expect(queryCtrl.target.azureMonitor.aggregation).toBe('Average');
expect(queryCtrl.target.azureMonitor.aggOptions).toBe(['Average', 'Total']);
expect(queryCtrl.target.azureMonitor.timeGrains).toBe(['PT1M', 'P1D']);
expect(queryCtrl.target.azureMonitor.data.singleResource.aggregation).toBe('Average');
expect(queryCtrl.target.azureMonitor.data.singleResource.aggOptions).toBe(['Average', 'Total']);
expect(queryCtrl.target.azureMonitor.data.singleResource.timeGrains).toBe(['PT1M', 'P1D']);
});
});
});
......
......@@ -3,6 +3,7 @@ import { DataQuery, DataSourceJsonData } from '@grafana/ui';
export interface AzureMonitorQuery extends DataQuery {
format: string;
subscription: string;
subscriptions: string[];
azureMonitor: AzureMetricQuery;
azureLogAnalytics: AzureLogsQuery;
// appInsights: any;
......@@ -26,9 +27,9 @@ export interface AzureDataSourceJsonData extends DataSourceJsonData {
// App Insights
appInsightsAppId?: string;
}
export interface AzureMetricQuery {
export interface AzureMonitorQueryData {
resourceGroup: string;
resourceGroups: string[];
resourceName: string;
metricDefinition: string;
metricNamespace: string;
......@@ -41,6 +42,12 @@ export interface AzureMetricQuery {
dimension: string;
dimensionFilter: string;
alias: string;
locations: string[];
}
export interface AzureMetricQuery extends AzureMonitorQueryData {
queryMode: string;
data: { [queryMode: string]: AzureMonitorQueryData };
}
export interface AzureLogsQuery {
......@@ -67,6 +74,24 @@ export interface AzureMonitorResourceGroupsResponse {
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
export interface KustoSchema {
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