Commit d5266470 by Ryan McKinley Committed by GitHub

Stats: include all fields (#24829)

parent 2a6ac88a
......@@ -21,6 +21,8 @@ import { GrafanaTheme } from '../types/theme';
import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { ScopedVars } from '../types/ScopedVars';
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
......@@ -32,6 +34,8 @@ export interface ReduceDataOptions {
limit?: number;
/** When !values, pick one value for the whole field */
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?
......@@ -84,6 +88,16 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
const calcs = reduceOptions.calcs.length ? reduceOptions.calcs : [ReducerID.last];
const values: FieldDisplay[] = [];
const fieldMatcher = getFieldMatcher(
reduceOptions.fields
? {
id: FieldMatcherID.byRegexp,
options: reduceOptions.fields,
}
: {
id: FieldMatcherID.numeric,
}
);
if (options.data) {
// Field overrides are applied already
......@@ -104,7 +118,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
const fieldLinksSupplier = field.getLinks;
// To filter out time field, need an option for this
if (field.type !== FieldType.number) {
if (!fieldMatcher(field, series, data)) {
continue;
}
......
......@@ -45,7 +45,13 @@ export const valueMappingsOverrideProcessor = (
};
export interface SelectFieldConfigSettings<T> {
allowCustomValue?: boolean;
/** The default options */
options: Array<SelectableValue<T>>;
/** Optionally use the context to define the options */
getOptions?: (context: FieldOverrideContext) => Promise<Array<SelectableValue<T>>>;
}
export const selectOverrideProcessor = (
......
import { Registry, RegistryItem } from '../utils/Registry';
import { ComponentType } from 'react';
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> {
value: TValue;
onChange: (value?: TValue) => void;
item: StandardEditorsRegistryItem<TValue, TSettings>;
context: StandardEditorContext;
}
export interface StandardEditorsRegistryItem<TValue = any, TSettings = any> extends RegistryItem {
editor: ComponentType<StandardEditorProps<TValue, TSettings>>;
......
import { ComponentType } from 'react';
import {
MatcherConfig,
FieldConfig,
Field,
DataFrame,
VariableSuggestionsScope,
VariableSuggestion,
GrafanaTheme,
TimeZone,
} from '../types';
import { MatcherConfig, FieldConfig, Field, DataFrame, GrafanaTheme, TimeZone } from '../types';
import { InterpolateFunction } from './panel';
import { StandardEditorProps, FieldConfigOptionsRegistry } from '../field';
import { StandardEditorProps, FieldConfigOptionsRegistry, StandardEditorContext } from '../field';
import { OptionsEditorItem } from './OptionsUIRegistryBuilder';
export interface DynamicConfigValue {
......@@ -31,14 +22,11 @@ export interface FieldConfigSource<TOptions extends object = any> {
overrides: ConfigOverrideRule[];
}
export interface FieldOverrideContext {
export interface FieldOverrideContext extends StandardEditorContext {
field?: Field;
dataFrameIndex?: number; // The index for the selected field frame
data: DataFrame[]; // All results
replaceVariables?: InterpolateFunction;
getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[];
}
export interface FieldConfigEditorProps<TValue, TSettings>
extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> {
item: FieldConfigPropertyItem<TValue, TSettings>; // The property info
......
import React from 'react';
import { FieldConfigEditorProps, SelectFieldConfigSettings } from '@grafana/data';
import { FieldConfigEditorProps, SelectFieldConfigSettings, SelectableValue } from '@grafana/data';
import { Select } from '../Select/Select';
export function SelectValueEditor<T>({
value,
onChange,
item,
}: FieldConfigEditorProps<T, SelectFieldConfigSettings<T>>) {
return <Select<T> defaultValue={value} onChange={e => onChange(e.value)} options={item.settings?.options} />;
interface State<T> {
isLoading: boolean;
options: Array<SelectableValue<T>>;
}
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}
/>
);
}
}
......@@ -20,7 +20,7 @@ import {
} from '@grafana/data';
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 { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
import { UnitValueEditor } from '../components/OptionsUI/units';
......@@ -238,9 +238,7 @@ export const getStandardOptionEditors = () => {
id: 'select',
name: 'Select',
description: 'Allows option selection',
editor: props => (
<Select value={props.value} onChange={e => props.onChange(e.value)} options={props.item.settings?.options} />
),
editor: SelectValueEditor as any,
};
const radio: StandardEditorsRegistryItem<any> = {
......
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 { Field, Label } from '@grafana/ui';
import groupBy from 'lodash/groupBy';
......@@ -7,11 +13,19 @@ import { OptionsGroup } from './OptionsGroup';
interface PanelOptionsEditorProps<TOptions> {
plugin: PanelPlugin;
data?: DataFrame[];
replaceVariables: InterpolateFunction;
options: TOptions;
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[]>>(() => {
return groupBy(plugin.optionEditors.list(), i => {
return i.category ? i.category[0] : 'Display';
......@@ -23,6 +37,11 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
onChange(newOptions);
};
const context: StandardEditorContext = {
data: data ?? [],
replaceVariables,
};
return (
<>
{Object.keys(optionEditors).map((c, i) => {
......@@ -43,6 +62,7 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
value={lodashGet(options, e.path)}
onChange={value => onOptionChange(e.path, value)}
item={e}
context={context}
/>
</Field>
);
......
......@@ -83,7 +83,9 @@ export const PanelOptionsTab: FC<Props> = ({
key="panel options"
options={panel.getOptions()}
onChange={onPanelOptionsChanged}
replaceVariables={panel.replaceVariables}
plugin={plugin}
data={data?.series}
/>
);
}
......
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';
// Structure copied from angular
......@@ -26,7 +33,8 @@ export const justifyModes: Array<SelectableValue<BigValueJustifyMode>> = [
export function addStandardDataReduceOptions(
builder: PanelOptionsEditorBuilder<SingleStatBaseOptions>,
includeOrientation = true
includeOrientation = true,
includeFieldMatcher = true
) {
builder.addRadio({
path: 'reduceOptions.values',
......@@ -65,6 +73,35 @@ export function addStandardDataReduceOptions(
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) {
builder.addRadio({
path: 'orientation',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment