SaveLibraryPanelModal.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import React, { useCallback, useState } from 'react';
  2. import { useAsync, useDebounce } from 'react-use';
  3. import { Button, Icon, Input, Modal, useStyles } from '@grafana/ui';
  4. import { getConnectedDashboards } from '../../state/api';
  5. import { getModalStyles } from '../../styles';
  6. import { PanelModelWithLibraryPanel } from '../../types';
  7. import { usePanelSave } from '../../utils/usePanelSave';
  8. interface Props {
  9. panel: PanelModelWithLibraryPanel;
  10. folderId: number;
  11. isUnsavedPrompt?: boolean;
  12. onConfirm: () => void;
  13. onDismiss: () => void;
  14. onDiscard: () => void;
  15. }
  16. export const SaveLibraryPanelModal: React.FC<Props> = ({
  17. panel,
  18. folderId,
  19. isUnsavedPrompt,
  20. onDismiss,
  21. onConfirm,
  22. onDiscard,
  23. }) => {
  24. const [searchString, setSearchString] = useState('');
  25. const dashState = useAsync(async () => {
  26. const searchHits = await getConnectedDashboards(panel.libraryPanel.uid);
  27. if (searchHits.length > 0) {
  28. return searchHits.map((dash) => dash.title);
  29. }
  30. return [];
  31. }, [panel.libraryPanel.uid]);
  32. const [filteredDashboards, setFilteredDashboards] = useState<string[]>([]);
  33. useDebounce(
  34. () => {
  35. if (!dashState.value) {
  36. return setFilteredDashboards([]);
  37. }
  38. return setFilteredDashboards(
  39. dashState.value.filter((dashName) => dashName.toLowerCase().includes(searchString.toLowerCase()))
  40. );
  41. },
  42. 300,
  43. [dashState.value, searchString]
  44. );
  45. const { saveLibraryPanel } = usePanelSave();
  46. const styles = useStyles(getModalStyles);
  47. const discardAndClose = useCallback(() => {
  48. onDiscard();
  49. }, [onDiscard]);
  50. const title = isUnsavedPrompt ? 'Unsaved library panel changes' : 'Save library panel';
  51. return (
  52. <Modal title={title} icon="save" onDismiss={onDismiss} isOpen={true}>
  53. <div>
  54. <p className={styles.textInfo}>
  55. {'This update will affect '}
  56. <strong>
  57. {panel.libraryPanel.meta.connectedDashboards}{' '}
  58. {panel.libraryPanel.meta.connectedDashboards === 1 ? 'dashboard' : 'dashboards'}.
  59. </strong>
  60. The following dashboards using the panel will be affected:
  61. </p>
  62. <Input
  63. className={styles.dashboardSearch}
  64. prefix={<Icon name="search" />}
  65. placeholder="Search affected dashboards"
  66. value={searchString}
  67. onChange={(e) => setSearchString(e.currentTarget.value)}
  68. />
  69. {dashState.loading ? (
  70. <p>Loading connected dashboards...</p>
  71. ) : (
  72. <table className={styles.myTable}>
  73. <thead>
  74. <tr>
  75. <th>Dashboard name</th>
  76. </tr>
  77. </thead>
  78. <tbody>
  79. {filteredDashboards.map((dashName, i) => (
  80. <tr key={`dashrow-${i}`}>
  81. <td>{dashName}</td>
  82. </tr>
  83. ))}
  84. </tbody>
  85. </table>
  86. )}
  87. <Modal.ButtonRow>
  88. <Button variant="secondary" onClick={onDismiss} fill="outline">
  89. Cancel
  90. </Button>
  91. {isUnsavedPrompt && (
  92. <Button variant="destructive" onClick={discardAndClose}>
  93. Discard
  94. </Button>
  95. )}
  96. <Button
  97. onClick={() => {
  98. saveLibraryPanel(panel, folderId).then(() => {
  99. onConfirm();
  100. });
  101. }}
  102. >
  103. Update all
  104. </Button>
  105. </Modal.ButtonRow>
  106. </div>
  107. </Modal>
  108. );
  109. };