mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NewPanelEditor: fluent API for custom field config and panel options creation (#23070)
* Registry of standard option editors * Move override processors to grafana data * API for declaratively creating field config/panel options * Enable declarative API in PanelPlugin for options and field config * Use new api in react table panel * Add color and unit picker to option registries * Add some docs and tests * Fix tests
This commit is contained in:
parent
0c9e50c7da
commit
4eae1b5483
@ -2,5 +2,6 @@ export * from './fieldDisplay';
|
|||||||
export * from './displayProcessor';
|
export * from './displayProcessor';
|
||||||
export * from './scale';
|
export * from './scale';
|
||||||
export * from './standardFieldConfigEditorRegistry';
|
export * from './standardFieldConfigEditorRegistry';
|
||||||
|
export * from './overrides/processors';
|
||||||
|
|
||||||
export { applyFieldOverrides, validateFieldConfig } from './fieldOverrides';
|
export { applyFieldOverrides, validateFieldConfig } from './fieldOverrides';
|
||||||
|
108
packages/grafana-data/src/field/overrides/processors.ts
Normal file
108
packages/grafana-data/src/field/overrides/processors.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { DataLink, FieldOverrideContext, SelectableValue, ThresholdsConfig, ValueMapping } from '../../types';
|
||||||
|
|
||||||
|
export const identityOverrideProcessor = <T>(value: T, _context: FieldOverrideContext, _settings: any) => {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface NumberFieldConfigSettings {
|
||||||
|
placeholder?: string;
|
||||||
|
integer?: boolean;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const numberOverrideProcessor = (
|
||||||
|
value: any,
|
||||||
|
context: FieldOverrideContext,
|
||||||
|
settings: NumberFieldConfigSettings
|
||||||
|
) => {
|
||||||
|
const v = parseFloat(`${value}`);
|
||||||
|
if (settings.max && v > settings.max) {
|
||||||
|
// ????
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DataLinksFieldConfigSettings {}
|
||||||
|
|
||||||
|
export const dataLinksOverrideProcessor = (
|
||||||
|
value: any,
|
||||||
|
_context: FieldOverrideContext,
|
||||||
|
_settings: DataLinksFieldConfigSettings
|
||||||
|
) => {
|
||||||
|
return value as DataLink[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ValueMappingFieldConfigSettings {}
|
||||||
|
|
||||||
|
export const valueMappingsOverrideProcessor = (
|
||||||
|
value: any,
|
||||||
|
_context: FieldOverrideContext,
|
||||||
|
_settings: ValueMappingFieldConfigSettings
|
||||||
|
) => {
|
||||||
|
return value as ValueMapping[]; // !!!! likely not !!!!
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SelectFieldConfigSettings<T> {
|
||||||
|
options: Array<SelectableValue<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const selectOverrideProcessor = (
|
||||||
|
value: any,
|
||||||
|
_context: FieldOverrideContext,
|
||||||
|
_settings: SelectFieldConfigSettings<any>
|
||||||
|
) => {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface StringFieldConfigSettings {
|
||||||
|
placeholder?: string;
|
||||||
|
maxLength?: number;
|
||||||
|
expandTemplateVars?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stringOverrideProcessor = (
|
||||||
|
value: any,
|
||||||
|
context: FieldOverrideContext,
|
||||||
|
settings: StringFieldConfigSettings
|
||||||
|
) => {
|
||||||
|
if (settings.expandTemplateVars && context.replaceVariables) {
|
||||||
|
return context.replaceVariables(value, context.field!.config.scopedVars);
|
||||||
|
}
|
||||||
|
return `${value}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ThresholdsFieldConfigSettings {
|
||||||
|
// Anything?
|
||||||
|
}
|
||||||
|
|
||||||
|
export const thresholdsOverrideProcessor = (
|
||||||
|
value: any,
|
||||||
|
_context: FieldOverrideContext,
|
||||||
|
_settings: ThresholdsFieldConfigSettings
|
||||||
|
) => {
|
||||||
|
return value as ThresholdsConfig; // !!!! likely not !!!!
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface UnitFieldConfigSettings {}
|
||||||
|
|
||||||
|
export const unitOverrideProcessor = (
|
||||||
|
value: boolean,
|
||||||
|
_context: FieldOverrideContext,
|
||||||
|
_settings: UnitFieldConfigSettings
|
||||||
|
) => {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const booleanOverrideProcessor = (
|
||||||
|
value: boolean,
|
||||||
|
_context: FieldOverrideContext,
|
||||||
|
_settings: ThresholdsFieldConfigSettings
|
||||||
|
) => {
|
||||||
|
return value; // !!!! likely not !!!!
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ColorFieldConfigSettings {
|
||||||
|
enableNamedColors?: boolean;
|
||||||
|
}
|
@ -1,4 +1,16 @@
|
|||||||
import { FieldConfigEditorRegistry, FieldPropertyEditorItem } from '../types/fieldOverrides';
|
import { FieldConfigEditorRegistry, FieldPropertyEditorItem } from '../types/fieldOverrides';
|
||||||
import { Registry } from '../utils/Registry';
|
import { Registry, RegistryItem } from '../utils/Registry';
|
||||||
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
|
export interface StandardEditorProps<TValue = any, TSettings = any> {
|
||||||
|
value: TValue;
|
||||||
|
onChange: (value?: TValue) => void;
|
||||||
|
item: StandardEditorsRegistryItem<TValue, TSettings>;
|
||||||
|
}
|
||||||
|
export interface StandardEditorsRegistryItem<TValue = any, TSettings = any> extends RegistryItem {
|
||||||
|
editor: ComponentType<StandardEditorProps<TValue, TSettings>>;
|
||||||
|
settings?: TSettings;
|
||||||
|
}
|
||||||
export const standardFieldConfigEditorRegistry: FieldConfigEditorRegistry = new Registry<FieldPropertyEditorItem>();
|
export const standardFieldConfigEditorRegistry: FieldConfigEditorRegistry = new Registry<FieldPropertyEditorItem>();
|
||||||
|
|
||||||
|
export const standardEditorsRegistry = new Registry<StandardEditorsRegistryItem<any>>();
|
||||||
|
@ -12,3 +12,4 @@ export * from './datetime';
|
|||||||
export * from './text';
|
export * from './text';
|
||||||
export * from './valueFormats';
|
export * from './valueFormats';
|
||||||
export * from './field';
|
export * from './field';
|
||||||
|
export { PanelPlugin } from './panel/PanelPlugin';
|
||||||
|
48
packages/grafana-data/src/panel/PanelPlugin.test.tsx
Normal file
48
packages/grafana-data/src/panel/PanelPlugin.test.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { identityOverrideProcessor } from '../field';
|
||||||
|
import { PanelPlugin } from './PanelPlugin';
|
||||||
|
|
||||||
|
describe('PanelPlugin', () => {
|
||||||
|
describe('declarative options', () => {
|
||||||
|
test('field config UI API', () => {
|
||||||
|
const panel = new PanelPlugin(() => {
|
||||||
|
return <div>Panel</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.setCustomFieldConfigEditor(builder => {
|
||||||
|
builder.addCustomEditor({
|
||||||
|
id: 'custom',
|
||||||
|
name: 'Custom',
|
||||||
|
description: 'Custom field config property description',
|
||||||
|
editor: () => <div>Editor</div>,
|
||||||
|
override: () => <div>Editor</div>,
|
||||||
|
process: identityOverrideProcessor,
|
||||||
|
settings: {},
|
||||||
|
shouldApply: () => true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(panel.customFieldConfigs).toBeDefined();
|
||||||
|
expect(panel.customFieldConfigs!.list()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('options UI API', () => {
|
||||||
|
const panel = new PanelPlugin(() => {
|
||||||
|
return <div>Panel</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.setOptionsEditor(builder => {
|
||||||
|
builder.addCustomEditor({
|
||||||
|
id: 'option',
|
||||||
|
name: 'Option editor',
|
||||||
|
description: 'Option editor description',
|
||||||
|
editor: () => <div>Editor</div>,
|
||||||
|
settings: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(panel.optionEditors).toBeDefined();
|
||||||
|
expect(panel.optionEditors!.list()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
191
packages/grafana-data/src/panel/PanelPlugin.ts
Normal file
191
packages/grafana-data/src/panel/PanelPlugin.ts
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import {
|
||||||
|
FieldConfigEditorRegistry,
|
||||||
|
FieldConfigSource,
|
||||||
|
GrafanaPlugin,
|
||||||
|
PanelEditorProps,
|
||||||
|
PanelMigrationHandler,
|
||||||
|
PanelOptionEditorsRegistry,
|
||||||
|
PanelPluginMeta,
|
||||||
|
PanelProps,
|
||||||
|
PanelTypeChangedHandler,
|
||||||
|
} from '../types';
|
||||||
|
import { FieldConfigEditorBuilder, PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders';
|
||||||
|
import { ComponentClass, ComponentType } from 'react';
|
||||||
|
|
||||||
|
export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta> {
|
||||||
|
private customFieldConfigsUIBuilder = new FieldConfigEditorBuilder();
|
||||||
|
private _customFieldConfigs?: FieldConfigEditorRegistry;
|
||||||
|
private registerCustomFieldConfigs?: (builder: FieldConfigEditorBuilder) => void;
|
||||||
|
|
||||||
|
private optionsUIBuilder = new PanelOptionsEditorBuilder();
|
||||||
|
private _optionEditors?: PanelOptionEditorsRegistry;
|
||||||
|
private registerOptionEditors?: (builder: PanelOptionsEditorBuilder) => void;
|
||||||
|
|
||||||
|
panel: ComponentType<PanelProps<TOptions>>;
|
||||||
|
editor?: ComponentClass<PanelEditorProps<TOptions>>;
|
||||||
|
defaults?: TOptions;
|
||||||
|
fieldConfigDefaults?: FieldConfigSource = {
|
||||||
|
defaults: {},
|
||||||
|
overrides: [],
|
||||||
|
};
|
||||||
|
onPanelMigration?: PanelMigrationHandler<TOptions>;
|
||||||
|
onPanelTypeChanged?: PanelTypeChangedHandler<TOptions>;
|
||||||
|
noPadding?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy angular ctrl. If this exists it will be used instead of the panel
|
||||||
|
*/
|
||||||
|
angularPanelCtrl?: any;
|
||||||
|
|
||||||
|
constructor(panel: ComponentType<PanelProps<TOptions>>) {
|
||||||
|
super();
|
||||||
|
this.panel = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
get customFieldConfigs() {
|
||||||
|
if (!this._customFieldConfigs && this.registerCustomFieldConfigs) {
|
||||||
|
this.registerCustomFieldConfigs(this.customFieldConfigsUIBuilder);
|
||||||
|
this._customFieldConfigs = this.customFieldConfigsUIBuilder.getRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._customFieldConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get optionEditors() {
|
||||||
|
if (!this._optionEditors && this.registerOptionEditors) {
|
||||||
|
this.registerOptionEditors(this.optionsUIBuilder);
|
||||||
|
this._optionEditors = this.optionsUIBuilder.getRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._optionEditors;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditor(editor: ComponentClass<PanelEditorProps<TOptions>>) {
|
||||||
|
this.editor = editor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaults(defaults: TOptions) {
|
||||||
|
this.defaults = defaults;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNoPadding() {
|
||||||
|
this.noPadding = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called before the panel first loads if
|
||||||
|
* the current version is different than the version that was saved.
|
||||||
|
*
|
||||||
|
* This is a good place to support any changes to the options model
|
||||||
|
*/
|
||||||
|
setMigrationHandler(handler: PanelMigrationHandler) {
|
||||||
|
this.onPanelMigration = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when the visualization was changed. This
|
||||||
|
* passes in the panel model for previous visualisation options inspection
|
||||||
|
* and panel model updates.
|
||||||
|
*
|
||||||
|
* This is useful for supporting PanelModel API updates when changing
|
||||||
|
* between Angular and React panels.
|
||||||
|
*/
|
||||||
|
setPanelChangeHandler(handler: PanelTypeChangedHandler) {
|
||||||
|
this.onPanelTypeChanged = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables custom field properties editor creation
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
*
|
||||||
|
* import { ShapePanel } from './ShapePanel';
|
||||||
|
*
|
||||||
|
* interface ShapePanelOptions {}
|
||||||
|
*
|
||||||
|
* export const plugin = new PanelPlugin<ShapePanelOptions>(ShapePanel)
|
||||||
|
* .setCustomFieldConfigEditor(builder => {
|
||||||
|
* builder
|
||||||
|
* .addNumberInput({
|
||||||
|
* id: 'shapeBorderWidth',
|
||||||
|
* name: 'Border width',
|
||||||
|
* description: 'Border width of the shape',
|
||||||
|
* settings: {
|
||||||
|
* min: 1,
|
||||||
|
* max: 5,
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* .addSelect({
|
||||||
|
* id: 'displayMode',
|
||||||
|
* name: 'Display mode',
|
||||||
|
* description: 'How the shape shout be rendered'
|
||||||
|
* settings: {
|
||||||
|
* options: [{value: 'fill', label: 'Fill' }, {value: 'transparent', label: 'Transparent }]
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
**/
|
||||||
|
setCustomFieldConfigEditor(builder: (builder: FieldConfigEditorBuilder) => void) {
|
||||||
|
// builder is applied lazily when custom field configs are accessed
|
||||||
|
this.registerCustomFieldConfigs = builder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables panel options editor creation
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
*
|
||||||
|
* import { ShapePanel } from './ShapePanel';
|
||||||
|
*
|
||||||
|
* interface ShapePanelOptions {}
|
||||||
|
*
|
||||||
|
* export const plugin = new PanelPlugin<ShapePanelOptions>(ShapePanel)
|
||||||
|
* .setOptionsEditor(builder => {
|
||||||
|
* builder
|
||||||
|
* .addSelect({
|
||||||
|
* id: 'shape',
|
||||||
|
* name: 'Shape',
|
||||||
|
* description: 'Select shape to render'
|
||||||
|
* settings: {
|
||||||
|
* options: [
|
||||||
|
* {value: 'circle', label: 'Circle' },
|
||||||
|
* {value: 'square', label: 'Square },
|
||||||
|
* {value: 'triangle', label: 'Triangle }
|
||||||
|
* ]
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
**/
|
||||||
|
setOptionsEditor(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
|
||||||
|
*/
|
||||||
|
setFieldConfigDefaults(defaultConfig: Partial<FieldConfigSource>) {
|
||||||
|
this.fieldConfigDefaults = {
|
||||||
|
defaults: {},
|
||||||
|
overrides: [],
|
||||||
|
...defaultConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
73
packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts
Normal file
73
packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { ComponentType } from 'react';
|
||||||
|
import { RegistryItem, Registry } from '../utils/Registry';
|
||||||
|
import { NumberFieldConfigSettings, SelectFieldConfigSettings, StringFieldConfigSettings } from '../field';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option editor registry item
|
||||||
|
*/
|
||||||
|
interface OptionsEditorItem<TSettings, TEditorProps> extends RegistryItem {
|
||||||
|
settings?: TSettings;
|
||||||
|
editor?: ComponentType<TEditorProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of option editor registry item
|
||||||
|
*/
|
||||||
|
type OptionEditorConfig<TSettings, TEditorProps> = Pick<
|
||||||
|
OptionsEditorItem<TSettings, TEditorProps>,
|
||||||
|
'id' | 'name' | 'description' | 'editor' | 'settings'
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes an API for option editors UI builder
|
||||||
|
*/
|
||||||
|
export interface OptionsUIRegistryBuilderAPI<TEditorProps, T extends OptionsEditorItem<any, TEditorProps>> {
|
||||||
|
addNumberInput?<TSettings extends NumberFieldConfigSettings = NumberFieldConfigSettings>(
|
||||||
|
config: OptionEditorConfig<TSettings, TEditorProps>
|
||||||
|
): this;
|
||||||
|
|
||||||
|
addTextInput?<TSettings extends StringFieldConfigSettings = StringFieldConfigSettings>(
|
||||||
|
config: OptionEditorConfig<TSettings, TEditorProps>
|
||||||
|
): this;
|
||||||
|
|
||||||
|
addSelect?<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
|
||||||
|
config: OptionEditorConfig<TSettings, TEditorProps>
|
||||||
|
): this;
|
||||||
|
|
||||||
|
addRadio?<TOption, TSettings extends SelectFieldConfigSettings<TOption> = SelectFieldConfigSettings<TOption>>(
|
||||||
|
config: OptionEditorConfig<TSettings, TEditorProps>
|
||||||
|
): this;
|
||||||
|
|
||||||
|
addBooleanSwitch?<TSettings = any>(config: OptionEditorConfig<TSettings, TEditorProps>): this;
|
||||||
|
|
||||||
|
addUnitPicker?<TSettings = any>(config: OptionEditorConfig<TSettings, TEditorProps>): this;
|
||||||
|
|
||||||
|
addColorPicker?<TSettings = any>(config: OptionEditorConfig<TSettings, TEditorProps>): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables custom editor definition
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
addCustomEditor<TSettings>(config: OptionsEditorItem<TSettings, TEditorProps>): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns registry of option editors
|
||||||
|
*/
|
||||||
|
getRegistry: () => Registry<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class OptionsUIRegistryBuilder<TEditorProps, T extends OptionsEditorItem<any, TEditorProps>>
|
||||||
|
implements OptionsUIRegistryBuilderAPI<TEditorProps, T> {
|
||||||
|
private properties: T[] = [];
|
||||||
|
|
||||||
|
addCustomEditor<TValue>(config: T & OptionsEditorItem<TValue, TEditorProps>): this {
|
||||||
|
this.properties.push(config);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegistry() {
|
||||||
|
return new Registry(() => {
|
||||||
|
return this.properties;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import { Registry, RegistryItem } from '../utils';
|
import { Registry, RegistryItem } from '../utils';
|
||||||
import { InterpolateFunction } from './panel';
|
import { InterpolateFunction } from './panel';
|
||||||
|
import { StandardEditorProps } from '../field';
|
||||||
|
|
||||||
export interface DynamicConfigValue {
|
export interface DynamicConfigValue {
|
||||||
prop: string;
|
prop: string;
|
||||||
@ -31,13 +32,6 @@ export interface FieldConfigSource {
|
|||||||
overrides: ConfigOverrideRule[];
|
overrides: ConfigOverrideRule[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldConfigEditorProps<TValue, TSettings> {
|
|
||||||
item: FieldPropertyEditorItem<TValue, TSettings>; // The property info
|
|
||||||
value: TValue;
|
|
||||||
context: FieldOverrideContext;
|
|
||||||
onChange: (value?: TValue) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldOverrideContext {
|
export interface FieldOverrideContext {
|
||||||
field?: Field;
|
field?: Field;
|
||||||
dataFrameIndex?: number; // The index for the selected field frame
|
dataFrameIndex?: number; // The index for the selected field frame
|
||||||
@ -46,11 +40,23 @@ export interface FieldOverrideContext {
|
|||||||
getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[];
|
getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldOverrideEditorProps<TValue, TSettings> {
|
export interface FieldConfigEditorProps<TValue, TSettings>
|
||||||
item: FieldPropertyEditorItem<TValue, TSettings>;
|
extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> {
|
||||||
|
item: FieldPropertyEditorItem<TValue, TSettings>; // The property info
|
||||||
value: TValue;
|
value: TValue;
|
||||||
context: FieldOverrideContext;
|
context: FieldOverrideContext;
|
||||||
onChange: (value?: any) => void;
|
onChange: (value?: TValue) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldOverrideEditorProps<TValue, TSettings> extends Omit<StandardEditorProps<TValue>, 'item'> {
|
||||||
|
item: FieldPropertyEditorItem<TValue, TSettings>;
|
||||||
|
context: FieldOverrideContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldConfigEditorConfig<TSettings = any, TValue = any>
|
||||||
|
extends Omit<Pick<FieldPropertyEditorItem<TValue, TSettings>, 'id' | 'description' | 'name'>, 'settings'> {
|
||||||
|
settings?: TSettings;
|
||||||
|
shouldApply?: (field: Field) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldPropertyEditorItem<TValue = any, TSettings = any> extends RegistryItem {
|
export interface FieldPropertyEditorItem<TValue = any, TSettings = any> extends RegistryItem {
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { ComponentClass, ComponentType } from 'react';
|
import { ComponentType } from 'react';
|
||||||
import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
|
import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
|
||||||
import { GrafanaPlugin, PluginMeta } from './plugin';
|
import { PluginMeta } from './plugin';
|
||||||
import { ScopedVars } from './ScopedVars';
|
import { ScopedVars } from './ScopedVars';
|
||||||
import { LoadingState } from './data';
|
import { LoadingState } from './data';
|
||||||
import { DataFrame } from './dataFrame';
|
import { DataFrame } from './dataFrame';
|
||||||
import { AbsoluteTimeRange, TimeRange, TimeZone } from './time';
|
import { AbsoluteTimeRange, TimeRange, TimeZone } from './time';
|
||||||
import { FieldConfigEditorRegistry, FieldConfigSource } from './fieldOverrides';
|
import { FieldConfigSource } from './fieldOverrides';
|
||||||
|
import { Registry, RegistryItem } from '../utils';
|
||||||
|
import { StandardEditorProps } from '../field';
|
||||||
|
|
||||||
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
|
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
|
||||||
|
|
||||||
@ -109,87 +111,18 @@ export type PanelTypeChangedHandler<TOptions = any> = (
|
|||||||
prevOptions: any
|
prevOptions: any
|
||||||
) => Partial<TOptions>;
|
) => Partial<TOptions>;
|
||||||
|
|
||||||
export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta> {
|
export type PanelOptionEditorsRegistry = Registry<PanelOptionsEditorItem>;
|
||||||
panel: ComponentType<PanelProps<TOptions>>;
|
|
||||||
editor?: ComponentClass<PanelEditorProps<TOptions>>;
|
|
||||||
customFieldConfigs?: FieldConfigEditorRegistry;
|
|
||||||
defaults?: TOptions;
|
|
||||||
fieldConfigDefaults?: FieldConfigSource = {
|
|
||||||
defaults: {},
|
|
||||||
overrides: [],
|
|
||||||
};
|
|
||||||
onPanelMigration?: PanelMigrationHandler<TOptions>;
|
|
||||||
onPanelTypeChanged?: PanelTypeChangedHandler<TOptions>;
|
|
||||||
noPadding?: boolean;
|
|
||||||
|
|
||||||
/**
|
export interface PanelOptionsEditorProps<TValue> extends StandardEditorProps<TValue> {}
|
||||||
* Legacy angular ctrl. If this exists it will be used instead of the panel
|
|
||||||
*/
|
|
||||||
angularPanelCtrl?: any;
|
|
||||||
|
|
||||||
constructor(panel: ComponentType<PanelProps<TOptions>>) {
|
export interface PanelOptionsEditorItem<TValue = any, TSettings = any> extends RegistryItem {
|
||||||
super();
|
editor: ComponentType<PanelOptionsEditorProps<TValue>>;
|
||||||
this.panel = panel;
|
settings?: TSettings;
|
||||||
}
|
|
||||||
|
|
||||||
setEditor(editor: ComponentClass<PanelEditorProps<TOptions>>) {
|
|
||||||
this.editor = editor;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefaults(defaults: TOptions) {
|
|
||||||
this.defaults = defaults;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setNoPadding() {
|
|
||||||
this.noPadding = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called before the panel first loads if
|
|
||||||
* the current version is different than the version that was saved.
|
|
||||||
*
|
|
||||||
* This is a good place to support any changes to the options model
|
|
||||||
*/
|
|
||||||
setMigrationHandler(handler: PanelMigrationHandler) {
|
|
||||||
this.onPanelMigration = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when the visualization was changed. This
|
|
||||||
* passes in the panel model for previous visualisation options inspection
|
|
||||||
* and panel model updates.
|
|
||||||
*
|
|
||||||
* This is useful for supporting PanelModel API updates when changing
|
|
||||||
* between Angular and React panels.
|
|
||||||
*/
|
|
||||||
setPanelChangeHandler(handler: PanelTypeChangedHandler) {
|
|
||||||
this.onPanelTypeChanged = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCustomFieldConfigs(registry: FieldConfigEditorRegistry) {
|
|
||||||
this.customFieldConfigs = registry;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables configuration of panel's default field config
|
|
||||||
*/
|
|
||||||
setFieldConfigDefaults(defaultConfig: Partial<FieldConfigSource>) {
|
|
||||||
this.fieldConfigDefaults = {
|
|
||||||
defaults: {},
|
|
||||||
overrides: [],
|
|
||||||
...defaultConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PanelOptionsEditorConfig<TSettings = any, TValue = any>
|
||||||
|
extends Pick<PanelOptionsEditorItem<TValue, TSettings>, 'id' | 'description' | 'name' | 'settings'> {}
|
||||||
|
|
||||||
export interface PanelMenuItem {
|
export interface PanelMenuItem {
|
||||||
type?: 'submenu' | 'divider';
|
type?: 'submenu' | 'divider';
|
||||||
text?: string;
|
text?: string;
|
||||||
|
166
packages/grafana-data/src/utils/OptionsUIBuilders.ts
Normal file
166
packages/grafana-data/src/utils/OptionsUIBuilders.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import {
|
||||||
|
FieldType,
|
||||||
|
FieldConfigEditorProps,
|
||||||
|
FieldPropertyEditorItem,
|
||||||
|
PanelOptionsEditorConfig,
|
||||||
|
PanelOptionsEditorItem,
|
||||||
|
FieldConfigEditorConfig,
|
||||||
|
} from '../types';
|
||||||
|
import { OptionsUIRegistryBuilder } from '../types/OptionsUIRegistryBuilder';
|
||||||
|
import {
|
||||||
|
numberOverrideProcessor,
|
||||||
|
selectOverrideProcessor,
|
||||||
|
stringOverrideProcessor,
|
||||||
|
booleanOverrideProcessor,
|
||||||
|
standardEditorsRegistry,
|
||||||
|
SelectFieldConfigSettings,
|
||||||
|
StandardEditorProps,
|
||||||
|
StringFieldConfigSettings,
|
||||||
|
NumberFieldConfigSettings,
|
||||||
|
ColorFieldConfigSettings,
|
||||||
|
identityOverrideProcessor,
|
||||||
|
UnitFieldConfigSettings,
|
||||||
|
unitOverrideProcessor,
|
||||||
|
} from '../field';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fluent API for declarative creation of field config option editors
|
||||||
|
*/
|
||||||
|
export class FieldConfigEditorBuilder extends OptionsUIRegistryBuilder<
|
||||||
|
FieldConfigEditorProps<any, any>,
|
||||||
|
FieldPropertyEditorItem
|
||||||
|
> {
|
||||||
|
addNumberInput<TSettings>(config: FieldConfigEditorConfig<TSettings & NumberFieldConfigSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
override: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
editor: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
process: numberOverrideProcessor,
|
||||||
|
shouldApply: config.shouldApply ? config.shouldApply : field => field.type === FieldType.number,
|
||||||
|
settings: config.settings || {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addTextInput<TSettings>(config: FieldConfigEditorConfig<TSettings & StringFieldConfigSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
override: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
editor: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
process: stringOverrideProcessor,
|
||||||
|
shouldApply: config.shouldApply ? config.shouldApply : field => field.type === FieldType.string,
|
||||||
|
settings: config.settings || {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addSelect<TOption, TSettings = any>(config: FieldConfigEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
override: standardEditorsRegistry.get('select').editor as any,
|
||||||
|
editor: standardEditorsRegistry.get('select').editor as any,
|
||||||
|
process: selectOverrideProcessor,
|
||||||
|
// ???
|
||||||
|
shouldApply: config.shouldApply ? config.shouldApply : () => true,
|
||||||
|
settings: config.settings || { options: [] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addRadio<TOption, TSettings = any>(config: FieldConfigEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
override: standardEditorsRegistry.get('radio').editor as any,
|
||||||
|
editor: standardEditorsRegistry.get('radio').editor as any,
|
||||||
|
process: selectOverrideProcessor,
|
||||||
|
// ???
|
||||||
|
shouldApply: config.shouldApply ? config.shouldApply : () => true,
|
||||||
|
settings: config.settings || { options: [] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addBooleanSwitch<TSettings = any>(config: FieldConfigEditorConfig<TSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('boolean').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('boolean').editor as any,
|
||||||
|
process: booleanOverrideProcessor,
|
||||||
|
shouldApply: config.shouldApply ? config.shouldApply : () => true,
|
||||||
|
settings: config.settings || {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addColorPicker<TSettings = any>(config: FieldConfigEditorConfig<TSettings & ColorFieldConfigSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('color').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('color').editor as any,
|
||||||
|
process: identityOverrideProcessor,
|
||||||
|
shouldApply: config.shouldApply ? config.shouldApply : () => true,
|
||||||
|
settings: config.settings || {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addUnitPicker<TSettings = any>(config: FieldConfigEditorConfig<TSettings & UnitFieldConfigSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('unit').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('unit').editor as any,
|
||||||
|
process: unitOverrideProcessor,
|
||||||
|
shouldApply: config.shouldApply ? config.shouldApply : () => true,
|
||||||
|
settings: config.settings || {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fluent API for declarative creation of panel options
|
||||||
|
*/
|
||||||
|
export class PanelOptionsEditorBuilder extends OptionsUIRegistryBuilder<StandardEditorProps, PanelOptionsEditorItem> {
|
||||||
|
addNumberInput<TSettings>(config: PanelOptionsEditorConfig<TSettings & NumberFieldConfigSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addTextInput<TSettings>(config: PanelOptionsEditorConfig<TSettings & StringFieldConfigSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addSelect<TOption, TSettings>(config: PanelOptionsEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('select').editor as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addRadio<TOption, TSettings>(config: PanelOptionsEditorConfig<TSettings & SelectFieldConfigSettings<TOption>>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('radio').editor as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addBooleanSwitch<TSettings = any>(config: PanelOptionsEditorConfig<TSettings>) {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('boolean').editor as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addColorPicker<TSettings = any>(config: PanelOptionsEditorConfig<TSettings & ColorFieldConfigSettings>): this {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('color').editor as any,
|
||||||
|
settings: config.settings || {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addUnitPicker<TSettings = any>(config: PanelOptionsEditorConfig<TSettings & UnitFieldConfigSettings>): this {
|
||||||
|
return this.addCustomEditor({
|
||||||
|
...config,
|
||||||
|
editor: standardEditorsRegistry.get('unit').editor as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
applyFieldOverrides,
|
|
||||||
FieldConfig,
|
FieldConfig,
|
||||||
FieldConfigSource,
|
FieldConfigSource,
|
||||||
InterpolateFunction,
|
InterpolateFunction,
|
||||||
@ -7,16 +6,19 @@ import {
|
|||||||
FieldMatcherID,
|
FieldMatcherID,
|
||||||
MutableDataFrame,
|
MutableDataFrame,
|
||||||
DataFrame,
|
DataFrame,
|
||||||
|
FieldType,
|
||||||
|
applyFieldOverrides,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
standardFieldConfigEditorRegistry,
|
standardFieldConfigEditorRegistry,
|
||||||
FieldType,
|
standardEditorsRegistry,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
import { getTheme } from '../../themes';
|
import { getTheme } from '../../themes';
|
||||||
import { getStandardFieldConfigs } from './standardFieldConfigEditors';
|
import { getStandardFieldConfigs, getStandardOptionEditors } from '../../utils';
|
||||||
|
|
||||||
describe('FieldOverrides', () => {
|
describe('FieldOverrides', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
standardEditorsRegistry.setInit(getStandardOptionEditors);
|
||||||
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
import { FieldOverrideContext, FieldConfigEditorProps, DataLink, FieldOverrideEditorProps } from '@grafana/data';
|
|
||||||
import React from 'react';
|
|
||||||
import { DataLinksInlineEditor } from '../DataLinks/DataLinksInlineEditor/DataLinksInlineEditor';
|
|
||||||
|
|
||||||
export interface DataLinksFieldConfigSettings {}
|
|
||||||
|
|
||||||
export const dataLinksOverrideProcessor = (
|
|
||||||
value: any,
|
|
||||||
context: FieldOverrideContext,
|
|
||||||
_settings: DataLinksFieldConfigSettings
|
|
||||||
) => {
|
|
||||||
return value as DataLink[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DataLinksValueEditor: React.FC<FieldConfigEditorProps<DataLink[], DataLinksFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
context,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<DataLinksInlineEditor
|
|
||||||
links={value}
|
|
||||||
onChange={onChange}
|
|
||||||
data={context.data}
|
|
||||||
suggestions={context.getSuggestions ? context.getSuggestions() : []}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DataLinksOverrideEditor: React.FC<FieldOverrideEditorProps<DataLink[], DataLinksFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
context,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<DataLinksInlineEditor
|
|
||||||
links={value}
|
|
||||||
onChange={onChange}
|
|
||||||
data={context.data}
|
|
||||||
suggestions={context.getSuggestions ? context.getSuggestions() : []}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,50 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps, ValueMapping } from '@grafana/data';
|
|
||||||
import { ValueMappingsEditor } from '../ValueMappingsEditor/ValueMappingsEditor';
|
|
||||||
|
|
||||||
export interface ValueMappingFieldConfigSettings {}
|
|
||||||
|
|
||||||
export const valueMappingsOverrideProcessor = (
|
|
||||||
value: any,
|
|
||||||
context: FieldOverrideContext,
|
|
||||||
settings: ValueMappingFieldConfigSettings
|
|
||||||
) => {
|
|
||||||
return value as ValueMapping[]; // !!!! likely not !!!!
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ValueMappingsValueEditor extends React.PureComponent<
|
|
||||||
FieldConfigEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>
|
|
||||||
> {
|
|
||||||
constructor(props: FieldConfigEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { onChange } = this.props;
|
|
||||||
let value = this.props.value;
|
|
||||||
if (!value) {
|
|
||||||
value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ValueMappingsEditor valueMappings={value} onChange={onChange} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ValueMappingsOverrideEditor extends React.PureComponent<
|
|
||||||
FieldOverrideEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>
|
|
||||||
> {
|
|
||||||
constructor(props: FieldOverrideEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { onChange } = this.props;
|
|
||||||
let value = this.props.value;
|
|
||||||
if (!value) {
|
|
||||||
value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ValueMappingsEditor valueMappings={value} onChange={onChange} />;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
FieldOverrideContext,
|
|
||||||
FieldOverrideEditorProps,
|
|
||||||
FieldConfigEditorProps,
|
|
||||||
toIntegerOrUndefined,
|
|
||||||
toFloatOrUndefined,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import Forms from '../Forms';
|
|
||||||
|
|
||||||
export interface NumberFieldConfigSettings {
|
|
||||||
placeholder?: string;
|
|
||||||
integer?: boolean;
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
step?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const numberOverrideProcessor = (
|
|
||||||
value: any,
|
|
||||||
context: FieldOverrideContext,
|
|
||||||
settings: NumberFieldConfigSettings
|
|
||||||
) => {
|
|
||||||
const v = parseFloat(`${value}`);
|
|
||||||
if (settings.max && v > settings.max) {
|
|
||||||
// ????
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NumberValueEditor: React.FC<FieldConfigEditorProps<number, NumberFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
item,
|
|
||||||
}) => {
|
|
||||||
const { settings } = item;
|
|
||||||
return (
|
|
||||||
<Forms.Input
|
|
||||||
value={isNaN(value) ? '' : value}
|
|
||||||
min={settings.min}
|
|
||||||
max={settings.max}
|
|
||||||
type="number"
|
|
||||||
step={settings.step}
|
|
||||||
placeholder={settings.placeholder}
|
|
||||||
onChange={e => {
|
|
||||||
onChange(
|
|
||||||
settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NumberOverrideEditor: React.FC<FieldOverrideEditorProps<number, NumberFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
item,
|
|
||||||
}) => {
|
|
||||||
const { settings } = item;
|
|
||||||
return (
|
|
||||||
<Forms.Input
|
|
||||||
value={isNaN(value) ? '' : value}
|
|
||||||
min={settings.min}
|
|
||||||
max={settings.max}
|
|
||||||
type="number"
|
|
||||||
step={settings.step}
|
|
||||||
placeholder={settings.placeholder}
|
|
||||||
onChange={e => {
|
|
||||||
onChange(
|
|
||||||
settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,32 +0,0 @@
|
|||||||
import React, { FC } from 'react';
|
|
||||||
|
|
||||||
import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps, SelectableValue } from '@grafana/data';
|
|
||||||
import Forms from '../Forms';
|
|
||||||
|
|
||||||
export interface SelectFieldConfigSettings<T> {
|
|
||||||
options: Array<SelectableValue<T>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const selectOverrideProcessor = (
|
|
||||||
value: any,
|
|
||||||
context: FieldOverrideContext,
|
|
||||||
settings: SelectFieldConfigSettings<any>
|
|
||||||
) => {
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SelectValueEditor: FC<FieldConfigEditorProps<string, SelectFieldConfigSettings<any>>> = ({
|
|
||||||
item,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return <Forms.Select value={value || ''} onChange={e => onChange(e.value)} options={item.settings.options} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SelectOverrideEditor: FC<FieldOverrideEditorProps<string, SelectFieldConfigSettings<any>>> = ({
|
|
||||||
item,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return <Forms.Select value={value || ''} onChange={e => onChange(e.value)} options={item.settings.options} />;
|
|
||||||
};
|
|
@ -1,162 +0,0 @@
|
|||||||
import { DataLink, FieldPropertyEditorItem, FieldType, ThresholdsConfig, ValueMapping } from '@grafana/data';
|
|
||||||
import { StringFieldConfigSettings, StringOverrideEditor, stringOverrideProcessor, StringValueEditor } from './string';
|
|
||||||
import { NumberFieldConfigSettings, NumberOverrideEditor, numberOverrideProcessor, NumberValueEditor } from './number';
|
|
||||||
import { UnitOverrideEditor, UnitValueEditor } from './units';
|
|
||||||
import {
|
|
||||||
ThresholdsFieldConfigSettings,
|
|
||||||
ThresholdsOverrideEditor,
|
|
||||||
thresholdsOverrideProcessor,
|
|
||||||
ThresholdsValueEditor,
|
|
||||||
} from './thresholds';
|
|
||||||
import { DataLinksOverrideEditor, dataLinksOverrideProcessor, DataLinksValueEditor } from './links';
|
|
||||||
import {
|
|
||||||
ValueMappingFieldConfigSettings,
|
|
||||||
ValueMappingsOverrideEditor,
|
|
||||||
valueMappingsOverrideProcessor,
|
|
||||||
ValueMappingsValueEditor,
|
|
||||||
} from './mappings';
|
|
||||||
|
|
||||||
export const getStandardFieldConfigs = () => {
|
|
||||||
const title: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
|
||||||
id: 'title', // Match field properties
|
|
||||||
name: 'Title',
|
|
||||||
description: 'The field title',
|
|
||||||
|
|
||||||
editor: StringValueEditor,
|
|
||||||
override: StringOverrideEditor,
|
|
||||||
process: stringOverrideProcessor,
|
|
||||||
settings: {
|
|
||||||
placeholder: 'auto',
|
|
||||||
expandTemplateVars: true,
|
|
||||||
},
|
|
||||||
shouldApply: field => field.type !== FieldType.time,
|
|
||||||
};
|
|
||||||
|
|
||||||
const unit: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
|
||||||
id: 'unit', // Match field properties
|
|
||||||
name: 'Unit',
|
|
||||||
description: 'value units',
|
|
||||||
|
|
||||||
editor: UnitValueEditor,
|
|
||||||
override: UnitOverrideEditor,
|
|
||||||
process: stringOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
placeholder: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldApply: field => field.type === FieldType.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const min: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
|
|
||||||
id: 'min', // Match field properties
|
|
||||||
name: 'Min',
|
|
||||||
description: 'Minimum expected value',
|
|
||||||
|
|
||||||
editor: NumberValueEditor,
|
|
||||||
override: NumberOverrideEditor,
|
|
||||||
process: numberOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
placeholder: 'auto',
|
|
||||||
},
|
|
||||||
shouldApply: field => field.type === FieldType.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const max: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
|
|
||||||
id: 'max', // Match field properties
|
|
||||||
name: 'Max',
|
|
||||||
description: 'Maximum expected value',
|
|
||||||
|
|
||||||
editor: NumberValueEditor,
|
|
||||||
override: NumberOverrideEditor,
|
|
||||||
process: numberOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
placeholder: 'auto',
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldApply: field => field.type === FieldType.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const decimals: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
|
|
||||||
id: 'decimals', // Match field properties
|
|
||||||
name: 'Decimals',
|
|
||||||
description: 'How many decimal places should be shown on a number',
|
|
||||||
|
|
||||||
editor: NumberValueEditor,
|
|
||||||
override: NumberOverrideEditor,
|
|
||||||
process: numberOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
placeholder: 'auto',
|
|
||||||
min: 0,
|
|
||||||
max: 15,
|
|
||||||
integer: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldApply: field => field.type === FieldType.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const thresholds: FieldPropertyEditorItem<ThresholdsConfig, ThresholdsFieldConfigSettings> = {
|
|
||||||
id: 'thresholds', // Match field properties
|
|
||||||
name: 'Thresholds',
|
|
||||||
description: 'Manage Thresholds',
|
|
||||||
|
|
||||||
editor: ThresholdsValueEditor,
|
|
||||||
override: ThresholdsOverrideEditor,
|
|
||||||
process: thresholdsOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
// ??
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldApply: field => field.type === FieldType.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mappings: FieldPropertyEditorItem<ValueMapping[], ValueMappingFieldConfigSettings> = {
|
|
||||||
id: 'mappings', // Match field properties
|
|
||||||
name: 'Value mappings',
|
|
||||||
description: 'Manage value mappings',
|
|
||||||
|
|
||||||
editor: ValueMappingsValueEditor,
|
|
||||||
override: ValueMappingsOverrideEditor,
|
|
||||||
process: valueMappingsOverrideProcessor,
|
|
||||||
settings: {
|
|
||||||
// ??
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldApply: field => field.type === FieldType.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const noValue: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
|
||||||
id: 'noValue', // Match field properties
|
|
||||||
name: 'No Value',
|
|
||||||
description: 'What to show when there is no value',
|
|
||||||
|
|
||||||
editor: StringValueEditor,
|
|
||||||
override: StringOverrideEditor,
|
|
||||||
process: stringOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
placeholder: '-',
|
|
||||||
},
|
|
||||||
// ??? any field with no value
|
|
||||||
shouldApply: () => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const links: FieldPropertyEditorItem<DataLink[], StringFieldConfigSettings> = {
|
|
||||||
id: 'links', // Match field properties
|
|
||||||
name: 'DataLinks',
|
|
||||||
description: 'Manage date links',
|
|
||||||
editor: DataLinksValueEditor,
|
|
||||||
override: DataLinksOverrideEditor,
|
|
||||||
process: dataLinksOverrideProcessor,
|
|
||||||
settings: {
|
|
||||||
placeholder: '-',
|
|
||||||
},
|
|
||||||
shouldApply: () => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return [unit, min, max, decimals, title, noValue, thresholds, mappings, links];
|
|
||||||
};
|
|
@ -1,35 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps } from '@grafana/data';
|
|
||||||
import Forms from '../Forms';
|
|
||||||
|
|
||||||
export interface StringFieldConfigSettings {
|
|
||||||
placeholder?: string;
|
|
||||||
maxLength?: number;
|
|
||||||
expandTemplateVars?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const stringOverrideProcessor = (
|
|
||||||
value: any,
|
|
||||||
context: FieldOverrideContext,
|
|
||||||
settings: StringFieldConfigSettings
|
|
||||||
) => {
|
|
||||||
if (settings.expandTemplateVars && context.replaceVariables) {
|
|
||||||
return context.replaceVariables(value, context.field!.config.scopedVars);
|
|
||||||
}
|
|
||||||
return `${value}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StringOverrideEditor: React.FC<FieldOverrideEditorProps<string, StringFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
|
|
||||||
};
|
|
@ -1,59 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
FieldOverrideContext,
|
|
||||||
FieldOverrideEditorProps,
|
|
||||||
FieldConfigEditorProps,
|
|
||||||
ThresholdsConfig,
|
|
||||||
ThresholdsMode,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { ThresholdsEditor } from '../ThresholdsEditorNew/ThresholdsEditor';
|
|
||||||
|
|
||||||
export interface ThresholdsFieldConfigSettings {
|
|
||||||
// Anything?
|
|
||||||
}
|
|
||||||
|
|
||||||
export const thresholdsOverrideProcessor = (
|
|
||||||
value: any,
|
|
||||||
context: FieldOverrideContext,
|
|
||||||
settings: ThresholdsFieldConfigSettings
|
|
||||||
) => {
|
|
||||||
return value as ThresholdsConfig; // !!!! likely not !!!!
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ThresholdsValueEditor extends React.PureComponent<
|
|
||||||
FieldConfigEditorProps<ThresholdsConfig, ThresholdsFieldConfigSettings>
|
|
||||||
> {
|
|
||||||
constructor(props: FieldConfigEditorProps<ThresholdsConfig, ThresholdsFieldConfigSettings>) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { onChange } = this.props;
|
|
||||||
let value = this.props.value;
|
|
||||||
if (!value) {
|
|
||||||
value = {
|
|
||||||
mode: ThresholdsMode.Percentage,
|
|
||||||
|
|
||||||
// Must be sorted by 'value', first value is always -Infinity
|
|
||||||
steps: [
|
|
||||||
// anything?
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ThresholdsEditor thresholds={value} onChange={onChange} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ThresholdsOverrideEditor extends React.PureComponent<
|
|
||||||
FieldOverrideEditorProps<ThresholdsConfig, ThresholdsFieldConfigSettings>
|
|
||||||
> {
|
|
||||||
constructor(props: FieldOverrideEditorProps<ThresholdsConfig, ThresholdsFieldConfigSettings>) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <div>THRESHOLDS OVERRIDE EDITOR {this.props.item.name}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { FieldOverrideEditorProps, FieldConfigEditorProps } from '@grafana/data';
|
|
||||||
import { UnitPicker } from '../UnitPicker/UnitPicker';
|
|
||||||
|
|
||||||
export interface UnitFieldConfigSettings {
|
|
||||||
// ??
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UnitValueEditor: React.FC<FieldConfigEditorProps<string, UnitFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return <UnitPicker value={value} onChange={onChange} useNewForms />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UnitOverrideEditor: React.FC<FieldOverrideEditorProps<string, UnitFieldConfigSettings>> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return <UnitPicker value={value} onChange={onChange} useNewForms />;
|
|
||||||
};
|
|
11
packages/grafana-ui/src/components/OptionsUI/color.tsx
Normal file
11
packages/grafana-ui/src/components/OptionsUI/color.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FieldConfigEditorProps, ColorFieldConfigSettings } from '@grafana/data';
|
||||||
|
import { ColorPicker } from '../ColorPicker/ColorPicker';
|
||||||
|
|
||||||
|
export const ColorValueEditor: React.FC<FieldConfigEditorProps<string, ColorFieldConfigSettings>> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
item,
|
||||||
|
}) => {
|
||||||
|
return <ColorPicker color={value} onChange={onChange} enableNamedColors={!!item.settings.enableNamedColors} />;
|
||||||
|
};
|
18
packages/grafana-ui/src/components/OptionsUI/links.tsx
Normal file
18
packages/grafana-ui/src/components/OptionsUI/links.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FieldConfigEditorProps, DataLink, DataLinksFieldConfigSettings } from '@grafana/data';
|
||||||
|
import { DataLinksInlineEditor } from '../DataLinks/DataLinksInlineEditor/DataLinksInlineEditor';
|
||||||
|
|
||||||
|
export const DataLinksValueEditor: React.FC<FieldConfigEditorProps<DataLink[], DataLinksFieldConfigSettings>> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<DataLinksInlineEditor
|
||||||
|
links={value}
|
||||||
|
onChange={onChange}
|
||||||
|
data={context.data}
|
||||||
|
suggestions={context.getSuggestions ? context.getSuggestions() : []}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
22
packages/grafana-ui/src/components/OptionsUI/mappings.tsx
Normal file
22
packages/grafana-ui/src/components/OptionsUI/mappings.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FieldConfigEditorProps, ValueMapping, ValueMappingFieldConfigSettings } from '@grafana/data';
|
||||||
|
import { ValueMappingsEditor } from '../ValueMappingsEditor/ValueMappingsEditor';
|
||||||
|
|
||||||
|
export class ValueMappingsValueEditor extends React.PureComponent<
|
||||||
|
FieldConfigEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>
|
||||||
|
> {
|
||||||
|
constructor(props: FieldConfigEditorProps<ValueMapping[], ValueMappingFieldConfigSettings>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
let value = this.props.value;
|
||||||
|
if (!value) {
|
||||||
|
value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ValueMappingsEditor valueMappings={value} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
}
|
31
packages/grafana-ui/src/components/OptionsUI/number.tsx
Normal file
31
packages/grafana-ui/src/components/OptionsUI/number.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FieldConfigEditorProps,
|
||||||
|
toIntegerOrUndefined,
|
||||||
|
toFloatOrUndefined,
|
||||||
|
NumberFieldConfigSettings,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import Forms from '../Forms';
|
||||||
|
|
||||||
|
export const NumberValueEditor: React.FC<FieldConfigEditorProps<number, NumberFieldConfigSettings>> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
item,
|
||||||
|
}) => {
|
||||||
|
const { settings } = item;
|
||||||
|
return (
|
||||||
|
<Forms.Input
|
||||||
|
value={isNaN(value) ? '' : value}
|
||||||
|
min={settings.min}
|
||||||
|
max={settings.max}
|
||||||
|
type="number"
|
||||||
|
step={settings.step}
|
||||||
|
placeholder={settings.placeholder}
|
||||||
|
onChange={e => {
|
||||||
|
onChange(
|
||||||
|
settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
11
packages/grafana-ui/src/components/OptionsUI/select.tsx
Normal file
11
packages/grafana-ui/src/components/OptionsUI/select.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FieldConfigEditorProps, SelectFieldConfigSettings } from '@grafana/data';
|
||||||
|
import Forms from '../Forms';
|
||||||
|
|
||||||
|
export function SelectValueEditor<T>({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
item,
|
||||||
|
}: FieldConfigEditorProps<T, SelectFieldConfigSettings<T>>) {
|
||||||
|
return <Forms.Select<T> defaultValue={value} onChange={e => onChange(e.value)} options={item.settings.options} />;
|
||||||
|
}
|
10
packages/grafana-ui/src/components/OptionsUI/string.tsx
Normal file
10
packages/grafana-ui/src/components/OptionsUI/string.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FieldConfigEditorProps, StringFieldConfigSettings } from '@grafana/data';
|
||||||
|
import Forms from '../Forms';
|
||||||
|
|
||||||
|
export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFieldConfigSettings>> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
return <Forms.Input value={value || ''} onChange={e => onChange(e.currentTarget.value)} />;
|
||||||
|
};
|
28
packages/grafana-ui/src/components/OptionsUI/thresholds.tsx
Normal file
28
packages/grafana-ui/src/components/OptionsUI/thresholds.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FieldConfigEditorProps, ThresholdsConfig, ThresholdsMode, ThresholdsFieldConfigSettings } from '@grafana/data';
|
||||||
|
import { ThresholdsEditor } from '../ThresholdsEditorNew/ThresholdsEditor';
|
||||||
|
|
||||||
|
export class ThresholdsValueEditor extends React.PureComponent<
|
||||||
|
FieldConfigEditorProps<ThresholdsConfig, ThresholdsFieldConfigSettings>
|
||||||
|
> {
|
||||||
|
constructor(props: FieldConfigEditorProps<ThresholdsConfig, ThresholdsFieldConfigSettings>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
let value = this.props.value;
|
||||||
|
if (!value) {
|
||||||
|
value = {
|
||||||
|
mode: ThresholdsMode.Percentage,
|
||||||
|
|
||||||
|
// Must be sorted by 'value', first value is always -Infinity
|
||||||
|
steps: [
|
||||||
|
// anything?
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ThresholdsEditor thresholds={value} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
}
|
11
packages/grafana-ui/src/components/OptionsUI/units.tsx
Normal file
11
packages/grafana-ui/src/components/OptionsUI/units.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FieldConfigEditorProps, UnitFieldConfigSettings } from '@grafana/data';
|
||||||
|
import { UnitPicker } from '../UnitPicker/UnitPicker';
|
||||||
|
|
||||||
|
export const UnitValueEditor: React.FC<FieldConfigEditorProps<string, UnitFieldConfigSettings>> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
return <UnitPicker value={value} onChange={onChange} useNewForms />;
|
||||||
|
};
|
@ -129,24 +129,9 @@ export { Drawer } from './Drawer/Drawer';
|
|||||||
export { Slider } from './Slider/Slider';
|
export { Slider } from './Slider/Slider';
|
||||||
|
|
||||||
// TODO: namespace!!
|
// TODO: namespace!!
|
||||||
export {
|
export { StringValueEditor } from './OptionsUI/string';
|
||||||
StringValueEditor,
|
export { NumberValueEditor } from './OptionsUI/number';
|
||||||
StringOverrideEditor,
|
export { SelectValueEditor } from './OptionsUI/select';
|
||||||
stringOverrideProcessor,
|
|
||||||
StringFieldConfigSettings,
|
|
||||||
} from './FieldConfigs/string';
|
|
||||||
export {
|
|
||||||
NumberValueEditor,
|
|
||||||
NumberOverrideEditor,
|
|
||||||
numberOverrideProcessor,
|
|
||||||
NumberFieldConfigSettings,
|
|
||||||
} from './FieldConfigs/number';
|
|
||||||
export {
|
|
||||||
selectOverrideProcessor,
|
|
||||||
SelectValueEditor,
|
|
||||||
SelectOverrideEditor,
|
|
||||||
SelectFieldConfigSettings,
|
|
||||||
} from './FieldConfigs/select';
|
|
||||||
export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle';
|
export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeaderTitle';
|
||||||
|
|
||||||
// Next-gen forms
|
// Next-gen forms
|
||||||
@ -154,5 +139,5 @@ export { default as Forms } from './Forms';
|
|||||||
export * from './Button';
|
export * from './Button';
|
||||||
export { ValuePicker } from './ValuePicker/ValuePicker';
|
export { ValuePicker } from './ValuePicker/ValuePicker';
|
||||||
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
|
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
|
||||||
export { getStandardFieldConfigs } from './FieldConfigs/standardFieldConfigEditors';
|
|
||||||
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
|
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
|
||||||
|
@ -8,3 +8,6 @@ export { default as ansicolor } from './ansicolor';
|
|||||||
|
|
||||||
import * as DOMUtil from './dom'; // includes Element.closest polyfil
|
import * as DOMUtil from './dom'; // includes Element.closest polyfil
|
||||||
export { DOMUtil };
|
export { DOMUtil };
|
||||||
|
|
||||||
|
// Exposes standard editors for registries of optionsUi config and panel options UI
|
||||||
|
export { getStandardFieldConfigs, getStandardOptionEditors } from './standardEditors';
|
||||||
|
269
packages/grafana-ui/src/utils/standardEditors.tsx
Normal file
269
packages/grafana-ui/src/utils/standardEditors.tsx
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
DataLink,
|
||||||
|
dataLinksOverrideProcessor,
|
||||||
|
FieldPropertyEditorItem,
|
||||||
|
FieldType,
|
||||||
|
identityOverrideProcessor,
|
||||||
|
NumberFieldConfigSettings,
|
||||||
|
numberOverrideProcessor,
|
||||||
|
standardEditorsRegistry,
|
||||||
|
StandardEditorsRegistryItem,
|
||||||
|
StringFieldConfigSettings,
|
||||||
|
stringOverrideProcessor,
|
||||||
|
ThresholdsConfig,
|
||||||
|
ThresholdsFieldConfigSettings,
|
||||||
|
thresholdsOverrideProcessor,
|
||||||
|
ValueMapping,
|
||||||
|
ValueMappingFieldConfigSettings,
|
||||||
|
valueMappingsOverrideProcessor,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { NumberValueEditor, Forms, StringValueEditor } from '../components';
|
||||||
|
import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings';
|
||||||
|
import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
|
||||||
|
import { UnitValueEditor } from '../components/OptionsUI/units';
|
||||||
|
import { DataLinksValueEditor } from '../components/OptionsUI/links';
|
||||||
|
import { ColorValueEditor } from '../components/OptionsUI/color';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns collection of common field config properties definitions
|
||||||
|
*/
|
||||||
|
export const getStandardFieldConfigs = () => {
|
||||||
|
const title: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
||||||
|
id: 'title',
|
||||||
|
name: 'Title',
|
||||||
|
description: "Field's title",
|
||||||
|
editor: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
process: stringOverrideProcessor,
|
||||||
|
settings: {
|
||||||
|
placeholder: 'auto',
|
||||||
|
expandTemplateVars: true,
|
||||||
|
},
|
||||||
|
shouldApply: field => field.type !== FieldType.time,
|
||||||
|
};
|
||||||
|
|
||||||
|
const unit: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
||||||
|
id: 'unit',
|
||||||
|
name: 'Unit',
|
||||||
|
description: 'Value units',
|
||||||
|
|
||||||
|
editor: standardEditorsRegistry.get('unit').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('unit').editor as any,
|
||||||
|
process: stringOverrideProcessor,
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
placeholder: 'none',
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldApply: field => field.type === FieldType.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
const min: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
|
||||||
|
id: 'min',
|
||||||
|
name: 'Min',
|
||||||
|
description: 'Minimum expected value',
|
||||||
|
|
||||||
|
editor: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
process: numberOverrideProcessor,
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
placeholder: 'auto',
|
||||||
|
},
|
||||||
|
shouldApply: field => field.type === FieldType.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
const max: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
|
||||||
|
id: 'max',
|
||||||
|
name: 'Max',
|
||||||
|
description: 'Maximum expected value',
|
||||||
|
|
||||||
|
editor: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
process: numberOverrideProcessor,
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
placeholder: 'auto',
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldApply: field => field.type === FieldType.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
const decimals: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
|
||||||
|
id: 'decimals',
|
||||||
|
name: 'Decimals',
|
||||||
|
description: 'Number of decimal to be shown for a value',
|
||||||
|
|
||||||
|
editor: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('number').editor as any,
|
||||||
|
process: numberOverrideProcessor,
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
placeholder: 'auto',
|
||||||
|
min: 0,
|
||||||
|
max: 15,
|
||||||
|
integer: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldApply: field => field.type === FieldType.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
const thresholds: FieldPropertyEditorItem<ThresholdsConfig, ThresholdsFieldConfigSettings> = {
|
||||||
|
id: 'thresholds',
|
||||||
|
name: 'Thresholds',
|
||||||
|
description: 'Manage thresholds',
|
||||||
|
|
||||||
|
editor: standardEditorsRegistry.get('thresholds').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('thresholds').editor as any,
|
||||||
|
process: thresholdsOverrideProcessor,
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
// ??
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldApply: field => field.type === FieldType.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mappings: FieldPropertyEditorItem<ValueMapping[], ValueMappingFieldConfigSettings> = {
|
||||||
|
id: 'mappings',
|
||||||
|
name: 'Value mappings',
|
||||||
|
description: 'Manage value mappings',
|
||||||
|
|
||||||
|
editor: standardEditorsRegistry.get('mappings').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('mappings').editor as any,
|
||||||
|
process: valueMappingsOverrideProcessor,
|
||||||
|
settings: {
|
||||||
|
// ??
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldApply: field => field.type === FieldType.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
const noValue: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
||||||
|
id: 'noValue',
|
||||||
|
name: 'No Value',
|
||||||
|
description: 'What to show when there is no value',
|
||||||
|
|
||||||
|
editor: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
process: stringOverrideProcessor,
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
placeholder: '-',
|
||||||
|
},
|
||||||
|
// ??? any optionsUi with no value
|
||||||
|
shouldApply: () => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const links: FieldPropertyEditorItem<DataLink[], StringFieldConfigSettings> = {
|
||||||
|
id: 'links',
|
||||||
|
name: 'DataLinks',
|
||||||
|
description: 'Manage date links',
|
||||||
|
editor: standardEditorsRegistry.get('links').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('links').editor as any,
|
||||||
|
process: dataLinksOverrideProcessor,
|
||||||
|
settings: {
|
||||||
|
placeholder: '-',
|
||||||
|
},
|
||||||
|
shouldApply: () => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const color: FieldPropertyEditorItem<string, StringFieldConfigSettings> = {
|
||||||
|
id: 'color',
|
||||||
|
name: 'Color',
|
||||||
|
description: 'Customise color',
|
||||||
|
editor: standardEditorsRegistry.get('color').editor as any,
|
||||||
|
override: standardEditorsRegistry.get('color').editor as any,
|
||||||
|
process: identityOverrideProcessor,
|
||||||
|
settings: {
|
||||||
|
placeholder: '-',
|
||||||
|
},
|
||||||
|
shouldApply: () => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [unit, min, max, decimals, title, noValue, thresholds, mappings, links, color];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns collection of standard option editors definitions
|
||||||
|
*/
|
||||||
|
export const getStandardOptionEditors = () => {
|
||||||
|
const number: StandardEditorsRegistryItem<number> = {
|
||||||
|
id: 'number',
|
||||||
|
name: 'Number',
|
||||||
|
description: 'Allows numeric values input',
|
||||||
|
editor: NumberValueEditor as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const text: StandardEditorsRegistryItem<string> = {
|
||||||
|
id: 'text',
|
||||||
|
name: 'Text',
|
||||||
|
description: 'Allows string values input',
|
||||||
|
editor: StringValueEditor as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const boolean: StandardEditorsRegistryItem<boolean> = {
|
||||||
|
id: 'boolean',
|
||||||
|
name: 'Boolean',
|
||||||
|
description: 'Allows boolean values input',
|
||||||
|
editor: props => <Forms.Switch {...props} onChange={e => props.onChange(e.currentTarget.checked)} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
const select: StandardEditorsRegistryItem<any> = {
|
||||||
|
id: 'select',
|
||||||
|
name: 'Select',
|
||||||
|
description: 'Allows option selection',
|
||||||
|
editor: props => (
|
||||||
|
<Forms.Select
|
||||||
|
defaultValue={props.value}
|
||||||
|
onChange={e => props.onChange(e.value)}
|
||||||
|
options={props.item.settings?.options}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const radio: StandardEditorsRegistryItem<any> = {
|
||||||
|
id: 'radio',
|
||||||
|
name: 'Radio',
|
||||||
|
description: 'Allows option selection',
|
||||||
|
editor: props => <Forms.RadioButtonGroup {...props} options={props.item.settings?.options} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
const unit: StandardEditorsRegistryItem<string> = {
|
||||||
|
id: 'unit',
|
||||||
|
name: 'Unit',
|
||||||
|
description: 'Allows unit input',
|
||||||
|
editor: UnitValueEditor as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const thresholds: StandardEditorsRegistryItem<ThresholdsConfig> = {
|
||||||
|
id: 'thresholds',
|
||||||
|
name: 'Thresholds',
|
||||||
|
description: 'Allows defining thresholds',
|
||||||
|
editor: ThresholdsValueEditor as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mappings: StandardEditorsRegistryItem<ValueMapping[]> = {
|
||||||
|
id: 'mappings',
|
||||||
|
name: 'Mappings',
|
||||||
|
description: 'Allows defining value mappings',
|
||||||
|
editor: ValueMappingsValueEditor as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const color: StandardEditorsRegistryItem<string> = {
|
||||||
|
id: 'color',
|
||||||
|
name: 'Color',
|
||||||
|
description: 'Allows color selection',
|
||||||
|
editor: ColorValueEditor as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const links: StandardEditorsRegistryItem<DataLink[]> = {
|
||||||
|
id: 'links',
|
||||||
|
name: 'Links',
|
||||||
|
description: 'Allows defining data links',
|
||||||
|
editor: DataLinksValueEditor as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [text, number, boolean, radio, select, unit, mappings, thresholds, links, color];
|
||||||
|
};
|
@ -25,7 +25,13 @@ import angular from 'angular';
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
// @ts-ignore ignoring this for now, otherwise we would have to extend _ interface with move
|
// @ts-ignore ignoring this for now, otherwise we would have to extend _ interface with move
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AppEvents, setLocale, setMarkdownOptions, standardFieldConfigEditorRegistry } from '@grafana/data';
|
import {
|
||||||
|
AppEvents,
|
||||||
|
setLocale,
|
||||||
|
setMarkdownOptions,
|
||||||
|
standardEditorsRegistry,
|
||||||
|
standardFieldConfigEditorRegistry,
|
||||||
|
} from '@grafana/data';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { addClassIfNoOverlayScrollbar } from 'app/core/utils/scrollbar';
|
import { addClassIfNoOverlayScrollbar } from 'app/core/utils/scrollbar';
|
||||||
import { checkBrowserCompatibility } from 'app/core/utils/browser';
|
import { checkBrowserCompatibility } from 'app/core/utils/browser';
|
||||||
@ -40,7 +46,7 @@ import { PerformanceBackend } from './core/services/echo/backends/PerformanceBac
|
|||||||
|
|
||||||
import 'app/routes/GrafanaCtrl';
|
import 'app/routes/GrafanaCtrl';
|
||||||
import 'app/features/all';
|
import 'app/features/all';
|
||||||
import { getStandardFieldConfigs } from '@grafana/ui';
|
import { getStandardFieldConfigs, getStandardOptionEditors } from '@grafana/ui';
|
||||||
import { getDefaultVariableAdapters, variableAdapters } from './features/variables/adapters';
|
import { getDefaultVariableAdapters, variableAdapters } from './features/variables/adapters';
|
||||||
|
|
||||||
// add move to lodash for backward compatabiltiy
|
// add move to lodash for backward compatabiltiy
|
||||||
@ -84,6 +90,8 @@ export class GrafanaApp {
|
|||||||
setLocale(config.bootData.user.locale);
|
setLocale(config.bootData.user.locale);
|
||||||
|
|
||||||
setMarkdownOptions({ sanitize: !config.disableSanitizeHtml });
|
setMarkdownOptions({ sanitize: !config.disableSanitizeHtml });
|
||||||
|
|
||||||
|
standardEditorsRegistry.setInit(getStandardOptionEditors);
|
||||||
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
||||||
variableAdapters.setInit(getDefaultVariableAdapters);
|
variableAdapters.setInit(getDefaultVariableAdapters);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConf
|
|||||||
import { AngularPanelOptions } from './AngularPanelOptions';
|
import { AngularPanelOptions } from './AngularPanelOptions';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import { GeneralPanelOptions } from './GeneralPanelOptions';
|
import { GeneralPanelOptions } from './GeneralPanelOptions';
|
||||||
|
import { PanelOptionsEditor } from './PanelOptionsEditor';
|
||||||
|
|
||||||
export const OptionsPaneContent: React.FC<{
|
export const OptionsPaneContent: React.FC<{
|
||||||
plugin?: PanelPlugin;
|
plugin?: PanelPlugin;
|
||||||
@ -64,8 +65,9 @@ export const OptionsPaneContent: React.FC<{
|
|||||||
|
|
||||||
const renderCustomPanelSettings = useCallback(
|
const renderCustomPanelSettings = useCallback(
|
||||||
(plugin: PanelPlugin) => {
|
(plugin: PanelPlugin) => {
|
||||||
|
const editors = [];
|
||||||
if (plugin.editor && panel) {
|
if (plugin.editor && panel) {
|
||||||
return (
|
editors.push(
|
||||||
<div className={styles.legacyOptions}>
|
<div className={styles.legacyOptions}>
|
||||||
<plugin.editor
|
<plugin.editor
|
||||||
data={data}
|
data={data}
|
||||||
@ -78,6 +80,17 @@ export const OptionsPaneContent: React.FC<{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When editor created declaratively
|
||||||
|
if (plugin.optionEditors && panel) {
|
||||||
|
editors.push(
|
||||||
|
<PanelOptionsEditor options={panel.getOptions()} onChange={onPanelOptionsChanged} plugin={plugin} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editors.length > 0) {
|
||||||
|
return <>{editors}</>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.legacyOptions}>
|
<div className={styles.legacyOptions}>
|
||||||
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
|
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { PanelPlugin } from '@grafana/data';
|
||||||
|
import { Forms } from '@grafana/ui';
|
||||||
|
|
||||||
|
interface PanelOptionsEditorProps<TOptions> {
|
||||||
|
plugin: PanelPlugin;
|
||||||
|
options: TOptions;
|
||||||
|
onChange: (options: TOptions) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plugin, options, onChange }) => {
|
||||||
|
const optionEditors = useMemo(() => plugin.optionEditors, [plugin]);
|
||||||
|
|
||||||
|
const onOptionChange = (key: string, value: any) => {
|
||||||
|
onChange({
|
||||||
|
...options,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{optionEditors.list().map(e => {
|
||||||
|
return (
|
||||||
|
<Forms.Field label={e.name} description={e.description}>
|
||||||
|
<e.editor value={options[e.id]} onChange={value => onOptionChange(e.id, value)} item={e} />
|
||||||
|
</Forms.Field>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,54 +0,0 @@
|
|||||||
import { FieldPropertyEditorItem, Registry, FieldConfigEditorRegistry } from '@grafana/data';
|
|
||||||
import {
|
|
||||||
NumberValueEditor,
|
|
||||||
NumberOverrideEditor,
|
|
||||||
numberOverrideProcessor,
|
|
||||||
NumberFieldConfigSettings,
|
|
||||||
selectOverrideProcessor,
|
|
||||||
SelectValueEditor,
|
|
||||||
SelectOverrideEditor,
|
|
||||||
SelectFieldConfigSettings,
|
|
||||||
} from '@grafana/ui';
|
|
||||||
|
|
||||||
export const tableFieldRegistry: FieldConfigEditorRegistry = new Registry<FieldPropertyEditorItem>(() => {
|
|
||||||
const columWidth: FieldPropertyEditorItem<number, NumberFieldConfigSettings> = {
|
|
||||||
id: 'width', // Match field properties
|
|
||||||
name: 'Column width',
|
|
||||||
description: 'column width (for table)',
|
|
||||||
|
|
||||||
editor: NumberValueEditor,
|
|
||||||
override: NumberOverrideEditor,
|
|
||||||
process: numberOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
placeholder: 'auto',
|
|
||||||
min: 20,
|
|
||||||
max: 300,
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldApply: () => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const cellDisplayMode: FieldPropertyEditorItem<string, SelectFieldConfigSettings<string>> = {
|
|
||||||
id: 'displayMode', // Match field properties
|
|
||||||
name: 'Cell display mode',
|
|
||||||
description: 'Color value, background, show as gauge, etc',
|
|
||||||
|
|
||||||
editor: SelectValueEditor,
|
|
||||||
override: SelectOverrideEditor,
|
|
||||||
process: selectOverrideProcessor,
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
options: [
|
|
||||||
{ value: 'auto', label: 'Auto' },
|
|
||||||
{ value: 'color-background', label: 'Color background' },
|
|
||||||
{ value: 'gradient-gauge', label: 'Gradient gauge' },
|
|
||||||
{ value: 'lcd-gauge', label: 'LCD gauge' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldApply: () => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return [columWidth, cellDisplayMode];
|
|
||||||
});
|
|
@ -1,11 +1,40 @@
|
|||||||
import { PanelPlugin } from '@grafana/data';
|
import { PanelPlugin } from '@grafana/data';
|
||||||
|
|
||||||
import { TablePanelEditor } from './TablePanelEditor';
|
|
||||||
import { TablePanel } from './TablePanel';
|
import { TablePanel } from './TablePanel';
|
||||||
import { tableFieldRegistry } from './custom';
|
|
||||||
import { Options, defaults } from './types';
|
import { Options, defaults } from './types';
|
||||||
|
|
||||||
export const plugin = new PanelPlugin<Options>(TablePanel)
|
export const plugin = new PanelPlugin<Options>(TablePanel)
|
||||||
.setDefaults(defaults)
|
.setDefaults(defaults)
|
||||||
.setCustomFieldConfigs(tableFieldRegistry)
|
.setCustomFieldConfigEditor(builder => {
|
||||||
.setEditor(TablePanelEditor);
|
builder
|
||||||
|
.addNumberInput({
|
||||||
|
id: 'width',
|
||||||
|
name: 'Column width',
|
||||||
|
description: 'column width (for table)',
|
||||||
|
settings: {
|
||||||
|
placeholder: 'auto',
|
||||||
|
min: 20,
|
||||||
|
max: 300,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addSelect({
|
||||||
|
id: 'displayMode',
|
||||||
|
name: 'Cell display mode',
|
||||||
|
description: 'Color value, background, show as gauge, etc',
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
options: [
|
||||||
|
{ value: 'auto', label: 'Auto' },
|
||||||
|
{ value: 'color-background', label: 'Color background' },
|
||||||
|
{ value: 'gradient-gauge', label: 'Gradient gauge' },
|
||||||
|
{ value: 'lcd-gauge', label: 'LCD gauge' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.setOptionsEditor(builder => {
|
||||||
|
builder.addBooleanSwitch({
|
||||||
|
id: 'showHeader',
|
||||||
|
name: 'Show header',
|
||||||
|
description: "To display table's header or not to display",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user