From 9f6683de2c67bf1db3af798c9146cbbfeb321c66 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 25 Oct 2018 12:47:09 +0200 Subject: [PATCH 01/25] wip: Initial commit for PanelHeaderMenu --- .../features/dashboard/dashgrid/DataPanel.tsx | 1 - .../dashboard/dashgrid/PanelChrome.tsx | 2 +- .../dashboard/dashgrid/PanelHeader.tsx | 83 ------------- .../dashgrid/PanelHeader/PanelHeader.tsx | 50 ++++++++ .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 109 ++++++++++++++++++ .../PanelHeader/PanelHeaderMenuItem.tsx | 34 ++++++ public/sass/components/_dropdown.scss | 5 + public/sass/pages/_dashboard.scss | 1 - 8 files changed, 199 insertions(+), 86 deletions(-) delete mode 100644 public/app/features/dashboard/dashgrid/PanelHeader.tsx create mode 100644 public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx create mode 100644 public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx create mode 100644 public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index d0122363668..a42d392c018 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -38,7 +38,6 @@ export class DataPanel extends Component { constructor(props: Props) { super(props); - this.state = { loading: LoadingState.NotStarted, response: { diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 953dfd62368..d4bfce67c48 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -5,7 +5,7 @@ import React, { ComponentClass, PureComponent } from 'react'; import { getTimeSrv } from '../time_srv'; // Components -import { PanelHeader } from './PanelHeader'; +import { PanelHeader } from './PanelHeader/PanelHeader'; import { DataPanel } from './DataPanel'; // Types diff --git a/public/app/features/dashboard/dashgrid/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader.tsx deleted file mode 100644 index 12d5cd37253..00000000000 --- a/public/app/features/dashboard/dashgrid/PanelHeader.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { PanelModel } from '../panel_model'; -import { DashboardModel } from '../dashboard_model'; -import { store } from 'app/store/configureStore'; -import { updateLocation } from 'app/core/actions'; - -interface PanelHeaderProps { - panel: PanelModel; - dashboard: DashboardModel; -} - -export class PanelHeader extends React.Component { - onEditPanel = () => { - store.dispatch( - updateLocation({ - query: { - panelId: this.props.panel.id, - edit: true, - fullscreen: true, - }, - }) - ); - }; - - onViewPanel = () => { - store.dispatch( - updateLocation({ - query: { - panelId: this.props.panel.id, - edit: false, - fullscreen: true, - }, - }) - ); - }; - - render() { - const isFullscreen = false; - const isLoading = false; - const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen }); - - return ( -
- - - - - - {isLoading && ( - - - - )} - -
- - - {this.props.panel.title} - - - - - - 4m - - -
-
- ); - } -} diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx new file mode 100644 index 00000000000..a5e30d9396e --- /dev/null +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import classNames from 'classnames'; +import { PanelModel } from 'app/features/dashboard/panel_model'; +import { DashboardModel } from 'app/features/dashboard/dashboard_model'; +// import { store } from 'app/store/configureStore'; +// import { updateLocation } from 'app/core/actions'; +import { PanelHeaderMenu } from './PanelHeaderMenu'; +// import appEvents from 'app/core/app_events'; + +interface PanelHeaderProps { + panel: PanelModel; + dashboard: DashboardModel; +} + +export class PanelHeader extends React.Component { + render() { + const isFullscreen = false; + const isLoading = false; + const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen }); + + return ( +
+ + + + + + {isLoading && ( + + + + )} + +
+
+ + + {this.props.panel.title} + + + + + 4m + +
+
+
+ ); + } +} diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx new file mode 100644 index 00000000000..6bc6bb54509 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -0,0 +1,109 @@ +import React, { PureComponent } from 'react'; +// import { store } from 'app/store/configureStore'; +import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; +import appEvents from 'app/core/app_events'; +import { store } from 'app/store/configureStore'; +import { updateLocation } from 'app/core/actions'; + +export interface PanelHeaderMenuProps { + panelId: number; +} + +export class PanelHeaderMenu extends PureComponent { + onEditPanel = () => { + store.dispatch( + updateLocation({ + query: { + panelId: this.props.panelId, + edit: true, + fullscreen: true, + }, + }) + ); + }; + + onViewPanel = () => { + store.dispatch( + updateLocation({ + query: { + panelId: this.props.panelId, + edit: false, + fullscreen: true, + }, + }) + ); + }; + + onRemovePanel = () => { + appEvents.emit('panel-remove', { + panelId: this.props.panelId, + }); + }; + + render() { + return ( +
+
    + + + {}} + shortcut="p s" + /> + {}} + > +
      + {}} + shortcut="p d" + /> + + {}} /> + + {}} /> + + {}} /> + + {}} + shortcut="p l" + /> +
    +
    + + +
