Registry: add a reusable function registry (#17047)

pull/18150/head
Ryan McKinley 6 years ago committed by GitHub
parent 5151b8ce07
commit c194ae1ba5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/grafana-data/src/types/index.ts
  2. 10
      packages/grafana-data/src/types/select.ts
  3. 63
      packages/grafana-data/src/utils/fieldReducer.test.ts
  4. 257
      packages/grafana-data/src/utils/fieldReducer.ts
  5. 1
      packages/grafana-data/src/utils/index.ts
  6. 134
      packages/grafana-data/src/utils/registry.ts
  7. 6
      packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx
  8. 8
      packages/grafana-ui/src/components/Select/ButtonSelect.story.tsx
  9. 11
      packages/grafana-ui/src/components/Select/ButtonSelect.tsx
  10. 21
      packages/grafana-ui/src/components/Select/Select.tsx
  11. 5
      packages/grafana-ui/src/components/SetInterval/SetInterval.tsx
  12. 8
      packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx
  13. 5
      packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx
  14. 7
      packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts
  15. 21
      packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx
  16. 9
      packages/grafana-ui/src/components/TimePicker/TimePicker.tsx
  17. 2
      packages/grafana-ui/src/components/index.ts
  18. 5
      public/app/core/components/PermissionList/AddPermission.tsx
  19. 5
      public/app/core/components/PermissionList/PermissionListItem.tsx
  20. 5
      public/app/core/components/Select/DataSourcePicker.tsx
  21. 7
      public/app/core/components/Select/MetricSelect.tsx
  22. 5
      public/app/features/explore/AdHocFilter.tsx
  23. 10
      public/app/features/explore/ExploreToolbar.tsx
  24. 4
      public/app/features/teams/TeamMemberRow.test.tsx
  25. 5
      public/app/features/teams/TeamMemberRow.tsx
  26. 6
      public/app/plugins/datasource/input/InputQueryEditor.tsx
  27. 7
      public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
  28. 17
      public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx
  29. 4
      public/app/plugins/datasource/stackdriver/components/Alignments.tsx
  30. 5
      public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx
  31. 5
      public/app/plugins/datasource/testdata/QueryEditor.tsx
  32. 7
      public/app/plugins/panel/bargauge/types.ts
  33. 7
      public/app/plugins/panel/gauge/GaugeMigrations.ts
  34. 9
      public/app/plugins/panel/singlestat2/FontSizeEditor.tsx
  35. 7
      public/app/plugins/panel/text2/TextPanelEditor.tsx

@ -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';

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

@ -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 : '<NOT FOUND>';
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 = [
{

@ -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<FieldReducerInfo>());
}
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<FieldReducerInfo>(() => [
{
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;
}

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

@ -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
}
}

@ -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<Props> {
super(props);
}
intervalsToOptions = (intervals: string[] | undefined): Array<SelectOptionItem<string>> => {
intervalsToOptions = (intervals: string[] | undefined): Array<SelectableValue<string>> => {
const intervalsOrDefault = intervals || defaultIntervals;
const options = intervalsOrDefault
.filter(str => str !== '')
@ -37,7 +37,7 @@ export class RefreshPicker extends PureComponent<Props> {
return options;
};
onChangeSelect = (item: SelectOptionItem<string>) => {
onChangeSelect = (item: SelectableValue<string>) => {
const { onIntervalChanged } = this.props;
if (onIntervalChanged) {
// @ts-ignore

@ -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<string> = { label: 'A label', value: 'A value' };
const value = object<SelectOptionItem<string>>('Selected Value:', intialState);
const options = object<Array<SelectOptionItem<string>>>('Options:', [
const intialState: SelectableValue<string> = { label: 'A label', value: 'A value' };
const value = object<SelectableValue<string>>('Selected Value:', intialState);
const options = object<Array<SelectableValue<string>>>('Options:', [
intialState,
{ label: 'Another label', value: 'Another value' },
]);

@ -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<T> {
className: string | undefined;
options: Array<SelectOptionItem<T>>;
value?: SelectOptionItem<T>;
options: Array<SelectableValue<T>>;
value?: SelectableValue<T>;
label?: ReactElement | string;
iconClass?: string;
components?: any;
maxMenuHeight?: number;
onChange: (item: SelectOptionItem<T>) => void;
onChange: (item: SelectableValue<T>) => void;
tooltipContent?: PopperContent<any>;
isMenuOpen?: boolean;
onOpenMenu?: () => void;
@ -45,7 +46,7 @@ export interface Props<T> {
}
export class ButtonSelect<T> extends PureComponent<Props<T>> {
onChange = (item: SelectOptionItem<T>) => {
onChange = (item: SelectableValue<T>) => {
const { onChange } = this.props;
onChange(item);
};

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

@ -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<number> {
export function getIntervalFromString(strInterval: string): SelectableValue<number> {
return {
label: strInterval,
value: stringToMs(strInterval),

@ -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<SelectOptionItem<boolean>> = [
const showOptions: Array<SelectableValue<boolean>> = [
{
value: true,
label: 'All Values',
@ -31,7 +31,7 @@ export interface Props {
}
export class FieldDisplayEditor extends PureComponent<Props> {
onShowValuesChange = (item: SelectOptionItem<boolean>) => {
onShowValuesChange = (item: SelectableValue<boolean>) => {
const val = item.value === true;
this.props.onChange({ ...this.props.value, values: val });
};

@ -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<Props> = ({ value, onChange, showMi
[value.max, onChange]
);
const onUnitChange = (unit: SelectOptionItem<string>) => {
const onUnitChange = (unit: SelectableValue<string>) => {
onChange({ ...value, unit: unit.value });
};

@ -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<SingleStatBaseO
// Make sure the stats have a valid name
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;

@ -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<Props> {
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<Props> {
}
};
onSelectionChange = (item: SelectOptionItem<string>) => {
onSelectionChange = (item: SelectableValue<string>) => {
const { onChange } = this.props;
if (isArray(item)) {
onChange(item.map(v => v.value));
@ -65,24 +64,16 @@ export class StatsPicker extends PureComponent<Props> {
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<SelectOptionItem<string>> = options.filter(option => stats.find(stat => option.value === stat));
const select = fieldReducers.selectOptions(stats);
return (
<Select
width={width}
value={value}
value={select.current}
isClearable={!defaultStat}
isMulti={allowMultiple}
isSearchable={true}
options={options}
options={select.options}
placeholder={placeholder}
onChange={this.onSelectionChange}
/>

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

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

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

@ -1,5 +1,6 @@
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 { FolderInfo } from 'app/types';
@ -39,7 +40,7 @@ interface Props {
}
export default class PermissionsListItem extends PureComponent<Props> {
onPermissionChanged = (option: SelectOptionItem<PermissionLevel>) => {
onPermissionChanged = (option: SelectableValue<PermissionLevel>) => {
this.props.onPermissionChanged(this.props.item, option.value);
};

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

@ -1,12 +1,13 @@
import React from 'react';
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';
export interface Props {
onChange: (value: string) => void;
options: Array<SelectOptionItem<string>>;
options: Array<SelectableValue<string>>;
isSearchable: boolean;
value: string;
placeholder?: string;
@ -15,7 +16,7 @@ export interface Props {
}
interface State {
options: Array<SelectOptionItem<string>>;
options: Array<SelectableValue<string>>;
}
export class MetricSelect extends React.Component<Props, State> {

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

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

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

@ -1,6 +1,7 @@
import React, { PureComponent } from 'react';
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 { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
@ -27,7 +28,7 @@ export class TeamMemberRow extends PureComponent<Props> {
this.props.removeTeamMember(member.userId);
}
onPermissionChange = (item: SelectOptionItem<TeamPermissionLevel>, member: TeamMember) => {
onPermissionChange = (item: SelectableValue<TeamPermissionLevel>, member: TeamMember) => {
const permission = item.value;
const updatedTeamMember = { ...member, permission };

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

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

@ -2,33 +2,32 @@ import _ from 'lodash';
import React, { PureComponent } from 'react';
// Types
import { FormLabel, Select, SelectOptionItem, Switch } from '@grafana/ui';
import { QueryEditorProps, DataSourceStatus } from '@grafana/ui';
import { FormLabel, Select, Switch, QueryEditorProps, DataSourceStatus } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { PrometheusDatasource } from '../datasource';
import { PromQuery, PromOptions } from '../types';
import PromQueryField from './PromQueryField';
import PromLink from './PromLink';
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: 'Table', value: 'table' },
{ 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,
label: '1/' + value,
}));
interface State {
legendFormat: string;
formatOption: SelectOptionItem<string>;
formatOption: SelectableValue<string>;
interval: string;
intervalFactorOption: SelectOptionItem<number>;
intervalFactorOption: SelectableValue<number>;
instant: boolean;
}
@ -58,7 +57,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
this.query.expr = query.expr;
};
onFormatChange = (option: SelectOptionItem<string>) => {
onFormatChange = (option: SelectableValue<string>) => {
this.query.format = option.value;
this.setState({ formatOption: option }, this.onRunQuery);
};
@ -75,7 +74,7 @@ export class PromQueryEditor extends PureComponent<Props, State> {
this.setState({ interval });
};
onIntervalFactorChange = (option: SelectOptionItem<number>) => {
onIntervalFactorChange = (option: SelectableValue<number>) => {
this.query.intervalFactor = option.value;
this.setState({ intervalFactorOption: option }, this.onRunQuery);
};

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

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

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

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

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

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

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

Loading…
Cancel
Save