AzureMonitor: Use list of resources for metrics (#61485)

This commit is contained in:
Andres Martinez Gotor
2023-01-16 09:49:35 +01:00
committed by GitHub
parent 8f37295b02
commit e481673b77
14 changed files with 132 additions and 54 deletions
@@ -36,8 +36,7 @@ export default function createMockQuery(overrides?: Partial<AzureMonitorQuery>):
metricName: 'Metric A',
metricNamespace: 'Microsoft.Compute/virtualMachines',
customNamespace: '',
resourceGroup: 'grafanastaging',
resourceName: 'grafana',
resources: [{ resourceGroup: 'grafanastaging', resourceName: 'grafana' }],
timeGrain: 'auto',
alias: '',
// timeGrains: [],
@@ -43,12 +43,12 @@ describe('AzureMonitorDatasource', () => {
},
{
description: 'filter query with no resourceGroup',
query: createMockQuery({ azureMonitor: { resourceGroup: undefined } }),
query: createMockQuery({ azureMonitor: { resources: [{ resourceGroup: undefined }] } }),
filtered: false,
},
{
description: 'filter query with no resourceName',
query: createMockQuery({ azureMonitor: { resourceName: undefined } }),
query: createMockQuery({ azureMonitor: { resources: [{ resourceName: undefined }] } }),
filtered: false,
},
{
@@ -117,9 +117,8 @@ describe('AzureMonitorDatasource', () => {
expect(templatedQuery).toMatchObject({
subscription,
azureMonitor: {
resourceGroup,
metricNamespace,
resourceName,
resources: [{ resourceGroup, resourceName }],
},
});
});
@@ -346,8 +345,8 @@ describe('AzureMonitorDatasource', () => {
it('should return a query with any template variables replaced', () => {
const templateableProps = [
'resourceGroup',
'resourceName',
'resources[0].resourceGroup',
'resources[0].resourceName',
'metricNamespace',
'timeGrain',
'aggregation',
@@ -68,8 +68,9 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
filterQuery(item: AzureMonitorQuery): boolean {
const hasResource =
hasValue(item?.azureMonitor?.resourceGroup) &&
hasValue(item?.azureMonitor?.resourceName) &&
item?.azureMonitor?.resources &&
item.azureMonitor.resources.length > 0 &&
item.azureMonitor.resources.every((r) => hasValue(r.resourceGroup) && hasValue(r.resourceName)) &&
hasValue(item?.azureMonitor?.metricDefinition || item?.azureMonitor?.metricNamespace);
const hasResourceUri = hasValue(item.azureMonitor?.resourceUri);
@@ -97,8 +98,10 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
const templateSrv = getTemplateSrv();
const subscriptionId = templateSrv.replace(target.subscription || this.defaultSubscriptionId, scopedVars);
const resourceGroup = templateSrv.replace(item.resourceGroup, scopedVars);
const resourceName = templateSrv.replace(item.resourceName, scopedVars);
const resources = item.resources?.map((r) => ({
resourceGroup: templateSrv.replace(r.resourceGroup, scopedVars),
resourceName: templateSrv.replace(r.resourceName, scopedVars),
}));
const metricNamespace = templateSrv.replace(item.metricNamespace, scopedVars);
const customNamespace = templateSrv.replace(item.customNamespace, scopedVars);
const timeGrain = templateSrv.replace((item.timeGrain || '').toString(), scopedVars);
@@ -117,10 +120,10 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
});
const azMonitorQuery: AzureMetricQuery = {
resourceGroup,
...item,
resources,
metricNamespace,
customNamespace,
resourceName,
timeGrain,
allowedTimeGrainsMs: item.allowedTimeGrainsMs,
metricName: templateSrv.replace(item.metricName, scopedVars),
@@ -81,8 +81,7 @@ describe('MetricsQueryEditor', () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const query = createMockQuery();
delete query?.subscription;
delete query?.azureMonitor?.resourceGroup;
delete query?.azureMonitor?.resourceName;
delete query?.azureMonitor?.resources;
delete query?.azureMonitor?.metricNamespace;
const onChange = jest.fn();
@@ -125,8 +124,12 @@ describe('MetricsQueryEditor', () => {
subscription: 'def-456',
azureMonitor: expect.objectContaining({
metricNamespace: 'microsoft.compute/virtualmachines',
resourceGroup: 'dev-3',
resourceName: 'web-server',
resources: [
{
resourceGroup: 'dev-3',
resourceName: 'web-server',
},
],
}),
})
);
@@ -39,9 +39,9 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
const metricNames = useMetricNames(query, datasource, onChange, setError);
const resource: AzureMetricResource = {
subscription: query.subscription,
resourceGroup: query.azureMonitor?.resourceGroup,
resourceGroup: query.azureMonitor?.resources?.[0]?.resourceGroup,
metricNamespace: query.azureMonitor?.metricNamespace,
resourceName: query.azureMonitor?.resourceName,
resourceName: query.azureMonitor?.resources?.[0]?.resourceName,
};
return (
<span data-testid="azure-monitor-metrics-query-editor-with-experimental-ui">
@@ -45,13 +45,21 @@ describe('AzureMonitor: metrics dataHooks', () => {
hook: useMetricNames,
emptyQueryPartial: {
metricNamespace: 'azure/vm',
resourceGroup: 'rg',
resourceName: 'rn',
resources: [
{
resourceGroup: 'rg',
resourceName: 'rn',
},
],
},
customProperties: {
metricNamespace: 'azure/vm',
resourceGroup: 'rg',
resourceName: 'rn',
resources: [
{
resourceGroup: 'rg',
resourceName: 'rn',
},
],
metricName: 'metric-$ENVIRONMENT',
},
expectedOptions: [
@@ -75,13 +83,21 @@ describe('AzureMonitor: metrics dataHooks', () => {
hook: useMetricNamespaces,
emptyQueryPartial: {
metricNamespace: 'azure/vm',
resourceGroup: 'rg',
resourceName: 'rn',
resources: [
{
resourceGroup: 'rg',
resourceName: 'rn',
},
],
},
customProperties: {
metricNamespace: 'azure/vm-$ENVIRONMENT',
resourceGroup: 'rg',
resourceName: 'rn',
resources: [
{
resourceGroup: 'rg',
resourceName: 'rn',
},
],
metricName: 'metric-name',
},
expectedOptions: [
@@ -188,8 +204,12 @@ describe('AzureMonitor: metrics dataHooks', () => {
name: 'useMetricMetadata',
hook: useMetricMetadata,
emptyQueryPartial: {
resourceGroup: 'rg',
resourceName: 'rn',
resources: [
{
resourceGroup: 'rg',
resourceName: 'rn',
},
],
metricNamespace: 'azure/vm',
metricName: 'Average CPU',
},
@@ -240,8 +260,12 @@ describe('AzureMonitor: metrics dataHooks', () => {
name: 'useMetricNamespaces',
hook: useMetricNamespaces,
emptyQueryPartial: {
resourceGroup: 'rg',
resourceName: 'rn',
resources: [
{
resourceGroup: 'rg',
resourceName: 'rn',
},
],
metricNamespace: 'azure/vm',
},
customProperties: {},
@@ -264,7 +288,11 @@ describe('AzureMonitor: metrics dataHooks', () => {
expect(result.current).toEqual(metricNamespacesConfig.expectedOptions);
expect(datasource.azureMonitorDatasource.getMetricNamespaces).toHaveBeenCalledWith(
expect.objectContaining(metricNamespacesConfig.emptyQueryPartial),
expect.objectContaining({
resourceGroup: 'rg',
resourceName: 'rn',
metricNamespace: 'azure/vm',
}),
// Here, "global" should be false
false
);
@@ -4,7 +4,7 @@ import { rangeUtil } from '@grafana/data';
import Datasource from '../../datasource';
import TimegrainConverter from '../../time_grain_converter';
import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types';
import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery, AzureMonitorResource } from '../../types';
import { toOption } from '../../utils/common';
import { useAsyncState } from '../../utils/useAsyncState';
@@ -38,9 +38,20 @@ export interface MetricMetadata {
type OnChangeFn = (newQuery: AzureMonitorQuery) => void;
const getResourceGroupAndName = (resources?: AzureMonitorResource[]) => {
if (!resources || !resources.length) {
return { resourceGroup: '', resourceName: '' };
}
return {
resourceGroup: resources[0].resourceGroup ?? '',
resourceName: resources[0].resourceName ?? '',
};
};
export const useMetricNamespaces: DataHook = (query, datasource, onChange, setError) => {
const { subscription } = query;
const { metricNamespace, resourceGroup, resourceName } = query.azureMonitor ?? {};
const { metricNamespace, resources } = query.azureMonitor ?? {};
const { resourceGroup, resourceName } = getResourceGroupAndName(resources);
const metricNamespaces = useAsyncState(
async () => {
@@ -75,7 +86,8 @@ export const useMetricNamespaces: DataHook = (query, datasource, onChange, setEr
export const useMetricNames: DataHook = (query, datasource, onChange, setError) => {
const { subscription } = query;
const { metricNamespace, metricName, resourceGroup, resourceName, customNamespace } = query.azureMonitor ?? {};
const { metricNamespace, metricName, resources, customNamespace } = query.azureMonitor ?? {};
const { resourceGroup, resourceName } = getResourceGroupAndName(resources);
return useAsyncState(
async () => {
@@ -110,8 +122,8 @@ const defaultMetricMetadata: MetricMetadata = {
export const useMetricMetadata = (query: AzureMonitorQuery, datasource: Datasource, onChange: OnChangeFn) => {
const [metricMetadata, setMetricMetadata] = useState<MetricMetadata>(defaultMetricMetadata);
const { subscription } = query;
const { resourceGroup, resourceName, metricNamespace, metricName, aggregation, timeGrain, customNamespace } =
query.azureMonitor ?? {};
const { resources, metricNamespace, metricName, aggregation, timeGrain, customNamespace } = query.azureMonitor ?? {};
const { resourceGroup, resourceName } = getResourceGroupAndName(resources);
// Fetch new metric metadata when the fields change
useEffect(() => {
@@ -184,8 +184,12 @@ describe('AzureMonitor ResourcePicker utils', () => {
aggregation: undefined,
metricName: undefined,
metricNamespace: 'microsoft.storage/storageaccounts',
resourceGroup: 'rg',
resourceName: 'testacct',
resources: [
{
resourceGroup: 'rg',
resourceName: 'testacct',
},
],
},
});
});
@@ -145,9 +145,8 @@ export function setResource(query: AzureMonitorQuery, resource?: string | AzureM
subscription: resource?.subscription,
azureMonitor: {
...query.azureMonitor,
resourceGroup: resource?.resourceGroup,
metricNamespace: resource?.metricNamespace?.toLocaleLowerCase(),
resourceName: resource?.resourceName,
resources: [{ resourceGroup: resource?.resourceGroup, resourceName: resource?.resourceName }],
metricName: undefined,
aggregation: undefined,
timeGrain: '',
@@ -36,9 +36,8 @@ const SubscriptionField: React.FC<SubscriptionFieldProps> = ({
if (query.queryType === AzureQueryType.AzureMonitor) {
newQuery.azureMonitor = {
...newQuery.azureMonitor,
resourceGroup: undefined,
resources: undefined,
metricNamespace: undefined,
resourceName: undefined,
metricName: undefined,
aggregation: undefined,
timeGrain: '',
@@ -6,7 +6,7 @@ describe('Azure Monitor Datasource', () => {
describe('interpolateVariablesInQueries()', () => {
it('should interpolate variables in the queries', () => {
const ds = new Datasource(createMockInstanceSetttings());
const queries = [createMockQuery({ azureMonitor: { resourceGroup: '$resourceGroup' } })];
const queries = [createMockQuery({ azureMonitor: { resources: [{ resourceGroup: '$resourceGroup' }] } })];
const interpolatedQueries = ds.interpolateVariablesInQueries(queries, {
resourceGroup: { text: 'the-resource-group', value: 'the-resource-group' },
@@ -14,7 +14,9 @@ describe('Azure Monitor Datasource', () => {
expect(interpolatedQueries).toContainEqual(
expect.objectContaining({
azureMonitor: expect.objectContaining({ resourceGroup: 'the-resource-group' }),
azureMonitor: expect.objectContaining({
resources: [expect.objectContaining({ resourceGroup: 'the-resource-group' })],
}),
})
);
});
@@ -39,12 +39,16 @@ export interface AzureMonitorQuery extends DataQuery {
resource?: string;
}
export interface AzureMonitorResource {
resourceGroup?: string;
resourceName?: string;
}
/**
* Azure Monitor Metrics sub-query properties
*/
export interface AzureMetricQuery {
resourceGroup?: string;
resourceName?: string;
resources?: AzureMetricResource[];
/** metricNamespace is used as the resource type (or resource namespace).
* It's usually equal to the target metric namespace.
* Kept the name of the variable as metricNamespace to avoid backward incompatibility issues.
@@ -74,6 +78,11 @@ export interface AzureMetricQuery {
/** @deprecated Use resourceGroup, resourceName and metricNamespace instead */
resourceUri?: string;
/** @deprecated Use resources instead */
resourceGroup?: string;
/** @deprecated Use resources instead */
resourceName?: string;
}
/**
@@ -53,8 +53,12 @@ const modernMetricsQuery: AzureMonitorQuery = {
dimensionFilters: [{ dimension: 'dependency/success', filters: ['*'], operator: 'eq' }],
metricName: 'dependencies/duration',
metricNamespace: 'microsoft.insights/components',
resourceGroup: 'cloud-datasources',
resourceName: 'AppInsightsTestData',
resources: [
{
resourceGroup: 'cloud-datasources',
resourceName: 'AppInsightsTestData',
},
],
timeGrain: 'PT5M',
top: '10',
},
@@ -173,8 +177,7 @@ describe('AzureMonitor: migrateQuery', () => {
subscription: modernMetricsQuery.subscription,
azureMonitor: expect.objectContaining({
metricNamespace: modernMetricsQuery.azureMonitor!.metricNamespace,
resourceGroup: modernMetricsQuery.azureMonitor!.resourceGroup,
resourceName: modernMetricsQuery.azureMonitor!.resourceName,
resources: modernMetricsQuery.azureMonitor!.resources,
resourceUri: undefined,
}),
})
@@ -28,6 +28,10 @@ export default function migrateQuery(query: AzureMonitorQuery): AzureMonitorQuer
workingQuery = migrateDimensionToResourceObj(workingQuery);
}
if (workingQuery.azureMonitor?.resourceGroup || workingQuery.azureMonitor?.resourceName) {
workingQuery = migrateResourceGroupAndName(workingQuery);
}
return workingQuery;
}
@@ -144,9 +148,8 @@ function migrateDimensionToResourceObj(query: AzureMonitorQuery): AzureMonitorQu
subscription: details?.subscription,
azureMonitor: {
...query.azureMonitor,
resourceGroup: details?.resourceGroup,
resources: [{ resourceGroup: details?.resourceGroup, resourceName: details?.resourceName }],
metricNamespace: details?.metricNamespace,
resourceName: details?.resourceName,
resourceUri: isWellFormedUri ? undefined : query.azureMonitor.resourceUri,
},
};
@@ -154,3 +157,18 @@ function migrateDimensionToResourceObj(query: AzureMonitorQuery): AzureMonitorQu
return query;
}
function migrateResourceGroupAndName(query: AzureMonitorQuery): AzureMonitorQuery {
let workingQuery = query;
if (workingQuery.azureMonitor) {
workingQuery.azureMonitor.resources = [
{ resourceGroup: workingQuery.azureMonitor.resourceGroup, resourceName: workingQuery.azureMonitor.resourceName },
];
delete workingQuery.azureMonitor.resourceGroup;
delete workingQuery.azureMonitor.resourceName;
}
return workingQuery;
}