1import React, { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react'; 2import { css } from '@emotion/css'; 3import { AsyncSelect, Button, Modal, useStyles2 } from '@grafana/ui'; 4import { GrafanaTheme2, SelectableValue, urlUtil } from '@grafana/data'; 5import { locationService } from '@grafana/runtime'; 6 7import { LibraryElementDTO } from '../../types'; 8import { DashboardSearchHit } from '../../../search/types'; 9import { getConnectedDashboards, getLibraryPanelConnectedDashboards } from '../../state/api'; 10import { debounce } from 'lodash'; 11 12export interface OpenLibraryPanelModalProps { 13 onDismiss: () => void; 14 libraryPanel: LibraryElementDTO; 15} 16 17export function OpenLibraryPanelModal({ libraryPanel, onDismiss }: OpenLibraryPanelModalProps): JSX.Element { 18 const styles = useStyles2(getStyles); 19 const [loading, setLoading] = useState(false); 20 const [connected, setConnected] = useState(0); 21 const [option, setOption] = useState<SelectableValue<DashboardSearchHit> | undefined>(undefined); 22 useEffect(() => { 23 const getConnected = async () => { 24 const connectedDashboards = await getLibraryPanelConnectedDashboards(libraryPanel.uid); 25 setConnected(connectedDashboards.length); 26 }; 27 getConnected(); 28 }, [libraryPanel.uid]); 29 const loadOptions = useCallback( 30 (searchString: string) => loadOptionsAsync(libraryPanel.uid, searchString, setLoading), 31 [libraryPanel.uid] 32 ); 33 const debouncedLoadOptions = useMemo(() => debounce(loadOptions, 300, { leading: true, trailing: true }), [ 34 loadOptions, 35 ]); 36 const onViewPanel = (e: MouseEvent<HTMLButtonElement>) => { 37 e.preventDefault(); 38 locationService.push(urlUtil.renderUrl(`/d/${option?.value?.uid}`, {})); 39 }; 40 41 return ( 42 <Modal title="View panel in dashboard" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen> 43 <div className={styles.container}> 44 {connected === 0 ? ( 45 <span>Panel is not linked to a dashboard. Add the panel to a dashboard and retry.</span> 46 ) : null} 47 {connected > 0 ? ( 48 <> 49 <p> 50 This panel is being used in{' '} 51 <strong> 52 {connected} {connected > 1 ? 'dashboards' : 'dashboard'} 53 </strong> 54 .Please choose which dashboard to view the panel in: 55 </p> 56 <AsyncSelect 57 menuShouldPortal 58 isClearable 59 isLoading={loading} 60 defaultOptions={true} 61 loadOptions={debouncedLoadOptions} 62 onChange={setOption} 63 placeholder="Start typing to search for dashboard" 64 noOptionsMessage="No dashboards found" 65 /> 66 </> 67 ) : null} 68 </div> 69 <Modal.ButtonRow> 70 <Button variant="secondary" onClick={onDismiss} fill="outline"> 71 Cancel 72 </Button> 73 <Button onClick={onViewPanel} disabled={!Boolean(option)}> 74 {option ? `View panel in ${option?.label}...` : 'View panel in dashboard...'} 75 </Button> 76 </Modal.ButtonRow> 77 </Modal> 78 ); 79} 80 81async function loadOptionsAsync(uid: string, searchString: string, setLoading: (loading: boolean) => void) { 82 setLoading(true); 83 const searchHits = await getConnectedDashboards(uid); 84 const options = searchHits 85 .filter((d) => d.title.toLowerCase().includes(searchString.toLowerCase())) 86 .map((d) => ({ label: d.title, value: d })); 87 setLoading(false); 88 89 return options; 90} 91 92function getStyles(theme: GrafanaTheme2) { 93 return { 94 container: css``, 95 }; 96} 97