From 159c5cdb63445514f18ea7ab4fb3ce6757548ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 14 Nov 2018 17:57:37 +0100 Subject: [PATCH 01/81] wip: adding general tab for react panel edit mode --- .../dashboard/dashgrid/GeneralTab.tsx | 60 +++++++++++++++++++ .../dashboard/dashgrid/PanelEditor.tsx | 4 +- public/app/features/panel/GeneralTabCtrl.ts | 23 +++++++ public/app/features/panel/panel_ctrl.ts | 4 +- .../panel/partials/general_tab.html} | 0 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/GeneralTab.tsx create mode 100644 public/app/features/panel/GeneralTabCtrl.ts rename public/app/{partials/panelgeneral.html => features/panel/partials/general_tab.html} (100%) diff --git a/public/app/features/dashboard/dashgrid/GeneralTab.tsx b/public/app/features/dashboard/dashgrid/GeneralTab.tsx new file mode 100644 index 00000000000..2ffac98bfd7 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/GeneralTab.tsx @@ -0,0 +1,60 @@ +import React, { PureComponent } from 'react'; + +import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import { EditorTabBody } from './EditorTabBody'; + +import { PanelModel } from '../panel_model'; +import { DashboardModel } from '../dashboard_model'; + +interface Props { + panel: PanelModel; + dashboard: DashboardModel; +} + +export class GeneralTab extends PureComponent { + element: any; + component: AngularComponent; + + constructor(props) { + super(props); + } + + componentDidMount() { + if (!this.element) { + return; + } + + const { panel, dashboard } = this.props; + + const loader = getAngularLoader(); + const template = ''; + const scopeProps = { + ctrl: { + panel: panel, + dashboard: dashboard, + }, + }; + + this.component = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.component) { + this.component.destroy(); + } + } + + render() { + const currentDataSource = { + title: 'ProductionDB', + imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg', + render: () =>

hello

, + }; + + return ( + +
(this.element = element)} style={{ width: '100%' }} /> + + ); + } +} diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 09a8c7052e3..6d9072048ae 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import { QueriesTab } from './QueriesTab'; import { VisualizationTab } from './VisualizationTab'; +import { GeneralTab } from './GeneralTab'; import { store } from 'app/store/store'; import { updateLocation } from 'app/core/actions'; @@ -31,9 +32,9 @@ export class PanelEditor extends PureComponent { super(props); this.tabs = [ + { id: 'general', text: 'General', icon: 'gicon gicon-preferences' }, { id: 'queries', text: 'Queries', icon: 'fa fa-database' }, { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' }, - { id: 'alert', text: 'Alert', icon: 'gicon gicon-alert' }, ]; } @@ -81,6 +82,7 @@ export class PanelEditor extends PureComponent {
+ {activeTab === 'general' && } {activeTab === 'queries' && } {activeTab === 'visualization' && ( diff --git a/public/app/features/panel/GeneralTabCtrl.ts b/public/app/features/panel/GeneralTabCtrl.ts new file mode 100644 index 00000000000..46db29b2e9b --- /dev/null +++ b/public/app/features/panel/GeneralTabCtrl.ts @@ -0,0 +1,23 @@ +import coreModule from 'app/core/core_module'; +import { DashboardModel } from '../dashboard/dashboard_model'; + +export class GeneralTabCtrl { + panelCtrl: any; + + /** @ngInject */ + constructor($scope) { + this.panelCtrl = $scope.ctrl; + } +} + +/** @ngInject */ +export function generalTab() { + 'use strict'; + return { + restrict: 'E', + templateUrl: 'public/app/features/panel/partials/general_tab.html', + controller: GeneralTabCtrl, + }; +} + +coreModule.directive('panelGeneralTab', generalTab); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 420210be380..fe9fa7eb728 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -12,6 +12,8 @@ import { sharePanel as sharePanelUtil, } from 'app/features/dashboard/utils/panel'; +import { generalTab } from './GeneralTabCtrl'; + import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants'; export class PanelCtrl { @@ -91,7 +93,7 @@ export class PanelCtrl { initEditMode() { this.editorTabs = []; - this.addEditorTab('General', 'public/app/partials/panelgeneral.html'); + this.addEditorTab('General', generalTab); this.editModeInitiated = true; this.events.emit('init-edit-mode', null); diff --git a/public/app/partials/panelgeneral.html b/public/app/features/panel/partials/general_tab.html similarity index 100% rename from public/app/partials/panelgeneral.html rename to public/app/features/panel/partials/general_tab.html From 487fd12d661abf5537127dc03f50c8a25dad756e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 15 Nov 2018 07:33:59 +0100 Subject: [PATCH 02/81] wip: minor update --- .../features/dashboard/dashgrid/EditorTabBody.tsx | 14 ++++++++------ .../app/features/dashboard/dashgrid/GeneralTab.tsx | 5 +---- .../features/dashboard/dashgrid/PanelEditor.tsx | 2 +- public/app/features/panel/GeneralTabCtrl.ts | 1 - 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 86ebdf86b67..ff83ee06011 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -4,7 +4,7 @@ import { FadeIn } from 'app/core/components/Animations/FadeIn'; interface Props { children: JSX.Element; - main: EditorToolBarView; + main?: EditorToolBarView; toolbarItems: EditorToolBarView[]; } @@ -75,11 +75,13 @@ export class EditorTabBody extends PureComponent { return ( <> -
- {this.renderMainSelection(main)} -
- {toolbarItems.map(item => this.renderButton(item))} -
+ {main && ( +
+ {this.renderMainSelection(main)} +
+ {toolbarItems.map(item => this.renderButton(item))} +
+ )}
diff --git a/public/app/features/dashboard/dashgrid/GeneralTab.tsx b/public/app/features/dashboard/dashgrid/GeneralTab.tsx index 2ffac98bfd7..073da7dcc98 100644 --- a/public/app/features/dashboard/dashgrid/GeneralTab.tsx +++ b/public/app/features/dashboard/dashgrid/GeneralTab.tsx @@ -4,11 +4,9 @@ import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoa import { EditorTabBody } from './EditorTabBody'; import { PanelModel } from '../panel_model'; -import { DashboardModel } from '../dashboard_model'; interface Props { panel: PanelModel; - dashboard: DashboardModel; } export class GeneralTab extends PureComponent { @@ -24,14 +22,13 @@ export class GeneralTab extends PureComponent { return; } - const { panel, dashboard } = this.props; + const { panel } = this.props; const loader = getAngularLoader(); const template = ''; const scopeProps = { ctrl: { panel: panel, - dashboard: dashboard, }, }; diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 6d9072048ae..d42f97efb85 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -82,7 +82,7 @@ export class PanelEditor extends PureComponent {
- {activeTab === 'general' && } + {activeTab === 'general' && } {activeTab === 'queries' && } {activeTab === 'visualization' && ( diff --git a/public/app/features/panel/GeneralTabCtrl.ts b/public/app/features/panel/GeneralTabCtrl.ts index 46db29b2e9b..c692106c92d 100644 --- a/public/app/features/panel/GeneralTabCtrl.ts +++ b/public/app/features/panel/GeneralTabCtrl.ts @@ -1,5 +1,4 @@ import coreModule from 'app/core/core_module'; -import { DashboardModel } from '../dashboard/dashboard_model'; export class GeneralTabCtrl { panelCtrl: any; From 911646f913c78f00bc6af6a1e74fb8c67fc55ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 15 Nov 2018 09:46:21 +0100 Subject: [PATCH 03/81] poc: handling panel edit mode in react even for angular panels poc --- public/app/core/services/AngularLoader.ts | 4 + .../dashboard/dashgrid/DashboardPanel.tsx | 77 ++++++++++--------- .../dashboard/dashgrid/PanelEditor.tsx | 12 ++- .../dashboard/dashgrid/VisualizationTab.tsx | 37 ++++++++- public/app/features/panel/panel_directive.ts | 56 +++----------- public/app/features/panel/panel_editor_tab.ts | 1 - 6 files changed, 103 insertions(+), 84 deletions(-) diff --git a/public/app/core/services/AngularLoader.ts b/public/app/core/services/AngularLoader.ts index 3f5c535297f..54dd9a35767 100644 --- a/public/app/core/services/AngularLoader.ts +++ b/public/app/core/services/AngularLoader.ts @@ -5,6 +5,7 @@ import _ from 'lodash'; export interface AngularComponent { destroy(); digest(); + getScope(); } export class AngularLoader { @@ -28,6 +29,9 @@ export class AngularLoader { digest: () => { scope.$digest(); }, + getScope: () => { + return scope; + }, }; } } diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index c8c3809fcc4..624b8c14c0f 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -23,11 +23,11 @@ export interface Props { export interface State { plugin: PanelPlugin; + angularPanel: AngularComponent; } export class DashboardPanel extends PureComponent { - element: any; - angularPanel: AngularComponent; + element: HTMLElement; specialPanels = {}; constructor(props) { @@ -35,6 +35,7 @@ export class DashboardPanel extends PureComponent { this.state = { plugin: null, + angularPanel: null, }; this.specialPanels['row'] = this.renderRow.bind(this); @@ -96,25 +97,30 @@ export class DashboardPanel extends PureComponent { this.loadPlugin(); // handle angular plugin loading - if (!this.element || this.angularPanel) { + if (!this.element || this.state.angularPanel) { return; } const loader = getAngularLoader(); const template = ''; const scopeProps = { panel: this.props.panel, dashboard: this.props.dashboard }; - this.angularPanel = loader.load(this.element, scopeProps, template); + const angularPanel = loader.load(this.element, scopeProps, template); + + this.setState({ angularPanel }); } - cleanUpAngularPanel() { - if (this.angularPanel) { - this.angularPanel.destroy(); - this.angularPanel = null; + cleanUpAngularPanel(unmounted?: boolean) { + if (this.state.angularPanel) { + this.state.angularPanel.destroy(); + + if (!unmounted) { + this.setState({ angularPanel: null }); + } } } componentWillUnmount() { - this.cleanUpAngularPanel(); + this.cleanUpAngularPanel(true); } onMouseEnter = () => { @@ -129,25 +135,16 @@ export class DashboardPanel extends PureComponent { const { dashboard, panel } = this.props; const { plugin } = this.state; - const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper'; - const panelWrapperClass = this.props.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 ( -
-
- -
- {panel.isEditing && ( - - )} -
- ); + return ; + } + + renderAngularPanel() { + return
(this.element = element)} className="panel-height-helper" />; } render() { - const { panel } = this.props; - const { plugin } = this.state; + const { panel, dashboard } = this.props; + const { plugin, angularPanel } = this.state; if (this.isSpecial()) { return this.specialPanels[panel.type](); @@ -158,19 +155,27 @@ export class DashboardPanel extends PureComponent { return null; } - // if exporting PanelComponent it must be a react panel - if (plugin.exports.Panel) { - return this.renderReactPanel(); - } + console.log('DashboardPanel.render()'); + + const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper'; + const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper'; - // legacy angular rendering return ( -
(this.element = element)} - className="panel-height-helper" - onMouseEnter={this.onMouseEnter} - onMouseLeave={this.onMouseLeave} - /> +
+
+ {plugin.exports.Panel && this.renderReactPanel()} + {plugin.exports.PanelCtrl && this.renderAngularPanel()} +
+ {panel.isEditing && ( + + )} +
); } } diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index d42f97efb85..24e67538504 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -7,6 +7,7 @@ import { GeneralTab } from './GeneralTab'; import { store } from 'app/store/store'; import { updateLocation } from 'app/core/actions'; +import { AngularComponent } from 'app/core/services/AngularLoader'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -16,6 +17,7 @@ interface PanelEditorProps { panel: PanelModel; dashboard: DashboardModel; plugin: PanelPlugin; + angularPanel?: AngularComponent; onTypeChanged: (newType: PanelPlugin) => void; } @@ -58,7 +60,7 @@ export class PanelEditor extends PureComponent { }; render() { - const { panel, dashboard, onTypeChanged, plugin } = this.props; + const { panel, dashboard, onTypeChanged, plugin, angularPanel } = this.props; const { location } = store.getState(); const activeTab = location.query.tab || 'queries'; @@ -85,7 +87,13 @@ export class PanelEditor extends PureComponent { {activeTab === 'general' && } {activeTab === 'queries' && } {activeTab === 'visualization' && ( - + )}
); diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 8702fab3ae4..51e76cfe57a 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -1,8 +1,14 @@ +// Libraries import React, { PureComponent } from 'react'; +// Utils & Services +import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; + +// Components import { EditorTabBody } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; +// Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { PanelPlugin } from 'app/types/plugins'; @@ -11,18 +17,26 @@ interface Props { panel: PanelModel; dashboard: DashboardModel; plugin: PanelPlugin; + angularPanel?: AngularComponent; onTypeChanged: (newType: PanelPlugin) => void; } export class VisualizationTab extends PureComponent { + element: HTMLElement; + angularOptions: AngularComponent; + constructor(props) { super(props); } renderPanelOptions() { - const { plugin, panel } = this.props; + const { plugin, panel, angularPanel } = this.props; const { PanelOptions } = plugin.exports; + if (angularPanel) { + return
(this.element = element)} />; + } + if (PanelOptions) { return ; } else { @@ -30,6 +44,27 @@ export class VisualizationTab extends PureComponent { } } + componentDidMount() { + const { angularPanel } = this.props; + + if (angularPanel) { + const scope = angularPanel.getScope(); + const panelCtrl = scope.$$childHead.ctrl; + + const loader = getAngularLoader(); + const template = ''; + const scopeProps = { ctrl: panelCtrl, tab: panelCtrl.editorTabs[2] }; + + this.angularOptions = loader.load(this.element, scopeProps, template); + } + } + + componentWillUnmount() { + if (this.angularOptions) { + this.angularOptions.destroy(); + } + } + onPanelOptionsChanged = (options: any) => { this.props.panel.updateOptions(options); this.forceUpdate(); diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 2dcdd22e97e..1b36a9bf18a 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -6,54 +6,22 @@ import baron from 'baron'; const module = angular.module('grafana.directives'); const panelTemplate = ` -
-
-
-
- - - - +
+
+ + + + - - - + + + - -
- -
- -
+
-
-
-
-
-

- {{ctrl.pluginName}} -

- - - - -
- -
-
- -
-
+
+
diff --git a/public/app/features/panel/panel_editor_tab.ts b/public/app/features/panel/panel_editor_tab.ts index f7e1c48a323..e9365cc1cfc 100644 --- a/public/app/features/panel/panel_editor_tab.ts +++ b/public/app/features/panel/panel_editor_tab.ts @@ -9,7 +9,6 @@ function panelEditorTab(dynamicDirectiveSrv) { scope: { ctrl: '=', editorTab: '=', - index: '=', }, directive: scope => { const pluginId = scope.ctrl.pluginId; From dac02d3d737870005c14321b02a32549c48520e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 16 Nov 2018 08:31:29 +0100 Subject: [PATCH 04/81] React edit mode for angular panels progress --- .../dashboard/dashgrid/GeneralTab.tsx | 1 + .../dashboard/dashgrid/QueriesTab.tsx | 1 + .../dashboard/dashgrid/VisualizationTab.tsx | 30 +++++++++++++++++-- .../app/features/panel/metrics_panel_ctrl.ts | 7 ----- public/app/features/panel/panel_ctrl.ts | 3 -- public/app/plugins/panel/graph/module.ts | 14 ++++----- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/GeneralTab.tsx b/public/app/features/dashboard/dashgrid/GeneralTab.tsx index 073da7dcc98..830421a6cc6 100644 --- a/public/app/features/dashboard/dashgrid/GeneralTab.tsx +++ b/public/app/features/dashboard/dashgrid/GeneralTab.tsx @@ -4,6 +4,7 @@ import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoa import { EditorTabBody } from './EditorTabBody'; import { PanelModel } from '../panel_model'; +import './../../panel/GeneralTabCtrl'; interface Props { panel: PanelModel; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index be8ffe30666..eeaca891059 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -6,6 +6,7 @@ import { DataSourcePicker } from './DataSourcePicker'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; +import './../../panel/metrics_tab'; interface Props { panel: PanelModel; diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 51e76cfe57a..ddd2dc15842 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -45,15 +45,41 @@ export class VisualizationTab extends PureComponent { } componentDidMount() { + this.loadAngularOptions(); + } + + componentDidUpdate() { + // in some cases we need to do this after mount because angularPanel was not available on mount + this.loadAngularOptions(); + } + + loadAngularOptions() { const { angularPanel } = this.props; + if (!angularPanel || !this.element || this.angularOptions) { + return; + } + if (angularPanel) { const scope = angularPanel.getScope(); + + // When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet + if (!scope.$$childHead) { + setTimeout(() => { + this.forceUpdate(); + }); + return; + } + const panelCtrl = scope.$$childHead.ctrl; + let template = ''; + for (let i = 0; i < panelCtrl.editorTabs.length; i++) { + template += ''; + } + const loader = getAngularLoader(); - const template = ''; - const scopeProps = { ctrl: panelCtrl, tab: panelCtrl.editorTabs[2] }; + const scopeProps = { ctrl: panelCtrl }; this.angularOptions = loader.load(this.element, scopeProps, template); } diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 9af4eb510a9..68167447222 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -5,7 +5,6 @@ import config from 'app/core/config'; import { PanelCtrl } from 'app/features/panel/panel_ctrl'; import { getExploreUrl } from 'app/core/utils/explore'; -import { metricsTabDirective } from './metrics_tab'; import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel'; class MetricsPanelCtrl extends PanelCtrl { @@ -42,7 +41,6 @@ class MetricsPanelCtrl extends PanelCtrl { this.panel.datasource = this.panel.datasource || null; this.events.on('refresh', this.onMetricsPanelRefresh.bind(this)); - this.events.on('init-edit-mode', this.onInitMetricsPanelEditMode.bind(this)); this.events.on('panel-teardown', this.onPanelTearDown.bind(this)); } @@ -53,11 +51,6 @@ class MetricsPanelCtrl extends PanelCtrl { } } - private onInitMetricsPanelEditMode() { - this.addEditorTab('Metrics', metricsTabDirective, 1, 'fa fa-database'); - this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html'); - } - private onMetricsPanelRefresh() { // ignore fetching data if another panel is in fullscreen if (this.otherPanelInFullscreenMode()) { diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index fe9fa7eb728..0a782ec808c 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -12,8 +12,6 @@ import { sharePanel as sharePanelUtil, } from 'app/features/dashboard/utils/panel'; -import { generalTab } from './GeneralTabCtrl'; - import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants'; export class PanelCtrl { @@ -93,7 +91,6 @@ export class PanelCtrl { initEditMode() { this.editorTabs = []; - this.addEditorTab('General', generalTab); this.editModeInitiated = true; this.events.emit('init-edit-mode', null); diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index a6c5190d937..13bf0cad313 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -4,8 +4,8 @@ import './thresholds_form'; import template from './template'; import _ from 'lodash'; -import config from 'app/core/config'; -import { MetricsPanelCtrl, alertTab } from 'app/plugins/sdk'; + +import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { DataProcessor } from './data_processor'; import { axesEditorComponent } from './axes_editor'; @@ -70,11 +70,11 @@ class GraphCtrl extends MetricsPanelCtrl { // length of a dash dashLength: 10, // length of space between two dashes - spaceLength: 10, + paceLength: 10, // show hide points points: false, // point radius in pixels - pointradius: 5, + pointradius: 2, // show hide bars bars: false, // enable/disable stacking @@ -137,9 +137,9 @@ class GraphCtrl extends MetricsPanelCtrl { this.addEditorTab('Axes', axesEditorComponent, 2); this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html', 3); - if (config.alertingEnabled) { - this.addEditorTab('Alert', alertTab, 5); - } + // if (config.alertingEnabled) { + // this.addEditorTab('Alert', alertTab, 5); + // } this.subTabIndex = 0; } From f21fe65bb1ddce4c27e03f3891a09059a2a6752a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 16 Nov 2018 10:00:13 +0100 Subject: [PATCH 05/81] fixed issues when changing type, need to remove event listeners and cleanup props --- public/app/core/constants.ts | 1 + .../dashboard/dashgrid/DashboardPanel.tsx | 2 +- public/app/features/dashboard/panel_model.ts | 57 ++++++++++++++++++- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/public/app/core/constants.ts b/public/app/core/constants.ts index d51c4cf83d6..7d295b27726 100644 --- a/public/app/core/constants.ts +++ b/public/app/core/constants.ts @@ -14,3 +14,4 @@ export const DASHBOARD_TOP_PADDING = 20; export const PANEL_HEADER_HEIGHT = 27; export const PANEL_BORDER = 2; +export const PANEL_OPTIONS_KEY_PREFIX = 'options-'; diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 624b8c14c0f..e909c2ec645 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -55,7 +55,7 @@ export class DashboardPanel extends PureComponent { } onPluginTypeChanged = (plugin: PanelPlugin) => { - this.props.panel.changeType(plugin.id); + this.props.panel.changeType(plugin.id, this.state.angularPanel !== null); this.loadPlugin(); }; diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index 20388a0450c..14b252964b3 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -1,5 +1,6 @@ import { Emitter } from 'app/core/utils/emitter'; import _ from 'lodash'; +import { PANEL_OPTIONS_KEY_PREFIX } from 'app/core/constants'; export interface GridPos { x: number; @@ -16,6 +17,42 @@ const notPersistedProperties: { [str: string]: boolean } = { hasRefreshed: true, }; +// For angular panels we need to clean up properties when changing type +// To make sure the change happens without strange bugs happening when panels use same +// named property with different type / value expectations +// This is not required for react panels + +const mustKeepProps: { [str: string]: boolean } = { + id: true, + gridPos: true, + type: true, + title: true, + scopedVars: true, + repeat: true, + repeatIteration: true, + repeatPanelId: true, + repeatDirection: true, + repeatedByRow: true, + minSpan: true, + collapsed: true, + panels: true, + targets: true, + datasource: true, + timeFrom: true, + timeShift: true, + hideTimeOverride: true, + maxDataPoints: true, + interval: true, + description: true, + links: true, + fullscreen: true, + isEditing: true, + hasRefreshed: true, + events: true, + cacheTimeout: true, + nullPointMode: true, +}; + const defaults: any = { gridPos: { x: 0, y: 0, h: 3, w: 6 }, datasource: null, @@ -82,7 +119,7 @@ export class PanelModel { } private getOptionsKey() { - return this.type + 'Options'; + return 'options-' + this.type; } getSaveModel() { @@ -146,11 +183,25 @@ export class PanelModel { this.events.emit('panel-initialized'); } - changeType(pluginId: string) { + changeType(pluginId: string, fromAngularPanel: boolean) { this.type = pluginId; - delete this.thresholds; + // for now we need to remove alert rules when changing type delete this.alert; + + // for angular panels only we need to remove all events and let angular panels do some cleanup + if (fromAngularPanel) { + this.destroy(); + + for (const key of _.keys(this)) { + if (mustKeepProps[key] || key.indexOf(PANEL_OPTIONS_KEY_PREFIX) === 0) { + continue; + } + + delete this[key]; + console.log('deleting ', key); + } + } } destroy() { From 7c72f8e0287f5884f6a1aff0ecc154f485b137fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 16 Nov 2018 10:44:39 +0100 Subject: [PATCH 06/81] panel options now load even when changing type --- .../dashboard/dashgrid/VisualizationTab.tsx | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index ddd2dc15842..350ad619ff1 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -45,49 +45,63 @@ export class VisualizationTab extends PureComponent { } componentDidMount() { - this.loadAngularOptions(); + if (this.shouldLoadAngularOptions()) { + this.loadAngularOptions(); + } } - componentDidUpdate() { - // in some cases we need to do this after mount because angularPanel was not available on mount - this.loadAngularOptions(); + componentDidUpdate(prevProps: Props) { + console.log('VizTab component did update'); + + // if type changed + if (this.props.plugin !== prevProps.plugin) { + this.cleanUpAngularOptions(); + } + + if (this.shouldLoadAngularOptions()) { + this.loadAngularOptions(); + } + } + + shouldLoadAngularOptions() { + return this.props.angularPanel && this.element && !this.angularOptions; } loadAngularOptions() { const { angularPanel } = this.props; + console.log('loadAngularOptions angularPanel=' + angularPanel); - if (!angularPanel || !this.element || this.angularOptions) { + const scope = angularPanel.getScope(); + + // When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet + if (!scope.$$childHead) { + setTimeout(() => { + this.forceUpdate(); + }); return; } - if (angularPanel) { - const scope = angularPanel.getScope(); + const panelCtrl = scope.$$childHead.ctrl; - // When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet - if (!scope.$$childHead) { - setTimeout(() => { - this.forceUpdate(); - }); - return; - } - - const panelCtrl = scope.$$childHead.ctrl; - - let template = ''; - for (let i = 0; i < panelCtrl.editorTabs.length; i++) { - template += ''; - } - - const loader = getAngularLoader(); - const scopeProps = { ctrl: panelCtrl }; - - this.angularOptions = loader.load(this.element, scopeProps, template); + let template = ''; + for (let i = 0; i < panelCtrl.editorTabs.length; i++) { + template += ''; } + + const loader = getAngularLoader(); + const scopeProps = { ctrl: panelCtrl }; + + this.angularOptions = loader.load(this.element, scopeProps, template); } componentWillUnmount() { + this.cleanUpAngularOptions(); + } + + cleanUpAngularOptions() { if (this.angularOptions) { this.angularOptions.destroy(); + this.angularOptions = null; } } From 57de2475b0f56d0a33abe64f8d2704ea83a16c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 16 Nov 2018 15:09:14 +0100 Subject: [PATCH 07/81] various fixes to angular loading --- public/app/core/services/dynamic_directive_srv.ts | 1 - .../app/features/dashboard/dashgrid/DashboardPanel.tsx | 9 --------- .../app/features/dashboard/dashgrid/VisualizationTab.tsx | 4 ---- public/app/features/dashboard/panel_model.ts | 1 - 4 files changed, 15 deletions(-) diff --git a/public/app/core/services/dynamic_directive_srv.ts b/public/app/core/services/dynamic_directive_srv.ts index 9b7ede59853..c27842ab54f 100644 --- a/public/app/core/services/dynamic_directive_srv.ts +++ b/public/app/core/services/dynamic_directive_srv.ts @@ -21,7 +21,6 @@ class DynamicDirectiveSrv { } if (!directiveInfo.fn.registered) { - console.log('register panel tab'); coreModule.directive(attrs.$normalize(directiveInfo.name), directiveInfo.fn); directiveInfo.fn.registered = true; } diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index e909c2ec645..fee84fe95b2 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -59,10 +59,6 @@ export class DashboardPanel extends PureComponent { this.loadPlugin(); }; - onAngularPluginTypeChanged = () => { - this.loadPlugin(); - }; - loadPlugin() { if (this.isSpecial()) { return; @@ -94,9 +90,6 @@ export class DashboardPanel extends PureComponent { } componentDidUpdate() { - this.loadPlugin(); - - // handle angular plugin loading if (!this.element || this.state.angularPanel) { return; } @@ -155,8 +148,6 @@ export class DashboardPanel extends PureComponent { return null; } - console.log('DashboardPanel.render()'); - const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper'; const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper'; diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 350ad619ff1..065a99ee60c 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -51,9 +51,6 @@ export class VisualizationTab extends PureComponent { } componentDidUpdate(prevProps: Props) { - console.log('VizTab component did update'); - - // if type changed if (this.props.plugin !== prevProps.plugin) { this.cleanUpAngularOptions(); } @@ -69,7 +66,6 @@ export class VisualizationTab extends PureComponent { loadAngularOptions() { const { angularPanel } = this.props; - console.log('loadAngularOptions angularPanel=' + angularPanel); const scope = angularPanel.getScope(); diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index 14b252964b3..149d8cafa93 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -199,7 +199,6 @@ export class PanelModel { } delete this[key]; - console.log('deleting ', key); } } } From 00596f0afc3443974f4b24eba602d3f41f602c8a Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 19 Nov 2018 14:35:16 +0100 Subject: [PATCH 08/81] react-panel: Finish the data source search on query tab and start moving switch-data-source-logic from angular --- .../dashboard/dashgrid/DataSourcePicker.tsx | 31 ++++++--- .../dashboard/dashgrid/EditorTabBody.tsx | 5 +- .../dashboard/dashgrid/QueriesTab.tsx | 63 +++++++++++++++++-- public/app/features/panel/metrics_tab.ts | 2 +- public/app/types/plugins.ts | 1 + 5 files changed, 83 insertions(+), 19 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx index c40ca753a8e..4ac128de39d 100644 --- a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx +++ b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx @@ -2,13 +2,14 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import _ from 'lodash'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DataSourceSelectItem } from 'app/types'; -interface Props {} +interface Props { + onChangeDataSource: (ds: any) => void; + datasources: DataSourceSelectItem[]; +} interface State { - datasources: DataSourceSelectItem[]; searchQuery: string; } @@ -17,31 +18,32 @@ export class DataSourcePicker extends PureComponent { constructor(props) { super(props); - this.state = { - datasources: getDatasourceSrv().getMetricSources(), searchQuery: '', }; } getDataSources() { - const { datasources, searchQuery } = this.state; + const { searchQuery } = this.state; const regex = new RegExp(searchQuery, 'i'); + const { datasources } = this.props; const filtered = datasources.filter(item => { return regex.test(item.name) || regex.test(item.meta.name); }); - return _.sortBy(filtered, 'sort'); + return filtered; } - renderDataSource = (ds: DataSourceSelectItem, index) => { + renderDataSource = (ds: DataSourceSelectItem, index: number) => { + const { onChangeDataSource } = this.props; + const onClick = () => onChangeDataSource(ds); const cssClass = classNames({ 'ds-picker-list__item': true, }); return ( -
+
{ds.name}
@@ -54,7 +56,16 @@ export class DataSourcePicker extends PureComponent { }, 300); } + onSearchQueryChange = evt => { + const value = evt.target.value; + this.setState(prevState => ({ + ...prevState, + searchQuery: value, + })); + }; + renderFilters() { + const { searchQuery } = this.state; return ( <> diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index ff83ee06011..4df7ceac469 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -12,7 +12,7 @@ export interface EditorToolBarView { title: string; imgSrc?: string; icon?: string; - render: () => JSX.Element; + render: (closeFunction: any) => JSX.Element; } interface State { @@ -64,7 +64,7 @@ export class EditorTabBody extends PureComponent { - {view.render()} + {view.render(this.onCloseOpenView)}
); } @@ -72,7 +72,6 @@ export class EditorTabBody extends PureComponent { render() { const { children, toolbarItems, main } = this.props; const { openView } = this.state; - return ( <> {main && ( diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index eeaca891059..01b0ccb2b87 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -7,18 +7,33 @@ import { DataSourcePicker } from './DataSourcePicker'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import './../../panel/metrics_tab'; +import config from 'app/core/config'; + +// Services +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { DataSourceSelectItem } from 'app/types'; interface Props { panel: PanelModel; dashboard: DashboardModel; } -export class QueriesTab extends PureComponent { +interface State { + currentDatasource: DataSourceSelectItem; +} + +export class QueriesTab extends PureComponent { element: any; component: AngularComponent; + datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources(); constructor(props) { super(props); + const { panel } = props; + + this.state = { + currentDatasource: this.datasources.find(datasource => datasource.value === panel.datasource), + }; } componentDidMount() { @@ -47,11 +62,47 @@ export class QueriesTab extends PureComponent { } } + onChangeDataSource = datasource => { + const { panel } = this.props; + const { currentDatasource } = this.state; + // switching to mixed + if (datasource.meta.mixed) { + panel.targets.forEach(target => { + target.datasource = panel.datasource; + if (!target.datasource) { + target.datasource = config.defaultDatasource; + } + }); + } else if (currentDatasource && currentDatasource.meta.mixed) { + panel.targets.forEach(target => { + delete target.datasource; + }); + } + + panel.datasource = datasource.value; + panel.refresh(); + + this.setState(prevState => ({ + ...prevState, + currentDatasource: datasource, + })); + // this.component.digest(); + }; + render() { - const currentDataSource = { - title: 'ProductionDB', - imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg', - render: () => , + const { currentDatasource } = this.state; + const dsInformation = { + title: currentDatasource.name, + imgSrc: currentDatasource.meta.info.logos.small, + render: closeOpenView => ( + { + closeOpenView(); + this.onChangeDataSource(ds); + }} + /> + ), }; const queryInspector = { @@ -66,7 +117,7 @@ export class QueriesTab extends PureComponent { }; return ( - +
(this.element = element)} style={{ width: '100%' }} /> ); diff --git a/public/app/features/panel/metrics_tab.ts b/public/app/features/panel/metrics_tab.ts index bf09e7f37d4..de5eb1bcae9 100644 --- a/public/app/features/panel/metrics_tab.ts +++ b/public/app/features/panel/metrics_tab.ts @@ -41,7 +41,7 @@ export class MetricsTabCtrl { this.datasources = datasourceSrv.getMetricSources(); this.panelDsValue = this.panelCtrl.panel.datasource; - // addded here as old query controller expects this on panelCtrl but + // added here as old query controller expects this on panelCtrl but // they are getting MetricsTabCtrl instead this.events = this.panel.events; diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 4e598382a9a..3214f7bc85c 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -37,6 +37,7 @@ export interface PluginMeta { logs?: boolean; explore?: boolean; annotations?: boolean; + mixed?: boolean; } export interface PluginInclude { From 05753077864dc221e5cdc8a082789b45649834f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Nov 2018 11:06:36 +0100 Subject: [PATCH 09/81] added header section to legacy tabs --- .../dashboard/dashgrid/VisualizationTab.tsx | 9 ++++++++- public/app/plugins/panel/graph/module.ts | 6 +++--- public/sass/components/_panel_editor.scss | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 065a99ee60c..d6573fe9eec 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -81,7 +81,14 @@ export class VisualizationTab extends PureComponent { let template = ''; for (let i = 0; i < panelCtrl.editorTabs.length; i++) { - template += ''; + template += ` +
+
{{ctrl.editorTabs[${i}].title}}
+
+ +
+
+ `; } const loader = getAngularLoader(); diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 8bf35d19e33..37b805f2f40 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -135,9 +135,9 @@ class GraphCtrl extends MetricsPanelCtrl { } onInitEditMode() { - this.addEditorTab('Axes', axesEditorComponent, 2); - this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html', 3); - this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4); + this.addEditorTab('Display options', 'public/app/plugins/panel/graph/tab_display.html'); + this.addEditorTab('Axes', axesEditorComponent); + this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html'); // if (config.alertingEnabled) { // this.addEditorTab('Alert', alertTab, 5); diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index a3732cd78f9..50b095671f9 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -206,3 +206,18 @@ font-size: $font-size-md; margin-bottom: 20px; } + +.form-section { + margin-bottom: 10px; +} + +.form-section__header { + border-bottom: 2px solid $dark-4; + padding: 5px 0px; + font-size: $font-size-h5; + margin-bottom: 20px; +} + +.form-section__body { + background: $dark-1; +} From 3b76d9ee17153202dce42b2778edb956d5a5c36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Nov 2018 11:26:01 +0100 Subject: [PATCH 10/81] minor style update --- public/sass/components/_panel_editor.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 50b095671f9..05608fd38ec 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -20,11 +20,10 @@ flex-grow: 1; min-width: 0; display: flex; - padding: 0 5px; } .panel-editor__content { - padding: 40px 15px; + padding: 40px 20px; } .panel-in-fullscreen { @@ -212,12 +211,13 @@ } .form-section__header { - border-bottom: 2px solid $dark-4; - padding: 5px 0px; + padding: 5px 10px; font-size: $font-size-h5; margin-bottom: 20px; + background: $input-label-bg; + border-radius: 3px; } .form-section__body { - background: $dark-1; + padding: 0 10px; } From 5402a0e00d109bd4c7e36306a8ac1352dcf9a544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Nov 2018 13:18:48 +0100 Subject: [PATCH 11/81] fixed menu to go to panel view mode --- public/app/features/dashboard/utils/getPanelMenu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/utils/getPanelMenu.ts b/public/app/features/dashboard/utils/getPanelMenu.ts index c1b0a578b68..07ce54108f3 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.ts @@ -12,7 +12,7 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { updateLocation({ query: { panelId: panel.id, - edit: false, + edit: null, fullscreen: true, }, partial: true, From ff87cbd4dbbd05588e389b7f5b7b06fb98483dd2 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 20 Nov 2018 14:17:03 +0100 Subject: [PATCH 12/81] react-panel: Add data source "help" --- .../dashboard/dashgrid/EditorTabBody.tsx | 24 +++++++- .../dashboard/dashgrid/QueriesTab.tsx | 61 ++++++++++++++++++- public/app/types/plugins.ts | 8 +++ 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 4df7ceac469..cba75aef979 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -12,6 +12,8 @@ export interface EditorToolBarView { title: string; imgSrc?: string; icon?: string; + disabled?: boolean; + onClick?: () => void; render: (closeFunction: any) => JSX.Element; } @@ -38,6 +40,19 @@ export class EditorTabBody extends PureComponent { this.setState({ openView: null }); }; + static getDerivedStateFromProps(props, state) { + if (state.openView) { + const activeToolbarItem = props.toolbarItems.find(item => item.title === state.openView.title); + if (activeToolbarItem) { + return { + ...state, + openView: activeToolbarItem, + }; + } + } + return state; + } + renderMainSelection(view: EditorToolBarView) { return (
this.onToggleToolBarView(view)} key={view.title}> @@ -49,9 +64,16 @@ export class EditorTabBody extends PureComponent { } renderButton(view: EditorToolBarView) { + const onClick = () => { + if (view.onClick) { + view.onClick(); + } + this.onToggleToolBarView(view); + }; + return (
-
diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 01b0ccb2b87..80db1e5fca4 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -11,21 +11,31 @@ import config from 'app/core/config'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv'; import { DataSourceSelectItem } from 'app/types'; +import Remarkable from 'remarkable'; + interface Props { panel: PanelModel; dashboard: DashboardModel; } +interface Help { + isLoading: boolean; + helpHtml: any; +} + interface State { currentDatasource: DataSourceSelectItem; + help: Help; } export class QueriesTab extends PureComponent { element: any; component: AngularComponent; datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources(); + backendSrv: BackendSrv = getBackendSrv(); constructor(props) { super(props); @@ -33,6 +43,10 @@ export class QueriesTab extends PureComponent { this.state = { currentDatasource: this.datasources.find(datasource => datasource.value === panel.datasource), + help: { + isLoading: false, + helpHtml: null, + }, }; } @@ -42,7 +56,6 @@ export class QueriesTab extends PureComponent { } const { panel, dashboard } = this.props; - const loader = getAngularLoader(); const template = ''; const scopeProps = { @@ -86,11 +99,51 @@ export class QueriesTab extends PureComponent { ...prevState, currentDatasource: datasource, })); - // this.component.digest(); + }; + + loadHelp = () => { + const { currentDatasource } = this.state; + const hasHelp = currentDatasource.meta.hasQueryHelp; + + if (hasHelp) { + this.setState(prevState => ({ + ...prevState, + help: { + helpHtml:

Loading help...

, + isLoading: true, + }, + })); + + this.backendSrv + .get(`/api/plugins/${currentDatasource.meta.id}/markdown/query_help`) + .then(res => { + const md = new Remarkable(); + const helpHtml = md.render(res); // TODO: Clean out dangerous code? Previous: this.helpHtml = this.$sce.trustAsHtml(md.render(res)); + this.setState(prevState => ({ + ...prevState, + help: { + helpHtml:
, + isLoading: false, + }, + })); + }) + .catch(() => { + this.setState(prevState => ({ + ...prevState, + help: { + helpHtml: 'Error occured when loading help', + isLoading: false, + }, + })); + }); + } }; render() { const { currentDatasource } = this.state; + const { helpHtml } = this.state.help; + const { hasQueryHelp } = currentDatasource.meta; + const dsInformation = { title: currentDatasource.name, imgSrc: currentDatasource.meta.info.logos.small, @@ -113,7 +166,9 @@ export class QueriesTab extends PureComponent { const dsHelp = { title: '', icon: 'fa fa-question', - render: () =>

hello

, + disabled: !hasQueryHelp, + onClick: this.loadHelp, + render: () => helpHtml, }; return ( diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 3214f7bc85c..6ad7ca67846 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -26,6 +26,12 @@ export interface PanelPlugin { exports?: PluginExports; } +interface PluginMetaQueryOptions { + cacheTimeout?: boolean; + maxDataPoints?: boolean; + minInterval?: boolean; +} + export interface PluginMeta { id: string; name: string; @@ -38,6 +44,8 @@ export interface PluginMeta { explore?: boolean; annotations?: boolean; mixed?: boolean; + hasQueryHelp?: boolean; + queryOptions?: PluginMetaQueryOptions; } export interface PluginInclude { From 5ea34844bae01119068e1a751c5e1a3854144a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Nov 2018 16:00:19 +0100 Subject: [PATCH 13/81] fixes to view mode for panels, can now go back as before --- .../dashboard/dashgrid/DashboardGrid.tsx | 3 +-- .../dashboard/dashgrid/DashboardPanel.tsx | 11 +++++++--- .../dashboard/dashgrid/PanelEditor.tsx | 13 ----------- public/sass/components/_dashboard_grid.scss | 22 ++++++++----------- public/sass/components/_navbar.scss | 4 ++++ public/sass/components/_panel_editor.scss | 15 +++++++++++-- 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 84db3a2cf99..6df8a8bc0aa 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -176,10 +176,9 @@ export class DashboardGrid extends React.Component { renderPanels() { const panelElements = []; - console.log('render panels'); for (const panel of this.props.dashboard.panels) { - const panelClasses = classNames({ panel: true, 'panel--fullscreen': panel.fullscreen }); + const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.fullscreen }); panelElements.push(
{ } render() { - const { panel, dashboard } = this.props; + const { panel, dashboard, isFullscreen, isEditing } = this.props; const { plugin, angularPanel } = this.state; if (this.isSpecial()) { @@ -148,8 +149,12 @@ export class DashboardPanel extends PureComponent { return null; } - const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper'; - const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper'; + const containerClass = classNames({ 'panel-editor-container': isEditing, 'panel-height-helper': !isEditing }); + const panelWrapperClass = classNames({ + 'panel-wrapper': true, + 'panel-wrapper--edit': isEditing, + 'panel-wrapper--view': isFullscreen && !isEditing, + }); return (
diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 24e67538504..82fc849f81a 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -50,15 +50,6 @@ export class PanelEditor extends PureComponent { this.forceUpdate(); }; - onClose = () => { - store.dispatch( - updateLocation({ - query: { tab: null, fullscreen: null, edit: null }, - partial: true, - }) - ); - }; - render() { const { panel, dashboard, onTypeChanged, plugin, angularPanel } = this.props; const { location } = store.getState(); @@ -78,10 +69,6 @@ export class PanelEditor extends PureComponent { return ; })} - -
{activeTab === 'general' && } diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss index 5f142dfd02f..1370bd96709 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -9,18 +9,14 @@ .react-grid-item { display: none !important; transition-property: none !important; - } - .panel--fullscreen { - display: block !important; - position: unset !important; - width: 100% !important; - height: 100% !important; - transform: translate(0px, 0px) !important; - } - - .panel { - margin: 0 !important; + &--fullscreen { + display: block !important; + position: unset !important; + width: 100% !important; + height: 100% !important; + transform: translate(0px, 0px) !important; + } } // Disable grid interaction indicators in fullscreen panels @@ -83,10 +79,10 @@ } // Disable animation on initial rendering and enable it when component has been mounted. -.react-grid-item.cssTransforms.panel { +.react-grid-item.cssTransforms { transition-property: none; } -.animated .react-grid-item.cssTransforms.panel { +.animated .react-grid-item.cssTransforms { transition-property: transform; } diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index dcf3b1f0aa9..84416c75c7d 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -50,6 +50,10 @@ .navbar-page-btn .fa-caret-down { display: none; } + + .navbar-buttons--close { + display: flex; + } } .navbar-page-btn { diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 05608fd38ec..3e1b3755917 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -4,8 +4,19 @@ height: 100%; } -.panel-editor-container__panel { - flex: 1 1 0; +.panel-wrapper { + height: 100%; + + &--edit { + flex: 1 1 0; + height: unset; + } + + &--view { + flex: 1 1 0; + height: 80%; + padding: $dashboard-padding; + } } .panel-editor-container__editor { From 97f7a7fbba392958a50753d437a8313b0ce89866 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 20 Nov 2018 16:33:26 +0100 Subject: [PATCH 14/81] react-panel: Add data source "options". Needs UX, WIP. --- .../dashboard/dashgrid/DataSourceOption.tsx | 31 ++++++++ .../dashboard/dashgrid/EditorTabBody.tsx | 6 +- .../dashboard/dashgrid/QueriesTab.tsx | 74 ++++++++++++++++++- public/app/features/dashboard/panel_model.ts | 1 + 4 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/DataSourceOption.tsx diff --git a/public/app/features/dashboard/dashgrid/DataSourceOption.tsx b/public/app/features/dashboard/dashgrid/DataSourceOption.tsx new file mode 100644 index 00000000000..0adfc4abe16 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DataSourceOption.tsx @@ -0,0 +1,31 @@ +import React, { SFC } from 'react'; +import Tooltip from 'app/core/components/Tooltip/Tooltip'; + +interface Props { + label: string; + placeholder?: string; + name?: string; + value?: string; + onChange?: (evt: any) => void; + tooltipInfo?: any; +} + +export const DataSourceOptions: SFC = ({ label, placeholder, name, value, onChange, tooltipInfo }) => { + const dsOption = ( +
+ + onChange(evt.target.value)} + /> +
+ ); + + return tooltipInfo ? {dsOption} : dsOption; +}; + +export default DataSourceOptions; diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index cba75aef979..f9f96b748f7 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -14,7 +14,7 @@ export interface EditorToolBarView { icon?: string; disabled?: boolean; onClick?: () => void; - render: (closeFunction: any) => JSX.Element; + render: (closeFunction: any) => JSX.Element | JSX.Element[]; } interface State { @@ -42,7 +42,9 @@ export class EditorTabBody extends PureComponent { static getDerivedStateFromProps(props, state) { if (state.openView) { - const activeToolbarItem = props.toolbarItems.find(item => item.title === state.openView.title); + const activeToolbarItem = props.toolbarItems.find( + item => item.title === state.openView.title && item.icon === state.openView.icon + ); if (activeToolbarItem) { return { ...state, diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 80db1e5fca4..878472094dc 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; - +import DataSourceOption from './DataSourceOption'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import { EditorTabBody } from './EditorTabBody'; import { DataSourcePicker } from './DataSourcePicker'; @@ -139,11 +139,71 @@ export class QueriesTab extends PureComponent { } }; + renderOptions = close => { + const { currentDatasource } = this.state; + const { queryOptions } = currentDatasource.meta; + const { panel } = this.props; + + const onChangeFn = (panelKey: string) => { + return (value: string | number) => { + panel[panelKey] = value; + panel.refresh(); + }; + }; + + const allOptions = { + cacheTimeout: { + label: 'Cache timeout', + placeholder: '60', + name: 'cacheTimeout', + value: panel.cacheTimeout, + tooltipInfo: ( + <> + If your time series store has a query cache this option can override the default cache timeout. Specify a + numeric value in seconds. + + ), + }, + maxDataPoints: { + label: 'Max data points', + placeholder: 'auto', + name: 'maxDataPoints', + value: panel.maxDataPoints, + tooltipInfo: ( + <> + The maximum data points the query should return. For graphs this is automatically set to one data point per + pixel. + + ), + }, + minInterval: { + label: 'Min time interval', + placeholder: '0', + name: 'minInterval', + value: panel.interval, + panelKey: 'interval', + tooltipInfo: ( + <> + A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '} + 1m if your data is written every minute. Access auto interval via variable{' '} + $__interval for time range string and $__interval_ms for numeric variable that can + be used in math expressions. + + ), + }, + }; + + return Object.keys(queryOptions).map(key => { + const options = allOptions[key]; + return ; + }); + }; + render() { const { currentDatasource } = this.state; const { helpHtml } = this.state.help; - const { hasQueryHelp } = currentDatasource.meta; - + const { hasQueryHelp, queryOptions } = currentDatasource.meta; + const hasQueryOptions = !!queryOptions; const dsInformation = { title: currentDatasource.name, imgSrc: currentDatasource.meta.info.logos.small, @@ -171,8 +231,14 @@ export class QueriesTab extends PureComponent { render: () => helpHtml, }; + const options = { + title: 'Options', + disabled: !hasQueryOptions, + render: this.renderOptions, + }; + return ( - +
(this.element = element)} style={{ width: '100%' }} /> ); diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index 692edaf666d..9643c23dbc1 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -94,6 +94,7 @@ export class PanelModel { isEditing: boolean; hasRefreshed: boolean; events: Emitter; + cacheTimeout?: any; constructor(model) { this.events = new Emitter(); From 9baa54e98cf0be83626c0fbfd8c5f17e64533c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Nov 2018 17:09:47 +0100 Subject: [PATCH 15/81] added alert tab to new react panel editor --- public/app/features/alerting/AlertTabCtrl.ts | 3 + .../features/dashboard/dashgrid/AlertTab.tsx | 72 +++++++++++++++++++ .../dashboard/dashgrid/GeneralTab.tsx | 2 +- .../dashboard/dashgrid/PanelEditor.tsx | 69 +++++++++++------- public/app/plugins/panel/graph/module.ts | 5 -- public/sass/components/_panel_editor.scss | 4 +- 6 files changed, 124 insertions(+), 31 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/AlertTab.tsx diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index ef68ddcf4a5..5a8abee00d7 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -1,4 +1,5 @@ import _ from 'lodash'; +import coreModule from 'app/core/core_module'; import { ThresholdMapper } from './state/ThresholdMapper'; import { QueryPart } from 'app/core/components/query_part/query_part'; import alertDef from './state/alertDef'; @@ -430,3 +431,5 @@ export function alertTab() { controller: AlertTabCtrl, }; } + +coreModule.directive('alertTab', alertTab); diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx new file mode 100644 index 00000000000..bf691782e92 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -0,0 +1,72 @@ +import React, { PureComponent } from 'react'; + +import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import { EditorTabBody } from './EditorTabBody'; +import 'app/features/alerting/AlertTabCtrl'; + +interface Props { + angularPanel?: AngularComponent; +} + +export class AlertTab extends PureComponent { + element: any; + component: AngularComponent; + + constructor(props) { + super(props); + } + + componentDidMount() { + if (this.shouldLoadAlertTab()) { + this.loadAlertTab(); + } + } + + componentDidUpdate(prevProps: Props) { + if (this.shouldLoadAlertTab()) { + this.loadAlertTab(); + } + } + + shouldLoadAlertTab() { + return this.props.angularPanel && this.element; + } + + componentWillUnmount() { + if (this.component) { + this.component.destroy(); + } + } + + loadAlertTab() { + const { angularPanel } = this.props; + + const scope = angularPanel.getScope(); + + // When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet + if (!scope.$$childHead) { + setTimeout(() => { + this.forceUpdate(); + }); + return; + } + + const panelCtrl = scope.$$childHead.ctrl; + const loader = getAngularLoader(); + const template = ''; + + const scopeProps = { + ctrl: panelCtrl, + }; + + this.component = loader.load(this.element, scopeProps, template); + } + + render() { + return ( + +
(this.element = element)} /> + + ); + } +} diff --git a/public/app/features/dashboard/dashgrid/GeneralTab.tsx b/public/app/features/dashboard/dashgrid/GeneralTab.tsx index 830421a6cc6..0b600e08d83 100644 --- a/public/app/features/dashboard/dashgrid/GeneralTab.tsx +++ b/public/app/features/dashboard/dashgrid/GeneralTab.tsx @@ -51,7 +51,7 @@ export class GeneralTab extends PureComponent { return ( -
(this.element = element)} style={{ width: '100%' }} /> +
(this.element = element)} /> ); } diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 82fc849f81a..019982707a1 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -4,7 +4,9 @@ import classNames from 'classnames'; import { QueriesTab } from './QueriesTab'; import { VisualizationTab } from './VisualizationTab'; import { GeneralTab } from './GeneralTab'; +import { AlertTab } from './AlertTab'; +import config from 'app/core/config'; import { store } from 'app/store/store'; import { updateLocation } from 'app/core/actions'; import { AngularComponent } from 'app/core/services/AngularLoader'; @@ -28,16 +30,8 @@ interface PanelEditorTab { } export class PanelEditor extends PureComponent { - tabs: PanelEditorTab[]; - constructor(props) { super(props); - - this.tabs = [ - { id: 'general', text: 'General', icon: 'gicon gicon-preferences' }, - { id: 'queries', text: 'Queries', icon: 'fa fa-database' }, - { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' }, - ]; } onChangeTab = (tab: PanelEditorTab) => { @@ -50,10 +44,48 @@ export class PanelEditor extends PureComponent { this.forceUpdate(); }; - render() { + renderCurrentTab(activeTab: string) { const { panel, dashboard, onTypeChanged, plugin, angularPanel } = this.props; - const { location } = store.getState(); - const activeTab = location.query.tab || 'queries'; + + switch (activeTab) { + case 'general': + return ; + case 'queries': + return ; + case 'alert': + return ; + case 'visualization': + return ( + + ); + default: + return null; + } + } + + render() { + const { plugin } = this.props; + const activeTab = store.getState().location.query.tab || 'queries'; + + const tabs = [ + { id: 'general', text: 'General', icon: 'gicon gicon-preferences' }, + { id: 'queries', text: 'Queries', icon: 'fa fa-database' }, + { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' }, + ]; + + if (config.alertingEnabled && plugin.id === 'graph') { + tabs.push({ + id: 'alert', + text: 'Alert', + icon: 'gicon gicon-alert', + }); + } return (
@@ -65,23 +97,12 @@ export class PanelEditor extends PureComponent {
    - {this.tabs.map(tab => { + {tabs.map(tab => { return ; })}
- - {activeTab === 'general' && } - {activeTab === 'queries' && } - {activeTab === 'visualization' && ( - - )} + {this.renderCurrentTab(activeTab)}
); } diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 37b805f2f40..24c8825e883 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -138,11 +138,6 @@ class GraphCtrl extends MetricsPanelCtrl { this.addEditorTab('Display options', 'public/app/plugins/panel/graph/tab_display.html'); this.addEditorTab('Axes', axesEditorComponent); this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html'); - - // if (config.alertingEnabled) { - // this.addEditorTab('Alert', alertTab, 5); - // } - this.subTabIndex = 0; } diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 3e1b3755917..0baaaa3ea88 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -10,12 +10,14 @@ &--edit { flex: 1 1 0; height: unset; + margin: 0 $dashboard-padding; } &--view { flex: 1 1 0; height: 80%; - padding: $dashboard-padding; + margin: 0 $dashboard-padding; + padding-top: $dashboard-padding; } } From 69e5584d398524820ad6fb1902e460e12b5ae347 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 20 Nov 2018 17:11:47 +0100 Subject: [PATCH 16/81] react-panel: Avoid duplicate keys --- public/app/features/dashboard/dashgrid/EditorTabBody.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index f9f96b748f7..0192de77e9f 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -57,7 +57,7 @@ export class EditorTabBody extends PureComponent { renderMainSelection(view: EditorToolBarView) { return ( -
this.onToggleToolBarView(view)} key={view.title}> +
this.onToggleToolBarView(view)} key={view.title + view.icon}>
{view.title}
@@ -74,7 +74,7 @@ export class EditorTabBody extends PureComponent { }; return ( -
+
From e47fc40b13253d4f50f82464206e7ebe3ffa80ec Mon Sep 17 00:00:00 2001 From: ijin08 Date: Wed, 21 Nov 2018 16:05:01 +0100 Subject: [PATCH 17/81] added icons for panel-edit side menu --- public/img/Alerts.svg | 9 ++++++++ public/img/Queries.svg | 8 +++++++ public/img/QueriesSelected.svg | 31 +++++++++++++++++++++++++ public/img/Visualization.svg | 7 ++++++ public/img/VisualizationSelected.svg | 34 ++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 public/img/Alerts.svg create mode 100644 public/img/Queries.svg create mode 100644 public/img/QueriesSelected.svg create mode 100644 public/img/Visualization.svg create mode 100644 public/img/VisualizationSelected.svg diff --git a/public/img/Alerts.svg b/public/img/Alerts.svg new file mode 100644 index 00000000000..482b3c16e73 --- /dev/null +++ b/public/img/Alerts.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/Queries.svg b/public/img/Queries.svg new file mode 100644 index 00000000000..994b8512bc9 --- /dev/null +++ b/public/img/Queries.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/img/QueriesSelected.svg b/public/img/QueriesSelected.svg new file mode 100644 index 00000000000..d200bbc367b --- /dev/null +++ b/public/img/QueriesSelected.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/Visualization.svg b/public/img/Visualization.svg new file mode 100644 index 00000000000..d2cdd81401f --- /dev/null +++ b/public/img/Visualization.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/img/VisualizationSelected.svg b/public/img/VisualizationSelected.svg new file mode 100644 index 00000000000..b7b5fd96aed --- /dev/null +++ b/public/img/VisualizationSelected.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c7e296307a6c2fb2154540d04b011b56e60dee21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Nov 2018 20:32:04 +0100 Subject: [PATCH 18/81] panel edit ux experiments --- .../dashboard/dashgrid/EditorTabBody.tsx | 20 ++++++------- .../dashboard/dashgrid/GeneralTab.tsx | 2 +- .../dashboard/dashgrid/PanelEditor.tsx | 17 ++++------- .../dashboard/dashgrid/QueriesTab.tsx | 2 +- .../dashboard/dashgrid/VisualizationTab.tsx | 2 +- .../img/{Alerts.svg => panel-tabs/alert.svg} | 0 .../queries-selected.svg} | 0 .../{Queries.svg => panel-tabs/queries.svg} | 0 .../visualization-selected.svg} | 0 .../visualization.svg} | 0 public/sass/components/_panel_editor.scss | 28 +++++++++---------- public/sass/components/_toolbar.scss | 9 ++++-- 12 files changed, 38 insertions(+), 42 deletions(-) rename public/img/{Alerts.svg => panel-tabs/alert.svg} (100%) rename public/img/{QueriesSelected.svg => panel-tabs/queries-selected.svg} (100%) rename public/img/{Queries.svg => panel-tabs/queries.svg} (100%) rename public/img/{VisualizationSelected.svg => panel-tabs/visualization-selected.svg} (100%) rename public/img/{Visualization.svg => panel-tabs/visualization.svg} (100%) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 0192de77e9f..63c8e74ef94 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -4,6 +4,7 @@ import { FadeIn } from 'app/core/components/Animations/FadeIn'; interface Props { children: JSX.Element; + heading: string; main?: EditorToolBarView; toolbarItems: EditorToolBarView[]; } @@ -94,17 +95,16 @@ export class EditorTabBody extends PureComponent { } render() { - const { children, toolbarItems, main } = this.props; + const { children, toolbarItems, main, heading } = this.props; const { openView } = this.state; return ( - <> - {main && ( -
- {this.renderMainSelection(main)} -
- {toolbarItems.map(item => this.renderButton(item))} -
- )} +
+
+
{heading}
+ {main && this.renderMainSelection(main)} +
+ {toolbarItems.map(item => this.renderButton(item))} +
@@ -115,7 +115,7 @@ export class EditorTabBody extends PureComponent {
- +
); } } diff --git a/public/app/features/dashboard/dashgrid/GeneralTab.tsx b/public/app/features/dashboard/dashgrid/GeneralTab.tsx index 0b600e08d83..f7de2d3ed30 100644 --- a/public/app/features/dashboard/dashgrid/GeneralTab.tsx +++ b/public/app/features/dashboard/dashgrid/GeneralTab.tsx @@ -50,7 +50,7 @@ export class GeneralTab extends PureComponent { }; return ( - +
(this.element = element)} /> ); diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 019982707a1..2ea49b0553f 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -26,7 +26,6 @@ interface PanelEditorProps { interface PanelEditorTab { id: string; text: string; - icon: string; } export class PanelEditor extends PureComponent { @@ -74,16 +73,14 @@ export class PanelEditor extends PureComponent { const activeTab = store.getState().location.query.tab || 'queries'; const tabs = [ - { id: 'general', text: 'General', icon: 'gicon gicon-preferences' }, - { id: 'queries', text: 'Queries', icon: 'fa fa-database' }, - { id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' }, + { id: 'queries', text: 'Queries' }, + { id: 'visualization', text: 'Visualization' }, ]; if (config.alertingEnabled && plugin.id === 'graph') { tabs.push({ id: 'alert', text: 'Alert', - icon: 'gicon gicon-alert', }); } @@ -96,11 +93,9 @@ export class PanelEditor extends PureComponent {
-
    {tabs.map(tab => { return ; })} -
{this.renderCurrentTab(activeTab)}
@@ -116,15 +111,15 @@ interface TabItemParams { function TabItem({ tab, activeTab, onClick }: TabItemParams) { const tabClasses = classNames({ - 'gf-tabs-link': true, + 'panel-editor-tabs__link': true, active: activeTab === tab.id, }); return ( -
  • onClick(tab)}> +
    onClick(tab)}> - {tab.text} + -
  • +
    ); } diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 878472094dc..5ce178c7931 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -238,7 +238,7 @@ export class QueriesTab extends PureComponent { }; return ( - +
    (this.element = element)} style={{ width: '100%' }} /> ); diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index d6573fe9eec..75e9940d67c 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -127,7 +127,7 @@ export class VisualizationTab extends PureComponent { }; return ( - + {this.renderPanelOptions()} ); diff --git a/public/img/Alerts.svg b/public/img/panel-tabs/alert.svg similarity index 100% rename from public/img/Alerts.svg rename to public/img/panel-tabs/alert.svg diff --git a/public/img/QueriesSelected.svg b/public/img/panel-tabs/queries-selected.svg similarity index 100% rename from public/img/QueriesSelected.svg rename to public/img/panel-tabs/queries-selected.svg diff --git a/public/img/Queries.svg b/public/img/panel-tabs/queries.svg similarity index 100% rename from public/img/Queries.svg rename to public/img/panel-tabs/queries.svg diff --git a/public/img/VisualizationSelected.svg b/public/img/panel-tabs/visualization-selected.svg similarity index 100% rename from public/img/VisualizationSelected.svg rename to public/img/panel-tabs/visualization-selected.svg diff --git a/public/img/Visualization.svg b/public/img/panel-tabs/visualization.svg similarity index 100% rename from public/img/Visualization.svg rename to public/img/panel-tabs/visualization.svg diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 0baaaa3ea88..dfa496a7d27 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -24,11 +24,17 @@ .panel-editor-container__editor { margin-top: $panel-margin*2; display: flex; - flex-direction: column; + flex-direction: row; height: 65%; position: relative; } +.panel-editor__right { + display: flex; + flex-direction: column; + flex-grow: 1; +} + .panel-editor__scroll { flex-grow: 1; min-width: 0; @@ -37,6 +43,7 @@ .panel-editor__content { padding: 40px 20px; + background: $input-label-bg; } .panel-in-fullscreen { @@ -149,22 +156,13 @@ .panel-editor-tabs { position: relative; z-index: 2; - box-shadow: $page-header-shadow; - border-bottom: 1px solid $page-header-border-color; - padding: 0 $dashboard-padding; - - @include clearfix(); - - .active.gf-tabs-link { - background: $toolbar-tab-bg; - } + display: flex; + flex-direction: column; + padding: 40px 10px 10px 10px; } -.panel-editor-tabs__close { - padding: 5px 9px; - border-radius: $border-radius; - float: right; - @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl); +.panel-editor-tabs__item { + margin-bottom: 25px; } .ds-picker-list { diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss index aa1c46e0fb4..d398e0eb97c 100644 --- a/public/sass/components/_toolbar.scss +++ b/public/sass/components/_toolbar.scss @@ -2,14 +2,17 @@ display: flex; align-content: center; align-items: center; - background: $toolbar-bg; - box-shadow: $toolbar-shadow; - padding: 7px 20px 7px 20px; + padding: 3px 20px 3px 20px; position: relative; z-index: 1; flex: 0 0 auto; } +.toolbar__heading { + font-size: $font-size-lg; + padding-right: 10px; +} + .toolbar__main { padding: $input-padding-y $input-padding-x; font-size: $font-size-md; From 9a2b2863fb714835f91f7ee3fbecf10bc8d1867b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 22 Nov 2018 08:46:54 +0100 Subject: [PATCH 19/81] styling progress --- .../features/dashboard/dashgrid/AlertTab.tsx | 2 +- .../dashboard/dashgrid/GeneralTab.tsx | 8 +-- .../dashboard/dashgrid/PanelEditor.tsx | 23 +++--- .../features/panel/partials/metrics_tab.html | 70 ------------------- public/sass/components/_panel_editor.scss | 41 ++++++++++- public/sass/components/_toolbar.scss | 3 + public/sass/layout/_page.scss | 6 ++ 7 files changed, 61 insertions(+), 92 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index bf691782e92..7df7864c758 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -64,7 +64,7 @@ export class AlertTab extends PureComponent { render() { return ( - +
    (this.element = element)} /> ); diff --git a/public/app/features/dashboard/dashgrid/GeneralTab.tsx b/public/app/features/dashboard/dashgrid/GeneralTab.tsx index f7de2d3ed30..2814b825f17 100644 --- a/public/app/features/dashboard/dashgrid/GeneralTab.tsx +++ b/public/app/features/dashboard/dashgrid/GeneralTab.tsx @@ -43,14 +43,8 @@ export class GeneralTab extends PureComponent { } render() { - const currentDataSource = { - title: 'ProductionDB', - imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg', - render: () =>

    hello

    , - }; - return ( - +
    (this.element = element)} /> ); diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 2ea49b0553f..0d38b60fa95 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -72,10 +72,7 @@ export class PanelEditor extends PureComponent { const { plugin } = this.props; const activeTab = store.getState().location.query.tab || 'queries'; - const tabs = [ - { id: 'queries', text: 'Queries' }, - { id: 'visualization', text: 'Visualization' }, - ]; + const tabs = [{ id: 'queries', text: 'Queries' }, { id: 'visualization', text: 'Visualization' }]; if (config.alertingEnabled && plugin.id === 'graph') { tabs.push({ @@ -86,16 +83,18 @@ export class PanelEditor extends PureComponent { return (
    -
    -
    -
    -
    -
    + { + //
    + //
    + //
    + //
    + //
    + }
    - {tabs.map(tab => { - return ; - })} + {tabs.map(tab => { + return ; + })}
    {this.renderCurrentTab(activeTab)}
    diff --git a/public/app/features/panel/partials/metrics_tab.html b/public/app/features/panel/partials/metrics_tab.html index 7899312f5c3..ecee1f76ea9 100644 --- a/public/app/features/panel/partials/metrics_tab.html +++ b/public/app/features/panel/partials/metrics_tab.html @@ -1,73 +1,3 @@ -
    -
    -
    - - - -
    - -
    - -
    - -
    - -
    -
    - -
    -
    - -
    -
    -
    - - - - A lower limit for the auto group by time interval. Recommended to be set to write frequency, - for example 1m if your data is written every minute. Access auto interval via variable $__interval for time range - string and $__interval_ms for numeric variable that can be used in math expressions. - -
    -
    - - - - If your time series store has a query cache this option can override the default - cache timeout. Specify a numeric value in seconds. - -
    -
    - - - - The maximum data points the query should return. For graphs this - is automatically set to one data point per pixel. - -
    -
    - -
    -
    - - - -
    - - -
    -
    diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index dfa496a7d27..27a3ad01bf7 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -33,6 +33,10 @@ display: flex; flex-direction: column; flex-grow: 1; + background: $page-bg; + margin: 0 67px; + border-left: 2px solid #ac5224; + border-radius: 3px; } .panel-editor__scroll { @@ -43,7 +47,6 @@ .panel-editor__content { padding: 40px 20px; - background: $input-label-bg; } .panel-in-fullscreen { @@ -158,11 +161,45 @@ z-index: 2; display: flex; flex-direction: column; - padding: 40px 10px 10px 10px; + position: absolute; + top: 60px; + left: 5px; + align-items: center; + + &::before { + content: ''; + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 25px; + width: 2px; + background: #8e8e8e; + } } .panel-editor-tabs__item { margin-bottom: 25px; + position: relative; + z-index: 1; + text-align: center; + + &:last-child { + margin-bottom: 0; + } +} + +.panel-editor-tabs__link { + display: inline-block; + + &.active { + position: relative; + left: 4px; + } + + img { + height: 44px; + } } .ds-picker-list { diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss index d398e0eb97c..9e850968bcf 100644 --- a/public/sass/components/_toolbar.scss +++ b/public/sass/components/_toolbar.scss @@ -6,6 +6,9 @@ position: relative; z-index: 1; flex: 0 0 auto; + background: $input-label-bg; + border-bottom: 1px solid $black; + border-radius: 3px; } .toolbar__heading { diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index faa5b94d4ad..4655c73b48d 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -14,6 +14,12 @@ background: $page-gradient; } +.panel-in-fullscreen { + .main-view { + background: linear-gradient(180deg, #222426 10px, #090909 200px); + } +} + .page-container { margin-left: auto; margin-right: auto; From 547044b65fef73e5058dfcc4235960079e53e80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 22 Nov 2018 11:41:33 +0100 Subject: [PATCH 20/81] style tweaks --- public/app/features/dashboard/dashgrid/EditorTabBody.tsx | 4 ++-- public/app/features/dashboard/dashgrid/PanelEditor.tsx | 2 +- .../app/features/dashboard/dashgrid/VisualizationTab.tsx | 8 +++++++- public/sass/components/_panel_editor.scss | 2 +- public/sass/components/_toolbar.scss | 9 +++------ 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 63c8e74ef94..6ac3cd0573f 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -98,7 +98,7 @@ export class EditorTabBody extends PureComponent { const { children, toolbarItems, main, heading } = this.props; const { openView } = this.state; return ( -
    + <>
    {heading}
    {main && this.renderMainSelection(main)} @@ -115,7 +115,7 @@ export class EditorTabBody extends PureComponent {
    -
    + ); } } diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 0d38b60fa95..24856006e28 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -96,7 +96,7 @@ export class PanelEditor extends PureComponent { return ; })}
    - {this.renderCurrentTab(activeTab)} +
    {this.renderCurrentTab(activeTab)}
    ); } diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 75e9940d67c..bcb1c0bb0a6 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -126,8 +126,14 @@ export class VisualizationTab extends PureComponent { }, }; + const panelHelp = { + title: '', + icon: 'fa fa-question', + render: () =>

    Help

    , + }; + return ( - + {this.renderPanelOptions()} ); diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 27a3ad01bf7..436014346d3 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -162,7 +162,7 @@ display: flex; flex-direction: column; position: absolute; - top: 60px; + top: 79px; left: 5px; align-items: center; diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss index 9e850968bcf..72ab783fed2 100644 --- a/public/sass/components/_toolbar.scss +++ b/public/sass/components/_toolbar.scss @@ -6,7 +6,7 @@ position: relative; z-index: 1; flex: 0 0 auto; - background: $input-label-bg; + background: $black; border-bottom: 1px solid $black; border-radius: 3px; } @@ -44,10 +44,7 @@ .toolbar-subview { position: relative; - padding: 20px 20px; - background-color: $empty-list-cta-bg; - top: -45px; - margin: 0 30px 20px 0px; + top: -23px; } .toolbar-subview__close { @@ -56,7 +53,7 @@ border: none; position: absolute; right: 15px; - top: 20px; + top: 0px; font-size: $font-size-md; &:hover { From d70dfd5fd7922a113ae63eeb7890f7a01a72b546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 22 Nov 2018 12:13:28 +0100 Subject: [PATCH 21/81] edit mode styling --- .../features/dashboard/dashgrid/EditorTabBody.tsx | 10 ++++------ public/sass/components/_panel_editor.scss | 15 +++++++++------ public/sass/components/_toolbar.scss | 9 ++------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 6ac3cd0573f..02404a917e6 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -107,12 +107,10 @@ export class EditorTabBody extends PureComponent {
    -
    - - {openView && this.renderOpenView(openView)} - - {children} -
    + +
    {openView && this.renderOpenView(openView)}
    +
    +
    {children}
    diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 436014346d3..907fa0ce44e 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -49,6 +49,11 @@ padding: 40px 20px; } +.panel-editor__toolbar-view { + background: $black; + padding: 20px; +} + .panel-in-fullscreen { .sidemenu { display: none; @@ -116,9 +121,8 @@ } .viz-picker__item { - background: $card-background; - box-shadow: $card-shadow; - + background: $panel-bg; + border: $panel-border; border-radius: 3px; height: 90px; width: 150px; @@ -210,9 +214,8 @@ } .ds-picker-list__item { - background: $card-background; - box-shadow: $card-shadow; - + background: $panel-bg; + border: $panel-border; border-radius: 3px; display: flex; cursor: pointer; diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss index 72ab783fed2..ca81a6a7316 100644 --- a/public/sass/components/_toolbar.scss +++ b/public/sass/components/_toolbar.scss @@ -42,18 +42,13 @@ height: 20px; } -.toolbar-subview { - position: relative; - top: -23px; -} - .toolbar-subview__close { background: transparent; padding: 4px 8px 4px 9px; border: none; position: absolute; - right: 15px; - top: 0px; + right: 25px; + top: 20px; font-size: $font-size-md; &:hover { From 1b92bc62cbdbe2cabdd2c43bd1ae198f9cdeb57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 22 Nov 2018 12:19:52 +0100 Subject: [PATCH 22/81] edit mode styling --- .../features/dashboard/dashgrid/EditorTabBody.tsx | 14 ++++++++++++-- .../app/features/dashboard/dashgrid/QueriesTab.tsx | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 02404a917e6..6228cbe6691 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -20,6 +20,7 @@ export interface EditorToolBarView { interface State { openView?: EditorToolBarView; + test: boolean; } export class EditorTabBody extends PureComponent { @@ -28,9 +29,14 @@ export class EditorTabBody extends PureComponent { this.state = { openView: null, + test: false, }; } + componentDidMount() { + this.setState({ test: true }); + } + onToggleToolBarView = (item: EditorToolBarView) => { this.setState({ openView: item === this.state.openView ? null : item, @@ -96,7 +102,7 @@ export class EditorTabBody extends PureComponent { render() { const { children, toolbarItems, main, heading } = this.props; - const { openView } = this.state; + const { openView, test } = this.state; return ( <>
    @@ -110,7 +116,11 @@ export class EditorTabBody extends PureComponent {
    {openView && this.renderOpenView(openView)}
    -
    {children}
    +
    + + {children} + +
    diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 5ce178c7931..cba631fb141 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -238,7 +238,7 @@ export class QueriesTab extends PureComponent { }; return ( - +
    (this.element = element)} style={{ width: '100%' }} /> ); From 300bd75b542f797a93d2a0b6312a4d79d84b1d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 22 Nov 2018 12:22:59 +0100 Subject: [PATCH 23/81] edit mode styling --- .../app/features/dashboard/dashgrid/EditorTabBody.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 6228cbe6691..8f213533a1e 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -20,7 +20,7 @@ export interface EditorToolBarView { interface State { openView?: EditorToolBarView; - test: boolean; + fadeIn: boolean; } export class EditorTabBody extends PureComponent { @@ -29,12 +29,12 @@ export class EditorTabBody extends PureComponent { this.state = { openView: null, - test: false, + fadeIn: false, }; } componentDidMount() { - this.setState({ test: true }); + this.setState({ fadeIn: true }); } onToggleToolBarView = (item: EditorToolBarView) => { @@ -102,7 +102,8 @@ export class EditorTabBody extends PureComponent { render() { const { children, toolbarItems, main, heading } = this.props; - const { openView, test } = this.state; + const { openView, fadeIn } = this.state; + return ( <>
    @@ -117,7 +118,7 @@ export class EditorTabBody extends PureComponent {
    {openView && this.renderOpenView(openView)}
    - + {children}
    From f8b1484265d06b5cc2f5fba41af2c9a33e3f8742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 22 Nov 2018 15:34:55 +0100 Subject: [PATCH 24/81] minor style changes --- public/sass/_variables.dark.scss | 5 ++--- public/sass/_variables.light.scss | 5 ++--- public/sass/components/_panel_editor.scss | 4 ++-- public/sass/components/_toolbar.scss | 5 ++--- public/sass/layout/_page.scss | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index cdd5fc46ad6..43aa7df7683 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -79,6 +79,7 @@ $brand-gradient: linear-gradient( ); $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px); +$edit-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 00px, #090909 50%); // Links // ------------------------- @@ -268,9 +269,7 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black; $tab-border-color: $dark-4; // Toolbar -$toolbar-bg: $page-header-bg; -$toolbar-shadow: 0 0 20px black; -$toolbar-tab-bg: $gray-blue; +$toolbar-bg: $black; // Pagination // ------------------------- diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 4e3c8fc3bd6..806aae353c3 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -76,6 +76,7 @@ $textShadow: none; // gradients $brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%); $page-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%); +$edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%); // Links // ------------------------- @@ -215,9 +216,7 @@ $menu-dropdown-shadow: 5px 5px 10px -5px $gray-1; $tab-border-color: $gray-5; // Toolbar -$toolbar-bg: linear-gradient(90deg, #ffffff, #e6eef9); -$toolbar-shadow: 1px 1px 3px #c7d0d8; -$toolbar-tab-bg: $white; +$toolbar-bg: white; // search $search-shadow: 0 5px 30px 0 $gray-4; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 907fa0ce44e..656c1eb429b 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -25,7 +25,7 @@ margin-top: $panel-margin*2; display: flex; flex-direction: row; - height: 65%; + height: 60%; position: relative; } @@ -34,7 +34,7 @@ flex-direction: column; flex-grow: 1; background: $page-bg; - margin: 0 67px; + margin: 0 62px; border-left: 2px solid #ac5224; border-radius: 3px; } diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss index ca81a6a7316..6eb4e4f7e7f 100644 --- a/public/sass/components/_toolbar.scss +++ b/public/sass/components/_toolbar.scss @@ -6,14 +6,13 @@ position: relative; z-index: 1; flex: 0 0 auto; - background: $black; - border-bottom: 1px solid $black; + background: $toolbar-bg; border-radius: 3px; } .toolbar__heading { font-size: $font-size-lg; - padding-right: 10px; + padding-right: 20px; } .toolbar__main { diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index 4655c73b48d..02f21ab96bd 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -16,7 +16,7 @@ .panel-in-fullscreen { .main-view { - background: linear-gradient(180deg, #222426 10px, #090909 200px); + background: $edit-gradient; } } From eaf9a0b39a855ad9e2ddfddea3b1dd903c7db6c4 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 22 Nov 2018 18:22:15 +0100 Subject: [PATCH 25/81] initial commit --- public/app/features/explore/Explore.tsx | 16 +++-- public/app/features/explore/QueryEditor.tsx | 59 +++++++++++++++++++ public/app/features/explore/QueryRows.tsx | 47 +++++++++++---- public/app/features/panel/metrics_wrapper.ts | 23 ++++++++ public/app/features/plugins/datasource_srv.ts | 5 +- 5 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 public/app/features/explore/QueryEditor.tsx create mode 100644 public/app/features/panel/metrics_wrapper.ts diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index d4e9b689495..e36f1de4776 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -20,7 +20,7 @@ import { getIntervals, generateKey, generateQueryKeys, - hasNonEmptyQuery, + // hasNonEmptyQuery, makeTimeSeriesList, updateHistory, } from 'app/core/utils/explore'; @@ -30,6 +30,7 @@ import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer' import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { getTimeSrv } from 'app/features/dashboard/time_srv'; import Panel from './Panel'; import QueryRows from './QueryRows'; @@ -132,6 +133,13 @@ export class Explore extends React.PureComponent { }; } this.modifiedQueries = initialQueries.slice(); + const timeSrv = getTimeSrv(); + timeSrv.init({ + time: { from: 'now-6h', to: 'now' }, + refresh: false, + getTimezone: () => 'utc', + timeRangeUpdated: () => console.log('refreshDashboard!'), + }); } async componentDidMount() { @@ -691,9 +699,9 @@ export class Explore extends React.PureComponent { async runQueries(resultType: ResultType, queryOptions: any, resultGetter?: any) { const queries = [...this.modifiedQueries]; - if (!hasNonEmptyQuery(queries)) { - return; - } + // if (!hasNonEmptyQuery(queries)) { + // return; + // } const { datasource } = this.state; const datasourceId = datasource.meta.id; // Run all queries concurrently diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx new file mode 100644 index 00000000000..bbff55a1cce --- /dev/null +++ b/public/app/features/explore/QueryEditor.tsx @@ -0,0 +1,59 @@ +import React, { PureComponent } from 'react'; +import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import 'app/features/panel/metrics_wrapper'; +import { DataQuery } from 'app/types'; + +interface QueryEditorProps { + datasource: any; + error?: string | JSX.Element; + onExecuteQuery?: () => void; + onQueryChange?: (value: DataQuery, override?: boolean) => void; +} + +export default class QueryEditor extends PureComponent { + element: any; + component: AngularComponent; + + async componentDidMount() { + if (!this.element) { + return; + } + + const { datasource } = this.props; + const loader = getAngularLoader(); + const template = ''; + const target = { datasource: datasource.name }; + // const changeableTarget = onChange(target, () => console.log(target)); + // const changeable = onChange(target, () => console.log(target)); + const scopeProps = { + target, //: changeable, + ctrl: { + refresh: () => { + this.props.onQueryChange({ refId: 'TEST', ...target }, false); + this.props.onExecuteQuery(); + }, + events: { + on: () => {}, + }, + panel: { + datasource, + }, + dashboard: { + getNextQueryLetter: x => 'TEST', + }, + }, + }; + + this.component = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.component) { + this.component.destroy(); + } + } + + render() { + return
    (this.element = element)} style={{ width: '100%' }} />; + } +} diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 52c705c469c..bbadf8ccbf1 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -2,7 +2,8 @@ import React, { PureComponent } from 'react'; import { QueryTransaction, HistoryItem, QueryHint } from 'app/types/explore'; -import DefaultQueryField from './QueryField'; +// import DefaultQueryField from './QueryField'; +import QueryEditor from './QueryEditor'; import QueryTransactionStatus from './QueryTransactionStatus'; import { DataSource, DataQuery } from 'app/types'; @@ -36,6 +37,11 @@ type QueryRowProps = QueryRowCommonProps & }; class QueryRow extends PureComponent { + onExecuteQuery = () => { + const { onExecuteQuery } = this.props; + onExecuteQuery(); + }; + onChangeQuery = (value: DataQuery, override?: boolean) => { const { index, onChangeQuery } = this.props; if (onChangeQuery) { @@ -80,23 +86,40 @@ class QueryRow extends PureComponent { const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); const queryError = transactionWithError ? transactionWithError.error : null; - const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField; + // const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField; + const QueryField = datasource.pluginExports.ExploreQueryField; + // const QueryEditor = datasource.pluginExports.QueryCtrl; return (
    - + {QueryField ? ( + + ) : ( + + )}
    - -
    ); } diff --git a/public/img/panel-tabs/alert-selected.svg b/public/img/panel-tabs/alert-selected.svg new file mode 100644 index 00000000000..482b3c16e73 --- /dev/null +++ b/public/img/panel-tabs/alert-selected.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/img/panel-tabs/general-selected.svg b/public/img/panel-tabs/general-selected.svg new file mode 100644 index 00000000000..d200bbc367b --- /dev/null +++ b/public/img/panel-tabs/general-selected.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/panel-tabs/general.svg b/public/img/panel-tabs/general.svg new file mode 100644 index 00000000000..994b8512bc9 --- /dev/null +++ b/public/img/panel-tabs/general.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 43aa7df7683..2e98bc48aae 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -79,7 +79,7 @@ $brand-gradient: linear-gradient( ); $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px); -$edit-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 00px, #090909 50%); +$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 00px, #090909 60%); // Links // ------------------------- @@ -366,6 +366,6 @@ $switch-slider-shadow: 0 0 3px black; //Checkbox // ------------------------- $checkbox-bg: $dark-1; -$checkbox-border: 1px solid $gray-2; +$checkbox-border: 1px solid $gray-1; $checkbox-checked-bg: linear-gradient(0deg, $orange, $red); $checkbox-color: $dark-1; diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index 48e886e4c93..b15283e00e3 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -181,6 +181,7 @@ $input-border: 1px solid $input-border-color; @at-root textarea#{&} { overflow: auto; white-space: pre-wrap; + height: auto; } // Unstyle the caret on ` */} +
    +
    + )} ); } diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index dcb4686701d..87947965fc3 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -172,6 +172,12 @@ padding-right: 20px; } +// No horizontal padding +.btn-p-x-0 { + padding-left: 0; + padding-right: 0; +} + // External services // Usage: // From 13d0a117980cdec06dfbe82f4ebc847523857e7b Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 27 Nov 2018 12:13:31 +0100 Subject: [PATCH 66/81] react-panel: Remove json-formatter-js since we will continue with the "patched" version --- package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/package.json b/package.json index fa9a0dcad2e..452e4ecb742 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,6 @@ "file-saver": "^1.3.3", "immutable": "^3.8.2", "jquery": "^3.2.1", - "json-formatter-js": "^2.2.1", "lodash": "^4.17.10", "moment": "^2.22.2", "mousetrap": "^1.6.0", diff --git a/yarn.lock b/yarn.lock index be55f6fd52e..2cebcae1d9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6811,11 +6811,6 @@ json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" -json-formatter-js@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json-formatter-js/-/json-formatter-js-2.2.1.tgz#b101d628e86f028dc9cf9a7e1c83c65e536c9f87" - integrity sha1-sQHWKOhvAo3Jz5p+HIPGXlNsn4c= - json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" From 3908e64ef0319093f758ec7b052ef33979f4e190 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 27 Nov 2018 12:23:11 +0100 Subject: [PATCH 67/81] react-panel: Use correct type for children prop to avoid the use of fragments <> --- .../app/core/components/CopyToClipboard/CopyToClipboard.tsx | 4 ++-- public/app/features/dashboard/dashgrid/QueryInspector.tsx | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx b/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx index 153dae2a9c6..ea63de58b47 100644 --- a/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx +++ b/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, ReactNode } from 'react'; import ClipboardJS from 'clipboard'; interface Props { @@ -7,7 +7,7 @@ interface Props { onSuccess?: (evt: any) => void; onError?: (evt: any) => void; className?: string; - children?: JSX.Element | string; + children?: ReactNode; } export class CopyToClipboard extends PureComponent { diff --git a/public/app/features/dashboard/dashgrid/QueryInspector.tsx b/public/app/features/dashboard/dashgrid/QueryInspector.tsx index 08da527c035..6fc2669ab33 100644 --- a/public/app/features/dashboard/dashgrid/QueryInspector.tsx +++ b/public/app/features/dashboard/dashgrid/QueryInspector.tsx @@ -211,9 +211,7 @@ export class QueryInspector extends PureComponent { text={this.getTextForClipboard} onSuccess={this.onClipboardSuccess} > - <> - Copy to Clipboard - + Copy to Clipboard
    From a03900e6cf795b51b973041620bc2f7ebce82238 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 27 Nov 2018 12:33:30 +0100 Subject: [PATCH 68/81] react-panel: Remove comments and improve readability in render() --- .../dashboard/dashgrid/QueryInspector.tsx | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueryInspector.tsx b/public/app/features/dashboard/dashgrid/QueryInspector.tsx index 6fc2669ab33..870fb4e7c96 100644 --- a/public/app/features/dashboard/dashgrid/QueryInspector.tsx +++ b/public/app/features/dashboard/dashgrid/QueryInspector.tsx @@ -74,19 +74,12 @@ export class QueryInspector extends PureComponent { }; onDataSourceResponse = (response: any = {}) => { - // ignore if closed - // if (!this.isOpen) { - // return; - // } - if (this.state.isMocking) { this.handleMocking(response); return; } - // this.isLoading = false; - // data = _.cloneDeep(data); - response = { ...response }; // clone + response = { ...response }; // clone - dont modify the response if (response.headers) { delete response.headers; @@ -109,15 +102,6 @@ export class QueryInspector extends PureComponent { if (response.data) { response.response = response.data; - // if (response.status === 200) { - // // if we are in error state, assume we automatically opened - // // and auto close it again - // if (this.hasError) { - // this.hasError = false; - // this.isOpen = false; - // } - // } - delete response.data; delete response.status; delete response.statusText; @@ -175,10 +159,26 @@ export class QueryInspector extends PureComponent { })); }; + renderExpandCollapse = () => { + const { allNodesExpanded } = this.state; + + const collapse = ( + <> + Collapse All + + ); + const expand = ( + <> + Expand All + + ); + return allNodesExpanded ? collapse : expand; + }; + render() { const { response, isLoading } = this.state.dsQuery; const { LoadingPlaceholder } = this.props; - const { allNodesExpanded, isMocking } = this.state; + const { isMocking } = this.state; const openNodes = this.getNrOfOpenNodes(); if (isLoading) { @@ -187,23 +187,12 @@ export class QueryInspector extends PureComponent { return ( <> - {/*
    - Mock Response - */}
    Date: Tue, 27 Nov 2018 12:36:57 +0100 Subject: [PATCH 69/81] react-panel: Remove mock response button for now --- public/app/features/dashboard/dashgrid/QueryInspector.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueryInspector.tsx b/public/app/features/dashboard/dashgrid/QueryInspector.tsx index 870fb4e7c96..ec618404651 100644 --- a/public/app/features/dashboard/dashgrid/QueryInspector.tsx +++ b/public/app/features/dashboard/dashgrid/QueryInspector.tsx @@ -188,9 +188,11 @@ export class QueryInspector extends PureComponent { return ( <>
    + {/* + */} @@ -215,12 +217,6 @@ export class QueryInspector extends PureComponent { onInput={this.setMockedResponse} placeholder="JSON" /> - {/* */}
    )} From 69ae3d2e6ac0540809ed061b38ca45f18b9624f9 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 28 Nov 2018 15:15:31 +0100 Subject: [PATCH 70/81] react-panel: Time range options moved to "Queries" tab --- public/app/core/components/Form/Element.tsx | 43 ++++++++ public/app/core/components/Form/Input.tsx | 96 +++++++++++++++++ public/app/core/components/Form/Label.tsx | 19 ++++ public/app/core/components/Form/index.ts | 3 + public/app/core/utils/rangeutil.ts | 9 ++ .../dashboard/dashgrid/QueriesTab.tsx | 101 +++++++++++++++++- public/app/types/form.ts | 4 + public/app/types/index.ts | 3 +- public/sass/utils/_validation.scss | 6 +- 9 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 public/app/core/components/Form/Element.tsx create mode 100644 public/app/core/components/Form/Input.tsx create mode 100644 public/app/core/components/Form/Label.tsx create mode 100644 public/app/core/components/Form/index.ts create mode 100644 public/app/types/form.ts diff --git a/public/app/core/components/Form/Element.tsx b/public/app/core/components/Form/Element.tsx new file mode 100644 index 00000000000..997d7f0e717 --- /dev/null +++ b/public/app/core/components/Form/Element.tsx @@ -0,0 +1,43 @@ +import React, { PureComponent, ReactNode, ReactElement } from 'react'; +import { Label } from './Label'; +import { uniqueId } from 'lodash'; + +interface Props { + label?: ReactNode; + labelClassName?: string; + id?: string; + children: ReactElement; +} + +export class Element extends PureComponent { + elementId: string = this.props.id || uniqueId('form-element-'); + + get elementLabel() { + const { label, labelClassName } = this.props; + + if (label) { + return ( + + ); + } + + return null; + } + + get children() { + const { children } = this.props; + + return React.cloneElement(children, { id: this.elementId }); + } + + render() { + return ( +
    + {this.elementLabel} + {this.children} +
    + ); + } +} diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx new file mode 100644 index 00000000000..a261203b3f3 --- /dev/null +++ b/public/app/core/components/Form/Input.tsx @@ -0,0 +1,96 @@ +import React, { PureComponent } from 'react'; +import { ValidationRule } from 'app/types'; + +export enum InputStatus { + Default = 'default', + Loading = 'loading', + Invalid = 'invalid', + Valid = 'valid', +} + +export enum InputTypes { + Text = 'text', + Number = 'number', + Password = 'password', + Email = 'email', +} + +interface Props { + status?: InputStatus; + validationRules: ValidationRule[]; + hideErrorMessage?: boolean; + onBlurWithStatus?: (evt, status: InputStatus) => void; + emptyToNull?: boolean; +} + +const validator = (value: string, validationRules: ValidationRule[]) => { + const errors = validationRules.reduce((acc, currRule) => { + if (!currRule.rule(value)) { + return acc.concat(currRule.errorMessage); + } + return acc; + }, []); + return errors.length > 0 ? errors : null; +}; + +export class Input extends PureComponent> { + state = { + error: null, + }; + + get status() { + const { error } = this.state; + if (error) { + return InputStatus.Invalid; + } + return InputStatus.Valid; + } + + onBlurWithValidation = evt => { + const { validationRules, onBlurWithStatus, onBlur } = this.props; + + let errors = null; + if (validationRules) { + errors = validator(evt.currentTarget.value, validationRules); + this.setState(prevState => { + return { + ...prevState, + error: errors ? errors[0] : null, + }; + }); + } + + if (onBlurWithStatus) { + onBlurWithStatus(evt, errors ? InputStatus.Invalid : InputStatus.Valid); + } + + if (onBlur) { + onBlur(evt); + } + }; + + render() { + const { + status, + validationRules, + onBlurWithStatus, + onBlur, + className, + hideErrorMessage, + emptyToNull, + ...restProps + } = this.props; + + const { error } = this.state; + + let inputClassName = 'gf-form-input'; + inputClassName = this.status === InputStatus.Invalid ? inputClassName + ' invalid' : inputClassName; + + return ( +
    + + {error && !hideErrorMessage && {error}} +
    + ); + } +} diff --git a/public/app/core/components/Form/Label.tsx b/public/app/core/components/Form/Label.tsx new file mode 100644 index 00000000000..385a1b325be --- /dev/null +++ b/public/app/core/components/Form/Label.tsx @@ -0,0 +1,19 @@ +import React, { PureComponent, ReactNode } from 'react'; + +interface Props { + children: ReactNode; + htmlFor?: string; + className?: string; +} + +export class Label extends PureComponent { + render() { + const { children, htmlFor, className } = this.props; + + return ( + + ); + } +} diff --git a/public/app/core/components/Form/index.ts b/public/app/core/components/Form/index.ts new file mode 100644 index 00000000000..e4c8197aaa9 --- /dev/null +++ b/public/app/core/components/Form/index.ts @@ -0,0 +1,3 @@ +export { Element } from './Element'; +export { Input } from './Input'; +export { Label } from './Label'; diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 2079aa39006..0150e80f1ed 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -159,3 +159,12 @@ export function describeTimeRange(range: RawTimeRange): string { return range.from.toString() + ' to ' + range.to.toString(); } + +export const isValidTimeSpan = (value: string) => { + if (value.indexOf('$') === 0 || value.indexOf('+$') === 0) { + return true; + } + + const info = describeTextRange(value); + return info.invalid !== true; +}; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 9a679832048..3c40c8a3568 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -8,6 +8,11 @@ import { DashboardModel } from '../dashboard_model'; import './../../panel/metrics_tab'; import config from 'app/core/config'; import { QueryInspector } from './QueryInspector'; +import { Switch } from 'app/core/components/Switch/Switch'; +import { Input } from 'app/core/components/Form'; +import { InputStatus } from 'app/core/components/Form/Input'; +import { isValidTimeSpan } from 'app/core/utils/rangeutil'; +import { ValidationRule } from 'app/types'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -29,6 +34,7 @@ interface Help { interface State { currentDatasource: DataSourceSelectItem; help: Help; + hideTimeOverride: boolean; } interface LoadingPlaceholderProps { @@ -36,6 +42,17 @@ interface LoadingPlaceholderProps { } const LoadingPlaceholder: SFC = ({ text }) =>

    {text}

    ; +const validationRules: ValidationRule[] = [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', + }, +]; export class QueriesTab extends PureComponent { element: any; @@ -53,6 +70,7 @@ export class QueriesTab extends PureComponent { isLoading: false, helpHtml: null, }, + hideTimeOverride: false, }; } @@ -215,9 +233,40 @@ export class QueriesTab extends PureComponent { return isLoading ? : helpHtml; }; + emptyToNull = (value: string) => { + return value === '' ? null : value; + }; + + onOverrideTime = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = this.emptyToNull(value); + if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { + panel.timeFrom = emptyToNullValue; + panel.refresh(); + } + }; + + onTimeShift = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = this.emptyToNull(value); + if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { + panel.timeShift = emptyToNullValue; + panel.refresh(); + } + }; + + onToggleTimeOverride = () => { + const { panel } = this.props; + panel.hideTimeOverride = !panel.hideTimeOverride; + panel.refresh(); + }; + render() { const { currentDatasource } = this.state; - + const hideTimeOverride = this.props.panel.hideTimeOverride; + console.log('hideTimeOverride', hideTimeOverride); const { hasQueryHelp, queryOptions } = currentDatasource.meta; const hasQueryOptions = !!queryOptions; const dsInformation = { @@ -256,7 +305,55 @@ export class QueriesTab extends PureComponent { return ( -
    (this.element = element)} style={{ width: '100%' }} /> + <> +
    (this.element = element)} style={{ width: '100%' }} /> + +
    Time Range
    + +
    +
    + + + + + Override relative time + Last + +
    + +
    + + + + Add time shift + Amount + +
    + +
    +
    + + + +
    + +
    +
    + ); } diff --git a/public/app/types/form.ts b/public/app/types/form.ts new file mode 100644 index 00000000000..180b41d8730 --- /dev/null +++ b/public/app/types/form.ts @@ -0,0 +1,4 @@ +export interface ValidationRule { + rule: (value: string) => boolean; + errorMessage: string; +} diff --git a/public/app/types/index.ts b/public/app/types/index.ts index bf19e52468b..e60dcb0993d 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -30,7 +30,7 @@ import { AppNotificationTimeout, } from './appNotifications'; import { DashboardSearchHit } from './search'; - +import { ValidationRule } from './form'; export { Team, TeamsState, @@ -89,6 +89,7 @@ export { AppNotificationTimeout, DashboardSearchHit, UserState, + ValidationRule, }; export interface StoreState { diff --git a/public/sass/utils/_validation.scss b/public/sass/utils/_validation.scss index 86b7c008bfd..657d1f0414b 100644 --- a/public/sass/utils/_validation.scss +++ b/public/sass/utils/_validation.scss @@ -1,7 +1,11 @@ -input[type="text"].ng-dirty.ng-invalid { +input[type='text'].ng-dirty.ng-invalid { } input.validation-error, input.ng-dirty.ng-invalid { box-shadow: inset 0 0px 5px $red; } + +input.invalid { + box-shadow: inset 0 0px 5px $red; +} From a8e184c02572ecb06fcc622d490d5bce7440eaa7 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 10:16:04 +0100 Subject: [PATCH 71/81] react-panel: Clean up input validation and increase code readability --- public/app/core/components/Form/Input.tsx | 94 +++++++++---------- public/app/core/utils/validate.ts | 11 +++ .../dashboard/dashgrid/QueriesTab.tsx | 35 +++---- public/app/types/form.ts | 6 +- public/app/types/index.ts | 3 +- 5 files changed, 79 insertions(+), 70 deletions(-) create mode 100644 public/app/core/utils/validate.ts diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index a261203b3f3..315acbd645c 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -1,9 +1,8 @@ import React, { PureComponent } from 'react'; -import { ValidationRule } from 'app/types'; +import { ValidationEvents, ValidationRule } from 'app/types'; +import { validate } from 'app/core/utils/validate'; export enum InputStatus { - Default = 'default', - Loading = 'loading', Invalid = 'invalid', Valid = 'valid', } @@ -15,80 +14,71 @@ export enum InputTypes { Email = 'email', } -interface Props { - status?: InputStatus; - validationRules: ValidationRule[]; - hideErrorMessage?: boolean; - onBlurWithStatus?: (evt, status: InputStatus) => void; - emptyToNull?: boolean; +export enum EventsWithValidation { + onBlur = 'onBlur', + onFocus = 'onFocus', + onChange = 'onChange', } -const validator = (value: string, validationRules: ValidationRule[]) => { - const errors = validationRules.reduce((acc, currRule) => { - if (!currRule.rule(value)) { - return acc.concat(currRule.errorMessage); - } - return acc; - }, []); - return errors.length > 0 ? errors : null; -}; +interface Props extends React.HTMLProps { + validationEvents: ValidationEvents; + hideErrorMessage?: boolean; -export class Input extends PureComponent> { + // Override event props and append status as argument + onBlur?: (event: React.FocusEvent, status?: InputStatus) => void; + onFocus?: (event: React.FocusEvent, status?: InputStatus) => void; + onChange?: (event: React.FormEvent, status?: InputStatus) => void; +} + +export class Input extends PureComponent { state = { error: null, }; get status() { - const { error } = this.state; - if (error) { - return InputStatus.Invalid; - } - return InputStatus.Valid; + return this.state.error ? InputStatus.Invalid : InputStatus.Valid; } - onBlurWithValidation = evt => { - const { validationRules, onBlurWithStatus, onBlur } = this.props; + get isInvalid() { + return this.status === InputStatus.Invalid; + } - let errors = null; - if (validationRules) { - errors = validator(evt.currentTarget.value, validationRules); + validatorAsync = (validationRules: ValidationRule[]) => { + return evt => { + const errors = validate(evt.currentTarget.value, validationRules); this.setState(prevState => { return { ...prevState, error: errors ? errors[0] : null, }; }); - } + }; + }; - if (onBlurWithStatus) { - onBlurWithStatus(evt, errors ? InputStatus.Invalid : InputStatus.Valid); - } - - if (onBlur) { - onBlur(evt); - } + populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => { + const inputElementProps = { ...restProps }; + Object.keys(EventsWithValidation).forEach(eventName => { + inputElementProps[eventName] = async evt => { + if (validationEvents[eventName]) { + await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); + } + if (restProps[eventName]) { + restProps[eventName].apply(null, [evt, this.status]); + } + }; + }); + return inputElementProps; }; render() { - const { - status, - validationRules, - onBlurWithStatus, - onBlur, - className, - hideErrorMessage, - emptyToNull, - ...restProps - } = this.props; - + const { validationEvents, className, hideErrorMessage, ...restProps } = this.props; const { error } = this.state; - - let inputClassName = 'gf-form-input'; - inputClassName = this.status === InputStatus.Invalid ? inputClassName + ' invalid' : inputClassName; + const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : ''); + const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents); return (
    - + {error && !hideErrorMessage && {error}}
    ); diff --git a/public/app/core/utils/validate.ts b/public/app/core/utils/validate.ts new file mode 100644 index 00000000000..34f8125833f --- /dev/null +++ b/public/app/core/utils/validate.ts @@ -0,0 +1,11 @@ +import { ValidationRule } from 'app/types'; + +export const validate = (value: string, validationRules: ValidationRule[]) => { + const errors = validationRules.reduce((acc, currRule) => { + if (!currRule.rule(value)) { + return acc.concat(currRule.errorMessage); + } + return acc; + }, []); + return errors.length > 0 ? errors : null; +}; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 3c40c8a3568..016299f574e 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -10,9 +10,9 @@ import config from 'app/core/config'; import { QueryInspector } from './QueryInspector'; import { Switch } from 'app/core/components/Switch/Switch'; import { Input } from 'app/core/components/Form'; -import { InputStatus } from 'app/core/components/Form/Input'; +import { InputStatus, EventsWithValidation } from 'app/core/components/Form/Input'; import { isValidTimeSpan } from 'app/core/utils/rangeutil'; -import { ValidationRule } from 'app/types'; +import { ValidationEvents } from 'app/types'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -42,17 +42,20 @@ interface LoadingPlaceholderProps { } const LoadingPlaceholder: SFC = ({ text }) =>

    {text}

    ; -const validationRules: ValidationRule[] = [ - { - rule: value => { - if (!value) { - return true; - } - return isValidTimeSpan(value); + +const timeRangeValidationEvents: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', }, - errorMessage: 'Not a valid timespan', - }, -]; + ], +}; export class QueriesTab extends PureComponent { element: any; @@ -322,8 +325,8 @@ export class QueriesTab extends PureComponent { type="text" className="gf-form-input max-width-8" placeholder="1h" - onBlurWithStatus={this.onOverrideTime} - validationRules={validationRules} + onBlur={this.onOverrideTime} + validationEvents={timeRangeValidationEvents} hideErrorMessage={true} />
    @@ -338,8 +341,8 @@ export class QueriesTab extends PureComponent { type="text" className="gf-form-input max-width-8" placeholder="1h" - onBlurWithStatus={this.onTimeShift} - validationRules={validationRules} + onBlur={this.onTimeShift} + validationEvents={timeRangeValidationEvents} hideErrorMessage={true} />
    diff --git a/public/app/types/form.ts b/public/app/types/form.ts index 180b41d8730..95026c30be9 100644 --- a/public/app/types/form.ts +++ b/public/app/types/form.ts @@ -1,4 +1,8 @@ export interface ValidationRule { - rule: (value: string) => boolean; + rule: (valueToValidate: string) => boolean; errorMessage: string; } + +export interface ValidationEvents { + [eventName: string]: ValidationRule[]; +} diff --git a/public/app/types/index.ts b/public/app/types/index.ts index e60dcb0993d..1a56fad7bf0 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -30,7 +30,7 @@ import { AppNotificationTimeout, } from './appNotifications'; import { DashboardSearchHit } from './search'; -import { ValidationRule } from './form'; +import { ValidationEvents, ValidationRule } from './form'; export { Team, TeamsState, @@ -89,6 +89,7 @@ export { AppNotificationTimeout, DashboardSearchHit, UserState, + ValidationEvents, ValidationRule, }; From c722ea4f768220969867d979d6bf3feb8ea37a63 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 11:04:56 +0100 Subject: [PATCH 72/81] react-panel: Input validation should be optional --- public/app/core/components/Form/Input.tsx | 24 ++++++++++++----------- public/app/core/utils/validate.ts | 7 ++++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index 315acbd645c..e815336edbe 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { ValidationEvents, ValidationRule } from 'app/types'; -import { validate } from 'app/core/utils/validate'; +import { validate, hasValidationEvent } from 'app/core/utils/validate'; export enum InputStatus { Invalid = 'invalid', @@ -21,7 +21,7 @@ export enum EventsWithValidation { } interface Props extends React.HTMLProps { - validationEvents: ValidationEvents; + validationEvents?: ValidationEvents; hideErrorMessage?: boolean; // Override event props and append status as argument @@ -57,15 +57,17 @@ export class Input extends PureComponent { populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => { const inputElementProps = { ...restProps }; - Object.keys(EventsWithValidation).forEach(eventName => { - inputElementProps[eventName] = async evt => { - if (validationEvents[eventName]) { - await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); - } - if (restProps[eventName]) { - restProps[eventName].apply(null, [evt, this.status]); - } - }; + Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => { + if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) { + inputElementProps[eventName] = async evt => { + if (hasValidationEvent(eventName, validationEvents)) { + await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); + } + if (restProps[eventName]) { + restProps[eventName].apply(null, [evt, this.status]); + } + }; + } }); return inputElementProps; }; diff --git a/public/app/core/utils/validate.ts b/public/app/core/utils/validate.ts index 34f8125833f..c6663882808 100644 --- a/public/app/core/utils/validate.ts +++ b/public/app/core/utils/validate.ts @@ -1,4 +1,5 @@ -import { ValidationRule } from 'app/types'; +import { ValidationRule, ValidationEvents } from 'app/types'; +import { EventsWithValidation } from 'app/core/components/Form/Input'; export const validate = (value: string, validationRules: ValidationRule[]) => { const errors = validationRules.reduce((acc, currRule) => { @@ -9,3 +10,7 @@ export const validate = (value: string, validationRules: ValidationRule[]) => { }, []); return errors.length > 0 ? errors : null; }; + +export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents) => { + return validationEvents && validationEvents[event]; +}; From 58da6e8c267b63e17cde977c20d2423b064cf90d Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 12:26:45 +0100 Subject: [PATCH 73/81] react-panel: Add test for Input with validation on blur --- .../app/core/components/Form/Input.test.tsx | 53 +++++++++++++++++++ public/app/core/components/Form/Input.tsx | 3 +- .../Form/__snapshots__/Input.test.tsx.snap | 11 ++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 public/app/core/components/Form/Input.test.tsx create mode 100644 public/app/core/components/Form/__snapshots__/Input.test.tsx.snap diff --git a/public/app/core/components/Form/Input.test.tsx b/public/app/core/components/Form/Input.test.tsx new file mode 100644 index 00000000000..9e903208e80 --- /dev/null +++ b/public/app/core/components/Form/Input.test.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { Input, EventsWithValidation } from './Input'; +import { ValidationEvents } from 'app/types'; + +const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars'; +const testBlurValidation: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: (value: string) => { + if (!value || value.length < 3) { + return true; + } + return false; + }, + errorMessage: TEST_ERROR_MESSAGE, + }, + ], +}; + +describe('Input', () => { + it('renders correctly', () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it('should validate with error onBlur', () => { + const wrapper = shallow(); + const evt = { + persist: jest.fn, + target: { + value: 'I can not be more than 2 chars', + }, + }; + + wrapper.find('input').simulate('blur', evt); + expect(wrapper.state('error')).toBe(TEST_ERROR_MESSAGE); + }); + + it('should validate without error onBlur', () => { + const wrapper = shallow(); + const evt = { + persist: jest.fn, + target: { + value: 'Hi', + }, + }; + + wrapper.find('input').simulate('blur', evt); + expect(wrapper.state('error')).toBe(null); + }); +}); diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index e815336edbe..6ba58b45e91 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -45,7 +45,7 @@ export class Input extends PureComponent { validatorAsync = (validationRules: ValidationRule[]) => { return evt => { - const errors = validate(evt.currentTarget.value, validationRules); + const errors = validate(evt.target.value, validationRules); this.setState(prevState => { return { ...prevState, @@ -60,6 +60,7 @@ export class Input extends PureComponent { Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => { if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) { inputElementProps[eventName] = async evt => { + evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling if (hasValidationEvent(eventName, validationEvents)) { await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); } diff --git a/public/app/core/components/Form/__snapshots__/Input.test.tsx.snap b/public/app/core/components/Form/__snapshots__/Input.test.tsx.snap new file mode 100644 index 00000000000..249d6b0be8d --- /dev/null +++ b/public/app/core/components/Form/__snapshots__/Input.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Input renders correctly 1`] = ` +
    + +
    +`; From c40bfb8eda332c985e0fe7e9aece961d5b8d657d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 30 Nov 2018 12:49:05 +0100 Subject: [PATCH 74/81] make sure target obj is not destructured so that angular copy of objected can be mutated --- public/app/features/explore/Explore.tsx | 7 +------ public/app/features/explore/QueryEditor.tsx | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 1c609897995..26884ec58dc 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -541,12 +541,7 @@ export class Explore extends React.PureComponent { const { datasource, range } = this.state; const { interval, intervalMs } = getIntervals(range, datasource, this.el.offsetWidth); - const configuredQueries = [ - { - ...queryOptions, - ...query, - }, - ]; + const configuredQueries = [Object.assign(query, queryOptions)]; // Clone range for query request // const queryRange: RawTimeRange = { ...range }; diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 0a4c0b78c3c..6fc1106576c 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -36,7 +36,7 @@ export default class QueryEditor extends PureComponent { target, ctrl: { refresh: () => { - this.props.onQueryChange({ refId: initialQuery.refId, ...target }, false); + this.props.onQueryChange(target, false); this.props.onExecuteQuery(); }, events: exploreEvents, From d02839d2d189e42b572bfdea13e2031801a6c93a Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 13:36:53 +0100 Subject: [PATCH 75/81] react-panel: Move time range options to its own component and render it under the options button instead --- .../dashboard/dashgrid/QueriesTab.tsx | 107 ++--------------- .../dashboard/dashgrid/TimeRangeOptions.tsx | 111 ++++++++++++++++++ 2 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 016299f574e..8cef54272d0 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -8,11 +8,7 @@ import { DashboardModel } from '../dashboard_model'; import './../../panel/metrics_tab'; import config from 'app/core/config'; import { QueryInspector } from './QueryInspector'; -import { Switch } from 'app/core/components/Switch/Switch'; -import { Input } from 'app/core/components/Form'; -import { InputStatus, EventsWithValidation } from 'app/core/components/Form/Input'; -import { isValidTimeSpan } from 'app/core/utils/rangeutil'; -import { ValidationEvents } from 'app/types'; +import { TimeRangeOptions } from './TimeRangeOptions'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -43,20 +39,6 @@ interface LoadingPlaceholderProps { const LoadingPlaceholder: SFC = ({ text }) =>

    {text}

    ; -const timeRangeValidationEvents: ValidationEvents = { - [EventsWithValidation.onBlur]: [ - { - rule: value => { - if (!value) { - return true; - } - return isValidTimeSpan(value); - }, - errorMessage: 'Not a valid timespan', - }, - ], -}; - export class QueriesTab extends PureComponent { element: any; component: AngularComponent; @@ -220,10 +202,17 @@ export class QueriesTab extends PureComponent { }, }; - return Object.keys(queryOptions).map(key => { + const dsOptions = Object.keys(queryOptions).map(key => { const options = allOptions[key]; return ; }); + + return ( + <> + + {dsOptions} + + ); }; renderQueryInspector = () => { @@ -236,40 +225,8 @@ export class QueriesTab extends PureComponent { return isLoading ? : helpHtml; }; - emptyToNull = (value: string) => { - return value === '' ? null : value; - }; - - onOverrideTime = (evt, status: InputStatus) => { - const { value } = evt.target; - const { panel } = this.props; - const emptyToNullValue = this.emptyToNull(value); - if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { - panel.timeFrom = emptyToNullValue; - panel.refresh(); - } - }; - - onTimeShift = (evt, status: InputStatus) => { - const { value } = evt.target; - const { panel } = this.props; - const emptyToNullValue = this.emptyToNull(value); - if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { - panel.timeShift = emptyToNullValue; - panel.refresh(); - } - }; - - onToggleTimeOverride = () => { - const { panel } = this.props; - panel.hideTimeOverride = !panel.hideTimeOverride; - panel.refresh(); - }; - render() { const { currentDatasource } = this.state; - const hideTimeOverride = this.props.panel.hideTimeOverride; - console.log('hideTimeOverride', hideTimeOverride); const { hasQueryHelp, queryOptions } = currentDatasource.meta; const hasQueryOptions = !!queryOptions; const dsInformation = { @@ -310,52 +267,6 @@ export class QueriesTab extends PureComponent { <>
    (this.element = element)} style={{ width: '100%' }} /> - -
    Time Range
    - -
    -
    - - - - - Override relative time - Last - -
    - -
    - - - - Add time shift - Amount - -
    - -
    -
    - - - -
    - -
    -
    ); diff --git a/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx b/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx new file mode 100644 index 00000000000..8c6830cf1db --- /dev/null +++ b/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx @@ -0,0 +1,111 @@ +import React, { PureComponent } from 'react'; +import { Switch } from 'app/core/components/Switch/Switch'; +import { Input } from 'app/core/components/Form'; +import { isValidTimeSpan } from 'app/core/utils/rangeutil'; +import { ValidationEvents } from 'app/types'; +import { EventsWithValidation } from 'app/core/components/Form/Input'; +import { PanelModel } from '../panel_model'; +import { InputStatus } from 'app/core/components/Form/Input'; + +const timeRangeValidationEvents: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', + }, + ], +}; + +const emptyToNull = (value: string) => { + return value === '' ? null : value; +}; + +interface Props { + panel: PanelModel; +} + +export class TimeRangeOptions extends PureComponent { + onOverrideTime = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = emptyToNull(value); + if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { + panel.timeFrom = emptyToNullValue; + panel.refresh(); + } + }; + + onTimeShift = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = emptyToNull(value); + if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { + panel.timeShift = emptyToNullValue; + panel.refresh(); + } + }; + + onToggleTimeOverride = () => { + const { panel } = this.props; + panel.hideTimeOverride = !panel.hideTimeOverride; + panel.refresh(); + }; + + render = () => { + const hideTimeOverride = this.props.panel.hideTimeOverride; + return ( + <> +
    Time Range
    + +
    +
    + + + + + Override relative time + Last + +
    + +
    + + + + Add time shift + Amount + +
    + +
    +
    + + + +
    + +
    +
    + + ); + }; +} From 363425d55e1fa6faa8c6f83a290b689c871382cd Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 13:39:06 +0100 Subject: [PATCH 76/81] react-panel: Options button should always be enabled now when Time Range-options are there --- public/app/features/dashboard/dashgrid/QueriesTab.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 8cef54272d0..7e909b06508 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -227,8 +227,7 @@ export class QueriesTab extends PureComponent { render() { const { currentDatasource } = this.state; - const { hasQueryHelp, queryOptions } = currentDatasource.meta; - const hasQueryOptions = !!queryOptions; + const { hasQueryHelp } = currentDatasource.meta; const dsInformation = { title: currentDatasource.name, imgSrc: currentDatasource.meta.info.logos.small, @@ -259,7 +258,7 @@ export class QueriesTab extends PureComponent { const options = { title: '', icon: 'fa fa-cog', - disabled: !hasQueryOptions, + disabled: false, render: this.renderOptions, }; From 0b65558c21abc9260c6f1617865a8eb3b122291a Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 13:42:49 +0100 Subject: [PATCH 77/81] react-panel: Add nullcheck to prevent error on datasources without meta options --- public/app/features/dashboard/dashgrid/QueriesTab.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 7e909b06508..f202aca4277 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -202,10 +202,12 @@ export class QueriesTab extends PureComponent { }, }; - const dsOptions = Object.keys(queryOptions).map(key => { - const options = allOptions[key]; - return ; - }); + const dsOptions = queryOptions + ? Object.keys(queryOptions).map(key => { + const options = allOptions[key]; + return ; + }) + : null; return ( <> From 6c09373995330f8da72f13a4a56ffa9448f9964d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 30 Nov 2018 04:45:25 -0800 Subject: [PATCH 78/81] fix: minor style changes, removed hover scale increase --- public/app/features/dashboard/dashgrid/DataSourcePicker.tsx | 4 ---- public/sass/_variables.dark.scss | 2 +- public/sass/components/_panel_editor.scss | 4 ---- public/sass/pages/_dashboard.scss | 1 + 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx index 4ac128de39d..9a3923a09f2 100644 --- a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx +++ b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx @@ -79,10 +79,6 @@ export class DataSourcePicker extends PureComponent { /> -
    - - -
    ); } diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index b57be74e6d7..e12522d0a10 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -79,7 +79,7 @@ $brand-gradient: linear-gradient( ); $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px); -$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 00px, #090909 60%); +$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909); // Links // ------------------------- diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 21338f25282..333d41a0f35 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -159,8 +159,6 @@ transition: transform 1 ease; &:hover { - //background: $card-background-hover; - transform: scale(1.05); box-shadow: $panel-editor-viz-item-shadow-hover; background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; @@ -171,7 +169,6 @@ border: 1px solid $orange; &:hover { - transform: scale(1.05); box-shadow: 0 0 6px $orange; border: 1px solid $orange; background: $panel-editor-viz-item-bg-hover-active; @@ -269,7 +266,6 @@ &:hover { background: $card-background-hover; - transform: scaleY(1.1); box-shadow: $panel-editor-viz-item-shadow-hover; background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 353aee55a09..dbbfe48c828 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -25,6 +25,7 @@ div.flot-text { bottom: 0; right: 0; margin: 0; + .panel-container { border: none; z-index: $zindex-sidemenu + 1; From be85944d846d36758a240ff1b682f595b41dfe0d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 30 Nov 2018 13:51:49 +0100 Subject: [PATCH 79/81] add an error alert component that will be displayed when there was an error loading ds in explore --- public/app/features/explore/Error.tsx | 21 +++++++++++++++++++++ public/app/features/explore/Explore.tsx | 11 ++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 public/app/features/explore/Error.tsx diff --git a/public/app/features/explore/Error.tsx b/public/app/features/explore/Error.tsx new file mode 100644 index 00000000000..2bfd366b3c9 --- /dev/null +++ b/public/app/features/explore/Error.tsx @@ -0,0 +1,21 @@ +import React, { SFC } from 'react'; + +interface Props { + message: any; +} + +export const Alert: SFC = props => { + const { message } = props; + return ( +
    +
    +
    + +
    +
    +
    {message}
    +
    +
    +
    + ); +}; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 26884ec58dc..345ccd472ce 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -40,6 +40,7 @@ import Logs from './Logs'; import Table from './Table'; import ErrorBoundary from './ErrorBoundary'; import TimePicker from './TimePicker'; +import { Alert } from './Error'; interface ExploreProps { datasourceSrv: DatasourceSrv; @@ -851,16 +852,16 @@ export class Explore extends React.PureComponent {
    - {datasourceLoading ?
    Loading datasource...
    : null} - {datasourceMissing ? (
    Please add a datasource that supports Explore (e.g., Prometheus).
    ) : null} - {datasourceError ? ( -
    Error connecting to datasource. [{datasourceError}]
    - ) : null} + {datasourceError && ( +
    + +
    + )} {datasource && !datasourceError ? (
    From 3c556c483302b1bb87bef6caa82634320f17a67f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 30 Nov 2018 13:53:39 +0100 Subject: [PATCH 80/81] never load fallback query field. remove commented code --- public/app/features/explore/QueryRows.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 36d1db8f546..45a8c48ca22 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -90,9 +90,7 @@ class QueryRow extends PureComponent { const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); const queryError = transactionWithError ? transactionWithError.error : null; - // const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField; const QueryField = datasource.pluginExports.ExploreQueryField; - // const QueryEditor = datasource.pluginExports.QueryCtrl; return (
    From 1db3885283441c9928ddba0bb237bdc7fec78e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 30 Nov 2018 05:14:23 -0800 Subject: [PATCH 81/81] style changes for panel placeholder (move and resize) effect --- public/sass/_variables.dark.scss | 3 +++ public/sass/_variables.light.scss | 3 +++ public/sass/components/_dashboard_grid.scss | 7 +++++++ public/sass/components/_panel_editor.scss | 3 +-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index e12522d0a10..53a570fe1fa 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -383,3 +383,6 @@ $panel-editor-viz-item-bg: $black; $panel-editor-tabs-line-color: #e3e3e3; $panel-editor-viz-item-bg-hover: darken($blue, 47%); $panel-editor-viz-item-bg-hover-active: darken($orange, 45%); + +$panel-grid-placeholder-bg: darken($blue, 47%); +$panel-grid-placeholder-shadow: 0 0 4px $blue; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 16cd96e3fc4..73f5c8c1252 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -393,3 +393,6 @@ $panel-editor-viz-item-bg: $white; $panel-editor-tabs-line-color: $dark-5; $panel-editor-viz-item-bg-hover: lighten($blue, 62%); $panel-editor-viz-item-bg-hover-active: lighten($orange, 34%); + +$panel-grid-placeholder-bg: lighten($blue, 62%); +$panel-grid-placeholder-shadow: 0 0 4px $blue-light; diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss index 1370bd96709..95b1448d826 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -57,6 +57,13 @@ } } +.react-grid-item.react-grid-placeholder { + box-shadow: $panel-grid-placeholder-shadow; + background: $panel-grid-placeholder-bg; + z-index: 0; + opacity: unset; +} + .theme-dark { .react-grid-item > .react-resizable-handle::after { border-right: 2px solid $gray-1; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 333d41a0f35..8d5e52fa60b 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -265,10 +265,9 @@ height: 44px; &:hover { - background: $card-background-hover; - box-shadow: $panel-editor-viz-item-shadow-hover; background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; + box-shadow: $panel-editor-viz-item-shadow-hover; } &--selected {