Commit 92231cc4 by Dominik Prokop Committed by GitHub

FieldConfig: Set field config defaults based on config registry (#23376)

* Set field config defaults based on config registry

* Rename FIeldPropertyEditorItem to FieldConfigPopertyItem

* Remove console.log

* Simplify overrides persistence model and add support for nested properties in overrides

* Review updates
parent 3c61627a
import { Registry } from '../utils';
import { FieldPropertyEditorItem } from '../types';
import { FieldConfigPropertyItem } from '../types';
export class FieldConfigOptionsRegistry extends Registry<FieldPropertyEditorItem> {}
export class FieldConfigOptionsRegistry extends Registry<FieldConfigPropertyItem> {}
......@@ -2,12 +2,13 @@ import {
FieldOverrideEnv,
findNumericFieldMinMax,
setFieldConfigDefaults,
setDynamicConfigValue,
applyFieldOverrides,
} from './fieldOverrides';
import { MutableDataFrame, toDataFrame } from '../dataframe';
import {
FieldConfig,
FieldPropertyEditorItem,
FieldConfigPropertyItem,
GrafanaTheme,
FieldType,
DataFrame,
......@@ -22,6 +23,7 @@ import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
const property1 = {
id: 'custom.property1', // Match field properties
path: 'property1', // Match field properties
isCustom: true,
process: (value: any) => value,
shouldApply: () => true,
} as any;
......@@ -29,12 +31,21 @@ const property1 = {
const property2 = {
id: 'custom.property2', // Match field properties
path: 'property2', // Match field properties
isCustom: true,
process: (value: any) => value,
shouldApply: () => true,
} as any;
export const customFieldRegistry: FieldConfigOptionsRegistry = new Registry<FieldPropertyEditorItem>(() => {
return [property1, property2, ...mockStandardProperties()];
const property3 = {
id: 'custom.property3.nested', // Match field properties
path: 'property3.nested', // Match field properties
isCustom: true,
process: (value: any) => value,
shouldApply: () => true,
} as any;
export const customFieldRegistry: FieldConfigOptionsRegistry = new Registry<FieldConfigPropertyItem>(() => {
return [property1, property2, property3, ...mockStandardProperties()];
});
describe('Global MinMax', () => {
......@@ -241,6 +252,7 @@ describe('setFieldConfigDefaults', () => {
expect(dsFieldConfig).toMatchInlineSnapshot(`
Object {
"custom": Object {},
"decimals": 2,
"max": 100,
"min": 0,
......@@ -276,10 +288,125 @@ describe('setFieldConfigDefaults', () => {
expect(dsFieldConfig).toMatchInlineSnapshot(`
Object {
"custom": Object {
"property1": 20,
"property1": 10,
"property2": 10,
},
}
`);
});
});
describe('setDynamicConfigValue', () => {
it('applies dynamic config values', () => {
const config = {
title: 'test',
// custom: {
// property1: 1,
// },
};
setDynamicConfigValue(
config,
{
id: 'title',
value: 'applied',
},
{
fieldConfigRegistry: customFieldRegistry,
data: [] as any,
field: { type: FieldType.number } as any,
dataFrameIndex: 0,
}
);
expect(config.title).toEqual('applied');
});
it('applies custom dynamic config values', () => {
const config = {
custom: {
property1: 1,
},
};
setDynamicConfigValue(
config,
{
id: 'custom.property1',
value: 'applied',
},
{
fieldConfigRegistry: customFieldRegistry,
data: [] as any,
field: { type: FieldType.number } as any,
dataFrameIndex: 0,
}
);
expect(config.custom.property1).toEqual('applied');
});
it('applies nested custom dynamic config values', () => {
const config = {
custom: {
property3: {
nested: 1,
},
},
};
setDynamicConfigValue(
config,
{
id: 'custom.property3.nested',
value: 'applied',
},
{
fieldConfigRegistry: customFieldRegistry,
data: [] as any,
field: { type: FieldType.number } as any,
dataFrameIndex: 0,
}
);
expect(config.custom.property3.nested).toEqual('applied');
});
it('removes properties', () => {
const config = {
title: 'title',
custom: {
property3: {
nested: 1,
},
},
};
setDynamicConfigValue(
config,
{
id: 'custom.property3.nested',
value: undefined,
},
{
fieldConfigRegistry: customFieldRegistry,
data: [] as any,
field: { type: FieldType.number } as any,
dataFrameIndex: 0,
}
);
setDynamicConfigValue(
config,
{
id: 'title',
value: undefined,
},
{
fieldConfigRegistry: customFieldRegistry,
data: [] as any,
field: { type: FieldType.number } as any,
dataFrameIndex: 0,
}
);
expect(config.custom.property3).toEqual({});
expect(config.title).toBeUndefined();
});
});
......@@ -10,10 +10,14 @@ import {
FieldOverrideContext,
ScopedVars,
ApplyFieldOverrideOptions,
FieldConfigPropertyItem,
} from '../types';
import { fieldMatchers, ReducerID, reduceField } from '../transformations';
import { FieldMatcher } from '../types/transformations';
import isNumber from 'lodash/isNumber';
import set from 'lodash/set';
import unset from 'lodash/unset';
import get from 'lodash/get';
import { getDisplayProcessor } from './displayProcessor';
import { guessFieldTypeForField } from '../dataframe';
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
......@@ -193,9 +197,8 @@ export interface FieldOverrideEnv extends FieldOverrideContext {
fieldConfigRegistry: FieldConfigOptionsRegistry;
}
function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigValue, context: FieldOverrideEnv) {
export function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigValue, context: FieldOverrideEnv) {
const reg = context.fieldConfigRegistry;
const item = reg.getIfExists(value.id);
if (!item || !item.shouldApply(context.field!)) {
return;
......@@ -206,19 +209,19 @@ function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigValue, c
const remove = val === undefined || val === null;
if (remove) {
if (value.isCustom && config.custom) {
delete config.custom[item.path];
if (item.isCustom && config.custom) {
unset(config.custom, item.path);
} else {
delete (config as any)[item.path];
unset(config, item.path);
}
} else {
if (value.isCustom) {
if (item.isCustom) {
if (!config.custom) {
config.custom = {};
}
config.custom[item.path] = val;
set(config.custom, item.path, val);
} else {
(config as any)[item.path] = val;
set(config, item.path, val);
}
}
}
......@@ -226,26 +229,16 @@ function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigValue, c
// config -> from DS
// defaults -> from Panel config
export function setFieldConfigDefaults(config: FieldConfig, defaults: FieldConfig, context: FieldOverrideEnv) {
if (defaults) {
const keys = Object.keys(defaults);
for (const key of keys) {
if (key === 'custom') {
if (!context.fieldConfigRegistry) {
continue;
}
if (!config.custom) {
config.custom = {};
}
const customKeys = Object.keys(defaults.custom!);
for (const customKey of customKeys) {
processFieldConfigValue(config.custom!, defaults.custom!, `custom.${customKey}`, context);
}
} else {
// when config from ds exists for a given field -> use it
processFieldConfigValue(config, defaults, key, context);
}
for (const fieldConfigProperty of context.fieldConfigRegistry.list()) {
if (fieldConfigProperty.isCustom && !config.custom) {
config.custom = {};
}
processFieldConfigValue(
fieldConfigProperty.isCustom ? config.custom : config,
fieldConfigProperty.isCustom ? defaults.custom : defaults,
fieldConfigProperty,
context
);
}
validateFieldConfig(config);
......@@ -254,20 +247,21 @@ export function setFieldConfigDefaults(config: FieldConfig, defaults: FieldConfi
const processFieldConfigValue = (
destination: Record<string, any>, // it's mutable
source: Record<string, any>,
key: string,
fieldConfigProperty: FieldConfigPropertyItem,
context: FieldOverrideEnv
) => {
const currentConfig = destination[key];
const currentConfig = get(destination, fieldConfigProperty.path);
if (currentConfig === null || currentConfig === undefined) {
const item = context.fieldConfigRegistry.getIfExists(key);
const item = context.fieldConfigRegistry.getIfExists(fieldConfigProperty.id);
if (!item) {
return;
}
if (item && item.shouldApply(context.field!)) {
const val = item.process(source[item.path], context, item.settings);
const val = item.process(get(source, item.path), context, item.settings);
if (val !== undefined && val !== null) {
destination[item.path] = val;
set(destination, item.path, val);
}
}
}
......
......@@ -276,7 +276,6 @@ export class PanelPlugin<TOptions = any, TFieldConfigOptions extends object = an
}
} else {
for (const fieldConfigProp of standardFieldConfigEditorRegistry.list()) {
console.log(fieldConfigProp);
registry.register(fieldConfigProp);
}
}
......
......@@ -17,7 +17,6 @@ import { OptionsEditorItem } from './OptionsUIRegistryBuilder';
export interface DynamicConfigValue {
id: string;
value?: any;
isCustom?: boolean;
}
export interface ConfigOverrideRule {
......@@ -43,14 +42,14 @@ export interface FieldOverrideContext {
export interface FieldConfigEditorProps<TValue, TSettings>
extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> {
item: FieldPropertyEditorItem<TValue, TSettings>; // The property info
item: FieldConfigPropertyItem<TValue, TSettings>; // The property info
value: TValue;
context: FieldOverrideContext;
onChange: (value?: TValue) => void;
}
export interface FieldOverrideEditorProps<TValue, TSettings> extends Omit<StandardEditorProps<TValue>, 'item'> {
item: FieldPropertyEditorItem<TValue, TSettings>;
item: FieldConfigPropertyItem<TValue, TSettings>;
context: FieldOverrideContext;
}
......@@ -63,7 +62,7 @@ export interface FieldConfigEditorConfig<TOptions, TSettings = any, TValue = any
defaultValue?: TValue;
}
export interface FieldPropertyEditorItem<TOptions = any, TValue = any, TSettings extends {} = any>
export interface FieldConfigPropertyItem<TOptions = any, TValue = any, TSettings extends {} = any>
extends OptionsEditorItem<TOptions, TSettings, FieldConfigEditorProps<TValue, TSettings>, TValue> {
// An editor that can be filled in with context info (template variables etc)
override: ComponentType<FieldOverrideEditorProps<TValue, TSettings>>;
......@@ -85,7 +84,7 @@ export interface ApplyFieldOverrideOptions {
theme: GrafanaTheme;
timeZone?: TimeZone;
autoMinMax?: boolean;
fieldConfigRegistry?: Registry<FieldPropertyEditorItem>;
fieldConfigRegistry?: Registry<FieldConfigPropertyItem>;
}
export enum FieldConfigProperty {
......
import {
FieldType,
FieldConfigEditorProps,
FieldPropertyEditorItem,
FieldConfigPropertyItem,
PanelOptionsEditorConfig,
PanelOptionsEditorItem,
FieldConfigEditorConfig,
......@@ -29,7 +29,7 @@ import {
export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder<
TOptions,
FieldConfigEditorProps<any, any>,
FieldPropertyEditorItem<TOptions>
FieldConfigPropertyItem<TOptions>
> {
addNumberInput<TSettings>(config: FieldConfigEditorConfig<TOptions, TSettings & NumberFieldConfigSettings, number>) {
return this.addCustomEditor({
......
......@@ -105,8 +105,8 @@ export const BarGaugeCell = () => {
{
matcher: { id: FieldMatcherID.byName, options: 'Progress' },
properties: [
{ id: 'width', value: '200', isCustom: true },
{ id: 'displayMode', value: 'gradient-gauge', isCustom: true },
{ id: 'width', value: '200' },
{ id: 'displayMode', value: 'gradient-gauge' },
{ id: 'min', value: '0' },
{ id: 'max', value: '100' },
],
......@@ -141,8 +141,8 @@ export const ColoredCells = () => {
{
matcher: { id: FieldMatcherID.byName, options: 'Progress' },
properties: [
{ id: 'width', value: '80', isCustom: true },
{ id: 'displayMode', value: 'color-background', isCustom: true },
{ id: 'width', value: '80' },
{ id: 'displayMode', value: 'color-background' },
{ id: 'min', value: '0' },
{ id: 'max', value: '100' },
{ id: 'thresholds', value: defaultThresholds },
......
......@@ -2,7 +2,7 @@ import React from 'react';
import {
DataLink,
dataLinksOverrideProcessor,
FieldPropertyEditorItem,
FieldConfigPropertyItem,
FieldType,
identityOverrideProcessor,
NumberFieldConfigSettings,
......@@ -31,7 +31,7 @@ import { StatsPickerEditor } from '../components/OptionsUI/stats';
* Returns collection of common field config properties definitions
*/
export const getStandardFieldConfigs = () => {
const title: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
const title: FieldConfigPropertyItem<any, string, StringFieldConfigSettings> = {
id: 'title',
path: 'title',
name: 'Title',
......@@ -46,7 +46,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type !== FieldType.time,
};
const unit: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
const unit: FieldConfigPropertyItem<any, string, StringFieldConfigSettings> = {
id: 'unit',
path: 'unit',
name: 'Unit',
......@@ -63,7 +63,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const min: FieldPropertyEditorItem<any, number, NumberFieldConfigSettings> = {
const min: FieldConfigPropertyItem<any, number, NumberFieldConfigSettings> = {
id: 'min',
path: 'min',
name: 'Min',
......@@ -79,7 +79,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const max: FieldPropertyEditorItem<any, number, NumberFieldConfigSettings> = {
const max: FieldConfigPropertyItem<any, number, NumberFieldConfigSettings> = {
id: 'max',
path: 'max',
name: 'Max',
......@@ -96,7 +96,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const decimals: FieldPropertyEditorItem<any, number, NumberFieldConfigSettings> = {
const decimals: FieldConfigPropertyItem<any, number, NumberFieldConfigSettings> = {
id: 'decimals',
path: 'decimals',
name: 'Decimals',
......@@ -116,7 +116,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const thresholds: FieldPropertyEditorItem<any, ThresholdsConfig, ThresholdsFieldConfigSettings> = {
const thresholds: FieldConfigPropertyItem<any, ThresholdsConfig, ThresholdsFieldConfigSettings> = {
id: 'thresholds',
path: 'thresholds',
name: 'Thresholds',
......@@ -136,7 +136,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const mappings: FieldPropertyEditorItem<any, ValueMapping[], ValueMappingFieldConfigSettings> = {
const mappings: FieldConfigPropertyItem<any, ValueMapping[], ValueMappingFieldConfigSettings> = {
id: 'mappings',
path: 'mappings',
name: 'Value mappings',
......@@ -150,7 +150,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const noValue: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
const noValue: FieldConfigPropertyItem<any, string, StringFieldConfigSettings> = {
id: 'noValue',
path: 'noValue',
name: 'No Value',
......@@ -167,7 +167,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: () => true,
};
const links: FieldPropertyEditorItem<any, DataLink[], StringFieldConfigSettings> = {
const links: FieldConfigPropertyItem<any, DataLink[], StringFieldConfigSettings> = {
id: 'links',
path: 'links',
name: 'DataLinks',
......@@ -181,7 +181,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: () => true,
};
const color: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
const color: FieldConfigPropertyItem<any, string, StringFieldConfigSettings> = {
id: 'color',
path: 'color',
name: 'Color',
......
......@@ -3,7 +3,7 @@ import cloneDeep from 'lodash/cloneDeep';
import {
FieldConfigSource,
DataFrame,
FieldPropertyEditorItem,
FieldConfigPropertyItem,
VariableSuggestionsScope,
PanelPlugin,
SelectableValue,
......@@ -142,7 +142,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
);
const renderEditor = useCallback(
(item: FieldPropertyEditorItem) => {
(item: FieldConfigPropertyItem) => {
const defaults = config.defaults;
const value = item.isCustom
? defaults.custom
......
......@@ -49,10 +49,9 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({ data, override,
);
const onDynamicConfigValueAdd = useCallback(
(id: string, custom?: boolean) => {
(id: string) => {
const propertyConfig: DynamicConfigValue = {
id,
isCustom: custom,
};
if (override.properties) {
override.properties.push(propertyConfig);
......@@ -69,7 +68,6 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({ data, override,
label: item.name,
value: item.id,
description: item.description,
custom: item.isCustom,
};
});
......@@ -117,7 +115,7 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({ data, override,
options={configPropertiesOptions}
variant={'link'}
onChange={o => {
onDynamicConfigValueAdd(o.value, o.custom);
onDynamicConfigValueAdd(o.value);
}}
/>
</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