From 22ac0fc3cdacb559eed6ef2e88b6da6ba183cefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Wed, 28 Apr 2021 15:22:28 +0200 Subject: [PATCH] Chore: Migrates remaining Angular modals to React (#33476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HelpModal: Migrates to new style * Alerting: Migrates how to do alerting modal to React * ApiKeysModal: migrates to new theme * Dashboard: View dasboard json modal migrated to React and new theme * PluginPage: migrates update plugin modal to react and new theme * Chore: deprecates events and functions * Simplify help modal * Updated json modal to use Modal.ButtonRow * Tweak to api key design * Tests: updates snapshot Co-authored-by: Torkel Ödegaard --- public/app/core/components/help/HelpModal.tsx | 228 ++++++++++-------- .../sidemenu/BottomNavLinks.test.tsx | 9 +- .../components/sidemenu/BottomNavLinks.tsx | 9 +- public/app/core/services/keybindingSrv.ts | 3 +- public/app/core/services/util_srv.ts | 10 +- .../app/features/alerting/AlertHowToModal.tsx | 21 ++ .../features/alerting/AlertRuleList.test.tsx | 11 +- .../app/features/alerting/AlertRuleList.tsx | 11 +- .../alerting/partials/alert_howto.html | 29 --- .../api-keys/ApiKeysAddedModal.test.tsx | 1 + .../features/api-keys/ApiKeysAddedModal.tsx | 67 +++-- public/app/features/api-keys/ApiKeysPage.tsx | 14 +- .../ApiKeysAddedModal.test.tsx.snap | 103 +++----- .../components/ShareModal/ShareExport.tsx | 16 +- .../components/ShareModal/ViewJsonModal.tsx | 31 +++ public/app/features/plugins/PluginPage.tsx | 22 +- .../features/plugins/UpdatePluginModal.tsx | 58 +++++ .../plugins/partials/update_instructions.html | 21 -- public/app/partials/edit_json.html | 24 -- public/app/types/events.ts | 6 + public/sass/_grafana.scss | 1 - public/sass/components/_panel_pluginlist.scss | 11 - public/sass/components/_shortcuts.scss | 44 ---- 23 files changed, 368 insertions(+), 382 deletions(-) create mode 100644 public/app/features/alerting/AlertHowToModal.tsx delete mode 100644 public/app/features/alerting/partials/alert_howto.html create mode 100644 public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx create mode 100644 public/app/features/plugins/UpdatePluginModal.tsx delete mode 100644 public/app/features/plugins/partials/update_instructions.html delete mode 100644 public/app/partials/edit_json.html delete mode 100644 public/sass/components/_shortcuts.scss diff --git a/public/app/core/components/help/HelpModal.tsx b/public/app/core/components/help/HelpModal.tsx index d78a0d4c4b1..05522b60bff 100644 --- a/public/app/core/components/help/HelpModal.tsx +++ b/public/app/core/components/help/HelpModal.tsx @@ -1,101 +1,141 @@ import React from 'react'; -import { appEvents } from 'app/core/core'; -import { Icon } from '@grafana/ui'; +import { css } from '@emotion/css'; +import { GrafanaThemeV2 } from '@grafana/data'; +import { Modal, useStyles2 } from '@grafana/ui'; -export class HelpModal extends React.PureComponent { - static tabIndex = 0; - static shortcuts = { - Global: [ - { keys: ['g', 'h'], description: 'Go to Home Dashboard' }, - { keys: ['g', 'p'], description: 'Go to Profile' }, - { keys: ['s', 'o'], description: 'Open search' }, - { keys: ['esc'], description: 'Exit edit/setting views' }, - ], - Dashboard: [ - { keys: ['mod+s'], description: 'Save dashboard' }, - { keys: ['d', 'r'], description: 'Refresh all panels' }, - { keys: ['d', 's'], description: 'Dashboard settings' }, - { keys: ['d', 'v'], description: 'Toggle in-active / view mode' }, - { keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' }, - { keys: ['d', 'E'], description: 'Expand all rows' }, - { keys: ['d', 'C'], description: 'Collapse all rows' }, - { keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' }, - { keys: ['mod+o'], description: 'Toggle shared graph crosshair' }, - { keys: ['d', 'l'], description: 'Toggle all panel legends' }, - ], - 'Focused Panel': [ - { keys: ['e'], description: 'Toggle panel edit view' }, - { keys: ['v'], description: 'Toggle panel fullscreen view' }, - { keys: ['p', 's'], description: 'Open Panel Share Modal' }, - { keys: ['p', 'd'], description: 'Duplicate Panel' }, - { keys: ['p', 'r'], description: 'Remove Panel' }, - { keys: ['p', 'l'], description: 'Toggle panel legend' }, - ], - 'Time Range': [ - { keys: ['t', 'z'], description: 'Zoom out time range' }, - { - keys: ['t', '←'], - description: 'Move time range back', - }, - { - keys: ['t', '→'], - description: 'Move time range forward', - }, - ], - }; +const shortcuts = { + Global: [ + { keys: ['g', 'h'], description: 'Go to Home Dashboard' }, + { keys: ['g', 'p'], description: 'Go to Profile' }, + { keys: ['s', 'o'], description: 'Open search' }, + { keys: ['esc'], description: 'Exit edit/setting views' }, + ], + Dashboard: [ + { keys: ['mod+s'], description: 'Save dashboard' }, + { keys: ['d', 'r'], description: 'Refresh all panels' }, + { keys: ['d', 's'], description: 'Dashboard settings' }, + { keys: ['d', 'v'], description: 'Toggle in-active / view mode' }, + { keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' }, + { keys: ['d', 'E'], description: 'Expand all rows' }, + { keys: ['d', 'C'], description: 'Collapse all rows' }, + { keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' }, + { keys: ['mod+o'], description: 'Toggle shared graph crosshair' }, + { keys: ['d', 'l'], description: 'Toggle all panel legends' }, + ], + 'Focused Panel': [ + { keys: ['e'], description: 'Toggle panel edit view' }, + { keys: ['v'], description: 'Toggle panel fullscreen view' }, + { keys: ['p', 's'], description: 'Open Panel Share Modal' }, + { keys: ['p', 'd'], description: 'Duplicate Panel' }, + { keys: ['p', 'r'], description: 'Remove Panel' }, + { keys: ['p', 'l'], description: 'Toggle panel legend' }, + ], + 'Time Range': [ + { keys: ['t', 'z'], description: 'Zoom out time range' }, + { + keys: ['t', '←'], + description: 'Move time range back', + }, + { + keys: ['t', '→'], + description: 'Move time range forward', + }, + ], +}; - dismiss() { - appEvents.emit('hide-modal'); - } +export interface HelpModalProps { + onDismiss: () => void; +} - render() { - return ( -
-
-

- - Shortcuts -

- - - -
- -
-

- mod = - CTRL on windows or linux and CMD key on Mac -

- - {Object.entries(HelpModal.shortcuts).map(([category, shortcuts], i) => ( -
- - - - +export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => { + const styles = useStyles2(getStyles); + return ( + +
+ mod = CTRL on windows or linux and CMD key on Mac +
+
+ {Object.entries(shortcuts).map(([category, shortcuts], i) => ( +
+
- {category} -
+ + + + + {shortcuts.map((shortcut, j) => ( + + + - {shortcuts.map((shortcut, j) => ( - - - - - ))} - -
+ {category} +
+ {shortcut.keys.map((key, k) => ( + + {key} + + ))} + {shortcut.description}
- {shortcut.keys.map((key, k) => ( - - {key} - - ))} - {shortcut.description}
-
- ))} + ))} + + +
+ ))} +
+ + ); +}; -
-
- - ); - } +function getStyles(theme: GrafanaThemeV2) { + return { + titleDescription: css` + font-size: ${theme.typography.bodySmall.fontSize}; + font-weight: ${theme.typography.bodySmall.fontWeight}; + color: ${theme.colors.text.disabled}; + padding-bottom: ${theme.spacing(2)}; + `, + categories: css` + font-size: ${theme.typography.bodySmall.fontSize}; + display: flex; + flex-flow: row wrap; + justify-content: space-between; + align-items: flex-start; + `, + shortcutCategory: css` + width: 50%; + font-size: ${theme.typography.bodySmall.fontSize}; + `, + shortcutTable: css` + margin-bottom: ${theme.spacing(2)}; + `, + shortcutTableCategoryHeader: css` + font-weight: normal; + font-size: ${theme.typography.h6.fontSize}; + text-align: left; + `, + shortcutTableDescription: css` + text-align: left; + color: ${theme.colors.text.disabled}; + width: 99%; + padding: ${theme.spacing(1, 2)}; + `, + shortcutTableKeys: css` + white-space: nowrap; + width: 1%; + text-align: right; + color: ${theme.colors.text.primary}; + `, + shortcutTableKey: css` + display: inline-block; + text-align: center; + margin-right: ${theme.spacing(0.5)}; + padding: 3px 5px; + font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace; + line-height: 10px; + vertical-align: middle; + border: solid 1px ${theme.colors.border.medium}; + border-radius: ${theme.shape.borderRadius(3)}; + color: ${theme.colors.text.primary}; + background-color: ${theme.colors.background.secondary}; + `, + }; } diff --git a/public/app/core/components/sidemenu/BottomNavLinks.test.tsx b/public/app/core/components/sidemenu/BottomNavLinks.test.tsx index a34062b79db..2f8030b6615 100644 --- a/public/app/core/components/sidemenu/BottomNavLinks.test.tsx +++ b/public/app/core/components/sidemenu/BottomNavLinks.test.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { shallow } from 'enzyme'; import BottomNavLinks from './BottomNavLinks'; import appEvents from '../../app_events'; -import { ShowModalEvent } from '../../../types/events'; +import { ShowModalReactEvent } from '../../../types/events'; +import { HelpModal } from '../help/HelpModal'; jest.mock('../../app_events', () => ({ publish: jest.fn(), @@ -94,11 +95,7 @@ describe('Functions', () => { const instance = wrapper.instance() as BottomNavLinks; instance.onOpenShortcuts(); - expect(appEvents.publish).toHaveBeenCalledWith( - new ShowModalEvent({ - templateHtml: '', - }) - ); + expect(appEvents.publish).toHaveBeenCalledWith(new ShowModalReactEvent({ component: HelpModal })); }); }); }); diff --git a/public/app/core/components/sidemenu/BottomNavLinks.tsx b/public/app/core/components/sidemenu/BottomNavLinks.tsx index 0fed3fce660..48531b75ad9 100644 --- a/public/app/core/components/sidemenu/BottomNavLinks.tsx +++ b/public/app/core/components/sidemenu/BottomNavLinks.tsx @@ -6,7 +6,8 @@ import { NavModelItem } from '@grafana/data'; import { Icon, IconName, Link } from '@grafana/ui'; import { OrgSwitcher } from '../OrgSwitcher'; import { getFooterLinks } from '../Footer/Footer'; -import { ShowModalEvent } from '../../../types/events'; +import { ShowModalReactEvent } from '../../../types/events'; +import { HelpModal } from '../help/HelpModal'; export interface Props { link: NavModelItem; @@ -23,11 +24,7 @@ export default class BottomNavLinks extends PureComponent { }; onOpenShortcuts = () => { - appEvents.publish( - new ShowModalEvent({ - templateHtml: '', - }) - ); + appEvents.publish(new ShowModalReactEvent({ component: HelpModal })); }; toggleSwitcherModal = () => { diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 00d19eae9f9..8e2e208a9df 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -22,6 +22,7 @@ import { getDatasourceSrv } from '../../features/plugins/datasource_srv'; import { getTimeSrv } from '../../features/dashboard/services/TimeSrv'; import { toggleTheme } from './toggleTheme'; import { withFocusedPanel } from './withFocusedPanelId'; +import { HelpModal } from '../components/help/HelpModal'; export class KeybindingSrv { modalOpen = false; @@ -97,7 +98,7 @@ export class KeybindingSrv { } private showHelpModal() { - appEvents.publish(new ShowModalEvent({ templateHtml: '' })); + appEvents.publish(new ShowModalReactEvent({ component: HelpModal })); } private exit() { diff --git a/public/app/core/services/util_srv.ts b/public/app/core/services/util_srv.ts index 0f9999cf84a..509f6404fd5 100644 --- a/public/app/core/services/util_srv.ts +++ b/public/app/core/services/util_srv.ts @@ -15,7 +15,7 @@ import { ShowModalReactEvent, } from '../../types/events'; import { ConfirmModal, ConfirmModalProps } from '@grafana/ui'; -import { textUtil } from '@grafana/data'; +import { deprecationWarning, textUtil } from '@grafana/data'; export class UtilSrv { modalScope: any; @@ -55,13 +55,21 @@ export class UtilSrv { this.reactModalRoot.removeChild(this.reactModalNode); }; + /** + * @deprecated use showModalReact instead that has this capability built in + */ hideModal() { + deprecationWarning('UtilSrv', 'hideModal', 'showModalReact'); if (this.modalScope && this.modalScope.dismiss) { this.modalScope.dismiss(); } } + /** + * @deprecated use showModalReact instead + */ showModal(options: any) { + deprecationWarning('UtilSrv', 'showModal', 'showModalReact'); if (this.modalScope && this.modalScope.dismiss) { this.modalScope.dismiss(); } diff --git a/public/app/features/alerting/AlertHowToModal.tsx b/public/app/features/alerting/AlertHowToModal.tsx new file mode 100644 index 00000000000..c4bcbda7db1 --- /dev/null +++ b/public/app/features/alerting/AlertHowToModal.tsx @@ -0,0 +1,21 @@ +import { Modal, VerticalGroup } from '@grafana/ui'; +import React from 'react'; + +export interface AlertHowToModalProps { + onDismiss: () => void; +} + +export function AlertHowToModal({ onDismiss }: AlertHowToModalProps): JSX.Element { + return ( + + + link to how to alert image +

+ Alerts are added and configured in the Alert tab of any dashboard graph panel, letting you build and visualize + an alert using existing queries. +

+

Remember to save the dashboard to persist your alert rule changes.

+
+
+ ); +} diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index 33421b6afb3..19621dd3ed0 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -8,7 +8,8 @@ import { setSearchQuery } from './state/reducers'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { locationService } from '@grafana/runtime'; -import { ShowModalEvent } from '../../types/events'; +import { ShowModalReactEvent } from '../../types/events'; +import { AlertHowToModal } from './AlertHowToModal'; jest.mock('../../core/app_events', () => ({ publish: jest.fn(), @@ -92,13 +93,7 @@ describe('Functions', () => { instance.onOpenHowTo(); - expect(appEvents.publish).toHaveBeenCalledWith( - new ShowModalEvent({ - src: 'public/app/features/alerting/partials/alert_howto.html', - modalClass: 'confirm-modal', - model: {}, - }) - ); + expect(appEvents.publish).toHaveBeenCalledWith(new ShowModalReactEvent({ component: AlertHowToModal })); }); }); diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index a9cac0077a3..cd9c75bbec3 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -15,7 +15,8 @@ import { setSearchQuery } from './state/reducers'; import { Button, LinkButton, Select, VerticalGroup } from '@grafana/ui'; import { AlertDefinitionItem } from './components/AlertDefinitionItem'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { ShowModalEvent } from '../../types/events'; +import { ShowModalReactEvent } from '../../types/events'; +import { AlertHowToModal } from './AlertHowToModal'; function mapStateToProps(state: StoreState) { return { @@ -73,13 +74,7 @@ export class AlertRuleListUnconnected extends PureComponent { }; onOpenHowTo = () => { - appEvents.publish( - new ShowModalEvent({ - src: 'public/app/features/alerting/partials/alert_howto.html', - modalClass: 'confirm-modal', - model: {}, - }) - ); + appEvents.publish(new ShowModalReactEvent({ component: AlertHowToModal })); }; onSearchQueryChange = (value: string) => { diff --git a/public/app/features/alerting/partials/alert_howto.html b/public/app/features/alerting/partials/alert_howto.html deleted file mode 100644 index 7332a660d48..00000000000 --- a/public/app/features/alerting/partials/alert_howto.html +++ /dev/null @@ -1,29 +0,0 @@ - - diff --git a/public/app/features/api-keys/ApiKeysAddedModal.test.tsx b/public/app/features/api-keys/ApiKeysAddedModal.test.tsx index 160418a7ab8..82a3d7de087 100644 --- a/public/app/features/api-keys/ApiKeysAddedModal.test.tsx +++ b/public/app/features/api-keys/ApiKeysAddedModal.test.tsx @@ -4,6 +4,7 @@ import { ApiKeysAddedModal, Props } from './ApiKeysAddedModal'; const setup = (propOverrides?: object) => { const props: Props = { + onDismiss: jest.fn(), apiKey: 'api key test', rootPath: 'test/path', }; diff --git a/public/app/features/api-keys/ApiKeysAddedModal.tsx b/public/app/features/api-keys/ApiKeysAddedModal.tsx index 1545ad4ef5b..1f6f3448b47 100644 --- a/public/app/features/api-keys/ApiKeysAddedModal.tsx +++ b/public/app/features/api-keys/ApiKeysAddedModal.tsx @@ -1,47 +1,44 @@ import React from 'react'; -import { Icon } from '@grafana/ui'; +import { css } from '@emotion/css'; +import { Alert, Field, Modal, useStyles2 } from '@grafana/ui'; +import { GrafanaThemeV2 } from '@grafana/data'; export interface Props { + onDismiss: () => void; apiKey: string; rootPath: string; } -export const ApiKeysAddedModal = (props: Props) => { +export function ApiKeysAddedModal({ onDismiss, apiKey, rootPath }: Props): JSX.Element { + const styles = useStyles2(getStyles); return ( -
-
-

- - API Key Created -

+ + + {apiKey} + - - - -
+ + It is not stored in this form, so be sure to copy it now. + -
-
-
- Key - {props.apiKey} -
-
- -
- You will only be able to view this key here once! It is not stored in this form, so be sure to copy it now. -
-
- You can authenticate a request using the Authorization HTTP header, example: -
-
-
-            curl -H "Authorization: Bearer {props.apiKey}" {props.rootPath}/api/dashboards/home
-          
-
-
-
+

You can authenticate a request using the Authorization HTTP header, example:

+
+        curl -H "Authorization: Bearer {apiKey}" {rootPath}/api/dashboards/home
+      
+ ); -}; +} -export default ApiKeysAddedModal; +function getStyles(theme: GrafanaThemeV2) { + return { + label: css` + padding: ${theme.spacing(1)}; + background-color: ${theme.colors.background.secondary}; + border-radius: ${theme.shape.borderRadius()}; + `, + small: css` + font-size: ${theme.typography.bodySmall.fontSize}; + font-weight: ${theme.typography.bodySmall.fontWeight}; + `, + }; +} diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 51212531602..f737aa6a217 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -1,5 +1,4 @@ import React, { PureComponent } from 'react'; -import ReactDOMServer from 'react-dom/server'; import { connect, ConnectedProps } from 'react-redux'; import { hot } from 'react-hot-loader'; // Utils @@ -8,7 +7,7 @@ import { getNavModel } from 'app/core/selectors/navModel'; import { getApiKeys, getApiKeysCount } from './state/selectors'; import { addApiKey, deleteApiKey, loadApiKeys } from './state/actions'; import Page from 'app/core/components/Page/Page'; -import ApiKeysAddedModal from './ApiKeysAddedModal'; +import { ApiKeysAddedModal } from './ApiKeysAddedModal'; import config from 'app/core/config'; import appEvents from 'app/core/app_events'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; @@ -20,7 +19,7 @@ import { ApiKeysForm } from './ApiKeysForm'; import { ApiKeysActionBar } from './ApiKeysActionBar'; import { ApiKeysTable } from './ApiKeysTable'; import { ApiKeysController } from './ApiKeysController'; -import { ShowModalEvent } from 'app/types/events'; +import { ShowModalReactEvent } from 'app/types/events'; const { Switch } = LegacyForms; @@ -82,11 +81,14 @@ export class ApiKeysPageUnconnected extends PureComponent { onAddApiKey = (newApiKey: NewApiKey) => { const openModal = (apiKey: string) => { const rootPath = window.location.origin + config.appSubUrl; - const modalTemplate = ReactDOMServer.renderToString(); appEvents.publish( - new ShowModalEvent({ - templateHtml: modalTemplate, + new ShowModalReactEvent({ + props: { + apiKey, + rootPath, + }, + component: ApiKeysAddedModal, }) ); }; diff --git a/public/app/features/api-keys/__snapshots__/ApiKeysAddedModal.test.tsx.snap b/public/app/features/api-keys/__snapshots__/ApiKeysAddedModal.test.tsx.snap index fb018b4663c..380a868aad9 100644 --- a/public/app/features/api-keys/__snapshots__/ApiKeysAddedModal.test.tsx.snap +++ b/public/app/features/api-keys/__snapshots__/ApiKeysAddedModal.test.tsx.snap @@ -1,79 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Render should render component 1`] = ` -
-
-

- - - API Key Created - -

- - - -
-
+ + -
-
- - Key - - - api key test - -
-
-
- You will only be able to view this key here once! It is not stored in this form, so be sure to copy it now. -
-
- You can authenticate a request using the Authorization HTTP header, example: -
-
-
-        curl -H "Authorization: Bearer 
-        api key test
-        " 
-        test/path
-        /api/dashboards/home
-      
-
-
-
+ It is not stored in this form, so be sure to copy it now. + +

+ You can authenticate a request using the Authorization HTTP header, example: +

+
+    curl -H "Authorization: Bearer 
+    api key test
+    " 
+    test/path
+    /api/dashboards/home
+  
+ `; diff --git a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx index 5a19a17b542..f055af6a7d9 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx @@ -4,7 +4,8 @@ import { Button, Field, Modal, Switch } from '@grafana/ui'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal'; import { appEvents } from 'app/core/core'; -import { ShowModalEvent } from 'app/types/events'; +import { ShowModalReactEvent } from 'app/types/events'; +import { ViewJsonModal } from './ViewJsonModal'; interface Props { dashboard: DashboardModel; @@ -70,15 +71,12 @@ export class ShareExport extends PureComponent { }; openJsonModal = (clone: object) => { - const model = { - object: clone, - enableCopy: true, - }; - appEvents.publish( - new ShowModalEvent({ - src: 'public/app/partials/edit_json.html', - model, + new ShowModalReactEvent({ + props: { + json: JSON.stringify(clone, null, 2), + }, + component: ViewJsonModal, }) ); diff --git a/public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx b/public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx new file mode 100644 index 00000000000..272d60057e1 --- /dev/null +++ b/public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx @@ -0,0 +1,31 @@ +import React, { useCallback } from 'react'; +import { ClipboardButton, CodeEditor, Modal } from '@grafana/ui'; + +import AutoSizer from 'react-virtualized-auto-sizer'; +import { notifyApp } from '../../../../core/actions'; +import { dispatch } from '../../../../store/store'; +import { createSuccessNotification } from '../../../../core/copy/appNotification'; + +export interface ViewJsonModalProps { + json: string; + onDismiss: () => void; +} + +export function ViewJsonModal({ json, onDismiss }: ViewJsonModalProps): JSX.Element { + const getClipboardText = useCallback(() => json, [json]); + const onClipboardCopy = () => { + dispatch(notifyApp(createSuccessNotification('Content copied to clipboard'))); + }; + return ( + + + {({ width }) => } + + + + Copy to Clipboard + + + + ); +} diff --git a/public/app/features/plugins/PluginPage.tsx b/public/app/features/plugins/PluginPage.tsx index 1ee04d3958c..564a9f8b96f 100644 --- a/public/app/features/plugins/PluginPage.tsx +++ b/public/app/features/plugins/PluginPage.tsx @@ -17,7 +17,7 @@ import { UrlQueryMap, } from '@grafana/data'; import { AppNotificationSeverity } from 'app/types'; -import { Alert, InfoBox, Tooltip, PluginSignatureBadge } from '@grafana/ui'; +import { Alert, InfoBox, LinkButton, PluginSignatureBadge, Tooltip } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; import { getPluginSettings } from './PluginSettingsCache'; @@ -31,8 +31,9 @@ import { config } from 'app/core/config'; import { contextSrv } from '../../core/services/context_srv'; import { css } from '@emotion/css'; import { selectors } from '@grafana/e2e-selectors'; -import { ShowModalEvent } from 'app/types/events'; +import { ShowModalReactEvent } from 'app/types/events'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { UpdatePluginModal } from './UpdatePluginModal'; interface Props extends GrafanaRouteComponentProps<{ pluginId: string }, UrlQueryMap> {} @@ -142,10 +143,14 @@ class PluginPage extends PureComponent { } showUpdateInfo = () => { + const { id, name } = this.state.plugin!.meta; appEvents.publish( - new ShowModalEvent({ - src: 'public/app/features/plugins/partials/update_instructions.html', - model: this.state.plugin!.meta, + new ShowModalReactEvent({ + props: { + id, + name, + }, + component: UpdatePluginModal, }) ); }; @@ -162,9 +167,12 @@ class PluginPage extends PureComponent { {meta.hasUpdate && ( )} diff --git a/public/app/features/plugins/UpdatePluginModal.tsx b/public/app/features/plugins/UpdatePluginModal.tsx new file mode 100644 index 00000000000..1e74517eb3a --- /dev/null +++ b/public/app/features/plugins/UpdatePluginModal.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Modal, useStyles2, VerticalGroup } from '@grafana/ui'; +import { GrafanaThemeV2 } from '@grafana/data'; +import { css } from '@emotion/css'; + +export interface UpdatePluginModalProps { + onDismiss: () => void; + id: string; + name: string; +} + +export function UpdatePluginModal({ onDismiss, id, name }: UpdatePluginModalProps): JSX.Element { + const styles = useStyles2(getStyles); + return ( + + + +

Type the following on the command line to update {name}.

+
+            grafana-cli plugins update {id}
+          
+ + Check out {name} on Grafana.com for README and changelog. + If you do not have access to the command line, ask your Grafana administator. + +
+

+ grafana logo + Pro tip: To update all plugins at once, type{' '} + grafana-cli plugins update-all on the command line. +

+
+
+ ); +} + +function getStyles(theme: GrafanaThemeV2) { + return { + small: css` + font-size: ${theme.typography.bodySmall.fontSize}; + font-weight: ${theme.typography.bodySmall.fontWeight}; + `, + weak: css` + color: ${theme.colors.text.disabled}; + font-size: ${theme.typography.bodySmall.fontSize}; + `, + logo: css` + vertical-align: sub; + margin-right: ${theme.spacing(0.3)}; + width: ${theme.spacing(2)}; + `, + codeSmall: css` + white-space: nowrap; + margin: 0 ${theme.spacing(0.25)}; + padding: ${theme.spacing(0.25)}; + `, + }; +} diff --git a/public/app/features/plugins/partials/update_instructions.html b/public/app/features/plugins/partials/update_instructions.html deleted file mode 100644 index d5e28210099..00000000000 --- a/public/app/features/plugins/partials/update_instructions.html +++ /dev/null @@ -1,21 +0,0 @@ - - diff --git a/public/app/partials/edit_json.html b/public/app/partials/edit_json.html deleted file mode 100644 index 03a2ade6537..00000000000 --- a/public/app/partials/edit_json.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
-

- JSON -

- - -
- -
-
- -
- -
- - -
-
-
diff --git a/public/app/types/events.ts b/public/app/types/events.ts index 9c8835c1cbd..7ee5c6ee2ca 100644 --- a/public/app/types/events.ts +++ b/public/app/types/events.ts @@ -172,6 +172,9 @@ export class RemovePanelEvent extends BusEventWithPayload { static type = 'remove-panel'; } +/** + * @deprecated use ShowModalReactEvent instead that has this capability built in + */ export class ShowModalEvent extends BusEventWithPayload { static type = 'show-modal'; } @@ -184,6 +187,9 @@ export class ShowModalReactEvent extends BusEventWithPayload