From 17287420961b370d89f2a3172ed2c23f412bc1b8 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Sun, 9 Feb 2020 14:34:42 +0100 Subject: [PATCH] New panel edit: field overrides ui (#22036) * Add title editor * Wip * FIeld config overrides UI (v1) * Basic property override editors * name to prop * use prop not path Co-authored-by: Ryan McKinley --- .../src/field/fieldOverrides.test.ts | 4 +- .../grafana-data/src/field/fieldOverrides.ts | 4 +- .../grafana-data/src/types/fieldOverrides.ts | 5 +- .../FieldConfigs/FieldConfigEditor.tsx | 145 +++++++++++++++++- .../src/components/FieldConfigs/number.tsx | 46 ++++-- .../src/components/FieldConfigs/string.tsx | 17 +- .../grafana-ui/src/components/Forms/index.ts | 2 + .../MatchersUI/FieldNameMatcherEditor.tsx | 27 +++- .../components/PanelEditor/PanelEditor.tsx | 6 +- 9 files changed, 215 insertions(+), 41 deletions(-) diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index d8cd2984f7c..3f8d32e9059 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -24,8 +24,8 @@ describe('FieldOverrides', () => { { matcher: { id: FieldMatcherID.numeric }, properties: [ - { path: 'decimals', value: 1 }, // Numeric - { path: 'title', value: 'Kittens' }, // Text + { prop: 'decimals', value: 1 }, // Numeric + { prop: 'title', value: 'Kittens' }, // Text ], }, ], diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 4d4bfc87113..3a6b1acdd3c 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -218,8 +218,8 @@ function prepareConfigValue(key: string, input: any, options?: DynamicConfigValu export function setDynamicConfigValue(config: FieldConfig, options: DynamicConfigValueOptions) { const { value } = options; - const v = prepareConfigValue(value.path, value.value, options); - set(config, value.path, v); + const v = prepareConfigValue(value.prop, value.value, options); + set(config, value.prop, v); } /** diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 9ae776c42b2..f9849f98790 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -5,8 +5,9 @@ import { InterpolateFunction } from './panel'; import { DataFrame } from 'apache-arrow'; export interface DynamicConfigValue { - path: string; - value: any; + prop: string; + value?: any; + custom?: boolean; } export interface ConfigOverrideRule { diff --git a/packages/grafana-ui/src/components/FieldConfigs/FieldConfigEditor.tsx b/packages/grafana-ui/src/components/FieldConfigs/FieldConfigEditor.tsx index 30fc75bff99..1705ad6ed7a 100644 --- a/packages/grafana-ui/src/components/FieldConfigs/FieldConfigEditor.tsx +++ b/packages/grafana-ui/src/components/FieldConfigs/FieldConfigEditor.tsx @@ -1,7 +1,15 @@ import React from 'react'; -import { FieldConfigEditorRegistry, FieldConfigSource, DataFrame, FieldPropertyEditorItem } from '@grafana/data'; +import cloneDeep from 'lodash/cloneDeep'; +import { + FieldConfigEditorRegistry, + FieldConfigSource, + DataFrame, + FieldPropertyEditorItem, + DynamicConfigValue, +} from '@grafana/data'; import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; import Forms from '../Forms'; +import { fieldMatchersUI } from '../MatchersUI/fieldMatchersUI'; interface Props { config: FieldConfigSource; @@ -43,6 +51,41 @@ export class FieldConfigEditor extends React.PureComponent { }); }; + onMatcherConfigChange = (index: number, matcherConfig?: any) => { + const { config } = this.props; + let overrides = cloneDeep(config.overrides); + if (matcherConfig === undefined) { + overrides = overrides.splice(index, 1); + } else { + overrides[index].matcher.options = matcherConfig; + } + this.props.onChange({ ...config, overrides }); + }; + + onDynamicConfigValueAdd = (index: number, prop: string, custom?: boolean) => { + const { config } = this.props; + let overrides = cloneDeep(config.overrides); + + const propertyConfig: DynamicConfigValue = { + prop, + custom, + }; + if (overrides[index].properties) { + overrides[index].properties.push(propertyConfig); + } else { + overrides[index].properties = [propertyConfig]; + } + + this.props.onChange({ ...config, overrides }); + }; + + onDynamicConfigValueChange = (overrideIndex: number, propertyIndex: number, value?: any) => { + const { config } = this.props; + let overrides = cloneDeep(config.overrides); + overrides[overrideIndex].properties[propertyIndex].value = value; + this.props.onChange({ ...config, overrides }); + }; + renderEditor(item: FieldPropertyEditorItem, custom: boolean) { const config = this.props.config.defaults; const value = custom ? (config.custom ? config.custom[item.id] : undefined) : (config as any)[item.id]; @@ -71,20 +114,110 @@ export class FieldConfigEditor extends React.PureComponent { } renderOverrides() { - return
Override rules
; + const { config, data, custom } = this.props; + + let configPropertiesOptions = standardFieldConfigEditorRegistry.list().map(i => ({ + label: i.name, + value: i.id, + description: i.description, + custom: false, + })); + + if (custom) { + configPropertiesOptions = configPropertiesOptions.concat( + custom.list().map(i => ({ + label: i.name, + value: i.id, + description: i.description, + custom: true, + })) + ); + } + + return ( + <> + {config.overrides.map((o, i) => { + const matcherUi = fieldMatchersUI.get(o.matcher.id); + return ( +
+ + <> + this.onMatcherConfigChange(i, option)} + /> + { + this.onDynamicConfigValueAdd(i, o.value!, o.custom); + }} + /> + + {o.properties.map((p, j) => { + const reg = p.custom ? custom : standardFieldConfigEditorRegistry; + const item = reg?.get(p.prop); + if (!item) { + return
Unknown property: {p.prop}
; + } + return ( + + { + this.onDynamicConfigValueChange(i, j, value); + }} + item={item} + context={{} as any} + /> + + ); + })} + +
+
+ ); + })} + + ); } - renderAddOverride() { - return
Override rules
; - } + renderAddOverride = () => { + return ( + ({ label: i.name, value: i.id, description: i.description }))} + onChange={value => { + const { onChange, config } = this.props; + onChange({ + ...config, + overrides: [ + ...config.overrides, + { + matcher: { + id: value.value!, + }, + properties: [], + }, + ], + }); + }} + /> + ); + }; render() { return (
{this.renderStandardConfigs()} {this.renderCustomConfigs()} - {this.renderOverrides()} {this.renderAddOverride()} + {this.renderOverrides()}
); } diff --git a/packages/grafana-ui/src/components/FieldConfigs/number.tsx b/packages/grafana-ui/src/components/FieldConfigs/number.tsx index 5cd2e46cf14..b5f1af650d7 100644 --- a/packages/grafana-ui/src/components/FieldConfigs/number.tsx +++ b/packages/grafana-ui/src/components/FieldConfigs/number.tsx @@ -1,6 +1,12 @@ import React from 'react'; -import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps } from '@grafana/data'; +import { + FieldOverrideContext, + FieldOverrideEditorProps, + FieldConfigEditorProps, + toIntegerOrUndefined, + toFloatOrUndefined, +} from '@grafana/data'; import Forms from '../Forms'; export interface NumberFieldConfigSettings { @@ -32,27 +38,37 @@ export const NumberValueEditor: React.FC { onChange( - item.settings.integer - ? parseInt(e.currentTarget.value, settings.step || 10) - : parseFloat(e.currentTarget.value) + settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value) ); }} /> ); }; -export class NumberOverrideEditor extends React.PureComponent< - FieldOverrideEditorProps -> { - constructor(props: FieldOverrideEditorProps) { - super(props); - } - - render() { - return
SHOW OVERRIDE EDITOR {this.props.item.name}
; - } -} +export const NumberOverrideEditor: React.FC> = ({ + value, + onChange, + item, +}) => { + const { settings } = item; + return ( + { + onChange( + settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value) + ); + }} + /> + ); +}; diff --git a/packages/grafana-ui/src/components/FieldConfigs/string.tsx b/packages/grafana-ui/src/components/FieldConfigs/string.tsx index 6d5bebf1925..1f387295e9f 100644 --- a/packages/grafana-ui/src/components/FieldConfigs/string.tsx +++ b/packages/grafana-ui/src/components/FieldConfigs/string.tsx @@ -23,14 +23,9 @@ export const StringValueEditor: React.FC onChange(e.currentTarget.value)} />; }; -export class StringOverrideEditor extends React.PureComponent< - FieldOverrideEditorProps -> { - constructor(props: FieldOverrideEditorProps) { - super(props); - } - - render() { - return
SHOW OVERRIDE EDITOR {this.props.item.name}
; - } -} +export const StringOverrideEditor: React.FC> = ({ + value, + onChange, +}) => { + return onChange(e.currentTarget.value)} />; +}; diff --git a/packages/grafana-ui/src/components/Forms/index.ts b/packages/grafana-ui/src/components/Forms/index.ts index 4a5a2fc9534..f5001f3c31a 100644 --- a/packages/grafana-ui/src/components/Forms/index.ts +++ b/packages/grafana-ui/src/components/Forms/index.ts @@ -2,6 +2,7 @@ import { getFormStyles } from './getFormStyles'; import { Label } from './Label'; import { Input } from './Input/Input'; import { Select } from './Select/Select'; +import { ButtonSelect } from './Select/ButtonSelect'; import { Form } from './Form'; import { Field } from './Field'; import { Button, LinkButton } from './Button'; @@ -16,6 +17,7 @@ const Forms = { Button, LinkButton, Select, + ButtonSelect, InputControl, }; diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx index 4e16ecf0bd4..0fcbcc2eef9 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx @@ -1,12 +1,35 @@ import React from 'react'; import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; import { FieldMatcherID, fieldMatchers } from '@grafana/data'; +import Forms from '../Forms'; export class FieldNameMatcherEditor extends React.PureComponent> { render() { - const { matcher } = this.props; + const { data, options, onChange } = this.props; + const names: Set = new Set(); - return
TODO: MATCH STRING for: {matcher.id}
; + for (const frame of data) { + for (const field of frame.fields) { + names.add(field.name); + } + } + if (options) { + names.add(options); + } + const selectOptions = Array.from(names).map(n => ({ + value: n, + label: n, + })); + const selectedOption = selectOptions.find(v => v.value === options); + + return ( + onChange(o.value!)} + /> + ); } } diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 8d92e0f1073..062ff1fa83f 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -240,7 +240,11 @@ export class PanelEditor extends PureComponent { } if (plugin.editor && panel) { - return ; + return ( +
+ +
+ ); } return
No editor (angular?)
;