import { cx } from '@emotion/css'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Alert, Button, Icon, Input, LoadingPlaceholder, Tooltip, useStyles2, Collapse, Label } from '@grafana/ui'; import ResourcePickerData from '../../resourcePicker/resourcePickerData'; import messageFromError from '../../utils/messageFromError'; import { Space } from '../Space'; import NestedRow from './NestedRow'; import getStyles from './styles'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; import { addResources, findRow, parseResourceURI } from './utils'; interface ResourcePickerProps { resourcePickerData: ResourcePickerData; resourceURI: string | undefined; selectableEntryTypes: ResourceRowType[]; onApply: (resourceURI: string | undefined) => void; onCancel: () => void; } const ResourcePicker = ({ resourcePickerData, resourceURI, onApply, onCancel, selectableEntryTypes, }: ResourcePickerProps) => { const styles = useStyles2(getStyles); type LoadingStatus = 'NotStarted' | 'Started' | 'Done'; const [loadingStatus, setLoadingStatus] = useState('NotStarted'); const [azureRows, setAzureRows] = useState([]); const [internalSelectedURI, setInternalSelectedURI] = useState(resourceURI); const [errorMessage, setErrorMessage] = useState(undefined); const [isAdvancedOpen, setIsAdvancedOpen] = useState(resourceURI?.includes('$')); // Sync the resourceURI prop to internal state useEffect(() => { setInternalSelectedURI(resourceURI); }, [resourceURI]); // Request initial data on first mount useEffect(() => { if (loadingStatus === 'NotStarted') { 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); setLoadingStatus('Done'); } catch (error) { setLoadingStatus('Done'); setErrorMessage(messageFromError(error)); } }; loadInitialData(); } }, [resourcePickerData, internalSelectedURI, azureRows, loadingStatus]); // Map the selected item into an array of rows const selectedResourceRows = useMemo(() => { const found = internalSelectedURI && findRow(azureRows, internalSelectedURI); return found ? [ { ...found, children: undefined, }, ] : []; }, [internalSelectedURI, azureRows]); // Request resources for a expanded resource group const requestNestedRows = useCallback( async (resourceGroupOrSubscription: 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) { 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); } catch (error) { setErrorMessage(messageFromError(error)); throw error; } }, [resourcePickerData, azureRows] ); const handleSelectionChanged = useCallback((row: ResourceRow, isSelected: boolean) => { isSelected ? setInternalSelectedURI(row.uri) : setInternalSelectedURI(undefined); }, []); const handleApply = useCallback(() => { onApply(internalSelectedURI); }, [internalSelectedURI, onApply]); return (
{loadingStatus === 'Started' ? (
) : ( <>
Scope Type Location
{azureRows.map((row) => ( ))}
{selectedResourceRows.length > 0 && ( <>
Selection
{selectedResourceRows.map((row) => ( ))}
)} setIsAdvancedOpen(!isAdvancedOpen)} > setInternalSelectedURI(event.currentTarget.value)} placeholder="ex: /subscriptions/$subId" />
)} {errorMessage && ( <> {errorMessage} )}
); }; export default ResourcePicker;