mirror of https://github.com/grafana/grafana.git
				
				
				
			LibraryPanels: adds View panel in dashboard modal (#33517)
This commit is contained in:
		
							parent
							
								
									45c763a76b
								
							
						
					
					
						commit
						e8707944a3
					
				|  | @ -1,4 +1,4 @@ | |||
| import React, { FC } from 'react'; | ||||
| import React, { FC, useState } from 'react'; | ||||
| import { connect, ConnectedProps } from 'react-redux'; | ||||
| 
 | ||||
| import { GrafanaRouteComponentProps } from '../../core/navigation/types'; | ||||
|  | @ -6,6 +6,8 @@ import { StoreState } from '../../types'; | |||
| import { getNavModel } from '../../core/selectors/navModel'; | ||||
| import Page from '../../core/components/Page/Page'; | ||||
| import { LibraryPanelsSearch } from './components/LibraryPanelsSearch/LibraryPanelsSearch'; | ||||
| import { LibraryPanelDTO } from './types'; | ||||
| import { OpenLibraryPanelModal } from './components/OpenLibraryPanelModal/OpenLibraryPanelModal'; | ||||
| 
 | ||||
| const mapStateToProps = (state: StoreState) => ({ | ||||
|   navModel: getNavModel(state.navIndex, 'library-panels'), | ||||
|  | @ -18,15 +20,16 @@ interface OwnProps extends GrafanaRouteComponentProps {} | |||
| type Props = OwnProps & ConnectedProps<typeof connector>; | ||||
| 
 | ||||
| export const LibraryPanelsPage: FC<Props> = ({ navModel }) => { | ||||
|   const [selected, setSelected] = useState<LibraryPanelDTO | undefined>(undefined); | ||||
| 
 | ||||
|   return ( | ||||
|     <Page navModel={navModel}> | ||||
|       <Page.Contents> | ||||
|         <LibraryPanelsSearch onClick={noop} showSecondaryActions showSort showFilter /> | ||||
|         <LibraryPanelsSearch onClick={setSelected} showSecondaryActions showSort showFilter /> | ||||
|         {selected ? <OpenLibraryPanelModal onDismiss={() => setSelected(undefined)} libraryPanel={selected} /> : null} | ||||
|       </Page.Contents> | ||||
|     </Page> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| function noop() {} | ||||
| 
 | ||||
| export default connect(mapStateToProps)(LibraryPanelsPage); | ||||
|  |  | |||
|  | @ -1,17 +1,10 @@ | |||
| import { DispatchResult, LibraryPanelDTO } from '../../types'; | ||||
| import { getLibraryPanelConnectedDashboards } from '../../state/api'; | ||||
| import { getBackendSrv } from '../../../../core/services/backend_srv'; | ||||
| import { getConnectedDashboards as apiGetConnectedDashboards } from '../../state/api'; | ||||
| import { searchCompleted } from './reducer'; | ||||
| 
 | ||||
| export function getConnectedDashboards(libraryPanel: LibraryPanelDTO): DispatchResult { | ||||
|   return async function (dispatch) { | ||||
|     const connectedDashboards = await getLibraryPanelConnectedDashboards(libraryPanel.uid); | ||||
|     if (!connectedDashboards.length) { | ||||
|       dispatch(searchCompleted({ dashboards: [] })); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const dashboards = await getBackendSrv().search({ dashboardIds: connectedDashboards }); | ||||
|     const dashboards = await apiGetConnectedDashboards(libraryPanel.uid); | ||||
|     dispatch(searchCompleted({ dashboards })); | ||||
|   }; | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,92 @@ | |||
| import React, { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react'; | ||||
| import { css } from '@emotion/css'; | ||||
| import { AsyncSelect, Button, Modal, useStyles2 } from '@grafana/ui'; | ||||
| import { GrafanaThemeV2, SelectableValue, urlUtil } from '@grafana/data'; | ||||
| import { locationService } from '@grafana/runtime'; | ||||
| 
 | ||||
| import { LibraryPanelDTO } from '../../types'; | ||||
| import { DashboardSearchHit } from '../../../search/types'; | ||||
| import { getConnectedDashboards, getLibraryPanelConnectedDashboards } from '../../state/api'; | ||||
| import { debounce } from 'lodash'; | ||||
| 
 | ||||
| export interface OpenLibraryPanelModalProps { | ||||
|   onDismiss: () => void; | ||||
|   libraryPanel: LibraryPanelDTO; | ||||
| } | ||||
| 
 | ||||
| export function OpenLibraryPanelModal({ libraryPanel, onDismiss }: OpenLibraryPanelModalProps): JSX.Element { | ||||
|   const styles = useStyles2(getStyles); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [connected, setConnected] = useState(0); | ||||
|   const [option, setOption] = useState<SelectableValue<DashboardSearchHit> | undefined>(undefined); | ||||
|   useEffect(() => { | ||||
|     const getConnected = async () => { | ||||
|       const connectedDashboards = await getLibraryPanelConnectedDashboards(libraryPanel.uid); | ||||
|       setConnected(connectedDashboards.length); | ||||
|     }; | ||||
|     getConnected(); | ||||
|   }, [libraryPanel.uid]); | ||||
|   const loadOptions = useCallback( | ||||
|     (searchString: string) => loadOptionsAsync(libraryPanel.uid, searchString, setLoading), | ||||
|     [libraryPanel.uid] | ||||
|   ); | ||||
|   const debouncedLoadOptions = useMemo(() => debounce(loadOptions, 300, { leading: true, trailing: true }), [ | ||||
|     loadOptions, | ||||
|   ]); | ||||
|   const onViewPanel = (e: MouseEvent<HTMLButtonElement>) => { | ||||
|     e.preventDefault(); | ||||
|     locationService.push(urlUtil.renderUrl(`/d/${option?.value?.uid}`, {})); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <Modal title="View panel in dashboard" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen> | ||||
|       <div className={styles.container}> | ||||
|         {connected === 0 ? ( | ||||
|           <span>Panel is not linked to a dashboard. Add the panel to a dashboard and retry.</span> | ||||
|         ) : null} | ||||
|         {connected > 0 ? ( | ||||
|           <> | ||||
|             <p> | ||||
|               This panel is being used in <strong>{connected} dashboards</strong>.Please choose which dashboard to view | ||||
|               the panel in: | ||||
|             </p> | ||||
|             <AsyncSelect | ||||
|               isClearable | ||||
|               isLoading={loading} | ||||
|               defaultOptions={true} | ||||
|               loadOptions={debouncedLoadOptions} | ||||
|               onChange={setOption} | ||||
|               placeholder="Start typing to search for dashboard" | ||||
|               noOptionsMessage="No dashboards found" | ||||
|             /> | ||||
|           </> | ||||
|         ) : null} | ||||
|       </div> | ||||
|       <Modal.ButtonRow> | ||||
|         <Button variant="secondary" onClick={onDismiss} fill="outline"> | ||||
|           Cancel | ||||
|         </Button> | ||||
|         <Button onClick={onViewPanel} disabled={!Boolean(option)}> | ||||
|           {option ? `View panel in ${option?.label}...` : 'View panel in dashboard...'} | ||||
|         </Button> | ||||
|       </Modal.ButtonRow> | ||||
|     </Modal> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| async function loadOptionsAsync(uid: string, searchString: string, setLoading: (loading: boolean) => void) { | ||||
|   setLoading(true); | ||||
|   const searchHits = await getConnectedDashboards(uid); | ||||
|   const options = searchHits | ||||
|     .filter((d) => d.title.toLowerCase().includes(searchString.toLowerCase())) | ||||
|     .map((d) => ({ label: d.title, value: d })); | ||||
|   setLoading(false); | ||||
| 
 | ||||
|   return options; | ||||
| } | ||||
| 
 | ||||
| function getStyles(theme: GrafanaThemeV2) { | ||||
|   return { | ||||
|     container: css``, | ||||
|   }; | ||||
| } | ||||
|  | @ -1,9 +1,8 @@ | |||
| import React, { useCallback, useState } from 'react'; | ||||
| import { Button, Icon, Input, Modal, useStyles } from '@grafana/ui'; | ||||
| import { useAsync, useDebounce } from 'react-use'; | ||||
| import { getBackendSrv } from 'app/core/services/backend_srv'; | ||||
| import { usePanelSave } from '../../utils/usePanelSave'; | ||||
| import { getLibraryPanelConnectedDashboards } from '../../state/api'; | ||||
| import { getConnectedDashboards } from '../../state/api'; | ||||
| import { PanelModelWithLibraryPanel } from '../../types'; | ||||
| import { getModalStyles } from '../../styles'; | ||||
| 
 | ||||
|  | @ -25,20 +24,14 @@ export const SaveLibraryPanelModal: React.FC<Props> = ({ | |||
|   onDiscard, | ||||
| }) => { | ||||
|   const [searchString, setSearchString] = useState(''); | ||||
|   const connectedDashboardsState = useAsync(async () => { | ||||
|     const connectedDashboards = await getLibraryPanelConnectedDashboards(panel.libraryPanel.uid); | ||||
|     return connectedDashboards; | ||||
|   }, []); | ||||
| 
 | ||||
|   const dashState = useAsync(async () => { | ||||
|     const connectedDashboards = connectedDashboardsState.value; | ||||
|     if (connectedDashboards && connectedDashboards.length > 0) { | ||||
|       const dashboardDTOs = await getBackendSrv().search({ dashboardIds: connectedDashboards }); | ||||
|       return dashboardDTOs.map((dash) => dash.title); | ||||
|     const searchHits = await getConnectedDashboards(panel.libraryPanel.uid); | ||||
|     if (searchHits.length > 0) { | ||||
|       return searchHits.map((dash) => dash.title); | ||||
|     } | ||||
| 
 | ||||
|     return []; | ||||
|   }, [connectedDashboardsState.value]); | ||||
|   }, [panel.libraryPanel.uid]); | ||||
| 
 | ||||
|   const [filteredDashboards, setFilteredDashboards] = useState<string[]>([]); | ||||
|   useDebounce( | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import { getBackendSrv } from '@grafana/runtime'; | ||||
| import { LibraryPanelDTO, LibraryPanelSearchResult, PanelModelWithLibraryPanel } from '../types'; | ||||
| import { DashboardSearchHit } from '../../search/types'; | ||||
| import { getBackendSrv } from '../../../core/services/backend_srv'; | ||||
| 
 | ||||
| export interface GetLibraryPanelsOptions { | ||||
|   searchString?: string; | ||||
|  | @ -68,3 +69,13 @@ export async function getLibraryPanelConnectedDashboards(libraryPanelUid: string | |||
|   const { result } = await getBackendSrv().get(`/api/library-panels/${libraryPanelUid}/dashboards`); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| export async function getConnectedDashboards(uid: string): Promise<DashboardSearchHit[]> { | ||||
|   const dashboardIds = await getLibraryPanelConnectedDashboards(uid); | ||||
|   if (dashboardIds.length === 0) { | ||||
|     return []; | ||||
|   } | ||||
| 
 | ||||
|   const searchHits = await getBackendSrv().search({ dashboardIds }); | ||||
|   return searchHits; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue