Commit 658f419c by ryan

add stat picker to single stat

parent f8094a6e
...@@ -69,7 +69,7 @@ export class StatsPicker extends PureComponent<Props> { ...@@ -69,7 +69,7 @@ export class StatsPicker extends PureComponent<Props> {
return { return {
value: s.id, value: s.id,
label: s.name, label: s.name,
desctipiton: s.description, description: s.description,
}; };
}); });
......
...@@ -27,6 +27,7 @@ export { Switch } from './Switch/Switch'; ...@@ -27,6 +27,7 @@ export { Switch } from './Switch/Switch';
export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult'; export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
export { PieChart, PieChartDataPoint, PieChartType } from './PieChart/PieChart'; export { PieChart, PieChartDataPoint, PieChartType } from './PieChart/PieChart';
export { UnitPicker } from './UnitPicker/UnitPicker'; export { UnitPicker } from './UnitPicker/UnitPicker';
export { StatsPicker } from './StatsPicker/StatsPicker';
export { Input, InputStatus } from './Input/Input'; export { Input, InputStatus } from './Input/Input';
// Visualizations // Visualizations
......
...@@ -5,6 +5,7 @@ export * from './colors'; ...@@ -5,6 +5,7 @@ export * from './colors';
export * from './namedColorsPalette'; export * from './namedColorsPalette';
export * from './thresholds'; export * from './thresholds';
export * from './string'; export * from './string';
export * from './statsCalculator';
export * from './displayValue'; export * from './displayValue';
export * from './deprecationWarning'; export * from './deprecationWarning';
export { getMappedValue } from './valueMappings'; export { getMappedValue } from './valueMappings';
......
...@@ -41,7 +41,7 @@ describe('Stats Calculators', () => { ...@@ -41,7 +41,7 @@ describe('Stats Calculators', () => {
it('should calculate basic stats', () => { it('should calculate basic stats', () => {
const stats = calculateStats({ const stats = calculateStats({
data: basicTable, table: basicTable,
columnIndex: 0, columnIndex: 0,
stats: ['first', 'last', 'mean'], stats: ['first', 'last', 'mean'],
}); });
...@@ -58,7 +58,7 @@ describe('Stats Calculators', () => { ...@@ -58,7 +58,7 @@ describe('Stats Calculators', () => {
it('should support a single stat also', () => { it('should support a single stat also', () => {
const stats = calculateStats({ const stats = calculateStats({
data: basicTable, table: basicTable,
columnIndex: 0, columnIndex: 0,
stats: ['first'], stats: ['first'],
}); });
...@@ -70,7 +70,7 @@ describe('Stats Calculators', () => { ...@@ -70,7 +70,7 @@ describe('Stats Calculators', () => {
it('should get non standard stats', () => { it('should get non standard stats', () => {
const stats = calculateStats({ const stats = calculateStats({
data: basicTable, table: basicTable,
columnIndex: 0, columnIndex: 0,
stats: [StatID.distinctCount, StatID.changeCount], stats: [StatID.distinctCount, StatID.changeCount],
}); });
...@@ -78,4 +78,15 @@ describe('Stats Calculators', () => { ...@@ -78,4 +78,15 @@ describe('Stats Calculators', () => {
expect(stats.distinctCount).toEqual(2); expect(stats.distinctCount).toEqual(2);
expect(stats.changeCount).toEqual(1); expect(stats.changeCount).toEqual(1);
}); });
it('should calculate step', () => {
const stats = calculateStats({
table: { columns: [{ text: 'A' }], rows: [[100], [200], [300], [400]] },
columnIndex: 0,
stats: [StatID.step, StatID.delta],
});
expect(stats.step).toEqual(100);
expect(stats.delta).toEqual(300);
});
}); });
...@@ -29,7 +29,7 @@ export interface ColumnStats { ...@@ -29,7 +29,7 @@ export interface ColumnStats {
} }
// Internal function // Internal function
type StatCalculator = (data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => ColumnStats; type StatCalculator = (table: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => ColumnStats;
export interface StatCalculatorInfo { export interface StatCalculatorInfo {
id: string; id: string;
...@@ -64,7 +64,7 @@ export function getStatsCalculators(ids?: string[]): StatCalculatorInfo[] { ...@@ -64,7 +64,7 @@ export function getStatsCalculators(ids?: string[]): StatCalculatorInfo[] {
} }
export interface CalculateStatsOptions { export interface CalculateStatsOptions {
data: TableData; table: TableData;
columnIndex: number; columnIndex: number;
stats: string[]; // The stats to calculate stats: string[]; // The stats to calculate
nullValueMode?: NullValueMode; nullValueMode?: NullValueMode;
...@@ -74,7 +74,7 @@ export interface CalculateStatsOptions { ...@@ -74,7 +74,7 @@ export interface CalculateStatsOptions {
* @returns an object with a key for each selected stat * @returns an object with a key for each selected stat
*/ */
export function calculateStats(options: CalculateStatsOptions): ColumnStats { export function calculateStats(options: CalculateStatsOptions): ColumnStats {
const { data, columnIndex, stats, nullValueMode } = options; const { table, columnIndex, stats, nullValueMode } = options;
if (!stats || stats.length < 1) { if (!stats || stats.length < 1) {
return {}; return {};
...@@ -84,7 +84,7 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats { ...@@ -84,7 +84,7 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
// Return early for empty tables // Return early for empty tables
// This lets the concrete implementations assume at least one row // This lets the concrete implementations assume at least one row
if (!data.rows || data.rows.length < 1) { if (!table.rows || table.rows.length < 1) {
const stats = {} as ColumnStats; const stats = {} as ColumnStats;
queue.forEach(stat => { queue.forEach(stat => {
stats[stat.id] = stat.emptyInputResult !== null ? stat.emptyInputResult : null; stats[stat.id] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
...@@ -97,19 +97,19 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats { ...@@ -97,19 +97,19 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
// Avoid calculating all the standard stats if possible // Avoid calculating all the standard stats if possible
if (queue.length === 1 && queue[0].calculator) { if (queue.length === 1 && queue[0].calculator) {
return queue[0].calculator(data, columnIndex, ignoreNulls, nullAsZero); return queue[0].calculator(table, columnIndex, ignoreNulls, nullAsZero);
} }
// For now everything can use the standard stats // For now everything can use the standard stats
let values = standardStatsStat(data, columnIndex, ignoreNulls, nullAsZero); let values = standardStatsStat(table, columnIndex, ignoreNulls, nullAsZero);
queue.forEach(calc => { for (const calc of queue) {
if (!values.hasOwnProperty(calc.id) && calc.calculator) { if (!values.hasOwnProperty(calc.id) && calc.calculator) {
values = { values = {
...values, ...values,
...calc.calculator(data, columnIndex, ignoreNulls, nullAsZero), ...calc.calculator(table, columnIndex, ignoreNulls, nullAsZero),
}; };
} }
}); }
return values; return values;
} }
...@@ -141,7 +141,7 @@ function getById(id: string): StatCalculatorInfo | undefined { ...@@ -141,7 +141,7 @@ function getById(id: string): StatCalculatorInfo | undefined {
{ id: StatID.first, name: 'First', description: 'First Value', standard: true, calculator: calculateFirst }, { id: StatID.first, name: 'First', description: 'First Value', standard: true, calculator: calculateFirst },
{ id: StatID.min, name: 'Min', description: 'Minimum Value', standard: true }, { id: StatID.min, name: 'Min', description: 'Minimum Value', standard: true },
{ id: StatID.max, name: 'Max', description: 'Maximum Value', standard: true }, { id: StatID.max, name: 'Max', description: 'Maximum Value', standard: true },
{ id: StatID.mean, name: 'Mean', description: 'Average Value', standard: true }, { id: StatID.mean, name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
{ {
id: StatID.sum, id: StatID.sum,
name: 'Total', name: 'Total',
...@@ -241,7 +241,7 @@ function standardStatsStat( ...@@ -241,7 +241,7 @@ function standardStatsStat(
range: null, range: null,
diff: null, diff: null,
delta: 0, delta: 0,
step: 0, step: Number.MAX_VALUE,
// Just used for calcutations -- not exposed as a stat // Just used for calcutations -- not exposed as a stat
previousDeltaUp: true, previousDeltaUp: true,
...@@ -260,8 +260,6 @@ function standardStatsStat( ...@@ -260,8 +260,6 @@ function standardStatsStat(
} }
if (currentValue !== null) { if (currentValue !== null) {
stats.last = currentValue;
const isFirst = stats.first === null; const isFirst = stats.first === null;
if (isFirst) { if (isFirst) {
stats.first = currentValue; stats.first = currentValue;
...@@ -324,6 +322,10 @@ function standardStatsStat( ...@@ -324,6 +322,10 @@ function standardStatsStat(
stats.min = null; stats.min = null;
} }
if (stats.step === Number.MAX_VALUE) {
stats.step = null;
}
if (stats.nonNullCount > 0) { if (stats.nonNullCount > 0) {
stats.mean = stats.sum! / stats.nonNullCount; stats.mean = stats.sum! / stats.nonNullCount;
} }
...@@ -342,7 +344,6 @@ function standardStatsStat( ...@@ -342,7 +344,6 @@ function standardStatsStat(
} }
function calculateFirst(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats { function calculateFirst(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
console.log('FIRST', data);
return { first: data.rows[0][columnIndex] }; return { first: data.rows[0][columnIndex] };
} }
......
...@@ -98,6 +98,12 @@ export class DashboardPanel extends PureComponent<Props, State> { ...@@ -98,6 +98,12 @@ export class DashboardPanel extends PureComponent<Props, State> {
} }
panel.changeType(pluginId, hook); panel.changeType(pluginId, hook);
} }
} else if (plugin.exports && plugin.exports.reactPanel) {
const hook = plugin.exports.reactPanel.panelTypeChangedHook;
if (hook) {
panel.options = hook(panel.options || {}, null, null);
console.log('OPITONS', pluginId, panel);
}
} }
this.setState({ plugin, angularPanel: null }); this.setState({ plugin, angularPanel: null });
......
import { VizOrientation, SelectOptionItem } from '@grafana/ui'; import { VizOrientation, SelectOptionItem, StatID } from '@grafana/ui';
import { SingleStatBaseOptions } from '../singlestat2/types'; import { SingleStatBaseOptions } from '../singlestat2/types';
export interface BarGaugeOptions extends SingleStatBaseOptions { export interface BarGaugeOptions extends SingleStatBaseOptions {
...@@ -25,7 +25,7 @@ export const defaults: BarGaugeOptions = { ...@@ -25,7 +25,7 @@ export const defaults: BarGaugeOptions = {
orientation: VizOrientation.Horizontal, orientation: VizOrientation.Horizontal,
valueOptions: { valueOptions: {
unit: 'none', unit: 'none',
stat: 'avg', stat: StatID.mean,
prefix: '', prefix: '',
suffix: '', suffix: '',
decimals: null, decimals: null,
......
import { SingleStatBaseOptions } from '../singlestat2/types'; import { SingleStatBaseOptions } from '../singlestat2/types';
import { VizOrientation } from '@grafana/ui'; import { VizOrientation, StatID } from '@grafana/ui';
export interface GaugeOptions extends SingleStatBaseOptions { export interface GaugeOptions extends SingleStatBaseOptions {
maxValue: number; maxValue: number;
...@@ -17,7 +17,7 @@ export const defaults: GaugeOptions = { ...@@ -17,7 +17,7 @@ export const defaults: GaugeOptions = {
prefix: '', prefix: '',
suffix: '', suffix: '',
decimals: null, decimals: null,
stat: 'avg', stat: StatID.mean,
unit: 'none', unit: 'none',
}, },
valueMappings: [], valueMappings: [],
......
...@@ -4,7 +4,7 @@ import React, { PureComponent, CSSProperties } from 'react'; ...@@ -4,7 +4,7 @@ import React, { PureComponent, CSSProperties } from 'react';
// Types // Types
import { SingleStatOptions, SingleStatBaseOptions } from './types'; import { SingleStatOptions, SingleStatBaseOptions } from './types';
import { DisplayValue, PanelProps, processTimeSeries, NullValueMode } from '@grafana/ui'; import { DisplayValue, PanelProps, NullValueMode, calculateStats } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { getDisplayProcessor } from '@grafana/ui'; import { getDisplayProcessor } from '@grafana/ui';
import { ProcessedValuesRepeater } from './ProcessedValuesRepeater'; import { ProcessedValuesRepeater } from './ProcessedValuesRepeater';
...@@ -14,7 +14,7 @@ export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): D ...@@ -14,7 +14,7 @@ export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): D
const { valueOptions, valueMappings } = options; const { valueOptions, valueMappings } = options;
const { unit, decimals, stat } = valueOptions; const { unit, decimals, stat } = valueOptions;
const processor = getDisplayProcessor({ const display = getDisplayProcessor({
unit, unit,
decimals, decimals,
mappings: valueMappings, mappings: valueMappings,
...@@ -24,12 +24,20 @@ export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): D ...@@ -24,12 +24,20 @@ export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): D
theme: config.theme, theme: config.theme,
}); });
return processTimeSeries({ return data.map(table => {
data, // Support last_time? Add that to the migration? last, but differnt column
if (stat === 'name') {
// Should not really be possible anymore?
return display(table.name);
}
return display(
calculateStats({
table,
columnIndex: 0, // Hardcoded for now!
stats: [stat], // The stats to calculate
nullValueMode: NullValueMode.Null, nullValueMode: NullValueMode.Null,
}).map((series, index) => { })[stat]
const value = stat !== 'name' ? series.stats[stat] : series.label; );
return processor(value);
}); });
}; };
......
...@@ -2,25 +2,11 @@ ...@@ -2,25 +2,11 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components // Components
import { FormField, FormLabel, PanelOptionsGroup, Select, UnitPicker } from '@grafana/ui'; import { FormField, FormLabel, PanelOptionsGroup, StatsPicker, UnitPicker, StatID } from '@grafana/ui';
// Types // Types
import { SingleStatValueOptions } from './types'; 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 {
...@@ -30,7 +16,12 @@ export interface Props { ...@@ -30,7 +16,12 @@ export interface Props {
export class SingleStatValueEditor 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 });
onStatsChange = stats => {
console.log('SELECTED', stats);
const stat = stats[0] || StatID.mean;
this.props.onChange({ ...this.props.options, stat });
};
onDecimalChange = event => { onDecimalChange = event => {
if (!isNaN(event.target.value)) { if (!isNaN(event.target.value)) {
...@@ -57,15 +48,19 @@ export class SingleStatValueEditor extends PureComponent<Props> { ...@@ -57,15 +48,19 @@ export class SingleStatValueEditor extends PureComponent<Props> {
decimalsString = decimals.toString(); decimalsString = decimals.toString();
} }
console.log('xxx', stat);
return ( return (
<PanelOptionsGroup title="Value"> <PanelOptionsGroup title="Value">
<div className="gf-form"> <div className="gf-form">
<FormLabel width={labelWidth}>Stat</FormLabel> <FormLabel width={labelWidth}>Stat</FormLabel>
<Select <StatsPicker
width={12} width={12}
options={statOptions} placeholder="Choose Stat"
onChange={this.onStatChange} defaultStat={StatID.mean}
value={statOptions.find(option => option.value === stat)} allowMultiple={false}
stats={[stat]}
onChange={this.onStatsChange}
/> />
</div> </div>
<div className="gf-form"> <div className="gf-form">
......
import { ReactPanelPlugin } from '@grafana/ui'; import { ReactPanelPlugin, getStatsCalculators } from '@grafana/ui';
import { SingleStatOptions, defaults, SingleStatBaseOptions } from './types'; import { SingleStatOptions, defaults, SingleStatBaseOptions } from './types';
import { SingleStatPanel } from './SingleStatPanel'; import { SingleStatPanel } from './SingleStatPanel';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
...@@ -21,6 +21,13 @@ export const singleStatBaseOptionsCheck = ( ...@@ -21,6 +21,13 @@ export const singleStatBaseOptionsCheck = (
}); });
} }
// 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];
console.log('CHANGED', valueOptions);
}
return options; return options;
}; };
......
import { VizOrientation, ValueMapping, Threshold } from '@grafana/ui'; import { VizOrientation, ValueMapping, Threshold, StatID } from '@grafana/ui';
export interface SingleStatBaseOptions { export interface SingleStatBaseOptions {
valueMappings: ValueMapping[]; valueMappings: ValueMapping[];
...@@ -24,7 +24,7 @@ export const defaults: SingleStatOptions = { ...@@ -24,7 +24,7 @@ export const defaults: SingleStatOptions = {
prefix: '', prefix: '',
suffix: '', suffix: '',
decimals: null, decimals: null,
stat: 'avg', stat: StatID.mean,
unit: 'none', unit: 'none',
}, },
valueMappings: [], valueMappings: [],
......
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