import { cx } from '@emotion/css'; import React, { useCallback, useEffect, useState } from 'react'; import { useEffectOnce } from 'react-use'; import { config } from '@grafana/runtime'; import { Alert, Button, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { selectors } from '../../e2e/selectors'; import ResourcePickerData, { ResourcePickerQueryType } from '../../resourcePicker/resourcePickerData'; import { AzureMetricResource } from '../../types'; import messageFromError from '../../utils/messageFromError'; import { Space } from '../Space'; import Advanced from './Advanced'; import AdvancedMulti from './AdvancedMulti'; import NestedRow from './NestedRow'; import Search from './Search'; import getStyles from './styles'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; import { findRows, parseMultipleResourceDetails, resourcesToStrings, matchURI, resourceToString } from './utils'; interface ResourcePickerProps { resourcePickerData: ResourcePickerData; resources: T[]; selectableEntryTypes: ResourceRowType[]; queryType: ResourcePickerQueryType; onApply: (resources: T[]) => void; onCancel: () => void; disableRow: (row: ResourceRow, selectedRows: ResourceRowGroup) => boolean; renderAdvanced: (resources: T[], onChange: (resources: T[]) => void) => React.ReactNode; selectionNotice?: (selectedRows: ResourceRowGroup) => string; } const ResourcePicker = ({ resourcePickerData, resources, onApply, onCancel, selectableEntryTypes, queryType, disableRow, renderAdvanced, selectionNotice, }: ResourcePickerProps) => { const styles = useStyles2(getStyles); const [isLoading, setIsLoading] = useState(false); const [rows, setRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]); const [internalSelected, setInternalSelected] = useState(resources); const [errorMessage, setErrorMessage] = useState(undefined); const [shouldShowLimitFlag, setShouldShowLimitFlag] = useState(false); const selectionNoticeText = selectionNotice?.(selectedRows); // Sync the resourceURI prop to internal state useEffect(() => { setInternalSelected(resources); }, [resources]); const loadInitialData = useCallback(async () => { if (!isLoading) { try { setIsLoading(true); const resources = await resourcePickerData.fetchInitialRows( queryType, parseMultipleResourceDetails(internalSelected ?? {}) ); setRows(resources); } catch (error) { setErrorMessage(messageFromError(error)); } setIsLoading(false); } }, [internalSelected, isLoading, resourcePickerData, queryType]); useEffectOnce(() => { loadInitialData(); }); // Avoid using empty resources const isValid = (r: string | AzureMetricResource) => typeof r === 'string' ? r !== '' : r.subscription && r.resourceGroup && r.resourceName && r.metricNamespace; // set selected row data whenever row or selection changes useEffect(() => { if (!internalSelected) { setSelectedRows([]); } const sanitized = internalSelected.filter((r) => isValid(r)); const found = internalSelected && findRows(rows, resourcesToStrings(sanitized)); if (found && found.length) { return setSelectedRows(found); } return setSelectedRows([]); }, [internalSelected, rows]); // Request resources for an 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, queryType); setRows(nestedRows); } catch (error) { setErrorMessage(messageFromError(error)); throw error; } }, [resourcePickerData, rows, queryType] ); const handleSelectionChanged = useCallback( (row: ResourceRow, isSelected: boolean) => { if (isSelected) { const newRes = queryType === 'logs' ? row.uri : parseMultipleResourceDetails([row.uri], row.location)[0]; const newSelected = internalSelected ? internalSelected.concat(newRes) : [newRes]; setInternalSelected(newSelected); } else { const newInternalSelected = internalSelected?.filter((r) => { return !matchURI(resourceToString(r), row.uri); }); setInternalSelected(newInternalSelected); } }, [queryType, internalSelected, setInternalSelected] ); const handleApply = useCallback(() => { if (internalSelected) { onApply(queryType === 'logs' ? internalSelected : parseMultipleResourceDetails(internalSelected)); } }, [queryType, internalSelected, onApply]); const handleSearch = useCallback( async (searchWord: string) => { // clear errors and warnings setErrorMessage(undefined); setShouldShowLimitFlag(false); if (!searchWord) { loadInitialData(); return; } try { setIsLoading(true); const searchResults = await resourcePickerData.search(searchWord, queryType); setRows(searchResults); if (searchResults.length >= resourcePickerData.resultLimit) { setShouldShowLimitFlag(true); } } catch (err) { setErrorMessage(messageFromError(err)); } setIsLoading(false); }, [loadInitialData, resourcePickerData, queryType] ); return (
{shouldShowLimitFlag ? (

Showing first {resourcePickerData.resultLimit} results

) : ( )}
Scope Type Location
{isLoading && ( )} {!isLoading && rows.length === 0 && ( )} {!isLoading && rows.map((row) => ( ))}
No resources found
{selectedRows.length > 0 && ( <>
Selection
{selectedRows.map((row) => ( false} /> ))}
{selectionNoticeText?.length ? ( {selectionNoticeText} ) : null} )} {config.featureToggles.azureMultipleResourcePicker ? ( setInternalSelected(r)} renderAdvanced={renderAdvanced} /> ) : ( setInternalSelected(r)} /> )}
{errorMessage && ( <> {errorMessage} )}
); }; export default ResourcePicker;