diff --git a/packages/grafana-data/src/types/index.ts b/packages/grafana-data/src/types/index.ts index 4f270b87d73..027a408cbff 100644 --- a/packages/grafana-data/src/types/index.ts +++ b/packages/grafana-data/src/types/index.ts @@ -2,6 +2,7 @@ export * from './data'; export * from './dataLink'; export * from './logs'; export * from './navModel'; +export * from './select'; export * from './time'; export * from './threshold'; export * from './utils'; diff --git a/packages/grafana-data/src/types/select.ts b/packages/grafana-data/src/types/select.ts new file mode 100644 index 00000000000..d807a1a033b --- /dev/null +++ b/packages/grafana-data/src/types/select.ts @@ -0,0 +1,10 @@ +/** + * Used in select elements + */ +export interface SelectableValue { + label?: string; + value?: T; + imgUrl?: string; + description?: string; + [key: string]: any; +} diff --git a/packages/grafana-data/src/utils/fieldReducer.test.ts b/packages/grafana-data/src/utils/fieldReducer.test.ts index 7e4e205cb22..414d7acc7ec 100644 --- a/packages/grafana-data/src/utils/fieldReducer.test.ts +++ b/packages/grafana-data/src/utils/fieldReducer.test.ts @@ -1,6 +1,14 @@ -import { getFieldReducers, ReducerID, reduceField } from './index'; +import { fieldReducers, ReducerID, reduceField } from './fieldReducer'; import _ from 'lodash'; +import { DataFrame } from '../types/data'; + +/** + * Run a reducer and get back the value + */ +function reduce(series: DataFrame, fieldIndex: number, id: string): any { + return reduceField({ series, fieldIndex, reducers: [id] })[id]; +} describe('Stats Calculators', () => { const basicTable = { @@ -9,29 +17,16 @@ describe('Stats Calculators', () => { }; it('should load all standard stats', () => { - const names = [ - ReducerID.sum, - ReducerID.max, - ReducerID.min, - ReducerID.logmin, - ReducerID.mean, - ReducerID.last, - ReducerID.first, - ReducerID.count, - ReducerID.range, - ReducerID.diff, - ReducerID.step, - ReducerID.delta, - // ReducerID.allIsZero, - // ReducerID.allIsNull, - ]; - const stats = getFieldReducers(names); - expect(stats.length).toBe(names.length); + for (const id of Object.keys(ReducerID)) { + const reducer = fieldReducers.getIfExists(id); + const found = reducer ? reducer.id : ''; + expect(found).toEqual(id); + } }); it('should fail to load unknown stats', () => { const names = ['not a stat', ReducerID.max, ReducerID.min, 'also not a stat']; - const stats = getFieldReducers(names); + const stats = fieldReducers.list(names); expect(stats.length).toBe(2); const found = stats.map(v => v.id); @@ -92,6 +87,34 @@ describe('Stats Calculators', () => { expect(stats.delta).toEqual(300); }); + it('consistenly check allIsNull/allIsZero', () => { + const empty = { + fields: [{ name: 'A' }], + rows: [], + }; + const allNull = ({ + fields: [{ name: 'A' }], + rows: [null, null, null, null], + } as unknown) as DataFrame; + const allNull2 = { + fields: [{ name: 'A' }], + rows: [[null], [null], [null], [null]], + }; + const allZero = { + fields: [{ name: 'A' }], + rows: [[0], [0], [0], [0]], + }; + + expect(reduce(empty, 0, ReducerID.allIsNull)).toEqual(true); + expect(reduce(allNull, 0, ReducerID.allIsNull)).toEqual(true); + expect(reduce(allNull2, 0, ReducerID.allIsNull)).toEqual(true); + + expect(reduce(empty, 0, ReducerID.allIsZero)).toEqual(false); + expect(reduce(allNull, 0, ReducerID.allIsZero)).toEqual(false); + expect(reduce(allNull2, 0, ReducerID.allIsZero)).toEqual(false); + expect(reduce(allZero, 0, ReducerID.allIsZero)).toEqual(true); + }); + it('consistent results for first/last value with null', () => { const info = [ { diff --git a/packages/grafana-data/src/utils/fieldReducer.ts b/packages/grafana-data/src/utils/fieldReducer.ts index f0b9b789208..d3cb1f787cb 100644 --- a/packages/grafana-data/src/utils/fieldReducer.ts +++ b/packages/grafana-data/src/utils/fieldReducer.ts @@ -1,7 +1,8 @@ // Libraries import isNumber from 'lodash/isNumber'; -import { DataFrame, NullValueMode } from '../types/index'; +import { DataFrame, NullValueMode } from '../types'; +import { Registry, RegistryItem } from './registry'; export enum ReducerID { sum = 'sum', @@ -34,38 +35,13 @@ export interface FieldCalcs { // Internal function type FieldReducer = (data: DataFrame, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => FieldCalcs; -export interface FieldReducerInfo { - id: string; - name: string; - description: string; - alias?: string; // optional secondary key. 'avg' vs 'mean', 'total' vs 'sum' - +export interface FieldReducerInfo extends RegistryItem { // Internal details emptyInputResult?: any; // typically null, but some things like 'count' & 'sum' should be zero standard: boolean; // The most common stats can all be calculated in a single pass reduce?: FieldReducer; } -/** - * @param ids list of stat names or null to get all of them - */ -export function getFieldReducers(ids?: string[]): FieldReducerInfo[] { - if (ids === null || ids === undefined) { - if (!hasBuiltIndex) { - getById(ReducerID.mean); - } - return listOfStats; - } - - return ids.reduce((list, id) => { - const stat = getById(id); - if (stat) { - list.push(stat); - } - return list; - }, new Array()); -} - interface ReduceFieldOptions { series: DataFrame; fieldIndex: number; @@ -83,7 +59,7 @@ export function reduceField(options: ReduceFieldOptions): FieldCalcs { return {}; } - const queue = getFieldReducers(reducers); + const queue = fieldReducers.list(reducers); // Return early for empty series // This lets the concrete implementations assume at least one row @@ -122,122 +98,107 @@ export function reduceField(options: ReduceFieldOptions): FieldCalcs { // // ------------------------------------------------------------------------------ -// private registry of all stats -interface TableStatIndex { - [id: string]: FieldReducerInfo; -} - -const listOfStats: FieldReducerInfo[] = []; -const index: TableStatIndex = {}; -let hasBuiltIndex = false; - -function getById(id: string): FieldReducerInfo | undefined { - if (!hasBuiltIndex) { - [ - { - id: ReducerID.lastNotNull, - name: 'Last (not null)', - description: 'Last non-null value', - standard: true, - alias: 'current', - reduce: calculateLastNotNull, - }, - { - id: ReducerID.last, - name: 'Last', - description: 'Last Value', - standard: true, - reduce: calculateLast, - }, - { id: ReducerID.first, name: 'First', description: 'First Value', standard: true, reduce: calculateFirst }, - { - id: ReducerID.firstNotNull, - name: 'First (not null)', - description: 'First non-null value', - standard: true, - reduce: calculateFirstNotNull, - }, - { id: ReducerID.min, name: 'Min', description: 'Minimum Value', standard: true }, - { id: ReducerID.max, name: 'Max', description: 'Maximum Value', standard: true }, - { id: ReducerID.mean, name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' }, - { - id: ReducerID.sum, - name: 'Total', - description: 'The sum of all values', - emptyInputResult: 0, - standard: true, - alias: 'total', - }, - { - id: ReducerID.count, - name: 'Count', - description: 'Number of values in response', - emptyInputResult: 0, - standard: true, - }, - { - id: ReducerID.range, - name: 'Range', - description: 'Difference between minimum and maximum values', - standard: true, - }, - { - id: ReducerID.delta, - name: 'Delta', - description: 'Cumulative change in value', - standard: true, - }, - { - id: ReducerID.step, - name: 'Step', - description: 'Minimum interval between values', - standard: true, - }, - { - id: ReducerID.diff, - name: 'Difference', - description: 'Difference between first and last values', - standard: true, - }, - { - id: ReducerID.logmin, - name: 'Min (above zero)', - description: 'Used for log min scale', - standard: true, - }, - { - id: ReducerID.changeCount, - name: 'Change Count', - description: 'Number of times the value changes', - standard: false, - reduce: calculateChangeCount, - }, - { - id: ReducerID.distinctCount, - name: 'Distinct Count', - description: 'Number of distinct values', - standard: false, - reduce: calculateDistinctCount, - }, - ].forEach(info => { - const { id, alias } = info; - if (index.hasOwnProperty(id)) { - console.warn('Duplicate Stat', id, info, index); - } - index[id] = info; - if (alias) { - if (index.hasOwnProperty(alias)) { - console.warn('Duplicate Stat (alias)', alias, info, index); - } - index[alias] = info; - } - listOfStats.push(info); - }); - hasBuiltIndex = true; - } - - return index[id]; -} +export const fieldReducers = new Registry(() => [ + { + id: ReducerID.lastNotNull, + name: 'Last (not null)', + description: 'Last non-null value', + standard: true, + alias: 'current', + reduce: calculateLastNotNull, + }, + { + id: ReducerID.last, + name: 'Last', + description: 'Last Value', + standard: true, + reduce: calculateLast, + }, + { id: ReducerID.first, name: 'First', description: 'First Value', standard: true, reduce: calculateFirst }, + { + id: ReducerID.firstNotNull, + name: 'First (not null)', + description: 'First non-null value', + standard: true, + reduce: calculateFirstNotNull, + }, + { id: ReducerID.min, name: 'Min', description: 'Minimum Value', standard: true }, + { id: ReducerID.max, name: 'Max', description: 'Maximum Value', standard: true }, + { id: ReducerID.mean, name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' }, + { + id: ReducerID.sum, + name: 'Total', + description: 'The sum of all values', + emptyInputResult: 0, + standard: true, + alias: 'total', + }, + { + id: ReducerID.count, + name: 'Count', + description: 'Number of values in response', + emptyInputResult: 0, + standard: true, + }, + { + id: ReducerID.range, + name: 'Range', + description: 'Difference between minimum and maximum values', + standard: true, + }, + { + id: ReducerID.delta, + name: 'Delta', + description: 'Cumulative change in value', + standard: true, + }, + { + id: ReducerID.step, + name: 'Step', + description: 'Minimum interval between values', + standard: true, + }, + { + id: ReducerID.diff, + name: 'Difference', + description: 'Difference between first and last values', + standard: true, + }, + { + id: ReducerID.logmin, + name: 'Min (above zero)', + description: 'Used for log min scale', + standard: true, + }, + { + id: ReducerID.allIsZero, + name: 'All Zeros', + description: 'All values are zero', + emptyInputResult: false, + standard: true, + }, + { + id: ReducerID.allIsNull, + name: 'All Nulls', + description: 'All values are null', + emptyInputResult: true, + standard: true, + }, + { + id: ReducerID.changeCount, + name: 'Change Count', + description: 'Number of times the value changes', + standard: false, + reduce: calculateChangeCount, + }, + { + id: ReducerID.distinctCount, + name: 'Distinct Count', + description: 'Number of distinct values', + standard: false, + reduce: calculateDistinctCount, + }, +]); function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs { const calcs = { @@ -253,7 +214,7 @@ function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boole count: 0, nonNullCount: 0, allIsNull: true, - allIsZero: false, + allIsZero: true, range: null, diff: null, delta: 0, @@ -264,7 +225,7 @@ function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boole } as FieldCalcs; for (let i = 0; i < data.rows.length; i++) { - let currentValue = data.rows[i][fieldIndex]; + let currentValue = data.rows[i] ? data.rows[i][fieldIndex] : null; if (i === 0) { calcs.first = currentValue; } @@ -350,6 +311,10 @@ function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boole calcs.mean = calcs.sum! / calcs.nonNullCount; } + if (calcs.allIsNull) { + calcs.allIsZero = false; + } + if (calcs.max !== null && calcs.min !== null) { calcs.range = calcs.max - calcs.min; } diff --git a/packages/grafana-data/src/utils/index.ts b/packages/grafana-data/src/utils/index.ts index a23bbc1a6b9..62f45b826e0 100644 --- a/packages/grafana-data/src/utils/index.ts +++ b/packages/grafana-data/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './string'; +export * from './registry'; export * from './markdown'; export * from './processDataFrame'; export * from './csv'; diff --git a/packages/grafana-data/src/utils/registry.ts b/packages/grafana-data/src/utils/registry.ts new file mode 100644 index 00000000000..bc4ce20091b --- /dev/null +++ b/packages/grafana-data/src/utils/registry.ts @@ -0,0 +1,134 @@ +import { SelectableValue } from '../types/select'; + +export interface RegistryItem { + id: string; // Unique Key -- saved in configs + name: string; // Display Name, can change without breaking configs + description: string; + aliasIds?: string[]; // when the ID changes, we may want backwards compatibility ('current' => 'last') + + /** + * Some extensions should not be user selectable + * like: 'all' and 'any' matchers; + */ + excludeFromPicker?: boolean; +} + +interface RegistrySelectInfo { + options: Array>; + current: Array>; +} + +export class Registry { + private ordered: T[] = []; + private byId = new Map(); + private initalized = false; + + constructor(private init?: () => T[]) {} + + getIfExists(id: string | undefined): T | undefined { + if (!this.initalized) { + if (this.init) { + for (const ext of this.init()) { + this.register(ext); + } + } + this.sort(); + this.initalized = true; + } + if (id) { + return this.byId.get(id); + } + return undefined; + } + + get(id: string): T { + const v = this.getIfExists(id); + if (!v) { + throw new Error('Undefined: ' + id); + } + return v; + } + + selectOptions(current?: string[], filter?: (ext: T) => boolean): RegistrySelectInfo { + if (!this.initalized) { + this.getIfExists('xxx'); // will trigger init + } + + const select = { + options: [], + current: [], + } as RegistrySelectInfo; + + const currentIds: any = {}; + if (current) { + for (const id of current) { + currentIds[id] = true; + } + } + + for (const ext of this.ordered) { + if (ext.excludeFromPicker) { + continue; + } + if (filter && !filter(ext)) { + continue; + } + + const option = { + value: ext.id, + label: ext.name, + description: ext.description, + }; + + select.options.push(option); + if (currentIds[ext.id]) { + select.current.push(option); + } + } + return select; + } + + /** + * Return a list of values by ID, or all values if not specified + */ + list(ids?: any[]): T[] { + if (ids) { + const found: T[] = []; + for (const id of ids) { + const v = this.getIfExists(id); + if (v) { + found.push(v); + } + } + return found; + } + if (!this.initalized) { + this.getIfExists('xxx'); // will trigger init + } + return [...this.ordered]; // copy of everythign just in case + } + + register(ext: T) { + if (this.byId.has(ext.id)) { + throw new Error('Duplicate Key:' + ext.id); + } + this.byId.set(ext.id, ext); + this.ordered.push(ext); + + if (ext.aliasIds) { + for (const alias of ext.aliasIds) { + if (!this.byId.has(alias)) { + this.byId.set(alias, ext); + } + } + } + + if (this.initalized) { + this.sort(); + } + } + + private sort() { + // TODO sort the list + } +} diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index ecde154320b..64a022ccf0b 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; -import { SelectOptionItem } from '../Select/Select'; +import { SelectableValue } from '@grafana/data'; import { Tooltip } from '../Tooltip/Tooltip'; import { ButtonSelect } from '../Select/ButtonSelect'; @@ -23,7 +23,7 @@ export class RefreshPicker extends PureComponent { super(props); } - intervalsToOptions = (intervals: string[] | undefined): Array> => { + intervalsToOptions = (intervals: string[] | undefined): Array> => { const intervalsOrDefault = intervals || defaultIntervals; const options = intervalsOrDefault .filter(str => str !== '') @@ -37,7 +37,7 @@ export class RefreshPicker extends PureComponent { return options; }; - onChangeSelect = (item: SelectOptionItem) => { + onChangeSelect = (item: SelectableValue) => { const { onIntervalChanged } = this.props; if (onIntervalChanged) { // @ts-ignore diff --git a/packages/grafana-ui/src/components/Select/ButtonSelect.story.tsx b/packages/grafana-ui/src/components/Select/ButtonSelect.story.tsx index 0e50be5b942..1b8f32df0a9 100644 --- a/packages/grafana-ui/src/components/Select/ButtonSelect.story.tsx +++ b/packages/grafana-ui/src/components/Select/ButtonSelect.story.tsx @@ -4,7 +4,7 @@ import { action } from '@storybook/addon-actions'; import { withKnobs, object, text } from '@storybook/addon-knobs'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { UseState } from '../../utils/storybook/UseState'; -import { SelectOptionItem } from './Select'; +import { SelectableValue } from '@grafana/data'; import { ButtonSelect } from './ButtonSelect'; const ButtonSelectStories = storiesOf('UI/Select/ButtonSelect', module); @@ -12,9 +12,9 @@ const ButtonSelectStories = storiesOf('UI/Select/ButtonSelect', module); ButtonSelectStories.addDecorator(withCenteredStory).addDecorator(withKnobs); ButtonSelectStories.add('default', () => { - const intialState: SelectOptionItem = { label: 'A label', value: 'A value' }; - const value = object>('Selected Value:', intialState); - const options = object>>('Options:', [ + const intialState: SelectableValue = { label: 'A label', value: 'A value' }; + const value = object>('Selected Value:', intialState); + const options = object>>('Options:', [ intialState, { label: 'Another label', value: 'Another value' }, ]); diff --git a/packages/grafana-ui/src/components/Select/ButtonSelect.tsx b/packages/grafana-ui/src/components/Select/ButtonSelect.tsx index 8bc80ca49df..5ed8419608d 100644 --- a/packages/grafana-ui/src/components/Select/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Select/ButtonSelect.tsx @@ -1,6 +1,7 @@ import React, { PureComponent, ReactElement } from 'react'; -import Select, { SelectOptionItem } from './Select'; +import Select from './Select'; import { PopperContent } from '../Tooltip/PopperController'; +import { SelectableValue } from '@grafana/data'; interface ButtonComponentProps { label: ReactElement | string | undefined; @@ -30,13 +31,13 @@ const ButtonComponent = (buttonProps: ButtonComponentProps) => (props: any) => { export interface Props { className: string | undefined; - options: Array>; - value?: SelectOptionItem; + options: Array>; + value?: SelectableValue; label?: ReactElement | string; iconClass?: string; components?: any; maxMenuHeight?: number; - onChange: (item: SelectOptionItem) => void; + onChange: (item: SelectableValue) => void; tooltipContent?: PopperContent; isMenuOpen?: boolean; onOpenMenu?: () => void; @@ -45,7 +46,7 @@ export interface Props { } export class ButtonSelect extends PureComponent> { - onChange = (item: SelectOptionItem) => { + onChange = (item: SelectableValue) => { const { onChange } = this.props; onChange(item); }; diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index 7463ed6d8c2..78d63675c1a 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -19,23 +19,16 @@ import resetSelectStyles from './resetSelectStyles'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; import { PopperContent } from '../Tooltip/PopperController'; import { Tooltip } from '../Tooltip/Tooltip'; - -export interface SelectOptionItem { - label?: string; - value?: T; - imgUrl?: string; - description?: string; - [key: string]: any; -} +import { SelectableValue } from '@grafana/data'; export interface CommonProps { defaultValue?: any; - getOptionLabel?: (item: SelectOptionItem) => string; - getOptionValue?: (item: SelectOptionItem) => string; - onChange: (item: SelectOptionItem) => {} | void; + getOptionLabel?: (item: SelectableValue) => string; + getOptionValue?: (item: SelectableValue) => string; + onChange: (item: SelectableValue) => {} | void; placeholder?: string; width?: number; - value?: SelectOptionItem; + value?: SelectableValue; className?: string; isDisabled?: boolean; isSearchable?: boolean; @@ -57,12 +50,12 @@ export interface CommonProps { } export interface SelectProps extends CommonProps { - options: Array>; + options: Array>; } interface AsyncProps extends CommonProps { defaultOptions: boolean; - loadOptions: (query: string) => Promise>>; + loadOptions: (query: string) => Promise>>; loadingMessage?: () => string; } diff --git a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx index e7d3e6e50c8..8a327f0126b 100644 --- a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx +++ b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx @@ -3,11 +3,10 @@ import { interval, Subscription, Subject, of, NEVER } from 'rxjs'; import { tap, switchMap } from 'rxjs/operators'; import _ from 'lodash'; -import { stringToMs } from '@grafana/data'; +import { stringToMs, SelectableValue } from '@grafana/data'; import { isLive } from '../RefreshPicker/RefreshPicker'; -import { SelectOptionItem } from '../Select/Select'; -export function getIntervalFromString(strInterval: string): SelectOptionItem { +export function getIntervalFromString(strInterval: string): SelectableValue { return { label: strInterval, value: stringToMs(strInterval), diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx index c487c1398e1..1c6c17f6f39 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx @@ -8,10 +8,10 @@ import { StatsPicker } from '../StatsPicker/StatsPicker'; // Types import { FieldDisplayOptions, DEFAULT_FIELD_DISPLAY_VALUES_LIMIT } from '../../utils/fieldDisplay'; -import Select, { SelectOptionItem } from '../Select/Select'; -import { Field, ReducerID, toNumberString, toIntegerOrUndefined } from '@grafana/data'; +import Select from '../Select/Select'; +import { Field, ReducerID, toNumberString, toIntegerOrUndefined, SelectableValue } from '@grafana/data'; -const showOptions: Array> = [ +const showOptions: Array> = [ { value: true, label: 'All Values', @@ -31,7 +31,7 @@ export interface Props { } export class FieldDisplayEditor extends PureComponent { - onShowValuesChange = (item: SelectOptionItem) => { + onShowValuesChange = (item: SelectableValue) => { const val = item.value === true; this.props.onChange({ ...this.props.value, values: val }); }; diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx index 994d3fe803e..20d9924b4e9 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx @@ -7,8 +7,7 @@ import { FormLabel } from '../FormLabel/FormLabel'; import { UnitPicker } from '../UnitPicker/UnitPicker'; // Types -import { toIntegerOrUndefined, Field } from '@grafana/data'; -import { SelectOptionItem } from '../Select/Select'; +import { toIntegerOrUndefined, Field, SelectableValue } from '@grafana/data'; import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay'; @@ -54,7 +53,7 @@ export const FieldPropertiesEditor: React.FC = ({ value, onChange, showMi [value.max, onChange] ); - const onUnitChange = (unit: SelectOptionItem) => { + const onUnitChange = (unit: SelectableValue) => { onChange({ ...value, unit: unit.value }); }; diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts index d5a64d5a0e1..48730894017 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts @@ -3,7 +3,7 @@ import omit from 'lodash/omit'; import { VizOrientation, PanelModel } from '../../types/panel'; import { FieldDisplayOptions } from '../../utils/fieldDisplay'; -import { Field, getFieldReducers, Threshold, sortThresholds } from '@grafana/data'; +import { Field, fieldReducers, Threshold, sortThresholds } from '@grafana/data'; export interface SingleStatBaseOptions { fieldOptions: FieldDisplayOptions; @@ -48,7 +48,10 @@ export const sharedSingleStatMigrationCheck = (panel: PanelModel s.id); + const reducer = fieldReducers.get(valueOptions.stat); + if (reducer) { + fieldOptions.calcs = [reducer.id]; + } } field.min = old.minValue; diff --git a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx index e7625ab4bb9..11946a7bb14 100644 --- a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx +++ b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx @@ -5,8 +5,7 @@ import difference from 'lodash/difference'; import { Select } from '../index'; -import { getFieldReducers } from '@grafana/data'; -import { SelectOptionItem } from '../Select/Select'; +import { fieldReducers, SelectableValue } from '@grafana/data'; interface Props { placeholder?: string; @@ -34,7 +33,7 @@ export class StatsPicker extends PureComponent { checkInput = () => { const { stats, allowMultiple, defaultStat, onChange } = this.props; - const current = getFieldReducers(stats); + const current = fieldReducers.list(stats); if (current.length !== stats.length) { const found = current.map(v => v.id); const notFound = difference(stats, found); @@ -54,7 +53,7 @@ export class StatsPicker extends PureComponent { } }; - onSelectionChange = (item: SelectOptionItem) => { + onSelectionChange = (item: SelectableValue) => { const { onChange } = this.props; if (isArray(item)) { onChange(item.map(v => v.value)); @@ -65,24 +64,16 @@ export class StatsPicker extends PureComponent { render() { const { width, stats, allowMultiple, defaultStat, placeholder } = this.props; - const options = getFieldReducers().map(s => { - return { - value: s.id, - label: s.name, - description: s.description, - }; - }); - - const value: Array> = options.filter(option => stats.find(stat => option.value === stat)); + const select = fieldReducers.selectOptions(stats); return (