mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Stats: include all fields (#24829)
This commit is contained in:
parent
2a6ac88a73
commit
d526647005
@ -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 = (
|
||||
|
@ -1,11 +1,19 @@
|
||||
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>>;
|
||||
|
@ -1,16 +1,7 @@
|
||||
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
|
||||
|
@ -1,11 +1,75 @@
|
||||
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> = {
|
||||
|
@ -1,5 +1,11 @@
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
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',
|
||||
|
Loading…
Reference in New Issue
Block a user