+
+ ); + } +} diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx new file mode 100644 index 00000000000..3eb4e72ca9d --- /dev/null +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx @@ -0,0 +1,34 @@ +import React, { SFC } from 'react'; + +export enum PanelHeaderMenuItemTypes { + Button = 'Button', // ? + Divider = 'Divider', + Link = 'Link', + SubMenu = 'SubMenu', +} + +export interface PanelHeaderMenuItemProps { + type: PanelHeaderMenuItemTypes; + text?: string; + iconClassName?: string; + handleClick?: () => void; + shortcut?: string; + children?: any; +} + +export const PanelHeaderMenuItem: SFC = props => { + const isSubMenu = props.type === PanelHeaderMenuItemTypes.SubMenu; + const isDivider = props.type === PanelHeaderMenuItemTypes.Divider; + return isDivider ? ( +
  • + ) : ( +
  • + + {props.iconClassName && } + {props.text} + {props.shortcut && {props.shortcut}} + + {props.children} +
  • + ); +}; diff --git a/public/sass/components/_dropdown.scss b/public/sass/components/_dropdown.scss index 37dbdcd89ef..9e7f46fe514 100644 --- a/public/sass/components/_dropdown.scss +++ b/public/sass/components/_dropdown.scss @@ -183,6 +183,11 @@ display: block; } + & > .dropdown > .dropdown-menu { + // Panel menu. TODO: See if we can merge this with above + display: block; + } + &.cascade-open { .dropdown-menu { display: block; diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 795766a22de..125edac500f 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -138,7 +138,6 @@ div.flot-text { padding: 3px 5px; visibility: hidden; opacity: 0; - position: absolute; width: 16px; height: 16px; left: 1px; From 212c086162c0f3f55e7eba10216c769c6a051e47 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 25 Oct 2018 13:57:23 +0200 Subject: [PATCH 02/25] Mobx is now Redux --- public/app/core/services/bridge_srv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/services/bridge_srv.ts b/public/app/core/services/bridge_srv.ts index ee184c243ac..1c91673495d 100644 --- a/public/app/core/services/bridge_srv.ts +++ b/public/app/core/services/bridge_srv.ts @@ -4,7 +4,7 @@ import { store } from 'app/store/configureStore'; import locationUtil from 'app/core/utils/location_util'; import { updateLocation } from 'app/core/actions'; -// Services that handles angular -> mobx store sync & other react <-> angular sync +// Services that handles angular -> redux store sync & other react <-> angular sync export class BridgeSrv { private fullPageReloadRoutes; From 820e47b4c0519f5bf1b8b926bef288080f7c0400 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 25 Oct 2018 13:58:26 +0200 Subject: [PATCH 03/25] wip: panel-header: Remove panel --- .../dashgrid/PanelHeader/PanelHeader.tsx | 3 +- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx index a5e30d9396e..ae04f0f0405 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -14,6 +14,7 @@ interface PanelHeaderProps { export class PanelHeader extends React.Component { render() { + const { dashboard } = this.props; const isFullscreen = false; const isLoading = false; const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen }); @@ -38,7 +39,7 @@ export class PanelHeader extends React.Component { {this.props.panel.title} - + 4m diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 6bc6bb54509..19fee872e4b 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -1,5 +1,7 @@ import React, { PureComponent } from 'react'; // import { store } from 'app/store/configureStore'; +import { DashboardModel } from 'app/features/dashboard/dashboard_model'; +import { PanelModel } from 'app/features/dashboard/panel_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; import appEvents from 'app/core/app_events'; import { store } from 'app/store/configureStore'; @@ -7,6 +9,7 @@ import { updateLocation } from 'app/core/actions'; export interface PanelHeaderMenuProps { panelId: number; + dashboard: DashboardModel; } export class PanelHeaderMenu extends PureComponent { @@ -35,9 +38,32 @@ export class PanelHeaderMenu extends PureComponent { }; onRemovePanel = () => { - appEvents.emit('panel-remove', { - panelId: this.props.panelId, - }); + const { panelId, dashboard } = this.props; + const panelInfo = dashboard.getPanelInfoById(panelId); + this.removePanel(panelInfo.panel, true); + }; + + removePanel = (panel: PanelModel, ask: boolean) => { + const { dashboard } = this.props; + + // confirm deletion + if (ask !== false) { + const text2 = panel.alert ? 'Panel includes an alert rule, removing panel will also remove alert rule' : null; + const confirmText = panel.alert ? 'YES' : null; + + appEvents.emit('confirm-modal', { + title: 'Remove Panel', + text: 'Are you sure you want to remove this panel?', + text2: text2, + icon: 'fa-trash', + confirmText: confirmText, + yesText: 'Remove', + onConfirm: () => this.removePanel(panel, false), + }); + return; + } + + dashboard.removePanel(panel); }; render() { From bf8703edb88664e15f6244d53a778f7fc6bff5f2 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 25 Oct 2018 14:29:03 +0200 Subject: [PATCH 04/25] wip: panel-header: Move code existing in both angular+react to utility functions --- .../app/features/dashboard/dashboard_ctrl.ts | 31 ++--------------- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 33 ++----------------- public/app/features/dashboard/utils/panel.ts | 27 +++++++++++++++ 3 files changed, 31 insertions(+), 60 deletions(-) create mode 100644 public/app/features/dashboard/utils/panel.ts diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 5871a579f3c..60517df19f6 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -2,13 +2,13 @@ import config from 'app/core/config'; import appEvents from 'app/core/app_events'; import coreModule from 'app/core/core_module'; +import { removePanel } from 'app/features/dashboard/utils/panel'; // Services import { AnnotationsSrv } from '../annotations/annotations_srv'; // Types import { DashboardModel } from './dashboard_model'; -import { PanelModel } from './panel_model'; export class DashboardCtrl { dashboard: DashboardModel; @@ -136,34 +136,7 @@ export class DashboardCtrl { } const panelInfo = this.dashboard.getPanelInfoById(options.panelId); - this.removePanel(panelInfo.panel, true); - } - - removePanel(panel: PanelModel, ask: boolean) { - // confirm deletion - if (ask !== false) { - let text2, confirmText; - - if (panel.alert) { - text2 = 'Panel includes an alert rule, removing panel will also remove alert rule'; - confirmText = 'YES'; - } - - this.$scope.appEvent('confirm-modal', { - title: 'Remove Panel', - text: 'Are you sure you want to remove this panel?', - text2: text2, - icon: 'fa-trash', - confirmText: confirmText, - yesText: 'Remove', - onConfirm: () => { - this.removePanel(panel, false); - }, - }); - return; - } - - this.dashboard.removePanel(panel); + removePanel(this.dashboard, panelInfo.panel, true); } onDestroy() { diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 19fee872e4b..3a27796bb90 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -1,11 +1,9 @@ import React, { PureComponent } from 'react'; -// import { store } from 'app/store/configureStore'; import { DashboardModel } from 'app/features/dashboard/dashboard_model'; -import { PanelModel } from 'app/features/dashboard/panel_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; -import appEvents from 'app/core/app_events'; import { store } from 'app/store/configureStore'; import { updateLocation } from 'app/core/actions'; +import { removePanel } from 'app/features/dashboard/utils/panel'; export interface PanelHeaderMenuProps { panelId: number; @@ -40,30 +38,7 @@ export class PanelHeaderMenu extends PureComponent { onRemovePanel = () => { const { panelId, dashboard } = this.props; const panelInfo = dashboard.getPanelInfoById(panelId); - this.removePanel(panelInfo.panel, true); - }; - - removePanel = (panel: PanelModel, ask: boolean) => { - const { dashboard } = this.props; - - // confirm deletion - if (ask !== false) { - const text2 = panel.alert ? 'Panel includes an alert rule, removing panel will also remove alert rule' : null; - const confirmText = panel.alert ? 'YES' : null; - - appEvents.emit('confirm-modal', { - title: 'Remove Panel', - text: 'Are you sure you want to remove this panel?', - text2: text2, - icon: 'fa-trash', - confirmText: confirmText, - yesText: 'Remove', - onConfirm: () => this.removePanel(panel, false), - }); - return; - } - - dashboard.removePanel(panel); + removePanel(dashboard, panelInfo.panel, true); }; render() { @@ -105,13 +80,9 @@ export class PanelHeaderMenu extends PureComponent { handleClick={() => {}} shortcut="p d" /> - {}} /> - {}} /> - {}} /> - { + // confirm deletion + if (ask !== false) { + const text2 = panel.alert ? 'Panel includes an alert rule, removing panel will also remove alert rule' : null; + const confirmText = panel.alert ? 'YES' : null; + + appEvents.emit('confirm-modal', { + title: 'Remove Panel', + text: 'Are you sure you want to remove this panel?', + text2: text2, + icon: 'fa-trash', + confirmText: confirmText, + yesText: 'Remove', + onConfirm: () => removePanel(dashboard, panel, false), + }); + return; + } + dashboard.removePanel(panel); +}; + +export default { + removePanel, +}; From 839057dc7a393667b231b42fb4d7451a0db77782 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 30 Oct 2018 14:21:47 +0100 Subject: [PATCH 05/25] wip: Add "Share" to the react panels --- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 3a27796bb90..5e6f5a5d28c 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -4,6 +4,7 @@ import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenu import { store } from 'app/store/configureStore'; import { updateLocation } from 'app/core/actions'; import { removePanel } from 'app/features/dashboard/utils/panel'; +import appEvents from 'app/core/app_events'; export interface PanelHeaderMenuProps { panelId: number; @@ -11,6 +12,13 @@ export interface PanelHeaderMenuProps { } export class PanelHeaderMenu extends PureComponent { + getPanel = () => { + // Pass in panel as prop instead? + const { panelId, dashboard } = this.props; + const panelInfo = dashboard.getPanelInfoById(panelId); + return panelInfo.panel; + }; + onEditPanel = () => { store.dispatch( updateLocation({ @@ -36,9 +44,22 @@ export class PanelHeaderMenu extends PureComponent { }; onRemovePanel = () => { - const { panelId, dashboard } = this.props; - const panelInfo = dashboard.getPanelInfoById(panelId); - removePanel(dashboard, panelInfo.panel, true); + const { dashboard } = this.props; + const panel = this.getPanel(); + removePanel(dashboard, panel, true); + }; + + onSharePanel = () => { + const { dashboard } = this.props; + const panel = this.getPanel(); + + appEvents.emit('show-modal', { + src: 'public/app/features/dashboard/partials/shareModal.html', + model: { + panel: panel, + dashboard: dashboard, + }, + }); }; render() { @@ -63,7 +84,7 @@ export class PanelHeaderMenu extends PureComponent { type={PanelHeaderMenuItemTypes.Link} text="Share" iconClassName="fa fa-fw fa-share" - handleClick={() => {}} + handleClick={this.onSharePanel} shortcut="p s" /> Date: Tue, 30 Oct 2018 14:38:18 +0100 Subject: [PATCH 06/25] wip: panel-header: Add "Duplicate" --- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 11 +++++++++-- public/app/features/dashboard/utils/panel.ts | 5 +++++ public/app/features/panel/panel_ctrl.ts | 3 ++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 5e6f5a5d28c..49adfbb703a 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -3,7 +3,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; import { store } from 'app/store/configureStore'; import { updateLocation } from 'app/core/actions'; -import { removePanel } from 'app/features/dashboard/utils/panel'; +import { removePanel, duplicatePanel } from 'app/features/dashboard/utils/panel'; import appEvents from 'app/core/app_events'; export interface PanelHeaderMenuProps { @@ -62,6 +62,13 @@ export class PanelHeaderMenu extends PureComponent { }); }; + onDuplicatePanel = () => { + const { dashboard } = this.props; + const panel = this.getPanel(); + + duplicatePanel(dashboard, panel); + }; + render() { return (
    @@ -98,7 +105,7 @@ export class PanelHeaderMenu extends PureComponent { type={PanelHeaderMenuItemTypes.Link} text="Duplicate" iconClassName="" - handleClick={() => {}} + handleClick={this.onDuplicatePanel} shortcut="p d" /> {}} /> diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index 6257aae232e..35ccfdf3b3f 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -22,6 +22,11 @@ export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: b dashboard.removePanel(panel); }; +export const duplicatePanel = (dashboard: DashboardModel, panel: PanelModel) => { + dashboard.duplicatePanel(panel); +}; + export default { removePanel, + duplicatePanel, }; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 08605132e82..d1e2dcc20cf 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -3,6 +3,7 @@ import _ from 'lodash'; import $ from 'jquery'; import { appEvents, profiler } from 'app/core/core'; import { PanelModel } from 'app/features/dashboard/panel_model'; +import { duplicatePanel } from 'app/features/dashboard/utils/panel'; import Remarkable from 'remarkable'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, LS_PANEL_COPY_KEY } from 'app/core/constants'; import store from 'app/core/store'; @@ -241,7 +242,7 @@ export class PanelCtrl { } duplicate() { - this.dashboard.duplicatePanel(this.panel); + duplicatePanel(this.dashboard, this.panel); } removePanel() { From edceb204e7a552b1ebd82149b76b78de96474cec Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 30 Oct 2018 14:53:04 +0100 Subject: [PATCH 07/25] wip: panel-header: Add "Copy" functionality --- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 11 ++++++++--- public/app/features/dashboard/utils/panel.ts | 8 ++++++++ public/app/features/panel/panel_ctrl.ts | 10 ++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 49adfbb703a..190c13ead9d 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -3,7 +3,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; import { store } from 'app/store/configureStore'; import { updateLocation } from 'app/core/actions'; -import { removePanel, duplicatePanel } from 'app/features/dashboard/utils/panel'; +import { removePanel, duplicatePanel, copyPanel } from 'app/features/dashboard/utils/panel'; import appEvents from 'app/core/app_events'; export interface PanelHeaderMenuProps { @@ -69,6 +69,11 @@ export class PanelHeaderMenu extends PureComponent { duplicatePanel(dashboard, panel); }; + onCopyPanel = () => { + const panel = this.getPanel(); + copyPanel(panel); + }; + render() { return (
    @@ -98,7 +103,7 @@ export class PanelHeaderMenu extends PureComponent { type={PanelHeaderMenuItemTypes.SubMenu} text="More ..." iconClassName="fa fa-fw fa-cube" - handleClick={() => {}} + handleClick={null} >
      { handleClick={this.onDuplicatePanel} shortcut="p d" /> - {}} /> + {}} /> {}} /> { // confirm deletion @@ -26,7 +28,13 @@ export const duplicatePanel = (dashboard: DashboardModel, panel: PanelModel) => dashboard.duplicatePanel(panel); }; +export const copyPanel = (panel: PanelModel) => { + store.set(LS_PANEL_COPY_KEY, JSON.stringify(panel.getSaveModel())); + appEvents.emit('alert-success', ['Panel copied. Open Add Panel to paste']); +}; + export default { removePanel, duplicatePanel, + copyPanel, }; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index d1e2dcc20cf..cb35b5ef470 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -1,12 +1,11 @@ import config from 'app/core/config'; import _ from 'lodash'; import $ from 'jquery'; -import { appEvents, profiler } from 'app/core/core'; +import { profiler } from 'app/core/core'; import { PanelModel } from 'app/features/dashboard/panel_model'; -import { duplicatePanel } from 'app/features/dashboard/utils/panel'; +import { duplicatePanel, copyPanel } from 'app/features/dashboard/utils/panel'; import Remarkable from 'remarkable'; -import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, LS_PANEL_COPY_KEY } from 'app/core/constants'; -import store from 'app/core/store'; +import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; const TITLE_HEIGHT = 27; const PANEL_BORDER = 2; @@ -264,8 +263,7 @@ export class PanelCtrl { } copyPanel() { - store.set(LS_PANEL_COPY_KEY, JSON.stringify(this.panel.getSaveModel())); - appEvents.emit('alert-success', ['Panel copied. Open Add Panel to paste']); + copyPanel(this.panel); } replacePanel(newPanel, oldPanel) { From f9dd5165782e9841f464dfaf3fa1205ebaa7b7cc Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 30 Oct 2018 16:07:59 +0100 Subject: [PATCH 08/25] wip: panel-header: Add "Edit JSON" functionality + make sure everyone using the json editor pass in the model property instead of the scope property when triggering the json modal --- .../app/core/controllers/json_editor_ctrl.ts | 8 ++--- .../app/features/dashboard/dashboard_ctrl.ts | 11 +++--- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 14 ++++++-- .../features/dashboard/export/export_modal.ts | 9 ++--- public/app/features/dashboard/utils/panel.ts | 33 ++++++++++++++--- public/app/features/panel/panel_ctrl.ts | 36 ++++--------------- 6 files changed, 63 insertions(+), 48 deletions(-) diff --git a/public/app/core/controllers/json_editor_ctrl.ts b/public/app/core/controllers/json_editor_ctrl.ts index 9c3f9d9e98d..7439433c55e 100644 --- a/public/app/core/controllers/json_editor_ctrl.ts +++ b/public/app/core/controllers/json_editor_ctrl.ts @@ -4,13 +4,13 @@ import coreModule from '../core_module'; export class JsonEditorCtrl { /** @ngInject */ constructor($scope) { - $scope.json = angular.toJson($scope.object, true); - $scope.canUpdate = $scope.updateHandler !== void 0 && $scope.contextSrv.isEditor; - $scope.canCopy = $scope.enableCopy; + $scope.json = angular.toJson($scope.model.object, true); + $scope.canUpdate = $scope.model.updateHandler !== void 0 && $scope.contextSrv.isEditor; + $scope.canCopy = $scope.model.enableCopy; $scope.update = () => { const newObject = angular.fromJson($scope.json); - $scope.updateHandler(newObject, $scope.object); + $scope.model.updateHandler(newObject, $scope.model.object); }; $scope.getContentForClipboard = () => $scope.json; diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 60517df19f6..6611a728803 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -19,7 +19,6 @@ export class DashboardCtrl { /** @ngInject */ constructor( private $scope, - private $rootScope, private keybindingSrv, private timeSrv, private variableSrv, @@ -112,12 +111,14 @@ export class DashboardCtrl { } showJsonEditor(evt, options) { - const editScope = this.$rootScope.$new(); - editScope.object = options.object; - editScope.updateHandler = options.updateHandler; + const model = { + object: options.object, + updateHandler: options.updateHandler, + }; + this.$scope.appEvent('show-dash-editor', { src: 'public/app/partials/edit_json.html', - scope: editScope, + model: model, }); } diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 190c13ead9d..9ac1e91483c 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -3,7 +3,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; import { store } from 'app/store/configureStore'; import { updateLocation } from 'app/core/actions'; -import { removePanel, duplicatePanel, copyPanel } from 'app/features/dashboard/utils/panel'; +import { removePanel, duplicatePanel, copyPanel, editPanelJson } from 'app/features/dashboard/utils/panel'; import appEvents from 'app/core/app_events'; export interface PanelHeaderMenuProps { @@ -74,6 +74,12 @@ export class PanelHeaderMenu extends PureComponent { copyPanel(panel); }; + onEditPanelJson = () => { + const { dashboard } = this.props; + const panel = this.getPanel(); + editPanelJson(dashboard, panel); + }; + render() { return (
      @@ -114,7 +120,11 @@ export class PanelHeaderMenu extends PureComponent { shortcut="p d" /> - {}} /> + {}} /> { appEvents.emit('alert-success', ['Panel copied. Open Add Panel to paste']); }; -export default { - removePanel, - duplicatePanel, - copyPanel, +const replacePanel = (dashboard: DashboardModel, newPanel: PanelModel, oldPanel: PanelModel) => { + const index = dashboard.panels.findIndex(panel => { + return panel.id === oldPanel.id; + }); + + const deletedPanel = dashboard.panels.splice(index, 1); + dashboard.events.emit('panel-removed', deletedPanel); + + newPanel = new PanelModel(newPanel); + newPanel.id = oldPanel.id; + + dashboard.panels.splice(index, 0, newPanel); + dashboard.sortPanelsByGridPos(); + dashboard.events.emit('panel-added', newPanel); +}; + +export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => { + const model = { + object: panel.getSaveModel(), + updateHandler: (newPanel: PanelModel, oldPanel: PanelModel) => { + replacePanel(dashboard, newPanel, oldPanel); + }, + enableCopy: true, + }; + + appEvents.emit('show-modal', { + src: 'public/app/partials/edit_json.html', + model: model, + }); }; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index cb35b5ef470..169ec8b322b 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -2,8 +2,11 @@ import config from 'app/core/config'; import _ from 'lodash'; import $ from 'jquery'; import { profiler } from 'app/core/core'; -import { PanelModel } from 'app/features/dashboard/panel_model'; -import { duplicatePanel, copyPanel } from 'app/features/dashboard/utils/panel'; +import { + duplicatePanel, + copyPanel as copyPanelUtil, + editPanelJson as editPanelJsonUtil, +} from 'app/features/dashboard/utils/panel'; import Remarkable from 'remarkable'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; @@ -251,36 +254,11 @@ export class PanelCtrl { } editPanelJson() { - const editScope = this.$scope.$root.$new(); - editScope.object = this.panel.getSaveModel(); - editScope.updateHandler = this.replacePanel.bind(this); - editScope.enableCopy = true; - - this.publishAppEvent('show-modal', { - src: 'public/app/partials/edit_json.html', - scope: editScope, - }); + editPanelJsonUtil(this.dashboard, this.panel); } copyPanel() { - copyPanel(this.panel); - } - - replacePanel(newPanel, oldPanel) { - const dashboard = this.dashboard; - const index = _.findIndex(dashboard.panels, panel => { - return panel.id === oldPanel.id; - }); - - const deletedPanel = dashboard.panels.splice(index, 1); - this.dashboard.events.emit('panel-removed', deletedPanel); - - newPanel = new PanelModel(newPanel); - newPanel.id = oldPanel.id; - - dashboard.panels.splice(index, 0, newPanel); - dashboard.sortPanelsByGridPos(); - dashboard.events.emit('panel-added', newPanel); + copyPanelUtil(this.panel); } sharePanel() { From 5375ce5ffdcc7b6588edcdc9c06c0ad8c5563f71 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 30 Oct 2018 16:39:08 +0100 Subject: [PATCH 09/25] wip: panel-header: Refactor so "Share" use the same code in angular+react --- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 11 ++--------- public/app/features/dashboard/shareModalCtrl.ts | 2 ++ public/app/features/dashboard/utils/panel.ts | 10 ++++++++++ public/app/features/panel/panel_ctrl.ts | 10 ++-------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 9ac1e91483c..13b0fca86e9 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -3,8 +3,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; import { store } from 'app/store/configureStore'; import { updateLocation } from 'app/core/actions'; -import { removePanel, duplicatePanel, copyPanel, editPanelJson } from 'app/features/dashboard/utils/panel'; -import appEvents from 'app/core/app_events'; +import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel'; export interface PanelHeaderMenuProps { panelId: number; @@ -53,13 +52,7 @@ export class PanelHeaderMenu extends PureComponent { const { dashboard } = this.props; const panel = this.getPanel(); - appEvents.emit('show-modal', { - src: 'public/app/features/dashboard/partials/shareModal.html', - model: { - panel: panel, - dashboard: dashboard, - }, - }); + sharePanel(dashboard, panel); }; onDuplicatePanel = () => { diff --git a/public/app/features/dashboard/shareModalCtrl.ts b/public/app/features/dashboard/shareModalCtrl.ts index c00a6d8d57f..f894d24202f 100644 --- a/public/app/features/dashboard/shareModalCtrl.ts +++ b/public/app/features/dashboard/shareModalCtrl.ts @@ -12,6 +12,8 @@ export function ShareModalCtrl($scope, $rootScope, $location, $timeout, timeSrv, $scope.editor = { index: $scope.tabIndex || 0 }; $scope.init = () => { + $scope.panel = $scope.model.panel || $scope.panel; // React pass panel and dashboard in the "model" property + $scope.dashboard = $scope.model.dashboard || $scope.dashboard; $scope.modeSharePanel = $scope.panel ? true : false; $scope.tabs = [{ title: 'Link', src: 'shareLink.html' }]; diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index 7f89090c336..fff837d9685 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -63,3 +63,13 @@ export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => { model: model, }); }; + +export const sharePanel = (dashboard: DashboardModel, panel: PanelModel) => { + appEvents.emit('show-modal', { + src: 'public/app/features/dashboard/partials/shareModal.html', + model: { + dashboard: dashboard, + panel: panel, + }, + }); +}; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 169ec8b322b..92932142690 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -6,6 +6,7 @@ import { duplicatePanel, copyPanel as copyPanelUtil, editPanelJson as editPanelJsonUtil, + sharePanel as sharePanelUtil, } from 'app/features/dashboard/utils/panel'; import Remarkable from 'remarkable'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; @@ -262,14 +263,7 @@ export class PanelCtrl { } sharePanel() { - const shareScope = this.$scope.$new(); - shareScope.panel = this.panel; - shareScope.dashboard = this.dashboard; - - this.publishAppEvent('show-modal', { - src: 'public/app/features/dashboard/partials/shareModal.html', - scope: shareScope, - }); + sharePanelUtil(this.dashboard, this.panel); } getInfoMode() { From 79da3dc9f6f89576727fc966a615051b1663c25c Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 31 Oct 2018 13:41:50 +0100 Subject: [PATCH 10/25] wip: panel-header: Change DashboardPanel to a PureComponent to avoid unwanted rerenders --- public/app/features/dashboard/dashgrid/DashboardPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 7dd8a06996d..fcfc84e287b 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import config from 'app/core/config'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -20,7 +20,7 @@ export interface State { pluginExports: PluginExports; } -export class DashboardPanel extends React.Component { +export class DashboardPanel extends PureComponent { element: any; angularPanel: AngularComponent; pluginInfo: any; From 61513102169f05d7955d34254830c85cd302cf01 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 31 Oct 2018 13:43:21 +0100 Subject: [PATCH 11/25] wip: panel-header: Start implementing the Toggle legend, but its not taken all the way --- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 16 ++++++++++++++-- public/app/features/dashboard/utils/panel.ts | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 13b0fca86e9..826406fddff 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -3,7 +3,14 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; import { store } from 'app/store/configureStore'; import { updateLocation } from 'app/core/actions'; -import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel'; +import { + removePanel, + duplicatePanel, + copyPanel, + editPanelJson, + sharePanel, + toggleLegend, +} from 'app/features/dashboard/utils/panel'; export interface PanelHeaderMenuProps { panelId: number; @@ -73,6 +80,11 @@ export class PanelHeaderMenu extends PureComponent { editPanelJson(dashboard, panel); }; + onToggleLegend = () => { + const panel = this.getPanel(); + toggleLegend(panel); + }; + render() { return (
      @@ -122,7 +134,7 @@ export class PanelHeaderMenu extends PureComponent { {}} + handleClick={this.onToggleLegend} shortcut="p l" />
    diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index fff837d9685..151c1ea8d61 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -73,3 +73,14 @@ export const sharePanel = (dashboard: DashboardModel, panel: PanelModel) => { }, }); }; + +export const refreshPanel = (panel: PanelModel) => { + panel.refresh(); +}; + +export const toggleLegend = (panel: PanelModel) => { + console.log('Toggle legend is not implemented yet'); + // We need to set panel.legend defaults first + // panel.legend.show = !panel.legend.show; + refreshPanel(panel); +}; From f124b9de6a0c12125d420c25f7e2625901b05af6 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 31 Oct 2018 16:04:14 +0100 Subject: [PATCH 12/25] wip: panel-header: Separate all panel actions to its own file so we decouple them from react --- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 159 +++--------------- .../PanelHeader/PanelHeaderMenuItem.tsx | 4 +- .../features/dashboard/utils/panel_menu.ts | 140 +++++++++++++++ 3 files changed, 169 insertions(+), 134 deletions(-) create mode 100644 public/app/features/dashboard/utils/panel_menu.ts diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index 826406fddff..adce83e8c40 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -1,16 +1,7 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, Fragment } from 'react'; import { DashboardModel } from 'app/features/dashboard/dashboard_model'; -import { PanelHeaderMenuItem, PanelHeaderMenuItemTypes } from './PanelHeaderMenuItem'; -import { store } from 'app/store/configureStore'; -import { updateLocation } from 'app/core/actions'; -import { - removePanel, - duplicatePanel, - copyPanel, - editPanelJson, - sharePanel, - toggleLegend, -} from 'app/features/dashboard/utils/panel'; +import { PanelHeaderMenuItem, PanelHeaderMenuItemProps } from './PanelHeaderMenuItem'; +import { getPanelMenu } from 'app/features/dashboard/utils/panel_menu'; export interface PanelHeaderMenuProps { panelId: number; @@ -25,130 +16,32 @@ export class PanelHeaderMenu extends PureComponent { return panelInfo.panel; }; - onEditPanel = () => { - store.dispatch( - updateLocation({ - query: { - panelId: this.props.panelId, - edit: true, - fullscreen: true, - }, - }) + renderItems = (menu: PanelHeaderMenuItemProps[], isSubMenu = false) => { + return ( +
      + {menu.map(menuItem => { + console.log(this); + return ( + + + {menuItem.subMenu && this.renderItems(menuItem.subMenu, true)} + + + ); + })} +
    ); }; - onViewPanel = () => { - store.dispatch( - updateLocation({ - query: { - panelId: this.props.panelId, - edit: false, - fullscreen: true, - }, - }) - ); - }; - - onRemovePanel = () => { - const { dashboard } = this.props; - const panel = this.getPanel(); - removePanel(dashboard, panel, true); - }; - - onSharePanel = () => { - const { dashboard } = this.props; - const panel = this.getPanel(); - - sharePanel(dashboard, panel); - }; - - onDuplicatePanel = () => { - const { dashboard } = this.props; - const panel = this.getPanel(); - - duplicatePanel(dashboard, panel); - }; - - onCopyPanel = () => { - const panel = this.getPanel(); - copyPanel(panel); - }; - - onEditPanelJson = () => { - const { dashboard } = this.props; - const panel = this.getPanel(); - editPanelJson(dashboard, panel); - }; - - onToggleLegend = () => { - const panel = this.getPanel(); - toggleLegend(panel); - }; - render() { - return ( -
    -
      - - - - -
        - - - - {}} /> - -
      -
      - - -
    -
    - ); + const { dashboard } = this.props; + const menu = getPanelMenu(dashboard, this.getPanel()); + return
    {this.renderItems(menu)}
    ; } } diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx index 3eb4e72ca9d..f0b5579c2a1 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx @@ -1,6 +1,6 @@ import React, { SFC } from 'react'; -export enum PanelHeaderMenuItemTypes { +export enum PanelHeaderMenuItemTypes { // TODO: Evaluate. Remove? Button = 'Button', // ? Divider = 'Divider', Link = 'Link', @@ -14,6 +14,8 @@ export interface PanelHeaderMenuItemProps { handleClick?: () => void; shortcut?: string; children?: any; + subMenu?: PanelHeaderMenuItemProps[]; + role?: string; } export const PanelHeaderMenuItem: SFC = props => { diff --git a/public/app/features/dashboard/utils/panel_menu.ts b/public/app/features/dashboard/utils/panel_menu.ts new file mode 100644 index 00000000000..de2ba852d13 --- /dev/null +++ b/public/app/features/dashboard/utils/panel_menu.ts @@ -0,0 +1,140 @@ +import { PanelHeaderMenuItemTypes, PanelHeaderMenuItemProps } from './../dashgrid/PanelHeader/PanelHeaderMenuItem'; +import { store } from 'app/store/configureStore'; +import { updateLocation } from 'app/core/actions'; +import { PanelModel } from 'app/features/dashboard/panel_model'; +import { DashboardModel } from 'app/features/dashboard/dashboard_model'; +import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel'; + +export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { + const onViewPanel = () => { + store.dispatch( + updateLocation({ + query: { + panelId: panel.id, + edit: false, + fullscreen: true, + }, + }) + ); + }; + + const onEditPanel = () => { + store.dispatch( + updateLocation({ + query: { + panelId: panel.id, + edit: true, + fullscreen: true, + }, + }) + ); + }; + + const onSharePanel = () => { + sharePanel(dashboard, panel); + }; + + const onDuplicatePanel = () => { + duplicatePanel(dashboard, panel); + }; + + const onCopyPanel = () => { + copyPanel(panel); + }; + + const onEditPanelJson = () => { + editPanelJson(dashboard, panel); + }; + + const onRemovePanel = () => { + removePanel(dashboard, panel, true); + }; + + const getSubMenu = () => { + const menu: PanelHeaderMenuItemProps[] = []; + + if (!panel.fullscreen && dashboard.meta.canEdit) { + menu.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'Duplicate', + handleClick: onDuplicatePanel, + shortcut: 'p d', + role: 'Editor', + }); + menu.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'Copy', + handleClick: onCopyPanel, + role: 'Editor', + }); + } + + menu.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'Panel JSON', + handleClick: onEditPanelJson, + }); + + // TODO: Handle this somehow + // this.events.emit('init-panel-actions', menu); + return menu; + }; + + const menu: PanelHeaderMenuItemProps[] = []; + + menu.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'View', + iconClassName: 'fa fa-fw fa-eye', + handleClick: onViewPanel, + shortcut: 'v', + }); + + if (dashboard.meta.canEdit) { + menu.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'Edit', + iconClassName: 'fa fa-fw fa-edit', + handleClick: onEditPanel, + shortcut: 'e', + role: 'Editor', + }); + } + + menu.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'Share', + iconClassName: 'fa fa-fw fa-share', + handleClick: onSharePanel, + shortcut: 'p s', + }); + + const subMenu: PanelHeaderMenuItemProps[] = getSubMenu(); + + menu.push({ + type: PanelHeaderMenuItemTypes.SubMenu, + text: 'More...', + iconClassName: 'fa fa-fw fa-cube', + handleClick: null, + subMenu: subMenu, + }); + + if (dashboard.meta.canEdit) { + menu.push({ + type: PanelHeaderMenuItemTypes.Divider, + role: 'Editor', + }); + menu.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'Remove', + iconClassName: 'fa fa-fw fa-trash', + handleClick: onRemovePanel, + shortcut: 'p r', + role: 'Editor', + }); + } + + // Additional items from sub-class + // menu.push(...this.getAdditionalMenuItems()); + return menu; +}; From 443d381dd91444d81fbf614828fd0c6b06f52b7c Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 31 Oct 2018 16:19:11 +0100 Subject: [PATCH 13/25] wip: panel-header: Add possibility to add custom actions to the menu by passing them in as props --- public/app/features/dashboard/utils/panel_menu.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/utils/panel_menu.ts b/public/app/features/dashboard/utils/panel_menu.ts index de2ba852d13..c86595953b5 100644 --- a/public/app/features/dashboard/utils/panel_menu.ts +++ b/public/app/features/dashboard/utils/panel_menu.ts @@ -5,7 +5,12 @@ import { PanelModel } from 'app/features/dashboard/panel_model'; import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel'; -export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { +export const getPanelMenu = ( + dashboard: DashboardModel, + panel: PanelModel, + extraMenuItems: PanelHeaderMenuItemProps[] = [], + extraSubMenuItems: PanelHeaderMenuItemProps[] = [] +) => { const onViewPanel = () => { store.dispatch( updateLocation({ @@ -77,6 +82,9 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { // TODO: Handle this somehow // this.events.emit('init-panel-actions', menu); + extraSubMenuItems.forEach(item => { + menu.push(item); + }); return menu; }; @@ -109,6 +117,10 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { shortcut: 'p s', }); + extraMenuItems.forEach(item => { + menu.push(item); + }); + const subMenu: PanelHeaderMenuItemProps[] = getSubMenu(); menu.push({ From f471482569a3d391c8c76d8ef6019f9251da9244 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 31 Oct 2018 16:22:48 +0100 Subject: [PATCH 14/25] wip: panel-header: Fragment not needed anymore --- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index adce83e8c40..b454ccad4a6 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent, Fragment } from 'react'; +import React, { PureComponent } from 'react'; import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemProps } from './PanelHeaderMenuItem'; import { getPanelMenu } from 'app/features/dashboard/utils/panel_menu'; @@ -22,17 +22,15 @@ export class PanelHeaderMenu extends PureComponent { {menu.map(menuItem => { console.log(this); return ( - - - {menuItem.subMenu && this.renderItems(menuItem.subMenu, true)} - - + + {menuItem.subMenu && this.renderItems(menuItem.subMenu, true)} + ); })} From ca4612af261aec49970ad45ca57a2e46e482cb62 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 1 Nov 2018 12:01:27 +0100 Subject: [PATCH 15/25] wip: panel-header: Merge conflicts --- .../dashboard/dashgrid/DashboardPanel.tsx | 3 +- .../dashboard/dashgrid/PanelChrome.tsx | 12 +-- .../dashgrid/PanelHeader/PanelHeader.tsx | 14 ++- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 12 ++- .../features/dashboard/utils/panel_menu.ts | 10 +- public/app/plugins/panel/graph2/module.tsx | 1 + .../plugins/panel/graph2/withMenuOptions.tsx | 94 +++++++++++++++++++ public/app/types/plugins.ts | 1 + 8 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 public/app/plugins/panel/graph2/withMenuOptions.tsx diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index fcfc84e287b..cf41595ce8c 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import config from 'app/core/config'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -123,6 +123,7 @@ export class DashboardPanel extends PureComponent {
    diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index d4bfce67c48..1a6f5a3cee2 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -13,19 +13,20 @@ import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { TimeRange, PanelProps } from 'app/types'; -export interface Props { +export interface PanelChromeProps { panel: PanelModel; dashboard: DashboardModel; component: ComponentClass; + withMenuOptions: any; } -export interface State { +export interface PanelChromeState { refreshCounter: number; renderCounter: number; timeRange?: TimeRange; } -export class PanelChrome extends PureComponent { +export class PanelChrome extends PureComponent { constructor(props) { super(props); @@ -67,16 +68,15 @@ export class PanelChrome extends PureComponent { } render() { - const { panel, dashboard } = this.props; + const { panel, dashboard, withMenuOptions } = this.props; const { datasource, targets } = panel; const { timeRange, renderCounter, refreshCounter } = this.state; const PanelComponent = this.props.component; - console.log('Panel chrome render'); return (
    - +
    { +export class PanelHeader extends PureComponent { render() { - const { dashboard } = this.props; + const { dashboard, withMenuOptions, panel } = this.props; const isFullscreen = false; const isLoading = false; const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen }); + const PanelHeaderMenuComponent = withMenuOptions ? withMenuOptions(PanelHeaderMenu, panel) : PanelHeaderMenu; return (
    @@ -39,7 +37,7 @@ export class PanelHeader extends React.Component { {this.props.panel.title} - + 4m diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index b454ccad4a6..c36eb9d8584 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -6,6 +6,9 @@ import { getPanelMenu } from 'app/features/dashboard/utils/panel_menu'; export interface PanelHeaderMenuProps { panelId: number; dashboard: DashboardModel; + datasource: any; + additionalMenuItems?: PanelHeaderMenuItemProps[]; + additionalSubMenuItems?: PanelHeaderMenuItemProps[]; } export class PanelHeaderMenu extends PureComponent { @@ -19,10 +22,10 @@ export class PanelHeaderMenu extends PureComponent { renderItems = (menu: PanelHeaderMenuItemProps[], isSubMenu = false) => { return (
      - {menu.map(menuItem => { - console.log(this); + {menu.map((menuItem, idx) => { return ( { }; render() { - const { dashboard } = this.props; - const menu = getPanelMenu(dashboard, this.getPanel()); + console.log('PanelHeaderMenu render'); + const { dashboard, additionalMenuItems, additionalSubMenuItems } = this.props; + const menu = getPanelMenu(dashboard, this.getPanel(), additionalMenuItems, additionalSubMenuItems); return
      {this.renderItems(menu)}
      ; } } diff --git a/public/app/features/dashboard/utils/panel_menu.ts b/public/app/features/dashboard/utils/panel_menu.ts index c86595953b5..67adf118edd 100644 --- a/public/app/features/dashboard/utils/panel_menu.ts +++ b/public/app/features/dashboard/utils/panel_menu.ts @@ -8,8 +8,8 @@ import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } fro export const getPanelMenu = ( dashboard: DashboardModel, panel: PanelModel, - extraMenuItems: PanelHeaderMenuItemProps[] = [], - extraSubMenuItems: PanelHeaderMenuItemProps[] = [] + additionalMenuItems: PanelHeaderMenuItemProps[] = [], + additionalSubMenuItems: PanelHeaderMenuItemProps[] = [] ) => { const onViewPanel = () => { store.dispatch( @@ -80,9 +80,7 @@ export const getPanelMenu = ( handleClick: onEditPanelJson, }); - // TODO: Handle this somehow - // this.events.emit('init-panel-actions', menu); - extraSubMenuItems.forEach(item => { + additionalSubMenuItems.forEach(item => { menu.push(item); }); return menu; @@ -117,7 +115,7 @@ export const getPanelMenu = ( shortcut: 'p s', }); - extraMenuItems.forEach(item => { + additionalMenuItems.forEach(item => { menu.push(item); }); diff --git a/public/app/plugins/panel/graph2/module.tsx b/public/app/plugins/panel/graph2/module.tsx index b132d3374f1..88b679e1645 100644 --- a/public/app/plugins/panel/graph2/module.tsx +++ b/public/app/plugins/panel/graph2/module.tsx @@ -73,3 +73,4 @@ export class GraphOptions extends PureComponent> { } export { Graph2 as PanelComponent, GraphOptions as PanelOptionsComponent }; +export { withMenuOptions } from './withMenuOptions'; diff --git a/public/app/plugins/panel/graph2/withMenuOptions.tsx b/public/app/plugins/panel/graph2/withMenuOptions.tsx new file mode 100644 index 00000000000..aaa89bf3406 --- /dev/null +++ b/public/app/plugins/panel/graph2/withMenuOptions.tsx @@ -0,0 +1,94 @@ +// Libraries +import React, { PureComponent } from 'react'; + +// Services +import { getTimeSrv } from 'app/features/dashboard/time_srv'; +import { contextSrv } from 'app/core/services/context_srv'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { store } from 'app/store/configureStore'; + +// Components +import { PanelHeaderMenu } from 'app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu'; +import config from 'app/core/config'; +import { getExploreUrl } from 'app/core/utils/explore'; +import { updateLocation } from 'app/core/actions'; + +// Types +import { PanelModel } from 'app/features/dashboard/panel_model'; +import { PanelHeaderMenuProps } from 'app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu'; +import { + PanelHeaderMenuItemProps, + PanelHeaderMenuItemTypes, +} from 'app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem'; + +interface LocalState { + datasource: any; +} + +export const withMenuOptions = (WrappedPanelHeaderMenu: typeof PanelHeaderMenu, panel: PanelModel) => { + return class extends PureComponent { + private datasourceSrv = getDatasourceSrv(); + private timeSrv = getTimeSrv(); + + constructor(props) { + super(props); + this.state = { + datasource: undefined, + }; + } + + componentDidMount() { + const dsPromise = getDatasourceSrv().get(panel.datasource); + dsPromise.then((datasource: any) => { + this.setState(() => ({ datasource })); + }); + } + + onExploreClick = async () => { + const { datasource } = this.state; + const url = await getExploreUrl(panel, panel.targets, datasource, this.datasourceSrv, this.timeSrv); + if (url) { + store.dispatch(updateLocation({ path: url })); + } + }; + + getAdditionalMenuItems = () => { + const { datasource } = this.state; + const items = []; + if ( + config.exploreEnabled && + contextSrv.isEditor && + datasource && + (datasource.meta.explore || datasource.meta.id === 'mixed') + ) { + items.push({ + type: PanelHeaderMenuItemTypes.Link, + text: 'Explore', + handleClick: this.onExploreClick, + iconClassName: 'fa fa-fw fa-rocket', + shortcut: 'x', + }); + } + return items; + }; + + getAdditionalSubMenuItems = () => { + return [ + { + type: PanelHeaderMenuItemTypes.Link, + text: 'Hello Sub Menu', + handleClick: () => { + alert('Hello world from HOC!'); + }, + shortcut: 's h w', + }, + ] as PanelHeaderMenuItemProps[]; + }; + + render() { + const menu: PanelHeaderMenuItemProps[] = this.getAdditionalMenuItems(); + const subMenu: PanelHeaderMenuItemProps[] = this.getAdditionalSubMenuItems(); + return ; + } + }; +}; diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 817777669d8..9ede3dd9f4b 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -13,6 +13,7 @@ export interface PluginExports { PanelCtrl?; PanelComponent?: ComponentClass; PanelOptionsComponent: ComponentClass; + withMenuOptions?: any; } export interface PanelPlugin { From 6c0c1254fe65e098bec784cc74508845d61648fa Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Sat, 3 Nov 2018 23:36:40 +0100 Subject: [PATCH 16/25] wip: panel-header: More merge conflicts --- .../dashboard/dashgrid/DashboardPanel.tsx | 2 +- .../features/dashboard/dashgrid/DataPanel.tsx | 26 ++++--- .../dashboard/dashgrid/PanelChrome.tsx | 65 +++++++++++++--- .../dashgrid/PanelHeader/PanelHeader.tsx | 15 ++-- .../dashgrid/PanelHeader/PanelHeaderMenu.tsx | 21 +++-- .../features/dashboard/utils/panel_menu.ts | 16 ++-- public/app/plugins/panel/graph2/module.tsx | 3 + .../app/plugins/panel/graph2/moduleMenu.tsx | 76 +++++++++++++++++++ public/app/types/plugins.ts | 1 + 9 files changed, 174 insertions(+), 51 deletions(-) create mode 100644 public/app/plugins/panel/graph2/moduleMenu.tsx diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index cf41595ce8c..d75e3abc67f 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -115,7 +115,6 @@ export class DashboardPanel extends PureComponent { const { pluginExports } = this.state; const containerClass = this.props.panel.isEditing ? 'panel-editor-container' : 'panel-height-helper'; const panelWrapperClass = this.props.panel.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper'; - // this might look strange with these classes that change when edit, but // I want to try to keep markup (parents) for panel the same in edit mode to avoide unmount / new mount of panel return ( @@ -126,6 +125,7 @@ export class DashboardPanel extends PureComponent { withMenuOptions={pluginExports.withMenuOptions} panel={this.props.panel} dashboard={this.props.dashboard} + moduleMenu={pluginExports.moduleMenu} />
    {this.props.panel.isEditing && ( diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index a42d392c018..77460d9dc83 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -1,11 +1,9 @@ // Library import React, { Component } from 'react'; -// Services -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; - // Types import { TimeRange, LoadingState, DataQueryOptions, DataQueryResponse, TimeSeries } from 'app/types'; +import { DataSourceApi } from 'app/types/series'; interface RenderProps { loading: LoadingState; @@ -13,7 +11,7 @@ interface RenderProps { } export interface Props { - datasource: string | null; + dataSourceApi: DataSourceApi; queries: any[]; panelId?: number; dashboardId?: number; @@ -21,6 +19,7 @@ export interface Props { timeRange?: TimeRange; refreshCounter: number; children: (r: RenderProps) => JSX.Element; + onIssueQueryResponse: any; } export interface State { @@ -60,13 +59,19 @@ export class DataPanel extends Component { } hasPropsChanged(prevProps: Props) { - return this.props.refreshCounter !== prevProps.refreshCounter || this.props.isVisible !== prevProps.isVisible; + const { refreshCounter, isVisible, dataSourceApi } = this.props; + + return ( + refreshCounter !== prevProps.refreshCounter || + isVisible !== prevProps.isVisible || + dataSourceApi !== prevProps.dataSourceApi + ); } issueQueries = async () => { - const { isVisible, queries, datasource, panelId, dashboardId, timeRange } = this.props; + const { isVisible, queries, panelId, dashboardId, timeRange, dataSourceApi } = this.props; - if (!isVisible) { + if (!isVisible || !dataSourceApi) { return; } @@ -78,9 +83,6 @@ export class DataPanel extends Component { this.setState({ loading: LoadingState.Loading }); try { - const dataSourceSrv = getDatasourceSrv(); - const ds = await dataSourceSrv.get(datasource); - const queryOptions: DataQueryOptions = { timezone: 'browser', panelId: panelId, @@ -96,7 +98,7 @@ export class DataPanel extends Component { }; console.log('Issuing DataPanel query', queryOptions); - const resp = await ds.query(queryOptions); + const resp = await dataSourceApi.query(queryOptions); console.log('Issuing DataPanel query Resp', resp); this.setState({ @@ -104,6 +106,8 @@ export class DataPanel extends Component { response: resp, isFirstLoad: false, }); + + this.props.onIssueQueryResponse(resp.data); } catch (err) { console.log('Loading error', err); this.setState({ loading: LoadingState.Error, isFirstLoad: false }); diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 1a6f5a3cee2..4ac0723e4e8 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -3,43 +3,62 @@ import React, { ComponentClass, PureComponent } from 'react'; // Services import { getTimeSrv } from '../time_srv'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; // Components import { PanelHeader } from './PanelHeader/PanelHeader'; import { DataPanel } from './DataPanel'; +import { PanelHeaderMenu } from './PanelHeader/PanelHeaderMenu'; // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; -import { TimeRange, PanelProps } from 'app/types'; +import { TimeRange, PanelProps, TimeSeries } from 'app/types'; +import { DataSourceApi } from 'app/types/series'; export interface PanelChromeProps { panel: PanelModel; dashboard: DashboardModel; component: ComponentClass; - withMenuOptions: any; + withMenuOptions?: (c: typeof PanelHeaderMenu, p: PanelModel) => typeof PanelHeaderMenu; + moduleMenu?: any; } export interface PanelChromeState { refreshCounter: number; renderCounter: number; timeRange?: TimeRange; + timeSeries?: TimeSeries[]; + dataSourceApi?: DataSourceApi; } export class PanelChrome extends PureComponent { constructor(props) { super(props); - this.state = { refreshCounter: 0, renderCounter: 0, }; } - componentDidMount() { + async componentDidMount() { + const { panel } = this.props; + const { datasource } = panel; + this.props.panel.events.on('refresh', this.onRefresh); this.props.panel.events.on('render', this.onRender); this.props.dashboard.panelInitialized(this.props.panel); + + try { + const dataSourceSrv = getDatasourceSrv(); + const dataSourceApi = await dataSourceSrv.get(datasource); + this.setState(prevState => ({ + ...prevState, + dataSourceApi, + })); + } catch (err) { + console.log('Datasource loading error', err); + } } componentWillUnmount() { @@ -50,10 +69,11 @@ export class PanelChrome extends PureComponent ({ + ...prevState, refreshCounter: this.state.refreshCounter + 1, timeRange: timeRange, - }); + })); }; onRender = () => { @@ -63,27 +83,50 @@ export class PanelChrome extends PureComponent { + this.setState(prevState => ({ + ...prevState, + timeSeries, + })); + }; + get isVisible() { return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); } render() { - const { panel, dashboard, withMenuOptions } = this.props; - const { datasource, targets } = panel; - const { timeRange, renderCounter, refreshCounter } = this.state; + const { panel, dashboard, moduleMenu } = this.props; + const { refreshCounter, timeRange, dataSourceApi, timeSeries, renderCounter } = this.state; + const { targets } = panel; const PanelComponent = this.props.component; console.log('Panel chrome render'); + // const PanelHeaderMenuComponent: typeof PanelHeaderMenu = withMenuOptions ? withMenuOptions(PanelHeaderMenu, panel) : PanelHeaderMenu; + const PanelHeaderMenuComponent = PanelHeaderMenu; + const mm = moduleMenu(panel, dataSourceApi, timeSeries); + const additionalMenuItems = mm.getAdditionalMenuItems || undefined; + const additionalSubMenuItems = mm.getAdditionalSubMenuItems || undefined; + console.log('panelChrome render'); return (
    - + + +
    {({ loading, timeSeries }) => { console.log('panelcrome inner render'); diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx index 3d23949afd0..ba5511014f2 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -1,21 +1,16 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; -import { PanelModel } from 'app/features/dashboard/panel_model'; -import { DashboardModel } from 'app/features/dashboard/dashboard_model'; -import { PanelHeaderMenu } from './PanelHeaderMenu'; interface PanelHeaderProps { - panel: PanelModel; - dashboard: DashboardModel; - withMenuOptions: any; + title: string; } + export class PanelHeader extends PureComponent { render() { - const { dashboard, withMenuOptions, panel } = this.props; const isFullscreen = false; const isLoading = false; const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen }); - const PanelHeaderMenuComponent = withMenuOptions ? withMenuOptions(PanelHeaderMenu, panel) : PanelHeaderMenu; + const { title } = this.props; return (
    @@ -34,10 +29,10 @@ export class PanelHeader extends PureComponent {
    - {this.props.panel.title} + {title} - + {this.props.children} 4m diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index c36eb9d8584..dae9e33b996 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -1,28 +1,25 @@ import React, { PureComponent } from 'react'; import { DashboardModel } from 'app/features/dashboard/dashboard_model'; +import { PanelModel } from 'app/features/dashboard/panel_model'; import { PanelHeaderMenuItem, PanelHeaderMenuItemProps } from './PanelHeaderMenuItem'; import { getPanelMenu } from 'app/features/dashboard/utils/panel_menu'; +import { DataSourceApi } from 'app/types/series'; +import { TimeSeries } from 'app/types'; export interface PanelHeaderMenuProps { - panelId: number; + panel: PanelModel; dashboard: DashboardModel; - datasource: any; + dataSourceApi: DataSourceApi; additionalMenuItems?: PanelHeaderMenuItemProps[]; additionalSubMenuItems?: PanelHeaderMenuItemProps[]; + timeSeries?: TimeSeries[]; } export class PanelHeaderMenu extends PureComponent { - getPanel = () => { - // Pass in panel as prop instead? - const { panelId, dashboard } = this.props; - const panelInfo = dashboard.getPanelInfoById(panelId); - return panelInfo.panel; - }; - renderItems = (menu: PanelHeaderMenuItemProps[], isSubMenu = false) => { return (