Преглед изворни кода

Merge pull request #13971 from grafana/react-panel-options

React panel options
Torkel Ödegaard пре 7 година
родитељ
комит
eae754a0c6

+ 23 - 2
public/app/features/dashboard/dashgrid/PanelChrome.tsx

@@ -21,6 +21,7 @@ export interface Props {
 
 
 export interface State {
 export interface State {
   refreshCounter: number;
   refreshCounter: number;
+  renderCounter: number;
   timeRange?: TimeRange;
   timeRange?: TimeRange;
 }
 }
 
 
@@ -30,11 +31,13 @@ export class PanelChrome extends PureComponent<Props, State> {
 
 
     this.state = {
     this.state = {
       refreshCounter: 0,
       refreshCounter: 0,
+      renderCounter: 0,
     };
     };
   }
   }
 
 
   componentDidMount() {
   componentDidMount() {
     this.props.panel.events.on('refresh', this.onRefresh);
     this.props.panel.events.on('refresh', this.onRefresh);
+    this.props.panel.events.on('render', this.onRender);
     this.props.dashboard.panelInitialized(this.props.panel);
     this.props.dashboard.panelInitialized(this.props.panel);
   }
   }
 
 
@@ -52,6 +55,13 @@ export class PanelChrome extends PureComponent<Props, State> {
     });
     });
   };
   };
 
 
+  onRender = () => {
+    console.log('onRender');
+    this.setState({
+      renderCounter: this.state.renderCounter + 1,
+    });
+  };
+
   get isVisible() {
   get isVisible() {
     return !this.props.dashboard.otherPanelInFullscreen(this.props.panel);
     return !this.props.dashboard.otherPanelInFullscreen(this.props.panel);
   }
   }
