diff --git a/public/app/features/library-panels/LibraryPanelsPage.tsx b/public/app/features/library-panels/LibraryPanelsPage.tsx index 24b0900f73e..01bd2dd2173 100644 --- a/public/app/features/library-panels/LibraryPanelsPage.tsx +++ b/public/app/features/library-panels/LibraryPanelsPage.tsx @@ -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; export const LibraryPanelsPage: FC = ({ navModel }) => { + const [selected, setSelected] = useState(undefined); + return ( - + + {selected ? setSelected(undefined)} libraryPanel={selected} /> : null} ); }; -function noop() {} - export default connect(mapStateToProps)(LibraryPanelsPage); diff --git a/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts b/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts index c8955fdff7e..3cf7004be89 100644 --- a/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts +++ b/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts @@ -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 })); }; } diff --git a/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx b/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx new file mode 100644 index 00000000000..b9e3ca1c832 --- /dev/null +++ b/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx @@ -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 | 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) => { + e.preventDefault(); + locationService.push(urlUtil.renderUrl(`/d/${option?.value?.uid}`, {})); + }; + + return ( + +
+ {connected === 0 ? ( + Panel is not linked to a dashboard. Add the panel to a dashboard and retry. + ) : null} + {connected > 0 ? ( + <> +

+ This panel is being used in {connected} dashboards.Please choose which dashboard to view + the panel in: +

+ + + ) : null} +
+ + + + +
+ ); +} + +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``, + }; +} diff --git a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx index 77417c16062..6840f948983 100644 --- a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx @@ -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 = ({ 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([]); useDebounce( diff --git a/public/app/features/library-panels/state/api.ts b/public/app/features/library-panels/state/api.ts index 1d6e7d911f2..9fbfd66f18b 100644 --- a/public/app/features/library-panels/state/api.ts +++ b/public/app/features/library-panels/state/api.ts @@ -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 { + const dashboardIds = await getLibraryPanelConnectedDashboards(uid); + if (dashboardIds.length === 0) { + return []; + } + + const searchHits = await getBackendSrv().search({ dashboardIds }); + return searchHits; +}