Commit 17287420 by Dominik Prokop Committed by GitHub

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 <ryantxu@gmail.com>
parent 25431f32
......@@ -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
],
},
],
......
......@@ -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);
}
/**
......
......@@ -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 {
......
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<Props> {
});
};
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<Props> {
}
renderOverrides() {
return <div>Override rules</div>;
}
const { config, data, custom } = this.props;
renderAddOverride() {
return <div>Override rules</div>;
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 (
<div key={`${o.matcher.id}/${i}`}>
<Forms.Field label={matcherUi.name} description={matcherUi.description}>
<>
<matcherUi.component
matcher={matcherUi.matcher}
data={data}
options={o.matcher.options}
onChange={option => this.onMatcherConfigChange(i, option)}
/>
<Forms.ButtonSelect
icon="plus"
placeholder="Set config property"
options={configPropertiesOptions}
onChange={o => {
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 <div>Unknown property: {p.prop}</div>;
}
return (
<Forms.Field label={item.name} description={item.description}>
<item.override
value={p.value}
onChange={value => {
this.onDynamicConfigValueChange(i, j, value);
}}
item={item}
context={{} as any}
/>
</Forms.Field>
);
})}
</>
</Forms.Field>
</div>
);
})}
</>
);
}
renderAddOverride = () => {
return (
<Forms.ButtonSelect
icon="plus"
placeholder={'Add override'}
value={{ label: 'Add override' }}
options={fieldMatchersUI.list().map(i => ({ 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 (
<div>
{this.renderStandardConfigs()}
{this.renderCustomConfigs()}
{this.renderOverrides()}
{this.renderAddOverride()}
{this.renderOverrides()}
</div>
);
}
......
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<FieldConfigEditorProps<number, NumberFi
return (
<Forms.Input
value={isNaN(value) ? '' : value}
min={settings.min}
max={settings.max}
type="number"
step={settings.step}
onChange={e => {
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<number, NumberFieldConfigSettings>
> {
constructor(props: FieldOverrideEditorProps<number, NumberFieldConfigSettings>) {
super(props);
}
render() {
return <div>SHOW OVERRIDE EDITOR {this.props.item.name}</div>;
}
}
export const NumberOverrideEditor: React.FC<FieldOverrideEditorProps<number, NumberFieldConfigSettings>> = ({
value,
onChange,
item,
}) => {
const { settings } = item;
return (
<Forms.Input
value={isNaN(value) ? '' : value}
min={settings.min}
max={settings.max}
type="number"
step={settings.step}
onChange={e => {
onChange(
settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value)
);
}}
/>
);
};
......@@ -23,14 +23,9 @@ export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFi
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
};
export class StringOverrideEditor extends React.PureComponent<
FieldOverrideEditorProps<string, StringFieldConfigSettings>
> {
constructor(props: FieldOverrideEditorProps<string, StringFieldConfigSettings>) {
super(props);
}
render() {
return <div>SHOW OVERRIDE EDITOR {this.props.item.name}</div>;
}
}
export const StringOverrideEditor: React.FC<FieldOverrideEditorProps<string, StringFieldConfigSettings>> = ({
value,
onChange,
}) => {
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
};
......@@ -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,
};
......
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<MatcherUIProps<string>> {
render() {
const { matcher } = this.props;
const { data, options, onChange } = this.props;
const names: Set<string> = new Set();
return <div>TODO: MATCH STRING for: {matcher.id}</div>;
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 (
<Forms.Select
allowCustomValue
value={selectedOption}
options={selectOptions}
onChange={o => onChange(o.value!)}
/>
);
}
}
......
......@@ -240,7 +240,11 @@ export class PanelEditor extends PureComponent<Props, State> {
}
if (plugin.editor && panel) {
return <plugin.editor data={data} options={panel.getOptions()} onOptionsChange={this.onPanelOptionsChanged} />;
return (
<div style={{ marginTop: '40px' }}>
<plugin.editor data={data} options={panel.getOptions()} onOptionsChange={this.onPanelOptionsChanged} />
</div>
);
}
return <div>No editor (angular?)</div>;
......
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