Registry: add a reusable function registry (#17047)

This commit is contained in:
Ryan McKinley 2019-07-16 11:40:23 -07:00 committed by GitHub
parent 5151b8ce07
commit c194ae1ba5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 406 additions and 283 deletions

View File

@ -2,6 +2,7 @@ export * from './data';
export * from './dataLink'; export * from './dataLink';
export * from './logs'; export * from './logs';
export * from './navModel'; export * from './navModel';
export * from './select';
export * from './time'; export * from './time';
export * from './threshold'; export * from './threshold';
export * from './utils'; export * from './utils';

View File

@ -0,0 +1,10 @@
/**
* Used in select elements
*/
export interface SelectableValue<T = any> {
label?: string;
value?: T;
imgUrl?: string;
description?: string;
[key: string]: any;
}

View File

@ -1,6 +1,14 @@
import { getFieldReducers, ReducerID, reduceField } from './index'; import { fieldReducers, ReducerID, reduceField } from './fieldReducer';
import _ from 'lodash'; 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', () => { describe('Stats Calculators', () => {
const basicTable = { const basicTable = {
@ -9,29 +17,16 @@ describe('Stats Calculators', () => {
}; };
it('should load all standard stats', () => { it('should load all standard stats', () => {
const names = [ for (const id of Object.keys(ReducerID)) {
ReducerID.sum, const reducer = fieldReducers.getIfExists(id);
ReducerID.max, const found = reducer ? reducer.id : '<NOT FOUND>';
ReducerID.min, expect(found).toEqual(id);
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);
}); });
it('should fail to load unknown stats', () => { it('should fail to load unknown stats', () => {
const names = ['not a stat', ReducerID.max, ReducerID.min, 'also not a stat']; 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); expect(stats.length).toBe(2);
const found = stats.map(v => v.id); const found = stats.map(v => v.id);
@ -92,6 +87,34 @@ describe('Stats Calculators', () => {
expect(stats.delta).toEqual(300); 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', () => { it('consistent results for first/last value with null', () => {
const info = [ const info = [
{ {

View File

@ -1,7 +1,8 @@
// Libraries // Libraries
import isNumber from 'lodash/isNumber'; import isNumber from 'lodash/isNumber';
import { DataFrame, NullValueMode } from '../types/index'; import { DataFrame, NullValueMode } from '../types';
import { Registry, RegistryItem } from './registry';
export enum ReducerID { export enum ReducerID {
sum = 'sum', sum = 'sum',
@ -34,38 +35,13 @@ export interface FieldCalcs {
// Internal function // Internal function
type FieldReducer = (data: DataFrame, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => FieldCalcs; type FieldReducer = (data: DataFrame, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => FieldCalcs;
export interface FieldReducerInfo { export interface FieldReducerInfo extends RegistryItem {
id: string;
name: string;
description: string;
alias?: string; // optional secondary key. 'avg' vs 'mean', 'total' vs 'sum'
// Internal details // Internal details
emptyInputResult?: any; // typically null, but some things like 'count' & 'sum' should be zero 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 standard: boolean; // The most common stats can all be calculated in a single pass
reduce?: FieldReducer; 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<FieldReducerInfo>());
}
interface ReduceFieldOptions { interface ReduceFieldOptions {
series: DataFrame; series: DataFrame;
fieldIndex: number; fieldIndex: number;
@ -83,7 +59,7 @@ export function reduceField(options: ReduceFieldOptions): FieldCalcs {
return {}; return {};
} }
const queue = getFieldReducers(reducers); const queue = fieldReducers.list(reducers);
// Return early for empty series // Return early for empty series
// This lets the concrete implementations assume at least one row // 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 export const fieldReducers = new Registry<FieldReducerInfo>(() => [
interface TableStatIndex { {
[id: string]: FieldReducerInfo; id: ReducerID.lastNotNull,
} name: 'Last (not null)',
description: 'Last non-null value',
const listOfStats: FieldReducerInfo[] = []; standard: true,
const index: TableStatIndex = {}; alias: 'current',
let hasBuiltIndex = false; reduce: calculateLastNotNull,
},
function getById(id: string): FieldReducerInfo | undefined { {
if (!hasBuiltIndex) { id: ReducerID.last,
[ name: 'Last',
{ description: 'Last Value',
id: ReducerID.lastNotNull, standard: true,
name: 'Last (not null)', reduce: calculateLast,
description: 'Last non-null value', },
standard: true, { id: ReducerID.first, name: 'First', description: 'First Value', standard: true, reduce: calculateFirst },
alias: 'current', {
reduce: calculateLastNotNull, id: ReducerID.firstNotNull,
}, name: 'First (not null)',
{ description: 'First non-null value',
id: ReducerID.last, standard: true,
name: 'Last', reduce: calculateFirstNotNull,
description: 'Last Value', },
standard: true, { id: ReducerID.min, name: 'Min', description: 'Minimum Value', standard: true },
reduce: calculateLast, { id: ReducerID.max, name: 'Max', description: 'Maximum Value', standard: true },
}, { id: ReducerID.mean, name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
{ id: ReducerID.first, name: 'First', description: 'First Value', standard: true, reduce: calculateFirst }, {
{ id: ReducerID.sum,
id: ReducerID.firstNotNull, name: 'Total',
name: 'First (not null)', description: 'The sum of all values',
description: 'First non-null value', emptyInputResult: 0,
standard: true, standard: true,
reduce: calculateFirstNotNull, alias: 'total',
}, },
{ id: ReducerID.min, name: 'Min', description: 'Minimum Value', standard: true }, {
{ id: ReducerID.max, name: 'Max', description: 'Maximum Value', standard: true }, id: ReducerID.count,
{ id: ReducerID.mean, name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' }, name: 'Count',
{ description: 'Number of values in response',
id: ReducerID.sum, emptyInputResult: 0,
name: 'Total', standard: true,
description: 'The sum of all values', },
emptyInputResult: 0, {
standard: true, id: ReducerID.range,
alias: 'total', name: 'Range',
}, description: 'Difference between minimum and maximum values',
{ standard: true,
id: ReducerID.count, },
name: 'Count', {
description: 'Number of values in response', id: ReducerID.delta,
emptyInputResult: 0, name: 'Delta',
standard: true, description: 'Cumulative change in value',
}, standard: true,
{ },
id: ReducerID.range, {
name: 'Range', id: ReducerID.step,
description: 'Difference between minimum and maximum values', name: 'Step',
standard: true, description: 'Minimum interval between values',
}, standard: true,
{ },
id: ReducerID.delta, {
name: 'Delta', id: ReducerID.diff,
description: 'Cumulative change in value', name: 'Difference',
standard: true, description: 'Difference between first and last values',
}, standard: true,
{ },
id: ReducerID.step, {
name: 'Step', id: ReducerID.logmin,
description: 'Minimum interval between values', name: 'Min (above zero)',
standard: true, description: 'Used for log min scale',
}, standard: true,
{ },
id: ReducerID.diff, {
name: 'Difference', id: ReducerID.allIsZero,
description: 'Difference between first and last values', name: 'All Zeros',
standard: true, description: 'All values are zero',
}, emptyInputResult: false,
{ standard: true,
id: ReducerID.logmin, },
name: 'Min (above zero)', {
description: 'Used for log min scale', id: ReducerID.allIsNull,
standard: true, name: 'All Nulls',
}, description: 'All values are null',
{ emptyInputResult: true,
id: ReducerID.changeCount, standard: true,
name: 'Change Count', },
description: 'Number of times the value changes', {
standard: false, id: ReducerID.changeCount,
reduce: calculateChangeCount, name: 'Change Count',
}, description: 'Number of times the value changes',
{ standard: false,
id: ReducerID.distinctCount, reduce: calculateChangeCount,
name: 'Distinct Count', },
description: 'Number of distinct values', {
standard: false, id: ReducerID.distinctCount,
reduce: calculateDistinctCount, name: 'Distinct Count',
}, description: 'Number of distinct values',
].forEach(info => { standard: false,
const { id, alias } = info; reduce: calculateDistinctCount,
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];
}
function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs { function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs {
const calcs = { const calcs = {
@ -253,7 +214,7 @@ function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boole
count: 0, count: 0,
nonNullCount: 0, nonNullCount: 0,
allIsNull: true, allIsNull: true,
allIsZero: false, allIsZero: true,
range: null, range: null,
diff: null, diff: null,
delta: 0, delta: 0,
@ -264,7 +225,7 @@ function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boole
} as FieldCalcs; } as FieldCalcs;
for (let i = 0; i < data.rows.length; i++) { 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) { if (i === 0) {
calcs.first = currentValue; calcs.first = currentValue;
} }
@ -350,6 +311,10 @@ function doStandardCalcs(data: DataFrame, fieldIndex: number, ignoreNulls: boole
calcs.mean = calcs.sum! / calcs.nonNullCount; calcs.mean = calcs.sum! / calcs.nonNullCount;
} }
if (calcs.allIsNull) {
calcs.allIsZero = false;
}
if (calcs.max !== null && calcs.min !== null) { if (calcs.max !== null && calcs.min !== null) {
calcs.range = calcs.max - calcs.min; calcs.range = calcs.max - calcs.min;
} }

View File

@ -1,4 +1,5 @@
export * from './string'; export * from './string';
export * from './registry';
export * from './markdown'; export * from './markdown';
export * from './processDataFrame'; export * from './processDataFrame';
export * from './csv'; export * from './csv';

View File

@ -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<SelectableValue<string>>;
current: Array<SelectableValue<string>>;
}
export class Registry<T extends RegistryItem> {
private ordered: T[] = [];
private byId = new Map<string, T>();
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
}
}

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { SelectOptionItem } from '../Select/Select'; import { SelectableValue } from '@grafana/data';
import { Tooltip } from '../Tooltip/Tooltip'; import { Tooltip } from '../Tooltip/Tooltip';
import { ButtonSelect } from '../Select/ButtonSelect'; import { ButtonSelect } from '../Select/ButtonSelect';
@ -23,7 +23,7 @@ export class RefreshPicker extends PureComponent<Props> {
super(props); super(props);
} }
intervalsToOptions = (intervals: string[] | undefined): Array<SelectOptionItem<string>> => { intervalsToOptions = (intervals: string[] | undefined): Array<SelectableValue<string>> => {
const intervalsOrDefault = intervals || defaultIntervals; const intervalsOrDefault = intervals || defaultIntervals;
const options = intervalsOrDefault const options = intervalsOrDefault
.filter(str => str !== '') .filter(str => str !== '')
@ -37,7 +37,7 @@ export class RefreshPicker extends PureComponent<Props> {
return options; return options;
}; };
onChangeSelect = (item: SelectOptionItem<string>) => { onChangeSelect = (item: SelectableValue<string>) => {
const { onIntervalChanged } = this.props; const { onIntervalChanged } = this.props;
if (onIntervalChanged) { if (onIntervalChanged) {
// @ts-ignore // @ts-ignore

View File

@ -4,7 +4,7 @@ import { action } from '@storybook/addon-actions';
import { withKnobs, object, text } from '@storybook/addon-knobs'; import { withKnobs, object, text } from '@storybook/addon-knobs';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { UseState } from '../../utils/storybook/UseState'; import { UseState } from '../../utils/storybook/UseState';
import { SelectOptionItem } from './Select'; import { SelectableValue } from '@grafana/data';
import { ButtonSelect } from './ButtonSelect'; import { ButtonSelect } from './ButtonSelect';
const ButtonSelectStories = storiesOf('UI/Select/ButtonSelect', module); const ButtonSelectStories = storiesOf('UI/Select/ButtonSelect', module);
@ -12,9 +12,9 @@ const ButtonSelectStories = storiesOf('UI/Select/ButtonSelect', module);
ButtonSelectStories.addDecorator(withCenteredStory).addDecorator(withKnobs); ButtonSelectStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
ButtonSelectStories.add('default', () => { ButtonSelectStories.add('default', () => {
const intialState: SelectOptionItem<string> = { label: 'A label', value: 'A value' }; const intialState: SelectableValue<string> = { label: 'A label', value: 'A value' };
const value = object<SelectOptionItem<string>>('Selected Value:', intialState); const value = object<SelectableValue<string>>('Selected Value:', intialState);
const options = object<Array<SelectOptionItem<string>>>('Options:', [ const options = object<Array<SelectableValue<string>>>('Options:', [
intialState, intialState,
{ label: 'Another label', value: 'Another value' }, { label: 'Another label', value: 'Another value' },
]); ]);

View File

@ -1,6 +1,7 @@
import React, { PureComponent, ReactElement } from 'react'; import React, { PureComponent, ReactElement } from 'react';
import Select, { SelectOptionItem } from './Select'; import Select from './Select';
import { PopperContent } from '../Tooltip/PopperController'; import { PopperContent } from '../Tooltip/PopperController';
import { SelectableValue } from '@grafana/data';
interface ButtonComponentProps { interface ButtonComponentProps {
label: ReactElement | string | undefined; label: ReactElement | string | undefined;
@ -30,13 +31,13 @@ const ButtonComponent = (buttonProps: ButtonComponentProps) => (props: any) => {
export interface Props<T> { export interface Props<T> {
className: string | undefined; className: string | undefined;
options: Array<SelectOptionItem<T>>; options: Array<SelectableValue<T>>;
value?: SelectOptionItem<T>; value?: SelectableValue<T>;
label?: ReactElement | string; label?: ReactElement | string;
iconClass?: string; iconClass?: string;
components?: any; components?: any;
maxMenuHeight?: number; maxMenuHeight?: number;
onChange: (item: SelectOptionItem<T>) => void; onChange: (item: SelectableValue<T>) => void;
tooltipContent?: PopperContent<any>; tooltipContent?: PopperContent<any>;
isMenuOpen?: boolean; isMenuOpen?: boolean;
onOpenMenu?: () => void; onOpenMenu?: () => void;
@ -45,7 +46,7 @@ export interface Props<T> {
} }
export class ButtonSelect<T> extends PureComponent<Props<T>> { export class ButtonSelect<T> extends PureComponent<Props<T>> {
onChange = (item: SelectOptionItem<T>) => { onChange = (item: SelectableValue<T>) => {
const { onChange } = this.props; const { onChange } = this.props;
onChange(item); onChange(item);
}; };

View File

@ -19,23 +19,16 @@ import resetSelectStyles from './resetSelectStyles';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { PopperContent } from '../Tooltip/PopperController'; import { PopperContent } from '../Tooltip/PopperController';
import { Tooltip } from '../Tooltip/Tooltip'; import { Tooltip } from '../Tooltip/Tooltip';
import { SelectableValue } from '@grafana/data';
export interface SelectOptionItem<T> {
label?: string;
value?: T;
imgUrl?: string;
description?: string;
[key: string]: any;
}
export interface CommonProps<T> { export interface CommonProps<T> {
defaultValue?: any; defaultValue?: any;
getOptionLabel?: (item: SelectOptionItem<T>) => string; getOptionLabel?: (item: SelectableValue<T>) => string;
getOptionValue?: (item: SelectOptionItem<T>) => string; getOptionValue?: (item: SelectableValue<T>) => string;
onChange: (item: SelectOptionItem<T>) => {} | void; onChange: (item: SelectableValue<T>) => {} | void;
placeholder?: string; placeholder?: string;
width?: number; width?: number;
value?: SelectOptionItem<T>; value?: SelectableValue<T>;
className?: string; className?: string;
isDisabled?: boolean; isDisabled?: boolean;
isSearchable?: boolean; isSearchable?: boolean;
@ -57,12 +50,12 @@ export interface CommonProps<T> {
} }
export interface SelectProps<T> extends CommonProps<T> { export interface SelectProps<T> extends CommonProps<T> {
options: Array<SelectOptionItem<T>>; options: Array<SelectableValue<T>>;
} }
interface AsyncProps<T> extends CommonProps<T> { interface AsyncProps<T> extends CommonProps<T> {
defaultOptions: boolean; defaultOptions: boolean;
loadOptions: (query: string) => Promise<Array<SelectOptionItem<T>>>; loadOptions: (query: string) => Promise<Array<SelectableValue<T>>>;
loadingMessage?: () => string; loadingMessage?: () => string;
} }

View File

@ -3,11 +3,10 @@ import { interval, Subscription, Subject, of, NEVER } from 'rxjs';
import { tap, switchMap } from 'rxjs/operators'; import { tap, switchMap } from 'rxjs/operators';
import _ from 'lodash'; import _ from 'lodash';
import { stringToMs } from '@grafana/data'; import { stringToMs, SelectableValue } from '@grafana/data';
import { isLive } from '../RefreshPicker/RefreshPicker'; import { isLive } from '../RefreshPicker/RefreshPicker';
import { SelectOptionItem } from '../Select/Select';
export function getIntervalFromString(strInterval: string): SelectOptionItem<number> { export function getIntervalFromString(strInterval: string): SelectableValue<number> {
return { return {
label: strInterval, label: strInterval,
value: stringToMs(strInterval), value: stringToMs(strInterval),

View File

@ -8,10 +8,10 @@ import { StatsPicker } from '../StatsPicker/StatsPicker';
// Types // Types
import { FieldDisplayOptions, DEFAULT_FIELD_DISPLAY_VALUES_LIMIT } from '../../utils/fieldDisplay'; import { FieldDisplayOptions, DEFAULT_FIELD_DISPLAY_VALUES_LIMIT } from '../../utils/fieldDisplay';
import Select, { SelectOptionItem } from '../Select/Select'; import Select from '../Select/Select';
import { Field, ReducerID, toNumberString, toIntegerOrUndefined } from '@grafana/data'; import { Field, ReducerID, toNumberString, toIntegerOrUndefined, SelectableValue } from '@grafana/data';
const showOptions: Array<SelectOptionItem<boolean>> = [ const showOptions: Array<SelectableValue<boolean>> = [
{ {
value: true, value: true,
label: 'All Values', label: 'All Values',
@ -31,7 +31,7 @@ export interface Props {
} }
export class FieldDisplayEditor extends PureComponent<Props> { export class FieldDisplayEditor extends PureComponent<Props> {
onShowValuesChange = (item: SelectOptionItem<boolean>) => { onShowValuesChange = (item: SelectableValue<boolean>) => {
const val = item.value === true; const val = item.value === true;
this.props.onChange({ ...this.props.value, values: val }); this.props.onChange({ ...this.props.value, values: val });
}; };

View File

@ -7,8 +7,7 @@ import { FormLabel } from '../FormLabel/FormLabel';
import { UnitPicker } from '../UnitPicker/UnitPicker'; import { UnitPicker } from '../UnitPicker/UnitPicker';
// Types // Types
import { toIntegerOrUndefined, Field } from '@grafana/data'; import { toIntegerOrUndefined, Field, SelectableValue } from '@grafana/data';
import { SelectOptionItem } from '../Select/Select';
import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay'; import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay';
@ -54,7 +53,7 @@ export const FieldPropertiesEditor: React.FC<Props> = ({ value, onChange, showMi
[value.max, onChange] [value.max, onChange]
); );
const onUnitChange = (unit: SelectOptionItem<string>) => { const onUnitChange = (unit: SelectableValue<string>) => {
onChange({ ...value, unit: unit.value }); onChange({ ...value, unit: unit.value });
}; };

View File

@ -3,7 +3,7 @@ import omit from 'lodash/omit';
import { VizOrientation, PanelModel } from '../../types/panel'; import { VizOrientation, PanelModel } from '../../types/panel';
import { FieldDisplayOptions } from '../../utils/fieldDisplay'; import { FieldDisplayOptions } from '../../utils/fieldDisplay';
import { Field, getFieldReducers, Threshold, sortThresholds } from '@grafana/data'; import { Field, fieldReducers, Threshold, sortThresholds } from '@grafana/data';
export interface SingleStatBaseOptions { export interface SingleStatBaseOptions {
fieldOptions: FieldDisplayOptions; fieldOptions: FieldDisplayOptions;
@ -48,7 +48,10 @@ export const sharedSingleStatMigrationCheck = (panel: PanelModel<SingleStatBaseO
// Make sure the stats have a valid name // Make sure the stats have a valid name
if (valueOptions.stat) { if (valueOptions.stat) {
fieldOptions.calcs = getFieldReducers([valueOptions.stat]).map(s => s.id); const reducer = fieldReducers.get(valueOptions.stat);
if (reducer) {
fieldOptions.calcs = [reducer.id];
}
} }
field.min = old.minValue; field.min = old.minValue;

View File

@ -5,8 +5,7 @@ import difference from 'lodash/difference';
import { Select } from '../index'; import { Select } from '../index';
import { getFieldReducers } from '@grafana/data'; import { fieldReducers, SelectableValue } from '@grafana/data';
import { SelectOptionItem } from '../Select/Select';
interface Props { interface Props {
placeholder?: string; placeholder?: string;
@ -34,7 +33,7 @@ export class StatsPicker extends PureComponent<Props> {
checkInput = () => { checkInput = () => {
const { stats, allowMultiple, defaultStat, onChange } = this.props; const { stats, allowMultiple, defaultStat, onChange } = this.props;
const current = getFieldReducers(stats); const current = fieldReducers.list(stats);
if (current.length !== stats.length) { if (current.length !== stats.length) {
const found = current.map(v => v.id); const found = current.map(v => v.id);
const notFound = difference(stats, found); const notFound = difference(stats, found);
@ -54,7 +53,7 @@ export class StatsPicker extends PureComponent<Props> {
} }
}; };
onSelectionChange = (item: SelectOptionItem<string>) => { onSelectionChange = (item: SelectableValue<string>) => {
const { onChange } = this.props; const { onChange } = this.props;
if (isArray(item)) { if (isArray(item)) {
onChange(item.map(v => v.value)); onChange(item.map(v => v.value));
@ -65,24 +64,16 @@ export class StatsPicker extends PureComponent<Props> {
render() { render() {
const { width, stats, allowMultiple, defaultStat, placeholder } = this.props; 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<SelectOptionItem<string>> = options.filter(option => stats.find(stat => option.value === stat));
const select = fieldReducers.selectOptions(stats);
return ( return (
<Select <Select
width={width} width={width}
value={value} value={select.current}
isClearable={!defaultStat} isClearable={!defaultStat}
isMulti={allowMultiple} isMulti={allowMultiple}
isSearchable={true} isSearchable={true}
options={options} options={select.options}
placeholder={placeholder} placeholder={placeholder}
onChange={this.onSelectionChange} onChange={this.onSelectionChange}
/> />

View File

@ -13,8 +13,7 @@ import { rangeUtil } from '@grafana/data';
import { rawToTimeRange } from './time'; import { rawToTimeRange } from './time';
// Types // Types
import { TimeRange, TimeOption, TimeZone, TIME_FORMAT } from '@grafana/data'; import { TimeRange, TimeOption, TimeZone, TIME_FORMAT, SelectableValue } from '@grafana/data';
import { SelectOptionItem } from '../Select/Select';
export interface Props { export interface Props {
value: TimeRange; value: TimeRange;
@ -77,7 +76,7 @@ export class TimePicker extends PureComponent<Props, State> {
isCustomOpen: false, isCustomOpen: false,
}; };
mapTimeOptionsToSelectOptionItems = (selectOptions: TimeOption[]) => { mapTimeOptionsToSelectableValues = (selectOptions: TimeOption[]) => {
const options = selectOptions.map(timeOption => { const options = selectOptions.map(timeOption => {
return { return {
label: timeOption.display, label: timeOption.display,
@ -93,7 +92,7 @@ export class TimePicker extends PureComponent<Props, State> {
return options; return options;
}; };
onSelectChanged = (item: SelectOptionItem<TimeOption>) => { onSelectChanged = (item: SelectableValue<TimeOption>) => {
const { onChange, timeZone } = this.props; const { onChange, timeZone } = this.props;
if (item.value && item.value.from === 'custom') { if (item.value && item.value.from === 'custom') {
@ -122,7 +121,7 @@ export class TimePicker extends PureComponent<Props, State> {
render() { render() {
const { selectOptions: selectTimeOptions, value, onMoveBackward, onMoveForward, onZoom, timeZone } = this.props; const { selectOptions: selectTimeOptions, value, onMoveBackward, onMoveForward, onZoom, timeZone } = this.props;
const { isCustomOpen } = this.state; const { isCustomOpen } = this.state;
const options = this.mapTimeOptionsToSelectOptionItems(selectTimeOptions); const options = this.mapTimeOptionsToSelectableValues(selectTimeOptions);
const currentOption = options.find(item => isTimeOptionEqualToTimeRange(item.value, value)); const currentOption = options.find(item => isTimeOptionEqualToTimeRange(item.value, value));
const rangeString = rangeUtil.describeTimeRange(value.raw); const rangeString = rangeUtil.describeTimeRange(value.raw);

View File

@ -9,7 +9,7 @@ export * from './Button/Button';
export { ButtonVariant } from './Button/AbstractButton'; export { ButtonVariant } from './Button/AbstractButton';
// Select // Select
export { Select, AsyncSelect, SelectOptionItem } from './Select/Select'; export { Select, AsyncSelect } from './Select/Select';
export { IndicatorsContainer } from './Select/IndicatorsContainer'; export { IndicatorsContainer } from './Select/IndicatorsContainer';
export { NoOptionsMessage } from './Select/NoOptionsMessage'; export { NoOptionsMessage } from './Select/NoOptionsMessage';
export { default as resetSelectStyles } from './Select/resetSelectStyles'; export { default as resetSelectStyles } from './Select/resetSelectStyles';

View File

@ -1,7 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { UserPicker } from 'app/core/components/Select/UserPicker'; import { UserPicker } from 'app/core/components/Select/UserPicker';
import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker'; import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
import { Select, SelectOptionItem } from '@grafana/ui'; import { Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { User } from 'app/types'; import { User } from 'app/types';
import { import {
dashboardPermissionLevels, dashboardPermissionLevels,
@ -61,7 +62,7 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
this.setState({ teamId: team && !Array.isArray(team) ? team.id : 0 }); this.setState({ teamId: team && !Array.isArray(team) ? team.id : 0 });
}; };
onPermissionChanged = (permission: SelectOptionItem<PermissionLevel>) => { onPermissionChanged = (permission: SelectableValue<PermissionLevel>) => {
this.setState({ permission: permission.value }); this.setState({ permission: permission.value });
}; };

View File

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Select, SelectOptionItem } from '@grafana/ui'; import { Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl'; import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
import { FolderInfo } from 'app/types'; import { FolderInfo } from 'app/types';
@ -39,7 +40,7 @@ interface Props {
} }
export default class PermissionsListItem extends PureComponent<Props> { export default class PermissionsListItem extends PureComponent<Props> {
onPermissionChanged = (option: SelectOptionItem<PermissionLevel>) => { onPermissionChanged = (option: SelectableValue<PermissionLevel>) => {
this.props.onPermissionChanged(this.props.item, option.value); this.props.onPermissionChanged(this.props.item, option.value);
}; };

View File

@ -2,7 +2,8 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components // Components
import { Select, SelectOptionItem } from '@grafana/ui'; import { Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
// Types // Types
import { DataSourceSelectItem } from '@grafana/ui'; import { DataSourceSelectItem } from '@grafana/ui';
@ -28,7 +29,7 @@ export class DataSourcePicker extends PureComponent<Props> {
super(props); super(props);
} }
onChange = (item: SelectOptionItem<string>) => { onChange = (item: SelectableValue<string>) => {
const ds = this.props.datasources.find(ds => ds.name === item.value); const ds = this.props.datasources.find(ds => ds.name === item.value);
this.props.onChange(ds); this.props.onChange(ds);
}; };

View File

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import { Select, SelectOptionItem } from '@grafana/ui'; import { Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { Variable } from 'app/types/templates'; import { Variable } from 'app/types/templates';
export interface Props { export interface Props {
onChange: (value: string) => void; onChange: (value: string) => void;
options: Array<SelectOptionItem<string>>; options: Array<SelectableValue<string>>;
isSearchable: boolean; isSearchable: boolean;
value: string; value: string;
placeholder?: string; placeholder?: string;
@ -15,7 +16,7 @@ export interface Props {
} }
interface State { interface State {
options: Array<SelectOptionItem<string>>; options: Array<SelectableValue<string>>;
} }
export class MetricSelect extends React.Component<Props, State> { export class MetricSelect extends React.Component<Props, State> {

View File

@ -1,6 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Select, GrafanaTheme, ThemeContext, SelectOptionItem } from '@grafana/ui'; import { Select, GrafanaTheme, ThemeContext } from '@grafana/ui';
import { css, cx } from 'emotion'; import { css, cx } from 'emotion';
import { SelectableValue } from '@grafana/data';
const getStyles = (theme: GrafanaTheme) => ({ const getStyles = (theme: GrafanaTheme) => ({
keyValueContainer: css` keyValueContainer: css`
@ -33,7 +34,7 @@ export const AdHocFilter: React.FunctionComponent<Props> = props => {
const theme = useContext(ThemeContext); const theme = useContext(ThemeContext);
const styles = getStyles(theme); const styles = getStyles(theme);
const onChange = (changeType: ChangeType) => (item: SelectOptionItem<string>) => { const onChange = (changeType: ChangeType) => (item: SelectableValue<string>) => {
const { onKeyChanged, onValueChanged, onOperatorChanged } = props; const { onKeyChanged, onValueChanged, onOperatorChanged } = props;
switch (changeType) { switch (changeType) {
case ChangeType.Key: case ChangeType.Key:

View File

@ -3,8 +3,8 @@ import { connect } from 'react-redux';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { ExploreId, ExploreMode } from 'app/types/explore'; import { ExploreId, ExploreMode } from 'app/types/explore';
import { DataSourceSelectItem, SelectOptionItem } from '@grafana/ui'; import { DataSourceSelectItem } from '@grafana/ui';
import { RawTimeRange, TimeZone, TimeRange, LoadingState } from '@grafana/data'; import { RawTimeRange, TimeZone, TimeRange, LoadingState, SelectableValue } from '@grafana/data';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { StoreState } from 'app/types/store'; import { StoreState } from 'app/types/store';
import { import {
@ -67,8 +67,8 @@ interface StateProps {
selectedDatasource: DataSourceSelectItem; selectedDatasource: DataSourceSelectItem;
splitted: boolean; splitted: boolean;
refreshInterval: string; refreshInterval: string;
supportedModeOptions: Array<SelectOptionItem<ExploreMode>>; supportedModeOptions: Array<SelectableValue<ExploreMode>>;
selectedModeOption: SelectOptionItem<ExploreMode>; selectedModeOption: SelectableValue<ExploreMode>;
hasLiveOption: boolean; hasLiveOption: boolean;
isLive: boolean; isLive: boolean;
} }
@ -258,7 +258,7 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps
const hasLiveOption = const hasLiveOption =
datasourceInstance && datasourceInstance.meta && datasourceInstance.meta.streaming ? true : false; datasourceInstance && datasourceInstance.meta && datasourceInstance.meta.streaming ? true : false;
const supportedModeOptions: Array<SelectOptionItem<ExploreMode>> = []; const supportedModeOptions: Array<SelectableValue<ExploreMode>> = [];
let selectedModeOption = null; let selectedModeOption = null;
for (const supportedMode of supportedModes) { for (const supportedMode of supportedModes) {
switch (supportedMode) { switch (supportedMode) {

View File

@ -3,7 +3,7 @@ import { shallow } from 'enzyme';
import { TeamMember, TeamPermissionLevel } from '../../types'; import { TeamMember, TeamPermissionLevel } from '../../types';
import { getMockTeamMember } from './__mocks__/teamMocks'; import { getMockTeamMember } from './__mocks__/teamMocks';
import { TeamMemberRow, Props } from './TeamMemberRow'; import { TeamMemberRow, Props } from './TeamMemberRow';
import { SelectOptionItem } from '@grafana/ui'; import { SelectableValue } from '@grafana/data';
const setup = (propOverrides?: object) => { const setup = (propOverrides?: object) => {
const props: Props = { const props: Props = {
@ -80,7 +80,7 @@ describe('Functions', () => {
}; };
const { instance } = setup({ member }); const { instance } = setup({ member });
const permission = TeamPermissionLevel.Admin; const permission = TeamPermissionLevel.Admin;
const item: SelectOptionItem<TeamPermissionLevel> = { value: permission }; const item: SelectableValue<TeamPermissionLevel> = { value: permission };
const expectedTeamMemeber = { ...member, permission }; const expectedTeamMemeber = { ...member, permission };
instance.onPermissionChange(item, member); instance.onPermissionChange(item, member);

View File

@ -1,6 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { DeleteButton, Select, SelectOptionItem } from '@grafana/ui'; import { DeleteButton, Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { TeamMember, teamsPermissionLevels, TeamPermissionLevel } from 'app/types'; import { TeamMember, teamsPermissionLevels, TeamPermissionLevel } from 'app/types';
import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle'; import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
@ -27,7 +28,7 @@ export class TeamMemberRow extends PureComponent<Props> {
this.props.removeTeamMember(member.userId); this.props.removeTeamMember(member.userId);
} }
onPermissionChange = (item: SelectOptionItem<TeamPermissionLevel>, member: TeamMember) => { onPermissionChange = (item: SelectableValue<TeamPermissionLevel>, member: TeamMember) => {
const permission = item.value; const permission = item.value;
const updatedTeamMember = { ...member, permission }; const updatedTeamMember = { ...member, permission };

View File

@ -5,8 +5,8 @@ import React, { PureComponent } from 'react';
import { InputDatasource, describeDataFrame } from './InputDatasource'; import { InputDatasource, describeDataFrame } from './InputDatasource';
import { InputQuery, InputOptions } from './types'; import { InputQuery, InputOptions } from './types';
import { FormLabel, Select, QueryEditorProps, SelectOptionItem, TableInputCSV } from '@grafana/ui'; import { FormLabel, Select, QueryEditorProps, TableInputCSV } from '@grafana/ui';
import { DataFrame, toCSV } from '@grafana/data'; import { DataFrame, toCSV, SelectableValue } from '@grafana/data';
type Props = QueryEditorProps<InputDatasource, InputQuery, InputOptions>; type Props = QueryEditorProps<InputDatasource, InputQuery, InputOptions>;
@ -30,7 +30,7 @@ export class InputQueryEditor extends PureComponent<Props, State> {
this.setState({ text }); this.setState({ text });
} }
onSourceChange = (item: SelectOptionItem<string>) => { onSourceChange = (item: SelectableValue<string>) => {
const { datasource, query, onChange, onRunQuery } = this.props; const { datasource, query, onChange, onRunQuery } = this.props;
let data: DataFrame[] | undefined = undefined; let data: DataFrame[] | undefined = undefined;
if (item.value === 'panel') { if (item.value === 'panel') {

View File

@ -1,9 +1,6 @@
// Libraries // Libraries
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components
// import { Select, SelectOptionItem } from '@grafana/ui';
// Types // Types
import { QueryEditorProps } from '@grafana/ui'; import { QueryEditorProps } from '@grafana/ui';
import { LokiDatasource } from '../datasource'; import { LokiDatasource } from '../datasource';
@ -37,7 +34,7 @@ export class LokiQueryEditor extends PureComponent<Props> {
// }); // });
// }; // };
// //
// onFormatChanged = (option: SelectOptionItem) => { // onFormatChanged = (option: SelectableValue) => {
// this.props.onChange({ // this.props.onChange({
// ...this.state.query, // ...this.state.query,
// resultFormat: option.value, // resultFormat: option.value,
@ -47,7 +44,7 @@ export class LokiQueryEditor extends PureComponent<Props> {
render() { render() {
// const { query } = this.state; // const { query } = this.state;
// const { datasource } = this.props; // const { datasource } = this.props;
// const formatOptions: SelectOptionItem[] = [ // const formatOptions: SelectableValue[] = [
// { label: 'Time Series', value: 'time_series' }, // { label: 'Time Series', value: 'time_series' },
// { label: 'Table', value: 'table' }, // { label: 'Table', value: 'table' },
// ]; // ];

View File

@ -2,33 +2,32 @@ import _ from 'lodash';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Types // Types
import { FormLabel, Select, SelectOptionItem, Switch } from '@grafana/ui'; import { FormLabel, Select, Switch, QueryEditorProps, DataSourceStatus } from '@grafana/ui';
import { QueryEditorProps, DataSourceStatus } from '@grafana/ui'; import { SelectableValue } from '@grafana/data';
import { PrometheusDatasource } from '../datasource'; import { PrometheusDatasource } from '../datasource';
import { PromQuery, PromOptions } from '../types'; import { PromQuery, PromOptions } from '../types';
import PromQueryField from './PromQueryField'; import PromQueryField from './PromQueryField';
import PromLink from './PromLink'; import PromLink from './PromLink';
export type Props = QueryEditorProps<PrometheusDatasource, PromQuery, PromOptions>; export type Props = QueryEditorProps<PrometheusDatasource, PromQuery, PromOptions>;
const FORMAT_OPTIONS: Array<SelectOptionItem<string>> = [ const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
{ label: 'Time series', value: 'time_series' }, { label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' }, { label: 'Table', value: 'table' },
{ label: 'Heatmap', value: 'heatmap' }, { label: 'Heatmap', value: 'heatmap' },
]; ];
const INTERVAL_FACTOR_OPTIONS: Array<SelectOptionItem<number>> = _.map([1, 2, 3, 4, 5, 10], (value: number) => ({ const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = _.map([1, 2, 3, 4, 5, 10], (value: number) => ({
value, value,
label: '1/' + value, label: '1/' + value,
})); }));
interface State { interface State {
legendFormat: string; legendFormat: string;
formatOption: SelectOptionItem<string>; formatOption: SelectableValue<string>;
interval: string; interval: string;
intervalFactorOption: SelectOptionItem<number>; intervalFactorOption: SelectableValue<number>;
instant: boolean; instant: boolean;
} }
@ -58,7 +57,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
this.query.expr = query.expr; this.query.expr = query.expr;
}; };
onFormatChange = (option: SelectOptionItem<string>) => { onFormatChange = (option: SelectableValue<string>) => {
this.query.format = option.value; this.query.format = option.value;
this.setState({ formatOption: option }, this.onRunQuery); this.setState({ formatOption: option }, this.onRunQuery);
}; };
@ -75,7 +74,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
this.setState({ interval }); this.setState({ interval });
}; };
onIntervalFactorChange = (option: SelectOptionItem<number>) => { onIntervalFactorChange = (option: SelectableValue<number>) => {
this.query.intervalFactor = option.value; this.query.intervalFactor = option.value;
this.setState({ intervalFactorOption: option }, this.onRunQuery); this.setState({ intervalFactorOption: option }, this.onRunQuery);
}; };

View File

@ -3,12 +3,12 @@ import _ from 'lodash';
import { MetricSelect } from 'app/core/components/Select/MetricSelect'; import { MetricSelect } from 'app/core/components/Select/MetricSelect';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
import { SelectOptionItem } from '@grafana/ui'; import { SelectableValue } from '@grafana/data';
export interface Props { export interface Props {
onChange: (perSeriesAligner: any) => void; onChange: (perSeriesAligner: any) => void;
templateSrv: TemplateSrv; templateSrv: TemplateSrv;
alignOptions: Array<SelectOptionItem<string>>; alignOptions: Array<SelectableValue<string>>;
perSeriesAligner: string; perSeriesAligner: string;
} }

View File

@ -13,8 +13,7 @@ import { Help } from './Help';
import { StackdriverQuery, MetricDescriptor } from '../types'; import { StackdriverQuery, MetricDescriptor } from '../types';
import { getAlignmentPickerData } from '../functions'; import { getAlignmentPickerData } from '../functions';
import StackdriverDatasource from '../datasource'; import StackdriverDatasource from '../datasource';
import { SelectOptionItem } from '@grafana/ui'; import { TimeSeries, SelectableValue } from '@grafana/data';
import { TimeSeries } from '@grafana/data';
export interface Props { export interface Props {
onQueryChange: (target: StackdriverQuery) => void; onQueryChange: (target: StackdriverQuery) => void;
@ -26,7 +25,7 @@ export interface Props {
} }
interface State extends StackdriverQuery { interface State extends StackdriverQuery {
alignOptions: Array<SelectOptionItem<string>>; alignOptions: Array<SelectableValue<string>>;
lastQuery: string; lastQuery: string;
lastQueryError: string; lastQueryError: string;
[key: string]: any; [key: string]: any;

View File

@ -6,7 +6,8 @@ import _ from 'lodash';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
// Components // Components
import { FormLabel, Select, SelectOptionItem } from '@grafana/ui'; import { FormLabel, Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
// Types // Types
import { QueryEditorProps } from '@grafana/ui'; import { QueryEditorProps } from '@grafana/ui';
@ -40,7 +41,7 @@ export class QueryEditor extends PureComponent<Props> {
this.setState({ scenarioList: scenarioList, current: current }); this.setState({ scenarioList: scenarioList, current: current });
} }
onScenarioChange = (item: SelectOptionItem<string>) => { onScenarioChange = (item: SelectableValue<string>) => {
this.props.onChange({ this.props.onChange({
...this.props.query, ...this.props.query,
scenarioId: item.value, scenarioId: item.value,

View File

@ -1,17 +1,18 @@
import { VizOrientation, SelectOptionItem, SingleStatBaseOptions } from '@grafana/ui'; import { VizOrientation, SingleStatBaseOptions } from '@grafana/ui';
import { standardGaugeFieldOptions } from '../gauge/types'; import { standardGaugeFieldOptions } from '../gauge/types';
import { SelectableValue } from '@grafana/data';
export interface BarGaugeOptions extends SingleStatBaseOptions { export interface BarGaugeOptions extends SingleStatBaseOptions {
displayMode: 'basic' | 'lcd' | 'gradient'; displayMode: 'basic' | 'lcd' | 'gradient';
} }
export const displayModes: Array<SelectOptionItem<string>> = [ export const displayModes: Array<SelectableValue<string>> = [
{ value: 'gradient', label: 'Gradient' }, { value: 'gradient', label: 'Gradient' },
{ value: 'lcd', label: 'Retro LCD' }, { value: 'lcd', label: 'Retro LCD' },
{ value: 'basic', label: 'Basic' }, { value: 'basic', label: 'Basic' },
]; ];
export const orientationOptions: Array<SelectOptionItem<VizOrientation>> = [ export const orientationOptions: Array<SelectableValue<VizOrientation>> = [
{ value: VizOrientation.Horizontal, label: 'Horizontal' }, { value: VizOrientation.Horizontal, label: 'Horizontal' },
{ value: VizOrientation.Vertical, label: 'Vertical' }, { value: VizOrientation.Vertical, label: 'Vertical' },
]; ];

View File

@ -1,11 +1,10 @@
import { Field, getFieldReducers } from '@grafana/data'; import { Field, fieldReducers } from '@grafana/data';
import { PanelModel } from '@grafana/ui'; import { PanelModel, FieldDisplayOptions } from '@grafana/ui';
import { GaugeOptions } from './types'; import { GaugeOptions } from './types';
import { import {
sharedSingleStatMigrationCheck, sharedSingleStatMigrationCheck,
migrateOldThresholds, migrateOldThresholds,
} from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions'; } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
import { FieldDisplayOptions } from '@grafana/ui/src/utils/fieldDisplay';
export const gaugePanelMigrationCheck = (panel: PanelModel<GaugeOptions>): Partial<GaugeOptions> => { export const gaugePanelMigrationCheck = (panel: PanelModel<GaugeOptions>): Partial<GaugeOptions> => {
if (!panel.options) { if (!panel.options) {
@ -33,7 +32,7 @@ export const gaugePanelMigrationCheck = (panel: PanelModel<GaugeOptions>): Parti
// Make sure the stats have a valid name // Make sure the stats have a valid name
if (valueOptions.stat) { if (valueOptions.stat) {
fieldOptions.calcs = getFieldReducers([valueOptions.stat]).map(s => s.id); fieldOptions.calcs = [fieldReducers.get(valueOptions.stat).id];
} }
field.min = old.minValue; field.min = old.minValue;
field.max = old.maxValue; field.max = old.maxValue;

View File

@ -2,10 +2,11 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components // Components
import { FormLabel, Select, PanelOptionsGroup, SelectOptionItem } from '@grafana/ui'; import { FormLabel, Select, PanelOptionsGroup } from '@grafana/ui';
// Types // Types
import { SingleStatOptions } from './types'; import { SingleStatOptions } from './types';
import { SelectableValue } from '@grafana/data';
const labelWidth = 6; const labelWidth = 6;
@ -20,13 +21,13 @@ const fontSizeOptions = percents.map(v => {
}); });
export class FontSizeEditor extends PureComponent<Props> { export class FontSizeEditor extends PureComponent<Props> {
setPrefixFontSize = (v: SelectOptionItem<string>) => setPrefixFontSize = (v: SelectableValue<string>) =>
this.props.onChange({ ...this.props.options, prefixFontSize: v.value }); this.props.onChange({ ...this.props.options, prefixFontSize: v.value });
setValueFontSize = (v: SelectOptionItem<string>) => setValueFontSize = (v: SelectableValue<string>) =>
this.props.onChange({ ...this.props.options, valueFontSize: v.value }); this.props.onChange({ ...this.props.options, valueFontSize: v.value });
setPostfixFontSize = (v: SelectOptionItem<string>) => setPostfixFontSize = (v: SelectableValue<string>) =>
this.props.onChange({ ...this.props.options, postfixFontSize: v.value }); this.props.onChange({ ...this.props.options, postfixFontSize: v.value });
render() { render() {

View File

@ -2,19 +2,20 @@
import React, { PureComponent, ChangeEvent } from 'react'; import React, { PureComponent, ChangeEvent } from 'react';
// Components // Components
import { PanelEditorProps, PanelOptionsGroup, Select, SelectOptionItem } from '@grafana/ui'; import { PanelEditorProps, PanelOptionsGroup, Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
// Types // Types
import { TextOptions, TextMode } from './types'; import { TextOptions, TextMode } from './types';
export class TextPanelEditor extends PureComponent<PanelEditorProps<TextOptions>> { export class TextPanelEditor extends PureComponent<PanelEditorProps<TextOptions>> {
modes: Array<SelectOptionItem<TextMode>> = [ modes: Array<SelectableValue<TextMode>> = [
{ value: 'markdown', label: 'Markdown' }, { value: 'markdown', label: 'Markdown' },
{ value: 'text', label: 'Text' }, { value: 'text', label: 'Text' },
{ value: 'html', label: 'HTML' }, { value: 'html', label: 'HTML' },
]; ];
onModeChange = (item: SelectOptionItem<TextMode>) => onModeChange = (item: SelectableValue<TextMode>) =>
this.props.onOptionsChange({ ...this.props.options, mode: item.value }); this.props.onOptionsChange({ ...this.props.options, mode: item.value });
onContentChange = (evt: ChangeEvent<HTMLTextAreaElement>) => { onContentChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {