2020-02-09 01:29:09 +08:00
|
|
|
import React from 'react';
|
2020-02-09 21:34:42 +08:00
|
|
|
import cloneDeep from 'lodash/cloneDeep';
|
|
|
|
import {
|
|
|
|
FieldConfigEditorRegistry,
|
|
|
|
FieldConfigSource,
|
|
|
|
DataFrame,
|
|
|
|
FieldPropertyEditorItem,
|
|
|
|
DynamicConfigValue,
|
2020-02-11 20:48:36 +08:00
|
|
|
VariableSuggestionsScope,
|
2020-02-14 04:37:24 +08:00
|
|
|
standardFieldConfigEditorRegistry,
|
2020-02-09 21:34:42 +08:00
|
|
|
} from '@grafana/data';
|
2020-02-14 04:37:24 +08:00
|
|
|
import { Forms, fieldMatchersUI, ValuePicker } from '@grafana/ui';
|
2020-02-11 20:48:36 +08:00
|
|
|
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
2020-02-12 17:42:57 +08:00
|
|
|
import { OptionsGroup } from './OptionsGroup';
|
|
|
|
|
2020-02-09 01:29:09 +08:00
|
|
|
interface Props {
|
|
|
|
config: FieldConfigSource;
|
|
|
|
custom?: FieldConfigEditorRegistry; // custom fields
|
|
|
|
include?: string[]; // Ordered list of which fields should be shown/included
|
|
|
|
onChange: (config: FieldConfigSource) => void;
|
2020-02-12 17:42:57 +08:00
|
|
|
/* Helpful for IntelliSense */
|
2020-02-09 01:29:09 +08:00
|
|
|
data: DataFrame[];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expects the container div to have size set and will fill it 100%
|
|
|
|
*/
|
|
|
|
export class FieldConfigEditor extends React.PureComponent<Props> {
|
|
|
|
private setDefaultValue = (name: string, value: any, custom: boolean) => {
|
|
|
|
const defaults = { ...this.props.config.defaults };
|
|
|
|
const remove = value === undefined || value === null || '';
|
2020-02-12 17:42:57 +08:00
|
|
|
|
2020-02-09 01:29:09 +08:00
|
|
|
if (custom) {
|
|
|
|
if (defaults.custom) {
|
|
|
|
if (remove) {
|
|
|
|
defaults.custom = { ...defaults.custom };
|
|
|
|
delete defaults.custom[name];
|
|
|
|
} else {
|
|
|
|
defaults.custom = { ...defaults.custom, [name]: value };
|
|
|
|
}
|
|
|
|
} else if (!remove) {
|
|
|
|
defaults.custom = { [name]: value };
|
|
|
|
}
|
|
|
|
} else if (remove) {
|
|
|
|
delete (defaults as any)[name];
|
|
|
|
} else {
|
|
|
|
(defaults as any)[name] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.props.onChange({
|
|
|
|
...this.props.config,
|
|
|
|
defaults,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-02-09 21:34:42 +08:00
|
|
|
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 });
|
|
|
|
};
|
|
|
|
|
2020-02-09 01:29:09 +08:00
|
|
|
renderEditor(item: FieldPropertyEditorItem, custom: boolean) {
|
2020-02-11 20:48:36 +08:00
|
|
|
const { data } = this.props;
|
2020-02-09 01:29:09 +08:00
|
|
|
const config = this.props.config.defaults;
|
|
|
|
const value = custom ? (config.custom ? config.custom[item.id] : undefined) : (config as any)[item.id];
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Forms.Field label={item.name} description={item.description} key={`${item.id}/${custom}`}>
|
2020-02-11 20:48:36 +08:00
|
|
|
<item.editor
|
|
|
|
item={item}
|
|
|
|
value={value}
|
|
|
|
onChange={v => this.setDefaultValue(item.id, v, custom)}
|
|
|
|
context={{
|
|
|
|
data,
|
|
|
|
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
|
|
|
|
}}
|
|
|
|
/>
|
2020-02-09 01:29:09 +08:00
|
|
|
</Forms.Field>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderStandardConfigs() {
|
|
|
|
const { include } = this.props;
|
|
|
|
if (include) {
|
|
|
|
return include.map(f => this.renderEditor(standardFieldConfigEditorRegistry.get(f), false));
|
|
|
|
}
|
|
|
|
return standardFieldConfigEditorRegistry.list().map(f => this.renderEditor(f, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
renderCustomConfigs() {
|
|
|
|
const { custom } = this.props;
|
|
|
|
if (!custom) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return custom.list().map(f => this.renderEditor(f, true));
|
|
|
|
}
|
|
|
|
|
|
|
|
renderOverrides() {
|
2020-02-09 21:34:42 +08:00
|
|
|
const { config, data, custom } = this.props;
|
2020-02-10 01:47:48 +08:00
|
|
|
if (config.overrides.length === 0) {
|
|
|
|
return null;
|
|
|
|
}
|
2020-02-09 01:29:09 +08:00
|
|
|
|
2020-02-09 21:34:42 +08:00
|
|
|
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 (
|
2020-02-10 01:47:48 +08:00
|
|
|
<div>
|
2020-02-09 21:34:42 +08:00
|
|
|
{config.overrides.map((o, i) => {
|
|
|
|
const matcherUi = fieldMatchersUI.get(o.matcher.id);
|
2020-02-14 04:37:24 +08:00
|
|
|
// TODO: apply matcher to retrieve fields
|
2020-02-09 21:34:42 +08:00
|
|
|
return (
|
2020-02-10 01:47:48 +08:00
|
|
|
<div key={`${o.matcher.id}/${i}`} style={{ border: `2px solid red`, marginBottom: '10px' }}>
|
2020-02-09 21:34:42 +08:00
|
|
|
<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)}
|
|
|
|
/>
|
|
|
|
|
2020-02-10 01:47:48 +08:00
|
|
|
<div style={{ border: `2px solid blue`, marginBottom: '5px' }}>
|
|
|
|
{o.properties.map((p, j) => {
|
|
|
|
const reg = p.custom ? custom : standardFieldConfigEditorRegistry;
|
|
|
|
const item = reg?.getIfExists(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}
|
2020-02-11 20:48:36 +08:00
|
|
|
context={{
|
|
|
|
data,
|
|
|
|
getSuggestions: (scope?: VariableSuggestionsScope) =>
|
|
|
|
getDataLinksVariableSuggestions(data, scope),
|
|
|
|
}}
|
2020-02-10 01:47:48 +08:00
|
|
|
/>
|
|
|
|
</Forms.Field>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
<ValuePicker
|
|
|
|
icon="plus"
|
|
|
|
label="Set config property"
|
|
|
|
options={configPropertiesOptions}
|
|
|
|
onChange={o => {
|
|
|
|
this.onDynamicConfigValueAdd(i, o.value!, o.custom);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
2020-02-09 21:34:42 +08:00
|
|
|
</>
|
|
|
|
</Forms.Field>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
})}
|
2020-02-10 01:47:48 +08:00
|
|
|
</div>
|
2020-02-09 21:34:42 +08:00
|
|
|
);
|
2020-02-09 01:29:09 +08:00
|
|
|
}
|
|
|
|
|
2020-02-09 21:34:42 +08:00
|
|
|
renderAddOverride = () => {
|
|
|
|
return (
|
2020-02-10 01:47:48 +08:00
|
|
|
<ValuePicker
|
2020-02-09 21:34:42 +08:00
|
|
|
icon="plus"
|
2020-02-10 01:47:48 +08:00
|
|
|
label="Add override"
|
2020-02-09 21:34:42 +08:00
|
|
|
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: [],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-02-09 01:29:09 +08:00
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<div>
|
2020-02-12 17:42:57 +08:00
|
|
|
<OptionsGroup title="Field configuration">{this.renderStandardConfigs()}</OptionsGroup>
|
|
|
|
|
|
|
|
{this.props.custom && <OptionsGroup title="Visualization options">{this.renderCustomConfigs()}</OptionsGroup>}
|
|
|
|
|
|
|
|
<OptionsGroup title="Field Overrides">
|
2020-02-10 00:39:26 +08:00
|
|
|
{this.renderOverrides()}
|
2020-02-10 01:47:48 +08:00
|
|
|
{this.renderAddOverride()}
|
2020-02-12 17:42:57 +08:00
|
|
|
</OptionsGroup>
|
2020-02-09 01:29:09 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default FieldConfigEditor;
|