From d526647005d8fcba85a02b4de70a10c689726d16 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 29 May 2020 12:36:15 -0700 Subject: [PATCH] Stats: include all fields (#24829) --- .../grafana-data/src/field/fieldDisplay.ts | 16 +++- .../src/field/overrides/processors.ts | 6 ++ .../standardFieldConfigEditorRegistry.ts | 8 ++ .../grafana-data/src/types/fieldOverrides.ts | 18 +---- .../src/components/OptionsUI/select.tsx | 78 +++++++++++++++++-- .../grafana-ui/src/utils/standardEditors.tsx | 6 +- .../PanelEditor/PanelOptionsEditor.tsx | 24 +++++- .../PanelEditor/PanelOptionsTab.tsx | 2 + public/app/plugins/panel/stat/types.ts | 41 +++++++++- 9 files changed, 168 insertions(+), 31 deletions(-) diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index 087c488a999..8ed6dcccbbe 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -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; } diff --git a/packages/grafana-data/src/field/overrides/processors.ts b/packages/grafana-data/src/field/overrides/processors.ts index 7d8ef2cae78..a50da40520e 100644 --- a/packages/grafana-data/src/field/overrides/processors.ts +++ b/packages/grafana-data/src/field/overrides/processors.ts @@ -45,7 +45,13 @@ export const valueMappingsOverrideProcessor = ( }; export interface SelectFieldConfigSettings { + allowCustomValue?: boolean; + + /** The default options */ options: Array>; + + /** Optionally use the context to define the options */ + getOptions?: (context: FieldOverrideContext) => Promise>>; } export const selectOverrideProcessor = ( diff --git a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts index 854d87eeb84..925e89c2987 100644 --- a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts +++ b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts @@ -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 { value: TValue; onChange: (value?: TValue) => void; item: StandardEditorsRegistryItem; + context: StandardEditorContext; } export interface StandardEditorsRegistryItem extends RegistryItem { editor: ComponentType>; diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index ba5f3a0cbe1..55b0d115095 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -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 { 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 extends Omit, 'item'> { item: FieldConfigPropertyItem; // The property info diff --git a/packages/grafana-ui/src/components/OptionsUI/select.tsx b/packages/grafana-ui/src/components/OptionsUI/select.tsx index b14572ee726..5f83109f6fe 100644 --- a/packages/grafana-ui/src/components/OptionsUI/select.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/select.tsx @@ -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({ - value, - onChange, - item, -}: FieldConfigEditorProps>) { - return defaultValue={value} onChange={e => onChange(e.value)} options={item.settings?.options} />; +interface State { + isLoading: boolean; + options: Array>; +} + +type Props = FieldConfigEditorProps>; + +export class SelectValueEditor extends React.PureComponent, State> { + state: State = { + isLoading: true, + options: [], + }; + + componentDidMount() { + this.updateOptions(); + } + + componentDidUpdate(oldProps: Props) { + 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> = 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 ( + + isLoading={isLoading} + value={current} + defaultValue={value} + allowCustomValue={allowCustomValue} + onChange={e => onChange(e.value)} + options={options} + /> + ); + } } diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index ae1dfae8c4e..7d9e808e819 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -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 => ( -