From dcf6bbc14f994a8b1b974ab5beb9ad47f224e8e9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 1 Apr 2020 15:33:10 +0200 Subject: [PATCH] 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 * 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 --- .../src/field/overrides/processors.ts | 3 + packages/grafana-data/src/index.ts | 2 +- .../src/panel/PanelPlugin.test.tsx | 200 +++++++++++++++++- .../grafana-data/src/panel/PanelPlugin.ts | 168 ++++++++++++--- .../src/types/OptionsUIRegistryBuilder.ts | 40 ++-- packages/grafana-data/src/types/dataFrame.ts | 4 +- .../grafana-data/src/types/fieldOverrides.ts | 26 ++- packages/grafana-data/src/types/panel.ts | 9 +- .../src/utils/OptionsUIBuilders.ts | 53 +++-- .../src/utils/deprecationWarning.ts | 2 +- .../src/components/OptionsUI/string.tsx | 9 +- .../grafana-ui/src/utils/standardEditors.tsx | 24 +-- .../PanelEditor/FieldConfigEditor.tsx | 21 +- .../PanelEditor/OptionsPaneContent.tsx | 1 + .../dashboard/state/PanelModel.test.ts | 22 +- public/app/plugins/panel/bargauge/module.tsx | 8 +- public/app/plugins/panel/gauge/module.tsx | 8 +- public/app/plugins/panel/piechart/module.tsx | 6 +- public/app/plugins/panel/stat/module.tsx | 8 +- public/app/plugins/panel/stat/types.ts | 23 +- public/app/plugins/panel/table2/module.tsx | 5 +- public/app/plugins/panel/table2/types.ts | 5 + 22 files changed, 507 insertions(+), 140 deletions(-) diff --git a/packages/grafana-data/src/field/overrides/processors.ts b/packages/grafana-data/src/field/overrides/processors.ts index bff63a677f9..ea195ecca52 100644 --- a/packages/grafana-data/src/field/overrides/processors.ts +++ b/packages/grafana-data/src/field/overrides/processors.ts @@ -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); } diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 43a488d7095..8581614c40e 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -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'; diff --git a/packages/grafana-data/src/panel/PanelPlugin.test.tsx b/packages/grafana-data/src/panel/PanelPlugin.test.tsx index e9244813ed6..1b272d20759 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.test.tsx +++ b/packages/grafana-data/src/panel/PanelPlugin.test.tsx @@ -1,9 +1,19 @@ 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
Panel
; @@ -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
Panel
; + }); + + 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: () =>
Editor
, + 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
Panel
; + }); + + 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
Panel
; + }); + + 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: () =>
Editor
, + override: () =>
Override editor
, + 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
Panel
; + }); + + 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
Panel
; + }); + + panel.useStandardFieldConfig(); + expect(panel.standardFieldConfigProperties).toEqual(Array.from(standardFieldConfigProperties.keys())); + }); + + test('selected standard config', () => { + const panel = new PanelPlugin(() => { + return
Panel
; + }); + + panel.useStandardFieldConfig([StandardFieldConfigProperties.Min, StandardFieldConfigProperties.Thresholds]); + expect(panel.standardFieldConfigProperties).toEqual(['min', 'thresholds']); + }); + + describe('default values', () => { + test('setting default values', () => { + const panel = new PanelPlugin(() => { + return
Panel
; + }); + + 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
Panel
; + }); + + panel.useStandardFieldConfig([StandardFieldConfigProperties.Color], { + [StandardFieldConfigProperties.Color]: '#ff00ff', + [StandardFieldConfigProperties.Min]: 10, + }); + + expect(panel.standardFieldConfigProperties).toEqual(['color']); + + expect(panel.fieldConfigDefaults).toEqual({ + defaults: { + color: '#ff00ff', + }, + overrides: [], + }); + }); + }); + }); + }); }); diff --git a/packages/grafana-data/src/panel/PanelPlugin.ts b/packages/grafana-data/src/panel/PanelPlugin.ts index 0ebf536585b..89c64721da6 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.ts +++ b/packages/grafana-data/src/panel/PanelPlugin.ts @@ -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 extends GrafanaPlugin { - private customFieldConfigsUIBuilder = new FieldConfigEditorBuilder(); - private _customFieldConfigs?: FieldConfigEditorRegistry; - private registerCustomFieldConfigs?: (builder: FieldConfigEditorBuilder) => void; +export const defaultStandardFieldConfigProperties: StandardFieldConfigProperties[] = [ + StandardFieldConfigProperties.Min, + StandardFieldConfigProperties.Max, + StandardFieldConfigProperties.Title, + StandardFieldConfigProperties.Unit, + StandardFieldConfigProperties.Decimals, + StandardFieldConfigProperties.NoValue, + StandardFieldConfigProperties.Color, + StandardFieldConfigProperties.Thresholds, + StandardFieldConfigProperties.Mappings, + StandardFieldConfigProperties.Links, +]; - private optionsUIBuilder = new PanelOptionsEditorBuilder(); - private _optionEditors?: PanelOptionEditorsRegistry; - private registerOptionEditors?: (builder: PanelOptionsEditorBuilder) => void; +export const standardFieldConfigProperties = new Map(defaultStandardFieldConfigProperties.map(p => [p, undefined])); - panel: ComponentType>; - editor?: ComponentClass>; - defaults?: TOptions; - fieldConfigDefaults?: FieldConfigSource = { +export class PanelPlugin extends GrafanaPlugin< + PanelPluginMeta +> { + private _defaults?: TOptions; + private _standardFieldConfigProperties?: Map; + + private _fieldConfigDefaults: FieldConfigSource = { defaults: {}, overrides: [], }; + private _customFieldConfigs?: FieldConfigEditorRegistry; + private customFieldConfigsUIBuilder = new FieldConfigEditorBuilder(); + private registerCustomFieldConfigs?: (builder: FieldConfigEditorBuilder) => void; + + private _optionEditors?: PanelOptionEditorsRegistry; + private optionsUIBuilder = new PanelOptionsEditorBuilder(); + private registerOptionEditors?: (builder: PanelOptionsEditorBuilder) => void; + + panel: ComponentType>; + editor?: ComponentClass>; onPanelMigration?: PanelMigrationHandler; onPanelTypeChanged?: PanelTypeChangedHandler; noPadding?: boolean; @@ -42,6 +64,66 @@ export class PanelPlugin extends GrafanaPlugin 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 { + 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 extends GrafanaPlugin return this; } - setDefaults(defaults: TOptions) { - this.defaults = defaults; - return this; - } - setNoPadding() { this.noPadding = true; return this; @@ -134,7 +211,7 @@ export class PanelPlugin extends GrafanaPlugin * * @public **/ - setCustomFieldOptions(builder: (builder: FieldConfigEditorBuilder) => void) { + setCustomFieldOptions(builder: (builder: FieldConfigEditorBuilder) => void) { // builder is applied lazily when custom field configs are accessed this.registerCustomFieldConfigs = builder; return this; @@ -170,22 +247,63 @@ export class PanelPlugin extends GrafanaPlugin * * @public **/ - setPanelOptions(builder: (builder: PanelOptionsEditorBuilder) => void) { + setPanelOptions(builder: (builder: PanelOptionsEditorBuilder) => 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(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(ShapePanel) + * .useStandardFieldConfig([StandardFieldConfigProperties.Min, StandardFieldConfigProperties.Max, StandardFieldConfigProperties.Links]); + * + * // when standard option's default value needs to be provided + * export const plugin = new PanelPlugin(ShapePanel) + * .useStandardFieldConfig([StandardFieldConfigProperties.Min, StandardFieldConfigProperties.Max], { + * [StandardFieldConfigProperties.Min]: 20, + * [StandardFieldConfigProperties.Max]: 100 + * }); + * + * ``` + * + * @public */ - setFieldConfigDefaults(defaultConfig: Partial) { - this.fieldConfigDefaults = { - defaults: {}, - overrides: [], - ...defaultConfig, - }; + useStandardFieldConfig( + properties?: StandardFieldConfigProperties[], + defauls?: Partial> + ) { + 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; } } diff --git a/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts b/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts index 7f7a575f9e5..87990e7ba73 100644 --- a/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts +++ b/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts @@ -5,52 +5,59 @@ import { NumberFieldConfigSettings, SelectFieldConfigSettings, StringFieldConfig /** * Option editor registry item */ -export interface OptionsEditorItem extends RegistryItem { +export interface OptionsEditorItem extends RegistryItem { + id: (keyof TOptions & string) | string; editor: ComponentType; settings?: TSettings; + defaultValue?: TValue; } /** * Configuration of option editor registry item */ -interface OptionEditorConfig { - id: string; +interface OptionEditorConfig { + id: keyof TOptions & string; name: string; description: string; settings?: TSettings; + defaultValue?: TValue; } /** * Describes an API for option editors UI builder */ -export interface OptionsUIRegistryBuilderAPI> { +export interface OptionsUIRegistryBuilderAPI< + TOptions, + TEditorProps, + T extends OptionsEditorItem +> { addNumberInput?( - config: OptionEditorConfig + config: OptionEditorConfig ): this; addTextInput?( - config: OptionEditorConfig + config: OptionEditorConfig ): this; addSelect?>( - config: OptionEditorConfig + config: OptionEditorConfig ): this; addRadio? = SelectFieldConfigSettings>( - config: OptionEditorConfig + config: OptionEditorConfig ): this; - addBooleanSwitch?(config: OptionEditorConfig): this; + addBooleanSwitch?(config: OptionEditorConfig): this; - addUnitPicker?(config: OptionEditorConfig): this; + addUnitPicker?(config: OptionEditorConfig): this; - addColorPicker?(config: OptionEditorConfig): this; + addColorPicker?(config: OptionEditorConfig): this; /** * Enables custom editor definition * @param config */ - addCustomEditor(config: OptionsEditorItem): this; + addCustomEditor(config: OptionsEditorItem): this; /** * Returns registry of option editors @@ -58,11 +65,14 @@ export interface OptionsUIRegistryBuilderAPI Registry; } -export abstract class OptionsUIRegistryBuilder> - implements OptionsUIRegistryBuilderAPI { +export abstract class OptionsUIRegistryBuilder< + TOptions, + TEditorProps, + T extends OptionsEditorItem +> implements OptionsUIRegistryBuilderAPI { private properties: T[] = []; - addCustomEditor(config: T & OptionsEditorItem): this { + addCustomEditor(config: T & OptionsEditorItem): this { this.properties.push(config); return this; } diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index c01543fa1cf..8d120f5df95 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -21,7 +21,7 @@ export enum FieldType { * * Plugins may extend this with additional properties. Something like series overrides */ -export interface FieldConfig { +export interface FieldConfig { 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; + custom?: TOptions; scopedVars?: ScopedVars; } diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 26e14751af6..7b1816acc49 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -25,9 +25,9 @@ export interface ConfigOverrideRule { properties: DynamicConfigValue[]; } -export interface FieldConfigSource { +export interface FieldConfigSource { // Defatuls applied to all numeric fields - defaults: FieldConfig; + defaults: FieldConfig; // Rules to override individual values overrides: ConfigOverrideRule[]; @@ -54,16 +54,17 @@ export interface FieldOverrideEditorProps extends Omit { - id: string; +export interface FieldConfigEditorConfig { + id: (keyof TOptions & string) | string; name: string; description: string; settings?: TSettings; shouldApply?: (field: Field) => boolean; + defaultValue?: TValue; } -export interface FieldPropertyEditorItem - extends OptionsEditorItem> { +export interface FieldPropertyEditorItem + extends OptionsEditorItem, TValue> { // An editor that can be filled in with context info (template variables etc) override: ComponentType>; @@ -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', +} diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index 37defee1989..c2d1c488c6f 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -115,14 +115,15 @@ export type PanelOptionEditorsRegistry = Registry; export interface PanelOptionsEditorProps extends StandardEditorProps {} -export interface PanelOptionsEditorItem - extends OptionsEditorItem> {} +export interface PanelOptionsEditorItem + extends OptionsEditorItem, TValue> {} -export interface PanelOptionsEditorConfig { - id: string; +export interface PanelOptionsEditorConfig { + id: (keyof TOptions & string) | string; name: string; description: string; settings?: TSettings; + defaultValue?: TValue; } export interface PanelMenuItem { diff --git a/packages/grafana-data/src/utils/OptionsUIBuilders.ts b/packages/grafana-data/src/utils/OptionsUIBuilders.ts index f6c0f5e15bb..726d90bae6e 100644 --- a/packages/grafana-data/src/utils/OptionsUIBuilders.ts +++ b/packages/grafana-data/src/utils/OptionsUIBuilders.ts @@ -26,11 +26,12 @@ import { /** * Fluent API for declarative creation of field config option editors */ -export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< +export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< + TOptions, FieldConfigEditorProps, - FieldPropertyEditorItem + FieldPropertyEditorItem > { - addNumberInput(config: FieldConfigEditorConfig) { + addNumberInput(config: FieldConfigEditorConfig) { return this.addCustomEditor({ ...config, override: standardEditorsRegistry.get('number').editor as any, @@ -41,7 +42,7 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< }); } - addTextInput(config: FieldConfigEditorConfig) { + addTextInput(config: FieldConfigEditorConfig) { return this.addCustomEditor({ ...config, override: standardEditorsRegistry.get('text').editor as any, @@ -52,7 +53,9 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< }); } - addSelect(config: FieldConfigEditorConfig>) { + addSelect>( + config: FieldConfigEditorConfig + ) { return this.addCustomEditor({ ...config, override: standardEditorsRegistry.get('select').editor as any, @@ -64,7 +67,7 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< }); } - addRadio(config: FieldConfigEditorConfig>) { + addRadio(config: FieldConfigEditorConfig) { return this.addCustomEditor({ ...config, override: standardEditorsRegistry.get('radio').editor as any, @@ -76,7 +79,7 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< }); } - addBooleanSwitch(config: FieldConfigEditorConfig) { + addBooleanSwitch(config: FieldConfigEditorConfig) { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('boolean').editor as any, @@ -87,7 +90,9 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< }); } - addColorPicker(config: FieldConfigEditorConfig) { + addColorPicker( + config: FieldConfigEditorConfig + ) { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('color').editor as any, @@ -98,7 +103,9 @@ export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder< }); } - addUnitPicker(config: FieldConfigEditorConfig) { + addUnitPicker( + config: FieldConfigEditorConfig + ) { 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 { - addNumberInput(config: PanelOptionsEditorConfig) { +export class PanelOptionsEditorBuilder extends OptionsUIRegistryBuilder< + TOptions, + StandardEditorProps, + PanelOptionsEditorItem +> { + addNumberInput(config: PanelOptionsEditorConfig) { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('number').editor as any, }); } - addTextInput(config: PanelOptionsEditorConfig) { + addTextInput(config: PanelOptionsEditorConfig) { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('text').editor as any, }); } - addSelect(config: PanelOptionsEditorConfig>) { + addSelect>( + config: PanelOptionsEditorConfig + ) { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('select').editor as any, }); } - addRadio(config: PanelOptionsEditorConfig>) { + addRadio>( + config: PanelOptionsEditorConfig + ) { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('radio').editor as any, }); } - addBooleanSwitch(config: PanelOptionsEditorConfig) { + addBooleanSwitch(config: PanelOptionsEditorConfig) { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('boolean').editor as any, }); } - addColorPicker(config: PanelOptionsEditorConfig): this { + addColorPicker( + config: PanelOptionsEditorConfig + ): this { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('color').editor as any, @@ -157,7 +174,9 @@ export class PanelOptionsEditorBuilder extends OptionsUIRegistryBuilder(config: PanelOptionsEditorConfig): this { + addUnitPicker( + config: PanelOptionsEditorConfig + ): this { return this.addCustomEditor({ ...config, editor: standardEditorsRegistry.get('unit').editor as any, diff --git a/packages/grafana-data/src/utils/deprecationWarning.ts b/packages/grafana-data/src/utils/deprecationWarning.ts index 8ba62595d0a..b959ccd1e18 100644 --- a/packages/grafana-data/src/utils/deprecationWarning.ts +++ b/packages/grafana-data/src/utils/deprecationWarning.ts @@ -6,7 +6,7 @@ const history: KeyValue = {}; 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]; diff --git a/packages/grafana-ui/src/components/OptionsUI/string.tsx b/packages/grafana-ui/src/components/OptionsUI/string.tsx index b0b460b9f7d..d4abc1428f5 100644 --- a/packages/grafana-ui/src/components/OptionsUI/string.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/string.tsx @@ -5,6 +5,13 @@ import Forms from '../Forms'; export const StringValueEditor: React.FC> = ({ value, onChange, + item, }) => { - return onChange(e.currentTarget.value)} />; + return ( + onChange(e.currentTarget.value)} + /> + ); }; diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index b04e3776991..271787b46aa 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -30,7 +30,7 @@ import { StatsPickerEditor } from '../components/OptionsUI/stats'; * Returns collection of common field config properties definitions */ export const getStandardFieldConfigs = () => { - const title: FieldPropertyEditorItem = { + const title: FieldPropertyEditorItem = { 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 = { + const unit: FieldPropertyEditorItem = { id: 'unit', name: 'Unit', description: 'Value units', @@ -60,7 +60,7 @@ export const getStandardFieldConfigs = () => { shouldApply: field => field.type === FieldType.number, }; - const min: FieldPropertyEditorItem = { + const min: FieldPropertyEditorItem = { id: 'min', name: 'Min', description: 'Minimum expected value', @@ -75,7 +75,7 @@ export const getStandardFieldConfigs = () => { shouldApply: field => field.type === FieldType.number, }; - const max: FieldPropertyEditorItem = { + const max: FieldPropertyEditorItem = { id: 'max', name: 'Max', description: 'Maximum expected value', @@ -91,7 +91,7 @@ export const getStandardFieldConfigs = () => { shouldApply: field => field.type === FieldType.number, }; - const decimals: FieldPropertyEditorItem = { + const decimals: FieldPropertyEditorItem = { 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 = { + const thresholds: FieldPropertyEditorItem = { id: 'thresholds', name: 'Thresholds', description: 'Manage thresholds', @@ -126,7 +126,7 @@ export const getStandardFieldConfigs = () => { shouldApply: field => field.type === FieldType.number, }; - const mappings: FieldPropertyEditorItem = { + const mappings: FieldPropertyEditorItem = { 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 = { + const noValue: FieldPropertyEditorItem = { 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 = { + const links: FieldPropertyEditorItem = { id: 'links', name: 'DataLinks', description: 'Manage date links', @@ -170,7 +170,7 @@ export const getStandardFieldConfigs = () => { shouldApply: () => true, }; - const color: FieldPropertyEditorItem = { + const color: FieldPropertyEditorItem = { id: 'color', name: 'Color', description: 'Customise color', @@ -217,7 +217,7 @@ export const getStandardOptionEditors = () => { description: 'Allows option selection', editor: props => ( props.onChange(e.value)} options={props.item.settings?.options} /> diff --git a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx b/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx index 59ea82ae67e..2421d88c4ad 100644 --- a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx @@ -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 => { 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 = ({ include, data, onCha ); const renderStandardConfigs = useCallback(() => { + if (include && include.length === 0) { + return null; + } if (include) { return <>{include.map(f => renderEditor(standardFieldConfigEditorRegistry.get(f), false))}; } diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx index 98fd49b6b09..5b11503a86e 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx @@ -60,6 +60,7 @@ export const OptionsPaneContent: React.FC<{ plugin={plugin} onChange={onFieldConfigsChange} data={data.series} + include={plugin.standardFieldConfigProperties} /> ); diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 16058904db3..4248ad85e94 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -1,6 +1,6 @@ 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'); diff --git a/public/app/plugins/panel/bargauge/module.tsx b/public/app/plugins/panel/bargauge/module.tsx index ff207a65608..b4f4a99d3f8 100644 --- a/public/app/plugins/panel/bargauge/module.tsx +++ b/public/app/plugins/panel/bargauge/module.tsx @@ -1,15 +1,14 @@ 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(BarGaugePanel) .setDefaults(defaults) .setEditor(BarGaugePanelEditor) - .setFieldConfigDefaults(standardFieldConfig) .setPanelOptions(builder => { addStandardDataReduceOptions(builder); @@ -33,4 +32,5 @@ export const plugin = new PanelPlugin(BarGaugePanel) }); }) .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) - .setMigrationHandler(barGaugePanelMigrationHandler); + .setMigrationHandler(barGaugePanelMigrationHandler) + .useStandardFieldConfig(defaultStandardFieldConfigProperties, standardFieldConfigDefaults); diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index 894c600b7d1..9b4159b0058 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -1,13 +1,12 @@ -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(GaugePanel) .setDefaults(defaults) - .setFieldConfigDefaults(standardFieldConfig) .setEditor(GaugePanelEditor) .setPanelOptions(builder => { addStandardDataReduceOptions(builder); @@ -25,4 +24,5 @@ export const plugin = new PanelPlugin(GaugePanel) }); }) .setPanelChangeHandler(gaugePanelChangedHandler) - .setMigrationHandler(gaugePanelMigrationHandler); + .setMigrationHandler(gaugePanelMigrationHandler) + .useStandardFieldConfig(defaultStandardFieldConfigProperties, standardFieldConfigDefaults); diff --git a/public/app/plugins/panel/piechart/module.tsx b/public/app/plugins/panel/piechart/module.tsx index 5782ecc2131..1ff9e6885d1 100644 --- a/public/app/plugins/panel/piechart/module.tsx +++ b/public/app/plugins/panel/piechart/module.tsx @@ -1,9 +1,11 @@ -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(PieChartPanel) .setDefaults(defaults) - .setFieldConfigDefaults({ defaults: { unit: 'short' } }) + .useStandardFieldConfig(defaultStandardFieldConfigProperties, { + [StandardFieldConfigProperties.Unit]: 'short', + }) .setEditor(PieChartPanelEditor); diff --git a/public/app/plugins/panel/stat/module.tsx b/public/app/plugins/panel/stat/module.tsx index e6eb90f0cc3..799ff473e2d 100644 --- a/public/app/plugins/panel/stat/module.tsx +++ b/public/app/plugins/panel/stat/module.tsx @@ -1,12 +1,11 @@ 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(StatPanel) .setDefaults(defaults) - .setFieldConfigDefaults(standardFieldConfig) .setEditor(StatPanelEditor) .setPanelOptions(builder => { addStandardDataReduceOptions(builder); @@ -48,4 +47,5 @@ export const plugin = new PanelPlugin(StatPanel) }) .setNoPadding() .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) - .setMigrationHandler(sharedSingleStatMigrationHandler); + .setMigrationHandler(sharedSingleStatMigrationHandler) + .useStandardFieldConfig(defaultStandardFieldConfigProperties, standardFieldConfigDefaults); diff --git a/public/app/plugins/panel/stat/types.ts b/public/app/plugins/panel/stat/types.ts index 3d990d74c51..e2e9ff11c05 100644 --- a/public/app/plugins/panel/stat/types.ts +++ b/public/app/plugins/panel/stat/types.ts @@ -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> = { + [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) { builder.addRadio({ id: 'reduceOptions.values', name: 'Show', diff --git a/public/app/plugins/panel/table2/module.tsx b/public/app/plugins/panel/table2/module.tsx index ee4839dbeaf..5fb17e3108e 100644 --- a/public/app/plugins/panel/table2/module.tsx +++ b/public/app/plugins/panel/table2/module.tsx @@ -1,8 +1,8 @@ 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(TablePanel) +export const plugin = new PanelPlugin(TablePanel) .setDefaults(defaults) .setCustomFieldOptions(builder => { builder @@ -15,6 +15,7 @@ export const plugin = new PanelPlugin(TablePanel) min: 20, max: 300, }, + defaultValue: 1, }) .addSelect({ id: 'displayMode', diff --git a/public/app/plugins/panel/table2/types.ts b/public/app/plugins/panel/table2/types.ts index 6814ce6a60c..d927f72e07a 100644 --- a/public/app/plugins/panel/table2/types.ts +++ b/public/app/plugins/panel/table2/types.ts @@ -2,6 +2,11 @@ export interface Options { showHeader: boolean; } +export interface CustomFieldConfig { + width: number; + displayMode: string; +} + export const defaults: Options = { showHeader: true, };