mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Feat: Singlestat panel react progress & refactorings (#16039)
* big value component * big value component * editor for font and sparkline * less logging * remove sparkline from storybook * add display value link wrapper * follow tooltip * follow tooltip * merge master * Just minor refactoring * use series after last merge * Refactoring: moving shared singlestat stuff to grafana-ui * Refactor: Moved final getSingleStatDisplayValues func
This commit is contained in:
committed by
Torkel Ödegaard
parent
1d955a8762
commit
c8b2102500
@@ -2,12 +2,11 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Services & Utils
|
||||
import { DisplayValue, PanelProps, BarGauge } from '@grafana/ui';
|
||||
import { DisplayValue, PanelProps, BarGauge, getSingleStatDisplayValues } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
// Types
|
||||
import { BarGaugeOptions } from './types';
|
||||
import { getSingleStatValues } from '../singlestat2/SingleStatPanel';
|
||||
import { ProcessedValuesRepeater } from '../singlestat2/ProcessedValuesRepeater';
|
||||
|
||||
export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
@@ -28,7 +27,14 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
};
|
||||
|
||||
getProcessedValues = (): DisplayValue[] => {
|
||||
return getSingleStatValues(this.props);
|
||||
return getSingleStatDisplayValues({
|
||||
valueMappings: this.props.options.valueMappings,
|
||||
thresholds: this.props.options.thresholds,
|
||||
valueOptions: this.props.options.valueOptions,
|
||||
data: this.props.data,
|
||||
theme: config.theme,
|
||||
replaceVariables: this.props.replaceVariables,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui';
|
||||
import {
|
||||
ThresholdsEditor,
|
||||
ValueMappingsEditor,
|
||||
PanelOptionsGrid,
|
||||
PanelOptionsGroup,
|
||||
FormField,
|
||||
SingleStatValueOptions,
|
||||
SingleStatValueEditor,
|
||||
} from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { FormLabel, PanelEditorProps, Threshold, Select, ValueMapping } from '@grafana/ui';
|
||||
import { BarGaugeOptions, orientationOptions, displayModes } from './types';
|
||||
import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
|
||||
import { SingleStatValueOptions } from '../singlestat2/types';
|
||||
|
||||
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ReactPanelPlugin } from '@grafana/ui';
|
||||
import { ReactPanelPlugin, sharedSingleStatOptionsCheck } from '@grafana/ui';
|
||||
|
||||
import { BarGaugePanel } from './BarGaugePanel';
|
||||
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
|
||||
import { BarGaugeOptions, defaults } from './types';
|
||||
import { singleStatBaseOptionsCheck } from '../singlestat2/module';
|
||||
|
||||
export const reactPanel = new ReactPanelPlugin<BarGaugeOptions>(BarGaugePanel)
|
||||
.setDefaults(defaults)
|
||||
.setEditor(BarGaugePanelEditor)
|
||||
.setPanelChangeHandler(singleStatBaseOptionsCheck);
|
||||
.setPanelChangeHandler(sharedSingleStatOptionsCheck);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { VizOrientation, SelectOptionItem, StatID } from '@grafana/ui';
|
||||
import { SingleStatBaseOptions } from '../singlestat2/types';
|
||||
import { VizOrientation, SelectOptionItem, StatID, SingleStatBaseOptions } from '@grafana/ui';
|
||||
|
||||
export interface BarGaugeOptions extends SingleStatBaseOptions {
|
||||
minValue: number;
|
||||
|
||||
@@ -9,8 +9,7 @@ import { Gauge } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { GaugeOptions } from './types';
|
||||
import { DisplayValue, PanelProps } from '@grafana/ui';
|
||||
import { getSingleStatValues } from '../singlestat2/SingleStatPanel';
|
||||
import { DisplayValue, PanelProps, getSingleStatDisplayValues } from '@grafana/ui';
|
||||
import { ProcessedValuesRepeater } from '../singlestat2/ProcessedValuesRepeater';
|
||||
|
||||
export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
||||
@@ -33,7 +32,14 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
||||
};
|
||||
|
||||
getProcessedValues = (): DisplayValue[] => {
|
||||
return getSingleStatValues(this.props);
|
||||
return getSingleStatDisplayValues({
|
||||
valueMappings: this.props.options.valueMappings,
|
||||
thresholds: this.props.options.thresholds,
|
||||
valueOptions: this.props.options.valueOptions,
|
||||
data: this.props.data,
|
||||
theme: config.theme,
|
||||
replaceVariables: this.props.replaceVariables,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
PanelOptionsGrid,
|
||||
ValueMappingsEditor,
|
||||
ValueMapping,
|
||||
SingleStatValueOptions,
|
||||
SingleStatValueEditor,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { GaugeOptionsBox } from './GaugeOptionsBox';
|
||||
import { GaugeOptions } from './types';
|
||||
import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
|
||||
import { SingleStatValueOptions } from '../singlestat2/types';
|
||||
|
||||
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { ReactPanelPlugin } from '@grafana/ui';
|
||||
|
||||
import { ReactPanelPlugin, sharedSingleStatMigrationCheck, sharedSingleStatOptionsCheck } from '@grafana/ui';
|
||||
import { GaugePanelEditor } from './GaugePanelEditor';
|
||||
import { GaugePanel } from './GaugePanel';
|
||||
import { GaugeOptions, defaults } from './types';
|
||||
import { singleStatBaseOptionsCheck, singleStatMigrationCheck } from '../singlestat2/module';
|
||||
|
||||
export const reactPanel = new ReactPanelPlugin<GaugeOptions>(GaugePanel)
|
||||
.setDefaults(defaults)
|
||||
.setEditor(GaugePanelEditor)
|
||||
.setPanelChangeHandler(singleStatBaseOptionsCheck)
|
||||
.setMigrationHandler(singleStatMigrationCheck);
|
||||
.setPanelChangeHandler(sharedSingleStatOptionsCheck)
|
||||
.setMigrationHandler(sharedSingleStatMigrationCheck);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { SingleStatBaseOptions } from '../singlestat2/types';
|
||||
import { VizOrientation, StatID } from '@grafana/ui';
|
||||
import { VizOrientation, StatID, SingleStatBaseOptions } from '@grafana/ui';
|
||||
|
||||
export interface GaugeOptions extends SingleStatBaseOptions {
|
||||
maxValue: number;
|
||||
|
||||
@@ -5,20 +5,26 @@ import React, { PureComponent } from 'react';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
// Components
|
||||
import { PieChart } from '@grafana/ui';
|
||||
import { PieChart, getSingleStatDisplayValues } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { PieChartOptions } from './types';
|
||||
import { PanelProps } from '@grafana/ui/src/types';
|
||||
import { getSingleStatValues } from '../singlestat2/SingleStatPanel';
|
||||
|
||||
interface Props extends PanelProps<PieChartOptions> {}
|
||||
|
||||
export class PieChartPanel extends PureComponent<Props> {
|
||||
render() {
|
||||
const { width, height, options } = this.props;
|
||||
const { width, height, options, data, replaceVariables } = this.props;
|
||||
|
||||
const values = getSingleStatValues(this.props);
|
||||
const values = getSingleStatDisplayValues({
|
||||
valueMappings: options.valueMappings,
|
||||
thresholds: options.thresholds,
|
||||
valueOptions: options.valueOptions,
|
||||
data: data,
|
||||
theme: config.theme,
|
||||
replaceVariables: replaceVariables,
|
||||
});
|
||||
|
||||
return (
|
||||
<PieChart
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { PanelEditorProps, PanelOptionsGrid, ValueMappingsEditor, ValueMapping } from '@grafana/ui';
|
||||
import {
|
||||
PanelEditorProps,
|
||||
PanelOptionsGrid,
|
||||
ValueMappingsEditor,
|
||||
ValueMapping,
|
||||
SingleStatValueOptions,
|
||||
SingleStatValueEditor,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { PieChartOptionsBox } from './PieChartOptionsBox';
|
||||
import { PieChartOptions } from './types';
|
||||
import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
|
||||
import { SingleStatValueOptions } from '../singlestat2/types';
|
||||
|
||||
export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChartOptions>> {
|
||||
onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { PieChartType, StatID, VizOrientation } from '@grafana/ui';
|
||||
import { SingleStatBaseOptions } from '../singlestat2/types';
|
||||
import { PieChartType, StatID, VizOrientation, SingleStatBaseOptions } from '@grafana/ui';
|
||||
|
||||
export interface PieChartOptions extends SingleStatBaseOptions {
|
||||
pieType: PieChartType;
|
||||
|
||||
68
public/app/plugins/panel/singlestat2/ColoringEditor.tsx
Normal file
68
public/app/plugins/panel/singlestat2/ColoringEditor.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { Switch, PanelOptionsGroup } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { SingleStatOptions } from './types';
|
||||
|
||||
const labelWidth = 6;
|
||||
|
||||
export interface Props {
|
||||
options: SingleStatOptions;
|
||||
onChange: (options: SingleStatOptions) => void;
|
||||
}
|
||||
|
||||
// colorBackground?: boolean;
|
||||
// colorValue?: boolean;
|
||||
// colorPrefix?: boolean;
|
||||
// colorPostfix?: boolean;
|
||||
|
||||
export class ColoringEditor extends PureComponent<Props> {
|
||||
onToggleColorBackground = () =>
|
||||
this.props.onChange({ ...this.props.options, colorBackground: !this.props.options.colorBackground });
|
||||
|
||||
onToggleColorValue = () => this.props.onChange({ ...this.props.options, colorValue: !this.props.options.colorValue });
|
||||
|
||||
onToggleColorPrefix = () =>
|
||||
this.props.onChange({ ...this.props.options, colorPrefix: !this.props.options.colorPrefix });
|
||||
|
||||
onToggleColorPostfix = () =>
|
||||
this.props.onChange({ ...this.props.options, colorPostfix: !this.props.options.colorPostfix });
|
||||
|
||||
render() {
|
||||
const { colorBackground, colorValue, colorPrefix, colorPostfix } = this.props.options;
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="Coloring">
|
||||
<Switch
|
||||
label="Background"
|
||||
labelClass={`width-${labelWidth}`}
|
||||
checked={colorBackground}
|
||||
onChange={this.onToggleColorBackground}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label="Value"
|
||||
labelClass={`width-${labelWidth}`}
|
||||
checked={colorValue}
|
||||
onChange={this.onToggleColorValue}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label="Prefix"
|
||||
labelClass={`width-${labelWidth}`}
|
||||
checked={colorPrefix}
|
||||
onChange={this.onToggleColorPrefix}
|
||||
/>
|
||||
<Switch
|
||||
label="Postfix"
|
||||
labelClass={`width-${labelWidth}`}
|
||||
checked={colorPostfix}
|
||||
onChange={this.onToggleColorPostfix}
|
||||
/>
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
67
public/app/plugins/panel/singlestat2/FontSizeEditor.tsx
Normal file
67
public/app/plugins/panel/singlestat2/FontSizeEditor.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { FormLabel, Select, PanelOptionsGroup, SelectOptionItem } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { SingleStatOptions } from './types';
|
||||
|
||||
const labelWidth = 6;
|
||||
|
||||
export interface Props {
|
||||
options: SingleStatOptions;
|
||||
onChange: (options: SingleStatOptions) => void;
|
||||
}
|
||||
|
||||
const percents = ['20%', '30%', '50%', '70%', '80%', '100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
const fontSizeOptions = percents.map(v => {
|
||||
return { value: v, label: v };
|
||||
});
|
||||
|
||||
export class FontSizeEditor extends PureComponent<Props> {
|
||||
setPrefixFontSize = (v: SelectOptionItem) => this.props.onChange({ ...this.props.options, prefixFontSize: v.value });
|
||||
|
||||
setValueFontSize = (v: SelectOptionItem) => this.props.onChange({ ...this.props.options, valueFontSize: v.value });
|
||||
|
||||
setPostfixFontSize = (v: SelectOptionItem) =>
|
||||
this.props.onChange({ ...this.props.options, postfixFontSize: v.value });
|
||||
|
||||
render() {
|
||||
const { prefixFontSize, valueFontSize, postfixFontSize } = this.props.options;
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="Font Size">
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Prefix</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={fontSizeOptions}
|
||||
onChange={this.setPrefixFontSize}
|
||||
value={fontSizeOptions.find(option => option.value === prefixFontSize)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Value</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={fontSizeOptions}
|
||||
onChange={this.setValueFontSize}
|
||||
value={fontSizeOptions.find(option => option.value === valueFontSize)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Postfix</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={fontSizeOptions}
|
||||
onChange={this.setPostfixFontSize}
|
||||
value={fontSizeOptions.find(option => option.value === postfixFontSize)}
|
||||
/>
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,14 @@ import {
|
||||
PanelOptionsGrid,
|
||||
ValueMappingsEditor,
|
||||
ValueMapping,
|
||||
SingleStatValueOptions,
|
||||
SingleStatValueEditor,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { SingleStatOptions, SingleStatValueOptions } from './types';
|
||||
import { SingleStatValueEditor } from './SingleStatValueEditor';
|
||||
import { SingleStatOptions, SparklineOptions } from './types';
|
||||
import { ColoringEditor } from './ColoringEditor';
|
||||
import { FontSizeEditor } from './FontSizeEditor';
|
||||
import { SparklineEditor } from './SparklineEditor';
|
||||
|
||||
export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatOptions>> {
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
@@ -31,6 +35,12 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
|
||||
valueOptions,
|
||||
});
|
||||
|
||||
onSparklineChanged = (sparkline: SparklineOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
sparkline,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
|
||||
@@ -38,6 +48,10 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
|
||||
<FontSizeEditor options={options} onChange={this.props.onOptionsChange} />
|
||||
<ColoringEditor options={options} onChange={this.props.onOptionsChange} />
|
||||
<SparklineEditor options={options.sparkline} onChange={this.onSparklineChanged} />
|
||||
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
|
||||
@@ -1,82 +1,127 @@
|
||||
// Libraries
|
||||
import React, { PureComponent, CSSProperties } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { SingleStatOptions, SingleStatBaseOptions } from './types';
|
||||
|
||||
import { DisplayValue, PanelProps, NullValueMode, FieldType, calculateStats } from '@grafana/ui';
|
||||
// Utils & Services
|
||||
import { config } from 'app/core/config';
|
||||
import { getDisplayProcessor } from '@grafana/ui';
|
||||
import { getFlotPairs } from '@grafana/ui/src/utils/flotPairs';
|
||||
|
||||
// Components
|
||||
import { ProcessedValuesRepeater } from './ProcessedValuesRepeater';
|
||||
|
||||
export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): DisplayValue[] => {
|
||||
const { data, replaceVariables, options } = props;
|
||||
const { valueOptions, valueMappings } = options;
|
||||
const { unit, decimals, stat } = valueOptions;
|
||||
// Types
|
||||
import { SingleStatOptions } from './types';
|
||||
import { BigValueSparkline, BigValue } from '@grafana/ui/src/components/BigValue/BigValue';
|
||||
import {
|
||||
DisplayValue,
|
||||
PanelProps,
|
||||
getDisplayProcessor,
|
||||
NullValueMode,
|
||||
FieldType,
|
||||
calculateStats,
|
||||
getFirstTimeField,
|
||||
} from '@grafana/ui';
|
||||
|
||||
const display = getDisplayProcessor({
|
||||
unit,
|
||||
decimals,
|
||||
mappings: valueMappings,
|
||||
thresholds: options.thresholds,
|
||||
prefix: replaceVariables(valueOptions.prefix),
|
||||
suffix: replaceVariables(valueOptions.suffix),
|
||||
theme: config.theme,
|
||||
});
|
||||
|
||||
const values: DisplayValue[] = [];
|
||||
|
||||
for (const series of data) {
|
||||
if (stat === 'name') {
|
||||
values.push(display(series.name));
|
||||
}
|
||||
|
||||
for (let i = 0; i < series.fields.length; i++) {
|
||||
const column = series.fields[i];
|
||||
|
||||
// Show all columns that are not 'time'
|
||||
if (column.type === FieldType.number) {
|
||||
const stats = calculateStats({
|
||||
series,
|
||||
fieldIndex: i,
|
||||
stats: [stat], // The stats to calculate
|
||||
nullValueMode: NullValueMode.Null,
|
||||
});
|
||||
const displayValue = display(stats[stat]);
|
||||
values.push(displayValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (values.length === 0) {
|
||||
values.push({
|
||||
numeric: 0,
|
||||
text: 'No data',
|
||||
});
|
||||
}
|
||||
|
||||
return values;
|
||||
};
|
||||
interface SingleStatDisplay {
|
||||
value: DisplayValue;
|
||||
prefix?: DisplayValue;
|
||||
suffix?: DisplayValue;
|
||||
sparkline?: BigValueSparkline;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>> {
|
||||
renderValue = (value: DisplayValue, width: number, height: number): JSX.Element => {
|
||||
const style: CSSProperties = {};
|
||||
style.margin = '0 auto';
|
||||
style.fontSize = '250%';
|
||||
style.textAlign = 'center';
|
||||
if (value.color) {
|
||||
style.color = value.color;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ width, height }}>
|
||||
<div style={style}>{value.text}</div>
|
||||
</div>
|
||||
);
|
||||
renderValue = (value: SingleStatDisplay, width: number, height: number): JSX.Element => {
|
||||
return <BigValue {...value} width={width} height={height} theme={config.theme} />;
|
||||
};
|
||||
|
||||
getProcessedValues = (): DisplayValue[] => {
|
||||
return getSingleStatValues(this.props);
|
||||
getProcessedValues = (): SingleStatDisplay[] => {
|
||||
const { data, replaceVariables, options, timeRange } = this.props;
|
||||
const { valueOptions, valueMappings } = options;
|
||||
|
||||
const display = getDisplayProcessor({
|
||||
unit: valueOptions.unit,
|
||||
decimals: valueOptions.decimals,
|
||||
mappings: valueMappings,
|
||||
thresholds: options.thresholds,
|
||||
theme: config.theme,
|
||||
});
|
||||
|
||||
const { colorBackground, colorValue, colorPrefix, colorPostfix, sparkline } = options;
|
||||
const { stat } = valueOptions;
|
||||
|
||||
const values: SingleStatDisplay[] = [];
|
||||
|
||||
for (const series of data) {
|
||||
const timeColumn = sparkline.show ? getFirstTimeField(series) : -1;
|
||||
|
||||
for (let i = 0; i < series.fields.length; i++) {
|
||||
const column = series.fields[i];
|
||||
|
||||
// Show all fields that are not 'time'
|
||||
if (column.type === FieldType.number) {
|
||||
const stats = calculateStats({
|
||||
series,
|
||||
fieldIndex: i,
|
||||
stats: [stat], // The stats to calculate
|
||||
nullValueMode: NullValueMode.Null,
|
||||
});
|
||||
|
||||
const v: SingleStatDisplay = {
|
||||
value: display(stats[stat]),
|
||||
};
|
||||
|
||||
const color = v.value.color;
|
||||
if (!colorValue) {
|
||||
delete v.value.color;
|
||||
}
|
||||
|
||||
if (colorBackground) {
|
||||
v.backgroundColor = color;
|
||||
}
|
||||
|
||||
if (options.valueFontSize) {
|
||||
v.value.fontSize = options.valueFontSize;
|
||||
}
|
||||
|
||||
if (valueOptions.prefix) {
|
||||
v.prefix = {
|
||||
text: replaceVariables(valueOptions.prefix),
|
||||
numeric: NaN,
|
||||
color: colorPrefix ? color : null,
|
||||
fontSize: options.prefixFontSize,
|
||||
};
|
||||
}
|
||||
if (valueOptions.suffix) {
|
||||
v.suffix = {
|
||||
text: replaceVariables(valueOptions.suffix),
|
||||
numeric: NaN,
|
||||
color: colorPostfix ? color : null,
|
||||
fontSize: options.postfixFontSize,
|
||||
};
|
||||
}
|
||||
|
||||
if (sparkline.show && timeColumn >= 0) {
|
||||
const points = getFlotPairs({
|
||||
series,
|
||||
xIndex: timeColumn,
|
||||
yIndex: i,
|
||||
nullValueMode: NullValueMode.Null,
|
||||
});
|
||||
|
||||
v.sparkline = {
|
||||
...sparkline,
|
||||
data: points,
|
||||
minX: timeRange.from.valueOf(),
|
||||
maxX: timeRange.to.valueOf(),
|
||||
};
|
||||
}
|
||||
|
||||
values.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { FormField, FormLabel, PanelOptionsGroup, StatsPicker, UnitPicker, StatID } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { SingleStatValueOptions } from './types';
|
||||
|
||||
const labelWidth = 6;
|
||||
|
||||
export interface Props {
|
||||
options: SingleStatValueOptions;
|
||||
onChange: (valueOptions: SingleStatValueOptions) => void;
|
||||
}
|
||||
|
||||
export class SingleStatValueEditor extends PureComponent<Props> {
|
||||
onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value });
|
||||
|
||||
onStatsChange = stats => {
|
||||
const stat = stats[0] || StatID.mean;
|
||||
this.props.onChange({ ...this.props.options, stat });
|
||||
};
|
||||
|
||||
onDecimalChange = event => {
|
||||
if (!isNaN(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 });
|
||||
onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value });
|
||||
|
||||
render() {
|
||||
const { stat, unit, decimals, prefix, suffix } = this.props.options;
|
||||
|
||||
let decimalsString = '';
|
||||
if (Number.isFinite(decimals)) {
|
||||
decimalsString = decimals.toString();
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="Value">
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Show</FormLabel>
|
||||
<StatsPicker
|
||||
width={12}
|
||||
placeholder="Choose Stat"
|
||||
defaultStat={StatID.mean}
|
||||
allowMultiple={false}
|
||||
stats={[stat]}
|
||||
onChange={this.onStatsChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Unit</FormLabel>
|
||||
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
|
||||
</div>
|
||||
<FormField
|
||||
label="Decimals"
|
||||
labelWidth={labelWidth}
|
||||
placeholder="auto"
|
||||
onChange={this.onDecimalChange}
|
||||
value={decimalsString}
|
||||
type="number"
|
||||
/>
|
||||
<FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
|
||||
<FormField label="Suffix" labelWidth={labelWidth} onChange={this.onSuffixChange} value={suffix || ''} />
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
33
public/app/plugins/panel/singlestat2/SparklineEditor.tsx
Normal file
33
public/app/plugins/panel/singlestat2/SparklineEditor.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { Switch, PanelOptionsGroup } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { SparklineOptions } from './types';
|
||||
|
||||
const labelWidth = 6;
|
||||
|
||||
export interface Props {
|
||||
options: SparklineOptions;
|
||||
onChange: (options: SparklineOptions) => void;
|
||||
}
|
||||
|
||||
export class SparklineEditor extends PureComponent<Props> {
|
||||
onToggleShow = () => this.props.onChange({ ...this.props.options, show: !this.props.options.show });
|
||||
|
||||
onToggleFull = () => this.props.onChange({ ...this.props.options, full: !this.props.options.full });
|
||||
|
||||
render() {
|
||||
const { show, full } = this.props.options;
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="Sparkline">
|
||||
<Switch label="Show" labelClass={`width-${labelWidth}`} checked={show} onChange={this.onToggleShow} />
|
||||
|
||||
<Switch label="Full Height" labelClass={`width-${labelWidth}`} checked={full} onChange={this.onToggleFull} />
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,10 @@
|
||||
import { ReactPanelPlugin, getStatsCalculators, PanelModel } from '@grafana/ui';
|
||||
import { SingleStatOptions, defaults, SingleStatBaseOptions } from './types';
|
||||
import { ReactPanelPlugin, sharedSingleStatMigrationCheck, sharedSingleStatOptionsCheck } from '@grafana/ui';
|
||||
import { SingleStatOptions, defaults } from './types';
|
||||
import { SingleStatPanel } from './SingleStatPanel';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { SingleStatEditor } from './SingleStatEditor';
|
||||
|
||||
const optionsToKeep = ['valueOptions', 'stat', 'maxValue', 'maxValue', 'thresholds', 'valueMappings'];
|
||||
|
||||
export const singleStatBaseOptionsCheck = (
|
||||
options: Partial<SingleStatBaseOptions>,
|
||||
prevPluginId: string,
|
||||
prevOptions: any
|
||||
) => {
|
||||
for (const k of optionsToKeep) {
|
||||
if (prevOptions.hasOwnProperty(k)) {
|
||||
options[k] = cloneDeep(prevOptions[k]);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
export const singleStatMigrationCheck = (panel: PanelModel<SingleStatOptions>) => {
|
||||
const options = panel.options;
|
||||
|
||||
if (!options) {
|
||||
// This happens on the first load or when migrating from angular
|
||||
return {};
|
||||
}
|
||||
|
||||
if (options.valueOptions) {
|
||||
// 6.1 renamed some stats, This makes sure they are up to date
|
||||
// avg -> mean, current -> last, total -> sum
|
||||
const { valueOptions } = options;
|
||||
if (valueOptions && valueOptions.stat) {
|
||||
valueOptions.stat = getStatsCalculators([valueOptions.stat]).map(s => s.id)[0];
|
||||
}
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
export const reactPanel = new ReactPanelPlugin<SingleStatOptions>(SingleStatPanel)
|
||||
.setDefaults(defaults)
|
||||
.setEditor(SingleStatEditor)
|
||||
.setPanelChangeHandler(singleStatMigrationCheck)
|
||||
.setMigrationHandler(singleStatMigrationCheck);
|
||||
.setPanelChangeHandler(sharedSingleStatOptionsCheck)
|
||||
.setMigrationHandler(sharedSingleStatMigrationCheck);
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
import { VizOrientation, ValueMapping, Threshold, StatID } from '@grafana/ui';
|
||||
import { VizOrientation, StatID, SingleStatBaseOptions } 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 SparklineOptions {
|
||||
show: boolean;
|
||||
full: boolean; // full height
|
||||
fillColor: string;
|
||||
lineColor: string;
|
||||
}
|
||||
|
||||
// Structure copied from angular
|
||||
export interface SingleStatOptions extends SingleStatBaseOptions {
|
||||
// TODO, fill in with options from angular
|
||||
prefixFontSize?: string;
|
||||
valueFontSize?: string;
|
||||
postfixFontSize?: string;
|
||||
|
||||
colorBackground?: boolean;
|
||||
colorValue?: boolean;
|
||||
colorPrefix?: boolean;
|
||||
colorPostfix?: boolean;
|
||||
|
||||
sparkline: SparklineOptions;
|
||||
}
|
||||
|
||||
export const defaults: SingleStatOptions = {
|
||||
sparkline: {
|
||||
show: true,
|
||||
full: false,
|
||||
lineColor: 'rgb(31, 120, 193)',
|
||||
fillColor: 'rgba(31, 118, 189, 0.18)',
|
||||
},
|
||||
|
||||
valueOptions: {
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
|
||||
Reference in New Issue
Block a user