OpenLibraryPanelModal.tsx 3.5 KB

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