Commit dcf6bbc1 by Dominik Prokop Committed by GitHub

NewPanelEditor: Options/FieldConfig API for defaults and common options selection (#23214)

* Add "some" typesafety to panel options/field config APIs

* Allow selected common field config properties config, allow option defaults config via fluent API

* Update packages/grafana-data/src/panel/PanelPlugin.ts

Co-Authored-By: Ryan McKinley <ryantxu@gmail.com>

* Add defaults support for custom field config

* Enable defaults setting for standard and custom field configs

* Remove setFieldConfigDefaults from PanelPlugin API and replace it with useStandardFieldConfig

* Update API for standard field config defaults

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
parent a92bcb78
......@@ -67,6 +67,9 @@ export const stringOverrideProcessor = (
context: FieldOverrideContext,
settings?: StringFieldConfigSettings
) => {
if (value === null || value === undefined) {
return value;
}
if (settings && settings.expandTemplateVars && context.replaceVariables) {
return context.replaceVariables(value, context.field!.config.scopedVars);
}
......
......@@ -12,4 +12,4 @@ export * from './datetime';
export * from './text';
export * from './valueFormats';
export * from './field';
export { PanelPlugin } from './panel/PanelPlugin';
export { PanelPlugin, defaultStandardFieldConfigProperties } from './panel/PanelPlugin';
import React from 'react';
import { identityOverrideProcessor } from '../field';
import { PanelPlugin } from './PanelPlugin';
import { identityOverrideProcessor, standardEditorsRegistry } from '../field';
import { PanelPlugin, standardFieldConfigProperties } from './PanelPlugin';
import { StandardFieldConfigProperties } from '../types';
describe('PanelPlugin', () => {
describe('declarative options', () => {
beforeAll(() => {
standardEditorsRegistry.setInit(() => {
return [
{
id: 'number',
},
] as any;
});
});
test('field config UI API', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
......@@ -45,4 +55,190 @@ describe('PanelPlugin', () => {
expect(panel.optionEditors!.list()).toHaveLength(1);
});
});
describe('default options', () => {
describe('panel options', () => {
test('default values', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.setPanelOptions(builder => {
builder
.addNumberInput({
id: 'numericOption',
name: 'Option editor',
description: 'Option editor description',
defaultValue: 10,
})
.addNumberInput({
id: 'numericOptionNoDefault',
name: 'Option editor',
description: 'Option editor description',
})
.addCustomEditor({
id: 'customOption',
name: 'Option editor',
description: 'Option editor description',
editor: () => <div>Editor</div>,
settings: {},
defaultValue: { value: 'Custom default value' },
});
});
const expectedDefaults = {
numericOption: 10,
customOption: { value: 'Custom default value' },
};
expect(panel.defaults).toEqual(expectedDefaults);
});
test('default values for nested paths', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.setPanelOptions(builder => {
builder.addNumberInput({
id: 'numericOption.nested',
name: 'Option editor',
description: 'Option editor description',
defaultValue: 10,
});
});
const expectedDefaults = {
numericOption: { nested: 10 },
};
expect(panel.defaults).toEqual(expectedDefaults);
});
});
describe('field config options', () => {
test('default values', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.setCustomFieldOptions(builder => {
builder
.addNumberInput({
id: 'numericOption',
name: 'Option editor',
description: 'Option editor description',
defaultValue: 10,
})
.addNumberInput({
id: 'numericOptionNoDefault',
name: 'Option editor',
description: 'Option editor description',
})
.addCustomEditor({
id: 'customOption',
name: 'Option editor',
description: 'Option editor description',
editor: () => <div>Editor</div>,
override: () => <div>Override editor</div>,
process: identityOverrideProcessor,
shouldApply: () => true,
settings: {},
defaultValue: { value: 'Custom default value' },
});
});
const expectedDefaults = {
numericOption: 10,
customOption: { value: 'Custom default value' },
};
expect(panel.fieldConfigDefaults.defaults.custom).toEqual(expectedDefaults);
});
test('default values for nested paths', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.setCustomFieldOptions(builder => {
builder.addNumberInput({
id: 'numericOption.nested',
name: 'Option editor',
description: 'Option editor description',
defaultValue: 10,
});
});
const expectedDefaults = {
numericOption: { nested: 10 },
};
expect(panel.fieldConfigDefaults.defaults.custom).toEqual(expectedDefaults);
});
});
describe('standard field config options', () => {
test('standard config', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.useStandardFieldConfig();
expect(panel.standardFieldConfigProperties).toEqual(Array.from(standardFieldConfigProperties.keys()));
});
test('selected standard config', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.useStandardFieldConfig([StandardFieldConfigProperties.Min, StandardFieldConfigProperties.Thresholds]);
expect(panel.standardFieldConfigProperties).toEqual(['min', 'thresholds']);
});
describe('default values', () => {
test('setting default values', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.useStandardFieldConfig([StandardFieldConfigProperties.Color, StandardFieldConfigProperties.Min], {
[StandardFieldConfigProperties.Color]: '#ff00ff',
[StandardFieldConfigProperties.Min]: 10,
});
expect(panel.standardFieldConfigProperties).toEqual(['color', 'min']);
expect(panel.fieldConfigDefaults).toEqual({
defaults: {
min: 10,
color: '#ff00ff',
},
overrides: [],
});
});
it('should ignore defaults that are not specified as availeble properties', () => {
const panel = new PanelPlugin(() => {
return <div>Panel</div>;
});
panel.useStandardFieldConfig([StandardFieldConfigProperties.Color], {
[StandardFieldConfigProperties.Color]: '#ff00ff',
[StandardFieldConfigProperties.Min]: 10,
});
expect(panel.standardFieldConfigProperties).toEqual(['color']);
expect(panel.fieldConfigDefaults).toEqual({
defaults: {
color: '#ff00ff',
},
overrides: [],
});
});
});
});
});
});
......@@ -8,26 +8,48 @@ import {
PanelPluginMeta,
PanelProps,
PanelTypeChangedHandler,
StandardFieldConfigProperties,
} from '../types';
import { FieldConfigEditorBuilder, PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders';
import { ComponentClass, ComponentType } from 'react';
import set from 'lodash/set';
import { deprecationWarning } from '../utils';
export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta> {
private customFieldConfigsUIBuilder = new FieldConfigEditorBuilder();
export const defaultStandardFieldConfigProperties: StandardFieldConfigProperties[] = [
StandardFieldConfigProperties.Min,
StandardFieldConfigProperties.Max,
StandardFieldConfigProperties.Title,
StandardFieldConfigProperties.Unit,
StandardFieldConfigProperties.Decimals,
StandardFieldConfigProperties.NoValue,
StandardFieldConfigProperties.Color,
StandardFieldConfigProperties.Thresholds,
StandardFieldConfigProperties.Mappings,
StandardFieldConfigProperties.Links,
];
export const standardFieldConfigProperties = new Map(defaultStandardFieldConfigProperties.map(p => [p, undefined]));
export class PanelPlugin<TOptions = any, TFieldConfigOptions extends object = any> extends GrafanaPlugin<
PanelPluginMeta
> {
private _defaults?: TOptions;
private _standardFieldConfigProperties?: Map<StandardFieldConfigProperties, any>;
private _fieldConfigDefaults: FieldConfigSource<TFieldConfigOptions> = {
defaults: {},
overrides: [],
};
private _customFieldConfigs?: FieldConfigEditorRegistry;
private registerCustomFieldConfigs?: (builder: FieldConfigEditorBuilder) => void;
private customFieldConfigsUIBuilder = new FieldConfigEditorBuilder<TFieldConfigOptions>();
private registerCustomFieldConfigs?: (builder: FieldConfigEditorBuilder<TFieldConfigOptions>) => void;
private optionsUIBuilder = new PanelOptionsEditorBuilder();
private _optionEditors?: PanelOptionEditorsRegistry;
private registerOptionEditors?: (builder: PanelOptionsEditorBuilder) => void;
private optionsUIBuilder = new PanelOptionsEditorBuilder<TOptions>();
private registerOptionEditors?: (builder: PanelOptionsEditorBuilder<TOptions>) => void;
panel: ComponentType<PanelProps<TOptions>>;
editor?: ComponentClass<PanelEditorProps<TOptions>>;
defaults?: TOptions;
fieldConfigDefaults?: FieldConfigSource = {
defaults: {},
overrides: [],
};
onPanelMigration?: PanelMigrationHandler<TOptions>;
onPanelTypeChanged?: PanelTypeChangedHandler<TOptions>;
noPadding?: boolean;
......@@ -42,6 +64,66 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
this.panel = panel;
}
get defaults() {
let result = this._defaults || {};
if (!this._defaults) {
const editors = this.optionEditors;
if (!editors || editors.list().length === 0) {
return null;
}
for (const editor of editors.list()) {
set(result, editor.id, editor.defaultValue);
}
}
return result;
}
get fieldConfigDefaults(): FieldConfigSource<TFieldConfigOptions> {
let customPropertiesDefaults = this._fieldConfigDefaults.defaults.custom;
if (!customPropertiesDefaults) {
customPropertiesDefaults = {} as TFieldConfigOptions;
}
const editors = this.customFieldConfigs;
if (editors && editors.list().length !== 0) {
for (const editor of editors.list()) {
set(customPropertiesDefaults, editor.id, editor.defaultValue);
}
}
return {
defaults: {
...(this._standardFieldConfigProperties ? Object.fromEntries(this._standardFieldConfigProperties) : {}),
custom:
Object.keys(customPropertiesDefaults).length > 0
? {
...customPropertiesDefaults,
}
: undefined,
...this._fieldConfigDefaults.defaults,
},
// TODO: not sure yet what about overrides, if anything
overrides: this._fieldConfigDefaults.overrides,
};
}
get standardFieldConfigProperties() {
return this._standardFieldConfigProperties ? Array.from(this._standardFieldConfigProperties.keys()) : [];
}
/**
* @deprecated setDefaults is deprecated in favor of setPanelOptions
*/
setDefaults(defaults: TOptions) {
deprecationWarning('PanelPlugin', 'setDefaults', 'setPanelOptions');
this._defaults = defaults;
return this;
}
get customFieldConfigs() {
if (!this._customFieldConfigs && this.registerCustomFieldConfigs) {
this.registerCustomFieldConfigs(this.customFieldConfigsUIBuilder);
......@@ -65,11 +147,6 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
return this;
}
setDefaults(defaults: TOptions) {
this.defaults = defaults;
return this;
}
setNoPadding() {
this.noPadding = true;
return this;
......@@ -134,7 +211,7 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
*
* @public
**/
setCustomFieldOptions(builder: (builder: FieldConfigEditorBuilder) => void) {
setCustomFieldOptions(builder: (builder: FieldConfigEditorBuilder<TFieldConfigOptions>) => void) {
// builder is applied lazily when custom field configs are accessed
this.registerCustomFieldConfigs = builder;
return this;
......@@ -170,22 +247,63 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
*
* @public
**/
setPanelOptions(builder: (builder: PanelOptionsEditorBuilder) => void) {
setPanelOptions(builder: (builder: PanelOptionsEditorBuilder<TOptions>) => void) {
// builder is applied lazily when options UI is created
this.registerOptionEditors = builder;
return this;
}
/**
* Enables configuration of panel's default field config
* Allows specyfing which standard field config options panel should use and defining default values
*
* @example
* ```typescript
*
* import { ShapePanel } from './ShapePanel';
*
* interface ShapePanelOptions {}
*
* // when plugin should use all standard options
* export const plugin = new PanelPlugin<ShapePanelOptions>(ShapePanel)
* .useStandardFieldConfig();
*
* // when plugin should only display specific standard options
* // note, that options will be displayed in the order they are provided
* export const plugin = new PanelPlugin<ShapePanelOptions>(ShapePanel)
* .useStandardFieldConfig([StandardFieldConfigProperties.Min, StandardFieldConfigProperties.Max, StandardFieldConfigProperties.Links]);
*
* // when standard option's default value needs to be provided
* export const plugin = new PanelPlugin<ShapePanelOptions>(ShapePanel)
* .useStandardFieldConfig([StandardFieldConfigProperties.Min, StandardFieldConfigProperties.Max], {
* [StandardFieldConfigProperties.Min]: 20,
* [StandardFieldConfigProperties.Max]: 100
* });
*
* ```
*
* @public
*/
setFieldConfigDefaults(defaultConfig: Partial<FieldConfigSource>) {
this.fieldConfigDefaults = {
defaults: {},
overrides: [],
...defaultConfig,
};
useStandardFieldConfig(
properties?: StandardFieldConfigProperties[],
defauls?: Partial<Record<StandardFieldConfigProperties, any>>
) {
if (!properties) {
this._standardFieldConfigProperties = standardFieldConfigProperties;
return this;
} else {
this._standardFieldConfigProperties = new Map(properties.map(p => [p, standardFieldConfigProperties.get(p)]));
}
if (defauls) {
Object.keys(defauls).map(k => {
if (properties.indexOf(k as StandardFieldConfigProperties) > -1) {
this._standardFieldConfigProperties!.set(
k as StandardFieldConfigProperties,
defauls[k as StandardFieldConfigProperties]
);
}
});
}
return this;
}
}
......@@ -5,52 +5,59 @@ import { NumberFieldConfigSettings, SelectFieldConfigSettings, StringFieldConfig
/**
* Option editor registry item
*/
export interface OptionsEditorItem<TSettings, TEditorProps> extends RegistryItem {
export interface OptionsEditorItem<TOptions, TSettings, TEditorProps, TValue> extends RegistryItem {
id: (keyof TOptions & string) | string;
editor: ComponentType<TEditorProps>;
settings?: TSettings;
defaultValue?: TValue;
}
/**
* Configuration of option editor registry item
*/
interface OptionEditorConfig<TSettings> {
id: string;
interface OptionEditorConfig<TOptions, TSettings, TValue = any> {
id: keyof TOptions & string;
name: string;
description: string;
settings?: TSettings;
defaultValue?: TValue;
}
/**
* Describes an API for option editors UI builder
*/
export interface OptionsUIRegistryBuilderAPI<TEditorProps, T extends OptionsEditorItem<any, TEditorProps>> {
export interface OptionsUIRegistryBuilderAPI<
TOptions,
TEditorProps,
T extends OptionsEditorItem<TOptions, any, TEditorProps, any>
> {
addNumberInput?<TSettings extends NumberFieldConfigSettings = NumberFieldConfigSettings>(
config: OptionEditorConfig<TSettings>
config: OptionEditorConfig<TOptions, TSettings, number>
): this;
addTextInput?<TSettings extends StringFieldConfigSettings = StringFieldConfigSettings>(
config: OptionEditorConfig<TSettings>
config: OptionEditorConfig<TOptions, TSettings, string>
): this;
addSelect?<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
config: OptionEditorConfig<TSettings>
config: OptionEditorConfig<TOptions, TSettings, TOption>
): this;
addRadio?<TOption, TSettings extends SelectFieldConfigSettings<TOption> = SelectFieldConfigSettings<TOption>>(
config: OptionEditorConfig<TSettings>
config: OptionEditorConfig<TOptions, TSettings, TOption>
): this;
addBooleanSwitch?<TSettings = any>(config: OptionEditorConfig<TSettings>): this;
addBooleanSwitch?<TSettings = any>(config: OptionEditorConfig<TOptions, TSettings, boolean>): this;
addUnitPicker?<TSettings = any>(config: OptionEditorConfig<TSettings>): this;
addUnitPicker?<TSettings = any>(config: OptionEditorConfig<TOptions, TSettings, string>): this;
addColorPicker?<TSettings = any>(config: OptionEditorConfig<TSettings>): this;
addColorPicker?<TSettings = any>(config: OptionEditorConfig<TOptions, TSettings, string>): this;
/**
* Enables custom editor definition
* @param config
*/
addCustomEditor<TSettings>(config: OptionsEditorItem<TSettings, TEditorProps>): this;
addCustomEditor<TSettings, TValue>(config: OptionsEditorItem<TOptions, TSettings, TEditorProps, TValue>): this;
/**
* Returns registry of option editors
......@@ -58,11 +65,14 @@ export interface OptionsUIRegistryBuilderAPI<TEditorProps, T extends OptionsEdit
getRegistry: () => Registry<T>;
}
export abstract class OptionsUIRegistryBuilder<TEditorProps, T extends OptionsEditorItem<any, TEditorProps>>
implements OptionsUIRegistryBuilderAPI<TEditorProps, T> {
export abstract class OptionsUIRegistryBuilder<
TOptions,
TEditorProps,
T extends OptionsEditorItem<TOptions, any, TEditorProps, any>
> implements OptionsUIRegistryBuilderAPI<TOptions, TEditorProps, T> {
private properties: T[] = [];
addCustomEditor<TValue>(config: T & OptionsEditorItem<TValue, TEditorProps>): this {
addCustomEditor<TSettings, TValue>(config: T & OptionsEditorItem<TOptions, TSettings, TEditorProps, TValue>): this {
this.properties.push(config);
return this;
}
......
......@@ -21,7 +21,7 @@ export enum FieldType {
*
* Plugins may extend this with additional properties. Something like series overrides
*/
export interface FieldConfig {
export interface FieldConfig<TOptions extends object = any> {
title?: string; // The display value for this field. This supports template variables blank is auto
filterable?: boolean;
......@@ -50,7 +50,7 @@ export interface FieldConfig {
noValue?: string;
// Panel Specific Values
custom?: Record<string, any>;
custom?: TOptions;
scopedVars?: ScopedVars;
}
......
......@@ -25,9 +25,9 @@ export interface ConfigOverrideRule {
properties: DynamicConfigValue[];
}
export interface FieldConfigSource {
export interface FieldConfigSource<TOptions extends object = any> {
// Defatuls applied to all numeric fields
defaults: FieldConfig;
defaults: FieldConfig<TOptions>;
// Rules to override individual values
overrides: ConfigOverrideRule[];
......@@ -54,16 +54,17 @@ export interface FieldOverrideEditorProps<TValue, TSettings> extends Omit<Standa
context: FieldOverrideContext;
}
export interface FieldConfigEditorConfig<TSettings = any> {
id: string;
export interface FieldConfigEditorConfig<TOptions, TSettings = any, TValue = any> {
id: (keyof TOptions & string) | string;
name: string;
description: string;
settings?: TSettings;
shouldApply?: (field: Field) => boolean;
defaultValue?: TValue;
}
export interface FieldPropertyEditorItem<TValue = any, TSettings extends {} = any>
extends OptionsEditorItem<TSettings, FieldConfigEditorProps<TValue, TSettings>> {
export interface FieldPropertyEditorItem<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>>;
......@@ -86,3 +87,16 @@ export interface ApplyFieldOverrideOptions {
standard?: FieldConfigEditorRegistry;
custom?: FieldConfigEditorRegistry;
}
export enum StandardFieldConfigProperties {
Unit = 'unit',
Min = 'min',
Max = 'max',
Decimals = 'decimals',
Title = 'title',
NoValue = 'noValue',
Thresholds = 'thresholds',
Mappings = 'mappings',
Links = 'links',
Color = 'color',
}
......@@ -115,14 +115,15 @@ export type PanelOptionEditorsRegistry = Registry<PanelOptionsEditorItem>;
export interface PanelOptionsEditorProps<TValue> extends StandardEditorProps<TValue> {}
export interface PanelOptionsEditorItem<TValue = any, TSettings = any>
extends OptionsEditorItem<TSettings, PanelOptionsEditorProps<TValue>> {}
export interface PanelOptionsEditorItem<TOptions = any, TValue = any, TSettings = any>
extends OptionsEditorItem<TOptions, TSettings, PanelOptionsEditorProps<TValue>, TValue> {}
export interface PanelOptionsEditorConfig<TSettings = any> {
id: string;
export interface PanelOptionsEditorConfig<TOptions, TSettings = any, TValue = any> {
id: (keyof TOptions & string) | string;
name: string;
description: string;
settings?: TSettings;
defaultValue?: TValue;
}
export interface PanelMenuItem {
......
......@@ -26,11 +26,12 @@ import {
/**
* Fluent API for declarative creation of field config option editors
*/
export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder<
TOptions,
FieldConfigEditorProps<any, any>,
FieldPropertyEditorItem
FieldPropertyEditorItem<TOptions>
> {
addNumberInput<TSettings>(config: FieldConfigEditorConfig<TSettings & NumberFieldConfigSettings>) {
addNumberInput<TSettings>(config: FieldConfigEditorConfig<TOptions, TSettings & NumberFieldConfigSettings, number>) {
return this.addCustomEditor({
...config,
override: standardEditorsRegistry.get('number').editor as any,
......@@ -41,7 +42,7 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
});
}
addTextInput<TSettings>(config: FieldConfigEditorConfig<TSettings & StringFieldConfigSettings>) {
addTextInput<TSettings>(config: FieldConfigEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string>) {
return this.addCustomEditor({
...config,
override: standardEditorsRegistry.get('text').editor as any,
......@@ -52,7 +53,9 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
});
}
addSelect<TOption, TSettings = any>(config: FieldConfigEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
addSelect<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
config: FieldConfigEditorConfig<TOptions, TSettings, TOption>
) {
return this.addCustomEditor({
...config,
override: standardEditorsRegistry.get('select').editor as any,
......@@ -64,7 +67,7 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
});
}
addRadio<TOption, TSettings = any>(config: FieldConfigEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
addRadio<TOption, TSettings = any>(config: FieldConfigEditorConfig<TOptions, TSettings, TOption>) {
return this.addCustomEditor({
...config,
override: standardEditorsRegistry.get('radio').editor as any,
......@@ -76,7 +79,7 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
});
}
addBooleanSwitch<TSettings = any>(config: FieldConfigEditorConfig<TSettings>) {
addBooleanSwitch<TSettings = any>(config: FieldConfigEditorConfig<TOptions, TSettings, boolean>) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('boolean').editor as any,
......@@ -87,7 +90,9 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
});
}
addColorPicker<TSettings = any>(config: FieldConfigEditorConfig<TSettings & ColorFieldConfigSettings>) {
addColorPicker<TSettings = any>(
config: FieldConfigEditorConfig<TOptions, TSettings & ColorFieldConfigSettings, string>
) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('color').editor as any,
......@@ -98,7 +103,9 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
});
}
addUnitPicker<TSettings = any>(config: FieldConfigEditorConfig<TSettings & UnitFieldConfigSettings>) {
addUnitPicker<TSettings = any>(
config: FieldConfigEditorConfig<TOptions, TSettings & UnitFieldConfigSettings, string>
) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('unit').editor as any,
......@@ -113,43 +120,53 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
/**
* Fluent API for declarative creation of panel options
*/
export class PanelOptionsEditorBuilder extends OptionsUIRegistryBuilder<StandardEditorProps, PanelOptionsEditorItem> {
addNumberInput<TSettings>(config: PanelOptionsEditorConfig<TSettings & NumberFieldConfigSettings>) {
export class PanelOptionsEditorBuilder<TOptions> extends OptionsUIRegistryBuilder<
TOptions,
StandardEditorProps,
PanelOptionsEditorItem<TOptions>
> {
addNumberInput<TSettings>(config: PanelOptionsEditorConfig<TOptions, TSettings & NumberFieldConfigSettings, number>) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('number').editor as any,
});
}
addTextInput<TSettings>(config: PanelOptionsEditorConfig<TSettings & StringFieldConfigSettings>) {
addTextInput<TSettings>(config: PanelOptionsEditorConfig<TOptions, TSettings & StringFieldConfigSettings, string>) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('text').editor as any,
});
}
addSelect<TOption, TSettings>(config: PanelOptionsEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
addSelect<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
config: PanelOptionsEditorConfig<TOptions, TSettings, TOption>
) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('select').editor as any,
});
}
addRadio<TOption, TSettings>(config: PanelOptionsEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
addRadio<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
config: PanelOptionsEditorConfig<TOptions, TSettings, TOption>
) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('radio').editor as any,
});
}
addBooleanSwitch<TSettings = any>(config: PanelOptionsEditorConfig<TSettings>) {
addBooleanSwitch<TSettings = any>(config: PanelOptionsEditorConfig<TOptions, TSettings, boolean>) {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('boolean').editor as any,
});
}
addColorPicker<TSettings = any>(config: PanelOptionsEditorConfig<TSettings & ColorFieldConfigSettings>): this {
addColorPicker<TSettings = any>(
config: PanelOptionsEditorConfig<TOptions, TSettings & ColorFieldConfigSettings, string>
): this {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('color').editor as any,
......@@ -157,7 +174,9 @@ export class PanelOptionsEditorBuilder extends OptionsUIRegistryBuilder<Standard
});
}
addUnitPicker<TSettings = any>(config: PanelOptionsEditorConfig<TSettings & UnitFieldConfigSettings>): this {
addUnitPicker<TSettings = any>(
config: PanelOptionsEditorConfig<TOptions, TSettings & UnitFieldConfigSettings, string>
): this {
return this.addCustomEditor({
...config,
editor: standardEditorsRegistry.get('unit').editor as any,
......
......@@ -6,7 +6,7 @@ const history: KeyValue<number> = {};
export const deprecationWarning = (file: string, oldName: string, newName?: string) => {
let message = `[Deprecation warning] ${file}: ${oldName} is deprecated`;
if (newName) {
message += `. Use ${newName} instead`;
message += `. Use ${newName} instead`;
}
const now = Date.now();
const last = history[message];
......
......@@ -5,6 +5,13 @@ import Forms from '../Forms';
export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFieldConfigSettings>> = ({
value,
onChange,
item,
}) => {
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
return (
<Forms.Input
placeholder={item.settings?.placeholder}
value={value || ''}
onChange={e => onChange(e.currentTarget.value)}
/>
);
};
......@@ -30,7 +30,7 @@ import { StatsPickerEditor } from '../components/OptionsUI/stats';
* Returns collection of common field config properties definitions
*/
export const getStandardFieldConfigs = () => {
const title: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
const title: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
id: 'title',
name: 'Title',
description: "Field's title",
......@@ -38,13 +38,13 @@ export const getStandardFieldConfigs = () => {
override: standardEditorsRegistry.get('text').editor as any,
process: stringOverrideProcessor,
settings: {
placeholder: 'auto',
placeholder: 'none',
expandTemplateVars: true,
},
shouldApply: field => field.type !== FieldType.time,
};
const unit: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
const unit: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
id: 'unit',
name: 'Unit',
description: 'Value units',
......@@ -60,7 +60,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const min: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
const min: FieldPropertyEditorItem<any, number, NumberFieldConfigSettings> = {
id: 'min',
name: 'Min',
description: 'Minimum expected value',
......@@ -75,7 +75,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const max: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
const max: FieldPropertyEditorItem<any, number, NumberFieldConfigSettings> = {
id: 'max',
name: 'Max',
description: 'Maximum expected value',
......@@ -91,7 +91,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const decimals: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
const decimals: FieldPropertyEditorItem<any, number, NumberFieldConfigSettings> = {
id: 'decimals',
name: 'Decimals',
description: 'Number of decimal to be shown for a value',
......@@ -110,7 +110,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const thresholds: FieldPropertyEditorItem<ThresholdsConfig, ThresholdsFieldConfigSettings> = {
const thresholds: FieldPropertyEditorItem<any, ThresholdsConfig, ThresholdsFieldConfigSettings> = {
id: 'thresholds',
name: 'Thresholds',
description: 'Manage thresholds',
......@@ -126,7 +126,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const mappings: FieldPropertyEditorItem<ValueMapping[], ValueMappingFieldConfigSettings> = {
const mappings: FieldPropertyEditorItem<any, ValueMapping[], ValueMappingFieldConfigSettings> = {
id: 'mappings',
name: 'Value mappings',
description: 'Manage value mappings',
......@@ -141,7 +141,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: field => field.type === FieldType.number,
};
const noValue: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
const noValue: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
id: 'noValue',
name: 'No Value',
description: 'What to show when there is no value',
......@@ -157,7 +157,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: () => true,
};
const links: FieldPropertyEditorItem<DataLink[], StringFieldConfigSettings> = {
const links: FieldPropertyEditorItem<any, DataLink[], StringFieldConfigSettings> = {
id: 'links',
name: 'DataLinks',
description: 'Manage date links',
......@@ -170,7 +170,7 @@ export const getStandardFieldConfigs = () => {
shouldApply: () => true,
};
const color: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
const color: FieldPropertyEditorItem<any, string, StringFieldConfigSettings> = {
id: 'color',
name: 'Color',
description: 'Customise color',
......@@ -217,7 +217,7 @@ export const getStandardOptionEditors = () => {
description: 'Allows option selection',
editor: props => (
<Forms.Select
defaultValue={props.value}
value={props.value}
onChange={e => props.onChange(e.value)}
options={props.item.settings?.options}
/>
......
......@@ -8,6 +8,7 @@ import {
standardFieldConfigEditorRegistry,
PanelPlugin,
SelectableValue,
StandardFieldConfigProperties,
} from '@grafana/data';
import { Forms, fieldMatchersUI, ValuePicker, useTheme } from '@grafana/ui';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
......@@ -17,7 +18,7 @@ import { css } from 'emotion';
interface Props {
plugin: PanelPlugin;
config: FieldConfigSource;
include?: string[]; // Ordered list of which fields should be shown/included
include?: StandardFieldConfigProperties[]; // Ordered list of which fields should be shown/included
onChange: (config: FieldConfigSource) => void;
/* Helpful for IntelliSense */
data: DataFrame[];
......@@ -67,12 +68,15 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
return null;
}
let configPropertiesOptions = standardFieldConfigEditorRegistry.list().map(i => ({
label: i.name,
value: i.id,
description: i.description,
custom: false,
}));
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(
......@@ -185,6 +189,9 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ include, data, onCha
);
const renderStandardConfigs = useCallback(() => {
if (include && include.length === 0) {
return null;
}
if (include) {
return <>{include.map(f => renderEditor(standardFieldConfigEditorRegistry.get(f), false))}</>;
}
......
......@@ -60,6 +60,7 @@ export const OptionsPaneContent: React.FC<{
plugin={plugin}
onChange={onFieldConfigsChange}
data={data.series}
include={plugin.standardFieldConfigProperties}
/>
</Container>
);
......
import { PanelModel } from './PanelModel';
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
import { ConfigOverrideRule, PanelProps } from '@grafana/data';
import { PanelProps, StandardFieldConfigProperties } from '@grafana/data';
import { ComponentClass } from 'react';
class TablePanelCtrl {}
......@@ -70,13 +70,6 @@ describe('PanelModel', () => {
};
model = new PanelModel(modelJson);
const overrideMock: ConfigOverrideRule = {
matcher: {
id: '2',
options: {},
},
properties: [],
};
const panelPlugin = getPanelPlugin(
{
......@@ -86,12 +79,9 @@ describe('PanelModel', () => {
TablePanelCtrl // angular
);
panelPlugin.setDefaults(defaultOptionsMock);
panelPlugin.setFieldConfigDefaults({
defaults: {
unit: 'flop',
decimals: 2,
},
overrides: [overrideMock],
panelPlugin.useStandardFieldConfig([StandardFieldConfigProperties.Unit, StandardFieldConfigProperties.Decimals], {
[StandardFieldConfigProperties.Unit]: 'flop',
[StandardFieldConfigProperties.Decimals]: 2,
});
model.pluginLoaded(panelPlugin);
});
......@@ -108,10 +98,6 @@ describe('PanelModel', () => {
expect(model.getOptions().arrayWith2Values.length).toBe(1);
});
it('should merge override field config options', () => {
expect(model.getFieldOverrideOptions().fieldOptions.overrides.length).toBe(2);
});
it('should apply field config defaults', () => {
// default unit is overriden by model
expect(model.getFieldOverrideOptions().fieldOptions.defaults.unit).toBe('mpg');
......
import { sharedSingleStatPanelChangedHandler } from '@grafana/ui';
import { PanelPlugin } from '@grafana/data';
import { defaultStandardFieldConfigProperties, PanelPlugin } from '@grafana/data';
import { BarGaugePanel } from './BarGaugePanel';
import { BarGaugeOptions, defaults } from './types';
import { standardFieldConfig, addStandardDataReduceOptions } from '../stat/types';
import { standardFieldConfigDefaults, addStandardDataReduceOptions } from '../stat/types';
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
import { barGaugePanelMigrationHandler } from './BarGaugeMigrations';
export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
.setDefaults(defaults)
.setEditor(BarGaugePanelEditor)
.setFieldConfigDefaults(standardFieldConfig)
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
......@@ -33,4 +32,5 @@ export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
});
})
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
.setMigrationHandler(barGaugePanelMigrationHandler);
.setMigrationHandler(barGaugePanelMigrationHandler)
.useStandardFieldConfig(defaultStandardFieldConfigProperties, standardFieldConfigDefaults);
import { PanelPlugin } from '@grafana/data';
import { defaultStandardFieldConfigProperties, PanelPlugin } from '@grafana/data';
import { GaugePanelEditor } from './GaugePanelEditor';
import { GaugePanel } from './GaugePanel';
import { GaugeOptions, defaults } from './types';
import { standardFieldConfig, addStandardDataReduceOptions } from '../stat/types';
import { standardFieldConfigDefaults, addStandardDataReduceOptions } from '../stat/types';
import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations';
export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel)
.setDefaults(defaults)
.setFieldConfigDefaults(standardFieldConfig)
.setEditor(GaugePanelEditor)
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
......@@ -25,4 +24,5 @@ export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel)
});
})
.setPanelChangeHandler(gaugePanelChangedHandler)
.setMigrationHandler(gaugePanelMigrationHandler);
.setMigrationHandler(gaugePanelMigrationHandler)
.useStandardFieldConfig(defaultStandardFieldConfigProperties, standardFieldConfigDefaults);
import { PanelPlugin } from '@grafana/data';
import { defaultStandardFieldConfigProperties, PanelPlugin, StandardFieldConfigProperties } from '@grafana/data';
import { PieChartPanelEditor } from './PieChartPanelEditor';
import { PieChartPanel } from './PieChartPanel';
import { PieChartOptions, defaults } from './types';
export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel)
.setDefaults(defaults)
.setFieldConfigDefaults({ defaults: { unit: 'short' } })
.useStandardFieldConfig(defaultStandardFieldConfigProperties, {
[StandardFieldConfigProperties.Unit]: 'short',
})
.setEditor(PieChartPanelEditor);
import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from '@grafana/ui';
import { PanelPlugin } from '@grafana/data';
import { StatPanelOptions, defaults, standardFieldConfig, addStandardDataReduceOptions } from './types';
import { defaultStandardFieldConfigProperties, PanelPlugin } from '@grafana/data';
import { StatPanelOptions, defaults, standardFieldConfigDefaults, addStandardDataReduceOptions } from './types';
import { StatPanel } from './StatPanel';
import { StatPanelEditor } from './StatPanelEditor';
export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel)
.setDefaults(defaults)
.setFieldConfigDefaults(standardFieldConfig)
.setEditor(StatPanelEditor)
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
......@@ -48,4 +47,5 @@ export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel)
})
.setNoPadding()
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
.setMigrationHandler(sharedSingleStatMigrationHandler);
.setMigrationHandler(sharedSingleStatMigrationHandler)
.useStandardFieldConfig(defaultStandardFieldConfigProperties, standardFieldConfigDefaults);
......@@ -4,9 +4,9 @@ import {
ReducerID,
ReduceDataOptions,
SelectableValue,
FieldConfigSource,
ThresholdsMode,
standardEditorsRegistry,
StandardFieldConfigProperties,
} from '@grafana/data';
import { PanelOptionsEditorBuilder } from '@grafana/data/src/utils/OptionsUIBuilders';
......@@ -37,21 +37,18 @@ export const commonValueOptionDefaults: ReduceDataOptions = {
calcs: [ReducerID.mean],
};
export const standardFieldConfig: FieldConfigSource = {
defaults: {
thresholds: {
mode: ThresholdsMode.Absolute,
steps: [
{ value: -Infinity, color: 'green' },
{ value: 80, color: 'red' }, // 80%
],
},
mappings: [],
export const standardFieldConfigDefaults: Partial<Record<StandardFieldConfigProperties, any>> = {
[StandardFieldConfigProperties.Thresholds]: {
mode: ThresholdsMode.Absolute,
steps: [
{ value: -Infinity, color: 'green' },
{ value: 80, color: 'red' }, // 80%
],
},
overrides: [],
[StandardFieldConfigProperties.Mappings]: [],
};
export function addStandardDataReduceOptions(builder: PanelOptionsEditorBuilder) {
export function addStandardDataReduceOptions(builder: PanelOptionsEditorBuilder<StatPanelOptions>) {
builder.addRadio({
id: 'reduceOptions.values',
name: 'Show',
......
import { PanelPlugin } from '@grafana/data';
import { TablePanel } from './TablePanel';
import { Options, defaults } from './types';
import { CustomFieldConfig, defaults, Options } from './types';
export const plugin = new PanelPlugin<Options>(TablePanel)
export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
.setDefaults(defaults)
.setCustomFieldOptions(builder => {
builder
......@@ -15,6 +15,7 @@ export const plugin = new PanelPlugin<Options>(TablePanel)
min: 20,
max: 300,
},
defaultValue: 1,
})
.addSelect({
id: 'displayMode',
......
......@@ -2,6 +2,11 @@ export interface Options {
showHeader: boolean;
}
export interface CustomFieldConfig {
width: number;
displayMode: string;
}
export const defaults: Options = {
showHeader: true,
};
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