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