diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/ResourceField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/ResourceField.tsx
index 2a2254319085..7d69c8ab17bd 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/ResourceField.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/ResourceField.tsx
@@ -17,10 +17,9 @@ function parseResourceDetails(resourceURI: string) {
}
return {
- id: resourceURI,
subscriptionName: parsed.subscriptionID,
resourceGroupName: parsed.resourceGroup,
- name: parsed.resource,
+ resourceName: parsed.resource,
};
}
@@ -85,7 +84,7 @@ const ResourceLabel = ({ resource, datasource }: ResourceLabelProps) => {
useEffect(() => {
if (resource && parseResourceDetails(resource)) {
- datasource.resourcePickerData.getResource(resource).then(setResourceComponents);
+ datasource.resourcePickerData.getResourceURIDisplayProperties(resource).then(setResourceComponents);
} else {
setResourceComponents(undefined);
}
@@ -118,10 +117,18 @@ const FormattedResource = ({ resource }: FormattedResourceProps) => {
return (
{resource.subscriptionName}
-
- {resource.resourceGroupName}
-
- {resource.name}
+ {resource.resourceGroupName && (
+ <>
+
+ {resource.resourceGroupName}
+ >
+ )}
+ {resource.resourceName && (
+ <>
+
+ {resource.resourceName}
+ >
+ )}
);
};
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx
index a238b9e5e9b5..78407c3ba5b3 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx
@@ -1,6 +1,7 @@
import { cx } from '@emotion/css';
import { Checkbox, Icon, IconButton, LoadingPlaceholder, useStyles2, useTheme2, FadeTransition } from '@grafana/ui';
import React, { useCallback, useEffect, useState } from 'react';
+import { Space } from '../Space';
import getStyles from './styles';
import { ResourceRowType, ResourceRow, ResourceRowGroup } from './types';
import { findRow } from './utils';
@@ -163,7 +164,9 @@ const NestedEntry: React.FC = ({
const theme = useTheme2();
const styles = useStyles2(getStyles);
const hasChildren = !!entry.children;
- const isSelectable = entry.type === ResourceRowType.Resource || entry.type === ResourceRowType.Variable;
+ // Subscriptions, resource groups, resources, and variables are all selectable, so
+ // the top-level variable group is the only thing that cannot be selected.
+ const isSelectable = entry.type !== ResourceRowType.VariableGroup;
const handleToggleCollapse = useCallback(() => {
onToggleCollapse(entry);
@@ -185,7 +188,7 @@ const NestedEntry: React.FC = ({
of the collapse button for leaf rows that have no children to get them to align */}
- {hasChildren && (
+ {hasChildren ? (
= ({
onClick={handleToggleCollapse}
id={entry.id}
/>
+ ) : (
+
)}
+
- {isSelectable && (
+ {isSelectable && (
+
- )}
-
+
+ )}
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/index.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/index.tsx
index c4f0d88e68c7..9c368f9cb1fd 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/index.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/index.tsx
@@ -42,7 +42,14 @@ const ResourcePicker = ({
// Map the selected item into an array of rows
const selectedResourceRows = useMemo(() => {
const found = internalSelected && findRow(rows, internalSelected);
- return found ? [found] : [];
+ return found
+ ? [
+ {
+ ...found,
+ children: undefined,
+ },
+ ]
+ : [];
}, [internalSelected, rows]);
// Request resources for a expanded resource group
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts
index 95f95ee70252..dc039a859c87 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts
@@ -29,13 +29,13 @@ const getStyles = (theme: GrafanaTheme2) => ({
}),
cell: css({
- padding: theme.spacing(1, 0),
+ padding: theme.spacing(1, 1, 1, 0),
width: '25%',
overflow: 'hidden',
textOverflow: 'ellipsis',
'&:first-of-type': {
width: '50%',
- padding: theme.spacing(1, 0, 1, 2),
+ padding: theme.spacing(1, 1, 1, 2),
},
}),
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
new file mode 100644
index 000000000000..58dd86f5be82
--- /dev/null
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.test.ts
@@ -0,0 +1,36 @@
+import { parseResourceURI } from './utils';
+
+describe('AzureMonitor ResourcePicker utils', () => {
+ describe('parseResourceURI', () => {
+ it('should parse subscription URIs', () => {
+ expect(parseResourceURI('/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572')).toEqual({
+ subscriptionID: '44693801-6ee6-49de-9b2d-9106972f9572',
+ });
+ });
+
+ it('should parse resource group URIs', () => {
+ expect(
+ parseResourceURI('/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources')
+ ).toEqual({
+ subscriptionID: '44693801-6ee6-49de-9b2d-9106972f9572',
+ resourceGroup: 'cloud-datasources',
+ });
+ });
+
+ it('should parse resource URIs', () => {
+ expect(
+ parseResourceURI(
+ '/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/Microsoft.Compute/virtualMachines/GithubTestDataVM'
+ )
+ ).toEqual({
+ subscriptionID: '44693801-6ee6-49de-9b2d-9106972f9572',
+ resourceGroup: 'cloud-datasources',
+ resource: 'GithubTestDataVM',
+ });
+ });
+
+ it('returns undefined for invalid input', () => {
+ expect(parseResourceURI('44693801-6ee6-49de-9b2d-9106972f9572')).toBeUndefined();
+ });
+ });
+});
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 ab9f0af4b24b..5d6d8fdd5984 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
@@ -1,16 +1,23 @@
import produce from 'immer';
import { ResourceRow, ResourceRowGroup } from './types';
-const RESOURCE_URI_REGEX = /\/subscriptions\/(?.+)\/resourceGroups\/(?.+)\/providers.+\/(?[\w-_]+)/;
+// This regex matches URIs representing:
+// - subscriptions: /subscriptions/44693801-6ee6-49de-9b2d-9106972f9572
+// - resource groups: /subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources
+// - resources: /subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/Microsoft.Compute/virtualMachines/GithubTestDataVM
+const RESOURCE_URI_REGEX = /\/subscriptions\/(?[^/]+)(?:\/resourceGroups\/(?[^/]+)(?:\/providers.+\/(?[^/]+))?)?/;
+
+type RegexGroups = Record;
export function parseResourceURI(resourceURI: string) {
const matches = RESOURCE_URI_REGEX.exec(resourceURI);
+ const groups: RegexGroups = matches?.groups ?? {};
+ const { subscriptionID, resourceGroup, resource } = groups;
- if (!matches?.groups?.subscriptionID || !matches?.groups?.resourceGroup) {
+ if (!subscriptionID) {
return undefined;
}
- const { subscriptionID, resourceGroup, resource } = matches.groups;
return { subscriptionID, resourceGroup, resource };
}
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.ts
index 87af244c534c..4ee61891a775 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.ts
@@ -1,6 +1,7 @@
import { FetchResponse, getBackendSrv } from '@grafana/runtime';
import { getLogAnalyticsResourcePickerApiRoute } from '../api/routes';
import { ResourceRowType, ResourceRow, ResourceRowGroup } from '../components/ResourcePicker/types';
+import { parseResourceURI } from '../components/ResourcePicker/utils';
import { getAzureCloud } from '../credentials';
import {
AzureDataSourceInstanceSettings,
@@ -73,21 +74,38 @@ export default class ResourcePickerData {
return formatResourceGroupChildren(response.data);
}
- async getResource(resourceURI: string) {
+ async getResourceURIDisplayProperties(resourceURI: string): Promise {
+ const { subscriptionID, resourceGroup } = parseResourceURI(resourceURI) ?? {};
+
+ if (!subscriptionID) {
+ throw new Error('Invalid resource URI passed');
+ }
+
+ // resourceGroupURI and resourceURI could be invalid values, but that's okay because the join
+ // will just silently fail as expected
+ const subscriptionURI = `/subscriptions/${subscriptionID}`;
+ const resourceGroupURI = `${subscriptionURI}/resourceGroups/${resourceGroup}`;
+
const query = `
- resources
- | join (
- resourcecontainers
- | where type == "microsoft.resources/subscriptions"
- | project subscriptionName=name, subscriptionId
- ) on subscriptionId
- | join (
- resourcecontainers
- | where type == "microsoft.resources/subscriptions/resourcegroups"
- | project resourceGroupName=name, resourceGroup
- ) on resourceGroup
- | where id == "${resourceURI}"
- | project id, name, subscriptionName, resourceGroupName
+ resourcecontainers
+ | where type == "microsoft.resources/subscriptions"
+ | where id == "${subscriptionURI}"
+ | project subscriptionName=name, subscriptionId
+
+ | join kind=leftouter (
+ resourcecontainers
+ | where type == "microsoft.resources/subscriptions/resourcegroups"
+ | where id == "${resourceGroupURI}"
+ | project resourceGroupName=name, resourceGroup, subscriptionId
+ ) on subscriptionId
+
+ | join kind=leftouter (
+ resources
+ | where id == "${resourceURI}"
+ | project resourceName=name, subscriptionId
+ ) on subscriptionId
+
+ | project subscriptionName, resourceGroupName, resourceName
`;
const { ok, data: response } = await this.makeResourceGraphRequest(query);
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/index.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/index.ts
index 22b6c64dc7d6..cb696c186dad 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/index.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/index.ts
@@ -230,10 +230,9 @@ export interface AzureQueryEditorFieldProps {
}
export interface AzureResourceSummaryItem {
- id: string;
- name: string;
subscriptionName: string;
- resourceGroupName: string;
+ resourceGroupName: string | undefined;
+ resourceName: string | undefined;
}
export interface RawAzureResourceGroupItem {