mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
parent
25431f32f0
commit
1728742096
@ -24,8 +24,8 @@ describe('FieldOverrides', () => {
|
|||||||
{
|
{
|
||||||
matcher: { id: FieldMatcherID.numeric },
|
matcher: { id: FieldMatcherID.numeric },
|
||||||
properties: [
|
properties: [
|
||||||
{ path: 'decimals', value: 1 }, // Numeric
|
{ prop: 'decimals', value: 1 }, // Numeric
|
||||||
{ path: 'title', value: 'Kittens' }, // Text
|
{ prop: 'title', value: 'Kittens' }, // Text
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -218,8 +218,8 @@ function prepareConfigValue(key: string, input: any, options?: DynamicConfigValu
|
|||||||
|
|
||||||
export function setDynamicConfigValue(config: FieldConfig, options: DynamicConfigValueOptions) {
|
export function setDynamicConfigValue(config: FieldConfig, options: DynamicConfigValueOptions) {
|
||||||
const { value } = options;
|
const { value } = options;
|
||||||
const v = prepareConfigValue(value.path, value.value, options);
|
const v = prepareConfigValue(value.prop, value.value, options);
|
||||||
set(config, value.path, v);
|
set(config, value.prop, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,8 +5,9 @@ import { InterpolateFunction } from './panel';
|
|||||||
import { DataFrame } from 'apache-arrow';
|
import { DataFrame } from 'apache-arrow';
|
||||||
|
|
||||||
export interface DynamicConfigValue {
|
export interface DynamicConfigValue {
|
||||||
path: string;
|
prop: string;
|
||||||
value: any;
|
value?: any;
|
||||||
|
custom?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigOverrideRule {
|
export interface ConfigOverrideRule {
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
import React from 'react';
|
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 { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
|
||||||
import Forms from '../Forms';
|
import Forms from '../Forms';
|
||||||
|
import { fieldMatchersUI } from '../MatchersUI/fieldMatchersUI';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
config: FieldConfigSource;
|
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) {
|
renderEditor(item: FieldPropertyEditorItem, custom: boolean) {
|
||||||
const config = this.props.config.defaults;
|
const config = this.props.config.defaults;
|
||||||
const value = custom ? (config.custom ? config.custom[item.id] : undefined) : (config as any)[item.id];
|
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() {
|
renderOverrides() {
|
||||||
return <div>Override rules</div>;
|
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 (
|
||||||
|
<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() {
|
renderAddOverride = () => {
|
||||||
return <div>Override rules</div>;
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.renderStandardConfigs()}
|
{this.renderStandardConfigs()}
|
||||||
{this.renderCustomConfigs()}
|
{this.renderCustomConfigs()}
|
||||||
{this.renderOverrides()}
|
|
||||||
{this.renderAddOverride()}
|
{this.renderAddOverride()}
|
||||||
|
{this.renderOverrides()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps } from '@grafana/data';
|
import {
|
||||||
|
FieldOverrideContext,
|
||||||
|
FieldOverrideEditorProps,
|
||||||
|
FieldConfigEditorProps,
|
||||||
|
toIntegerOrUndefined,
|
||||||
|
toFloatOrUndefined,
|
||||||
|
} from '@grafana/data';
|
||||||
import Forms from '../Forms';
|
import Forms from '../Forms';
|
||||||
|
|
||||||
export interface NumberFieldConfigSettings {
|
export interface NumberFieldConfigSettings {
|
||||||
@ -32,27 +38,37 @@ export const NumberValueEditor: React.FC<FieldConfigEditorProps<number, NumberFi
|
|||||||
return (
|
return (
|
||||||
<Forms.Input
|
<Forms.Input
|
||||||
value={isNaN(value) ? '' : value}
|
value={isNaN(value) ? '' : value}
|
||||||
|
min={settings.min}
|
||||||
|
max={settings.max}
|
||||||
type="number"
|
type="number"
|
||||||
step={settings.step}
|
step={settings.step}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
onChange(
|
onChange(
|
||||||
item.settings.integer
|
settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value)
|
||||||
? parseInt(e.currentTarget.value, settings.step || 10)
|
|
||||||
: parseFloat(e.currentTarget.value)
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export class NumberOverrideEditor extends React.PureComponent<
|
export const NumberOverrideEditor: React.FC<FieldOverrideEditorProps<number, NumberFieldConfigSettings>> = ({
|
||||||
FieldOverrideEditorProps<number, NumberFieldConfigSettings>
|
value,
|
||||||
> {
|
onChange,
|
||||||
constructor(props: FieldOverrideEditorProps<number, NumberFieldConfigSettings>) {
|
item,
|
||||||
super(props);
|
}) => {
|
||||||
}
|
const { settings } = item;
|
||||||
|
return (
|
||||||
render() {
|
<Forms.Input
|
||||||
return <div>SHOW OVERRIDE EDITOR {this.props.item.name}</div>;
|
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)} />;
|
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class StringOverrideEditor extends React.PureComponent<
|
export const StringOverrideEditor: React.FC<FieldOverrideEditorProps<string, StringFieldConfigSettings>> = ({
|
||||||
FieldOverrideEditorProps<string, StringFieldConfigSettings>
|
value,
|
||||||
> {
|
onChange,
|
||||||
constructor(props: FieldOverrideEditorProps<string, StringFieldConfigSettings>) {
|
}) => {
|
||||||
super(props);
|
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
return <div>SHOW OVERRIDE EDITOR {this.props.item.name}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,6 +2,7 @@ import { getFormStyles } from './getFormStyles';
|
|||||||
import { Label } from './Label';
|
import { Label } from './Label';
|
||||||
import { Input } from './Input/Input';
|
import { Input } from './Input/Input';
|
||||||
import { Select } from './Select/Select';
|
import { Select } from './Select/Select';
|
||||||
|
import { ButtonSelect } from './Select/ButtonSelect';
|
||||||
import { Form } from './Form';
|
import { Form } from './Form';
|
||||||
import { Field } from './Field';
|
import { Field } from './Field';
|
||||||
import { Button, LinkButton } from './Button';
|
import { Button, LinkButton } from './Button';
|
||||||
@ -16,6 +17,7 @@ const Forms = {
|
|||||||
Button,
|
Button,
|
||||||
LinkButton,
|
LinkButton,
|
||||||
Select,
|
Select,
|
||||||
|
ButtonSelect,
|
||||||
InputControl,
|
InputControl,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,12 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types';
|
import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types';
|
||||||
import { FieldMatcherID, fieldMatchers } from '@grafana/data';
|
import { FieldMatcherID, fieldMatchers } from '@grafana/data';
|
||||||
|
import Forms from '../Forms';
|
||||||
|
|
||||||
export class FieldNameMatcherEditor extends React.PureComponent<MatcherUIProps<string>> {
|
export class FieldNameMatcherEditor extends React.PureComponent<MatcherUIProps<string>> {
|
||||||
render() {
|
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) {
|
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>;
|
return <div>No editor (angular?)</div>;
|
||||||
|
Loading…
Reference in New Issue
Block a user