LibraryPanels: adds View panel in dashboard modal (#33517)

This commit is contained in:
Hugo Häggmark 2021-04-29 14:51:15 +02:00 committed by GitHub
parent 45c763a76b
commit e8707944a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 26 deletions

View File

@ -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);

View File

@ -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 }));
};
}

View File

@ -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``,
};
}

View File

@ -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(

View File

@ -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;
}