Stats: include all fields (#24829)

This commit is contained in:
Ryan McKinley 2020-05-29 12:36:15 -07:00 committed by GitHub
parent 2a6ac88a73
commit d526647005
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 168 additions and 31 deletions

View File

@ -21,6 +21,8 @@ import { GrafanaTheme } from '../types/theme';
import { reduceField, ReducerID } from '../transformations/fieldReducer'; import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { ScopedVars } from '../types/ScopedVars'; import { ScopedVars } from '../types/ScopedVars';
import { getTimeField } from '../dataframe/processDataFrame'; import { getTimeField } from '../dataframe/processDataFrame';
import { getFieldMatcher } from '../transformations';
import { FieldMatcherID } from '../transformations/matchers/ids';
/** /**
* Options for how to turn DataFrames into an array of display values * Options for how to turn DataFrames into an array of display values
@ -32,6 +34,8 @@ export interface ReduceDataOptions {
limit?: number; limit?: number;
/** When !values, pick one value for the whole field */ /** When !values, pick one value for the whole field */
calcs: string[]; calcs: string[];
/** Which fields to show. By default this is only numeric fields */
fields?: string;
} }
// TODO: use built in variables, same as for data links? // TODO: use built in variables, same as for data links?
@ -84,6 +88,16 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
const calcs = reduceOptions.calcs.length ? reduceOptions.calcs : [ReducerID.last]; const calcs = reduceOptions.calcs.length ? reduceOptions.calcs : [ReducerID.last];
const values: FieldDisplay[] = []; const values: FieldDisplay[] = [];
const fieldMatcher = getFieldMatcher(
reduceOptions.fields
? {
id: FieldMatcherID.byRegexp,
options: reduceOptions.fields,
}
: {
id: FieldMatcherID.numeric,
}
);
if (options.data) { if (options.data) {
// Field overrides are applied already // Field overrides are applied already
@ -104,7 +118,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
const fieldLinksSupplier = field.getLinks; const fieldLinksSupplier = field.getLinks;
// To filter out time field, need an option for this // To filter out time field, need an option for this
if (field.type !== FieldType.number) { if (!fieldMatcher(field, series, data)) {
continue; continue;
} }

View File

@ -45,7 +45,13 @@ export const valueMappingsOverrideProcessor = (
}; };
export interface SelectFieldConfigSettings<T> { export interface SelectFieldConfigSettings<T> {
allowCustomValue?: boolean;
/** The default options */
options: Array<SelectableValue<T>>; options: Array<SelectableValue<T>>;
/** Optionally use the context to define the options */
getOptions?: (context: FieldOverrideContext) => Promise<Array<SelectableValue<T>>>;
} }
export const selectOverrideProcessor = ( export const selectOverrideProcessor = (

View File

@ -1,11 +1,19 @@
import { Registry, RegistryItem } from '../utils/Registry'; import { Registry, RegistryItem } from '../utils/Registry';
import { ComponentType } from 'react'; import { ComponentType } from 'react';
import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
import { DataFrame, InterpolateFunction, VariableSuggestionsScope, VariableSuggestion } from '../types';
export interface StandardEditorContext {
data?: DataFrame[]; // All results
replaceVariables?: InterpolateFunction;
getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[];
}
export interface StandardEditorProps<TValue = any, TSettings = any> { export interface StandardEditorProps<TValue = any, TSettings = any> {
value: TValue; value: TValue;
onChange: (value?: TValue) => void; onChange: (value?: TValue) => void;
item: StandardEditorsRegistryItem<TValue, TSettings>; item: StandardEditorsRegistryItem<TValue, TSettings>;
context: StandardEditorContext;
} }
export interface StandardEditorsRegistryItem<TValue = any, TSettings = any> extends RegistryItem { export interface StandardEditorsRegistryItem<TValue = any, TSettings = any> extends RegistryItem {
editor: ComponentType<StandardEditorProps<TValue, TSettings>>; editor: ComponentType<StandardEditorProps<TValue, TSettings>>;

View File

@ -1,16 +1,7 @@
import { ComponentType } from 'react'; import { ComponentType } from 'react';
import { import { MatcherConfig, FieldConfig, Field, DataFrame, GrafanaTheme, TimeZone } from '../types';
MatcherConfig,
FieldConfig,
Field,
DataFrame,
VariableSuggestionsScope,
VariableSuggestion,
GrafanaTheme,
TimeZone,
} from '../types';
import { InterpolateFunction } from './panel'; import { InterpolateFunction } from './panel';
import { StandardEditorProps, FieldConfigOptionsRegistry } from '../field'; import { StandardEditorProps, FieldConfigOptionsRegistry, StandardEditorContext } from '../field';
import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; import { OptionsEditorItem } from './OptionsUIRegistryBuilder';
export interface DynamicConfigValue { export interface DynamicConfigValue {
@ -31,14 +22,11 @@ export interface FieldConfigSource<TOptions extends object = any> {
overrides: ConfigOverrideRule[]; overrides: ConfigOverrideRule[];
} }
export interface FieldOverrideContext { export interface FieldOverrideContext extends StandardEditorContext {
field?: Field; field?: Field;
dataFrameIndex?: number; // The index for the selected field frame dataFrameIndex?: number; // The index for the selected field frame
data: DataFrame[]; // All results data: DataFrame[]; // All results
replaceVariables?: InterpolateFunction;
getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[];
} }
export interface FieldConfigEditorProps<TValue, TSettings> export interface FieldConfigEditorProps<TValue, TSettings>
extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> { extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> {
item: FieldConfigPropertyItem<TValue, TSettings>; // The property info item: FieldConfigPropertyItem<TValue, TSettings>; // The property info

View File

@ -1,11 +1,75 @@
import React from 'react'; import React from 'react';
import { FieldConfigEditorProps, SelectFieldConfigSettings } from '@grafana/data'; import { FieldConfigEditorProps, SelectFieldConfigSettings, SelectableValue } from '@grafana/data';
import { Select } from '../Select/Select'; import { Select } from '../Select/Select';
export function SelectValueEditor<T>({ interface State<T> {
value, isLoading: boolean;
onChange, options: Array<SelectableValue<T>>;
item, }
}: FieldConfigEditorProps<T, SelectFieldConfigSettings<T>>) {
return <Select<T> defaultValue={value} onChange={e => onChange(e.value)} options={item.settings?.options} />; type Props<T> = FieldConfigEditorProps<T, SelectFieldConfigSettings<T>>;
export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>> {
state: State<T> = {
isLoading: true,
options: [],
};
componentDidMount() {
this.updateOptions();
}
componentDidUpdate(oldProps: Props<T>) {
const old = oldProps.item?.settings;
const now = this.props.item?.settings;
if (old !== now) {
this.updateOptions();
} else if (now.getOptions) {
const old = oldProps.context?.data;
const now = this.props.context?.data;
if (old !== now) {
this.updateOptions();
}
}
}
updateOptions = async () => {
const { item } = this.props;
const { settings } = item;
let options: Array<SelectableValue<T>> = item.settings?.options || [];
if (settings?.getOptions) {
options = await settings.getOptions(this.props.context);
}
if (this.state.options !== options) {
this.setState({
isLoading: false,
options,
});
}
};
render() {
const { options, isLoading } = this.state;
const { value, onChange, item } = this.props;
const { settings } = item;
const { allowCustomValue } = settings;
let current = options.find(v => v.value === value);
if (!current && value) {
current = {
label: `${value}`,
value,
};
}
return (
<Select<T>
isLoading={isLoading}
value={current}
defaultValue={value}
allowCustomValue={allowCustomValue}
onChange={e => onChange(e.value)}
options={options}
/>
);
}
} }

View File

@ -20,7 +20,7 @@ import {
} from '@grafana/data'; } from '@grafana/data';
import { Switch } from '../components/Switch/Switch'; import { Switch } from '../components/Switch/Switch';
import { NumberValueEditor, RadioButtonGroup, StringValueEditor, Select } from '../components'; import { NumberValueEditor, RadioButtonGroup, StringValueEditor, SelectValueEditor } from '../components';
import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings'; import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings';
import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds'; import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
import { UnitValueEditor } from '../components/OptionsUI/units'; import { UnitValueEditor } from '../components/OptionsUI/units';
@ -238,9 +238,7 @@ export const getStandardOptionEditors = () => {
id: 'select', id: 'select',
name: 'Select', name: 'Select',
description: 'Allows option selection', description: 'Allows option selection',
editor: props => ( editor: SelectValueEditor as any,
<Select value={props.value} onChange={e => props.onChange(e.value)} options={props.item.settings?.options} />
),
}; };
const radio: StandardEditorsRegistryItem<any> = { const radio: StandardEditorsRegistryItem<any> = {

View File

@ -1,5 +1,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { PanelOptionsEditorItem, PanelPlugin } from '@grafana/data'; import {
PanelOptionsEditorItem,
PanelPlugin,
DataFrame,
StandardEditorContext,
InterpolateFunction,
} from '@grafana/data';
import { get as lodashGet, set as lodashSet } from 'lodash'; import { get as lodashGet, set as lodashSet } from 'lodash';
import { Field, Label } from '@grafana/ui'; import { Field, Label } from '@grafana/ui';
import groupBy from 'lodash/groupBy'; import groupBy from 'lodash/groupBy';
@ -7,11 +13,19 @@ import { OptionsGroup } from './OptionsGroup';
interface PanelOptionsEditorProps<TOptions> { interface PanelOptionsEditorProps<TOptions> {
plugin: PanelPlugin; plugin: PanelPlugin;
data?: DataFrame[];
replaceVariables: InterpolateFunction;
options: TOptions; options: TOptions;
onChange: (options: TOptions) => void; onChange: (options: TOptions) => void;
} }
export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plugin, options, onChange }) => { export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({
plugin,
options,
onChange,
data,
replaceVariables,
}) => {
const optionEditors = useMemo<Record<string, PanelOptionsEditorItem[]>>(() => { const optionEditors = useMemo<Record<string, PanelOptionsEditorItem[]>>(() => {
return groupBy(plugin.optionEditors.list(), i => { return groupBy(plugin.optionEditors.list(), i => {
return i.category ? i.category[0] : 'Display'; return i.category ? i.category[0] : 'Display';
@ -23,6 +37,11 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
onChange(newOptions); onChange(newOptions);
}; };
const context: StandardEditorContext = {
data: data ?? [],
replaceVariables,
};
return ( return (
<> <>
{Object.keys(optionEditors).map((c, i) => { {Object.keys(optionEditors).map((c, i) => {
@ -43,6 +62,7 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
value={lodashGet(options, e.path)} value={lodashGet(options, e.path)}
onChange={value => onOptionChange(e.path, value)} onChange={value => onOptionChange(e.path, value)}
item={e} item={e}
context={context}
/> />
</Field> </Field>
); );

View File

@ -83,7 +83,9 @@ export const PanelOptionsTab: FC<Props> = ({
key="panel options" key="panel options"
options={panel.getOptions()} options={panel.getOptions()}
onChange={onPanelOptionsChanged} onChange={onPanelOptionsChanged}
replaceVariables={panel.replaceVariables}
plugin={plugin} plugin={plugin}
data={data?.series}
/> />
); );
} }

View File

@ -1,5 +1,12 @@
import { SingleStatBaseOptions, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode } from '@grafana/ui'; import { SingleStatBaseOptions, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode } from '@grafana/ui';
import { ReducerID, SelectableValue, standardEditorsRegistry } from '@grafana/data'; import {
ReducerID,
SelectableValue,
standardEditorsRegistry,
FieldOverrideContext,
getFieldDisplayName,
escapeStringForRegex,
} from '@grafana/data';
import { PanelOptionsEditorBuilder } from '@grafana/data'; import { PanelOptionsEditorBuilder } from '@grafana/data';
// Structure copied from angular // Structure copied from angular
@ -26,7 +33,8 @@ export const justifyModes: Array<SelectableValue<BigValueJustifyMode>> = [
export function addStandardDataReduceOptions( export function addStandardDataReduceOptions(
builder: PanelOptionsEditorBuilder<SingleStatBaseOptions>, builder: PanelOptionsEditorBuilder<SingleStatBaseOptions>,
includeOrientation = true includeOrientation = true,
includeFieldMatcher = true
) { ) {
builder.addRadio({ builder.addRadio({
path: 'reduceOptions.values', path: 'reduceOptions.values',
@ -65,6 +73,35 @@ export function addStandardDataReduceOptions(
showIf: currentConfig => currentConfig.reduceOptions.values === false, showIf: currentConfig => currentConfig.reduceOptions.values === false,
}); });
if (includeFieldMatcher) {
builder.addSelect({
path: 'reduceOptions.fields',
name: 'Fields',
description: 'Select the fields that should be included in the panel',
settings: {
allowCustomValue: true,
options: [],
getOptions: async (context: FieldOverrideContext) => {
const options = [
{ value: '', label: 'Numeric Fields' },
{ value: '/.*/', label: 'All Fields' },
];
if (context && context.data) {
for (const frame of context.data) {
for (const field of frame.fields) {
const name = getFieldDisplayName(field, frame, context.data);
const value = `/^${escapeStringForRegex(name)}$/`;
options.push({ value, label: name });
}
}
}
return Promise.resolve(options);
},
},
defaultValue: '',
});
}
if (includeOrientation) { if (includeOrientation) {
builder.addRadio({ builder.addRadio({
path: 'orientation', path: 'orientation',