Commit 985f057a by ryan

revert most options sharing

parent 8299c954
import { PanelData, NullValueMode, SingleStatValueInfo } from '../types'; import { PanelData, NullValueMode, SingleStatValueInfo } from '../types';
import { processTimeSeries } from './processTimeSeries'; import { processTimeSeries } from './processTimeSeries';
import { DisplayValueOptions } from './displayValue';
export interface SingleStatProcessingOptions { export interface SingleStatProcessingOptions {
panelData: PanelData; panelData: PanelData;
stat: string; stat: string;
} }
export interface SingleStatOptions {
stat: string;
display: DisplayValueOptions;
}
// //
// This is a temporary thing, waiting for a better data model and maybe unification between time series & table data // This is a temporary thing, waiting for a better data model and maybe unification between time series & table data
// //
......
...@@ -25,6 +25,7 @@ import * as heatmapPanel from 'app/plugins/panel/heatmap/module'; ...@@ -25,6 +25,7 @@ import * as heatmapPanel from 'app/plugins/panel/heatmap/module';
import * as tablePanel from 'app/plugins/panel/table/module'; import * as tablePanel from 'app/plugins/panel/table/module';
import * as table2Panel from 'app/plugins/panel/table2/module'; import * as table2Panel from 'app/plugins/panel/table2/module';
import * as singlestatPanel from 'app/plugins/panel/singlestat/module'; import * as singlestatPanel from 'app/plugins/panel/singlestat/module';
import * as singlestatPanel2 from 'app/plugins/panel/singlestat2/module';
import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module'; import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module';
import * as gaugePanel from 'app/plugins/panel/gauge/module'; import * as gaugePanel from 'app/plugins/panel/gauge/module';
import * as barGaugePanel from 'app/plugins/panel/bargauge/module'; import * as barGaugePanel from 'app/plugins/panel/bargauge/module';
...@@ -57,6 +58,7 @@ const builtInPlugins = { ...@@ -57,6 +58,7 @@ const builtInPlugins = {
'app/plugins/panel/table/module': tablePanel, 'app/plugins/panel/table/module': tablePanel,
'app/plugins/panel/table2/module': table2Panel, 'app/plugins/panel/table2/module': table2Panel,
'app/plugins/panel/singlestat/module': singlestatPanel, 'app/plugins/panel/singlestat/module': singlestatPanel,
'app/plugins/panel/singlestat2/module': singlestatPanel2,
'app/plugins/panel/gettingstarted/module': gettingStartedPanel, 'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
'app/plugins/panel/gauge/module': gaugePanel, 'app/plugins/panel/gauge/module': gaugePanel,
'app/plugins/panel/bargauge/module': barGaugePanel, 'app/plugins/panel/bargauge/module': barGaugePanel,
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
// Services & Utils // Services & Utils
import { DisplayValue, VizOrientation } from '@grafana/ui'; import { DisplayValue } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
// Components // Components
...@@ -10,17 +10,11 @@ import { BarGauge } from '@grafana/ui'; ...@@ -10,17 +10,11 @@ import { BarGauge } from '@grafana/ui';
// Types // Types
import { BarGaugeOptions } from './types'; import { BarGaugeOptions } from './types';
import { SingleStatPanel } from '../gauge/SingleStatPanel'; import { SingleStatBase } from '../singlestat2/SingleStatBase';
export class BarGaugePanel extends SingleStatPanel<BarGaugeOptions> {
getOrientation(): VizOrientation {
const { options } = this.props;
return options.orientation;
}
export class BarGaugePanel extends SingleStatBase<BarGaugeOptions> {
renderStat(value: DisplayValue, width: number, height: number) { renderStat(value: DisplayValue, width: number, height: number) {
const { options } = this.props; const { options } = this.props;
const { display } = options;
return ( return (
<BarGauge <BarGauge
...@@ -28,7 +22,7 @@ export class BarGaugePanel extends SingleStatPanel<BarGaugeOptions> { ...@@ -28,7 +22,7 @@ export class BarGaugePanel extends SingleStatPanel<BarGaugeOptions> {
width={width} width={width}
height={height} height={height}
orientation={options.orientation} orientation={options.orientation}
thresholds={display.thresholds} thresholds={options.thresholds}
theme={config.theme} theme={config.theme}
/> />
); );
......
...@@ -2,38 +2,31 @@ ...@@ -2,38 +2,31 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components // Components
import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor'; import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui';
import {
PanelOptionsGrid,
PanelOptionsGroup,
FormField,
DisplayValueOptions,
ThresholdsEditor,
Threshold,
} from '@grafana/ui';
// Types // Types
import { FormLabel, PanelEditorProps, Select, ValueMappingsEditor, ValueMapping } from '@grafana/ui'; import { FormLabel, PanelEditorProps, Threshold, Select, ValueMapping } from '@grafana/ui';
import { BarGaugeOptions, orientationOptions } from './types'; import { BarGaugeOptions, orientationOptions } from './types';
import { DisplayValueEditor } from '../gauge/DisplayValueEditor'; import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
import { SingleStatValueOptions } from '../singlestat2/types';
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> { export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
onDisplayOptionsChanged = (displayOptions: DisplayValueOptions) => onThresholdsChanged = (thresholds: Threshold[]) =>
this.props.onOptionsChange({ this.props.onOptionsChange({
...this.props.options, ...this.props.options,
display: displayOptions,
});
onThresholdsChanged = (thresholds: Threshold[]) =>
this.onDisplayOptionsChanged({
...this.props.options.display,
thresholds, thresholds,
}); });
onValueMappingsChanged = (valueMappings: ValueMapping[]) => onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
this.onDisplayOptionsChanged({ this.props.onOptionsChange({
...this.props.options.display, ...this.props.options,
mappings: valueMappings, valueMappings,
});
onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
this.props.onOptionsChange({
...this.props.options,
valueOptions,
}); });
onMinValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, minValue: target.value }); onMinValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, minValue: target.value });
...@@ -41,17 +34,12 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge ...@@ -41,17 +34,12 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
onOrientationChange = ({ value }) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); onOrientationChange = ({ value }) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
render() { render() {
const { onOptionsChange, options } = this.props; const { options } = this.props;
const { display } = options;
return ( return (
<> <>
<PanelOptionsGrid> <PanelOptionsGrid>
{/* This just sets the 'stats', that should be moved to somethign more general */} <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
<SingleStatValueEditor onChange={onOptionsChange} options={options} />
<DisplayValueEditor onChange={this.onDisplayOptionsChanged} options={display} />
<PanelOptionsGroup title="Gauge"> <PanelOptionsGroup title="Gauge">
<FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} /> <FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} />
<FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={options.maxValue} /> <FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={options.maxValue} />
...@@ -66,9 +54,10 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge ...@@ -66,9 +54,10 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
/> />
</div> </div>
</PanelOptionsGroup> </PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={display.thresholds} /> <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={display.mappings} />
</PanelOptionsGrid> </PanelOptionsGrid>
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
</> </>
); );
} }
......
...@@ -3,10 +3,10 @@ import { ReactPanelPlugin } from '@grafana/ui'; ...@@ -3,10 +3,10 @@ import { ReactPanelPlugin } from '@grafana/ui';
import { BarGaugePanel } from './BarGaugePanel'; import { BarGaugePanel } from './BarGaugePanel';
import { BarGaugePanelEditor } from './BarGaugePanelEditor'; import { BarGaugePanelEditor } from './BarGaugePanelEditor';
import { BarGaugeOptions, defaults } from './types'; import { BarGaugeOptions, defaults } from './types';
import { gaugePanelTypeChangedHook } from '../gauge/module'; import { singleStatOptionsCheck } from '../singlestat2/module';
export const reactPanel = new ReactPanelPlugin<BarGaugeOptions>(BarGaugePanel); export const reactPanel = new ReactPanelPlugin<BarGaugeOptions>(BarGaugePanel);
reactPanel.setEditor(BarGaugePanelEditor); reactPanel.setEditor(BarGaugePanelEditor);
reactPanel.setDefaults(defaults); reactPanel.setDefaults(defaults);
reactPanel.setPanelTypeChangedHook(gaugePanelTypeChangedHook); reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);
import { SelectOptionItem, VizOrientation } from '@grafana/ui'; import { VizOrientation, SelectOptionItem } from '@grafana/ui';
import { GaugeOptions, defaults as gaugeDefaults } from '../gauge/types'; import { SingleStatBaseOptions } from '../singlestat2/types';
export interface BarGaugeOptions extends GaugeOptions {
orientation: VizOrientation;
}
export const orientationOptions: SelectOptionItem[] = [ export const orientationOptions: SelectOptionItem[] = [
{ value: VizOrientation.Horizontal, label: 'Horizontal' }, { value: VizOrientation.Horizontal, label: 'Horizontal' },
{ value: VizOrientation.Vertical, label: 'Vertical' }, { value: VizOrientation.Vertical, label: 'Vertical' },
]; ];
export interface BarGaugeOptions extends SingleStatBaseOptions {
minValue: number;
maxValue: number;
}
export const defaults: BarGaugeOptions = { export const defaults: BarGaugeOptions = {
...gaugeDefaults, minValue: 0,
maxValue: 100,
orientation: VizOrientation.Horizontal, orientation: VizOrientation.Horizontal,
valueOptions: {
unit: 'none',
stat: 'avg',
prefix: '',
suffix: '',
decimals: null,
},
thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
valueMappings: [],
}; };
...@@ -10,19 +10,18 @@ import { Gauge } from '@grafana/ui'; ...@@ -10,19 +10,18 @@ import { Gauge } from '@grafana/ui';
// Types // Types
import { GaugeOptions } from './types'; import { GaugeOptions } from './types';
import { DisplayValue } from '@grafana/ui/src/utils/displayValue'; import { DisplayValue } from '@grafana/ui/src/utils/displayValue';
import { SingleStatPanel } from './SingleStatPanel'; import { SingleStatBase } from '../singlestat2/SingleStatBase';
export class GaugePanel extends SingleStatPanel<GaugeOptions> { export class GaugePanel extends SingleStatBase<GaugeOptions> {
renderStat(value: DisplayValue, width: number, height: number) { renderStat(value: DisplayValue, width: number, height: number) {
const { options } = this.props; const { options } = this.props;
const { display } = options;
return ( return (
<Gauge <Gauge
value={value} value={value}
width={width} width={width}
height={height} height={height}
thresholds={display.thresholds} thresholds={options.thresholds}
showThresholdLabels={options.showThresholdLabels} showThresholdLabels={options.showThresholdLabels}
showThresholdMarkers={options.showThresholdMarkers} showThresholdMarkers={options.showThresholdMarkers}
minValue={options.minValue} minValue={options.minValue}
......
...@@ -9,46 +9,42 @@ import { ...@@ -9,46 +9,42 @@ import {
ValueMapping, ValueMapping,
} from '@grafana/ui'; } from '@grafana/ui';
import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
import { GaugeOptionsBox } from './GaugeOptionsBox'; import { GaugeOptionsBox } from './GaugeOptionsBox';
import { GaugeOptions } from './types'; import { GaugeOptions } from './types';
import { DisplayValueEditor } from './DisplayValueEditor'; import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
import { DisplayValueOptions } from '@grafana/ui'; import { SingleStatValueOptions } from '../singlestat2/types';
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> { export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
onDisplayOptionsChanged = (displayOptions: DisplayValueOptions) => onThresholdsChanged = (thresholds: Threshold[]) =>
this.props.onOptionsChange({ this.props.onOptionsChange({
...this.props.options, ...this.props.options,
display: displayOptions,
});
onThresholdsChanged = (thresholds: Threshold[]) =>
this.onDisplayOptionsChanged({
...this.props.options.display,
thresholds, thresholds,
}); });
onValueMappingsChanged = (valueMappings: ValueMapping[]) => onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
this.onDisplayOptionsChanged({ this.props.onOptionsChange({
...this.props.options.display, ...this.props.options,
mappings: valueMappings, valueMappings,
});
onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
this.props.onOptionsChange({
...this.props.options,
valueOptions,
}); });
render() { render() {
const { onOptionsChange, options } = this.props; const { onOptionsChange, options } = this.props;
const { display } = options;
return ( return (
<> <>
<PanelOptionsGrid> <PanelOptionsGrid>
{/* This just sets the 'stats', that should be moved to somethign more general */} <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
<SingleStatValueEditor onChange={onOptionsChange} options={options} />
<DisplayValueEditor onChange={this.onDisplayOptionsChanged} options={display} />
<GaugeOptionsBox onOptionsChange={onOptionsChange} options={options} /> <GaugeOptionsBox onOptionsChange={onOptionsChange} options={options} />
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={display.thresholds} /> <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
</PanelOptionsGrid> </PanelOptionsGrid>
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={display.mappings} /> <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
</> </>
); );
} }
......
// Libraries
import React, { PureComponent } from 'react';
// Components
import { FormLabel, PanelOptionsGroup, Select } from '@grafana/ui';
// Types
import { GaugeOptions } from './types';
const statOptions = [
{ value: 'min', label: 'Min' },
{ value: 'max', label: 'Max' },
{ value: 'avg', label: 'Average' },
{ value: 'current', label: 'Current' },
{ value: 'total', label: 'Total' },
{ value: 'name', label: 'Name' },
{ value: 'first', label: 'First' },
{ value: 'delta', label: 'Delta' },
{ value: 'diff', label: 'Difference' },
{ value: 'range', label: 'Range' },
{ value: 'last_time', label: 'Time of last point' },
];
const labelWidth = 6;
export interface Props {
options: GaugeOptions;
onChange: (options: GaugeOptions) => void;
}
export class SingleStatValueEditor extends PureComponent<Props> {
onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
render() {
const { stat } = this.props.options;
return (
<PanelOptionsGroup title="Show Value">
<div className="gf-form">
<FormLabel width={labelWidth}>Stat</FormLabel>
<Select
width={12}
options={statOptions}
onChange={this.onStatChange}
value={statOptions.find(option => option.value === stat)}
/>
</div>
</PanelOptionsGroup>
);
}
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Gauge Module migrations should migrate from 6.0 settings to 6.1 1`] = `
Object {
"display": Object {
"decimals": 4,
"mappings": Array [],
"prefix": "a",
"stat": "avg",
"suffix": "z",
"thresholds": Array [
Object {
"color": "green",
"index": 0,
"value": -Infinity,
},
Object {
"color": "red",
"index": 1,
"value": 80,
},
],
"unit": "ms",
},
"maxValue": 60,
"minValue": 50,
"showThresholdLabels": false,
"showThresholdMarkers": true,
"stat": "avg",
}
`;
import { gaugePanelTypeChangedHook } from './module';
describe('Gauge Module', () => {
describe('migrations', () => {
it('should migrate from 6.0 settings to 6.1', () => {
const v60 = {
minValue: 50,
maxValue: 60,
showThresholdMarkers: true,
showThresholdLabels: false,
valueOptions: {
prefix: 'a',
suffix: 'z',
decimals: 4,
stat: 'avg',
unit: 'ms',
},
valueMappings: [],
thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
};
const after = gaugePanelTypeChangedHook(v60);
expect((after.stat = 'avg'));
expect(after).toMatchSnapshot();
});
});
});
import { ReactPanelPlugin, DisplayValueOptions } from '@grafana/ui'; import { ReactPanelPlugin } from '@grafana/ui';
import cloneDeep from 'lodash/cloneDeep';
import { GaugePanelEditor } from './GaugePanelEditor'; import { GaugePanelEditor } from './GaugePanelEditor';
import { GaugePanel } from './GaugePanel'; import { GaugePanel } from './GaugePanel';
import { GaugeOptions, defaults } from './types'; import { GaugeOptions, defaults } from './types';
import { singleStatOptionsCheck } from '../singlestat2/module';
export const reactPanel = new ReactPanelPlugin<GaugeOptions>(GaugePanel); export const reactPanel = new ReactPanelPlugin<GaugeOptions>(GaugePanel);
// Bar Gauge uses the same handler
const optionsToCheck = ['display', 'stat', 'maxValue', 'maxValue'];
export const gaugePanelTypeChangedHook = (options: Partial<GaugeOptions>, prevPluginId?: string, prevOptions?: any) => {
// TODO! migrate to new settings format
//
// thresholds?: Threshold[];
// valueMappings?: ValueMapping[];
// valueOptions?: SingleStatValueOptions;
//
// if (props.options.valueOptions) {
// console.warn('TODO!! how do we best migration options?');
// }
// 6.0 -> 6.1, settings were stored on the root, now moved to display
if (!options.display && !prevOptions && options.hasOwnProperty('thresholds')) {
console.log('Migrating old gauge settings format', options);
const migrate = options as any;
const display = (migrate.valueOptions || {}) as DisplayValueOptions;
display.thresholds = migrate.thresholds;
display.mappings = migrate.valueMappings;
if (migrate.valueMappings) {
options.stat = migrate.valueMappings.stat;
delete migrate.valueMappings.stat;
}
delete migrate.valueOptions;
delete migrate.thresholds;
delete migrate.valueMappings;
options.display = display;
}
if (prevOptions) {
optionsToCheck.forEach(v => {
if (prevOptions.hasOwnProperty(v)) {
options[v] = cloneDeep(prevOptions.display);
}
});
}
return options;
};
reactPanel.setEditor(GaugePanelEditor); reactPanel.setEditor(GaugePanelEditor);
reactPanel.setDefaults(defaults); reactPanel.setDefaults(defaults);
reactPanel.setPanelTypeChangedHook(gaugePanelTypeChangedHook); reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);
import { SingleStatOptions } from '@grafana/ui'; import { SingleStatBaseOptions } from '../singlestat2/types';
import { VizOrientation } from '@grafana/ui';
export interface GaugeOptions extends SingleStatOptions { export interface GaugeOptions extends SingleStatBaseOptions {
maxValue: number; maxValue: number;
minValue: number; minValue: number;
showThresholdLabels: boolean; showThresholdLabels: boolean;
...@@ -12,14 +13,14 @@ export const defaults: GaugeOptions = { ...@@ -12,14 +13,14 @@ export const defaults: GaugeOptions = {
maxValue: 100, maxValue: 100,
showThresholdMarkers: true, showThresholdMarkers: true,
showThresholdLabels: false, showThresholdLabels: false,
valueOptions: {
stat: 'avg',
display: {
prefix: '', prefix: '',
suffix: '', suffix: '',
decimals: null, decimals: null,
stat: 'avg',
unit: 'none', unit: 'none',
mappings: [],
thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
}, },
valueMappings: [],
thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
orientation: VizOrientation.Auto,
}; };
# Singlestat Panel - Native Plugin
The Singlestat Panel is **included** with Grafana.
The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series.
Read more about it here:
[http://docs.grafana.org/reference/singlestat/](http://docs.grafana.org/reference/singlestat/)
\ No newline at end of file
// Libraries
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { processSingleStatPanelData, DisplayValue, PanelProps } from '@grafana/ui';
// Services & Utils
import { processSingleStatPanelData, SingleStatOptions, DisplayValue, PanelProps, VizOrientation } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
// Components
import { VizRepeater, getDisplayProcessor } from '@grafana/ui'; import { VizRepeater, getDisplayProcessor } from '@grafana/ui';
import { SingleStatBaseOptions } from './types';
interface State { export interface State {
values: DisplayValue[]; values: DisplayValue[];
} }
export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<PanelProps<T>, State> { export class SingleStatBase<T extends SingleStatBaseOptions> extends PureComponent<PanelProps<T>, State> {
constructor(props: PanelProps<T>) { constructor(props: PanelProps<T>) {
super(props); super(props);
this.state = { this.state = {
values: this.findDisplayValues(props), values: this.findDisplayValues(props),
}; };
...@@ -29,18 +24,20 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent< ...@@ -29,18 +24,20 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<
findDisplayValues(props: PanelProps<T>): DisplayValue[] { findDisplayValues(props: PanelProps<T>): DisplayValue[] {
const { panelData, replaceVariables, options } = this.props; const { panelData, replaceVariables, options } = this.props;
const { display } = options; const { valueOptions, valueMappings } = options;
const processor = getDisplayProcessor({ const processor = getDisplayProcessor({
...display, unit: valueOptions.unit,
prefix: replaceVariables(display.prefix), decimals: valueOptions.decimals,
suffix: replaceVariables(display.suffix), mappings: valueMappings,
thresholds: options.thresholds,
prefix: replaceVariables(valueOptions.prefix),
suffix: replaceVariables(valueOptions.suffix),
theme: config.theme, theme: config.theme,
}); });
return processSingleStatPanelData({ return processSingleStatPanelData({
panelData: panelData, panelData: panelData,
stat: options.stat, stat: valueOptions.stat,
}).map(stat => processor(stat.value)); }).map(stat => processor(stat.value));
} }
...@@ -51,17 +48,12 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent< ...@@ -51,17 +48,12 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<
return <div style={{ width, height, border: '1px solid red' }}>{value.text}</div>; return <div style={{ width, height, border: '1px solid red' }}>{value.text}</div>;
} }
// Or we could add this to single stat props?
getOrientation(): VizOrientation {
return VizOrientation.Auto;
}
render() { render() {
const { height, width } = this.props; const { height, width, options } = this.props;
const { orientation } = options;
const { values } = this.state; const { values } = this.state;
return ( return (
<VizRepeater height={height} width={width} values={values} orientation={this.getOrientation()}> <VizRepeater height={height} width={width} values={values} orientation={orientation}>
{({ vizHeight, vizWidth, value }) => this.renderStat(value, vizWidth, vizHeight)} {({ vizHeight, vizWidth, value }) => this.renderStat(value, vizWidth, vizHeight)}
</VizRepeater> </VizRepeater>
); );
......
// Libraries
import React, { PureComponent } from 'react';
import {
PanelEditorProps,
ThresholdsEditor,
Threshold,
PanelOptionsGrid,
ValueMappingsEditor,
ValueMapping,
} from '@grafana/ui';
import { SingleStatOptions, SingleStatValueOptions } from './types';
import { SingleStatValueEditor } from './SingleStatValueEditor';
export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatOptions>> {
onThresholdsChanged = (thresholds: Threshold[]) =>
this.props.onOptionsChange({
...this.props.options,
thresholds,
});
onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
this.props.onOptionsChange({
...this.props.options,
valueMappings,
});
onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
this.props.onOptionsChange({
...this.props.options,
valueOptions,
});
render() {
const { options } = this.props;
return (
<>
<PanelOptionsGrid>
<SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
</PanelOptionsGrid>
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
</>
);
}
}
// Libraries
import React from 'react';
// Types
import { SingleStatOptions } from './types';
import { DisplayValue } from '@grafana/ui/src/utils/displayValue';
import { SingleStatBase } from './SingleStatBase';
export class SingleStatPanel extends SingleStatBase<SingleStatOptions> {
renderStat(value: DisplayValue, width: number, height: number) {
return (
<div style={{ width, height }}>
<b>{value.text}</b>
</div>
);
}
}
...@@ -2,20 +2,35 @@ ...@@ -2,20 +2,35 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components // Components
import { FormField, FormLabel, PanelOptionsGroup, UnitPicker } from '@grafana/ui'; import { FormField, FormLabel, PanelOptionsGroup, Select, UnitPicker } from '@grafana/ui';
// Types // Types
import { DisplayValueOptions } from '@grafana/ui'; import { SingleStatValueOptions } from './types';
const statOptions = [
{ value: 'min', label: 'Min' },
{ value: 'max', label: 'Max' },
{ value: 'avg', label: 'Average' },
{ value: 'current', label: 'Current' },
{ value: 'total', label: 'Total' },
{ value: 'name', label: 'Name' },
{ value: 'first', label: 'First' },
{ value: 'delta', label: 'Delta' },
{ value: 'diff', label: 'Difference' },
{ value: 'range', label: 'Range' },
{ value: 'last_time', label: 'Time of last point' },
];
const labelWidth = 6; const labelWidth = 6;
export interface Props { export interface Props {
options: DisplayValueOptions; options: SingleStatValueOptions;
onChange: (options: DisplayValueOptions) => void; onChange: (valueOptions: SingleStatValueOptions) => void;
} }
export class DisplayValueEditor extends PureComponent<Props> { export class SingleStatValueEditor extends PureComponent<Props> {
onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value }); onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.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)) {
...@@ -35,7 +50,7 @@ export class DisplayValueEditor extends PureComponent<Props> { ...@@ -35,7 +50,7 @@ export class DisplayValueEditor extends PureComponent<Props> {
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 { unit, decimals, prefix, suffix } = this.props.options; const { stat, unit, decimals, prefix, suffix } = this.props.options;
let decimalsString = ''; let decimalsString = '';
if (Number.isFinite(decimals)) { if (Number.isFinite(decimals)) {
...@@ -43,7 +58,16 @@ export class DisplayValueEditor extends PureComponent<Props> { ...@@ -43,7 +58,16 @@ export class DisplayValueEditor extends PureComponent<Props> {
} }
return ( return (
<PanelOptionsGroup title="Display Value"> <PanelOptionsGroup title="Value">
<div className="gf-form">
<FormLabel width={labelWidth}>Stat</FormLabel>
<Select
width={12}
options={statOptions}
onChange={this.onStatChange}
value={statOptions.find(option => option.value === stat)}
/>
</div>
<div className="gf-form"> <div className="gf-form">
<FormLabel width={labelWidth}>Unit</FormLabel> <FormLabel width={labelWidth}>Unit</FormLabel>
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} /> <UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.26;fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_2_);}
.st2{fill:url(#SVGID_3_);}
.st3{fill:url(#SVGID_4_);}
.st4{fill:url(#SVGID_5_);}
.st5{fill:none;stroke:url(#SVGID_6_);stroke-miterlimit:10;}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="50" y1="65.6698" x2="50" y2="93.5681">
<stop offset="0" style="stop-color:#FFF23A"/>
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
<stop offset="0.1171" style="stop-color:#FED41A"/>
<stop offset="0.1964" style="stop-color:#FDC90F"/>
<stop offset="0.2809" style="stop-color:#FDC60B"/>
<stop offset="0.6685" style="stop-color:#F28F3F"/>
<stop offset="0.8876" style="stop-color:#ED693C"/>
<stop offset="1" style="stop-color:#E83E39"/>
</linearGradient>
<path class="st0" d="M97.6,83.8H2.4c-1.3,0-2.4-1.1-2.4-2.4v-1.8l17-1l19.2-4.3l16.3-1.6l16.5,0l15.8-4.7l15.1-3v16.3
C100,82.8,98.9,83.8,97.6,83.8z"/>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="19.098" y1="76.0776" x2="19.098" y2="27.8027">
<stop offset="0" style="stop-color:#FFF23A"/>
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
<stop offset="0.1171" style="stop-color:#FED41A"/>
<stop offset="0.1964" style="stop-color:#FDC90F"/>
<stop offset="0.2809" style="stop-color:#FDC60B"/>
<stop offset="0.6685" style="stop-color:#F28F3F"/>
<stop offset="0.8876" style="stop-color:#ED693C"/>
<stop offset="1" style="stop-color:#E83E39"/>
</linearGradient>
<path class="st1" d="M19.6,64.3V38.9l-5.2,3.9l-3.5-6l9.4-6.9h6.8v34.4H19.6z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="42.412" y1="76.0776" x2="42.412" y2="27.8027">
<stop offset="0" style="stop-color:#FFF23A"/>
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
<stop offset="0.1171" style="stop-color:#FED41A"/>
<stop offset="0.1964" style="stop-color:#FDC90F"/>
<stop offset="0.2809" style="stop-color:#FDC60B"/>
<stop offset="0.6685" style="stop-color:#F28F3F"/>
<stop offset="0.8876" style="stop-color:#ED693C"/>
<stop offset="1" style="stop-color:#E83E39"/>
</linearGradient>
<path class="st2" d="M53.1,39.4c0,1.1-0.1,2.2-0.4,3.2c-0.3,1-0.7,1.9-1.2,2.8c-0.5,0.9-1,1.7-1.7,2.5c-0.6,0.8-1.2,1.6-1.9,2.3
l-6.4,7.4h11.1v6.7H32.3v-6.9l10.5-12c0.8-1,1.5-2,2-3c0.5-1,0.7-2,0.7-2.9c0-1-0.2-1.9-0.7-2.6c-0.5-0.7-1.2-1.1-2.2-1.1
c-0.9,0-1.7,0.4-2.3,1.1c-0.6,0.8-1,1.9-1.1,3.3l-7.3-0.7c0.4-3.5,1.6-6.1,3.6-7.9c2-1.7,4.5-2.6,7.4-2.6c1.6,0,3,0.2,4.3,0.7
c1.3,0.5,2.3,1.2,3.2,2c0.9,0.9,1.6,1.9,2.1,3.2C52.8,36.4,53.1,37.8,53.1,39.4z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="60.3739" y1="76.0776" x2="60.3739" y2="27.8027">
<stop offset="0" style="stop-color:#FFF23A"/>
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
<stop offset="0.1171" style="stop-color:#FED41A"/>
<stop offset="0.1964" style="stop-color:#FDC90F"/>
<stop offset="0.2809" style="stop-color:#FDC60B"/>
<stop offset="0.6685" style="stop-color:#F28F3F"/>
<stop offset="0.8876" style="stop-color:#ED693C"/>
<stop offset="1" style="stop-color:#E83E39"/>
</linearGradient>
<path class="st3" d="M64.5,60.4c0,1.2-0.4,2.3-1.2,3.1c-0.8,0.8-1.8,1.3-3,1.3c-1.2,0-2.2-0.4-3-1.3c-0.8-0.8-1.1-1.9-1.1-3.1
c0-1.2,0.4-2.2,1.1-3.1c0.8-0.9,1.8-1.3,3-1.3c1.2,0,2.2,0.4,3,1.3C64.1,58.1,64.5,59.2,64.5,60.4z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="77.5234" y1="76.0776" x2="77.5234" y2="27.8027">
<stop offset="0" style="stop-color:#FFF23A"/>
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
<stop offset="0.1171" style="stop-color:#FED41A"/>
<stop offset="0.1964" style="stop-color:#FDC90F"/>
<stop offset="0.2809" style="stop-color:#FDC60B"/>
<stop offset="0.6685" style="stop-color:#F28F3F"/>
<stop offset="0.8876" style="stop-color:#ED693C"/>
<stop offset="1" style="stop-color:#E83E39"/>
</linearGradient>
<path class="st4" d="M85.5,57.4v6.9h-6.9v-6.9H66v-6.6l10.1-20.9h9.4V51H89v6.4H85.5z M78.8,37.5L78.8,37.5l-6,13.5h6V37.5z"/>
</g>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-2.852199e-02" y1="72.3985" x2="100.0976" y2="72.3985">
<stop offset="0" style="stop-color:#F28F3F"/>
<stop offset="1" style="stop-color:#F28F3F"/>
</linearGradient>
<polyline class="st5" points="0,79.7 17,78.7 36.2,74.4 52.5,72.8 69,72.9 84.9,68.1 100,65.1 "/>
</g>
</svg>
import { ReactPanelPlugin } from '@grafana/ui';
import { SingleStatOptions, defaults } from './types';
import { SingleStatPanel } from './SingleStatPanel';
import cloneDeep from 'lodash/cloneDeep';
import { SingleStatEditor } from './SingleStatEditor';
export const reactPanel = new ReactPanelPlugin<SingleStatOptions>(SingleStatPanel);
const optionsToKeep = ['valueOptions', 'stat', 'maxValue', 'maxValue', 'thresholds', 'valueMappings'];
export const singleStatOptionsCheck = (
options: Partial<SingleStatOptions>,
prevPluginId?: string,
prevOptions?: any
) => {
if (prevOptions) {
optionsToKeep.forEach(v => {
if (prevOptions.hasOwnProperty(v)) {
options[v] = cloneDeep(prevOptions.display);
}
});
}
return options;
};
reactPanel.setEditor(SingleStatEditor);
reactPanel.setDefaults(defaults);
reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);
{
"type": "panel",
"name": "Singlestat (react)",
"id": "singlestat2",
"state": "alpha",
"dataFormats": ["time_series", "table"],
"info": {
"description": "Singlestat Panel for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
},
"logos": {
"small": "img/icn-singlestat-panel.svg",
"large": "img/icn-singlestat-panel.svg"
}
}
}
import { VizOrientation, ValueMapping, Threshold } from '@grafana/ui';
export interface SingleStatBaseOptions {
valueMappings: ValueMapping[];
thresholds: Threshold[];
valueOptions: SingleStatValueOptions;
orientation: VizOrientation;
}
export interface SingleStatValueOptions {
unit: string;
suffix: string;
stat: string;
prefix: string;
decimals?: number | null;
}
export interface SingleStatOptions extends SingleStatBaseOptions {
// TODO, fill in with options from angular
}
export const defaults: SingleStatOptions = {
valueOptions: {
prefix: '',
suffix: '',
decimals: null,
stat: 'avg',
unit: 'none',
},
valueMappings: [],
thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
orientation: VizOrientation.Auto,
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment