mirror of https://github.com/grafana/grafana
Merge pull request #15925 from ryantxu/reusable-formatting-options
make value processing/formatting more reusablepull/16035/head
commit
bfa54d2e26
@ -0,0 +1,157 @@ |
|||||||
|
import { getDisplayProcessor, getColorFromThreshold, DisplayProcessor, DisplayValue } from './displayValue'; |
||||||
|
import { MappingType, ValueMapping } from '../types/panel'; |
||||||
|
|
||||||
|
function assertSame(input: any, processors: DisplayProcessor[], match: DisplayValue) { |
||||||
|
processors.forEach(processor => { |
||||||
|
const value = processor(input); |
||||||
|
expect(value.text).toEqual(match.text); |
||||||
|
if (match.hasOwnProperty('numeric')) { |
||||||
|
expect(value.numeric).toEqual(match.numeric); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
describe('Process simple display values', () => { |
||||||
|
// Don't test float values here since the decimal formatting changes
|
||||||
|
const processors = [ |
||||||
|
// Without options, this shortcuts to a much easier implementation
|
||||||
|
getDisplayProcessor(), |
||||||
|
|
||||||
|
// Add a simple option that is not used (uses a different base class)
|
||||||
|
getDisplayProcessor({ color: '#FFF' }), |
||||||
|
|
||||||
|
// Add a simple option that is not used (uses a different base class)
|
||||||
|
getDisplayProcessor({ unit: 'locale' }), |
||||||
|
]; |
||||||
|
|
||||||
|
it('support null', () => { |
||||||
|
assertSame(null, processors, { text: '', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('support undefined', () => { |
||||||
|
assertSame(undefined, processors, { text: '', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('support NaN', () => { |
||||||
|
assertSame(NaN, processors, { text: 'NaN', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Integer', () => { |
||||||
|
assertSame(3, processors, { text: '3', numeric: 3 }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Text to number', () => { |
||||||
|
assertSame('3', processors, { text: '3', numeric: 3 }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Simple String', () => { |
||||||
|
assertSame('hello', processors, { text: 'hello', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('empty array', () => { |
||||||
|
assertSame([], processors, { text: '', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('array of text', () => { |
||||||
|
assertSame(['a', 'b', 'c'], processors, { text: 'a,b,c', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('array of numbers', () => { |
||||||
|
assertSame([1, 2, 3], processors, { text: '1,2,3', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('empty object', () => { |
||||||
|
assertSame({}, processors, { text: '[object Object]', numeric: NaN }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('boolean true', () => { |
||||||
|
assertSame(true, processors, { text: 'true', numeric: 1 }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('boolean false', () => { |
||||||
|
assertSame(false, processors, { text: 'false', numeric: 0 }); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Processor with more configs', () => { |
||||||
|
it('support prefix & suffix', () => { |
||||||
|
const processor = getDisplayProcessor({ |
||||||
|
prefix: 'AA_', |
||||||
|
suffix: '_ZZ', |
||||||
|
}); |
||||||
|
|
||||||
|
expect(processor('XXX').text).toEqual('AA_XXX_ZZ'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Get color from threshold', () => { |
||||||
|
it('should get first threshold color when only one threshold', () => { |
||||||
|
const thresholds = [{ index: 0, value: -Infinity, color: '#7EB26D' }]; |
||||||
|
expect(getColorFromThreshold(49, thresholds)).toEqual('#7EB26D'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should get the threshold color if value is same as a threshold', () => { |
||||||
|
const thresholds = [ |
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' }, |
||||||
|
{ index: 1, value: 50, color: '#EAB839' }, |
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' }, |
||||||
|
]; |
||||||
|
expect(getColorFromThreshold(50, thresholds)).toEqual('#EAB839'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should get the nearest threshold color between thresholds', () => { |
||||||
|
const thresholds = [ |
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' }, |
||||||
|
{ index: 1, value: 50, color: '#EAB839' }, |
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' }, |
||||||
|
]; |
||||||
|
expect(getColorFromThreshold(55, thresholds)).toEqual('#EAB839'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Format value', () => { |
||||||
|
it('should return if value isNaN', () => { |
||||||
|
const valueMappings: ValueMapping[] = []; |
||||||
|
const value = 'N/A'; |
||||||
|
const instance = getDisplayProcessor({ mappings: valueMappings }); |
||||||
|
|
||||||
|
const result = instance(value); |
||||||
|
|
||||||
|
expect(result.text).toEqual('N/A'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return formatted value if there are no value mappings', () => { |
||||||
|
const valueMappings: ValueMapping[] = []; |
||||||
|
const value = '6'; |
||||||
|
|
||||||
|
const instance = getDisplayProcessor({ mappings: valueMappings, decimals: 1 }); |
||||||
|
|
||||||
|
const result = instance(value); |
||||||
|
|
||||||
|
expect(result.text).toEqual('6.0'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return formatted value if there are no matching value mappings', () => { |
||||||
|
const valueMappings: ValueMapping[] = [ |
||||||
|
{ id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, |
||||||
|
{ id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, |
||||||
|
]; |
||||||
|
const value = '10'; |
||||||
|
const instance = getDisplayProcessor({ mappings: valueMappings, decimals: 1 }); |
||||||
|
|
||||||
|
const result = instance(value); |
||||||
|
|
||||||
|
expect(result.text).toEqual('10.0'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return mapped value if there are matching value mappings', () => { |
||||||
|
const valueMappings: ValueMapping[] = [ |
||||||
|
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, |
||||||
|
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, |
||||||
|
]; |
||||||
|
const value = '11'; |
||||||
|
const instance = getDisplayProcessor({ mappings: valueMappings, decimals: 1 }); |
||||||
|
|
||||||
|
expect(instance(value).text).toEqual('1-20'); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,145 @@ |
|||||||
|
import { ValueMapping, Threshold } from '../types'; |
||||||
|
import _ from 'lodash'; |
||||||
|
import { getValueFormat, DecimalCount } from './valueFormats/valueFormats'; |
||||||
|
import { getMappedValue } from './valueMappings'; |
||||||
|
import { GrafanaTheme, GrafanaThemeType } from '../types'; |
||||||
|
import { getColorFromHexRgbOrName } from './namedColorsPalette'; |
||||||
|
import moment from 'moment'; |
||||||
|
|
||||||
|
export interface DisplayValue { |
||||||
|
text: string; // Show in the UI
|
||||||
|
numeric: number; // Use isNaN to check if it is a real number
|
||||||
|
color?: string; // color based on configs or Threshold
|
||||||
|
} |
||||||
|
|
||||||
|
export interface DisplayValueOptions { |
||||||
|
unit?: string; |
||||||
|
decimals?: DecimalCount; |
||||||
|
scaledDecimals?: DecimalCount; |
||||||
|
dateFormat?: string; // If set try to convert numbers to date
|
||||||
|
|
||||||
|
color?: string; |
||||||
|
mappings?: ValueMapping[]; |
||||||
|
thresholds?: Threshold[]; |
||||||
|
prefix?: string; |
||||||
|
suffix?: string; |
||||||
|
|
||||||
|
// Alternative to empty string
|
||||||
|
noValue?: string; |
||||||
|
|
||||||
|
// Context
|
||||||
|
isUtc?: boolean; |
||||||
|
theme?: GrafanaTheme; // Will pick 'dark' if not defined
|
||||||
|
} |
||||||
|
|
||||||
|
export type DisplayProcessor = (value: any) => DisplayValue; |
||||||
|
|
||||||
|
export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProcessor { |
||||||
|
if (options && !_.isEmpty(options)) { |
||||||
|
const formatFunc = getValueFormat(options.unit || 'none'); |
||||||
|
return (value: any) => { |
||||||
|
const { prefix, suffix, mappings, thresholds, theme } = options; |
||||||
|
let color = options.color; |
||||||
|
|
||||||
|
let text = _.toString(value); |
||||||
|
let numeric = toNumber(value); |
||||||
|
|
||||||
|
let shouldFormat = true; |
||||||
|
if (mappings && mappings.length > 0) { |
||||||
|
const mappedValue = getMappedValue(mappings, value); |
||||||
|
if (mappedValue) { |
||||||
|
text = mappedValue.text; |
||||||
|
const v = toNumber(text); |
||||||
|
if (!isNaN(v)) { |
||||||
|
numeric = v; |
||||||
|
} |
||||||
|
shouldFormat = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (options.dateFormat) { |
||||||
|
const date = toMoment(value, numeric, options.dateFormat); |
||||||
|
if (date.isValid()) { |
||||||
|
text = date.format(options.dateFormat); |
||||||
|
shouldFormat = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!isNaN(numeric)) { |
||||||
|
if (shouldFormat && !_.isBoolean(value)) { |
||||||
|
text = formatFunc(numeric, options.decimals, options.scaledDecimals, options.isUtc); |
||||||
|
} |
||||||
|
if (thresholds && thresholds.length > 0) { |
||||||
|
color = getColorFromThreshold(numeric, thresholds, theme); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!text) { |
||||||
|
text = options.noValue ? options.noValue : ''; |
||||||
|
} |
||||||
|
if (prefix) { |
||||||
|
text = prefix + text; |
||||||
|
} |
||||||
|
if (suffix) { |
||||||
|
text = text + suffix; |
||||||
|
} |
||||||
|
return { text, numeric, color }; |
||||||
|
}; |
||||||
|
} |
||||||
|
return toStringProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
function toMoment(value: any, numeric: number, format: string): moment.Moment { |
||||||
|
if (!isNaN(numeric)) { |
||||||
|
const v = moment(numeric); |
||||||
|
if (v.isValid()) { |
||||||
|
return v; |
||||||
|
} |
||||||
|
} |
||||||
|
const v = moment(value, format); |
||||||
|
if (v.isValid) { |
||||||
|
return v; |
||||||
|
} |
||||||
|
return moment(value); // moment will try to parse the format
|
||||||
|
} |
||||||
|
|
||||||
|
/** Will return any value as a number or NaN */ |
||||||
|
function toNumber(value: any): number { |
||||||
|
if (typeof value === 'number') { |
||||||
|
return value; |
||||||
|
} |
||||||
|
if (value === null || value === undefined || Array.isArray(value)) { |
||||||
|
return NaN; // lodash calls them 0
|
||||||
|
} |
||||||
|
if (typeof value === 'boolean') { |
||||||
|
return value ? 1 : 0; |
||||||
|
} |
||||||
|
return _.toNumber(value); |
||||||
|
} |
||||||
|
|
||||||
|
function toStringProcessor(value: any): DisplayValue { |
||||||
|
return { text: _.toString(value), numeric: toNumber(value) }; |
||||||
|
} |
||||||
|
|
||||||
|
export function getColorFromThreshold(value: number, thresholds: Threshold[], theme?: GrafanaTheme): string { |
||||||
|
const themeType = theme ? theme.type : GrafanaThemeType.Dark; |
||||||
|
|
||||||
|
if (thresholds.length === 1) { |
||||||
|
return getColorFromHexRgbOrName(thresholds[0].color, themeType); |
||||||
|
} |
||||||
|
|
||||||
|
const atThreshold = thresholds.filter(threshold => value === threshold.value)[0]; |
||||||
|
if (atThreshold) { |
||||||
|
return getColorFromHexRgbOrName(atThreshold.color, themeType); |
||||||
|
} |
||||||
|
|
||||||
|
const belowThreshold = thresholds.filter(threshold => value > threshold.value); |
||||||
|
|
||||||
|
if (belowThreshold.length > 0) { |
||||||
|
const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0]; |
||||||
|
return getColorFromHexRgbOrName(nearestThreshold.color, themeType); |
||||||
|
} |
||||||
|
|
||||||
|
// Use the first threshold as the default color
|
||||||
|
return getColorFromHexRgbOrName(thresholds[0].color, themeType); |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
import React, { PureComponent } from 'react'; |
||||||
|
import { VizOrientation } from '@grafana/ui'; |
||||||
|
import { VizRepeater } from '@grafana/ui'; |
||||||
|
|
||||||
|
export interface Props<T> { |
||||||
|
width: number; |
||||||
|
height: number; |
||||||
|
orientation: VizOrientation; |
||||||
|
source: any; // If this changes, the values will be processed
|
||||||
|
processFlag?: boolean; // change to force processing
|
||||||
|
|
||||||
|
getProcessedValues: () => T[]; |
||||||
|
renderValue: (value: T, width: number, height: number) => JSX.Element; |
||||||
|
} |
||||||
|
|
||||||
|
interface State<T> { |
||||||
|
values: T[]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This is essentially a cache of processed values. This checks for changes |
||||||
|
* to the source and then saves the processed values in the State |
||||||
|
*/ |
||||||
|
export class ProcessedValuesRepeater<T> extends PureComponent<Props<T>, State<T>> { |
||||||
|
constructor(props: Props<T>) { |
||||||
|
super(props); |
||||||
|
this.state = { |
||||||
|
values: props.getProcessedValues(), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Props<T>) { |
||||||
|
const { processFlag, source } = this.props; |
||||||
|
if (processFlag !== prevProps.processFlag || source !== prevProps.source) { |
||||||
|
this.setState({ values: this.props.getProcessedValues() }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { orientation, height, width, renderValue } = this.props; |
||||||
|
const { values } = this.state; |
||||||
|
return ( |
||||||
|
<VizRepeater height={height} width={width} values={values} orientation={orientation}> |
||||||
|
{({ vizHeight, vizWidth, value }) => renderValue(value, vizWidth, vizHeight)} |
||||||
|
</VizRepeater> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -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/) |
@ -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} /> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
// Libraries
|
||||||
|
import React, { PureComponent, CSSProperties } from 'react'; |
||||||
|
|
||||||
|
// Types
|
||||||
|
import { SingleStatOptions, SingleStatBaseOptions } from './types'; |
||||||
|
|
||||||
|
import { processSingleStatPanelData, DisplayValue, PanelProps } from '@grafana/ui'; |
||||||
|
import { config } from 'app/core/config'; |
||||||
|
import { getDisplayProcessor } from '@grafana/ui'; |
||||||
|
import { ProcessedValuesRepeater } from './ProcessedValuesRepeater'; |
||||||
|
|
||||||
|
export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): DisplayValue[] => { |
||||||
|
const { panelData, replaceVariables, options } = props; |
||||||
|
const { valueOptions, valueMappings } = options; |
||||||
|
const processor = getDisplayProcessor({ |
||||||
|
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: valueOptions.stat, |
||||||
|
}).map(stat => processor(stat.value)); |
||||||
|
}; |
||||||
|
|
||||||
|
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> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
getProcessedValues = (): DisplayValue[] => { |
||||||
|
return getSingleStatValues(this.props); |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
const { height, width, options, panelData } = this.props; |
||||||
|
const { orientation } = options; |
||||||
|
return ( |
||||||
|
<ProcessedValuesRepeater |
||||||
|
getProcessedValues={this.getProcessedValues} |
||||||
|
renderValue={this.renderValue} |
||||||
|
width={width} |
||||||
|
height={height} |
||||||
|
source={panelData} |
||||||
|
orientation={orientation} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 4.9 KiB |
@ -0,0 +1,29 @@ |
|||||||
|
import { ReactPanelPlugin } from '@grafana/ui'; |
||||||
|
import { SingleStatOptions, defaults, SingleStatBaseOptions } 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 singleStatBaseOptionsCheck = ( |
||||||
|
options: Partial<SingleStatBaseOptions>, |
||||||
|
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(singleStatBaseOptionsCheck); |
@ -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" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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, |
||||||
|
}; |
Loading…
Reference in new issue