From e481673b776b464df3fac624e001b3ab60c8326c Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Mon, 16 Jan 2023 09:49:35 +0100 Subject: [PATCH] AzureMonitor: Use list of resources for metrics (#61485) --- .../__mocks__/query.ts | 3 +- .../azure_monitor_datasource.test.ts | 11 ++-- .../azure_monitor/azure_monitor_datasource.ts | 15 +++--- .../MetricsQueryEditor.test.tsx | 11 ++-- .../MetricsQueryEditor/MetricsQueryEditor.tsx | 4 +- .../MetricsQueryEditor/dataHooks.test.ts | 54 ++++++++++++++----- .../MetricsQueryEditor/dataHooks.ts | 22 ++++++-- .../components/ResourcePicker/utils.test.ts | 8 ++- .../components/ResourcePicker/utils.ts | 3 +- .../components/SubscriptionField.tsx | 3 +- .../datasource.test.ts | 6 ++- .../types/query.ts | 13 ++++- .../utils/migrateQuery.test.ts | 11 ++-- .../utils/migrateQuery.ts | 22 +++++++- 14 files changed, 132 insertions(+), 54 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/query.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/query.ts index 2d08a83eb61..12d14c1f449 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/query.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/query.ts @@ -36,8 +36,7 @@ export default function createMockQuery(overrides?: Partial): metricName: 'Metric A', metricNamespace: 'Microsoft.Compute/virtualMachines', customNamespace: '', - resourceGroup: 'grafanastaging', - resourceName: 'grafana', + resources: [{ resourceGroup: 'grafanastaging', resourceName: 'grafana' }], timeGrain: 'auto', alias: '', // timeGrains: [], diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts index 406e9d39891..622bdfc88ea 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts @@ -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', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts index ad4633dd32c..16c37b1b2e5 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts @@ -68,8 +68,9 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend 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 ({ + 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 { 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', + }, + ], }), }) ); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx index b9e8251f358..39b39a76555 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -39,9 +39,9 @@ const MetricsQueryEditor: React.FC = ({ 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 ( diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts index 3ca999e8e25..20f7ab9cc88 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts @@ -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 ); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts index 19185023676..b6edf825d7c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts @@ -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(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(() => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.test.ts index 57083d316fd..c0101c4b63b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.test.ts @@ -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', + }, + ], }, }); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts index a5a8778eb76..16ae043f0d1 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts @@ -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: '', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx index cb349249883..72e4843d35a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx @@ -36,9 +36,8 @@ const SubscriptionField: React.FC = ({ if (query.queryType === AzureQueryType.AzureMonitor) { newQuery.azureMonitor = { ...newQuery.azureMonitor, - resourceGroup: undefined, + resources: undefined, metricNamespace: undefined, - resourceName: undefined, metricName: undefined, aggregation: undefined, timeGrain: '', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.test.ts index ff3e0b329df..6c4713cd651 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.test.ts @@ -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' })], + }), }) ); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts index bd24cdaa1d1..844a56d6f2b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts @@ -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; } /** diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts index 7fe16431aec..9f3437bb6d7 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts @@ -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, }), }) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts index 1b44cb3be91..bf8a895e69b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts @@ -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; +}