@@ -59,9 +69,11 @@ export class PanelChrome extends PureComponent<Props, State> {
   render() {
   render() {
     const { panel, dashboard } = this.props;
     const { panel, dashboard } = this.props;
     const { datasource, targets } = panel;
     const { datasource, targets } = panel;
-    const { refreshCounter, timeRange } = this.state;
+    const { timeRange, renderCounter, refreshCounter } = this.state;
     const PanelComponent = this.props.component;
     const PanelComponent = this.props.component;
 
 
+    console.log('Panel chrome render');
+
     return (
     return (
       <div className="panel-container">
       <div className="panel-container">
         <PanelHeader panel={panel} dashboard={dashboard} />
         <PanelHeader panel={panel} dashboard={dashboard} />
@@ -74,7 +86,16 @@ export class PanelChrome extends PureComponent<Props, State> {
             refreshCounter={refreshCounter}
             refreshCounter={refreshCounter}
           >
           >
             {({ loading, timeSeries }) => {
             {({ loading, timeSeries }) => {
-              return <PanelComponent loading={loading} timeSeries={timeSeries} timeRange={timeRange} />;
+              console.log('panelcrome inner render');
+              return (
+                <PanelComponent
+                  loading={loading}
+                  timeSeries={timeSeries}
+                  timeRange={timeRange}
+                  options={panel.getOptions()}
+                  renderCounter={renderCounter}
+                />
+              );
             }}
             }}
           </DataPanel>
           </DataPanel>
         </div>
         </div>

+ 19 - 10
public/app/features/dashboard/dashgrid/PanelEditor.tsx

@@ -1,13 +1,16 @@
-import React from 'react';
+import React, { PureComponent } from 'react';
 import classNames from 'classnames';
 import classNames from 'classnames';
-import { PanelModel } from '../panel_model';
-import { DashboardModel } from '../dashboard_model';
-import { store } from 'app/store/configureStore';
+
 import { QueriesTab } from './QueriesTab';
 import { QueriesTab } from './QueriesTab';
-import { PanelPlugin, PluginExports } from 'app/types/plugins';
 import { VizTypePicker } from './VizTypePicker';
 import { VizTypePicker } from './VizTypePicker';
+
+import { store } from 'app/store/configureStore';
 import { updateLocation } from 'app/core/actions';
 import { updateLocation } from 'app/core/actions';
 
 
+import { PanelModel } from '../panel_model';
+import { DashboardModel } from '../dashboard_model';
+import { PanelPlugin, PluginExports } from 'app/types/plugins';
+
 interface PanelEditorProps {
 interface PanelEditorProps {
   panel: PanelModel;
   panel: PanelModel;
   dashboard: DashboardModel;
   dashboard: DashboardModel;
@@ -22,7 +25,7 @@ interface PanelEditorTab {
   icon: string;
   icon: string;
 }
 }
 
 
-export class PanelEditor extends React.Component<PanelEditorProps, any> {
+export class PanelEditor extends PureComponent<PanelEditorProps> {
   tabs: PanelEditorTab[];
   tabs: PanelEditorTab[];
 
 
   constructor(props) {
   constructor(props) {
@@ -39,16 +42,21 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> {
   }
   }
 
 
   renderPanelOptions() {
   renderPanelOptions() {
-    const { pluginExports } = this.props;
+    const { pluginExports, panel } = this.props;
 
 
-    if (pluginExports.PanelOptions) {
-      const PanelOptions = pluginExports.PanelOptions;
-      return <PanelOptions />;
+    if (pluginExports.PanelOptionsComponent) {
+      const OptionsComponent = pluginExports.PanelOptionsComponent;
+      return <OptionsComponent options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
     } else {
     } else {
       return <p>Visualization has no options</p>;
       return <p>Visualization has no options</p>;
     }
     }
   }
   }
 
 
+  onPanelOptionsChanged = (options: any) => {
+    this.props.panel.updateOptions(options);
+    this.forceUpdate();
+  };
+
   renderVizTab() {
   renderVizTab() {
     return (
     return (
       <div className="viz-editor">
       <div className="viz-editor">
@@ -70,6 +78,7 @@ export class PanelEditor extends React.Component<PanelEditorProps, any> {
         partial: true,
         partial: true,
       })
       })
     );
     );
+    this.forceUpdate();
   };
   };
 
 
   render() {
   render() {

+ 15 - 4
public/app/features/dashboard/panel_model.ts

@@ -60,6 +60,21 @@ export class PanelModel {
     _.defaultsDeep(this, _.cloneDeep(defaults));
     _.defaultsDeep(this, _.cloneDeep(defaults));
   }
   }
 
 
+  getOptions() {
+    return this[this.getOptionsKey()] || {};
+  }
+
+  updateOptions(options: object) {
+    const update: any = {};
+    update[this.getOptionsKey()] = options;
+    Object.assign(this, update);
+    this.render();
+  }
+
+  private getOptionsKey() {
+    return this.type + 'Options';
+  }
+
   getSaveModel() {
   getSaveModel() {
     const model: any = {};
     const model: any = {};
     for (const property in this) {
     for (const property in this) {
@@ -121,10 +136,6 @@ export class PanelModel {
     this.events.emit('panel-initialized');
     this.events.emit('panel-initialized');
   }
   }
 
 
-  initEditMode() {
-    this.events.emit('panel-init-edit-mode');
-  }
-
   changeType(pluginId: string) {
   changeType(pluginId: string) {
     this.type = pluginId;
     this.type = pluginId;
 
 

+ 1 - 1
public/app/features/dashboard/settings/settings.ts

@@ -32,9 +32,9 @@ export class SettingsCtrl {
 
 
     this.$scope.$on('$destroy', () => {
     this.$scope.$on('$destroy', () => {
       this.dashboard.updateSubmenuVisibility();
       this.dashboard.updateSubmenuVisibility();
-      this.dashboard.startRefresh();
       setTimeout(() => {
       setTimeout(() => {
         this.$rootScope.appEvent('dash-scroll', { restore: true });
         this.$rootScope.appEvent('dash-scroll', { restore: true });
+        this.dashboard.startRefresh();
       });
       });
     });
     });
 
 

+ 39 - 15
public/app/plugins/panel/graph2/module.tsx

@@ -1,23 +1,22 @@
-// Libraries
 import _ from 'lodash';
 import _ from 'lodash';
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 
 
-// Components
 import Graph from 'app/viz/Graph';
 import Graph from 'app/viz/Graph';
-import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
 import { Switch } from 'app/core/components/Switch/Switch';
 import { Switch } from 'app/core/components/Switch/Switch';
 
 
-// Types
-import { PanelProps, NullValueMode } from 'app/types';
+import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
+import { PanelProps, PanelOptionsProps, NullValueMode } from 'app/types';
 
 
 interface Options {
 interface Options {
   showBars: boolean;
   showBars: boolean;
-}
+  showLines: boolean;
+  showPoints: boolean;
 
 
-interface Props extends PanelProps {
-  options: Options;
+  onChange: (options: Options) => void;
 }
 }
 
 
+interface Props extends PanelProps<Options> {}
+
 export class Graph2 extends PureComponent<Props> {
 export class Graph2 extends PureComponent<Props> {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
@@ -25,27 +24,52 @@ export class Graph2 extends PureComponent<Props> {
 
 
   render() {
   render() {
     const { timeSeries, timeRange } = this.props;
     const { timeSeries, timeRange } = this.props;
+    const { showLines, showBars, showPoints } = this.props.options;
 
 
     const vmSeries = getTimeSeriesVMs({
     const vmSeries = getTimeSeriesVMs({
       timeSeries: timeSeries,
       timeSeries: timeSeries,
       nullValueMode: NullValueMode.Ignore,
       nullValueMode: NullValueMode.Ignore,
     });
     });
 
 
-    return <Graph timeSeries={vmSeries} timeRange={timeRange} />;
+    return (
+      <Graph
+        timeSeries={vmSeries}
+        timeRange={timeRange}
+        showLines={showLines}
+        showPoints={showPoints}
+        showBars={showBars}
+      />
+    );
   }
   }
 }
 }
 
 
-export class TextOptions extends PureComponent<any> {
-  onChange = () => {};
+export class GraphOptions extends PureComponent<PanelOptionsProps<Options>> {
+  onToggleLines = () => {
+    this.props.onChange({ ...this.props.options, showLines: !this.props.options.showLines });
+  };
+
+  onToggleBars = () => {
+    this.props.onChange({ ...this.props.options, showBars: !this.props.options.showBars });
+  };
+
+  onTogglePoints = () => {
+    this.props.onChange({ ...this.props.options, showPoints: !this.props.options.showPoints });
+  };
 
 
   render() {
   render() {
+    const { showBars, showPoints, showLines } = this.props.options;
+
     return (
     return (
-      <div className="section gf-form-group">
-        <h5 className="section-heading">Draw Modes</h5>
-        <Switch label="Lines" checked={true} onChange={this.onChange} />
+      <div>
+        <div className="section gf-form-group">
+          <h5 className="page-heading">Draw Modes</h5>
+          <Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
+          <Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
+          <Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
+        </div>
       </div>
       </div>
     );
     );
   }
   }
 }
 }
 
 
-export { Graph2 as PanelComponent, TextOptions as PanelOptions };
+export { Graph2 as PanelComponent, GraphOptions as PanelOptionsComponent };

+ 2 - 1
public/app/types/index.ts

@@ -20,7 +20,7 @@ import {
   DataQueryResponse,
   DataQueryResponse,
   DataQueryOptions,
   DataQueryOptions,
 } from './series';
 } from './series';
-import { PanelProps } from './panel';
+import { PanelProps, PanelOptionsProps } from './panel';
 import { PluginDashboard, PluginMeta, Plugin, PluginsState } from './plugins';
 import { PluginDashboard, PluginMeta, Plugin, PluginsState } from './plugins';
 import { Organization, OrganizationPreferences, OrganizationState } from './organization';
 import { Organization, OrganizationPreferences, OrganizationState } from './organization';
 import {
 import {
@@ -69,6 +69,7 @@ export {
   TimeRange,
   TimeRange,
   LoadingState,
   LoadingState,
   PanelProps,
   PanelProps,
+  PanelOptionsProps,
   TimeSeries,
   TimeSeries,
   TimeSeriesVM,
   TimeSeriesVM,
   TimeSeriesVMs,
   TimeSeriesVMs,

+ 8 - 1
public/app/types/panel.ts

@@ -1,7 +1,14 @@
 import { LoadingState, TimeSeries, TimeRange } from './series';
 import { LoadingState, TimeSeries, TimeRange } from './series';
 
 
-export interface PanelProps {
+export interface PanelProps<T = any> {
   timeSeries: TimeSeries[];
   timeSeries: TimeSeries[];
   timeRange: TimeRange;
   timeRange: TimeRange;
   loading: LoadingState;
   loading: LoadingState;
+  options: T;
+  renderCounter: number;
+}
+
+export interface PanelOptionsProps<T = any> {
+  options: T;
+  onChange: (options: T) => void;
 }
 }

+ 8 - 3
public/app/types/plugins.ts

@@ -1,13 +1,18 @@
+import { ComponentClass } from 'react';
+import { PanelProps, PanelOptionsProps } from './panel';
+
 export interface PluginExports {
 export interface PluginExports {
-  PanelCtrl?;
-  PanelComponent?: any;
   Datasource?: any;
   Datasource?: any;
   QueryCtrl?: any;
   QueryCtrl?: any;
   ConfigCtrl?: any;
   ConfigCtrl?: any;
   AnnotationsQueryCtrl?: any;
   AnnotationsQueryCtrl?: any;
-  PanelOptions?: any;
   ExploreQueryField?: any;
   ExploreQueryField?: any;
   ExploreStartPage?: any;
   ExploreStartPage?: any;
+
+  // Panel plugin
+  PanelCtrl?;
+  PanelComponent?: ComponentClass<PanelProps>;
+  PanelOptionsComponent: ComponentClass<PanelOptionsProps>;
 }
 }
 
 
 export interface PanelPlugin {
 export interface PanelPlugin {

+ 77 - 59
public/app/viz/Graph.tsx

@@ -8,63 +8,22 @@ import 'vendor/flot/jquery.flot.time';
 // Types
 // Types
 import { TimeRange, TimeSeriesVMs } from 'app/types';
 import { TimeRange, TimeSeriesVMs } from 'app/types';
 
 
-// Copied from graph.ts
-function time_format(ticks, min, max) {
-  if (min && max && ticks) {
-    const range = max - min;
-    const secPerTick = range / ticks / 1000;
-    const oneDay = 86400000;
-    const oneYear = 31536000000;
-
-    if (secPerTick <= 45) {
-      return '%H:%M:%S';
-    }
-    if (secPerTick <= 7200 || range <= oneDay) {
-      return '%H:%M';
-    }
-    if (secPerTick <= 80000) {
-      return '%m/%d %H:%M';
-    }
-    if (secPerTick <= 2419200 || range <= oneYear) {
-      return '%m/%d';
-    }
-    return '%Y-%m';
-  }
-
-  return '%H:%M';
-}
-
-const FLOT_OPTIONS = {
-  legend: {
-    show: false,
-  },
-  series: {
-    lines: {
-      linewidth: 1,
-      zero: false,
-    },
-    shadowSize: 0,
-  },
-  grid: {
-    minBorderMargin: 0,
-    markings: [],
-    backgroundColor: null,
-    borderWidth: 0,
-    // hoverable: true,
-    clickable: true,
-    color: '#a1a1a1',
-    margin: { left: 0, right: 0 },
-    labelMarginX: 0,
-  },
-};
-
 interface GraphProps {
 interface GraphProps {
   timeSeries: TimeSeriesVMs;
   timeSeries: TimeSeriesVMs;
   timeRange: TimeRange;
   timeRange: TimeRange;
+  showLines?: boolean;
+  showPoints?: boolean;
+  showBars?: boolean;
   size?: { width: number; height: number };
   size?: { width: number; height: number };
 }
 }
 
 
 export class Graph extends PureComponent<GraphProps> {
 export class Graph extends PureComponent<GraphProps> {
+  static defaultProps = {
+    showLines: true,
+    showPoints: false,
+    showBars: false,
+  };
+
   element: any;
   element: any;
 
 
   componentDidUpdate(prevProps: GraphProps) {
   componentDidUpdate(prevProps: GraphProps) {
@@ -82,7 +41,7 @@ export class Graph extends PureComponent<GraphProps> {
   }
   }
 
 
   draw() {
   draw() {
-    const { size, timeSeries, timeRange } = this.props;
+    const { size, timeSeries, timeRange, showLines, showBars, showPoints } = this.props;
 
 
     if (!size) {
     if (!size) {
       return;
       return;
@@ -92,7 +51,31 @@ export class Graph extends PureComponent<GraphProps> {
     const min = timeRange.from.valueOf();
     const min = timeRange.from.valueOf();
     const max = timeRange.to.valueOf();
     const max = timeRange.to.valueOf();
 
 
-    const dynamicOptions = {
+    const flotOptions = {
+      legend: {
+        show: false,
+      },
+      series: {
+        lines: {
+          show: showLines,
+          linewidth: 1,
+          zero: false,
+        },
+        points: {
+          show: showPoints,
+          fill: 1,
+          fillColor: false,
+          radius: 2,
+        },
+        bars: {
+          show: showBars,
+          fill: 1,
+          barWidth: 1,
+          zero: false,
+          lineWidth: 0,
+        },
+        shadowSize: 0,
+      },
       xaxis: {
       xaxis: {
         mode: 'time',
         mode: 'time',
         min: min,
         min: min,
@@ -101,15 +84,24 @@ export class Graph extends PureComponent<GraphProps> {
         ticks: ticks,
         ticks: ticks,
         timeformat: time_format(ticks, min, max),
         timeformat: time_format(ticks, min, max),
       },
       },
+      grid: {
+        minBorderMargin: 0,
+        markings: [],
+        backgroundColor: null,
+        borderWidth: 0,
+        // hoverable: true,
+        clickable: true,
+        color: '#a1a1a1',
+        margin: { left: 0, right: 0 },
+        labelMarginX: 0,
+      },
     };
     };
 
 
-    const options = {
-      ...FLOT_OPTIONS,
-      ...dynamicOptions,
-    };
-
-    console.log('plot', timeSeries, options);
-    $.plot(this.element, timeSeries, options);
+    try {
+      $.plot(this.element, timeSeries, flotOptions);
+    } catch (err) {
+      console.log('Graph rendering error', err, flotOptions, timeSeries);
+    }
   }
   }
 
 
   render() {
   render() {
@@ -121,4 +113,30 @@ export class Graph extends PureComponent<GraphProps> {
   }
   }
 }
 }
 
 
+// Copied from graph.ts
+function time_format(ticks, min, max) {
+  if (min && max && ticks) {
+    const range = max - min;
+    const secPerTick = range / ticks / 1000;
+    const oneDay = 86400000;
+    const oneYear = 31536000000;
+
+    if (secPerTick <= 45) {
+      return '%H:%M:%S';
+    }
+    if (secPerTick <= 7200 || range <= oneDay) {
+      return '%H:%M';
+    }
+    if (secPerTick <= 80000) {
+      return '%m/%d %H:%M';
+    }
+    if (secPerTick <= 2419200 || range <= oneYear) {
+      return '%m/%d';
+    }
+    return '%Y-%m';
+  }
+
+  return '%H:%M';
+}
+
 export default withSize()(Graph);
 export default withSize()(Graph);