diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts
index 93f1b68cca3..e59c3a40c3a 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts
@@ -321,5 +321,25 @@ describe('AzureMonitorUrlBuilder', () => {
);
});
});
+
+ describe('when metric definition does not contain a metric namespace', () => {
+ it('should build the getMetricNames url in the longer format', () => {
+ const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
+ '',
+ '2017-05-01-preview',
+ {
+ subscription: 'sub1',
+ resourceGroup: 'rg',
+ metricDefinition: 'microsoft.compute/virtualmachines',
+ resourceName: 'rn1',
+ },
+ templateSrv
+ );
+ expect(url).toBe(
+ '/subscriptions/sub1/resourceGroups/rg/providers/microsoft.compute/virtualmachines/rn1/' +
+ 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview'
+ );
+ });
+ });
});
});
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts
index b2a9b1212bb..25c92753dc5 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts
@@ -82,9 +82,10 @@ export default class UrlBuilder {
);
}
- return (
- `${baseUrl}${resourceUri}/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}` +
- `&metricnamespace=${encodeURIComponent(metricNamespace)}`
- );
+ let url = `${baseUrl}${resourceUri}/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}`;
+ if (metricNamespace) {
+ url += `&metricnamespace=${encodeURIComponent(metricNamespace)}`;
+ }
+ return url;
}
}
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx
index 7c4c6cef5a1..6291edb1814 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx
@@ -33,6 +33,7 @@ const defaultProps = {
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' }]),
+ getResourceNames: jest.fn().mockResolvedValue([{ text: 'foobar', value: 'foobar' }]),
getVariablesRaw: jest.fn().mockReturnValue([
{ label: 'query0', name: 'sub0' },
{ label: 'query1', name: 'rg', query: { queryType: AzureQueryType.ResourceGroupsQuery } },
@@ -143,8 +144,24 @@ describe('VariableEditor:', () => {
});
describe('predefined queries:', () => {
- it('should show the new query types if feature gate is enabled', async () => {
+ const selectAndRerender = async (
+ label: string,
+ text: string,
+ onChange: jest.Mock,
+ rerender: (ui: React.ReactElement) => void
+ ) => {
+ openMenu(screen.getByLabelText(label));
+ screen.getByText(text).click();
+ // Simulate onChange behavior
+ const newQuery = onChange.mock.calls.at(-1)[0];
+ rerender();
+ await waitFor(() => expect(screen.getByText(text)).toBeInTheDocument());
+ };
+ beforeEach(() => {
grafanaRuntime.config.featureToggles.azTemplateVars = true;
+ });
+
+ it('should show the new query types if feature gate is enabled', async () => {
render();
openMenu(screen.getByLabelText('select query type'));
await waitFor(() => expect(screen.getByText('Subscriptions')).toBeInTheDocument());
@@ -158,36 +175,21 @@ describe('VariableEditor:', () => {
});
it('should run the query if requesting subscriptions', async () => {
- grafanaRuntime.config.featureToggles.azTemplateVars = true;
const onChange = jest.fn();
const { rerender } = render();
- openMenu(screen.getByLabelText('select query type'));
- screen.getByText('Subscriptions').click();
- // Simulate onChange behavior
- const newQuery = onChange.mock.calls.at(-1)[0];
- rerender();
- await waitFor(() => expect(screen.getByText('Subscriptions')).toBeInTheDocument());
+ await selectAndRerender('select query type', 'Subscriptions', onChange, rerender);
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({ queryType: AzureQueryType.SubscriptionsQuery, refId: 'A' })
);
});
it('should run the query if requesting resource groups', async () => {
- grafanaRuntime.config.featureToggles.azTemplateVars = true;
const onChange = jest.fn();
const { rerender } = render();
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
- // Select RGs variable
- openMenu(screen.getByLabelText('select query type'));
- screen.getByText('Resource Groups').click();
- // Simulate onChange behavior
- const newQuery = onChange.mock.calls.at(-1)[0];
- rerender();
- await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument());
- // Select a subscription
- openMenu(screen.getByLabelText('select subscription'));
- screen.getByText('Primary Subscription').click();
+ await selectAndRerender('select query type', 'Resource Groups', onChange, rerender);
+ await selectAndRerender('select subscription', 'Primary Subscription', onChange, rerender);
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
queryType: AzureQueryType.ResourceGroupsQuery,
@@ -199,17 +201,10 @@ describe('VariableEditor:', () => {
it('should show template variables as options ', async () => {
const onChange = jest.fn();
- grafanaRuntime.config.featureToggles.azTemplateVars = true;
const { rerender } = render();
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
- // Select RGs variable
- openMenu(screen.getByLabelText('select query type'));
- screen.getByText('Resource Groups').click();
- // Simulate onChange behavior
- const newQuery = onChange.mock.calls.at(-1)[0];
- rerender();
- await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument());
+ await selectAndRerender('select query type', 'Resource Groups', onChange, rerender);
// Select a subscription
openMenu(screen.getByLabelText('select subscription'));
await waitFor(() => expect(screen.getByText('Primary Subscription')).toBeInTheDocument());
@@ -223,21 +218,12 @@ describe('VariableEditor:', () => {
});
it('should run the query if requesting namespaces', async () => {
- grafanaRuntime.config.featureToggles.azTemplateVars = true;
const onChange = jest.fn();
const { rerender } = render();
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
- // Select RGs variable
- openMenu(screen.getByLabelText('select query type'));
- screen.getByText('Namespaces').click();
- // Simulate onChange behavior
- const newQuery = onChange.mock.calls.at(-1)[0];
- rerender();
- await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument());
- // Select a subscription
- openMenu(screen.getByLabelText('select subscription'));
- screen.getByText('Primary Subscription').click();
+ await selectAndRerender('select query type', 'Namespaces', onChange, rerender);
+ await selectAndRerender('select subscription', 'Primary Subscription', onChange, rerender);
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
queryType: AzureQueryType.NamespacesQuery,
@@ -248,21 +234,12 @@ describe('VariableEditor:', () => {
});
it('should run the query if requesting resource names', async () => {
- grafanaRuntime.config.featureToggles.azTemplateVars = true;
const onChange = jest.fn();
const { rerender } = render();
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
- // Select RGs variable
- openMenu(screen.getByLabelText('select query type'));
- screen.getByText('Resource Names').click();
- // Simulate onChange behavior
- const newQuery = onChange.mock.calls.at(-1)[0];
- rerender();
- await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument());
- // Select a subscription
- openMenu(screen.getByLabelText('select subscription'));
- screen.getByText('Primary Subscription').click();
+ await selectAndRerender('select query type', 'Resource Names', onChange, rerender);
+ await selectAndRerender('select subscription', 'Primary Subscription', onChange, rerender);
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
queryType: AzureQueryType.ResourceNamesQuery,
@@ -271,5 +248,43 @@ describe('VariableEditor:', () => {
})
);
});
+
+ it('should run the query if requesting metric names', async () => {
+ const onChange = jest.fn();
+ const { rerender } = render();
+ // wait for initial load
+ await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
+ await selectAndRerender('select query type', '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);
+ expect(onChange).toHaveBeenCalledWith(
+ expect.objectContaining({
+ queryType: AzureQueryType.MetricNamesQuery,
+ subscription: 'sub',
+ resourceGroup: 'rg',
+ namespace: 'foo/bar',
+ resource: 'foobar',
+ refId: 'A',
+ })
+ );
+ });
+
+ it('should run the query if requesting workspaces', async () => {
+ const onChange = jest.fn();
+ const { rerender } = render();
+ // wait for initial load
+ await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
+ await selectAndRerender('select query type', 'Workspaces', onChange, rerender);
+ await selectAndRerender('select subscription', 'Primary Subscription', onChange, rerender);
+ expect(onChange).toHaveBeenCalledWith(
+ expect.objectContaining({
+ queryType: AzureQueryType.WorkspacesQuery,
+ subscription: 'sub',
+ refId: 'A',
+ })
+ );
+ });
});
});
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx
index c2abb163768..9626f13685f 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx
@@ -34,6 +34,8 @@ const VariableEditor = (props: Props) => {
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Resource Groups', value: AzureQueryType.ResourceGroupsQuery });
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Namespaces', value: AzureQueryType.NamespacesQuery });
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Resource Names', value: AzureQueryType.ResourceNamesQuery });
+ AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Metric Names', value: AzureQueryType.MetricNamesQuery });
+ AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Workspaces', value: AzureQueryType.WorkspacesQuery });
}
const [variableOptionGroup, setVariableOptionGroup] = useState<{ label: string; options: AzureMonitorOption[] }>({
label: 'Template Variables',
@@ -42,9 +44,13 @@ const VariableEditor = (props: Props) => {
const [requireSubscription, setRequireSubscription] = useState(false);
const [hasResourceGroup, setHasResourceGroup] = useState(false);
const [hasNamespace, setHasNamespace] = useState(false);
+ const [requireResourceGroup, setRequireResourceGroup] = useState(false);
+ const [requireNamespace, setRequireNamespace] = useState(false);
+ const [requireResource, setRequireResource] = useState(false);
const [subscriptions, setSubscriptions] = useState([]);
const [resourceGroups, setResourceGroups] = useState([]);
const [namespaces, setNamespaces] = useState([]);
+ const [resources, setResources] = useState([]);
const [errorMessage, setError] = useLastError();
const queryType = typeof query === 'string' ? '' : query.queryType;
@@ -58,8 +64,12 @@ const VariableEditor = (props: Props) => {
setRequireSubscription(false);
setHasResourceGroup(false);
setHasNamespace(false);
+ setRequireResourceGroup(false);
+ setRequireNamespace(false);
+ setRequireResource(false);
switch (queryType) {
case AzureQueryType.ResourceGroupsQuery:
+ case AzureQueryType.WorkspacesQuery:
setRequireSubscription(true);
break;
case AzureQueryType.NamespacesQuery:
@@ -71,6 +81,12 @@ const VariableEditor = (props: Props) => {
setHasResourceGroup(true);
setHasNamespace(true);
break;
+ case AzureQueryType.MetricNamesQuery:
+ setRequireSubscription(true);
+ setRequireResourceGroup(true);
+ setRequireNamespace(true);
+ setRequireResource(true);
+ break;
}
}, [queryType]);
@@ -111,6 +127,15 @@ const VariableEditor = (props: Props) => {
}
}, [datasource, subscription, resourceGroup]);
+ const namespace = (typeof query === 'object' && query.namespace) || '';
+ useEffect(() => {
+ if (subscription) {
+ datasource.getResourceNames(subscription, resourceGroup, namespace).then((rgs) => {
+ setResources(rgs.map((s) => ({ label: s.text, value: s.value })));
+ });
+ }
+ }, [datasource, subscription, resourceGroup, namespace]);
+
if (typeof query === 'string') {
// still migrating the query
return null;
@@ -148,6 +173,13 @@ const VariableEditor = (props: Props) => {
});
};
+ const onChangeResource = (selectableValue: SelectableValue) => {
+ onChange({
+ ...query,
+ resource: selectableValue.value,
+ });
+ };
+
const onLogsQueryChange = (queryChange: AzureMonitorQuery) => {
onChange(queryChange);
};
@@ -198,27 +230,46 @@ const VariableEditor = (props: Props) => {
/>
)}
- {hasResourceGroup && (
-
+ {(requireResourceGroup || hasResourceGroup) && (
+
)}
- {hasNamespace && (
-
+ {(requireNamespace || hasNamespace) && (
+
+
+ )}
+ {requireResource && (
+
+
)}
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts
index 49f5b6618fb..40de38a9d07 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts
@@ -170,6 +170,15 @@ export default class Datasource extends DataSourceWithBackend {
done();
});
});
+
+ it('can fetch metric names', (done) => {
+ const expectedResults = ['test'];
+ const variableSupport = new VariableSupport(
+ createMockDatasource({
+ getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults),
+ })
+ );
+ const mockRequest = {
+ targets: [
+ {
+ refId: 'A',
+ queryType: AzureQueryType.MetricNamesQuery,
+ subscription: 'sub',
+ resourceGroup: 'rg',
+ namespace: 'ns',
+ resource: 'rn',
+ } as AzureMonitorQuery,
+ ],
+ } as DataQueryRequest;
+ const observables = variableSupport.query(mockRequest);
+ observables.subscribe((result: DataQueryResponseData) => {
+ expect(result.data[0].source).toEqual(expectedResults);
+ done();
+ });
+ });
+
+ it('can fetch workspaces', (done) => {
+ const expectedResults = ['test'];
+ const variableSupport = new VariableSupport(
+ createMockDatasource({
+ getAzureLogAnalyticsWorkspaces: jest.fn().mockResolvedValueOnce(expectedResults),
+ })
+ );
+ const mockRequest = {
+ targets: [
+ {
+ refId: 'A',
+ queryType: AzureQueryType.WorkspacesQuery,
+ subscription: 'sub',
+ } as AzureMonitorQuery,
+ ],
+ } as DataQueryRequest;
+ const observables = variableSupport.query(mockRequest);
+ observables.subscribe((result: DataQueryResponseData) => {
+ expect(result.data[0].source).toEqual(expectedResults);
+ done();
+ });
+ });
});
});
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts
index d3dd1d6d73b..0315cda03db 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts
@@ -61,6 +61,25 @@ export class VariableSupport extends CustomVariableSupport