From e01d8ad5b5308fb1df62307acdb420e3d6d9bd94 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Mon, 27 Jan 2025 15:53:00 +0000 Subject: [PATCH] Azure: Add support for custom namespace and custom metrics variable queries (#99279) * Add custom metric namespace and metric name queries * Fix outdated type * Support specifying custom - Add custom support to getMetricNamespaces - Ensure the customNamespace is specified in getMetricNames calls * Update data source tests * Support custom namespace/metrics variable queries - Add tests * Add fields to variable editor - Update tests - Update docs - Update selectors * Remove unneeded Promise.resolve * Add comment * Don't mutate expected path * Lint * Update docs/sources/datasources/azure-monitor/template-variables/index.md Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> * Update docs/sources/datasources/azure-monitor/template-variables/index.md Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> * Update docs * Update conditionals * Lint --------- Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> --- .../azure-monitor/template-variables/index.md | 29 ++++--- .../x/AzureMonitorDataQuery_types.gen.ts | 18 +++- .../kinds/dataquery/types_dataquery_gen.go | 16 +++- .../azure_monitor_datasource.test.ts | 27 +++++- .../azure_monitor/azure_monitor_datasource.ts | 10 ++- .../VariableEditor/VariableEditor.test.tsx | 57 ++++++++++++- .../VariableEditor/VariableEditor.tsx | 77 ++++++++++++++++- .../datasource/azuremonitor/dataquery.cue | 10 ++- .../datasource/azuremonitor/dataquery.gen.ts | 18 +++- .../datasource/azuremonitor/datasource.ts | 23 ++++- .../datasource/azuremonitor/e2e/selectors.ts | 3 + .../datasource/azuremonitor/types/types.ts | 8 +- .../datasource/azuremonitor/variables.test.ts | 84 +++++++++++++++++++ .../datasource/azuremonitor/variables.ts | 53 ++++++++++++ 14 files changed, 394 insertions(+), 39 deletions(-) diff --git a/docs/sources/datasources/azure-monitor/template-variables/index.md b/docs/sources/datasources/azure-monitor/template-variables/index.md index 5eb2d9f983c..73711ee701a 100644 --- a/docs/sources/datasources/azure-monitor/template-variables/index.md +++ b/docs/sources/datasources/azure-monitor/template-variables/index.md @@ -48,19 +48,24 @@ For an introduction to templating and template variables, refer to the [Templati You can specify these Azure Monitor data source queries in the Variable edit view's **Query Type** field. -| Name | Description | -| ------------------- | ------------------------------------------------------------------------------------------------------------------ | -| **Subscriptions** | Returns subscriptions. | -| **Resource Groups** | Returns resource groups for a specified. Supports multi-value. subscription. | -| **Namespaces** | Returns metric namespaces for the specified subscription and resource group. | -| **Regions** | Returns regions for the specified subscription | -| **Resource Names** | Returns a list of resource names for a specified subscription, resource group and namespace. Supports multi-value. | -| **Metric Names** | Returns a list of metric names for a resource. | -| **Workspaces** | Returns a list of workspaces for the specified subscription. | -| **Logs** | Use a KQL query to return values. | -| **Resource Graph** | Use an ARG query to return values. | +| Name | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------ | +| **Subscriptions** | Returns subscriptions. | +| **Resource Groups** | Returns resource groups for a specified. Supports multi-value. subscription. | +| **Namespaces** | Returns metric namespaces for the specified subscription and resource group. | +| **Regions** | Returns regions for the specified subscription | +| **Resource Names** | Returns a list of resource names for a specified subscription, resource group and namespace. Supports multi-value. | +| **Metric Names** | Returns a list of metric names for a resource. | +| **Workspaces** | Returns a list of workspaces for the specified subscription. | +| **Logs** | Use a KQL query to return values. | +| **Custom Namespaces** | Returns metric namespaces for the specified resource. | +| **Custom Metric Names** | Returns a list of custom metric names for the specified resource. | -Any Log Analytics Kusto Query Language (KQL) query that returns a single list of values can also be used in the Query field. +{{< admonition type="note" >}} +Custom metrics cannot be emitted against a subscription or resource group. Select resources only when you need to retrieve custom metric namespaces or custom metric names associated with a specific resource. +{{< /admonition >}} + +You can use any Log Analytics Kusto Query Language (KQL) query that returns a single list of values in the `Query` field. For example: | Query | List of values returned | diff --git a/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts index 11d29c84a8c..d90d2ba7b14 100644 --- a/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts @@ -29,23 +29,37 @@ export interface AzureMonitorQuery extends common.DataQuery { * Application Insights Traces sub-query properties. */ azureTraces?: AzureTracesQuery; + /** + * Custom namespace used in template variable queries + */ + customNamespace?: string; /** * @deprecated Legacy template variable support. */ grafanaTemplateVariableFn?: GrafanaTemplateVariableQuery; + /** + * Namespace used in template variable queries + */ namespace?: string; /** * Used only for exemplar queries from Prometheus */ query?: string; + /** + * Region used in template variable queries + */ region?: string; + /** + * Resource used in template variable queries + */ resource?: string; /** - * Template variables params. These exist for backwards compatiblity with legacy template variables. + * Resource group used in template variable queries */ resourceGroup?: string; /** * Azure subscription containing the resource(s) to be queried. + * Also used for template variable queries */ subscription?: string; /** @@ -65,6 +79,8 @@ export enum AzureQueryType { AzureMonitor = 'Azure Monitor', AzureResourceGraph = 'Azure Resource Graph', AzureTraces = 'Azure Traces', + CustomMetricNamesQuery = 'Azure Custom Metric Names', + CustomNamespacesQuery = 'Azure Custom Namespaces', GrafanaTemplateVariableFn = 'Grafana Template Variable Function', LocationsQuery = 'Azure Regions', LogAnalytics = 'Azure Log Analytics', diff --git a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go index a474d5fe419..a51aef464a5 100644 --- a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go @@ -28,6 +28,7 @@ type AzureMonitorQuery struct { // TODO make this required and give it a default QueryType *string `json:"queryType,omitempty"` // Azure subscription containing the resource(s) to be queried. + // Also used for template variable queries Subscription *string `json:"subscription,omitempty"` // Subscriptions to be queried via Azure Resource Graph. Subscriptions []string `json:"subscriptions,omitempty"` @@ -41,11 +42,16 @@ type AzureMonitorQuery struct { AzureTraces *AzureTracesQuery `json:"azureTraces,omitempty"` // @deprecated Legacy template variable support. GrafanaTemplateVariableFn *GrafanaTemplateVariableQuery `json:"grafanaTemplateVariableFn,omitempty"` - // Template variables params. These exist for backwards compatiblity with legacy template variables. + // Resource group used in template variable queries ResourceGroup *string `json:"resourceGroup,omitempty"` - Namespace *string `json:"namespace,omitempty"` - Resource *string `json:"resource,omitempty"` - Region *string `json:"region,omitempty"` + // Namespace used in template variable queries + Namespace *string `json:"namespace,omitempty"` + // Resource used in template variable queries + Resource *string `json:"resource,omitempty"` + // Region used in template variable queries + Region *string `json:"region,omitempty"` + // Custom namespace used in template variable queries + CustomNamespace *string `json:"customNamespace,omitempty"` // For mixed data sources the selected datasource is on the query level. // For non mixed scenarios this is undefined. // TODO find a better way to do this ^ that's friendly to schema @@ -77,6 +83,8 @@ const ( AzureQueryTypeLocationsQuery AzureQueryType = "Azure Regions" AzureQueryTypeGrafanaTemplateVariableFn AzureQueryType = "Grafana Template Variable Function" AzureQueryTypeTraceExemplar AzureQueryType = "traceql" + AzureQueryTypeCustomNamespacesQuery AzureQueryType = "Azure Custom Namespaces" + AzureQueryTypeCustomMetricNamesQuery AzureQueryType = "Azure Custom Metric Names" ) type AzureMetricQuery struct { diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts index 9962ffdedd0..2a51c9dc20b 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts @@ -6,7 +6,10 @@ import { multiVariable } from '../__mocks__/variables'; import AzureMonitorDatasource from '../datasource'; import { AzureAPIResponse, AzureMonitorDataSourceInstanceSettings, Location } from '../types'; -let replace = () => ''; +// We want replace to just return the value as is in general/ +// We declare this as a function so that we can overwrite it in each test +// without affecting the rest of the @grafana/runtime module. +let replace = (val: string) => val; jest.mock('@grafana/runtime', () => { return { @@ -251,8 +254,8 @@ describe('AzureMonitorDatasource', () => { const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp'; const expected = basePath + - '/providers/microsoft.insights/components/resource1' + - '/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview®ion=global'; + '/providers/microsoft.insights/components/resource1/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview' + + (path.includes('®ion=global') ? '®ion=global' : ''); expect(path).toBe(expected); return Promise.resolve(response); }); @@ -295,6 +298,24 @@ describe('AzureMonitorDatasource', () => { ); }); }); + + it('when custom is specified will only return custom namespaces', () => { + return ctx.ds.azureMonitorDatasource + .getMetricNamespaces( + { + resourceUri: + '/subscriptions/mock-subscription-id/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1', + }, + false, + undefined, + true + ) + .then((results: Array<{ text: string; value: string }>) => { + expect(results.length).toEqual(1); + expect(results[0].text).toEqual('Azure.ApplicationInsights'); + expect(results[0].value).toEqual('Azure.ApplicationInsights'); + }); + }); }); describe('When performing getMetricNames', () => { diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts index bd610440838..4d77926296b 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts @@ -1,4 +1,3 @@ -import { Namespace } from 'i18next'; import { find, startsWith } from 'lodash'; import { AzureCredentials } from '@grafana/azure-sdk'; @@ -26,6 +25,7 @@ import { Location, ResourceGroup, Metric, + MetricNamespace, } from '../types'; import { routeNames } from '../utils/common'; import migrateQuery from '../utils/migrateQuery'; @@ -238,7 +238,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend< return (await Promise.all(promises)).flat(); } - getMetricNamespaces(query: GetMetricNamespacesQuery, globalRegion: boolean, region?: string) { + // Note globalRegion should be false when querying custom metric namespaces + getMetricNamespaces(query: GetMetricNamespacesQuery, globalRegion: boolean, region?: string, custom?: boolean) { const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( this.resourcePath, this.apiPreviewVersion, @@ -249,7 +250,10 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend< region ); return this.getResource(url) - .then((result: AzureAPIResponse) => { + .then((result: AzureAPIResponse) => { + if (custom) { + result.value = result.value.filter((namespace) => namespace.classification === 'Custom'); + } return ResponseParser.parseResponseValues( result, 'properties.metricNamespaceName', diff --git a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx index ba3def6fe35..d734ba4c65d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx @@ -40,7 +40,16 @@ const defaultProps = { datasource: createMockDatasource({ getSubscriptions: jest.fn().mockResolvedValue([{ text: 'Primary Subscription', value: 'sub' }]), getResourceGroups: jest.fn().mockResolvedValue([{ text: 'rg', value: 'rg' }]), - getMetricNamespaces: jest.fn().mockResolvedValue([{ text: 'foo/bar', value: 'foo/bar' }]), + getMetricNamespaces: jest + .fn() + .mockImplementation( + async (_subscriptionId: string, _resourceGroup?: string, _resourceUri?: string, custom?: boolean) => { + if (custom !== true) { + return [{ text: 'foo/bar', value: 'foo/bar' }]; + } + return [{ text: 'foo/custom', value: 'foo/custom' }]; + } + ), getResourceNames: jest.fn().mockResolvedValue([{ text: 'foobar', value: 'foobar' }]), getVariablesRaw: jest.fn().mockReturnValue([ { label: 'query0', name: 'sub0' }, @@ -350,5 +359,51 @@ describe('VariableEditor:', () => { }) ); }); + + it('should run the query if requesting custom metric namespaces', async () => { + const onChange = jest.fn(); + const { rerender } = render(); + // wait for initial load + await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument()); + await selectAndRerender('select query type', 'Custom Namespaces', onChange, rerender); + await selectAndRerender('select subscription', 'Primary Subscription', onChange, rerender); + await selectAndRerender('select resource group', 'rg', onChange, rerender); + await selectAndRerender('select namespace', 'foo/bar', onChange, rerender); + await selectAndRerender('select resource', 'foobar', onChange, rerender); + expect(onChange).toHaveBeenCalledWith( + expect.objectContaining({ + queryType: AzureQueryType.CustomNamespacesQuery, + subscription: 'sub', + resourceGroup: 'rg', + namespace: 'foo/bar', + resource: 'foobar', + refId: 'A', + }) + ); + }); + + it('should run the query if requesting custom metrics for a resource', async () => { + const onChange = jest.fn(); + const { rerender } = render(); + // wait for initial load + await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument()); + await selectAndRerender('select query type', 'Custom Metric Names', onChange, rerender); + await selectAndRerender('select subscription', 'Primary Subscription', onChange, rerender); + await selectAndRerender('select resource group', 'rg', onChange, rerender); + await selectAndRerender('select namespace', 'foo/bar', onChange, rerender); + await selectAndRerender('select resource', 'foobar', onChange, rerender); + await selectAndRerender('select custom namespace', 'foo/custom', onChange, rerender); + expect(onChange).toHaveBeenCalledWith( + expect.objectContaining({ + queryType: AzureQueryType.CustomMetricNamesQuery, + subscription: 'sub', + resourceGroup: 'rg', + namespace: 'foo/bar', + resource: 'foobar', + refId: 'A', + customNamespace: 'foo/custom', + }) + ); + }); }); }); diff --git a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx index 9cdbaba8ff8..24aad61c8b5 100644 --- a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.tsx @@ -3,8 +3,10 @@ import { useEffect, useState } from 'react'; import { useEffectOnce } from 'react-use'; import { SelectableValue } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; import { Alert, Field, Select, Space } from '@grafana/ui'; +import UrlBuilder from '../../azure_monitor/url_builder'; import DataSource from '../../datasource'; import { selectors } from '../../e2e/selectors'; import { migrateQuery } from '../../grafanaTemplateVariableFns'; @@ -35,6 +37,8 @@ const VariableEditor = (props: Props) => { { label: 'Workspaces', value: AzureQueryType.WorkspacesQuery }, { label: 'Resource Graph', value: AzureQueryType.AzureResourceGraph }, { label: 'Logs', value: AzureQueryType.LogAnalytics }, + { label: 'Custom Namespaces', value: AzureQueryType.CustomNamespacesQuery }, + { label: 'Custom Metric Names', value: AzureQueryType.CustomMetricNamesQuery }, ]; if (typeof props.query === 'object' && props.query.queryType === AzureQueryType.GrafanaTemplateVariableFn) { // Add the option for the GrafanaTemplateVariableFn only if it's already in use @@ -53,10 +57,12 @@ const VariableEditor = (props: Props) => { const [hasRegion, setHasRegion] = useState(false); const [requireResourceGroup, setRequireResourceGroup] = useState(false); const [requireNamespace, setRequireNamespace] = useState(false); + const [requireCustomNamespace, setRequireCustomNamespace] = useState(false); const [requireResource, setRequireResource] = useState(false); const [subscriptions, setSubscriptions] = useState([]); const [resourceGroups, setResourceGroups] = useState([]); const [namespaces, setNamespaces] = useState([]); + const [customNamespaces, setCustomNamespaces] = useState([]); const [resources, setResources] = useState([]); const [regions, setRegions] = useState([]); const [errorMessage, setError] = useLastError(); @@ -77,6 +83,7 @@ const VariableEditor = (props: Props) => { setRequireResourceGroup(false); setRequireNamespace(false); setRequireResource(false); + setRequireCustomNamespace(false); switch (queryType) { case AzureQueryType.ResourceGroupsQuery: case AzureQueryType.WorkspacesQuery: @@ -101,6 +108,19 @@ const VariableEditor = (props: Props) => { case AzureQueryType.LocationsQuery: setRequireSubscription(true); break; + case AzureQueryType.CustomNamespacesQuery: + setRequireSubscription(true); + setRequireResourceGroup(true); + setRequireNamespace(true); + setRequireResource(true); + break; + case AzureQueryType.CustomMetricNamesQuery: + setRequireSubscription(true); + setRequireResourceGroup(true); + setRequireResource(true); + setRequireNamespace(true); + setRequireCustomNamespace(true); + break; } }, [queryType]); @@ -117,6 +137,7 @@ const VariableEditor = (props: Props) => { }); }, [datasource, queryType]); + // Always retrieve subscriptions first as they're used in most template variable queries useEffectOnce(() => { datasource.getSubscriptions().then((subs) => { setSubscriptions(subs.map((s) => ({ label: s.text, value: s.value }))); @@ -124,6 +145,7 @@ const VariableEditor = (props: Props) => { }); const subscription = typeof query === 'object' && query.subscription; + // When subscription is set, retrieve resource groups useEffect(() => { if (subscription) { datasource.getResourceGroups(subscription).then((rgs) => { @@ -133,14 +155,16 @@ const VariableEditor = (props: Props) => { }, [datasource, subscription]); const resourceGroup = (typeof query === 'object' && query.resourceGroup) || ''; + // When resource group is set, retrieve metric namespaces (aka resource types for a custom metric and custom metric namespace query) useEffect(() => { - if (subscription) { + if (subscription && resourceGroup) { datasource.getMetricNamespaces(subscription, resourceGroup).then((rgs) => { setNamespaces(rgs.map((s) => ({ label: s.text, value: s.value }))); }); } }, [datasource, subscription, resourceGroup]); + // When subscription is set also retrieve locations useEffect(() => { if (subscription) { datasource.azureMonitorDatasource.getLocations([subscription]).then((rgs) => { @@ -152,14 +176,31 @@ const VariableEditor = (props: Props) => { }, [datasource, subscription, resourceGroup]); const namespace = (typeof query === 'object' && query.namespace) || ''; + // When subscription, resource group, and namespace are all set, retrieve resource names useEffect(() => { - if (subscription) { + if (subscription && resourceGroup && namespace) { datasource.getResourceNames(subscription, resourceGroup, namespace).then((rgs) => { setResources(rgs.map((s) => ({ label: s.text, value: s.value }))); }); } }, [datasource, subscription, resourceGroup, namespace]); + const resource = (typeof query === 'object' && query.resource) || ''; + // When subscription, resource group, namespace, and resource name are all set, retrieve custom metric namespaces + useEffect(() => { + if (subscription && resourceGroup && namespace && resource) { + const resourceUri = UrlBuilder.buildResourceUri(getTemplateSrv(), { + subscription, + resourceGroup, + metricNamespace: namespace, + resourceName: resource, + }); + datasource.getMetricNamespaces(subscription, resourceGroup, resourceUri, true).then((rgs) => { + setCustomNamespaces(rgs.map((s) => ({ label: s.text, value: s.value }))); + }); + } + }, [datasource, subscription, resourceGroup, namespace, resource]); + if (typeof query === 'string') { // still migrating the query return null; @@ -225,6 +266,13 @@ const VariableEditor = (props: Props) => { onChange(queryChange); }; + const onChangeCustomNamespace = (selectableValue: SelectableValue) => { + onChange({ + ...query, + customNamespace: selectableValue.value, + }); + }; + return ( <> @@ -289,7 +337,14 @@ const VariableEditor = (props: Props) => { )} {(requireNamespace || hasNamespace) && ( - + + + )} {query.queryType === AzureQueryType.AzureResourceGraph && ( <> { expect(result.data[0].fields[0].values).toEqual(expectedResults); }); }); + + it('can fetch custom namespaces', async () => { + const expectedResults = ['test-custom/namespace']; + const variableSupport = new VariableSupport( + createMockDatasource({ + getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults), + }) + ); + const mockRequest = { + targets: [ + { + refId: 'A', + queryType: AzureQueryType.CustomNamespacesQuery, + subscription: 'sub', + resourceGroup: 'rg', + namespace: 'ns', + resource: 'rn', + } as AzureMonitorQuery, + ], + } as DataQueryRequest; + const result = await lastValueFrom(variableSupport.query(mockRequest)); + expect(result.data[0].fields[0].values).toEqual(expectedResults); + }); + + it('returns no data if calling custom namespaces but the subscription is a template variable with no value', async () => { + const variableSupport = new VariableSupport(createMockDatasource()); + const mockRequest = { + targets: [ + { + refId: 'A', + queryType: AzureQueryType.CustomNamespacesQuery, + subscription: '$sub', + resourceGroup: 'rg', + namespace: 'ns', + resource: 'rn', + } as AzureMonitorQuery, + ], + } as DataQueryRequest; + const result = await lastValueFrom(variableSupport.query(mockRequest)); + expect(result.data).toEqual([]); + }); + + it('can fetch custom metric names', async () => { + const expectedResults = ['test-custom-metric']; + const variableSupport = new VariableSupport( + createMockDatasource({ + getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults), + }) + ); + const mockRequest = { + targets: [ + { + refId: 'A', + queryType: AzureQueryType.CustomMetricNamesQuery, + subscription: 'sub', + resourceGroup: 'rg', + namespace: 'ns', + resource: 'rn', + customNamespace: 'test-custom/namespace', + } as AzureMonitorQuery, + ], + } as DataQueryRequest; + const result = await lastValueFrom(variableSupport.query(mockRequest)); + expect(result.data[0].fields[0].values).toEqual(expectedResults); + }); + + it('returns no data if calling custom metric names but the subscription is a template variable with no value', async () => { + const variableSupport = new VariableSupport(createMockDatasource()); + const mockRequest = { + targets: [ + { + refId: 'A', + queryType: AzureQueryType.CustomMetricNamesQuery, + subscription: '$sub', + resourceGroup: 'rg', + namespace: 'ns', + resource: 'rn', + customNamespace: 'test-custom/namespace', + } as AzureMonitorQuery, + ], + } as DataQueryRequest; + const result = await lastValueFrom(variableSupport.query(mockRequest)); + expect(result.data).toEqual([]); + }); }); diff --git a/public/app/plugins/datasource/azuremonitor/variables.ts b/public/app/plugins/datasource/azuremonitor/variables.ts index 0fdfc779e09..794f0283ab8 100644 --- a/public/app/plugins/datasource/azuremonitor/variables.ts +++ b/public/app/plugins/datasource/azuremonitor/variables.ts @@ -9,6 +9,7 @@ import { } from '@grafana/data'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; +import UrlBuilder from './azure_monitor/url_builder'; import VariableEditor from './components/VariableEditor/VariableEditor'; import DataSource from './datasource'; import { migrateQuery } from './grafanaTemplateVariableFns'; @@ -119,6 +120,58 @@ export class VariableSupport extends CustomVariableSupport