mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Changed how react panels store their options (#15468)
Changed how react panels store their options * Added a ReactPanelPlugin as the interface that react panels export, this way react panels have clearer api, and gives us hooks to handle migrations and a way for panel to handle panel changes in the future * Moved gauge value options into a sub oject and made editor more generic, will be moved out of gauge pane later and shared between singlestat, gauge, bargauge, honecomb * Also remove nested options prop that was there due to bug * Added missing Gauge props * Fixed gauge issue that will require migration later and also value options editor did not handle null decimals or 0 decimals * Fixed unit tests * More fixes for react panels
This commit is contained in:
parent
a6cae5b2b8
commit
abddb442a1
@ -9,7 +9,7 @@ import { Themeable } from '../../index';
|
|||||||
type TimeSeriesValue = string | number | null;
|
type TimeSeriesValue = string | number | null;
|
||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface Props extends Themeable {
|
||||||
decimals: number;
|
decimals?: number | null;
|
||||||
height: number;
|
height: number;
|
||||||
valueMappings: ValueMapping[];
|
valueMappings: ValueMapping[];
|
||||||
maxValue: number;
|
maxValue: number;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ComponentClass } from 'react';
|
||||||
import { TimeSeries, LoadingState, TableData } from './data';
|
import { TimeSeries, LoadingState, TableData } from './data';
|
||||||
import { TimeRange } from './time';
|
import { TimeRange } from './time';
|
||||||
|
|
||||||
@ -19,11 +20,29 @@ export interface PanelData {
|
|||||||
tableData?: TableData;
|
tableData?: TableData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PanelOptionsProps<T = any> {
|
export interface PanelEditorProps<T = any> {
|
||||||
options: T;
|
options: T;
|
||||||
onChange: (options: T) => void;
|
onChange: (options: T) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ReactPanelPlugin<TOptions = any> {
|
||||||
|
panel: ComponentClass<PanelProps<TOptions>>;
|
||||||
|
editor?: ComponentClass<PanelEditorProps<TOptions>>;
|
||||||
|
defaults?: TOptions;
|
||||||
|
|
||||||
|
constructor(panel: ComponentClass<PanelProps<TOptions>>) {
|
||||||
|
this.panel = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditor(editor: ComponentClass<PanelEditorProps<TOptions>>) {
|
||||||
|
this.editor = editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaults(defaults: TOptions) {
|
||||||
|
this.defaults = defaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface PanelSize {
|
export interface PanelSize {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ComponentClass } from 'react';
|
import { ComponentClass } from 'react';
|
||||||
import { PanelProps, PanelOptionsProps } from './panel';
|
import { ReactPanelPlugin } from './panel';
|
||||||
import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource';
|
import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource';
|
||||||
|
|
||||||
export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
||||||
@ -81,9 +81,7 @@ export interface PluginExports {
|
|||||||
|
|
||||||
// Panel plugin
|
// Panel plugin
|
||||||
PanelCtrl?: any;
|
PanelCtrl?: any;
|
||||||
Panel?: ComponentClass<PanelProps>;
|
reactPanel: ReactPanelPlugin;
|
||||||
PanelOptions?: ComponentClass<PanelOptionsProps>;
|
|
||||||
PanelDefaults?: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginMeta {
|
export interface PluginMeta {
|
||||||
|
@ -14,4 +14,3 @@ export const DASHBOARD_TOP_PADDING = 20;
|
|||||||
|
|
||||||
export const PANEL_HEADER_HEIGHT = 27;
|
export const PANEL_HEADER_HEIGHT = 27;
|
||||||
export const PANEL_BORDER = 2;
|
export const PANEL_BORDER = 2;
|
||||||
export const PANEL_OPTIONS_KEY_PREFIX = 'options-';
|
|
||||||
|
@ -78,7 +78,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
|||||||
],
|
],
|
||||||
"refresh": undefined,
|
"refresh": undefined,
|
||||||
"revision": undefined,
|
"revision": undefined,
|
||||||
"schemaVersion": 17,
|
"schemaVersion": 18,
|
||||||
"snapshot": undefined,
|
"snapshot": undefined,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": Array [],
|
"tags": Array [],
|
||||||
@ -190,7 +190,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
|||||||
],
|
],
|
||||||
"refresh": undefined,
|
"refresh": undefined,
|
||||||
"revision": undefined,
|
"revision": undefined,
|
||||||
"schemaVersion": 17,
|
"schemaVersion": 18,
|
||||||
"snapshot": undefined,
|
"snapshot": undefined,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": Array [],
|
"tags": Array [],
|
||||||
@ -313,7 +313,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
|||||||
],
|
],
|
||||||
"refresh": undefined,
|
"refresh": undefined,
|
||||||
"revision": undefined,
|
"revision": undefined,
|
||||||
"schemaVersion": 17,
|
"schemaVersion": 18,
|
||||||
"snapshot": undefined,
|
"snapshot": undefined,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": Array [],
|
"tags": Array [],
|
||||||
@ -423,7 +423,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
|||||||
],
|
],
|
||||||
"refresh": undefined,
|
"refresh": undefined,
|
||||||
"revision": undefined,
|
"revision": undefined,
|
||||||
"schemaVersion": 17,
|
"schemaVersion": 18,
|
||||||
"snapshot": undefined,
|
"snapshot": undefined,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": Array [],
|
"tags": Array [],
|
||||||
@ -518,7 +518,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
|||||||
],
|
],
|
||||||
"refresh": undefined,
|
"refresh": undefined,
|
||||||
"revision": undefined,
|
"revision": undefined,
|
||||||
"schemaVersion": 17,
|
"schemaVersion": 18,
|
||||||
"snapshot": undefined,
|
"snapshot": undefined,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": Array [],
|
"tags": Array [],
|
||||||
|
@ -173,7 +173,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
|||||||
onMouseLeave={this.onMouseLeave}
|
onMouseLeave={this.onMouseLeave}
|
||||||
style={styles}
|
style={styles}
|
||||||
>
|
>
|
||||||
{plugin.exports.Panel && this.renderReactPanel()}
|
{plugin.exports.reactPanel && this.renderReactPanel()}
|
||||||
{plugin.exports.PanelCtrl && this.renderAngularPanel()}
|
{plugin.exports.PanelCtrl && this.renderAngularPanel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -162,7 +162,7 @@ export class DataPanel extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onError(message, err);
|
onError(message, err);
|
||||||
this.setState({ isFirstLoad: false });
|
this.setState({ isFirstLoad: false, loading: LoadingState.Error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -187,7 +187,8 @@ export class DataPanel extends Component<Props, State> {
|
|||||||
const { loading, isFirstLoad } = this.state;
|
const { loading, isFirstLoad } = this.state;
|
||||||
const panelData = this.getPanelData();
|
const panelData = this.getPanelData();
|
||||||
|
|
||||||
if (isFirstLoad && loading === LoadingState.Loading) {
|
// do not render component until we have first data
|
||||||
|
if (isFirstLoad && (loading === LoadingState.Loading || loading === LoadingState.NotStarted)) {
|
||||||
return this.renderLoadingState();
|
return this.renderLoadingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,21 +202,17 @@ export class DataPanel extends Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.renderLoadingState()}
|
{loading === LoadingState.Loading && this.renderLoadingState()}
|
||||||
{this.props.children({ loading, panelData })}
|
{this.props.children({ loading, panelData })}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderLoadingState(): JSX.Element {
|
private renderLoadingState(): JSX.Element {
|
||||||
const { loading } = this.state;
|
return (
|
||||||
if (loading === LoadingState.Loading) {
|
<div className="panel-loading">
|
||||||
return (
|
<i className="fa fa-spinner fa-spin" />
|
||||||
<div className="panel-loading">
|
</div>
|
||||||
<i className="fa fa-spinner fa-spin" />
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element {
|
renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element {
|
||||||
const { panel, plugin } = this.props;
|
const { panel, plugin } = this.props;
|
||||||
const { timeRange, renderCounter } = this.state;
|
const { timeRange, renderCounter } = this.state;
|
||||||
const PanelComponent = plugin.exports.Panel;
|
const PanelComponent = plugin.exports.reactPanel.panel;
|
||||||
|
|
||||||
// This is only done to increase a counter that is used by backend
|
// This is only done to increase a counter that is used by backend
|
||||||
// image rendering (phantomjs/headless chrome) to know when to capture image
|
// image rendering (phantomjs/headless chrome) to know when to capture image
|
||||||
@ -154,7 +154,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
panelData={panelData}
|
panelData={panelData}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
options={panel.getOptions(plugin.exports.PanelDefaults)}
|
options={panel.getOptions(plugin.exports.reactPanel.defaults)}
|
||||||
width={width - 2 * variables.panelhorizontalpadding}
|
width={width - 2 * variables.panelhorizontalpadding}
|
||||||
height={height - PANEL_HEADER_HEIGHT - variables.panelverticalpadding}
|
height={height - PANEL_HEADER_HEIGHT - variables.panelverticalpadding}
|
||||||
renderCounter={renderCounter}
|
renderCounter={renderCounter}
|
||||||
|
@ -3,7 +3,7 @@ import _ from 'lodash';
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { PanelProps } from '@grafana/ui';
|
import { PanelProps, ReactPanelPlugin } from '@grafana/ui';
|
||||||
import { PanelPlugin } from 'app/types';
|
import { PanelPlugin } from 'app/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -63,7 +63,7 @@ export function getPanelPluginNotFound(id: string): PanelPlugin {
|
|||||||
},
|
},
|
||||||
|
|
||||||
exports: {
|
exports: {
|
||||||
Panel: NotFound,
|
reactPanel: new ReactPanelPlugin(NotFound),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -50,33 +50,27 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getPanelDefaultOptions = () => {
|
getReactPanelOptions = () => {
|
||||||
const { panel, plugin } = this.props;
|
const { panel, plugin } = this.props;
|
||||||
|
return panel.getOptions(plugin.exports.reactPanel.defaults);
|
||||||
if (plugin.exports.PanelDefaults) {
|
|
||||||
return panel.getOptions(plugin.exports.PanelDefaults.options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return panel.getOptions(plugin.exports.PanelDefaults);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPanelOptions() {
|
renderPanelOptions() {
|
||||||
const { plugin, angularPanel } = this.props;
|
const { plugin, angularPanel } = this.props;
|
||||||
const { PanelOptions } = plugin.exports;
|
|
||||||
|
|
||||||
if (angularPanel) {
|
if (angularPanel) {
|
||||||
return <div ref={element => (this.element = element)} />;
|
return <div ref={element => (this.element = element)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (plugin.exports.reactPanel) {
|
||||||
<>
|
const PanelEditor = plugin.exports.reactPanel.editor;
|
||||||
{PanelOptions ? (
|
|
||||||
<PanelOptions options={this.getPanelDefaultOptions()} onChange={this.onPanelOptionsChanged} />
|
if (PanelEditor) {
|
||||||
) : (
|
return <PanelEditor options={this.getReactPanelOptions()} onChange={this.onPanelOptionsChanged} />;
|
||||||
<p>Visualization has no options</p>
|
}
|
||||||
)}
|
}
|
||||||
</>
|
|
||||||
);
|
return <p>Visualization has no options</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -127,7 +127,7 @@ describe('DashboardModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('dashboard schema version should be set to latest', () => {
|
it('dashboard schema version should be set to latest', () => {
|
||||||
expect(model.schemaVersion).toBe(17);
|
expect(model.schemaVersion).toBe(18);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('graph thresholds should be migrated', () => {
|
it('graph thresholds should be migrated', () => {
|
||||||
|
@ -22,7 +22,7 @@ export class DashboardMigrator {
|
|||||||
let i, j, k, n;
|
let i, j, k, n;
|
||||||
const oldVersion = this.dashboard.schemaVersion;
|
const oldVersion = this.dashboard.schemaVersion;
|
||||||
const panelUpgrades = [];
|
const panelUpgrades = [];
|
||||||
this.dashboard.schemaVersion = 17;
|
this.dashboard.schemaVersion = 18;
|
||||||
|
|
||||||
if (oldVersion === this.dashboard.schemaVersion) {
|
if (oldVersion === this.dashboard.schemaVersion) {
|
||||||
return;
|
return;
|
||||||
@ -387,6 +387,30 @@ export class DashboardMigrator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 18) {
|
||||||
|
// migrate change to gauge options
|
||||||
|
panelUpgrades.push(panel => {
|
||||||
|
if (panel['options-gauge']) {
|
||||||
|
panel.options = panel['options-gauge'];
|
||||||
|
panel.options.valueOptions = {
|
||||||
|
unit: panel.options.unit,
|
||||||
|
stat: panel.options.stat,
|
||||||
|
decimals: panel.options.decimals,
|
||||||
|
prefix: panel.options.prefix,
|
||||||
|
suffix: panel.options.suffix,
|
||||||
|
};
|
||||||
|
// this options prop was due to a bug
|
||||||
|
delete panel.options.options;
|
||||||
|
delete panel.options.unit;
|
||||||
|
delete panel.options.stat;
|
||||||
|
delete panel.options.decimals;
|
||||||
|
delete panel.options.prefix;
|
||||||
|
delete panel.options.suffix;
|
||||||
|
delete panel['options-gauge'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (panelUpgrades.length === 0) {
|
if (panelUpgrades.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -55,5 +55,19 @@ describe('PanelModel', () => {
|
|||||||
expect(model.alert).toBe(undefined);
|
expect(model.alert).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('get panel options', () => {
|
||||||
|
it('should apply defaults', () => {
|
||||||
|
model.options = { existingProp: 10 };
|
||||||
|
const options = model.getOptions({
|
||||||
|
defaultProp: true,
|
||||||
|
existingProp: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(options.defaultProp).toBe(true);
|
||||||
|
expect(options.existingProp).toBe(10);
|
||||||
|
expect(model.options).toBe(options);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,6 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
import { PANEL_OPTIONS_KEY_PREFIX } from 'app/core/constants';
|
|
||||||
import { DataQuery, TimeSeries } from '@grafana/ui';
|
import { DataQuery, TimeSeries } from '@grafana/ui';
|
||||||
import { TableData } from '@grafana/ui/src';
|
import { TableData } from '@grafana/ui/src';
|
||||||
|
|
||||||
@ -92,6 +91,7 @@ export class PanelModel {
|
|||||||
timeFrom?: any;
|
timeFrom?: any;
|
||||||
timeShift?: any;
|
timeShift?: any;
|
||||||
hideTimeOverride?: any;
|
hideTimeOverride?: any;
|
||||||
|
options: object;
|
||||||
|
|
||||||
maxDataPoints?: number;
|
maxDataPoints?: number;
|
||||||
interval?: string;
|
interval?: string;
|
||||||
@ -105,8 +105,6 @@ export class PanelModel {
|
|||||||
hasRefreshed: boolean;
|
hasRefreshed: boolean;
|
||||||
events: Emitter;
|
events: Emitter;
|
||||||
cacheTimeout?: any;
|
cacheTimeout?: any;
|
||||||
|
|
||||||
// cache props between plugins
|
|
||||||
cachedPluginOptions?: any;
|
cachedPluginOptions?: any;
|
||||||
|
|
||||||
constructor(model) {
|
constructor(model) {
|
||||||
@ -134,20 +132,14 @@ export class PanelModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOptions(panelDefaults) {
|
getOptions(panelDefaults) {
|
||||||
return _.defaultsDeep(this[this.getOptionsKey()] || {}, panelDefaults);
|
return _.defaultsDeep(this.options || {}, panelDefaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOptions(options: object) {
|
updateOptions(options: object) {
|
||||||
const update: any = {};
|
this.options = options;
|
||||||
update[this.getOptionsKey()] = options;
|
|
||||||
Object.assign(this, update);
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getOptionsKey() {
|
|
||||||
return PANEL_OPTIONS_KEY_PREFIX + this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSaveModel() {
|
getSaveModel() {
|
||||||
const model: any = {};
|
const model: any = {};
|
||||||
for (const property in this) {
|
for (const property in this) {
|
||||||
@ -240,14 +232,15 @@ export class PanelModel {
|
|||||||
// for angular panels only we need to remove all events and let angular panels do some cleanup
|
// for angular panels only we need to remove all events and let angular panels do some cleanup
|
||||||
if (fromAngularPanel) {
|
if (fromAngularPanel) {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
for (const key of _.keys(this)) {
|
// remove panel type specific options
|
||||||
if (mustKeepProps[key]) {
|
for (const key of _.keys(this)) {
|
||||||
continue;
|
if (mustKeepProps[key]) {
|
||||||
}
|
continue;
|
||||||
|
|
||||||
delete this[key];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete this[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.restorePanelOptions(pluginId);
|
this.restorePanelOptions(pluginId);
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
// Libraries
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { FormField, PanelOptionsProps, PanelOptionsGroup, Switch } from '@grafana/ui';
|
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import { Switch, PanelOptionsGroup } from '@grafana/ui';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { FormField, PanelEditorProps } from '@grafana/ui';
|
||||||
import { GaugeOptions } from './types';
|
import { GaugeOptions } from './types';
|
||||||
|
|
||||||
export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
export class GaugeOptionsBox extends PureComponent<PanelEditorProps<GaugeOptions>> {
|
||||||
onToggleThresholdLabels = () =>
|
onToggleThresholdLabels = () =>
|
||||||
this.props.onChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels });
|
this.props.onChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels });
|
||||||
|
|
@ -16,9 +16,10 @@ interface Props extends PanelProps<GaugeOptions> {}
|
|||||||
export class GaugePanel extends PureComponent<Props> {
|
export class GaugePanel extends PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { panelData, width, height, onInterpolate, options } = this.props;
|
const { panelData, width, height, onInterpolate, options } = this.props;
|
||||||
|
const { valueOptions } = options;
|
||||||
|
|
||||||
const prefix = onInterpolate(options.prefix);
|
const prefix = onInterpolate(valueOptions.prefix);
|
||||||
const suffix = onInterpolate(options.suffix);
|
const suffix = onInterpolate(valueOptions.suffix);
|
||||||
let value: TimeSeriesValue;
|
let value: TimeSeriesValue;
|
||||||
|
|
||||||
if (panelData.timeSeries) {
|
if (panelData.timeSeries) {
|
||||||
@ -28,7 +29,7 @@ export class GaugePanel extends PureComponent<Props> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (vmSeries[0]) {
|
if (vmSeries[0]) {
|
||||||
value = vmSeries[0].stats[options.stat];
|
value = vmSeries[0].stats[valueOptions.stat];
|
||||||
} else {
|
} else {
|
||||||
value = null;
|
value = null;
|
||||||
}
|
}
|
||||||
@ -41,11 +42,18 @@ export class GaugePanel extends PureComponent<Props> {
|
|||||||
{theme => (
|
{theme => (
|
||||||
<Gauge
|
<Gauge
|
||||||
value={value}
|
value={value}
|
||||||
{...this.props.options}
|
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
prefix={prefix}
|
prefix={prefix}
|
||||||
suffix={suffix}
|
suffix={suffix}
|
||||||
|
unit={valueOptions.unit}
|
||||||
|
decimals={valueOptions.decimals}
|
||||||
|
thresholds={options.thresholds}
|
||||||
|
valueMappings={options.valueMappings}
|
||||||
|
showThresholdLabels={options.showThresholdLabels}
|
||||||
|
showThresholdMarkers={options.showThresholdMarkers}
|
||||||
|
minValue={options.minValue}
|
||||||
|
maxValue={options.maxValue}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import {
|
import {
|
||||||
PanelOptionsProps,
|
PanelEditorProps,
|
||||||
ThresholdsEditor,
|
ThresholdsEditor,
|
||||||
Threshold,
|
Threshold,
|
||||||
PanelOptionsGrid,
|
PanelOptionsGrid,
|
||||||
@ -8,29 +8,11 @@ import {
|
|||||||
ValueMapping,
|
ValueMapping,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
|
import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
|
||||||
import GaugeOptionsEditor from './GaugeOptionsEditor';
|
import { GaugeOptionsBox } from './GaugeOptionsBox';
|
||||||
import { GaugeOptions } from './types';
|
import { GaugeOptions, SingleStatValueOptions } from './types';
|
||||||
|
|
||||||
export const defaultProps = {
|
|
||||||
options: {
|
|
||||||
minValue: 0,
|
|
||||||
maxValue: 100,
|
|
||||||
prefix: '',
|
|
||||||
showThresholdMarkers: true,
|
|
||||||
showThresholdLabels: false,
|
|
||||||
suffix: '',
|
|
||||||
decimals: 0,
|
|
||||||
stat: 'avg',
|
|
||||||
unit: 'none',
|
|
||||||
valueMappings: [],
|
|
||||||
thresholds: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
|
||||||
static defaultProps = defaultProps;
|
|
||||||
|
|
||||||
|
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
|
||||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
...this.props.options,
|
...this.props.options,
|
||||||
@ -43,14 +25,20 @@ export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<G
|
|||||||
valueMappings,
|
valueMappings,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
|
||||||
|
this.props.onChange({
|
||||||
|
...this.props.options,
|
||||||
|
valueOptions,
|
||||||
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onChange, options } = this.props;
|
const { onChange, options } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PanelOptionsGrid>
|
<PanelOptionsGrid>
|
||||||
<ValueOptions onChange={onChange} options={options} />
|
<SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
|
||||||
<GaugeOptionsEditor onChange={onChange} options={options} />
|
<GaugeOptionsBox onChange={onChange} options={options} />
|
||||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
||||||
</PanelOptionsGrid>
|
</PanelOptionsGrid>
|
||||||
|
|
@ -1,7 +1,12 @@
|
|||||||
|
// Libraries
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { FormField, FormLabel, PanelOptionsProps, PanelOptionsGroup, Select } from '@grafana/ui';
|
|
||||||
|
// Components
|
||||||
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
||||||
import { GaugeOptions } from './types';
|
import { FormField, FormLabel, PanelOptionsGroup, Select } from '@grafana/ui';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { SingleStatValueOptions } from './types';
|
||||||
|
|
||||||
const statOptions = [
|
const statOptions = [
|
||||||
{ value: 'min', label: 'Min' },
|
{ value: 'min', label: 'Min' },
|
||||||
@ -19,24 +24,40 @@ const statOptions = [
|
|||||||
|
|
||||||
const labelWidth = 6;
|
const labelWidth = 6;
|
||||||
|
|
||||||
export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
export interface Props {
|
||||||
onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value });
|
options: SingleStatValueOptions;
|
||||||
|
onChange: (valueOptions: SingleStatValueOptions) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SingleStatValueEditor extends PureComponent<Props> {
|
||||||
|
onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value });
|
||||||
onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
|
onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
|
||||||
|
|
||||||
onDecimalChange = event => {
|
onDecimalChange = event => {
|
||||||
if (!isNaN(event.target.value)) {
|
if (!isNaN(event.target.value)) {
|
||||||
this.props.onChange({ ...this.props.options, decimals: event.target.value });
|
this.props.onChange({
|
||||||
|
...this.props.options,
|
||||||
|
decimals: parseInt(event.target.value, 10),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.props.onChange({
|
||||||
|
...this.props.options,
|
||||||
|
decimals: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onPrefixChange = event => this.props.onChange({ ...this.props.options, prefix: event.target.value });
|
onPrefixChange = event => this.props.onChange({ ...this.props.options, prefix: event.target.value });
|
||||||
|
|
||||||
onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value });
|
onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value });
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { stat, unit, decimals, prefix, suffix } = this.props.options;
|
const { stat, unit, decimals, prefix, suffix } = this.props.options;
|
||||||
|
|
||||||
|
let decimalsString = '';
|
||||||
|
if (Number.isFinite(decimals)) {
|
||||||
|
decimalsString = decimals.toString();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelOptionsGroup title="Value">
|
<PanelOptionsGroup title="Value">
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
@ -57,7 +78,7 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO
|
|||||||
labelWidth={labelWidth}
|
labelWidth={labelWidth}
|
||||||
placeholder="auto"
|
placeholder="auto"
|
||||||
onChange={this.onDecimalChange}
|
onChange={this.onDecimalChange}
|
||||||
value={decimals || ''}
|
value={decimalsString}
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
<FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
|
<FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
|
@ -1,4 +1,10 @@
|
|||||||
import GaugePanelOptions, { defaultProps } from './GaugePanelOptions';
|
import { ReactPanelPlugin } from '@grafana/ui';
|
||||||
import { GaugePanel } from './GaugePanel';
|
|
||||||
|
|
||||||
export { GaugePanel as Panel, GaugePanelOptions as PanelOptions, defaultProps as PanelDefaults };
|
import { GaugePanelEditor } from './GaugePanelEditor';
|
||||||
|
import { GaugePanel } from './GaugePanel';
|
||||||
|
import { GaugeOptions, defaults } from './types';
|
||||||
|
|
||||||
|
export const reactPanel = new ReactPanelPlugin<GaugeOptions>(GaugePanel);
|
||||||
|
|
||||||
|
reactPanel.setEditor(GaugePanelEditor);
|
||||||
|
reactPanel.setDefaults(defaults);
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
import { Threshold, ValueMapping } from '@grafana/ui';
|
import { Threshold, ValueMapping } from '@grafana/ui';
|
||||||
|
|
||||||
export interface GaugeOptions {
|
export interface GaugeOptions {
|
||||||
decimals: number;
|
|
||||||
valueMappings: ValueMapping[];
|
valueMappings: ValueMapping[];
|
||||||
maxValue: number;
|
maxValue: number;
|
||||||
minValue: number;
|
minValue: number;
|
||||||
prefix: string;
|
|
||||||
showThresholdLabels: boolean;
|
showThresholdLabels: boolean;
|
||||||
showThresholdMarkers: boolean;
|
showThresholdMarkers: boolean;
|
||||||
stat: string;
|
|
||||||
suffix: string;
|
|
||||||
thresholds: Threshold[];
|
thresholds: Threshold[];
|
||||||
unit: string;
|
valueOptions: SingleStatValueOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SingleStatValueOptions {
|
||||||
|
unit: string;
|
||||||
|
suffix: string;
|
||||||
|
stat: string;
|
||||||
|
prefix: string;
|
||||||
|
decimals?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaults: GaugeOptions = {
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: 100,
|
||||||
|
showThresholdMarkers: true,
|
||||||
|
showThresholdLabels: false,
|
||||||
|
valueOptions: {
|
||||||
|
prefix: '',
|
||||||
|
suffix: '',
|
||||||
|
decimals: null,
|
||||||
|
stat: 'avg',
|
||||||
|
unit: 'none',
|
||||||
|
},
|
||||||
|
valueMappings: [],
|
||||||
|
thresholds: [],
|
||||||
|
};
|
||||||
|
@ -3,10 +3,10 @@ import _ from 'lodash';
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { PanelOptionsProps, Switch } from '@grafana/ui';
|
import { PanelEditorProps, Switch } from '@grafana/ui';
|
||||||
import { Options } from './types';
|
import { Options } from './types';
|
||||||
|
|
||||||
export class GraphPanelOptions extends PureComponent<PanelOptionsProps<Options>> {
|
export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> {
|
||||||
onToggleLines = () => {
|
onToggleLines = () => {
|
||||||
this.props.onChange({ ...this.props.options, showLines: !this.props.options.showLines });
|
this.props.onChange({ ...this.props.options, showLines: !this.props.options.showLines });
|
||||||
};
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { GraphPanel } from './GraphPanel';
|
import { GraphPanel } from './GraphPanel';
|
||||||
import { GraphPanelOptions } from './GraphPanelOptions';
|
import { GraphPanelEditor } from './GraphPanelEditor';
|
||||||
|
|
||||||
export { GraphPanel as Panel, GraphPanelOptions as PanelOptions };
|
export { GraphPanel as Panel, GraphPanelEditor as PanelOptions };
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { PanelProps } from '@grafana/ui';
|
import { PanelProps, ReactPanelPlugin } from '@grafana/ui';
|
||||||
|
|
||||||
export class Text2 extends PureComponent<PanelProps> {
|
export class Text2 extends PureComponent<PanelProps> {
|
||||||
constructor(props: PanelProps) {
|
constructor(props: PanelProps) {
|
||||||
@ -11,4 +11,4 @@ export class Text2 extends PureComponent<PanelProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Text2 as Panel };
|
export const reactPanel = new ReactPanelPlugin(Text2);
|
||||||
|
Loading…
Reference in New Issue
Block a user