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 { 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 = (
|
||||||
|
@ -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>>;
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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> = {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user