mirror of https://github.com/grafana/grafana.git
				
				
				
			FieldOverrides: Move FieldConfigSource from fieldOptions to PanelModel.fieldConfig (#22600)
* Apply field overrides in PanelChrome * Move applyFieldOverrides to panel query runner * Review updates * Make sure overrides are applied back on souce panel when exiting the new edit mode * TS ignores in est * Make field display work in viz repeater * Review updates * Review and test updates * Change the way overrides and trransformations are retrieved in PQR * Add fieldConfig property to PanelModel * Dashboard migration v1 * Use field config when exiting new panel edit mode * Gauge - use fieldConfig from panel model * FieldDisplayOptions - don's extend FieldConfigSource * Fix fieldDisplay ts * StatPanel updated * Stat panel defaults applied * Table2 panel options update * React graph updates * BarGauge updated * PieChart, Gauge, BarGauge and Stat updates * PieChart - remove field config defaults from options * FieldDisplayEditor - remove unused methos * PanelModel - remove debugger * Remove fieldConfig from field options when migrating dashboard * Update data links migrations * Update fieldDisaplay tests to respect new fieldConfig * Update dashboard schema version in snapshots * Fix BarGaugePanel test * Rebase fixes * Add onFieldConfigChange to PanelProps type * Update shared single stat migration * Pass PanelModel instead of options only for panel type change handler [breaking] * Renames * Don't mutate panel options * Migrations update * Remove obsolete snap * Minor updates after review * Fix null checks * Temporarily (until we decide to switch to new pane edit) bring back old aditors * Temporarily rename ValueMappingEditor and MappingRow to Legacy* * Migrations update * Updae setFieldConfigDefaults API * Update the way field config defaults are applied * Use standard field config for gauge, bar gauge and stat panels * refactoring * Revert dashboard fieldOptions migrations as those are handled by single stat migrator * Fix ts in tests * Strict null fix and some minor fixes Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
		
							parent
							
								
									d99a67075f
								
							
						
					
					
						commit
						bf7579d984
					
				|  | @ -28,7 +28,9 @@ describe('FieldDisplay', () => { | |||
|     const options = createDisplayOptions({ | ||||
|       fieldOptions: { | ||||
|         calcs: [ReducerID.first], | ||||
|         override: {}, | ||||
|       }, | ||||
|       fieldConfig: { | ||||
|         overrides: [], | ||||
|         defaults: { | ||||
|           title: '$__cell_0 * $__field_name * $__series_name', | ||||
|         }, | ||||
|  | @ -42,8 +44,6 @@ describe('FieldDisplay', () => { | |||
|     const options = createDisplayOptions({ | ||||
|       fieldOptions: { | ||||
|         calcs: [ReducerID.last], | ||||
|         override: {}, | ||||
|         defaults: {}, | ||||
|       }, | ||||
|     }); | ||||
|     const display = getFieldDisplayValues(options); | ||||
|  | @ -56,8 +56,6 @@ describe('FieldDisplay', () => { | |||
|         values: true, //
 | ||||
|         limit: 1000, | ||||
|         calcs: [], | ||||
|         override: {}, | ||||
|         defaults: {}, | ||||
|       }, | ||||
|     }); | ||||
|     const display = getFieldDisplayValues(options); | ||||
|  | @ -70,8 +68,6 @@ describe('FieldDisplay', () => { | |||
|         values: true, //
 | ||||
|         limit: 2, | ||||
|         calcs: [], | ||||
|         override: {}, | ||||
|         defaults: {}, | ||||
|       }, | ||||
|     }); | ||||
|     const display = getFieldDisplayValues(options); | ||||
|  | @ -101,7 +97,7 @@ describe('FieldDisplay', () => { | |||
| 
 | ||||
|   it('Should return field thresholds when there is no data', () => { | ||||
|     const options = createEmptyDisplayOptions({ | ||||
|       fieldOptions: { | ||||
|       fieldConfig: { | ||||
|         defaults: { | ||||
|           thresholds: { steps: [{ color: '#F2495C', value: 50 }] }, | ||||
|         }, | ||||
|  | @ -123,7 +119,7 @@ describe('FieldDisplay', () => { | |||
|   it('Should return field mapped value when there is no data', () => { | ||||
|     const mapEmptyToText = '0'; | ||||
|     const options = createEmptyDisplayOptions({ | ||||
|       fieldOptions: { | ||||
|       fieldConfig: { | ||||
|         defaults: { | ||||
|           mappings: [ | ||||
|             { | ||||
|  | @ -146,8 +142,8 @@ describe('FieldDisplay', () => { | |||
|   it('Should always return display numeric 0 when there is no data', () => { | ||||
|     const mapEmptyToText = '0'; | ||||
|     const options = createEmptyDisplayOptions({ | ||||
|       fieldOptions: { | ||||
|         override: { | ||||
|       fieldConfig: { | ||||
|         overrides: { | ||||
|           mappings: [ | ||||
|             { | ||||
|               id: 1, | ||||
|  | @ -241,7 +237,7 @@ function createEmptyDisplayOptions(extend = {}): GetFieldDisplayValuesOptions { | |||
|   }); | ||||
| } | ||||
| 
 | ||||
| function createDisplayOptions(extend = {}): GetFieldDisplayValuesOptions { | ||||
| function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}): GetFieldDisplayValuesOptions { | ||||
|   const options: GetFieldDisplayValuesOptions = { | ||||
|     data: [ | ||||
|       toDataFrame({ | ||||
|  | @ -258,8 +254,10 @@ function createDisplayOptions(extend = {}): GetFieldDisplayValuesOptions { | |||
|     }, | ||||
|     fieldOptions: { | ||||
|       calcs: [], | ||||
|       defaults: {}, | ||||
|     }, | ||||
|     fieldConfig: { | ||||
|       overrides: [], | ||||
|       defaults: {}, | ||||
|     }, | ||||
|     theme: {} as GrafanaTheme, | ||||
|   }; | ||||
|  |  | |||
|  | @ -19,7 +19,8 @@ import { ReducerID, reduceField } from '../transformations/fieldReducer'; | |||
| import { ScopedVars } from '../types/ScopedVars'; | ||||
| import { getTimeField } from '../dataframe/processDataFrame'; | ||||
| 
 | ||||
| export interface FieldDisplayOptions extends FieldConfigSource { | ||||
| // export interface FieldDisplayOptions extends FieldConfigSource {
 | ||||
| export interface FieldDisplayOptions { | ||||
|   values?: boolean; // If true show each row value
 | ||||
|   limit?: number; // if showing all values limit
 | ||||
|   calcs: string[]; // when !values, pick one value for the whole field
 | ||||
|  | @ -57,6 +58,7 @@ function getTitleTemplate(title: string | undefined, stats: string[], data?: Dat | |||
|   if (fieldCount > 1 || !parts.length) { | ||||
|     parts.push('${' + VAR_FIELD_NAME + '}'); | ||||
|   } | ||||
| 
 | ||||
|   return parts.join(' '); | ||||
| } | ||||
| 
 | ||||
|  | @ -75,6 +77,7 @@ export interface FieldDisplay { | |||
| export interface GetFieldDisplayValuesOptions { | ||||
|   data?: DataFrame[]; | ||||
|   fieldOptions: FieldDisplayOptions; | ||||
|   fieldConfig: FieldConfigSource; | ||||
|   replaceVariables: InterpolateFunction; | ||||
|   sparkline?: boolean; // Calculate the sparkline
 | ||||
|   theme: GrafanaTheme; | ||||
|  | @ -84,7 +87,7 @@ export interface GetFieldDisplayValuesOptions { | |||
| export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25; | ||||
| 
 | ||||
| export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): FieldDisplay[] => { | ||||
|   const { replaceVariables, fieldOptions } = options; | ||||
|   const { replaceVariables, fieldOptions, fieldConfig } = options; | ||||
|   const calcs = fieldOptions.calcs.length ? fieldOptions.calcs : [ReducerID.last]; | ||||
| 
 | ||||
|   const values: FieldDisplay[] = []; | ||||
|  | @ -94,7 +97,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi | |||
|     const data = options.data; | ||||
|     let hitLimit = false; | ||||
|     const limit = fieldOptions.limit ? fieldOptions.limit : DEFAULT_FIELD_DISPLAY_VALUES_LIMIT; | ||||
|     const defaultTitle = getTitleTemplate(fieldOptions.defaults.title, calcs, data); | ||||
|     const defaultTitle = getTitleTemplate(fieldConfig.defaults.title, calcs, data); | ||||
|     const scopedVars: ScopedVars = {}; | ||||
| 
 | ||||
|     for (let s = 0; s < data.length && !hitLimit; s++) { | ||||
|  | @ -194,7 +197,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi | |||
| 
 | ||||
|   if (values.length === 0) { | ||||
|     values.push(createNoValuesFieldDisplay(options)); | ||||
|   } else if (values.length === 1 && !fieldOptions.defaults.title) { | ||||
|   } else if (values.length === 1 && !fieldConfig.defaults.title) { | ||||
|     // Don't show title for single item
 | ||||
|     values[0].display.title = undefined; | ||||
|   } | ||||
|  | @ -237,8 +240,8 @@ export function getDisplayValueAlignmentFactors(values: FieldDisplay[]): Display | |||
| 
 | ||||
| function createNoValuesFieldDisplay(options: GetFieldDisplayValuesOptions): FieldDisplay { | ||||
|   const displayName = 'No data'; | ||||
|   const { fieldOptions } = options; | ||||
|   const { defaults } = fieldOptions; | ||||
|   const { fieldConfig } = options; | ||||
|   const { defaults } = fieldConfig; | ||||
| 
 | ||||
|   const displayProcessor = getDisplayProcessor({ | ||||
|     field: { | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import { ScopedVars } from './ScopedVars'; | |||
| import { LoadingState } from './data'; | ||||
| import { DataFrame } from './dataFrame'; | ||||
| import { AbsoluteTimeRange, TimeRange, TimeZone } from './time'; | ||||
| import { FieldConfigEditorRegistry } from './fieldOverrides'; | ||||
| import { FieldConfigEditorRegistry, FieldConfigSource } from './fieldOverrides'; | ||||
| 
 | ||||
| export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; | ||||
| 
 | ||||
|  | @ -54,6 +54,10 @@ export interface PanelProps<T = any> { | |||
|   timeZone: TimeZone; | ||||
|   options: T; | ||||
|   onOptionsChange: (options: T) => void; | ||||
|   /** Panel fields configuration */ | ||||
|   fieldConfig: FieldConfigSource; | ||||
|   /** Enables panel field config manipulation */ | ||||
|   onFieldConfigChange: (config: FieldConfigSource) => void; | ||||
|   renderCounter: number; | ||||
|   transparent: boolean; | ||||
|   width: number; | ||||
|  | @ -70,11 +74,23 @@ export interface PanelEditorProps<T = any> { | |||
|     callback?: () => void | ||||
|   ) => void; | ||||
|   data: PanelData; | ||||
| 
 | ||||
|   /** | ||||
|    * Panel fields configuration - temporart solution | ||||
|    * TODO[FieldConfig]: Remove when we switch old editor to new | ||||
|    */ | ||||
|   fieldConfig: FieldConfigSource; | ||||
|   /** | ||||
|    * Enables panel field config manipulation | ||||
|    * TODO[FieldConfig]: Remove when we switch old editor to new | ||||
|    */ | ||||
|   onFieldConfigChange: (config: FieldConfigSource) => void; | ||||
| } | ||||
| 
 | ||||
| export interface PanelModel<TOptions = any> { | ||||
|   id: number; | ||||
|   options: TOptions; | ||||
|   fieldConfig: FieldConfigSource; | ||||
|   pluginVersion?: string; | ||||
|   scopedVars?: ScopedVars; | ||||
| } | ||||
|  | @ -98,6 +114,10 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta> | |||
|   editor?: ComponentClass<PanelEditorProps<TOptions>>; | ||||
|   customFieldConfigs?: FieldConfigEditorRegistry; | ||||
|   defaults?: TOptions; | ||||
|   fieldConfigDefaults?: FieldConfigSource = { | ||||
|     defaults: {}, | ||||
|     overrides: [], | ||||
|   }; | ||||
|   onPanelMigration?: PanelMigrationHandler<TOptions>; | ||||
|   onPanelTypeChanged?: PanelTypeChangedHandler<TOptions>; | ||||
|   noPadding?: boolean; | ||||
|  | @ -155,6 +175,19 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta> | |||
|     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 PanelMenuItem { | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ import { | |||
|   InterpolateFunction, | ||||
|   GrafanaTheme, | ||||
|   FieldMatcherID, | ||||
|   FieldDisplayOptions, | ||||
|   MutableDataFrame, | ||||
|   DataFrame, | ||||
|   toDataFrame, | ||||
|  | @ -82,7 +81,7 @@ describe('FieldOverrides', () => { | |||
|   it('will apply field overrides', () => { | ||||
|     const data = applyFieldOverrides({ | ||||
|       data: [f0], // the frame
 | ||||
|       fieldOptions: src as FieldDisplayOptions, // defaults + overrides
 | ||||
|       fieldOptions: src as FieldConfigSource, // defaults + overrides
 | ||||
|       replaceVariables: (undefined as any) as InterpolateFunction, | ||||
|       theme: (undefined as any) as GrafanaTheme, | ||||
|     })[0]; | ||||
|  | @ -108,7 +107,7 @@ describe('FieldOverrides', () => { | |||
|   it('will apply set min/max when asked', () => { | ||||
|     const data = applyFieldOverrides({ | ||||
|       data: [f0], // the frame
 | ||||
|       fieldOptions: src as FieldDisplayOptions, // defaults + overrides
 | ||||
|       fieldOptions: src as FieldConfigSource, // defaults + overrides
 | ||||
|       replaceVariables: (undefined as any) as InterpolateFunction, | ||||
|       theme: (undefined as any) as GrafanaTheme, | ||||
|       autoMinMax: true, | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps, ValueMapping } from '@grafana/data'; | ||||
| import { ValueMappingsEditor } from '..'; | ||||
| import { LegacyValueMappingsEditor } from '..'; | ||||
| 
 | ||||
| export interface ValueMappingFieldConfigSettings {} | ||||
| 
 | ||||
|  | @ -27,7 +27,7 @@ export class ValueMappingsValueEditor extends React.PureComponent< | |||
|       value = []; | ||||
|     } | ||||
| 
 | ||||
|     return <ValueMappingsEditor valueMappings={value} onChange={onChange} />; | ||||
|     return <LegacyValueMappingsEditor valueMappings={value} onChange={onChange} />; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ import { | |||
|   toNumberString, | ||||
|   toIntegerOrUndefined, | ||||
|   SelectableValue, | ||||
|   FieldConfig, | ||||
| } from '@grafana/data'; | ||||
| 
 | ||||
| const showOptions: Array<SelectableValue<boolean>> = [ | ||||
|  | @ -47,10 +46,6 @@ export class FieldDisplayEditor extends PureComponent<Props> { | |||
|     this.props.onChange({ ...this.props.value, calcs }); | ||||
|   }; | ||||
| 
 | ||||
|   onDefaultsChange = (value: FieldConfig) => { | ||||
|     this.props.onChange({ ...this.props.value, defaults: value }); | ||||
|   }; | ||||
| 
 | ||||
|   onLimitChange = (event: ChangeEvent<HTMLInputElement>) => { | ||||
|     this.props.onChange({ | ||||
|       ...this.props.value, | ||||
|  |  | |||
|  | @ -34,7 +34,48 @@ describe('sharedSingleStatMigrationHandler', () => { | |||
|       type: 'bargauge', | ||||
|     }; | ||||
| 
 | ||||
|     expect(sharedSingleStatMigrationHandler(panel as any)).toMatchSnapshot(); | ||||
|     sharedSingleStatMigrationHandler(panel as any); | ||||
|     expect((panel as any).fieldConfig).toMatchInlineSnapshot(` | ||||
|       Object { | ||||
|         "defaults": Object { | ||||
|           "color": Object { | ||||
|             "mode": "thresholds", | ||||
|           }, | ||||
|           "decimals": 5, | ||||
|           "mappings": Array [ | ||||
|             Object { | ||||
|               "text": "OK", | ||||
|               "type": 1, | ||||
|               "value": "1", | ||||
|             }, | ||||
|           ], | ||||
|           "max": 100, | ||||
|           "min": 10, | ||||
|           "thresholds": Object { | ||||
|             "mode": "absolute", | ||||
|             "steps": Array [ | ||||
|               Object { | ||||
|                 "color": "green", | ||||
|                 "index": 0, | ||||
|                 "value": -Infinity, | ||||
|               }, | ||||
|               Object { | ||||
|                 "color": "orange", | ||||
|                 "index": 1, | ||||
|                 "value": 40, | ||||
|               }, | ||||
|               Object { | ||||
|                 "color": "red", | ||||
|                 "index": 2, | ||||
|                 "value": 80, | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|           "unit": "watt", | ||||
|         }, | ||||
|         "overrides": Array [], | ||||
|       } | ||||
|     `);
 | ||||
|   }); | ||||
| 
 | ||||
|   it('move thresholds to scale', () => { | ||||
|  | @ -64,7 +105,17 @@ describe('sharedSingleStatMigrationHandler', () => { | |||
|       }, | ||||
|     }; | ||||
| 
 | ||||
|     expect(sharedSingleStatMigrationHandler(panel as any)).toMatchSnapshot(); | ||||
|     sharedSingleStatMigrationHandler(panel as any); | ||||
| 
 | ||||
|     expect((panel as any).fieldConfig).toMatchInlineSnapshot(` | ||||
|       Object { | ||||
|         "defaults": Object { | ||||
|           "mappings": undefined, | ||||
|           "thresholds": undefined, | ||||
|         }, | ||||
|         "overrides": Array [], | ||||
|       } | ||||
|     `);
 | ||||
|   }); | ||||
| 
 | ||||
|   it('Remove unused `overrides` option', () => { | ||||
|  | @ -90,6 +141,17 @@ describe('sharedSingleStatMigrationHandler', () => { | |||
|       type: 'bargauge', | ||||
|     }; | ||||
| 
 | ||||
|     expect(sharedSingleStatMigrationHandler(panel as any)).toMatchSnapshot(); | ||||
|     sharedSingleStatMigrationHandler(panel as any); | ||||
|     expect((panel as any).fieldConfig).toMatchInlineSnapshot(` | ||||
|       Object { | ||||
|         "defaults": Object { | ||||
|           "mappings": undefined, | ||||
|           "max": 100, | ||||
|           "min": 0, | ||||
|           "thresholds": undefined, | ||||
|         }, | ||||
|         "overrides": Array [], | ||||
|       } | ||||
|     `);
 | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ import { | |||
|   VizOrientation, | ||||
|   PanelModel, | ||||
|   FieldDisplayOptions, | ||||
|   ConfigOverrideRule, | ||||
|   ThresholdsMode, | ||||
|   ThresholdsConfig, | ||||
|   validateFieldConfig, | ||||
|  | @ -32,20 +31,38 @@ export function sharedSingleStatPanelChangedHandler( | |||
|   prevOptions: any | ||||
| ) { | ||||
|   let options = panel.options; | ||||
| 
 | ||||
|   panel.fieldConfig = panel.fieldConfig || { | ||||
|     defaults: {}, | ||||
|     overrides: [], | ||||
|   }; | ||||
| 
 | ||||
|   // Migrating from angular singlestat
 | ||||
|   if (prevPluginId === 'singlestat' && prevOptions.angular) { | ||||
|     return migrateFromAngularSinglestat(panel, prevOptions); | ||||
|   } | ||||
| 
 | ||||
|   for (const k of optionsToKeep) { | ||||
|     if (prevOptions.hasOwnProperty(k)) { | ||||
|       options[k] = cloneDeep(prevOptions[k]); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return options; | ||||
| } | ||||
| 
 | ||||
| function migrateFromAngularSinglestat(panel: PanelModel<Partial<SingleStatBaseOptions>> | any, prevOptions: any) { | ||||
|   const prevPanel = prevOptions.angular; | ||||
|   const reducer = fieldReducers.getIfExists(prevPanel.valueName); | ||||
|     options = { | ||||
|   const options = { | ||||
|     fieldOptions: { | ||||
|         defaults: {} as FieldConfig, | ||||
|         overrides: [] as ConfigOverrideRule[], | ||||
|       calcs: [reducer ? reducer.id : ReducerID.mean], | ||||
|     }, | ||||
|     orientation: VizOrientation.Horizontal, | ||||
|   }; | ||||
| 
 | ||||
|     const defaults = options.fieldOptions.defaults; | ||||
|   const defaults: FieldConfig = {}; | ||||
| 
 | ||||
|   if (prevPanel.format) { | ||||
|     defaults.unit = prevPanel.format; | ||||
|   } | ||||
|  | @ -91,14 +108,9 @@ export function sharedSingleStatPanelChangedHandler( | |||
|     defaults.min = prevPanel.gauge.minValue; | ||||
|     defaults.max = prevPanel.gauge.maxValue; | ||||
|   } | ||||
|     return options; | ||||
|   } | ||||
| 
 | ||||
|   for (const k of optionsToKeep) { | ||||
|     if (prevOptions.hasOwnProperty(k)) { | ||||
|       options[k] = cloneDeep(prevOptions[k]); | ||||
|     } | ||||
|   } | ||||
|   panel.fieldConfig.defaults = defaults; | ||||
| 
 | ||||
|   return options; | ||||
| } | ||||
| 
 | ||||
|  | @ -162,6 +174,22 @@ export function sharedSingleStatMigrationHandler(panel: PanelModel<SingleStatBas | |||
|     validateFieldConfig(defaults); | ||||
|   } | ||||
| 
 | ||||
|   if (previousVersion < 7.0) { | ||||
|     panel.fieldConfig = panel.fieldConfig || { defaults: {}, overrides: [] }; | ||||
|     panel.fieldConfig = { | ||||
|       defaults: | ||||
|         options.fieldOptions && options.fieldOptions.defaults | ||||
|           ? { ...panel.fieldConfig.defaults, ...options.fieldOptions.defaults } | ||||
|           : panel.fieldConfig.defaults, | ||||
|       overrides: | ||||
|         options.fieldOptions && options.fieldOptions.overrides | ||||
|           ? [...panel.fieldConfig.overrides, ...options.fieldOptions.overrides] | ||||
|           : panel.fieldConfig.overrides, | ||||
|     }; | ||||
|     delete options.fieldOptions.defaults; | ||||
|     delete options.fieldOptions.overrides; | ||||
|   } | ||||
| 
 | ||||
|   return options as SingleStatBaseOptions; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,75 +0,0 @@ | |||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
| 
 | ||||
| exports[`sharedSingleStatMigrationHandler Remove unused \`overrides\` option 1`] = ` | ||||
| Object { | ||||
|   "fieldOptions": Object { | ||||
|     "decimals": 5, | ||||
|     "defaults": Object { | ||||
|       "mappings": undefined, | ||||
|       "max": 100, | ||||
|       "min": 0, | ||||
|       "thresholds": undefined, | ||||
|     }, | ||||
|     "overrides": Array [], | ||||
|     "stat": "last", | ||||
|     "unit": "watt", | ||||
|   }, | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`sharedSingleStatMigrationHandler from old valueOptions model without pluginVersion 1`] = ` | ||||
| Object { | ||||
|   "fieldOptions": Object { | ||||
|     "calcs": Array [ | ||||
|       "last", | ||||
|     ], | ||||
|     "defaults": Object { | ||||
|       "color": Object { | ||||
|         "mode": "thresholds", | ||||
|       }, | ||||
|       "decimals": 5, | ||||
|       "mappings": Array [ | ||||
|         Object { | ||||
|           "text": "OK", | ||||
|           "type": 1, | ||||
|           "value": "1", | ||||
|         }, | ||||
|       ], | ||||
|       "max": 100, | ||||
|       "min": 10, | ||||
|       "thresholds": Object { | ||||
|         "mode": "absolute", | ||||
|         "steps": Array [ | ||||
|           Object { | ||||
|             "color": "green", | ||||
|             "index": 0, | ||||
|             "value": -Infinity, | ||||
|           }, | ||||
|           Object { | ||||
|             "color": "orange", | ||||
|             "index": 1, | ||||
|             "value": 40, | ||||
|           }, | ||||
|           Object { | ||||
|             "color": "red", | ||||
|             "index": 2, | ||||
|             "value": 80, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       "unit": "watt", | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`sharedSingleStatMigrationHandler move thresholds to scale 1`] = ` | ||||
| Object { | ||||
|   "fieldOptions": Object { | ||||
|     "defaults": Object { | ||||
|       "mappings": undefined, | ||||
|       "thresholds": undefined, | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| `; | ||||
|  | @ -58,7 +58,7 @@ export class StatsPicker extends PureComponent<Props> { | |||
|     if (isArray(item)) { | ||||
|       onChange(item.map(v => v.value)); | ||||
|     } else { | ||||
|       onChange(item.value ? [item.value] : []); | ||||
|       onChange(item && item.value ? [item.value] : []); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ const mappingOptions = [ | |||
|   { value: MappingType.RangeToText, label: 'Range' }, | ||||
| ]; | ||||
| 
 | ||||
| export default class MappingRow extends PureComponent<Props, State> { | ||||
| export default class LegacyMappingRow extends PureComponent<Props, State> { | ||||
|   constructor(props: Props) { | ||||
|     super(props); | ||||
| 
 | ||||
|  | @ -0,0 +1,10 @@ | |||
| import React from 'react'; | ||||
| import { storiesOf } from '@storybook/react'; | ||||
| import { action } from '@storybook/addon-actions'; | ||||
| import { LegacyValueMappingsEditor } from './LegacyValueMappingsEditor'; | ||||
| 
 | ||||
| const ValueMappingsEditorStories = storiesOf('Panel/LegacyValueMappingsEditor', module); | ||||
| 
 | ||||
| ValueMappingsEditorStories.add('default', () => { | ||||
|   return <LegacyValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />; | ||||
| }); | ||||
|  | @ -1,7 +1,7 @@ | |||
| import React from 'react'; | ||||
| import { shallow } from 'enzyme'; | ||||
| 
 | ||||
| import { ValueMappingsEditor, Props } from './ValueMappingsEditor'; | ||||
| import { LegacyValueMappingsEditor, Props } from './LegacyValueMappingsEditor'; | ||||
| import { MappingType } from '@grafana/data'; | ||||
| 
 | ||||
| const setup = (propOverrides?: object) => { | ||||
|  | @ -15,9 +15,9 @@ const setup = (propOverrides?: object) => { | |||
| 
 | ||||
|   Object.assign(props, propOverrides); | ||||
| 
 | ||||
|   const wrapper = shallow(<ValueMappingsEditor {...props} />); | ||||
|   const wrapper = shallow(<LegacyValueMappingsEditor {...props} />); | ||||
| 
 | ||||
|   const instance = wrapper.instance() as ValueMappingsEditor; | ||||
|   const instance = wrapper.instance() as LegacyValueMappingsEditor; | ||||
| 
 | ||||
|   return { | ||||
|     instance, | ||||
|  | @ -1,6 +1,6 @@ | |||
| import React, { PureComponent } from 'react'; | ||||
| 
 | ||||
| import MappingRow from './MappingRow'; | ||||
| import LegacyMappingRow from './LegacyMappingRow'; | ||||
| import { MappingType, ValueMapping } from '@grafana/data'; | ||||
| import { Button } from '../Button/Button'; | ||||
| import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; | ||||
|  | @ -15,7 +15,7 @@ interface State { | |||
|   nextIdToAdd: number; | ||||
| } | ||||
| 
 | ||||
| export class ValueMappingsEditor extends PureComponent<Props, State> { | ||||
| export class LegacyValueMappingsEditor extends PureComponent<Props, State> { | ||||
|   constructor(props: Props) { | ||||
|     super(props); | ||||
| 
 | ||||
|  | @ -91,7 +91,7 @@ export class ValueMappingsEditor extends PureComponent<Props, State> { | |||
|         <div> | ||||
|           {valueMappings.length > 0 && | ||||
|             valueMappings.map((valueMapping, index) => ( | ||||
|               <MappingRow | ||||
|               <LegacyMappingRow | ||||
|                 key={`${valueMapping.text}-${index}`} | ||||
|                 valueMapping={valueMapping} | ||||
|                 updateValueMapping={this.updateGauge} | ||||
|  | @ -1,10 +0,0 @@ | |||
| import React from 'react'; | ||||
| import { storiesOf } from '@storybook/react'; | ||||
| import { action } from '@storybook/addon-actions'; | ||||
| import { ValueMappingsEditor } from './ValueMappingsEditor'; | ||||
| 
 | ||||
| const ValueMappingsEditorStories = storiesOf('Panel/ValueMappingsEditor', module); | ||||
| 
 | ||||
| ValueMappingsEditorStories.add('default', () => { | ||||
|   return <ValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />; | ||||
| }); | ||||
|  | @ -5,7 +5,7 @@ exports[`Render should render component 1`] = ` | |||
|   title="Value mappings" | ||||
| > | ||||
|   <div> | ||||
|     <MappingRow | ||||
|     <LegacyMappingRow | ||||
|       key="Ok-0" | ||||
|       removeValueMapping={[Function]} | ||||
|       updateValueMapping={[Function]} | ||||
|  | @ -19,7 +19,7 @@ exports[`Render should render component 1`] = ` | |||
|         } | ||||
|       } | ||||
|     /> | ||||
|     <MappingRow | ||||
|     <LegacyMappingRow | ||||
|       key="Meh-1" | ||||
|       removeValueMapping={[Function]} | ||||
|       updateValueMapping={[Function]} | ||||
|  | @ -28,7 +28,7 @@ export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; | |||
| export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover'; | ||||
| export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; | ||||
| export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid'; | ||||
| export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor'; | ||||
| export { LegacyValueMappingsEditor } from './ValueMappingsEditor/LegacyValueMappingsEditor'; | ||||
| export { Switch } from './Switch/Switch'; | ||||
| export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult'; | ||||
| export { PieChart, PieChartType } from './PieChart/PieChart'; | ||||
|  |  | |||
|  | @ -89,33 +89,25 @@ export class PanelEditorUnconnected extends PureComponent<Props> { | |||
|     this.props.updateLocation({ query: { tab: tab.id }, partial: true }); | ||||
|   }; | ||||
| 
 | ||||
|   onFieldConfigsChange = (fieldOptions: FieldConfigSource) => { | ||||
|     // NOTE: for now, assume this is from 'fieldOptions' -- TODO? put on panel model directly?
 | ||||
|   onFieldConfigChange = (config: FieldConfigSource) => { | ||||
|     const { panel } = this.props; | ||||
|     const options = panel.getOptions(); | ||||
|     panel.updateOptions({ | ||||
|       ...options, | ||||
|       fieldOptions, // Assume it is from shared singlestat -- TODO own property?
 | ||||
| 
 | ||||
|     panel.updateFieldConfig({ | ||||
|       ...config, | ||||
|     }); | ||||
|     this.forceUpdate(); | ||||
|   }; | ||||
| 
 | ||||
|   renderFieldOptions(plugin: PanelPlugin) { | ||||
|     const { panel, data } = this.props; | ||||
|     const { fieldConfig } = panel; | ||||
| 
 | ||||
|     const fieldOptions = panel.options['fieldOptions'] as FieldConfigSource; | ||||
| 
 | ||||
|     if (!fieldOptions) { | ||||
|     if (!fieldConfig) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <FieldConfigEditor | ||||
|         config={fieldOptions} | ||||
|         plugin={plugin} | ||||
|         onChange={this.onFieldConfigsChange} | ||||
|         data={data.series} | ||||
|       /> | ||||
|       <FieldConfigEditor config={fieldConfig} plugin={plugin} onChange={this.onFieldConfigChange} data={data.series} /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  | @ -130,7 +122,13 @@ export class PanelEditorUnconnected extends PureComponent<Props> { | |||
|     if (plugin.editor && panel) { | ||||
|       return ( | ||||
|         <div style={{ marginTop: '10px' }}> | ||||
|           <plugin.editor data={data} options={panel.getOptions()} onOptionsChange={this.onPanelOptionsChanged} /> | ||||
|           <plugin.editor | ||||
|             data={data} | ||||
|             options={panel.getOptions()} | ||||
|             onOptionsChange={this.onPanelOptionsChanged} | ||||
|             fieldConfig={panel.getFieldConfig()} | ||||
|             onFieldConfigChange={this.onFieldConfigChange} | ||||
|           /> | ||||
|         </div> | ||||
|       ); | ||||
|     } | ||||
|  |  | |||
|  | @ -89,7 +89,7 @@ describe('ShareModal', () => { | |||
|         }, | ||||
|       }; | ||||
|       ctx.mount({ | ||||
|         panel: { id: 22, options: {} }, | ||||
|         panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -101,7 +101,7 @@ describe('ShareModal', () => { | |||
|     it('should generate render url', () => { | ||||
|       mockLocationHref('http://dashboards.grafana.com/d/abcdefghi/my-dash'); | ||||
|       ctx.mount({ | ||||
|         panel: { id: 22, options: {} }, | ||||
|         panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||
|       }); | ||||
| 
 | ||||
|       const state = ctx.wrapper?.state(); | ||||
|  | @ -113,7 +113,7 @@ describe('ShareModal', () => { | |||
|     it('should generate render url for scripted dashboard', () => { | ||||
|       mockLocationHref('http://dashboards.grafana.com/dashboard/script/my-dash.js'); | ||||
|       ctx.mount({ | ||||
|         panel: { id: 22, options: {} }, | ||||
|         panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||
|       }); | ||||
| 
 | ||||
|       const state = ctx.wrapper?.state(); | ||||
|  | @ -142,7 +142,7 @@ describe('ShareModal', () => { | |||
|     it('should remove fullscreen from image url when is first param in querystring and modeSharePanel is true', () => { | ||||
|       mockLocationHref('http://server/#!/test?fullscreen&edit'); | ||||
|       ctx.mount({ | ||||
|         panel: { id: 1, options: {} }, | ||||
|         panel: { id: 1, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||
|       }); | ||||
| 
 | ||||
|       const state = ctx.wrapper?.state(); | ||||
|  | @ -153,7 +153,7 @@ describe('ShareModal', () => { | |||
|     it('should remove edit from image url when is first param in querystring and modeSharePanel is true', () => { | ||||
|       mockLocationHref('http://server/#!/test?edit&fullscreen'); | ||||
|       ctx.mount({ | ||||
|         panel: { id: 1, options: {} }, | ||||
|         panel: { id: 1, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||
|       }); | ||||
| 
 | ||||
|       const state = ctx.wrapper?.state(); | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ import { | |||
|   PanelEvents, | ||||
|   PanelData, | ||||
|   PanelPlugin, | ||||
|   FieldConfigSource, | ||||
| } from '@grafana/data'; | ||||
| 
 | ||||
| const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; | ||||
|  | @ -217,6 +218,10 @@ export class PanelChrome extends PureComponent<Props, State> { | |||
|     this.props.panel.updateOptions(options); | ||||
|   }; | ||||
| 
 | ||||
|   onFieldConfigChange = (config: FieldConfigSource) => { | ||||
|     this.props.panel.updateFieldConfig(config); | ||||
|   }; | ||||
| 
 | ||||
|   onPanelError = (message: string) => { | ||||
|     if (this.state.errorMessage !== message) { | ||||
|       this.setState({ errorMessage: message }); | ||||
|  | @ -281,12 +286,14 @@ export class PanelChrome extends PureComponent<Props, State> { | |||
|             timeRange={timeRange} | ||||
|             timeZone={this.props.dashboard.getTimezone()} | ||||
|             options={panelOptions} | ||||
|             fieldConfig={panel.fieldConfig} | ||||
|             transparent={panel.transparent} | ||||
|             width={panelWidth} | ||||
|             height={innerPanelHeight} | ||||
|             renderCounter={renderCounter} | ||||
|             replaceVariables={panel.replaceVariables} | ||||
|             onOptionsChange={this.onOptionsChange} | ||||
|             onFieldConfigChange={this.onFieldConfigChange} | ||||
|             onChangeTimeRange={this.onChangeTimeRange} | ||||
|           /> | ||||
|         </div> | ||||
|  |  | |||
|  | @ -15,7 +15,14 @@ import { PanelModel, DashboardModel } from '../state'; | |||
| import { VizPickerSearch } from './VizPickerSearch'; | ||||
| import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; | ||||
| import { Unsubscribable } from 'rxjs'; | ||||
| import { PanelPlugin, PanelPluginMeta, PanelData, LoadingState, DefaultTimeRange } from '@grafana/data'; | ||||
| import { | ||||
|   PanelPlugin, | ||||
|   PanelPluginMeta, | ||||
|   PanelData, | ||||
|   LoadingState, | ||||
|   DefaultTimeRange, | ||||
|   FieldConfigSource, | ||||
| } from '@grafana/data'; | ||||
| 
 | ||||
| interface Props { | ||||
|   panel: PanelModel; | ||||
|  | @ -59,6 +66,11 @@ export class VisualizationTab extends PureComponent<Props, State> { | |||
|     return panel.getOptions(); | ||||
|   }; | ||||
| 
 | ||||
|   getReactPanelFieldConfig = () => { | ||||
|     const { panel } = this.props; | ||||
|     return panel.getFieldConfig(); | ||||
|   }; | ||||
| 
 | ||||
|   renderPanelOptions() { | ||||
|     const { plugin, dashboard, panel } = this.props; | ||||
| 
 | ||||
|  | @ -72,6 +84,10 @@ export class VisualizationTab extends PureComponent<Props, State> { | |||
|           data={this.state.data} | ||||
|           options={this.getReactPanelOptions()} | ||||
|           onOptionsChange={this.onPanelOptionsChanged} | ||||
|           // TODO[FieldConfig]: Remove when we switch old editor to new
 | ||||
|           fieldConfig={this.getReactPanelFieldConfig()} | ||||
|           // TODO[FieldConfig]: Remove when we switch old editor to new
 | ||||
|           onFieldConfigChange={this.onPanelFieldConfigChange} | ||||
|         /> | ||||
|       ); | ||||
|     } | ||||
|  | @ -103,6 +119,12 @@ export class VisualizationTab extends PureComponent<Props, State> { | |||
|     this.forceUpdate(callback); | ||||
|   }; | ||||
| 
 | ||||
|   // TODO[FieldConfig]: Remove when we switch old editor to new
 | ||||
|   onPanelFieldConfigChange = (config: FieldConfigSource, callback?: () => void) => { | ||||
|     this.props.panel.updateFieldConfig(config); | ||||
|     this.forceUpdate(callback); | ||||
|   }; | ||||
| 
 | ||||
|   onOpenVizPicker = () => { | ||||
|     this.setState({ isVizPickerOpen: true, scrollTop: 0 }); | ||||
|   }; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { PanelModel } from './PanelModel'; | ||||
| import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; | ||||
| import { PanelProps } from '@grafana/data'; | ||||
| import { ConfigOverrideRule, PanelProps } from '@grafana/data'; | ||||
| import { ComponentClass } from 'react'; | ||||
| 
 | ||||
| class TablePanelCtrl {} | ||||
|  | @ -53,9 +53,31 @@ describe('PanelModel', () => { | |||
|         showColumns: true, | ||||
|         targets: [{ refId: 'A' }, { noRefId: true }], | ||||
|         options: persistedOptionsMock, | ||||
|         fieldConfig: { | ||||
|           defaults: { | ||||
|             unit: 'mpg', | ||||
|           }, | ||||
|           overrides: [ | ||||
|             { | ||||
|               matcher: { | ||||
|                 id: '1', | ||||
|                 options: {}, | ||||
|               }, | ||||
|               properties: [], | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       }; | ||||
| 
 | ||||
|       model = new PanelModel(modelJson); | ||||
|       const overrideMock: ConfigOverrideRule = { | ||||
|         matcher: { | ||||
|           id: '2', | ||||
|           options: {}, | ||||
|         }, | ||||
|         properties: [], | ||||
|       }; | ||||
| 
 | ||||
|       const panelPlugin = getPanelPlugin( | ||||
|         { | ||||
|           id: 'table', | ||||
|  | @ -64,6 +86,13 @@ describe('PanelModel', () => { | |||
|         TablePanelCtrl // angular
 | ||||
|       ); | ||||
|       panelPlugin.setDefaults(defaultOptionsMock); | ||||
|       panelPlugin.setFieldConfigDefaults({ | ||||
|         defaults: { | ||||
|           unit: 'flop', | ||||
|           decimals: 2, | ||||
|         }, | ||||
|         overrides: [overrideMock], | ||||
|       }); | ||||
|       model.pluginLoaded(panelPlugin); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -79,6 +108,17 @@ 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'); | ||||
|       // default decimals are aplied
 | ||||
|       expect(model.getFieldOverrideOptions().fieldOptions.defaults.decimals).toBe(2); | ||||
|     }); | ||||
| 
 | ||||
|     it('should set model props on instance', () => { | ||||
|       expect(model.showColumns).toBe(true); | ||||
|     }); | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ import { | |||
|   PanelEvents, | ||||
|   PanelPlugin, | ||||
|   ScopedVars, | ||||
|   FieldConfigSource, | ||||
| } from '@grafana/data'; | ||||
| import { EDIT_PANEL_ID } from 'app/core/constants'; | ||||
| 
 | ||||
|  | @ -81,6 +82,7 @@ const mustKeepProps: { [str: string]: boolean } = { | |||
|   pluginVersion: true, | ||||
|   queryRunner: true, | ||||
|   transformations: true, | ||||
|   fieldConfig: true, | ||||
| }; | ||||
| 
 | ||||
| const defaults: any = { | ||||
|  | @ -121,6 +123,7 @@ export class PanelModel implements DataConfigSource { | |||
|   options: { | ||||
|     [key: string]: any; | ||||
|   }; | ||||
|   fieldConfig: FieldConfigSource; | ||||
| 
 | ||||
|   maxDataPoints?: number; | ||||
|   interval?: string; | ||||
|  | @ -177,9 +180,19 @@ export class PanelModel implements DataConfigSource { | |||
|   getOptions() { | ||||
|     return this.options; | ||||
|   } | ||||
|   getFieldConfig() { | ||||
|     return this.fieldConfig; | ||||
|   } | ||||
| 
 | ||||
|   updateOptions(options: object) { | ||||
|     this.options = options; | ||||
| 
 | ||||
|     this.render(); | ||||
|   } | ||||
| 
 | ||||
|   updateFieldConfig(config: FieldConfigSource) { | ||||
|     this.fieldConfig = config; | ||||
| 
 | ||||
|     this.resendLastResult(); | ||||
|     this.render(); | ||||
|   } | ||||
|  | @ -273,6 +286,23 @@ export class PanelModel implements DataConfigSource { | |||
|         return srcValue; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     this.fieldConfig = { | ||||
|       defaults: _.mergeWith( | ||||
|         {}, | ||||
|         plugin.fieldConfigDefaults.defaults, | ||||
|         this.fieldConfig ? this.fieldConfig.defaults : {}, | ||||
|         (objValue: any, srcValue: any): any => { | ||||
|           if (_.isArray(srcValue)) { | ||||
|             return srcValue; | ||||
|           } | ||||
|         } | ||||
|       ), | ||||
|       overrides: [ | ||||
|         ...plugin.fieldConfigDefaults.overrides, | ||||
|         ...(this.fieldConfig && this.fieldConfig.overrides ? this.fieldConfig.overrides : []), | ||||
|       ], | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   pluginLoaded(plugin: PanelPlugin) { | ||||
|  | @ -382,7 +412,7 @@ export class PanelModel implements DataConfigSource { | |||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       fieldOptions: this.options.fieldOptions, | ||||
|       fieldOptions: this.fieldConfig, | ||||
|       replaceVariables: this.replaceVariables, | ||||
|       custom: this.plugin.customFieldConfigs, | ||||
|       theme: config.theme, | ||||
|  |  | |||
|  | @ -43,8 +43,43 @@ describe('BarGauge Panel Migrations', () => { | |||
|       targets: [], | ||||
|       title: 'Usage', | ||||
|       type: 'bargauge', | ||||
|     } as PanelModel; | ||||
|     } as Omit<PanelModel, 'fieldConfig'>; | ||||
| 
 | ||||
|     expect(barGaugePanelMigrationHandler(panel)).toMatchSnapshot(); | ||||
|     expect(barGaugePanelMigrationHandler(panel as PanelModel)).toMatchSnapshot(); | ||||
|     expect((panel as any).fieldConfig).toMatchInlineSnapshot(` | ||||
|       Object { | ||||
|         "defaults": Object { | ||||
|           "color": Object { | ||||
|             "mode": "thresholds", | ||||
|           }, | ||||
|           "decimals": null, | ||||
|           "mappings": Array [], | ||||
|           "max": 33, | ||||
|           "min": -22, | ||||
|           "thresholds": Object { | ||||
|             "mode": "absolute", | ||||
|             "steps": Array [ | ||||
|               Object { | ||||
|                 "color": "green", | ||||
|                 "index": 0, | ||||
|                 "value": -Infinity, | ||||
|               }, | ||||
|               Object { | ||||
|                 "color": "orange", | ||||
|                 "index": 1, | ||||
|                 "value": 40, | ||||
|               }, | ||||
|               Object { | ||||
|                 "color": "red", | ||||
|                 "index": 2, | ||||
|                 "value": 80, | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|           "unit": "watt", | ||||
|         }, | ||||
|         "overrides": Array [], | ||||
|       } | ||||
|     `);
 | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import { | |||
|   PanelProps, | ||||
|   LoadingState, | ||||
|   dateTime, | ||||
|   FieldConfigSource, | ||||
|   toDataFrame, | ||||
| } from '@grafana/data'; | ||||
| import { BarGaugeDisplayMode } from '@grafana/ui'; | ||||
|  | @ -66,13 +67,15 @@ function createBarGaugePanelWithData(data: PanelData): ReactWrapper<PanelProps<B | |||
|     displayMode: BarGaugeDisplayMode.Lcd, | ||||
|     fieldOptions: { | ||||
|       calcs: ['mean'], | ||||
|       defaults: {}, | ||||
|       values: false, | ||||
|       overrides: [], | ||||
|     }, | ||||
|     orientation: VizOrientation.Horizontal, | ||||
|     showUnfilled: true, | ||||
|   }; | ||||
|   const fieldConfig: FieldConfigSource = { | ||||
|     defaults: {}, | ||||
|     overrides: [], | ||||
|   }; | ||||
| 
 | ||||
|   return mount<BarGaugePanel>( | ||||
|     <BarGaugePanel | ||||
|  | @ -81,6 +84,8 @@ function createBarGaugePanelWithData(data: PanelData): ReactWrapper<PanelProps<B | |||
|       timeRange={timeRange} | ||||
|       timeZone={'utc'} | ||||
|       options={options} | ||||
|       fieldConfig={fieldConfig} | ||||
|       onFieldConfigChange={() => {}} | ||||
|       onOptionsChange={() => {}} | ||||
|       onChangeTimeRange={() => {}} | ||||
|       replaceVariables={s => s} | ||||
|  |  | |||
|  | @ -51,9 +51,10 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> { | |||
|   }; | ||||
| 
 | ||||
|   getValues = (): FieldDisplay[] => { | ||||
|     const { data, options, replaceVariables } = this.props; | ||||
|     const { data, options, replaceVariables, fieldConfig } = this.props; | ||||
|     return getFieldDisplayValues({ | ||||
|       ...options, | ||||
|       fieldConfig, | ||||
|       fieldOptions: options.fieldOptions, | ||||
|       replaceVariables, | ||||
|       theme: config.theme, | ||||
|       data: data.series, | ||||
|  |  | |||
|  | @ -2,83 +2,94 @@ | |||
| import React, { PureComponent } from 'react'; | ||||
| 
 | ||||
| import { | ||||
|   ThresholdsEditor, | ||||
|   ValueMappingsEditor, | ||||
|   PanelOptionsGrid, | ||||
|   FieldDisplayEditor, | ||||
|   FieldPropertiesEditor, | ||||
|   PanelOptionsGroup, | ||||
|   FormLabel, | ||||
|   Select, | ||||
|   DataLinksEditor, | ||||
|   Switch, | ||||
|   FieldPropertiesEditor, | ||||
|   ThresholdsEditor, | ||||
|   LegacyValueMappingsEditor, | ||||
|   DataLinksEditor, | ||||
| } from '@grafana/ui'; | ||||
| import { | ||||
|   DataLink, | ||||
|   FieldConfig, | ||||
|   FieldDisplayOptions, | ||||
|   PanelEditorProps, | ||||
|   ThresholdsConfig, | ||||
|   ValueMapping, | ||||
|   FieldDisplayOptions, | ||||
|   FieldConfig, | ||||
|   DataLink, | ||||
|   PanelEditorProps, | ||||
| } from '@grafana/data'; | ||||
| import { BarGaugeOptions, displayModes } from './types'; | ||||
| import { orientationOptions } from '../gauge/types'; | ||||
| import { | ||||
|   getDataLinksVariableSuggestions, | ||||
|   getCalculationValueDataLinksVariableSuggestions, | ||||
| } from 'app/features/panel/panellinks/link_srv'; | ||||
|   getDataLinksVariableSuggestions, | ||||
| } from '../../../features/panel/panellinks/link_srv'; | ||||
| 
 | ||||
| export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> { | ||||
|   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||
|     const current = this.props.options.fieldOptions.defaults; | ||||
|     this.onDefaultsChange({ | ||||
|       ...current, | ||||
|       thresholds, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||
|     const current = this.props.options.fieldOptions.defaults; | ||||
|     this.onDefaultsChange({ | ||||
|       ...current, | ||||
|       mappings, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) => | ||||
|     this.props.onOptionsChange({ | ||||
|       ...this.props.options, | ||||
|       fieldOptions, | ||||
|     }); | ||||
| 
 | ||||
|   onDefaultsChange = (field: FieldConfig) => { | ||||
|     this.onDisplayOptionsChanged({ | ||||
|       ...this.props.options.fieldOptions, | ||||
|       defaults: field, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); | ||||
|   onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value }); | ||||
|   onToggleShowUnfilled = () => { | ||||
|     this.props.onOptionsChange({ ...this.props.options, showUnfilled: !this.props.options.showUnfilled }); | ||||
|   }; | ||||
| 
 | ||||
|   onDataLinksChanged = (links: DataLink[]) => { | ||||
|     this.onDefaultsChange({ | ||||
|       ...this.props.options.fieldOptions.defaults, | ||||
|       links, | ||||
|   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         thresholds, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
|   render() { | ||||
|     const { options } = this.props; | ||||
|     const { fieldOptions } = options; | ||||
|     const { defaults } = fieldOptions; | ||||
| 
 | ||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         mappings, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onDataLinksChanged = (links: DataLink[]) => { | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         links, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onDefaultsChange = (field: FieldConfig, event?: React.SyntheticEvent<HTMLElement>, callback?: () => void) => { | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...this.props.fieldConfig, | ||||
|       defaults: field, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { options, fieldConfig } = this.props; | ||||
|     const { fieldOptions } = options; | ||||
|     const { defaults } = fieldConfig; | ||||
| 
 | ||||
|     const labelWidth = 6; | ||||
|     const suggestions = fieldOptions.values | ||||
|       ? getDataLinksVariableSuggestions(this.props.data.series) | ||||
|       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); | ||||
|     const labelWidth = 6; | ||||
| 
 | ||||
|     return ( | ||||
|       <> | ||||
|  | @ -105,6 +116,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge | |||
|                 value={displayModes.find(item => item.value === options.displayMode)} | ||||
|               /> | ||||
|             </div> | ||||
|             <> | ||||
|               {options.displayMode !== 'lcd' && ( | ||||
|                 <Switch | ||||
|                   label="Unfilled" | ||||
|  | @ -113,6 +125,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge | |||
|                   onChange={this.onToggleShowUnfilled} | ||||
|                 /> | ||||
|               )} | ||||
|             </> | ||||
|           </PanelOptionsGroup> | ||||
|           <PanelOptionsGroup title="Field"> | ||||
|             <FieldPropertiesEditor | ||||
|  | @ -126,7 +139,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge | |||
|           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> | ||||
|         </PanelOptionsGrid> | ||||
| 
 | ||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
|         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
| 
 | ||||
|         <PanelOptionsGroup title="Data links"> | ||||
|           <DataLinksEditor | ||||
|  |  | |||
|  | @ -7,37 +7,6 @@ Object { | |||
|     "calcs": Array [ | ||||
|       "mean", | ||||
|     ], | ||||
|     "defaults": Object { | ||||
|       "color": Object { | ||||
|         "mode": "thresholds", | ||||
|       }, | ||||
|       "decimals": null, | ||||
|       "mappings": Array [], | ||||
|       "max": 33, | ||||
|       "min": -22, | ||||
|       "thresholds": Object { | ||||
|         "mode": "absolute", | ||||
|         "steps": Array [ | ||||
|           Object { | ||||
|             "color": "green", | ||||
|             "index": 0, | ||||
|             "value": -Infinity, | ||||
|           }, | ||||
|           Object { | ||||
|             "color": "orange", | ||||
|             "index": 1, | ||||
|             "value": 40, | ||||
|           }, | ||||
|           Object { | ||||
|             "color": "red", | ||||
|             "index": 2, | ||||
|             "value": 80, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       "unit": "watt", | ||||
|     }, | ||||
|     "overrides": Array [], | ||||
|     "thresholds": Array [ | ||||
|       Object { | ||||
|         "color": "green", | ||||
|  |  | |||
|  | @ -3,10 +3,12 @@ import { PanelPlugin } from '@grafana/data'; | |||
| import { BarGaugePanel } from './BarGaugePanel'; | ||||
| import { BarGaugePanelEditor } from './BarGaugePanelEditor'; | ||||
| import { BarGaugeOptions, defaults } from './types'; | ||||
| import { standardFieldConfig } from '../stat/types'; | ||||
| import { barGaugePanelMigrationHandler } from './BarGaugeMigrations'; | ||||
| 
 | ||||
| export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel) | ||||
|   .setDefaults(defaults) | ||||
|   .setFieldConfigDefaults(standardFieldConfig) | ||||
|   .setEditor(BarGaugePanelEditor) | ||||
|   .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) | ||||
|   .setMigrationHandler(barGaugePanelMigrationHandler); | ||||
|  |  | |||
|  | @ -75,9 +75,68 @@ describe('Gauge Panel Migrations', () => { | |||
|       timeShift: null, | ||||
|       title: 'Panel Title', | ||||
|       type: 'gauge', | ||||
|     } as PanelModel; | ||||
|     } as Omit<PanelModel, 'fieldConfig'>; | ||||
| 
 | ||||
|     expect(gaugePanelMigrationHandler(panel)).toMatchSnapshot(); | ||||
|     const result = gaugePanelMigrationHandler(panel as PanelModel); | ||||
|     expect(result).toMatchSnapshot(); | ||||
| 
 | ||||
|     // Ignored due to the API change
 | ||||
|     //@ts-ignore
 | ||||
|     expect(result.fieldOptions.defaults).toBeUndefined(); | ||||
|     // Ignored due to the API change
 | ||||
|     //@ts-ignore
 | ||||
|     expect(result.fieldOptions.overrides).toBeUndefined(); | ||||
| 
 | ||||
|     expect((panel as PanelModel).fieldConfig).toMatchInlineSnapshot(` | ||||
|       Object { | ||||
|         "defaults": Object { | ||||
|           "color": Object { | ||||
|             "mode": "thresholds", | ||||
|           }, | ||||
|           "decimals": 3, | ||||
|           "mappings": Array [ | ||||
|             Object { | ||||
|               "from": "50", | ||||
|               "id": 1, | ||||
|               "operator": "", | ||||
|               "text": "BIG", | ||||
|               "to": "1000", | ||||
|               "type": 2, | ||||
|               "value": "", | ||||
|             }, | ||||
|           ], | ||||
|           "max": "50", | ||||
|           "min": "-50", | ||||
|           "thresholds": Object { | ||||
|             "mode": "absolute", | ||||
|             "steps": Array [ | ||||
|               Object { | ||||
|                 "color": "green", | ||||
|                 "index": 0, | ||||
|                 "value": -Infinity, | ||||
|               }, | ||||
|               Object { | ||||
|                 "color": "#EAB839", | ||||
|                 "index": 1, | ||||
|                 "value": -25, | ||||
|               }, | ||||
|               Object { | ||||
|                 "color": "#6ED0E0", | ||||
|                 "index": 2, | ||||
|                 "value": 0, | ||||
|               }, | ||||
|               Object { | ||||
|                 "color": "red", | ||||
|                 "index": 3, | ||||
|                 "value": 25, | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|           "unit": "accMS2", | ||||
|         }, | ||||
|         "overrides": Array [], | ||||
|       } | ||||
|     `);
 | ||||
|   }); | ||||
| 
 | ||||
|   it('change from angular singlestat to gauge', () => { | ||||
|  | @ -95,11 +154,12 @@ describe('Gauge Panel Migrations', () => { | |||
|       }, | ||||
|     }; | ||||
| 
 | ||||
|     const newOptions = gaugePanelChangedHandler({} as any, 'singlestat', old); | ||||
|     expect(newOptions.fieldOptions.defaults.unit).toBe('ms'); | ||||
|     expect(newOptions.fieldOptions.defaults.min).toBe(-10); | ||||
|     expect(newOptions.fieldOptions.defaults.max).toBe(150); | ||||
|     expect(newOptions.fieldOptions.defaults.decimals).toBe(7); | ||||
|     const panel = {} as PanelModel; | ||||
|     const newOptions = gaugePanelChangedHandler(panel, 'singlestat', old); | ||||
|     expect(panel.fieldConfig.defaults.unit).toBe('ms'); | ||||
|     expect(panel.fieldConfig.defaults.min).toBe(-10); | ||||
|     expect(panel.fieldConfig.defaults.max).toBe(150); | ||||
|     expect(panel.fieldConfig.defaults.decimals).toBe(7); | ||||
|     expect(newOptions.showThresholdMarkers).toBe(true); | ||||
|     expect(newOptions.showThresholdLabels).toBe(true); | ||||
|   }); | ||||
|  | @ -116,10 +176,10 @@ describe('Gauge Panel Migrations', () => { | |||
|         }, | ||||
|       }, | ||||
|     }; | ||||
| 
 | ||||
|     const newOptions = gaugePanelChangedHandler({} as any, 'singlestat', old); | ||||
|     expect(newOptions.fieldOptions.defaults.unit).toBe('ms'); | ||||
|     expect(newOptions.fieldOptions.defaults.min).toBe(undefined); | ||||
|     expect(newOptions.fieldOptions.defaults.max).toBe(undefined); | ||||
|     const panel = {} as PanelModel; | ||||
|     gaugePanelChangedHandler(panel, 'singlestat', old); | ||||
|     expect(panel.fieldConfig.defaults.unit).toBe('ms'); | ||||
|     expect(panel.fieldConfig.defaults.min).toBe(undefined); | ||||
|     expect(panel.fieldConfig.defaults.max).toBe(undefined); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -40,8 +40,9 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> { | |||
|   }; | ||||
| 
 | ||||
|   getValues = (): FieldDisplay[] => { | ||||
|     const { data, options, replaceVariables } = this.props; | ||||
|     const { data, options, replaceVariables, fieldConfig } = this.props; | ||||
|     return getFieldDisplayValues({ | ||||
|       fieldConfig, | ||||
|       fieldOptions: options.fieldOptions, | ||||
|       replaceVariables, | ||||
|       theme: config.theme, | ||||
|  |  | |||
|  | @ -1,29 +1,29 @@ | |||
| // Libraries
 | ||||
| import React, { PureComponent } from 'react'; | ||||
| import { | ||||
|   ThresholdsEditor, | ||||
|   PanelOptionsGrid, | ||||
|   ValueMappingsEditor, | ||||
|   FieldDisplayEditor, | ||||
|   FieldPropertiesEditor, | ||||
|   Switch, | ||||
|   PanelOptionsGroup, | ||||
|   FieldPropertiesEditor, | ||||
|   ThresholdsEditor, | ||||
|   LegacyValueMappingsEditor, | ||||
|   DataLinksEditor, | ||||
| } from '@grafana/ui'; | ||||
| import { | ||||
|   PanelEditorProps, | ||||
|   FieldDisplayOptions, | ||||
|   ThresholdsConfig, | ||||
|   ValueMapping, | ||||
|   FieldConfig, | ||||
|   DataLink, | ||||
|   FieldConfig, | ||||
|   ValueMapping, | ||||
| } from '@grafana/data'; | ||||
| 
 | ||||
| import { GaugeOptions } from './types'; | ||||
| import { | ||||
|   getCalculationValueDataLinksVariableSuggestions, | ||||
|   getDataLinksVariableSuggestions, | ||||
| } from 'app/features/panel/panellinks/link_srv'; | ||||
| } from '../../../features/panel/panellinks/link_srv'; | ||||
| 
 | ||||
| export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> { | ||||
|   labelWidth = 6; | ||||
|  | @ -37,27 +37,11 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption | |||
|       showThresholdMarkers: !this.props.options.showThresholdMarkers, | ||||
|     }); | ||||
| 
 | ||||
|   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||
|     const current = this.props.options.fieldOptions.defaults; | ||||
|     this.onDefaultsChange({ | ||||
|       ...current, | ||||
|       thresholds, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||
|     const current = this.props.options.fieldOptions.defaults; | ||||
|     this.onDefaultsChange({ | ||||
|       ...current, | ||||
|       mappings, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onDisplayOptionsChanged = ( | ||||
|     fieldOptions: FieldDisplayOptions, | ||||
|     event?: React.SyntheticEvent<HTMLElement>, | ||||
|     callback?: () => void | ||||
|   ) => | ||||
|   ) => { | ||||
|     this.props.onOptionsChange( | ||||
|       { | ||||
|         ...this.props.options, | ||||
|  | @ -65,38 +49,57 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption | |||
|       }, | ||||
|       callback | ||||
|     ); | ||||
| 
 | ||||
|   onDefaultsChange = (field: FieldConfig, event?: React.SyntheticEvent<HTMLElement>, callback?: () => void) => { | ||||
|     this.onDisplayOptionsChanged( | ||||
|       { | ||||
|         ...this.props.options.fieldOptions, | ||||
|         defaults: field, | ||||
|       }, | ||||
|       event, | ||||
|       callback | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   onDataLinksChanged = (links: DataLink[], callback?: () => void) => { | ||||
|     this.onDefaultsChange( | ||||
|       { | ||||
|         ...this.props.options.fieldOptions.defaults, | ||||
|   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         thresholds, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         mappings, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onDataLinksChanged = (links: DataLink[]) => { | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         links, | ||||
|       }, | ||||
|       undefined, | ||||
|       callback | ||||
|     ); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onDefaultsChange = (field: FieldConfig) => { | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...this.props.fieldConfig, | ||||
|       defaults: field, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { options } = this.props; | ||||
|     const { fieldOptions, showThresholdLabels, showThresholdMarkers } = options; | ||||
|     const { defaults } = fieldOptions; | ||||
|     const { options, fieldConfig } = this.props; | ||||
|     const { showThresholdLabels, showThresholdMarkers, fieldOptions } = options; | ||||
| 
 | ||||
|     const { defaults } = fieldConfig; | ||||
| 
 | ||||
|     const suggestions = fieldOptions.values | ||||
|       ? getDataLinksVariableSuggestions(this.props.data.series) | ||||
|       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); | ||||
| 
 | ||||
|     return ( | ||||
|       <> | ||||
|         <PanelOptionsGrid> | ||||
|  | @ -128,11 +131,9 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption | |||
|               value={defaults} | ||||
|             /> | ||||
|           </PanelOptionsGroup> | ||||
| 
 | ||||
|           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> | ||||
|         </PanelOptionsGrid> | ||||
| 
 | ||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
|         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
| 
 | ||||
|         <PanelOptionsGroup title="Data links"> | ||||
|           <DataLinksEditor | ||||
|  |  | |||
|  | @ -6,51 +6,6 @@ Object { | |||
|     "calcs": Array [ | ||||
|       "last", | ||||
|     ], | ||||
|     "defaults": Object { | ||||
|       "color": Object { | ||||
|         "mode": "thresholds", | ||||
|       }, | ||||
|       "decimals": 3, | ||||
|       "mappings": Array [ | ||||
|         Object { | ||||
|           "from": "50", | ||||
|           "id": 1, | ||||
|           "operator": "", | ||||
|           "text": "BIG", | ||||
|           "to": "1000", | ||||
|           "type": 2, | ||||
|           "value": "", | ||||
|         }, | ||||
|       ], | ||||
|       "max": "50", | ||||
|       "min": "-50", | ||||
|       "thresholds": Object { | ||||
|         "mode": "absolute", | ||||
|         "steps": Array [ | ||||
|           Object { | ||||
|             "color": "green", | ||||
|             "index": 0, | ||||
|             "value": -Infinity, | ||||
|           }, | ||||
|           Object { | ||||
|             "color": "#EAB839", | ||||
|             "index": 1, | ||||
|             "value": -25, | ||||
|           }, | ||||
|           Object { | ||||
|             "color": "#6ED0E0", | ||||
|             "index": 2, | ||||
|             "value": 0, | ||||
|           }, | ||||
|           Object { | ||||
|             "color": "red", | ||||
|             "index": 3, | ||||
|             "value": 25, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       "unit": "accMS2", | ||||
|     }, | ||||
|   }, | ||||
|   "orientation": "auto", | ||||
|   "showThresholdLabels": true, | ||||
|  |  | |||
|  | @ -2,10 +2,12 @@ import { PanelPlugin } from '@grafana/data'; | |||
| import { GaugePanelEditor } from './GaugePanelEditor'; | ||||
| import { GaugePanel } from './GaugePanel'; | ||||
| import { GaugeOptions, defaults } from './types'; | ||||
| import { standardFieldConfig } from '../stat/types'; | ||||
| import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations'; | ||||
| 
 | ||||
| export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel) | ||||
|   .setDefaults(defaults) | ||||
|   .setFieldConfigDefaults(standardFieldConfig) | ||||
|   .setEditor(GaugePanelEditor) | ||||
|   .setPanelChangeHandler(gaugePanelChangedHandler) | ||||
|   .setMigrationHandler(gaugePanelMigrationHandler); | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ export const GraphPanel: React.FunctionComponent<GraphPanelProps> = ({ | |||
|   width, | ||||
|   height, | ||||
|   options, | ||||
|   fieldConfig, | ||||
|   onOptionsChange, | ||||
|   onChangeTimeRange, | ||||
| }) => { | ||||
|  | @ -43,6 +44,7 @@ export const GraphPanel: React.FunctionComponent<GraphPanelProps> = ({ | |||
|       data={data} | ||||
|       timeZone={timeZone} | ||||
|       options={options} | ||||
|       fieldConfig={fieldConfig} | ||||
|       onOptionsChange={onOptionsChange} | ||||
|       onChangeTimeRange={onChangeTimeRange} | ||||
|     > | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import React from 'react'; | ||||
| import { GraphSeriesToggler } from '@grafana/ui'; | ||||
| import { PanelData, GraphSeriesXY, AbsoluteTimeRange, TimeZone } from '@grafana/data'; | ||||
| import { PanelData, GraphSeriesXY, AbsoluteTimeRange, TimeZone, FieldConfigSource } from '@grafana/data'; | ||||
| 
 | ||||
| import { getGraphSeriesModel } from './getGraphSeriesModel'; | ||||
| import { Options, SeriesOptions } from './types'; | ||||
|  | @ -18,6 +18,7 @@ interface GraphPanelControllerAPI { | |||
| interface GraphPanelControllerProps { | ||||
|   children: (api: GraphPanelControllerAPI) => JSX.Element; | ||||
|   options: Options; | ||||
|   fieldConfig: FieldConfigSource; | ||||
|   data: PanelData; | ||||
|   timeZone: TimeZone; | ||||
|   onOptionsChange: (options: Options) => void; | ||||
|  | @ -44,7 +45,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr | |||
|         props.options.series, | ||||
|         props.options.graph, | ||||
|         props.options.legend, | ||||
|         props.options.fieldOptions | ||||
|         props.fieldConfig | ||||
|       ), | ||||
|     }; | ||||
|   } | ||||
|  | @ -58,7 +59,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr | |||
|         props.options.series, | ||||
|         props.options.graph, | ||||
|         props.options.legend, | ||||
|         props.options.fieldOptions | ||||
|         props.fieldConfig | ||||
|       ), | ||||
|     }; | ||||
|   } | ||||
|  |  | |||
|  | @ -3,15 +3,15 @@ import _ from 'lodash'; | |||
| import React, { PureComponent } from 'react'; | ||||
| 
 | ||||
| // Types
 | ||||
| import { PanelEditorProps, FieldConfig } from '@grafana/data'; | ||||
| import { FieldConfig, PanelEditorProps } from '@grafana/data'; | ||||
| import { | ||||
|   Switch, | ||||
|   LegendOptions, | ||||
|   GraphTooltipOptions, | ||||
|   PanelOptionsGrid, | ||||
|   PanelOptionsGroup, | ||||
|   FieldPropertiesEditor, | ||||
|   Select, | ||||
|   FieldPropertiesEditor, | ||||
| } from '@grafana/ui'; | ||||
| import { Options, GraphOptions } from './types'; | ||||
| import { GraphLegendEditor } from './GraphLegendEditor'; | ||||
|  | @ -47,13 +47,10 @@ export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> { | |||
|     this.onGraphOptionsChange({ showPoints: !this.props.options.graph.showPoints }); | ||||
|   }; | ||||
| 
 | ||||
|   onDefaultsChange = (field: FieldConfig) => { | ||||
|     this.props.onOptionsChange({ | ||||
|       ...this.props.options, | ||||
|       fieldOptions: { | ||||
|         ...this.props.options.fieldOptions, | ||||
|   onDefaultsChange = (field: FieldConfig, event?: React.SyntheticEvent<HTMLElement>, callback?: () => void) => { | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...this.props.fieldConfig, | ||||
|       defaults: field, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -76,7 +73,7 @@ export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> { | |||
|             <FieldPropertiesEditor | ||||
|               showMinMax={false} | ||||
|               onChange={this.onDefaultsChange} | ||||
|               value={this.props.options.fieldOptions.defaults} | ||||
|               value={this.props.fieldConfig.defaults} | ||||
|             /> | ||||
|           </PanelOptionsGroup> | ||||
|           <PanelOptionsGroup title="Tooltip"> | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ import { | |||
|   GraphSeriesXY, | ||||
|   getTimeField, | ||||
|   DataFrame, | ||||
|   FieldDisplayOptions, | ||||
|   getSeriesTimeStep, | ||||
|   TimeZone, | ||||
|   hasMsResolution, | ||||
|  | @ -17,6 +16,7 @@ import { | |||
|   DEFAULT_DATE_TIME_FORMAT, | ||||
|   FieldColor, | ||||
|   FieldColorMode, | ||||
|   FieldConfigSource, | ||||
| } from '@grafana/data'; | ||||
| 
 | ||||
| import { SeriesOptions, GraphOptions } from './types'; | ||||
|  | @ -28,7 +28,7 @@ export const getGraphSeriesModel = ( | |||
|   seriesOptions: SeriesOptions, | ||||
|   graphOptions: GraphOptions, | ||||
|   legendOptions: GraphLegendEditorLegendOptions, | ||||
|   fieldOptions?: FieldDisplayOptions | ||||
|   fieldOptions?: FieldConfigSource | ||||
| ) => { | ||||
|   const graphs: GraphSeriesXY[] = []; | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,9 +16,10 @@ interface Props extends PanelProps<PieChartOptions> {} | |||
| 
 | ||||
| export class PieChartPanel extends PureComponent<Props> { | ||||
|   render() { | ||||
|     const { width, height, options, data, replaceVariables } = this.props; | ||||
|     const { width, height, options, data, replaceVariables, fieldConfig } = this.props; | ||||
| 
 | ||||
|     const values = getFieldDisplayValues({ | ||||
|       fieldConfig, | ||||
|       fieldOptions: options.fieldOptions, | ||||
|       data: data.series, | ||||
|       theme: config.theme, | ||||
|  |  | |||
|  | @ -1,22 +1,25 @@ | |||
| import React, { PureComponent } from 'react'; | ||||
| import { | ||||
|   PanelOptionsGrid, | ||||
|   ValueMappingsEditor, | ||||
|   FieldDisplayEditor, | ||||
|   FieldPropertiesEditor, | ||||
|   PanelOptionsGroup, | ||||
|   FieldPropertiesEditor, | ||||
|   LegacyValueMappingsEditor, | ||||
| } from '@grafana/ui'; | ||||
| import { ValueMapping, FieldConfig, PanelEditorProps, FieldDisplayOptions } from '@grafana/data'; | ||||
| import { PanelEditorProps, FieldDisplayOptions, ValueMapping, FieldConfig } from '@grafana/data'; | ||||
| 
 | ||||
| import { PieChartOptionsBox } from './PieChartOptionsBox'; | ||||
| import { PieChartOptions } from './types'; | ||||
| 
 | ||||
| export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChartOptions>> { | ||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||
|     const current = this.props.options.fieldOptions.defaults; | ||||
|     this.onDefaultsChange({ | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         mappings, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -27,16 +30,16 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart | |||
|     }); | ||||
| 
 | ||||
|   onDefaultsChange = (field: FieldConfig) => { | ||||
|     this.onDisplayOptionsChanged({ | ||||
|       ...this.props.options.fieldOptions, | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...this.props.fieldConfig, | ||||
|       defaults: field, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { onOptionsChange, options, data } = this.props; | ||||
|     const { onOptionsChange, options, data, fieldConfig, onFieldConfigChange } = this.props; | ||||
|     const { fieldOptions } = options; | ||||
|     const { defaults } = fieldOptions; | ||||
|     const { defaults } = fieldConfig; | ||||
| 
 | ||||
|     return ( | ||||
|       <> | ||||
|  | @ -49,10 +52,15 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart | |||
|             <FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} /> | ||||
|           </PanelOptionsGroup> | ||||
| 
 | ||||
|           <PieChartOptionsBox data={data} onOptionsChange={onOptionsChange} options={options} /> | ||||
|           <PieChartOptionsBox | ||||
|             data={data} | ||||
|             onOptionsChange={onOptionsChange} | ||||
|             options={options} | ||||
|             fieldConfig={fieldConfig} | ||||
|             onFieldConfigChange={onFieldConfigChange} | ||||
|           /> | ||||
|         </PanelOptionsGrid> | ||||
| 
 | ||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
|         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
|  | @ -5,4 +5,5 @@ import { PieChartOptions, defaults } from './types'; | |||
| 
 | ||||
| export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel) | ||||
|   .setDefaults(defaults) | ||||
|   .setFieldConfigDefaults({ defaults: { unit: 'short' } }) | ||||
|   .setEditor(PieChartPanelEditor); | ||||
|  |  | |||
|  | @ -14,8 +14,5 @@ export const defaults: PieChartOptions = { | |||
|   fieldOptions: { | ||||
|     ...standardFieldDisplayOptions, | ||||
|     calcs: [ReducerID.last], | ||||
|     defaults: { | ||||
|       unit: 'short', | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  |  | |||
|  | @ -69,10 +69,11 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> { | |||
|   }; | ||||
| 
 | ||||
|   getValues = (): FieldDisplay[] => { | ||||
|     const { data, options, replaceVariables } = this.props; | ||||
|     const { data, options, replaceVariables, fieldConfig } = this.props; | ||||
| 
 | ||||
|     return getFieldDisplayValues({ | ||||
|       ...options, | ||||
|       fieldConfig, | ||||
|       fieldOptions: options.fieldOptions, | ||||
|       replaceVariables, | ||||
|       theme: config.theme, | ||||
|       data: data.series, | ||||
|  |  | |||
|  | @ -2,48 +2,53 @@ | |||
| import React, { PureComponent } from 'react'; | ||||
| 
 | ||||
| import { | ||||
|   ThresholdsEditor, | ||||
|   PanelOptionsGrid, | ||||
|   ValueMappingsEditor, | ||||
|   FieldDisplayEditor, | ||||
|   FieldPropertiesEditor, | ||||
|   PanelOptionsGroup, | ||||
|   DataLinksEditor, | ||||
|   FormLabel, | ||||
|   Select, | ||||
|   FieldPropertiesEditor, | ||||
|   ThresholdsEditor, | ||||
|   LegacyValueMappingsEditor, | ||||
|   DataLinksEditor, | ||||
| } from '@grafana/ui'; | ||||
| 
 | ||||
| import { | ||||
|   ThresholdsConfig, | ||||
|   ValueMapping, | ||||
|   FieldConfig, | ||||
|   DataLink, | ||||
|   PanelEditorProps, | ||||
|   FieldDisplayOptions, | ||||
|   FieldConfig, | ||||
|   ValueMapping, | ||||
|   ThresholdsConfig, | ||||
|   DataLink, | ||||
| } from '@grafana/data'; | ||||
| 
 | ||||
| import { StatPanelOptions, colorModes, graphModes, justifyModes } from './types'; | ||||
| import { orientationOptions } from '../gauge/types'; | ||||
| 
 | ||||
| import { | ||||
|   getDataLinksVariableSuggestions, | ||||
|   getCalculationValueDataLinksVariableSuggestions, | ||||
| } from 'app/features/panel/panellinks/link_srv'; | ||||
|   getDataLinksVariableSuggestions, | ||||
| } from '../../../features/panel/panellinks/link_srv'; | ||||
| 
 | ||||
| export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOptions>> { | ||||
|   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||
|     const current = this.props.options.fieldOptions.defaults; | ||||
|     this.onDefaultsChange({ | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         thresholds, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||
|     const current = this.props.options.fieldOptions.defaults; | ||||
|     this.onDefaultsChange({ | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         mappings, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -59,23 +64,28 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt | |||
|   onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); | ||||
| 
 | ||||
|   onDefaultsChange = (field: FieldConfig) => { | ||||
|     this.onDisplayOptionsChanged({ | ||||
|       ...this.props.options.fieldOptions, | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...this.props.fieldConfig, | ||||
|       defaults: field, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   onDataLinksChanged = (links: DataLink[]) => { | ||||
|     this.onDefaultsChange({ | ||||
|       ...this.props.options.fieldOptions.defaults, | ||||
|     const current = this.props.fieldConfig; | ||||
|     this.props.onFieldConfigChange({ | ||||
|       ...current, | ||||
|       defaults: { | ||||
|         ...current.defaults, | ||||
|         links, | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { options } = this.props; | ||||
|     const { options, fieldConfig } = this.props; | ||||
|     const { fieldOptions } = options; | ||||
|     const { defaults } = fieldOptions; | ||||
|     const { defaults } = fieldConfig; | ||||
| 
 | ||||
|     const suggestions = fieldOptions.values | ||||
|       ? getDataLinksVariableSuggestions(this.props.data.series) | ||||
|       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); | ||||
|  | @ -126,7 +136,6 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt | |||
|               /> | ||||
|             </div> | ||||
|           </PanelOptionsGroup> | ||||
| 
 | ||||
|           <PanelOptionsGroup title="Field"> | ||||
|             <FieldPropertiesEditor | ||||
|               showMinMax={true} | ||||
|  | @ -138,8 +147,7 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt | |||
| 
 | ||||
|           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> | ||||
|         </PanelOptionsGrid> | ||||
| 
 | ||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
|         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||
| 
 | ||||
|         <PanelOptionsGroup title="Data links"> | ||||
|           <DataLinksEditor | ||||
|  |  | |||
|  | @ -1,11 +1,12 @@ | |||
| import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from '@grafana/ui'; | ||||
| import { PanelPlugin } from '@grafana/data'; | ||||
| import { StatPanelOptions, defaults } from './types'; | ||||
| import { StatPanelOptions, defaults, standardFieldConfig } from './types'; | ||||
| import { StatPanel } from './StatPanel'; | ||||
| import { StatPanelEditor } from './StatPanelEditor'; | ||||
| 
 | ||||
| export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel) | ||||
|   .setDefaults(defaults) | ||||
|   .setFieldConfigDefaults(standardFieldConfig) | ||||
|   .setEditor(StatPanelEditor) | ||||
|   .setNoPadding() | ||||
|   .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) | ||||
|  |  | |||
|  | @ -1,5 +1,12 @@ | |||
| import { SingleStatBaseOptions, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode } from '@grafana/ui'; | ||||
| import { VizOrientation, ReducerID, FieldDisplayOptions, SelectableValue, ThresholdsMode } from '@grafana/data'; | ||||
| import { | ||||
|   VizOrientation, | ||||
|   ReducerID, | ||||
|   FieldDisplayOptions, | ||||
|   SelectableValue, | ||||
|   FieldConfigSource, | ||||
|   ThresholdsMode, | ||||
| } from '@grafana/data'; | ||||
| 
 | ||||
| // Structure copied from angular
 | ||||
| export interface StatPanelOptions extends SingleStatBaseOptions { | ||||
|  | @ -26,6 +33,9 @@ export const justifyModes: Array<SelectableValue<BigValueJustifyMode>> = [ | |||
| export const standardFieldDisplayOptions: FieldDisplayOptions = { | ||||
|   values: false, | ||||
|   calcs: [ReducerID.mean], | ||||
| }; | ||||
| 
 | ||||
| export const standardFieldConfig: FieldConfigSource = { | ||||
|   defaults: { | ||||
|     thresholds: { | ||||
|       mode: ThresholdsMode.Absolute, | ||||
|  |  | |||
|  | @ -1,14 +1,7 @@ | |||
| import { FieldConfigSource } from '@grafana/data'; | ||||
| 
 | ||||
| export interface Options { | ||||
|   fieldOptions: FieldConfigSource; | ||||
|   showHeader: boolean; | ||||
| } | ||||
| 
 | ||||
| export const defaults: Options = { | ||||
|   fieldOptions: { | ||||
|     defaults: {}, | ||||
|     overrides: [], | ||||
|   }, | ||||
|   showHeader: true, | ||||
| }; | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue