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 { findRow } 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 [rows, setRows] = 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'); const resources = await resourcePickerData.fetchInitialRows(internalSelectedURI || ''); setRows(resources); setLoadingStatus('Done'); } catch (error) { setLoadingStatus('Done'); setErrorMessage(messageFromError(error)); } }; loadInitialData(); } }, [resourcePickerData, internalSelectedURI, rows, loadingStatus]); // Map the selected item into an array of rows const selectedResourceRows = useMemo(() => { const found = internalSelectedURI && findRow(rows, internalSelectedURI); return found ? [ { ...found, children: undefined, }, ] : []; }, [internalSelectedURI, rows]); // Request resources for a expanded resource group const requestNestedRows = useCallback( 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 (parentRow.children?.length) { return; } try { const nestedRows = await resourcePickerData.fetchAndAppendNestedRow(rows, parentRow); setRows(nestedRows); } catch (error) { setErrorMessage(messageFromError(error)); throw error; } }, [resourcePickerData, rows] ); 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
{rows.map((row) => ( ))}
{selectedResourceRows.length > 0 && ( <>
Selection
{selectedResourceRows.map((row) => ( ))}
)} setIsAdvancedOpen(!isAdvancedOpen)} > setInternalSelectedURI(event.currentTarget.value)} placeholder="ex: /subscriptions/$subId" />
)} {errorMessage && ( <> {errorMessage} )}
); }; export default ResourcePicker;