mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: Migrate Metrics query editor to React (#30783)
* AzureMonitor: Remove anys from datasource to get the inferred type * AzureMonitor: Cast some datasource types TODO: we want proper types for these * AzureMonitor: Initial react Metrics editor components * start dimension fields * replace replaceTemplateVariable with datasource.replace, and rename onQueryChange to onChange * actually just do template variable replacement in the datasource * don't use azureMonitorIsConfigured * Refactors, mainly around the metric metadata - Convert all the metric metadata options for the Select before its set into state - Stop using SelectableValue because it's basically any when all the properties are optional - the onChange function passed to the fields now just accepts the direct value, rather than wrapped in a SelectableValue * added proper fields, and adding and removing for DimensionFields * Update query with Dimension changes * Width * subscription and query type fields * Should be feature complete now, more or less * fix missing import * fix lint issues * set default subscription ID * Starting to write some tests * tests for query editor * Remove subscription ID from the label in Metrics But we keep it there for the angular stuff * MetricsQueryEditor tests * Update index.test.tsx * fix tests * add template variables to dropdowns * clean up * update tests * Reorganise react components * Group query fields into rows * Rename Option type, add Azure response type * Refactor Metrics metric metadata - Types the Azure API - Moves default metadata values into datasource * nit * update test
This commit is contained in:
@@ -23,6 +23,8 @@ import { HelpModal } from './components/help/HelpModal';
|
|||||||
import { Footer } from './components/Footer/Footer';
|
import { Footer } from './components/Footer/Footer';
|
||||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||||
import { SearchField, SearchResults, SearchResultsFilter } from '../features/search';
|
import { SearchField, SearchResults, SearchResultsFilter } from '../features/search';
|
||||||
|
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
|
||||||
|
import QueryEditor from 'app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor';
|
||||||
|
|
||||||
const { SecretFormField } = LegacyForms;
|
const { SecretFormField } = LegacyForms;
|
||||||
|
|
||||||
@@ -181,4 +183,22 @@ export function registerAngularDirectives() {
|
|||||||
['onLoad', { watchDepth: 'reference', wrapApply: true }],
|
['onLoad', { watchDepth: 'reference', wrapApply: true }],
|
||||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
react2AngularDirective('timePickerSettings', TimePickerSettings, [
|
||||||
|
'renderCount',
|
||||||
|
'refreshIntervals',
|
||||||
|
'timePickerHidden',
|
||||||
|
'nowDelay',
|
||||||
|
'timezone',
|
||||||
|
['onTimeZoneChange', { watchDepth: 'reference', wrapApply: true }],
|
||||||
|
['onRefreshIntervalChange', { watchDepth: 'reference', wrapApply: true }],
|
||||||
|
['onNowDelayChange', { watchDepth: 'reference', wrapApply: true }],
|
||||||
|
['onHideTimePickerChange', { watchDepth: 'reference', wrapApply: true }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
react2AngularDirective('azureMonitorQueryEditor', QueryEditor, [
|
||||||
|
'query',
|
||||||
|
['datasource', { watchDepth: 'reference' }],
|
||||||
|
'onChange',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import Datasource from '../datasource';
|
||||||
|
|
||||||
|
type DeepPartial<T> = {
|
||||||
|
[P in keyof T]?: DeepPartial<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function createMockDatasource() {
|
||||||
|
// We make this a partial so we get _some_ kind of type safety when making this, rather than
|
||||||
|
// having it be any or casted immediately to Datasource
|
||||||
|
const _mockDatasource: DeepPartial<Datasource> = {
|
||||||
|
getVariables: jest.fn().mockReturnValueOnce([]),
|
||||||
|
|
||||||
|
azureMonitorDatasource: {
|
||||||
|
isConfigured() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
getSubscriptions: jest.fn().mockResolvedValueOnce([]),
|
||||||
|
},
|
||||||
|
|
||||||
|
getResourceGroups: jest.fn().mockResolvedValueOnce([]),
|
||||||
|
getMetricDefinitions: jest.fn().mockResolvedValueOnce([]),
|
||||||
|
getResourceNames: jest.fn().mockResolvedValueOnce([]),
|
||||||
|
getMetricNamespaces: jest.fn().mockResolvedValueOnce([]),
|
||||||
|
getMetricNames: jest.fn().mockResolvedValueOnce([]),
|
||||||
|
getMetricMetadata: jest.fn().mockResolvedValueOnce({
|
||||||
|
primaryAggType: 'average',
|
||||||
|
supportedAggTypes: [],
|
||||||
|
supportedTimeGrains: [],
|
||||||
|
dimensions: [],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDatasource = _mockDatasource as Datasource;
|
||||||
|
|
||||||
|
return mockDatasource;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { AzureMonitorQuery, AzureQueryType } from '../types';
|
||||||
|
|
||||||
|
const azureMonitorQuery: AzureMonitorQuery = {
|
||||||
|
appInsights: undefined, // The actualy shape of this at runtime disagrees with the ts interface
|
||||||
|
|
||||||
|
azureLogAnalytics: {
|
||||||
|
query:
|
||||||
|
'//change this example to create your own time series query\n<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full chart’s time range, choose the datetime column here\n| summarize count() by <group by column>, bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
|
||||||
|
resultFormat: 'time_series',
|
||||||
|
workspace: 'e3fe4fde-ad5e-4d60-9974-e2f3562ffdf2',
|
||||||
|
},
|
||||||
|
|
||||||
|
azureMonitor: {
|
||||||
|
// aggOptions: [],
|
||||||
|
aggregation: 'Average',
|
||||||
|
allowedTimeGrainsMs: [60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000],
|
||||||
|
// dimensionFilter: '*',
|
||||||
|
dimensionFilters: [],
|
||||||
|
metricDefinition: 'Microsoft.Compute/virtualMachines',
|
||||||
|
metricName: 'Metric A',
|
||||||
|
metricNamespace: 'Microsoft.Compute/virtualMachines',
|
||||||
|
resourceGroup: 'grafanastaging',
|
||||||
|
resourceName: 'grafana',
|
||||||
|
timeGrain: 'auto',
|
||||||
|
alias: '',
|
||||||
|
// timeGrains: [],
|
||||||
|
top: '10',
|
||||||
|
},
|
||||||
|
|
||||||
|
insightsAnalytics: {
|
||||||
|
query: '',
|
||||||
|
resultFormat: 'time_series',
|
||||||
|
},
|
||||||
|
|
||||||
|
queryType: AzureQueryType.AzureMonitor,
|
||||||
|
refId: 'A',
|
||||||
|
subscription: 'abc-123',
|
||||||
|
|
||||||
|
format: 'dunno lol', // unsure what this value should be. It's not there at runtime, but it's in the ts interface
|
||||||
|
};
|
||||||
|
|
||||||
|
export default azureMonitorQuery;
|
||||||
@@ -20,9 +20,16 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
|||||||
url: string;
|
url: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* TODO: Which one of these values should be used? Was there a migration?
|
||||||
|
* */
|
||||||
|
logAnalyticsSubscriptionId: string;
|
||||||
|
subscriptionId: string;
|
||||||
|
|
||||||
azureMonitorUrl: string;
|
azureMonitorUrl: string;
|
||||||
defaultOrFirstWorkspace: string;
|
defaultOrFirstWorkspace: string;
|
||||||
subscriptionId: string;
|
|
||||||
cache: Map<string, any>;
|
cache: Map<string, any>;
|
||||||
|
|
||||||
constructor(private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
|
constructor(private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
it('should return a list of subscriptions', () => {
|
it('should return a list of subscriptions', () => {
|
||||||
return ctx.ds.metricFindQuery('subscriptions()').then((results: Array<{ text: string; value: string }>) => {
|
return ctx.ds.metricFindQuery('subscriptions()').then((results: Array<{ text: string; value: string }>) => {
|
||||||
expect(results.length).toBe(2);
|
expect(results.length).toBe(2);
|
||||||
expect(results[0].text).toBe('Primary - sub1');
|
expect(results[0].text).toBe('Primary');
|
||||||
expect(results[0].value).toBe('sub1');
|
expect(results[0].value).toBe('sub1');
|
||||||
expect(results[1].text).toBe('Secondary - sub2');
|
expect(results[1].text).toBe('Secondary');
|
||||||
expect(results[1].value).toBe('sub2');
|
expect(results[1].value).toBe('sub2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -545,7 +545,7 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
it('should return list of Resource Groups', () => {
|
it('should return list of Resource Groups', () => {
|
||||||
return ctx.ds.getSubscriptions().then((results: Array<{ text: string; value: string }>) => {
|
return ctx.ds.getSubscriptions().then((results: Array<{ text: string; value: string }>) => {
|
||||||
expect(results.length).toEqual(1);
|
expect(results.length).toEqual(1);
|
||||||
expect(results[0].text).toEqual('Primary Subscription - 99999999-cccc-bbbb-aaaa-9106972f9572');
|
expect(results[0].text).toEqual('Primary Subscription');
|
||||||
expect(results[0].value).toEqual('99999999-cccc-bbbb-aaaa-9106972f9572');
|
expect(results[0].value).toEqual('99999999-cccc-bbbb-aaaa-9106972f9572');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -856,10 +856,10 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
'default',
|
'default',
|
||||||
'UsedCapacity'
|
'UsedCapacity'
|
||||||
)
|
)
|
||||||
.then((results: any) => {
|
.then((results) => {
|
||||||
expect(results.primaryAggType).toEqual('Total');
|
expect(results.primaryAggType).toEqual('Total');
|
||||||
expect(results.supportedAggTypes.length).toEqual(6);
|
expect(results.supportedAggTypes.length).toEqual(6);
|
||||||
expect(results.supportedTimeGrains.length).toEqual(4);
|
expect(results.supportedTimeGrains.length).toEqual(5); // 4 time grains from the API + auto
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -934,15 +934,15 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
expect(results.dimensions).toMatchInlineSnapshot(`
|
expect(results.dimensions).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"text": "Response type",
|
"label": "Response type",
|
||||||
"value": "ResponseType",
|
"value": "ResponseType",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"text": "Geo type",
|
"label": "Geo type",
|
||||||
"value": "GeoType",
|
"value": "GeoType",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"text": "API name",
|
"label": "API name",
|
||||||
"value": "ApiName",
|
"value": "ApiName",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import {
|
|||||||
AzureMonitorMetricDefinitionsResponse,
|
AzureMonitorMetricDefinitionsResponse,
|
||||||
AzureMonitorResourceGroupsResponse,
|
AzureMonitorResourceGroupsResponse,
|
||||||
AzureQueryType,
|
AzureQueryType,
|
||||||
|
AzureMonitorMetricsMetadataResponse,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
|
import { DataSourceInstanceSettings, ScopedVars, MetricFindValue } from '@grafana/data';
|
||||||
import { getBackendSrv, DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
|
import { getBackendSrv, DataSourceWithBackend, getTemplateSrv, FetchResponse } from '@grafana/runtime';
|
||||||
|
|
||||||
const defaultDropdownValue = 'select';
|
const defaultDropdownValue = 'select';
|
||||||
|
|
||||||
@@ -224,7 +225,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
|||||||
.then((result: AzureMonitorMetricDefinitionsResponse) => {
|
.then((result: AzureMonitorMetricDefinitionsResponse) => {
|
||||||
return ResponseParser.parseResponseValues(result, 'type', 'type');
|
return ResponseParser.parseResponseValues(result, 'type', 'type');
|
||||||
})
|
})
|
||||||
.then((result: any) => {
|
.then((result) => {
|
||||||
return filter(result, (t) => {
|
return filter(result, (t) => {
|
||||||
for (let i = 0; i < this.supportedMetricNamespaces.length; i++) {
|
for (let i = 0; i < this.supportedMetricNamespaces.length; i++) {
|
||||||
if (t.value.toLowerCase() === this.supportedMetricNamespaces[i].toLowerCase()) {
|
if (t.value.toLowerCase() === this.supportedMetricNamespaces[i].toLowerCase()) {
|
||||||
@@ -235,7 +236,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((result: any) => {
|
.then((result) => {
|
||||||
let shouldHardcodeBlobStorage = false;
|
let shouldHardcodeBlobStorage = false;
|
||||||
for (let i = 0; i < result.length; i++) {
|
for (let i = 0; i < result.length; i++) {
|
||||||
if (result[i].value === 'Microsoft.Storage/storageAccounts') {
|
if (result[i].value === 'Microsoft.Storage/storageAccounts') {
|
||||||
@@ -340,8 +341,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
|||||||
this.apiVersion
|
this.apiVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.doRequest(url).then((result: any) => {
|
return this.doRequest<AzureMonitorMetricsMetadataResponse>(url).then((result) => {
|
||||||
return ResponseParser.parseMetadata(result, metricName);
|
return ResponseParser.parseMetadata(result.data, metricName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,15 +401,15 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
|||||||
return field && field.length > 0;
|
return field && field.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
doRequest(url: string, maxRetries = 1): Promise<any> {
|
doRequest<T = any>(url: string, maxRetries = 1): Promise<FetchResponse<T>> {
|
||||||
return getBackendSrv()
|
return getBackendSrv()
|
||||||
.datasourceRequest({
|
.datasourceRequest<T>({
|
||||||
url: this.url + url,
|
url: this.url + url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
if (maxRetries > 0) {
|
if (maxRetries > 0) {
|
||||||
return this.doRequest(url, maxRetries - 1);
|
return this.doRequest<T>(url, maxRetries - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import TimeGrainConverter from '../time_grain_converter';
|
import TimeGrainConverter from '../time_grain_converter';
|
||||||
|
import {
|
||||||
|
AzureMonitorLocalizedValue,
|
||||||
|
AzureMonitorMetricAvailabilityMetadata,
|
||||||
|
AzureMonitorMetricsMetadataResponse,
|
||||||
|
AzureMonitorOption,
|
||||||
|
} from '../types';
|
||||||
export default class ResponseParser {
|
export default class ResponseParser {
|
||||||
static parseResponseValues(
|
static parseResponseValues(
|
||||||
result: any,
|
result: any,
|
||||||
@@ -45,10 +51,11 @@ export default class ResponseParser {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseMetadata(result: any, metricName: string) {
|
static parseMetadata(result: AzureMonitorMetricsMetadataResponse, metricName: string) {
|
||||||
const defaultAggTypes = ['None', 'Average', 'Minimum', 'Maximum', 'Total', 'Count'];
|
const defaultAggTypes = ['None', 'Average', 'Minimum', 'Maximum', 'Total', 'Count'];
|
||||||
|
const metricData = result?.value.find((v) => v.name.value === metricName);
|
||||||
|
|
||||||
if (!result) {
|
if (!metricData) {
|
||||||
return {
|
return {
|
||||||
primaryAggType: '',
|
primaryAggType: '',
|
||||||
supportedAggTypes: defaultAggTypes,
|
supportedAggTypes: defaultAggTypes,
|
||||||
@@ -57,20 +64,21 @@ export default class ResponseParser {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const metricData: any = _.find(result.data.value, (o) => {
|
|
||||||
return _.get(o, 'name.value') === metricName;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
primaryAggType: metricData.primaryAggregationType,
|
primaryAggType: metricData.primaryAggregationType,
|
||||||
supportedAggTypes: metricData.supportedAggregationTypes || defaultAggTypes,
|
supportedAggTypes: metricData.supportedAggregationTypes || defaultAggTypes,
|
||||||
supportedTimeGrains: ResponseParser.parseTimeGrains(metricData.metricAvailabilities || []),
|
|
||||||
dimensions: ResponseParser.parseDimensions(metricData),
|
supportedTimeGrains: [
|
||||||
|
{ label: 'Auto', value: 'auto' },
|
||||||
|
...ResponseParser.parseTimeGrains(metricData.metricAvailabilities ?? []),
|
||||||
|
],
|
||||||
|
dimensions: ResponseParser.parseDimensions(metricData.dimensions ?? []),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseTimeGrains(metricAvailabilities: any[]): Array<{ text: string; value: string }> {
|
static parseTimeGrains(metricAvailabilities: AzureMonitorMetricAvailabilityMetadata[]): AzureMonitorOption[] {
|
||||||
const timeGrains: any[] = [];
|
const timeGrains: AzureMonitorOption[] = [];
|
||||||
|
|
||||||
if (!metricAvailabilities) {
|
if (!metricAvailabilities) {
|
||||||
return timeGrains;
|
return timeGrains;
|
||||||
}
|
}
|
||||||
@@ -78,31 +86,23 @@ export default class ResponseParser {
|
|||||||
metricAvailabilities.forEach((avail) => {
|
metricAvailabilities.forEach((avail) => {
|
||||||
if (avail.timeGrain) {
|
if (avail.timeGrain) {
|
||||||
timeGrains.push({
|
timeGrains.push({
|
||||||
text: TimeGrainConverter.createTimeGrainFromISO8601Duration(avail.timeGrain),
|
label: TimeGrainConverter.createTimeGrainFromISO8601Duration(avail.timeGrain),
|
||||||
value: avail.timeGrain,
|
value: avail.timeGrain,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return timeGrains;
|
return timeGrains;
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseDimensions(metricData: any): Array<{ text: string; value: string }> {
|
static parseDimensions(metadataDimensions: AzureMonitorLocalizedValue[]) {
|
||||||
const dimensions: Array<{ text: string; value: string }> = [];
|
return metadataDimensions.map((dimension) => {
|
||||||
if (!metricData.dimensions || metricData.dimensions.length === 0) {
|
return {
|
||||||
return dimensions;
|
label: dimension.localizedValue || dimension.value,
|
||||||
}
|
value: dimension.value,
|
||||||
|
};
|
||||||
for (let i = 0; i < metricData.dimensions.length; i++) {
|
|
||||||
const text = metricData.dimensions[i].localizedValue;
|
|
||||||
const value = metricData.dimensions[i].value;
|
|
||||||
|
|
||||||
dimensions.push({
|
|
||||||
text: !text ? value : text,
|
|
||||||
value: value,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parseSubscriptions(result: any): Array<{ text: string; value: string }> {
|
static parseSubscriptions(result: any): Array<{ text: string; value: string }> {
|
||||||
const list: Array<{ text: string; value: string }> = [];
|
const list: Array<{ text: string; value: string }> = [];
|
||||||
@@ -116,7 +116,7 @@ export default class ResponseParser {
|
|||||||
for (let i = 0; i < result.data.value.length; i++) {
|
for (let i = 0; i < result.data.value.length; i++) {
|
||||||
if (!_.find(list, ['value', _.get(result.data.value[i], valueFieldName)])) {
|
if (!_.find(list, ['value', _.get(result.data.value[i], valueFieldName)])) {
|
||||||
list.push({
|
list.push({
|
||||||
text: `${_.get(result.data.value[i], textFieldName)} - ${_.get(result.data.value[i], valueFieldName)}`,
|
text: `${_.get(result.data.value[i], textFieldName)}`,
|
||||||
value: _.get(result.data.value[i], valueFieldName),
|
value: _.get(result.data.value[i], valueFieldName),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { InlineField } from '@grafana/ui';
|
||||||
|
import React from 'react';
|
||||||
|
import { Props as InlineFieldProps } from '@grafana/ui/src/components/Forms/InlineField';
|
||||||
|
|
||||||
|
const DEFAULT_LABEL_WIDTH = 18;
|
||||||
|
|
||||||
|
export const Field = (props: InlineFieldProps) => {
|
||||||
|
return <InlineField labelWidth={DEFAULT_LABEL_WIDTH} {...props} />;
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption } from '../common';
|
||||||
|
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||||
|
|
||||||
|
interface AggregationFieldProps extends AzureQueryEditorFieldProps {
|
||||||
|
aggregationOptions: AzureMonitorOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const AggregationField: React.FC<AggregationFieldProps> = ({
|
||||||
|
query,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
aggregationOptions,
|
||||||
|
}) => {
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
aggregation: change.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => [...aggregationOptions, variableOptionGroup], [
|
||||||
|
aggregationOptions,
|
||||||
|
variableOptionGroup,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Aggregation">
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-metrics-aggregation-field"
|
||||||
|
value={findOption(aggregationOptions, query.azureMonitor.aggregation)}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AggregationField;
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { Button, Select, Input, HorizontalGroup, VerticalGroup, InlineLabel } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption } from '../common';
|
||||||
|
import { AzureMetricDimension, AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types';
|
||||||
|
|
||||||
|
interface DimensionFieldsProps extends AzureQueryEditorFieldProps {
|
||||||
|
dimensionOptions: AzureMonitorOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DimensionFields: React.FC<DimensionFieldsProps> = ({ query, dimensionOptions, onQueryChange }) => {
|
||||||
|
const setDimensionFilters = (newFilters: AzureMetricDimension[]) => {
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
dimensionFilters: newFilters,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addFilter = useCallback(() => {
|
||||||
|
setDimensionFilters([
|
||||||
|
...query.azureMonitor.dimensionFilters,
|
||||||
|
{
|
||||||
|
dimension: '',
|
||||||
|
operator: 'eq',
|
||||||
|
filter: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [query.azureMonitor.dimensionFilters]);
|
||||||
|
|
||||||
|
const removeFilter = (index: number) => {
|
||||||
|
const newFilters = [...query.azureMonitor.dimensionFilters];
|
||||||
|
newFilters.splice(index, 1);
|
||||||
|
setDimensionFilters(newFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFieldChange = <Key extends keyof AzureMetricDimension>(
|
||||||
|
filterIndex: number,
|
||||||
|
fieldName: Key,
|
||||||
|
value: AzureMetricDimension[Key]
|
||||||
|
) => {
|
||||||
|
const newFilters = [...query.azureMonitor.dimensionFilters];
|
||||||
|
const newFilter = newFilters[filterIndex];
|
||||||
|
newFilter[fieldName] = value;
|
||||||
|
setDimensionFilters(newFilters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFilterInputChange = (index: number, ev: React.FormEvent) => {
|
||||||
|
if (ev.target instanceof HTMLInputElement) {
|
||||||
|
onFieldChange(index, 'filter', ev.target.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Dimension">
|
||||||
|
<VerticalGroup spacing="xs">
|
||||||
|
{query.azureMonitor.dimensionFilters.map((filter, index) => (
|
||||||
|
<HorizontalGroup key={index} spacing="xs">
|
||||||
|
<Select
|
||||||
|
placeholder="Field"
|
||||||
|
value={findOption(dimensionOptions, filter.dimension)}
|
||||||
|
options={dimensionOptions}
|
||||||
|
onChange={(v) => onFieldChange(index, 'dimension', v.value ?? '')}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
<InlineLabel aria-label="equals">==</InlineLabel>
|
||||||
|
<Input placeholder="" value={filter.filter} onChange={(ev) => onFilterInputChange(index, ev)} />
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="md"
|
||||||
|
icon="trash-alt"
|
||||||
|
aria-label="Remove"
|
||||||
|
onClick={() => removeFilter(index)}
|
||||||
|
></Button>
|
||||||
|
</HorizontalGroup>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Button variant="secondary" size="md" onClick={addFilter}>
|
||||||
|
Add new dimension
|
||||||
|
</Button>
|
||||||
|
</VerticalGroup>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DimensionFields;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { Input } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { AzureQueryEditorFieldProps } from '../../types';
|
||||||
|
|
||||||
|
const LegendFormatField: React.FC<AzureQueryEditorFieldProps> = ({ onQueryChange, query }) => {
|
||||||
|
const [value, setValue] = useState<string>(query.azureMonitor.alias ?? '');
|
||||||
|
|
||||||
|
// As calling onQueryChange initiates a the datasource refresh, we only want to call it once
|
||||||
|
// the field loses focus
|
||||||
|
const handleChange = useCallback((ev: React.FormEvent) => {
|
||||||
|
if (ev.target instanceof HTMLInputElement) {
|
||||||
|
setValue(ev.target.value);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleBlur = useCallback(() => {
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
alias: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [query, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Legend Format">
|
||||||
|
<Input
|
||||||
|
id="azure-monitor-metrics-legend-field"
|
||||||
|
placeholder="Alias patterns"
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LegendFormatField;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption, toOption } from '../common';
|
||||||
|
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||||
|
|
||||||
|
const MetricName: React.FC<AzureQueryEditorFieldProps> = ({
|
||||||
|
query,
|
||||||
|
datasource,
|
||||||
|
subscriptionId,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
}) => {
|
||||||
|
const [metricNames, setMetricNames] = useState<AzureMonitorOption[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
subscriptionId &&
|
||||||
|
query.azureMonitor.resourceGroup &&
|
||||||
|
query.azureMonitor.metricDefinition &&
|
||||||
|
query.azureMonitor.resourceName &&
|
||||||
|
query.azureMonitor.metricNamespace
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
metricNames.length > 0 && setMetricNames([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource
|
||||||
|
.getMetricNames(
|
||||||
|
subscriptionId,
|
||||||
|
query.azureMonitor.resourceGroup,
|
||||||
|
query.azureMonitor.metricDefinition,
|
||||||
|
query.azureMonitor.resourceName,
|
||||||
|
query.azureMonitor.metricNamespace
|
||||||
|
)
|
||||||
|
.then((results) => setMetricNames(results.map(toOption)))
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO: handle error
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
subscriptionId,
|
||||||
|
query.azureMonitor.resourceGroup,
|
||||||
|
query.azureMonitor.metricDefinition,
|
||||||
|
query.azureMonitor.resourceName,
|
||||||
|
query.azureMonitor.metricNamespace,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
metricName: change.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => [...metricNames, variableOptionGroup], [metricNames, variableOptionGroup]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Metric">
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-metrics-metric-field"
|
||||||
|
value={findOption(metricNames, query.azureMonitor.metricName)}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricName;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption, toOption } from '../common';
|
||||||
|
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||||
|
|
||||||
|
const MetricNamespaceField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||||
|
query,
|
||||||
|
datasource,
|
||||||
|
subscriptionId,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
}) => {
|
||||||
|
const [metricNamespaces, setMetricNamespaces] = useState<AzureMonitorOption[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!(subscriptionId && query.azureMonitor.resourceGroup, query.azureMonitor.metricDefinition)) {
|
||||||
|
metricNamespaces.length > 0 && setMetricNamespaces([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource
|
||||||
|
.getMetricNamespaces(
|
||||||
|
subscriptionId,
|
||||||
|
query.azureMonitor.resourceGroup,
|
||||||
|
query.azureMonitor.metricDefinition,
|
||||||
|
query.azureMonitor.resourceName
|
||||||
|
)
|
||||||
|
.then((results) => setMetricNamespaces(results.map(toOption)))
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO: handle error
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
subscriptionId,
|
||||||
|
query.azureMonitor.resourceGroup,
|
||||||
|
query.azureMonitor.metricDefinition,
|
||||||
|
query.azureMonitor.resourceName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
metricNamespace: change.value,
|
||||||
|
|
||||||
|
metricName: 'select',
|
||||||
|
dimensionFilters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => [...metricNamespaces, variableOptionGroup], [metricNamespaces, variableOptionGroup]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Metric Namespace">
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-metrics-metric-namespace-field"
|
||||||
|
value={findOption(metricNamespaces, query.azureMonitor.metricNamespace)}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricNamespaceField;
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import selectEvent from 'react-select-event';
|
||||||
|
|
||||||
|
import MetricsQueryEditor from './MetricsQueryEditor';
|
||||||
|
|
||||||
|
import mockQuery from '../../__mocks__/query';
|
||||||
|
import createMockDatasource from '../../__mocks__/datasource';
|
||||||
|
|
||||||
|
const variableOptionGroup = {
|
||||||
|
label: 'Template variables',
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Azure Monitor QueryEditor', () => {
|
||||||
|
it('should render', async () => {
|
||||||
|
const mockDatasource = createMockDatasource();
|
||||||
|
render(
|
||||||
|
<MetricsQueryEditor
|
||||||
|
subscriptionId="123"
|
||||||
|
query={mockQuery}
|
||||||
|
datasource={mockDatasource}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the subscription ID when selected', async () => {
|
||||||
|
const mockDatasource = createMockDatasource();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
mockDatasource.azureMonitorDatasource.getSubscriptions = jest.fn().mockResolvedValueOnce([
|
||||||
|
{
|
||||||
|
value: 'abc-123',
|
||||||
|
text: 'Primary Subscription',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'abc-456',
|
||||||
|
text: 'Another Subscription',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MetricsQueryEditor
|
||||||
|
subscriptionId="123"
|
||||||
|
query={mockQuery}
|
||||||
|
datasource={mockDatasource}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const subscriptions = await screen.findByLabelText('Subscription');
|
||||||
|
await selectEvent.select(subscriptions, 'Another Subscription');
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
...mockQuery,
|
||||||
|
subscription: 'abc-456',
|
||||||
|
azureMonitor: {
|
||||||
|
...mockQuery.azureMonitor,
|
||||||
|
resourceGroup: 'select',
|
||||||
|
metricDefinition: 'select',
|
||||||
|
resourceName: 'select',
|
||||||
|
metricName: 'select',
|
||||||
|
aggregation: '',
|
||||||
|
timeGrain: '',
|
||||||
|
dimensionFilters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the metric name when selected', async () => {
|
||||||
|
const mockDatasource = createMockDatasource();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
mockDatasource.getMetricNames = jest.fn().mockResolvedValueOnce([
|
||||||
|
{
|
||||||
|
value: 'metric-a',
|
||||||
|
text: 'Metric A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'metric-b',
|
||||||
|
text: 'Metric B',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MetricsQueryEditor
|
||||||
|
subscriptionId="123"
|
||||||
|
query={mockQuery}
|
||||||
|
datasource={mockDatasource}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument());
|
||||||
|
|
||||||
|
const metrics = await screen.findByLabelText('Metric');
|
||||||
|
await selectEvent.select(metrics, 'Metric B');
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
...mockQuery,
|
||||||
|
azureMonitor: {
|
||||||
|
...mockQuery.azureMonitor,
|
||||||
|
metricName: 'metric-b',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Datasource from '../../datasource';
|
||||||
|
import { AzureMonitorQuery, AzureMonitorOption } from '../../types';
|
||||||
|
import { useMetricsMetadata } from '../metrics';
|
||||||
|
import SubscriptionField from '../SubscriptionField';
|
||||||
|
import MetricNamespaceField from './MetricNamespaceField';
|
||||||
|
import NamespaceField from './NamespaceField';
|
||||||
|
import ResourceGroupsField from './ResourceGroupsField';
|
||||||
|
import ResourceNameField from './ResourceNameField';
|
||||||
|
import MetricNameField from './MetricNameField';
|
||||||
|
import AggregationField from './AggregationField';
|
||||||
|
import TimeGrainField from './TimeGrainField';
|
||||||
|
import DimensionFields from './DimensionFields';
|
||||||
|
import TopField from './TopField';
|
||||||
|
import LegendFormatField from './LegendFormatField';
|
||||||
|
import { InlineFieldRow } from '@grafana/ui';
|
||||||
|
|
||||||
|
interface MetricsQueryEditorProps {
|
||||||
|
query: AzureMonitorQuery;
|
||||||
|
datasource: Datasource;
|
||||||
|
subscriptionId: string;
|
||||||
|
onChange: (newQuery: AzureMonitorQuery) => void;
|
||||||
|
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||||
|
query,
|
||||||
|
datasource,
|
||||||
|
subscriptionId,
|
||||||
|
variableOptionGroup,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const metricsMetadata = useMetricsMetadata(datasource, query, subscriptionId, onChange);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="azure-monitor-metrics-query-editor">
|
||||||
|
<InlineFieldRow>
|
||||||
|
<SubscriptionField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ResourceGroupsField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
</InlineFieldRow>
|
||||||
|
|
||||||
|
<InlineFieldRow>
|
||||||
|
<NamespaceField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
<ResourceNameField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
</InlineFieldRow>
|
||||||
|
|
||||||
|
<InlineFieldRow>
|
||||||
|
<MetricNamespaceField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
<MetricNameField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
</InlineFieldRow>
|
||||||
|
<InlineFieldRow>
|
||||||
|
<AggregationField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
aggregationOptions={metricsMetadata?.aggOptions ?? []}
|
||||||
|
/>
|
||||||
|
<TimeGrainField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
timeGrainOptions={metricsMetadata?.timeGrains ?? []}
|
||||||
|
/>
|
||||||
|
</InlineFieldRow>
|
||||||
|
<DimensionFields
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
dimensionOptions={metricsMetadata?.dimensions ?? []}
|
||||||
|
/>
|
||||||
|
<TopField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
<LegendFormatField
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onQueryChange={onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricsQueryEditor;
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption, toOption } from '../common';
|
||||||
|
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||||
|
|
||||||
|
const NamespaceField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||||
|
query,
|
||||||
|
datasource,
|
||||||
|
subscriptionId,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
}) => {
|
||||||
|
const [namespaces, setNamespaces] = useState<AzureMonitorOption[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!(subscriptionId && query.azureMonitor.resourceGroup)) {
|
||||||
|
namespaces.length && setNamespaces([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource
|
||||||
|
.getMetricDefinitions(subscriptionId, query.azureMonitor.resourceGroup)
|
||||||
|
.then((results) => setNamespaces(results.map(toOption)))
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO: handle error
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [subscriptionId, query.azureMonitor.resourceGroup]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
metricDefinition: change.value,
|
||||||
|
resourceName: 'select',
|
||||||
|
metricNamespace: 'select',
|
||||||
|
metricName: 'select',
|
||||||
|
aggregation: '',
|
||||||
|
timeGrain: '',
|
||||||
|
dimensionFilters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => [...namespaces, variableOptionGroup], [namespaces, variableOptionGroup]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Namespace">
|
||||||
|
{/* It's expected that the label reads Namespace but the property is metricDefinition */}
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-metrics-namespace-field"
|
||||||
|
value={findOption(namespaces, query.azureMonitor.metricDefinition)}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NamespaceField;
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption, toOption } from '../common';
|
||||||
|
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||||
|
|
||||||
|
const ResourceGroupsField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||||
|
query,
|
||||||
|
datasource,
|
||||||
|
subscriptionId,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
}) => {
|
||||||
|
const [resourceGroups, setResourceGroups] = useState<AzureMonitorOption[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!subscriptionId) {
|
||||||
|
resourceGroups.length > 0 && setResourceGroups([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource
|
||||||
|
.getResourceGroups(subscriptionId)
|
||||||
|
.then((results) => setResourceGroups(results.map(toOption)))
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO: handle error
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [subscriptionId]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
resourceGroup: change.value,
|
||||||
|
metricDefinition: 'select',
|
||||||
|
resourceName: 'select',
|
||||||
|
metricNamespace: 'select',
|
||||||
|
metricName: 'select',
|
||||||
|
aggregation: '',
|
||||||
|
timeGrain: '',
|
||||||
|
dimensionFilters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => [...resourceGroups, variableOptionGroup], [resourceGroups, variableOptionGroup]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Resource Group">
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-metrics-resource-group-field"
|
||||||
|
value={findOption(resourceGroups, query.azureMonitor.resourceGroup)}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResourceGroupsField;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption, toOption } from '../common';
|
||||||
|
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||||
|
|
||||||
|
const ResourceNameField: React.FC<AzureQueryEditorFieldProps> = ({
|
||||||
|
query,
|
||||||
|
datasource,
|
||||||
|
subscriptionId,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
}) => {
|
||||||
|
const [resourceNames, setResourceNames] = useState<AzureMonitorOption[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!(subscriptionId && query.azureMonitor.resourceGroup && query.azureMonitor.metricDefinition)) {
|
||||||
|
resourceNames.length > 0 && setResourceNames([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource
|
||||||
|
.getResourceNames(subscriptionId, query.azureMonitor.resourceGroup, query.azureMonitor.metricDefinition)
|
||||||
|
.then((results) => setResourceNames(results.map(toOption)))
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO: handle error
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [subscriptionId, query.azureMonitor.resourceGroup, query.azureMonitor.metricDefinition]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
resourceName: change.value,
|
||||||
|
|
||||||
|
metricNamespace: 'select',
|
||||||
|
metricName: 'select',
|
||||||
|
aggregation: '',
|
||||||
|
timeGrain: '',
|
||||||
|
dimensionFilters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => [...resourceNames, variableOptionGroup], [resourceNames, variableOptionGroup]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Resource Name">
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-metrics-resource-name-field"
|
||||||
|
value={findOption(resourceNames, query.azureMonitor.resourceName)}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResourceNameField;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { findOption } from '../common';
|
||||||
|
import TimegrainConverter from '../../time_grain_converter';
|
||||||
|
import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types';
|
||||||
|
|
||||||
|
interface TimeGrainFieldProps extends AzureQueryEditorFieldProps {
|
||||||
|
timeGrainOptions: AzureMonitorOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeGrainField: React.FC<TimeGrainFieldProps> = ({
|
||||||
|
query,
|
||||||
|
timeGrainOptions,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
}) => {
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
timeGrain: change.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
const timeGrains = useMemo(() => {
|
||||||
|
const autoInterval = TimegrainConverter.findClosestTimeGrain(
|
||||||
|
'1m',
|
||||||
|
timeGrainOptions.map((o) => TimegrainConverter.createKbnUnitFromISO8601Duration(o.value)) || [
|
||||||
|
'1m',
|
||||||
|
'5m',
|
||||||
|
'15m',
|
||||||
|
'30m',
|
||||||
|
'1h',
|
||||||
|
'6h',
|
||||||
|
'12h',
|
||||||
|
'1d',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseTimeGrains = timeGrainOptions.map((v) => (v.value === 'auto' ? { ...v, description: autoInterval } : v));
|
||||||
|
|
||||||
|
return [...baseTimeGrains, variableOptionGroup];
|
||||||
|
}, [timeGrainOptions, variableOptionGroup]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Time Grain">
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-metrics-time-grain-field"
|
||||||
|
value={findOption(timeGrainOptions, query.azureMonitor.timeGrain)}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={timeGrains}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TimeGrainField;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { Input } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { AzureQueryEditorFieldProps } from '../../types';
|
||||||
|
|
||||||
|
const TopField: React.FC<AzureQueryEditorFieldProps> = ({ onQueryChange, query }) => {
|
||||||
|
const [value, setValue] = useState<string>(query.azureMonitor.top ?? '');
|
||||||
|
|
||||||
|
// As calling onQueryChange initiates a the datasource refresh, we only want to call it once
|
||||||
|
// the field loses focus
|
||||||
|
const handleChange = useCallback((ev: React.FormEvent) => {
|
||||||
|
if (ev.target instanceof HTMLInputElement) {
|
||||||
|
setValue(ev.target.value);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleBlur = useCallback(() => {
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
top: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [query, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Top">
|
||||||
|
<Input
|
||||||
|
id="azure-monitor-metrics-top-field"
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
width={16}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TopField;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './MetricsQueryEditor';
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import selectEvent from 'react-select-event';
|
||||||
|
|
||||||
|
import QueryEditor from './QueryEditor';
|
||||||
|
|
||||||
|
import mockQuery from '../../__mocks__/query';
|
||||||
|
import createMockDatasource from '../../__mocks__/datasource';
|
||||||
|
import { AzureQueryType } from '../../types';
|
||||||
|
|
||||||
|
const variableOptionGroup = {
|
||||||
|
label: 'Template variables',
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Azure Monitor QueryEditor', () => {
|
||||||
|
it('renders the Metrics query editor when the query type is Metrics', async () => {
|
||||||
|
const mockDatasource = createMockDatasource();
|
||||||
|
render(
|
||||||
|
<QueryEditor
|
||||||
|
query={mockQuery}
|
||||||
|
datasource={mockDatasource}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render the Metrics query editor when the query type isn't Metrics", async () => {
|
||||||
|
const mockDatasource = createMockDatasource();
|
||||||
|
const logsMockQuery = {
|
||||||
|
...mockQuery,
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<QueryEditor
|
||||||
|
query={logsMockQuery}
|
||||||
|
datasource={mockDatasource}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(screen.queryByTestId('azure-monitor-metrics-query-editor')).not.toBeInTheDocument());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes the query type when selected', async () => {
|
||||||
|
const mockDatasource = createMockDatasource();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
render(
|
||||||
|
<QueryEditor
|
||||||
|
query={mockQuery}
|
||||||
|
datasource={mockDatasource}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => expect(screen.getByTestId('azure-monitor-query-editor')).toBeInTheDocument());
|
||||||
|
|
||||||
|
const metrics = await screen.findByLabelText('Service');
|
||||||
|
await selectEvent.select(metrics, 'Logs');
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
...mockQuery,
|
||||||
|
queryType: AzureQueryType.LogAnalytics,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Datasource from '../../datasource';
|
||||||
|
import { AzureMonitorQuery, AzureQueryType, AzureMonitorOption } from '../../types';
|
||||||
|
import MetricsQueryEditor from '../MetricsQueryEditor';
|
||||||
|
import QueryTypeField from './QueryTypeField';
|
||||||
|
|
||||||
|
interface BaseQueryEditorProps {
|
||||||
|
query: AzureMonitorQuery;
|
||||||
|
datasource: Datasource;
|
||||||
|
onChange: (newQuery: AzureMonitorQuery) => void;
|
||||||
|
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const QueryEditor: React.FC<BaseQueryEditorProps> = ({ query, datasource, onChange }) => {
|
||||||
|
const subscriptionId = query.subscription || datasource.azureMonitorDatasource.subscriptionId;
|
||||||
|
const variableOptionGroup = {
|
||||||
|
label: 'Template Variables',
|
||||||
|
options: datasource.getVariables().map((v) => ({ label: v, value: v })),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="azure-monitor-query-editor">
|
||||||
|
<QueryTypeField query={query} onQueryChange={onChange} />
|
||||||
|
<EditorForQueryType
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
onChange={onChange}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface EditorForQueryTypeProps extends BaseQueryEditorProps {
|
||||||
|
subscriptionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
|
||||||
|
subscriptionId,
|
||||||
|
query,
|
||||||
|
datasource,
|
||||||
|
variableOptionGroup,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
switch (query.queryType) {
|
||||||
|
case AzureQueryType.AzureMonitor:
|
||||||
|
return (
|
||||||
|
<MetricsQueryEditor
|
||||||
|
subscriptionId={subscriptionId}
|
||||||
|
query={query}
|
||||||
|
datasource={datasource}
|
||||||
|
onChange={onChange}
|
||||||
|
variableOptionGroup={variableOptionGroup}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueryEditor;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
import { Field } from '../Field';
|
||||||
|
import { AzureMonitorQuery, AzureQueryType } from '../../types';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { findOption } from '../common';
|
||||||
|
|
||||||
|
const QUERY_TYPES = [
|
||||||
|
{ value: AzureQueryType.AzureMonitor, label: 'Metrics' },
|
||||||
|
{ value: AzureQueryType.LogAnalytics, label: 'Logs' },
|
||||||
|
{ value: AzureQueryType.ApplicationInsights, label: 'Application Insights' },
|
||||||
|
{ value: AzureQueryType.InsightsAnalytics, label: 'Insights Analytics' },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface QueryTypeFieldProps {
|
||||||
|
query: AzureMonitorQuery;
|
||||||
|
onQueryChange: (newQuery: AzureMonitorQuery) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QueryTypeField: React.FC<QueryTypeFieldProps> = ({ query, onQueryChange }) => {
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<AzureQueryType>) => {
|
||||||
|
change.value &&
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
queryType: change.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[query]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Service">
|
||||||
|
<Select
|
||||||
|
inputId="azure-monitor-query-type-field"
|
||||||
|
value={findOption(QUERY_TYPES, query.queryType)}
|
||||||
|
options={QUERY_TYPES}
|
||||||
|
onChange={handleChange}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueryTypeField;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './QueryEditor';
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { AzureMonitorQuery, AzureQueryType, AzureQueryEditorFieldProps, AzureMonitorOption } from '../types';
|
||||||
|
import { findOption } from './common';
|
||||||
|
import { Field } from './Field';
|
||||||
|
|
||||||
|
interface SubscriptionFieldProps extends AzureQueryEditorFieldProps {
|
||||||
|
onQueryChange: (newQuery: AzureMonitorQuery) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
|
||||||
|
datasource,
|
||||||
|
query,
|
||||||
|
variableOptionGroup,
|
||||||
|
onQueryChange,
|
||||||
|
}) => {
|
||||||
|
const [subscriptions, setSubscriptions] = useState<AzureMonitorOption[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!datasource.azureMonitorDatasource.isConfigured()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource.azureMonitorDatasource.getSubscriptions().then((results) => {
|
||||||
|
const newSubscriptions = results.map((v) => ({ label: v.text, value: v.value, description: v.value }));
|
||||||
|
setSubscriptions(newSubscriptions);
|
||||||
|
|
||||||
|
// Set a default subscription ID, if we can
|
||||||
|
let newSubscription = query.subscription;
|
||||||
|
|
||||||
|
if (!newSubscription && query.queryType === AzureQueryType.AzureMonitor) {
|
||||||
|
newSubscription = datasource.azureMonitorDatasource.subscriptionId;
|
||||||
|
} else if (!query.subscription && query.queryType === AzureQueryType.LogAnalytics) {
|
||||||
|
newSubscription =
|
||||||
|
datasource.azureLogAnalyticsDatasource.logAnalyticsSubscriptionId ||
|
||||||
|
datasource.azureLogAnalyticsDatasource.subscriptionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newSubscription && newSubscriptions.length > 0) {
|
||||||
|
newSubscription = newSubscriptions[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSubscription !== query.subscription &&
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
subscription: newSubscription,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(change: SelectableValue<string>) => {
|
||||||
|
if (!change.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newQuery: AzureMonitorQuery = {
|
||||||
|
...query,
|
||||||
|
subscription: change.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (query.queryType === AzureQueryType.AzureMonitor) {
|
||||||
|
newQuery.azureMonitor = {
|
||||||
|
...newQuery.azureMonitor,
|
||||||
|
resourceGroup: 'select',
|
||||||
|
metricDefinition: 'select',
|
||||||
|
resourceName: 'select',
|
||||||
|
metricName: 'select',
|
||||||
|
aggregation: '',
|
||||||
|
timeGrain: '',
|
||||||
|
dimensionFilters: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChange(newQuery);
|
||||||
|
},
|
||||||
|
[query, onQueryChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => [...subscriptions, variableOptionGroup], [subscriptions, variableOptionGroup]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field label="Subscription">
|
||||||
|
<Select
|
||||||
|
value={findOption(subscriptions, query.subscription)}
|
||||||
|
inputId="azure-monitor-subscriptions-field"
|
||||||
|
onChange={handleChange}
|
||||||
|
options={options}
|
||||||
|
width={38}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubscriptionField;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { rangeUtil } from '@grafana/data';
|
||||||
|
import TimegrainConverter from '../time_grain_converter';
|
||||||
|
import { AzureMonitorOption } from '../types';
|
||||||
|
|
||||||
|
// Defaults to returning a fallback option so the UI still shows the value while the API is loading
|
||||||
|
export const findOption = (options: AzureMonitorOption[], value: string) =>
|
||||||
|
options.find((v) => v.value === value) ?? { value, label: value };
|
||||||
|
|
||||||
|
export const toOption = (v: { text: string; value: string }) => ({ value: v.value, label: v.text });
|
||||||
|
|
||||||
|
export function convertTimeGrainsToMs<T extends { value: string }>(timeGrains: T[]) {
|
||||||
|
const allowedTimeGrainsMs: number[] = [];
|
||||||
|
timeGrains.forEach((tg: any) => {
|
||||||
|
if (tg.value !== 'auto') {
|
||||||
|
allowedTimeGrainsMs.push(rangeUtil.intervalToMs(TimegrainConverter.createKbnUnitFromISO8601Duration(tg.value)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return allowedTimeGrainsMs;
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import Datasource from '../datasource';
|
||||||
|
import { AzureMonitorQuery } from '../types';
|
||||||
|
import { convertTimeGrainsToMs } from './common';
|
||||||
|
|
||||||
|
export interface MetricMetadata {
|
||||||
|
aggOptions: Array<{ label: string; value: string }>;
|
||||||
|
timeGrains: Array<{ label: string; value: string }>;
|
||||||
|
dimensions: Array<{ label: string; value: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMetricsMetadata(
|
||||||
|
datasource: Datasource,
|
||||||
|
query: AzureMonitorQuery,
|
||||||
|
subscriptionId: string,
|
||||||
|
onQueryChange: (newQuery: AzureMonitorQuery) => void
|
||||||
|
) {
|
||||||
|
const [metricMetadata, setMetricMetadata] = useState<MetricMetadata>({
|
||||||
|
aggOptions: [],
|
||||||
|
timeGrains: [],
|
||||||
|
dimensions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
subscriptionId &&
|
||||||
|
query.azureMonitor.resourceGroup &&
|
||||||
|
query.azureMonitor.metricDefinition &&
|
||||||
|
query.azureMonitor.resourceName &&
|
||||||
|
query.azureMonitor.metricNamespace &&
|
||||||
|
query.azureMonitor.metricName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource
|
||||||
|
.getMetricMetadata(
|
||||||
|
subscriptionId,
|
||||||
|
query.azureMonitor.resourceGroup,
|
||||||
|
query.azureMonitor.metricDefinition,
|
||||||
|
query.azureMonitor.resourceName,
|
||||||
|
query.azureMonitor.metricNamespace,
|
||||||
|
query.azureMonitor.metricName
|
||||||
|
)
|
||||||
|
.then((metadata) => {
|
||||||
|
onQueryChange({
|
||||||
|
...query,
|
||||||
|
azureMonitor: {
|
||||||
|
...query.azureMonitor,
|
||||||
|
aggregation: metadata.primaryAggType,
|
||||||
|
timeGrain: 'auto',
|
||||||
|
allowedTimeGrainsMs: convertTimeGrainsToMs(metadata.supportedTimeGrains),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Move the aggregationTypes and timeGrain defaults into `getMetricMetadata`
|
||||||
|
const aggregations = (metadata.supportedAggTypes || [metadata.primaryAggType]).map((v) => ({
|
||||||
|
label: v,
|
||||||
|
value: v,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setMetricMetadata({
|
||||||
|
aggOptions: aggregations,
|
||||||
|
timeGrains: metadata.supportedTimeGrains,
|
||||||
|
dimensions: metadata.dimensions,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO: handle error
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
subscriptionId,
|
||||||
|
query.azureMonitor.resourceGroup,
|
||||||
|
query.azureMonitor.metricDefinition,
|
||||||
|
query.azureMonitor.resourceName,
|
||||||
|
query.azureMonitor.metricNamespace,
|
||||||
|
query.azureMonitor.metricName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return metricMetadata;
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
ScopedVars,
|
ScopedVars,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { forkJoin, Observable, of } from 'rxjs';
|
import { forkJoin, Observable, of } from 'rxjs';
|
||||||
import { DataSourceWithBackend } from '@grafana/runtime';
|
import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||||
import InsightsAnalyticsDatasource from './insights_analytics/insights_analytics_datasource';
|
import InsightsAnalyticsDatasource from './insights_analytics/insights_analytics_datasource';
|
||||||
import { migrateMetricsDimensionFilters } from './query_ctrl';
|
import { migrateMetricsDimensionFilters } from './query_ctrl';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
@@ -27,7 +27,10 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
pseudoDatasource: Record<AzureQueryType, DataSourceWithBackend>;
|
pseudoDatasource: Record<AzureQueryType, DataSourceWithBackend>;
|
||||||
optionsKey: Record<AzureQueryType, string>;
|
optionsKey: Record<AzureQueryType, string>;
|
||||||
|
|
||||||
constructor(instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
|
constructor(
|
||||||
|
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||||
|
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||||
|
) {
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings);
|
this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings);
|
||||||
this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings);
|
this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings);
|
||||||
@@ -190,15 +193,22 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
|
|
||||||
/* Azure Monitor REST API methods */
|
/* Azure Monitor REST API methods */
|
||||||
getResourceGroups(subscriptionId: string) {
|
getResourceGroups(subscriptionId: string) {
|
||||||
return this.azureMonitorDatasource.getResourceGroups(subscriptionId);
|
return this.azureMonitorDatasource.getResourceGroups(this.replaceTemplateVariable(subscriptionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetricDefinitions(subscriptionId: string, resourceGroup: string) {
|
getMetricDefinitions(subscriptionId: string, resourceGroup: string) {
|
||||||
return this.azureMonitorDatasource.getMetricDefinitions(subscriptionId, resourceGroup);
|
return this.azureMonitorDatasource.getMetricDefinitions(
|
||||||
|
this.replaceTemplateVariable(subscriptionId),
|
||||||
|
this.replaceTemplateVariable(resourceGroup)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResourceNames(subscriptionId: string, resourceGroup: string, metricDefinition: string) {
|
getResourceNames(subscriptionId: string, resourceGroup: string, metricDefinition: string) {
|
||||||
return this.azureMonitorDatasource.getResourceNames(subscriptionId, resourceGroup, metricDefinition);
|
return this.azureMonitorDatasource.getResourceNames(
|
||||||
|
this.replaceTemplateVariable(subscriptionId),
|
||||||
|
this.replaceTemplateVariable(resourceGroup),
|
||||||
|
this.replaceTemplateVariable(metricDefinition)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetricNames(
|
getMetricNames(
|
||||||
@@ -209,20 +219,20 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
metricNamespace: string
|
metricNamespace: string
|
||||||
) {
|
) {
|
||||||
return this.azureMonitorDatasource.getMetricNames(
|
return this.azureMonitorDatasource.getMetricNames(
|
||||||
subscriptionId,
|
this.replaceTemplateVariable(subscriptionId),
|
||||||
resourceGroup,
|
this.replaceTemplateVariable(resourceGroup),
|
||||||
metricDefinition,
|
this.replaceTemplateVariable(metricDefinition),
|
||||||
resourceName,
|
this.replaceTemplateVariable(resourceName),
|
||||||
metricNamespace
|
this.replaceTemplateVariable(metricNamespace)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetricNamespaces(subscriptionId: string, resourceGroup: string, metricDefinition: string, resourceName: string) {
|
getMetricNamespaces(subscriptionId: string, resourceGroup: string, metricDefinition: string, resourceName: string) {
|
||||||
return this.azureMonitorDatasource.getMetricNamespaces(
|
return this.azureMonitorDatasource.getMetricNamespaces(
|
||||||
subscriptionId,
|
this.replaceTemplateVariable(subscriptionId),
|
||||||
resourceGroup,
|
this.replaceTemplateVariable(resourceGroup),
|
||||||
metricDefinition,
|
this.replaceTemplateVariable(metricDefinition),
|
||||||
resourceName
|
this.replaceTemplateVariable(resourceName)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,12 +245,12 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
metricName: string
|
metricName: string
|
||||||
) {
|
) {
|
||||||
return this.azureMonitorDatasource.getMetricMetadata(
|
return this.azureMonitorDatasource.getMetricMetadata(
|
||||||
subscriptionId,
|
this.replaceTemplateVariable(subscriptionId),
|
||||||
resourceGroup,
|
this.replaceTemplateVariable(resourceGroup),
|
||||||
metricDefinition,
|
this.replaceTemplateVariable(metricDefinition),
|
||||||
resourceName,
|
this.replaceTemplateVariable(resourceName),
|
||||||
metricNamespace,
|
this.replaceTemplateVariable(metricNamespace),
|
||||||
metricName
|
this.replaceTemplateVariable(metricName)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,4 +281,12 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
|||||||
(query) => this.pseudoDatasource[query.queryType].applyTemplateVariables(query, scopedVars) as AzureMonitorQuery
|
(query) => this.pseudoDatasource[query.queryType].applyTemplateVariables(query, scopedVars) as AzureMonitorQuery
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replaceTemplateVariable(variable: string) {
|
||||||
|
return this.templateSrv.replace(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
getVariables() {
|
||||||
|
return this.templateSrv.getVariables().map((v) => `$${v.name}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<query-editor-row query-ctrl="ctrl" can-collapse="false">
|
<query-editor-row
|
||||||
|
query-ctrl="ctrl"
|
||||||
|
can-collapse="false"
|
||||||
|
ng-if="!ctrl.reactQueryEditors.includes(ctrl.target.queryType)"
|
||||||
|
>
|
||||||
<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">Service</label>
|
<label class="gf-form-label query-keyword width-9">Service</label>
|
||||||
@@ -519,3 +523,9 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</query-editor-row>
|
</query-editor-row>
|
||||||
|
|
||||||
|
<!-- Partial migration to React -->
|
||||||
|
<div ng-if="ctrl.reactQueryEditors.includes(ctrl.target.queryType)">
|
||||||
|
<azure-monitor-query-editor query="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.handleNewQuery">
|
||||||
|
</azure-monitor-query-editor>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import './editor/editor_component';
|
|||||||
import { TemplateSrv } from '@grafana/runtime';
|
import { TemplateSrv } from '@grafana/runtime';
|
||||||
import { auto, IPromise } from 'angular';
|
import { auto, IPromise } from 'angular';
|
||||||
import { DataFrame, PanelEvents, rangeUtil } from '@grafana/data';
|
import { DataFrame, PanelEvents, rangeUtil } from '@grafana/data';
|
||||||
import { AzureQueryType, AzureMetricQuery } from './types';
|
import { AzureQueryType, AzureMetricQuery, AzureMonitorQuery } from './types';
|
||||||
|
import { convertTimeGrainsToMs } from './components/common';
|
||||||
|
import Datasource from './datasource';
|
||||||
|
|
||||||
export interface ResultFormat {
|
export interface ResultFormat {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -28,6 +30,11 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
{ id: AzureQueryType.InsightsAnalytics, label: 'Insights Analytics' },
|
{ id: AzureQueryType.InsightsAnalytics, label: 'Insights Analytics' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Query types that have been migrated to React
|
||||||
|
reactQueryEditors = [AzureQueryType.AzureMonitor];
|
||||||
|
|
||||||
|
// target: AzureMonitorQuery;
|
||||||
|
|
||||||
target: {
|
target: {
|
||||||
// should be: AzureMonitorQuery
|
// should be: AzureMonitorQuery
|
||||||
refId: string;
|
refId: string;
|
||||||
@@ -217,7 +224,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
oldAzureTimeGrains.length > 0 &&
|
oldAzureTimeGrains.length > 0 &&
|
||||||
(!this.target.azureMonitor.allowedTimeGrainsMs || this.target.azureMonitor.allowedTimeGrainsMs.length === 0)
|
(!this.target.azureMonitor.allowedTimeGrainsMs || this.target.azureMonitor.allowedTimeGrainsMs.length === 0)
|
||||||
) {
|
) {
|
||||||
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(oldAzureTimeGrains);
|
this.target.azureMonitor.allowedTimeGrainsMs = convertTimeGrainsToMs(oldAzureTimeGrains);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -225,7 +232,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
this.target.appInsights.timeGrains.length > 0 &&
|
this.target.appInsights.timeGrains.length > 0 &&
|
||||||
(!this.target.appInsights.allowedTimeGrainsMs || this.target.appInsights.allowedTimeGrainsMs.length === 0)
|
(!this.target.appInsights.allowedTimeGrainsMs || this.target.appInsights.allowedTimeGrainsMs.length === 0)
|
||||||
) {
|
) {
|
||||||
this.target.appInsights.allowedTimeGrainsMs = this.convertTimeGrainsToMs(this.target.appInsights.timeGrains);
|
this.target.appInsights.allowedTimeGrainsMs = convertTimeGrainsToMs(this.target.appInsights.timeGrains);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,9 +286,9 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(variable: string) {
|
replace = (variable: string) => {
|
||||||
return this.templateSrv.replace(variable, this.panelCtrl.panel.scopedVars);
|
return this.templateSrv.replace(variable, this.panelCtrl.panel.scopedVars);
|
||||||
}
|
};
|
||||||
|
|
||||||
onQueryTypeChange() {
|
onQueryTypeChange() {
|
||||||
if (this.target.queryType === 'Azure Log Analytics') {
|
if (this.target.queryType === 'Azure Log Analytics') {
|
||||||
@@ -294,7 +301,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.datasource.azureMonitorDatasource.getSubscriptions().then((subs: any) => {
|
// assert the type
|
||||||
|
if (!(this.datasource instanceof Datasource)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.datasource.azureMonitorDatasource.getSubscriptions().then((subscriptions) => {
|
||||||
|
// We changed the format in the datasource for the new react stuff, so here we change it back
|
||||||
|
const subs = subscriptions.map((v) => ({
|
||||||
|
text: `${v.text} - ${v.value}`,
|
||||||
|
value: v.value,
|
||||||
|
}));
|
||||||
|
|
||||||
this.subscriptions = subs;
|
this.subscriptions = subs;
|
||||||
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;
|
||||||
@@ -475,7 +493,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
.then((metadata: any) => {
|
.then((metadata: any) => {
|
||||||
this.target.azureMonitor.aggregation = metadata.primaryAggType;
|
this.target.azureMonitor.aggregation = metadata.primaryAggType;
|
||||||
this.target.azureMonitor.timeGrain = 'auto';
|
this.target.azureMonitor.timeGrain = 'auto';
|
||||||
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(metadata.supportedTimeGrains || []);
|
this.target.azureMonitor.allowedTimeGrainsMs = convertTimeGrainsToMs(metadata.supportedTimeGrains || []);
|
||||||
|
|
||||||
// HACK: this saves the last metadata values in the panel json ¯\_(ツ)_/¯
|
// HACK: this saves the last metadata values in the panel json ¯\_(ツ)_/¯
|
||||||
const hackState = this.target.azureMonitor as any;
|
const hackState = this.target.azureMonitor as any;
|
||||||
@@ -492,6 +510,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
.catch(this.handleQueryCtrlError.bind(this));
|
.catch(this.handleQueryCtrlError.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is reimplement
|
||||||
convertTimeGrainsToMs(timeGrains: Array<{ text: string; value: string }>) {
|
convertTimeGrainsToMs(timeGrains: Array<{ text: string; value: string }>) {
|
||||||
const allowedTimeGrainsMs: number[] = [];
|
const allowedTimeGrainsMs: number[] = [];
|
||||||
timeGrains.forEach((tg: any) => {
|
timeGrains.forEach((tg: any) => {
|
||||||
@@ -683,6 +702,14 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
}
|
}
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a full new query object from React and updates it into the Angular controller
|
||||||
|
*/
|
||||||
|
handleNewQuery = (newQuery: AzureMonitorQuery) => {
|
||||||
|
Object.assign(this.target, newQuery);
|
||||||
|
this.refresh();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modifies the actual query object
|
// Modifies the actual query object
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DataQuery, DataSourceJsonData, DataSourceSettings, TableData } from '@grafana/data';
|
import { DataQuery, DataSourceJsonData, DataSourceSettings, TableData } from '@grafana/data';
|
||||||
|
import Datasource from './datasource';
|
||||||
|
|
||||||
export type AzureDataSourceSettings = DataSourceSettings<AzureDataSourceJsonData, AzureDataSourceSecureJsonData>;
|
export type AzureDataSourceSettings = DataSourceSettings<AzureDataSourceJsonData, AzureDataSourceSecureJsonData>;
|
||||||
|
|
||||||
@@ -91,6 +92,30 @@ export interface InsightsAnalyticsQuery {
|
|||||||
|
|
||||||
// Azure Monitor API Types
|
// Azure Monitor API Types
|
||||||
|
|
||||||
|
export interface AzureMonitorMetricsMetadataResponse {
|
||||||
|
value: AzureMonitorMetricMetadataItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureMonitorMetricMetadataItem {
|
||||||
|
id: string;
|
||||||
|
resourceId: string;
|
||||||
|
primaryAggregationType: string;
|
||||||
|
supportedAggregationTypes: string[];
|
||||||
|
name: AzureMonitorLocalizedValue;
|
||||||
|
dimensions?: AzureMonitorLocalizedValue[];
|
||||||
|
metricAvailabilities?: AzureMonitorMetricAvailabilityMetadata[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureMonitorMetricAvailabilityMetadata {
|
||||||
|
timeGrain: string;
|
||||||
|
retention: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureMonitorLocalizedValue {
|
||||||
|
value: string;
|
||||||
|
localizedValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AzureMonitorMetricDefinitionsResponse {
|
export interface AzureMonitorMetricDefinitionsResponse {
|
||||||
data: {
|
data: {
|
||||||
value: Array<{ name: string; type: string; location?: string }>;
|
value: Array<{ name: string; type: string; location?: string }>;
|
||||||
@@ -153,3 +178,17 @@ export interface AzureLogsTableColumn {
|
|||||||
text: string;
|
text: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AzureMonitorOption<T = string> {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureQueryEditorFieldProps {
|
||||||
|
query: AzureMonitorQuery;
|
||||||
|
datasource: Datasource;
|
||||||
|
subscriptionId: string;
|
||||||
|
variableOptionGroup: { label: string; options: AzureMonitorOption[] };
|
||||||
|
|
||||||
|
onQueryChange: (newQuery: AzureMonitorQuery) => void;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user