FieldConfig: Unify the custom and standard registry (#23307)

* FieldConfig: Unifying standard and custom registry

* Adding path to option items to make id be prefixed for custom options

* Code updates progress

* Add docs back

* Fix TS

* ld overrides tests from ui to data

* Refactor - rename

* Gauge and table cleanup

* F-I-X e2e

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Torkel Ödegaard
2020-04-06 16:24:41 +02:00
committed by GitHub
parent 6347a1f1eb
commit b10392733d
33 changed files with 706 additions and 553 deletions

View File

@@ -181,7 +181,7 @@ export class PanelInspector extends PureComponent<Props, State> {
const processed = applyFieldOverrides({
data,
theme: config.theme,
fieldOptions: { defaults: {}, overrides: [] },
fieldConfig: { defaults: {}, overrides: [] },
replaceVariables: (value: string) => {
return value;
},

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { DynamicConfigValue, FieldConfigEditorRegistry, FieldOverrideContext, GrafanaTheme } from '@grafana/data';
import { DynamicConfigValue, FieldConfigOptionsRegistry, FieldOverrideContext, GrafanaTheme } from '@grafana/data';
import { FieldConfigItemHeaderTitle, selectThemeVariant, stylesFactory, useTheme } from '@grafana/ui';
import { css } from 'emotion';
interface DynamicConfigValueEditorProps {
property: DynamicConfigValue;
editorsRegistry: FieldConfigEditorRegistry;
registry: FieldConfigOptionsRegistry;
onChange: (value: DynamicConfigValue) => void;
context: FieldOverrideContext;
onRemove: () => void;
@@ -14,13 +14,13 @@ interface DynamicConfigValueEditorProps {
export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> = ({
property,
context,
editorsRegistry,
registry,
onChange,
onRemove,
}) => {
const theme = useTheme();
const styles = getStyles(theme);
const item = editorsRegistry?.getIfExists(property.prop);
const item = registry?.getIfExists(property.id);
if (!item) {
return null;

View File

@@ -5,10 +5,8 @@ import {
DataFrame,
FieldPropertyEditorItem,
VariableSuggestionsScope,
standardFieldConfigEditorRegistry,
PanelPlugin,
SelectableValue,
FieldConfigProperty,
} from '@grafana/data';
import { Forms, fieldMatchersUI, ValuePicker, useTheme } from '@grafana/ui';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
@@ -18,7 +16,6 @@ import { css } from 'emotion';
interface Props {
plugin: PanelPlugin;
config: FieldConfigSource;
include?: FieldConfigProperty[]; // Ordered list of which fields should be shown/included
onChange: (config: FieldConfigSource) => void;
/* Helpful for IntelliSense */
data: DataFrame[];
@@ -62,33 +59,12 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
const renderOverrides = () => {
const { config, data, plugin } = props;
const { customFieldConfigs } = plugin;
const { fieldConfigRegistry } = plugin;
if (config.overrides.length === 0) {
return null;
}
let configPropertiesOptions = plugin.standardFieldConfigProperties.map(i => {
const editor = standardFieldConfigEditorRegistry.get(i);
return {
label: editor.name,
value: editor.id,
description: editor.description,
custom: false,
};
});
if (customFieldConfigs) {
configPropertiesOptions = configPropertiesOptions.concat(
customFieldConfigs.list().map(i => ({
label: i.name,
value: i.id,
description: i.description,
custom: true,
}))
);
}
return (
<div>
{config.overrides.map((o, i) => {
@@ -100,8 +76,7 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
override={o}
onChange={value => onOverrideChange(i, value)}
onRemove={() => onOverrideRemove(i)}
configPropertiesOptions={configPropertiesOptions}
customPropertiesRegistry={customFieldConfigs}
registry={fieldConfigRegistry}
/>
);
})}
@@ -135,7 +110,7 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
);
};
export const DefaultFieldConfigEditor: React.FC<Props> = ({ include, data, onChange, config, plugin }) => {
export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, config, plugin }) => {
const setDefaultValue = useCallback(
(name: string, value: any, custom: boolean) => {
const defaults = { ...config.defaults };
@@ -167,16 +142,20 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ include, data, onCha
);
const renderEditor = useCallback(
(item: FieldPropertyEditorItem, custom: boolean) => {
(item: FieldPropertyEditorItem) => {
const defaults = config.defaults;
const value = custom ? (defaults.custom ? defaults.custom[item.id] : undefined) : (defaults as any)[item.id];
const value = item.isCustom
? defaults.custom
? defaults.custom[item.path]
: undefined
: (defaults as any)[item.path];
return (
<Forms.Field label={item.name} description={item.description} key={`${item.id}/${custom}`}>
<Forms.Field label={item.name} description={item.description} key={`${item.id}`}>
<item.editor
item={item}
value={value}
onChange={v => setDefaultValue(item.id, v, custom)}
onChange={v => setDefaultValue(item.path, v, item.isCustom)}
context={{
data,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
@@ -188,28 +167,6 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ include, data, onCha
[config]
);
const renderStandardConfigs = useCallback(() => {
if (include && include.length === 0) {
return null;
}
if (include) {
return <>{include.map(f => renderEditor(standardFieldConfigEditorRegistry.get(f), false))}</>;
}
return <>{standardFieldConfigEditorRegistry.list().map(f => renderEditor(f, false))}</>;
}, [plugin, config]);
const renderCustomConfigs = useCallback(() => {
if (!plugin.customFieldConfigs) {
return null;
}
return plugin.customFieldConfigs.list().map(f => renderEditor(f, true));
}, [plugin, config]);
return (
<>
{plugin.customFieldConfigs && renderCustomConfigs()}
{renderStandardConfigs()}
</>
);
// render all field configs
return <>{plugin.fieldConfigRegistry.list().map(renderEditor)}</>;
};

View File

@@ -60,7 +60,6 @@ export const OptionsPaneContent: React.FC<{
plugin={plugin}
onChange={onFieldConfigsChange}
data={data.series}
include={plugin.standardFieldConfigProperties}
/>
</Container>
);

View File

@@ -3,10 +3,8 @@ import {
ConfigOverrideRule,
DataFrame,
DynamicConfigValue,
FieldConfigEditorRegistry,
standardFieldConfigEditorRegistry,
FieldConfigOptionsRegistry,
VariableSuggestionsScope,
SelectableValue,
GrafanaTheme,
} from '@grafana/data';
import { fieldMatchersUI, stylesFactory, useTheme, ValuePicker, selectThemeVariant } from '@grafana/ui';
@@ -21,18 +19,10 @@ interface OverrideEditorProps {
override: ConfigOverrideRule;
onChange: (config: ConfigOverrideRule) => void;
onRemove: () => void;
customPropertiesRegistry?: FieldConfigEditorRegistry;
configPropertiesOptions: Array<SelectableValue<string>>;
registry: FieldConfigOptionsRegistry;
}
export const OverrideEditor: React.FC<OverrideEditorProps> = ({
data,
override,
onChange,
onRemove,
customPropertiesRegistry,
configPropertiesOptions,
}) => {
export const OverrideEditor: React.FC<OverrideEditorProps> = ({ data, override, onChange, onRemove, registry }) => {
const theme = useTheme();
const onMatcherConfigChange = useCallback(
(matcherConfig: any) => {
@@ -59,10 +49,10 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
);
const onDynamicConfigValueAdd = useCallback(
(prop: string, custom?: boolean) => {
(id: string, custom?: boolean) => {
const propertyConfig: DynamicConfigValue = {
prop,
custom,
id,
isCustom: custom,
};
if (override.properties) {
override.properties.push(propertyConfig);
@@ -74,6 +64,15 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
[override, onChange]
);
let configPropertiesOptions = registry.list().map(item => {
return {
label: item.name,
value: item.id,
description: item.description,
custom: item.isCustom,
};
});
const matcherUi = fieldMatchersUI.get(override.matcher.id);
const styles = getStyles(theme);
return (
@@ -90,20 +89,19 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
</FieldConfigItemHeaderTitle>
<div>
{override.properties.map((p, j) => {
const reg = p.custom ? customPropertiesRegistry : standardFieldConfigEditorRegistry;
const item = reg?.getIfExists(p.prop);
const item = registry.getIfExists(p.id);
if (!item) {
return <div>Unknown property: {p.prop}</div>;
return <div>Unknown property: {p.id}</div>;
}
return (
<div key={`${p.prop}/${j}`}>
<div key={`${p.id}/${j}`}>
<DynamicConfigValueEditor
onChange={value => onDynamicConfigValueChange(j, value)}
onRemove={() => onDynamicConfigValueRemove(j)}
property={p}
editorsRegistry={reg}
registry={registry}
context={{
data,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),

View File

@@ -22,7 +22,7 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
{optionEditors.list().map(e => {
return (
<Forms.Field label={e.name} description={e.description} key={e.id}>
<e.editor value={lodashGet(options, e.id)} onChange={value => onOptionChange(e.id, value)} item={e} />
<e.editor value={lodashGet(options, e.path)} onChange={value => onOptionChange(e.path, value)} item={e} />
</Forms.Field>
);
})}

View File

@@ -1,10 +1,46 @@
import { PanelModel } from './PanelModel';
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
import { PanelProps, FieldConfigProperty } from '@grafana/data';
import {
FieldConfigProperty,
identityOverrideProcessor,
PanelProps,
standardFieldConfigEditorRegistry,
} from '@grafana/data';
import { ComponentClass } from 'react';
class TablePanelCtrl {}
export const mockStandardProperties = () => {
const unit = {
id: 'unit',
path: 'unit',
name: 'Unit',
description: 'Value units',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
const decimals = {
id: 'decimals',
path: 'decimals',
name: 'Decimals',
description: 'Number of decimal to be shown for a value',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
return [unit, decimals];
};
standardFieldConfigEditorRegistry.setInit(() => mockStandardProperties());
describe('PanelModel', () => {
describe('when creating new panel model', () => {
let model: any;
@@ -79,9 +115,16 @@ describe('PanelModel', () => {
TablePanelCtrl // angular
);
panelPlugin.setDefaults(defaultOptionsMock);
panelPlugin.useStandardFieldConfig([FieldConfigProperty.Unit, FieldConfigProperty.Decimals], {
[FieldConfigProperty.Unit]: 'flop',
[FieldConfigProperty.Decimals]: 2,
/* panelPlugin.useStandardFieldConfig([FieldConfigOptionId.Unit, FieldConfigOptionId.Decimals], {
[FieldConfigOptionId.Unit]: 'flop',
[FieldConfigOptionId.Decimals]: 2,
}); */
panelPlugin.useFieldConfig({
standardOptions: [FieldConfigProperty.Unit, FieldConfigProperty.Decimals],
standardOptionsDefaults: {
[FieldConfigProperty.Unit]: 'flop',
[FieldConfigProperty.Decimals]: 2,
},
});
model.pluginLoaded(panelPlugin);
});
@@ -100,9 +143,9 @@ describe('PanelModel', () => {
it('should apply field config defaults', () => {
// default unit is overriden by model
expect(model.getFieldOverrideOptions().fieldOptions.defaults.unit).toBe('mpg');
expect(model.getFieldOverrideOptions().fieldConfig.defaults.unit).toBe('mpg');
// default decimals are aplied
expect(model.getFieldOverrideOptions().fieldOptions.defaults.decimals).toBe(2);
expect(model.getFieldOverrideOptions().fieldConfig.defaults.decimals).toBe(2);
});
it('should set model props on instance', () => {

View File

@@ -415,9 +415,9 @@ export class PanelModel implements DataConfigSource {
}
return {
fieldOptions: this.fieldConfig,
fieldConfig: this.fieldConfig,
replaceVariables: this.replaceVariables,
custom: this.plugin.customFieldConfigs,
fieldConfigRegistry: this.plugin.fieldConfigRegistry,
theme: config.theme,
};
}

View File

@@ -210,7 +210,7 @@ describe('PanelQueryRunner', () => {
},
{
getFieldOverrideOptions: () => ({
fieldOptions: {
fieldConfig: {
defaults: {
unit: 'm/s',
},

View File

@@ -21,7 +21,7 @@ describe('getFieldDisplayValuesProxy', () => {
],
}),
],
fieldOptions: {
fieldConfig: {
defaults: {},
overrides: [],
},

View File

@@ -129,7 +129,7 @@ describe('getLinksFromLogsField', () => {
],
}),
],
fieldOptions: {
fieldConfig: {
defaults: {},
overrides: [],
},