mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
revert most options sharing
This commit is contained in:
@@ -1,17 +1,11 @@
|
||||
import { PanelData, NullValueMode, SingleStatValueInfo } from '../types';
|
||||
import { processTimeSeries } from './processTimeSeries';
|
||||
import { DisplayValueOptions } from './displayValue';
|
||||
|
||||
export interface SingleStatProcessingOptions {
|
||||
panelData: PanelData;
|
||||
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
|
||||
//
|
||||
|
@@ -25,6 +25,7 @@ import * as heatmapPanel from 'app/plugins/panel/heatmap/module';
|
||||
import * as tablePanel from 'app/plugins/panel/table/module';
|
||||
import * as table2Panel from 'app/plugins/panel/table2/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 gaugePanel from 'app/plugins/panel/gauge/module';
|
||||
import * as barGaugePanel from 'app/plugins/panel/bargauge/module';
|
||||
@@ -57,6 +58,7 @@ const builtInPlugins = {
|
||||
'app/plugins/panel/table/module': tablePanel,
|
||||
'app/plugins/panel/table2/module': table2Panel,
|
||||
'app/plugins/panel/singlestat/module': singlestatPanel,
|
||||
'app/plugins/panel/singlestat2/module': singlestatPanel2,
|
||||
'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
|
||||
'app/plugins/panel/gauge/module': gaugePanel,
|
||||
'app/plugins/panel/bargauge/module': barGaugePanel,
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
|
||||
// Services & Utils
|
||||
import { DisplayValue, VizOrientation } from '@grafana/ui';
|
||||
import { DisplayValue } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
// Components
|
||||
@@ -10,17 +10,11 @@ import { BarGauge } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { BarGaugeOptions } from './types';
|
||||
import { SingleStatPanel } from '../gauge/SingleStatPanel';
|
||||
|
||||
export class BarGaugePanel extends SingleStatPanel<BarGaugeOptions> {
|
||||
getOrientation(): VizOrientation {
|
||||
const { options } = this.props;
|
||||
return options.orientation;
|
||||
}
|
||||
import { SingleStatBase } from '../singlestat2/SingleStatBase';
|
||||
|
||||
export class BarGaugePanel extends SingleStatBase<BarGaugeOptions> {
|
||||
renderStat(value: DisplayValue, width: number, height: number) {
|
||||
const { options } = this.props;
|
||||
const { display } = options;
|
||||
|
||||
return (
|
||||
<BarGauge
|
||||
@@ -28,7 +22,7 @@ export class BarGaugePanel extends SingleStatPanel<BarGaugeOptions> {
|
||||
width={width}
|
||||
height={height}
|
||||
orientation={options.orientation}
|
||||
thresholds={display.thresholds}
|
||||
thresholds={options.thresholds}
|
||||
theme={config.theme}
|
||||
/>
|
||||
);
|
||||
|
@@ -2,38 +2,31 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
|
||||
import {
|
||||
PanelOptionsGrid,
|
||||
PanelOptionsGroup,
|
||||
FormField,
|
||||
DisplayValueOptions,
|
||||
ThresholdsEditor,
|
||||
Threshold,
|
||||
} from '@grafana/ui';
|
||||
import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui';
|
||||
|
||||
// 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 { DisplayValueEditor } from '../gauge/DisplayValueEditor';
|
||||
import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
|
||||
import { SingleStatValueOptions } from '../singlestat2/types';
|
||||
|
||||
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
|
||||
onDisplayOptionsChanged = (displayOptions: DisplayValueOptions) =>
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
display: displayOptions,
|
||||
});
|
||||
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.display,
|
||||
thresholds,
|
||||
});
|
||||
|
||||
onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.display,
|
||||
mappings: valueMappings,
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
valueMappings,
|
||||
});
|
||||
|
||||
onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
valueOptions,
|
||||
});
|
||||
|
||||
onMinValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, minValue: target.value });
|
||||
@@ -41,17 +34,12 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
|
||||
onOrientationChange = ({ value }) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
|
||||
|
||||
render() {
|
||||
const { onOptionsChange, options } = this.props;
|
||||
const { display } = options;
|
||||
const { options } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
{/* This just sets the 'stats', that should be moved to somethign more general */}
|
||||
<SingleStatValueEditor onChange={onOptionsChange} options={options} />
|
||||
|
||||
<DisplayValueEditor onChange={this.onDisplayOptionsChanged} options={display} />
|
||||
|
||||
<SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
|
||||
<PanelOptionsGroup title="Gauge">
|
||||
<FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} />
|
||||
<FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={options.maxValue} />
|
||||
@@ -66,9 +54,10 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
|
||||
/>
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={display.thresholds} />
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={display.mappings} />
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -3,10 +3,10 @@ import { ReactPanelPlugin } from '@grafana/ui';
|
||||
import { BarGaugePanel } from './BarGaugePanel';
|
||||
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
|
||||
import { BarGaugeOptions, defaults } from './types';
|
||||
import { gaugePanelTypeChangedHook } from '../gauge/module';
|
||||
import { singleStatOptionsCheck } from '../singlestat2/module';
|
||||
|
||||
export const reactPanel = new ReactPanelPlugin<BarGaugeOptions>(BarGaugePanel);
|
||||
|
||||
reactPanel.setEditor(BarGaugePanelEditor);
|
||||
reactPanel.setDefaults(defaults);
|
||||
reactPanel.setPanelTypeChangedHook(gaugePanelTypeChangedHook);
|
||||
reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);
|
||||
|
@@ -1,17 +1,28 @@
|
||||
import { SelectOptionItem, VizOrientation } from '@grafana/ui';
|
||||
import { VizOrientation, SelectOptionItem } from '@grafana/ui';
|
||||
|
||||
import { GaugeOptions, defaults as gaugeDefaults } from '../gauge/types';
|
||||
|
||||
export interface BarGaugeOptions extends GaugeOptions {
|
||||
orientation: VizOrientation;
|
||||
}
|
||||
import { SingleStatBaseOptions } from '../singlestat2/types';
|
||||
|
||||
export const orientationOptions: SelectOptionItem[] = [
|
||||
{ value: VizOrientation.Horizontal, label: 'Horizontal' },
|
||||
{ value: VizOrientation.Vertical, label: 'Vertical' },
|
||||
];
|
||||
|
||||
export interface BarGaugeOptions extends SingleStatBaseOptions {
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
}
|
||||
|
||||
export const defaults: BarGaugeOptions = {
|
||||
...gaugeDefaults,
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
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';
|
||||
// Types
|
||||
import { GaugeOptions } from './types';
|
||||
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) {
|
||||
const { options } = this.props;
|
||||
const { display } = options;
|
||||
|
||||
return (
|
||||
<Gauge
|
||||
value={value}
|
||||
width={width}
|
||||
height={height}
|
||||
thresholds={display.thresholds}
|
||||
thresholds={options.thresholds}
|
||||
showThresholdLabels={options.showThresholdLabels}
|
||||
showThresholdMarkers={options.showThresholdMarkers}
|
||||
minValue={options.minValue}
|
||||
|
@@ -9,46 +9,42 @@ import {
|
||||
ValueMapping,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
|
||||
import { GaugeOptionsBox } from './GaugeOptionsBox';
|
||||
import { GaugeOptions } from './types';
|
||||
import { DisplayValueEditor } from './DisplayValueEditor';
|
||||
import { DisplayValueOptions } from '@grafana/ui';
|
||||
import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
|
||||
import { SingleStatValueOptions } from '../singlestat2/types';
|
||||
|
||||
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
|
||||
onDisplayOptionsChanged = (displayOptions: DisplayValueOptions) =>
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
display: displayOptions,
|
||||
});
|
||||
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.display,
|
||||
thresholds,
|
||||
});
|
||||
|
||||
onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.display,
|
||||
mappings: valueMappings,
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
valueMappings,
|
||||
});
|
||||
|
||||
onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
valueOptions,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { onOptionsChange, options } = this.props;
|
||||
const { display } = options;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
{/* This just sets the 'stats', that should be moved to somethign more general */}
|
||||
<SingleStatValueEditor onChange={onOptionsChange} options={options} />
|
||||
<DisplayValueEditor onChange={this.onDisplayOptionsChanged} options={display} />
|
||||
<SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
|
||||
<GaugeOptionsBox onOptionsChange={onOptionsChange} options={options} />
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={display.thresholds} />
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={display.mappings} />
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -1,51 +0,0 @@
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
// 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",
|
||||
}
|
||||
`;
|
@@ -1,27 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,58 +1,12 @@
|
||||
import { ReactPanelPlugin, DisplayValueOptions } from '@grafana/ui';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { ReactPanelPlugin } from '@grafana/ui';
|
||||
|
||||
import { GaugePanelEditor } from './GaugePanelEditor';
|
||||
import { GaugePanel } from './GaugePanel';
|
||||
import { GaugeOptions, defaults } from './types';
|
||||
import { singleStatOptionsCheck } from '../singlestat2/module';
|
||||
|
||||
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.setDefaults(defaults);
|
||||
reactPanel.setPanelTypeChangedHook(gaugePanelTypeChangedHook);
|
||||
reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
minValue: number;
|
||||
showThresholdLabels: boolean;
|
||||
@@ -12,14 +13,14 @@ export const defaults: GaugeOptions = {
|
||||
maxValue: 100,
|
||||
showThresholdMarkers: true,
|
||||
showThresholdLabels: false,
|
||||
|
||||
stat: 'avg',
|
||||
display: {
|
||||
valueOptions: {
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
decimals: null,
|
||||
stat: 'avg',
|
||||
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,
|
||||
};
|
||||
|
9
public/app/plugins/panel/singlestat2/README.md
Normal file
9
public/app/plugins/panel/singlestat2/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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/)
|
@@ -1,21 +1,16 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Services & Utils
|
||||
import { processSingleStatPanelData, SingleStatOptions, DisplayValue, PanelProps, VizOrientation } from '@grafana/ui';
|
||||
import { processSingleStatPanelData, DisplayValue, PanelProps } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
// Components
|
||||
import { VizRepeater, getDisplayProcessor } from '@grafana/ui';
|
||||
import { SingleStatBaseOptions } from './types';
|
||||
|
||||
interface State {
|
||||
export interface State {
|
||||
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>) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
values: this.findDisplayValues(props),
|
||||
};
|
||||
@@ -29,18 +24,20 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<
|
||||
|
||||
findDisplayValues(props: PanelProps<T>): DisplayValue[] {
|
||||
const { panelData, replaceVariables, options } = this.props;
|
||||
const { display } = options;
|
||||
|
||||
const { valueOptions, valueMappings } = options;
|
||||
const processor = getDisplayProcessor({
|
||||
...display,
|
||||
prefix: replaceVariables(display.prefix),
|
||||
suffix: replaceVariables(display.suffix),
|
||||
unit: valueOptions.unit,
|
||||
decimals: valueOptions.decimals,
|
||||
mappings: valueMappings,
|
||||
thresholds: options.thresholds,
|
||||
|
||||
prefix: replaceVariables(valueOptions.prefix),
|
||||
suffix: replaceVariables(valueOptions.suffix),
|
||||
theme: config.theme,
|
||||
});
|
||||
|
||||
return processSingleStatPanelData({
|
||||
panelData: panelData,
|
||||
stat: options.stat,
|
||||
stat: valueOptions.stat,
|
||||
}).map(stat => processor(stat.value));
|
||||
}
|
||||
|
||||
@@ -51,17 +48,12 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<
|
||||
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() {
|
||||
const { height, width } = this.props;
|
||||
const { height, width, options } = this.props;
|
||||
const { orientation } = options;
|
||||
const { values } = this.state;
|
||||
|
||||
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)}
|
||||
</VizRepeater>
|
||||
);
|
48
public/app/plugins/panel/singlestat2/SingleStatEditor.tsx
Normal file
48
public/app/plugins/panel/singlestat2/SingleStatEditor.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
17
public/app/plugins/panel/singlestat2/SingleStatPanel.tsx
Normal file
17
public/app/plugins/panel/singlestat2/SingleStatPanel.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { FormField, FormLabel, PanelOptionsGroup, UnitPicker } from '@grafana/ui';
|
||||
import { FormField, FormLabel, PanelOptionsGroup, Select, UnitPicker } from '@grafana/ui';
|
||||
|
||||
// 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;
|
||||
|
||||
export interface Props {
|
||||
options: DisplayValueOptions;
|
||||
onChange: (options: DisplayValueOptions) => void;
|
||||
options: SingleStatValueOptions;
|
||||
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 });
|
||||
onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
|
||||
|
||||
onDecimalChange = event => {
|
||||
if (!isNaN(event.target.value)) {
|
||||
@@ -35,7 +50,7 @@ export class DisplayValueEditor extends PureComponent<Props> {
|
||||
onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value });
|
||||
|
||||
render() {
|
||||
const { unit, decimals, prefix, suffix } = this.props.options;
|
||||
const { stat, unit, decimals, prefix, suffix } = this.props.options;
|
||||
|
||||
let decimalsString = '';
|
||||
if (Number.isFinite(decimals)) {
|
||||
@@ -43,7 +58,16 @@ export class DisplayValueEditor extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
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">
|
||||
<FormLabel width={labelWidth}>Unit</FormLabel>
|
||||
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
|
@@ -0,0 +1,83 @@
|
||||
<?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>
|
After Width: | Height: | Size: 4.9 KiB |
29
public/app/plugins/panel/singlestat2/module.tsx
Normal file
29
public/app/plugins/panel/singlestat2/module.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
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);
|
20
public/app/plugins/panel/singlestat2/plugin.json
Normal file
20
public/app/plugins/panel/singlestat2/plugin.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
33
public/app/plugins/panel/singlestat2/types.ts
Normal file
33
public/app/plugins/panel/singlestat2/types.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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,
|
||||
};
|
Reference in New Issue
Block a user