From 16f0e75448b633a72d5ab6d818ae5582ffa7cf3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 16 Dec 2019 09:18:48 +0100 Subject: [PATCH] New panel editor (behind feature toggle) (#21097) * WIP: initial 10min poc of new panel editor * added queries * PanelEditor: copy panel model when going into edit mode * Added option --- packages/grafana-runtime/src/config.ts | 2 + public/app/core/services/keybindingSrv.ts | 6 ++ .../components/PanelEditor/PanelEditor.tsx | 96 +++++++++++++++++++ .../dashboard/components/PanelEditor/index.ts | 1 + .../dashboard/containers/DashboardPage.tsx | 12 ++- .../features/dashboard/state/PanelModel.ts | 2 + .../features/dashboard/utils/getPanelMenu.ts | 22 +++++ 7 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx create mode 100644 public/app/features/dashboard/components/PanelEditor/index.ts diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index d62895b2088..44869a478da 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -15,6 +15,7 @@ interface FeatureToggles { transformations: boolean; inspect: boolean; expressions: boolean; + newEdit: boolean; } export class GrafanaBootConfig { datasources: { [str: string]: DataSourceInstanceSettings } = {}; @@ -51,6 +52,7 @@ export class GrafanaBootConfig { transformations: false, inspect: false, expressions: false, + newEdit: false, }; constructor(options: GrafanaBootConfig) { diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index fb48d33d8f2..89a1dac5b16 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -123,6 +123,12 @@ export class KeybindingSrv { return; } + if (search.editPanel) { + delete search.editPanel; + this.$location.search(search); + return; + } + if (search.fullscreen) { appEvents.emit(PanelEvents.panelChangeView, { fullscreen: false, edit: false }); return; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx new file mode 100644 index 00000000000..2d05f0425c8 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -0,0 +1,96 @@ +import React, { PureComponent } from 'react'; +import { css } from 'emotion'; +import { GrafanaTheme } from '@grafana/data'; +import { stylesFactory } from '@grafana/ui'; +import config from 'app/core/config'; + +import { PanelModel } from '../../state/PanelModel'; +import { DashboardModel } from '../../state/DashboardModel'; +import { DashboardPanel } from '../../dashgrid/DashboardPanel'; +import { QueriesTab } from '../../panel_editor/QueriesTab'; + +const getStyles = stylesFactory((theme: GrafanaTheme) => ({ + wrapper: css` + width: 100%; + height: 100%; + position: fixed; + z-index: ${theme.zIndex.modal}; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: ${theme.colors.pageBg}; + display: flex; + padding: ${theme.spacing.md}; + flex-direction: row; + `, + leftPane: css` + flex-grow: 1; + height: 100%; + `, + rightPane: css` + width: 450px; + height: 100%; + flex-grow: 0; + `, + leftPaneViz: css` + width: 100%; + height: 50%; + `, + leftPaneData: css` + width: 100%; + height: 50%; + padding-top: ${theme.spacing.md}; + `, +})); + +interface Props { + dashboard: DashboardModel; + panel: PanelModel; +} + +interface State { + dirtyPanel?: PanelModel; +} + +export class PanelEditor extends PureComponent { + state: State = {}; + + componentDidMount() { + const { panel } = this.props; + const dirtyPanel = new PanelModel(panel.getSaveModel()); + + this.setState({ dirtyPanel }); + } + + render() { + const { dashboard } = this.props; + const { dirtyPanel } = this.state; + + const styles = getStyles(config.theme); + + if (!dirtyPanel) { + return null; + } + + return ( +
+
+
+ +
+
+ ; +
+
+
Visualization settings
+
+ ); + } +} diff --git a/public/app/features/dashboard/components/PanelEditor/index.ts b/public/app/features/dashboard/components/PanelEditor/index.ts new file mode 100644 index 00000000000..e7f029fdb41 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/index.ts @@ -0,0 +1 @@ +export { PanelEditor } from './PanelEditor'; diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 0f049ad4f99..40c92dd8b27 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -12,7 +12,9 @@ import { DashboardGrid } from '../dashgrid/DashboardGrid'; import { DashNav } from '../components/DashNav'; import { SubMenu } from '../components/SubMenu'; import { DashboardSettings } from '../components/DashboardSettings'; -import { Alert, CustomScrollbar } from '@grafana/ui'; +import { PanelEditor } from '../components/PanelEditor'; +import { CustomScrollbar, Alert } from '@grafana/ui'; + // Redux import { initDashboard } from '../state/initDashboard'; import { cleanUpDashboard } from '../state/actions'; @@ -25,6 +27,7 @@ import { DashboardRouteInfo, StoreState, } from 'app/types'; + import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { PanelInspector } from '../components/Inspector/PanelInspector'; @@ -35,6 +38,7 @@ export interface Props { editview?: string; urlPanelId?: string; urlFolderId?: string; + urlEditPanel?: string; inspectPanelId?: string; $scope: any; $injector: any; @@ -248,7 +252,7 @@ export class DashboardPage extends PureComponent { } render() { - const { dashboard, editview, $injector, isInitSlow, initError, inspectPanelId } = this.props; + const { dashboard, editview, $injector, isInitSlow, initError, inspectPanelId, urlEditPanel } = this.props; const { isSettingsOpening, isEditing, isFullscreen, scrollTop, updateScrollTop } = this.state; if (!dashboard) { @@ -270,6 +274,8 @@ export class DashboardPage extends PureComponent { // Find the panel to inspect const inspectPanel = inspectPanelId ? dashboard.getPanelById(parseInt(inspectPanelId, 10)) : null; + // find panel being edited + const editPanel = urlEditPanel ? dashboard.getPanelById(parseInt(urlEditPanel, 10)) : null; // Only trigger render when the scroll has moved by 25 const approximateScrollTop = Math.round(scrollTop / 25) * 25; @@ -309,6 +315,7 @@ export class DashboardPage extends PureComponent { {inspectPanel && } + {editPanel && } ); } @@ -320,6 +327,7 @@ export const mapStateToProps = (state: StoreState) => ({ urlType: state.location.routeParams.type, editview: state.location.query.editview, urlPanelId: state.location.query.panelId, + urlEditPanel: state.location.query.editPanel, urlFolderId: state.location.query.folderId, urlFullscreen: !!state.location.query.fullscreen, urlEdit: !!state.location.query.edit, diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index c0bbaf74089..54689533bdd 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -35,6 +35,7 @@ const notPersistedProperties: { [str: string]: boolean } = { fullscreen: true, isEditing: true, isInView: true, + isNewEdit: true, hasRefreshed: true, cachedPluginOptions: true, plugin: true, @@ -125,6 +126,7 @@ export class PanelModel { fullscreen: boolean; isEditing: boolean; isInView: boolean; + isNewEdit: boolean; hasRefreshed: boolean; events: Emitter; cacheTimeout?: any; diff --git a/public/app/features/dashboard/utils/getPanelMenu.ts b/public/app/features/dashboard/utils/getPanelMenu.ts index e86f48ea694..224d12b9ce0 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.ts @@ -41,6 +41,18 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { ); }; + const onNewEditPanel = (event: React.MouseEvent) => { + event.preventDefault(); + store.dispatch( + updateLocation({ + query: { + editPanel: panel.id, + }, + partial: true, + }) + ); + }; + const onSharePanel = (event: React.MouseEvent) => { event.preventDefault(); sharePanel(dashboard, panel); @@ -119,6 +131,7 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { onClick: onNavigateToExplore, }); } + if (config.featureToggles.inspect) { menu.push({ text: 'Inspect', @@ -128,6 +141,15 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { }); } + if (config.featureToggles.newEdit) { + menu.push({ + text: 'New edit', + iconClassName: 'gicon gicon-editor', + onClick: onNewEditPanel, + shortcut: 'p i', + }); + } + const subMenu: PanelMenuItem[] = []; if (!panel.fullscreen && dashboard.meta.canEdit) {