diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/resourcePickerData.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/resourcePickerData.ts deleted file mode 100644 index e4659041b64..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/resourcePickerData.ts +++ /dev/null @@ -1,20 +0,0 @@ -import ResourcePicker from '../resourcePicker/resourcePickerData'; - -type DeepPartial = { - [P in keyof T]?: DeepPartial; -}; - -export default function createMockResourcePickerData(overrides?: DeepPartial) { - const _mockResourcePicker: DeepPartial = { - getSubscriptions: () => jest.fn().mockResolvedValue([]), - getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue([]), - getResourcesForResourceGroup: jest.fn().mockResolvedValue([]), - getResourceURIFromWorkspace: jest.fn().mockReturnValue(''), - getResourceURIDisplayProperties: jest.fn().mockResolvedValue({}), - ...overrides, - }; - - const mockDatasource = _mockResourcePicker as ResourcePicker; - - return jest.mocked(mockDatasource, true); -} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx index 4a0400bec9e..a2eaad574af 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -5,13 +5,14 @@ import React from 'react'; import { selectOptionInTest } from '@grafana/ui'; import createMockDatasource from '../../__mocks__/datasource'; +import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; import createMockQuery from '../../__mocks__/query'; -import createMockResourcePickerData from '../../__mocks__/resourcePickerData'; import { createMockResourceGroupsBySubscription, createMockSubscriptions, mockResourcesByResourceGroup, } from '../../__mocks__/resourcePickerRows'; +import ResourcePickerData from '../../resourcePicker/resourcePickerData'; import MetricsQueryEditor from './MetricsQueryEditor'; @@ -20,11 +21,19 @@ const variableOptionGroup = { options: [], }; -const resourcePickerData = createMockResourcePickerData({ - getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()), - getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue(createMockResourceGroupsBySubscription()), - getResourcesForResourceGroup: jest.fn().mockResolvedValue(mockResourcesByResourceGroup()), -}); +export function createMockResourcePickerData() { + const mockDatasource = new ResourcePickerData(createMockInstanceSetttings()); + + mockDatasource.getSubscriptions = jest.fn().mockResolvedValue(createMockSubscriptions()); + mockDatasource.getResourceGroupsBySubscriptionId = jest + .fn() + .mockResolvedValue(createMockResourceGroupsBySubscription()); + mockDatasource.getResourcesForResourceGroup = jest.fn().mockResolvedValue(mockResourcesByResourceGroup()); + mockDatasource.getResourceURIFromWorkspace = jest.fn().mockReturnValue(''); + mockDatasource.getResourceURIDisplayProperties = jest.fn().mockResolvedValue({}); + + return mockDatasource; +} describe('MetricsQueryEditor', () => { const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView; @@ -36,7 +45,7 @@ describe('MetricsQueryEditor', () => { }); it('should render', async () => { - const mockDatasource = createMockDatasource({ resourcePickerData }); + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); render( { }); it('should change resource when a resource is selected in the ResourcePicker', async () => { - const mockDatasource = createMockDatasource({ resourcePickerData }); + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); const query = createMockQuery(); delete query?.azureMonitor?.resourceUri; const onChange = jest.fn(); @@ -101,7 +110,7 @@ describe('MetricsQueryEditor', () => { }); it('should reset metric namespace, metric name, and aggregation fields after selecting a new resource when a valid query has already been set', async () => { - const mockDatasource = createMockDatasource({ resourcePickerData }); + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); const query = createMockQuery(); const onChange = jest.fn(); @@ -159,7 +168,7 @@ describe('MetricsQueryEditor', () => { }); it('should change the metric name when selected', async () => { - const mockDatasource = createMockDatasource({ resourcePickerData }); + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); const onChange = jest.fn(); const mockQuery = createMockQuery(); mockDatasource.azureMonitorDatasource.getMetricNames = jest.fn().mockResolvedValue([ @@ -199,7 +208,7 @@ describe('MetricsQueryEditor', () => { }); it('should change the aggregation type when selected', async () => { - const mockDatasource = createMockDatasource({ resourcePickerData }); + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); const onChange = jest.fn(); const mockQuery = createMockQuery(); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx index 645ea671155..4853518af02 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx @@ -2,12 +2,13 @@ import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import createMockResourcePickerData from '../../__mocks__/resourcePickerData'; +import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; import { createMockResourceGroupsBySubscription, createMockSubscriptions, mockResourcesByResourceGroup, } from '../../__mocks__/resourcePickerRows'; +import ResourcePickerData from '../../resourcePicker/resourcePickerData'; import { ResourceRowType } from './types'; @@ -20,14 +21,24 @@ const singleResourceSelectionURI = '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server'; const noop: any = () => {}; +function createMockResourcePickerData() { + const mockDatasource = new ResourcePickerData(createMockInstanceSetttings()); + + mockDatasource.getSubscriptions = jest.fn().mockResolvedValue(createMockSubscriptions()); + mockDatasource.getResourceGroupsBySubscriptionId = jest + .fn() + .mockResolvedValue(createMockResourceGroupsBySubscription()); + mockDatasource.getResourcesForResourceGroup = jest.fn().mockResolvedValue(mockResourcesByResourceGroup()); + mockDatasource.getResourceURIFromWorkspace = jest.fn().mockReturnValue(''); + mockDatasource.getResourceURIDisplayProperties = jest.fn().mockResolvedValue({}); + + return mockDatasource; +} + const defaultProps = { templateVariables: [], resourceURI: noResourceURI, - resourcePickerData: createMockResourcePickerData({ - getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()), - getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue(createMockResourceGroupsBySubscription()), - getResourcesForResourceGroup: jest.fn().mockResolvedValue(mockResourcesByResourceGroup()), - }), + resourcePickerData: createMockResourcePickerData(), onCancel: noop, onApply: noop, selectableEntryTypes: [ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx index 5929cd8efe0..47b7265289b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx @@ -10,7 +10,7 @@ import { Space } from '../Space'; import NestedRow from './NestedRow'; import getStyles from './styles'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; -import { addResources, findRow, parseResourceURI } from './utils'; +import { findRow } from './utils'; interface ResourcePickerProps { resourcePickerData: ResourcePickerData; @@ -32,7 +32,7 @@ const ResourcePicker = ({ type LoadingStatus = 'NotStarted' | 'Started' | 'Done'; const [loadingStatus, setLoadingStatus] = useState('NotStarted'); - const [azureRows, setAzureRows] = useState([]); + const [rows, setRows] = useState([]); const [internalSelectedURI, setInternalSelectedURI] = useState(resourceURI); const [errorMessage, setErrorMessage] = useState(undefined); const [isAdvancedOpen, setIsAdvancedOpen] = useState(resourceURI?.includes('$')); @@ -47,32 +47,8 @@ const ResourcePicker = ({ const loadInitialData = async () => { try { setLoadingStatus('Started'); - let resources = await resourcePickerData.getSubscriptions(); - if (!internalSelectedURI) { - setAzureRows(resources); - setLoadingStatus('Done'); - return; - } - - const parsedURI = parseResourceURI(internalSelectedURI ?? ''); - if (parsedURI) { - const resourceGroupURI = `/subscriptions/${parsedURI.subscriptionID}/resourceGroups/${parsedURI.resourceGroup}`; - - // if a resource group was previously selected, but the resource groups under the parent subscription have not been loaded yet - if (parsedURI.resourceGroup && !findRow(resources, resourceGroupURI)) { - const resourceGroups = await resourcePickerData.getResourceGroupsBySubscriptionId( - parsedURI.subscriptionID - ); - resources = addResources(resources, `/subscriptions/${parsedURI.subscriptionID}`, resourceGroups); - } - - // if a resource was previously selected, but the resources under the parent resource group have not been loaded yet - if (parsedURI.resource && !findRow(azureRows, parsedURI.resource ?? '')) { - const resourcesForResourceGroup = await resourcePickerData.getResourcesForResourceGroup(resourceGroupURI); - resources = addResources(resources, resourceGroupURI, resourcesForResourceGroup); - } - } - setAzureRows(resources); + const resources = await resourcePickerData.fetchInitialRows(internalSelectedURI || ''); + setRows(resources); setLoadingStatus('Done'); } catch (error) { setLoadingStatus('Done'); @@ -82,11 +58,11 @@ const ResourcePicker = ({ loadInitialData(); } - }, [resourcePickerData, internalSelectedURI, azureRows, loadingStatus]); + }, [resourcePickerData, internalSelectedURI, rows, loadingStatus]); // Map the selected item into an array of rows const selectedResourceRows = useMemo(() => { - const found = internalSelectedURI && findRow(azureRows, internalSelectedURI); + const found = internalSelectedURI && findRow(rows, internalSelectedURI); return found ? [ @@ -96,34 +72,28 @@ const ResourcePicker = ({ }, ] : []; - }, [internalSelectedURI, azureRows]); + }, [internalSelectedURI, rows]); // Request resources for a expanded resource group const requestNestedRows = useCallback( - async (resourceGroupOrSubscription: ResourceRow) => { + async (parentRow: ResourceRow) => { // clear error message (also when loading cached resources) setErrorMessage(undefined); // If we already have children, we don't need to re-fetch them. - if (resourceGroupOrSubscription.children?.length) { + if (parentRow.children?.length) { return; } try { - const rows = - resourceGroupOrSubscription.type === ResourceRowType.Subscription - ? await resourcePickerData.getResourceGroupsBySubscriptionId(resourceGroupOrSubscription.id) - : await resourcePickerData.getResourcesForResourceGroup(resourceGroupOrSubscription.id); - - const newRows = addResources(azureRows, resourceGroupOrSubscription.uri, rows); - - setAzureRows(newRows); + const nestedRows = await resourcePickerData.fetchAndAppendNestedRow(rows, parentRow); + setRows(nestedRows); } catch (error) { setErrorMessage(messageFromError(error)); throw error; } }, - [resourcePickerData, azureRows] + [resourcePickerData, rows] ); const handleSelectionChanged = useCallback((row: ResourceRow, isSelected: boolean) => { @@ -155,7 +125,7 @@ const ResourcePicker = ({
- {azureRows.map((row) => ( + {rows.map((row) => ( { + const subscriptions = await this.getSubscriptions(); + if (!currentSelection) { + return subscriptions; + } + + let resources = subscriptions; + const parsedURI = parseResourceURI(currentSelection); + if (parsedURI) { + const resourceGroupURI = `/subscriptions/${parsedURI.subscriptionID}/resourceGroups/${parsedURI.resourceGroup}`; + + if (parsedURI.resourceGroup) { + const resourceGroups = await this.getResourceGroupsBySubscriptionId(parsedURI.subscriptionID); + resources = addResources(resources, `/subscriptions/${parsedURI.subscriptionID}`, resourceGroups); + } + + if (parsedURI.resource) { + const resourcesForResourceGroup = await this.getResourcesForResourceGroup(resourceGroupURI); + resources = addResources(resources, resourceGroupURI, resourcesForResourceGroup); + } + } + return resources; + } + + async fetchAndAppendNestedRow(rows: ResourceRowGroup, parentRow: ResourceRow): Promise { + const nestedRows = + parentRow.type === ResourceRowType.Subscription + ? await this.getResourceGroupsBySubscriptionId(parentRow.id) + : await this.getResourcesForResourceGroup(parentRow.id); + + return addResources(rows, parentRow.uri, nestedRows); + } + + // private async getSubscriptions(): Promise { const query = ` resources