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({ |     const options = createDisplayOptions({ | ||||||
|       fieldOptions: { |       fieldOptions: { | ||||||
|         calcs: [ReducerID.first], |         calcs: [ReducerID.first], | ||||||
|         override: {}, |       }, | ||||||
|  |       fieldConfig: { | ||||||
|  |         overrides: [], | ||||||
|         defaults: { |         defaults: { | ||||||
|           title: '$__cell_0 * $__field_name * $__series_name', |           title: '$__cell_0 * $__field_name * $__series_name', | ||||||
|         }, |         }, | ||||||
|  | @ -42,8 +44,6 @@ describe('FieldDisplay', () => { | ||||||
|     const options = createDisplayOptions({ |     const options = createDisplayOptions({ | ||||||
|       fieldOptions: { |       fieldOptions: { | ||||||
|         calcs: [ReducerID.last], |         calcs: [ReducerID.last], | ||||||
|         override: {}, |  | ||||||
|         defaults: {}, |  | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|     const display = getFieldDisplayValues(options); |     const display = getFieldDisplayValues(options); | ||||||
|  | @ -56,8 +56,6 @@ describe('FieldDisplay', () => { | ||||||
|         values: true, //
 |         values: true, //
 | ||||||
|         limit: 1000, |         limit: 1000, | ||||||
|         calcs: [], |         calcs: [], | ||||||
|         override: {}, |  | ||||||
|         defaults: {}, |  | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|     const display = getFieldDisplayValues(options); |     const display = getFieldDisplayValues(options); | ||||||
|  | @ -70,8 +68,6 @@ describe('FieldDisplay', () => { | ||||||
|         values: true, //
 |         values: true, //
 | ||||||
|         limit: 2, |         limit: 2, | ||||||
|         calcs: [], |         calcs: [], | ||||||
|         override: {}, |  | ||||||
|         defaults: {}, |  | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|     const display = getFieldDisplayValues(options); |     const display = getFieldDisplayValues(options); | ||||||
|  | @ -101,7 +97,7 @@ describe('FieldDisplay', () => { | ||||||
| 
 | 
 | ||||||
|   it('Should return field thresholds when there is no data', () => { |   it('Should return field thresholds when there is no data', () => { | ||||||
|     const options = createEmptyDisplayOptions({ |     const options = createEmptyDisplayOptions({ | ||||||
|       fieldOptions: { |       fieldConfig: { | ||||||
|         defaults: { |         defaults: { | ||||||
|           thresholds: { steps: [{ color: '#F2495C', value: 50 }] }, |           thresholds: { steps: [{ color: '#F2495C', value: 50 }] }, | ||||||
|         }, |         }, | ||||||
|  | @ -123,7 +119,7 @@ describe('FieldDisplay', () => { | ||||||
|   it('Should return field mapped value when there is no data', () => { |   it('Should return field mapped value when there is no data', () => { | ||||||
|     const mapEmptyToText = '0'; |     const mapEmptyToText = '0'; | ||||||
|     const options = createEmptyDisplayOptions({ |     const options = createEmptyDisplayOptions({ | ||||||
|       fieldOptions: { |       fieldConfig: { | ||||||
|         defaults: { |         defaults: { | ||||||
|           mappings: [ |           mappings: [ | ||||||
|             { |             { | ||||||
|  | @ -146,8 +142,8 @@ describe('FieldDisplay', () => { | ||||||
|   it('Should always return display numeric 0 when there is no data', () => { |   it('Should always return display numeric 0 when there is no data', () => { | ||||||
|     const mapEmptyToText = '0'; |     const mapEmptyToText = '0'; | ||||||
|     const options = createEmptyDisplayOptions({ |     const options = createEmptyDisplayOptions({ | ||||||
|       fieldOptions: { |       fieldConfig: { | ||||||
|         override: { |         overrides: { | ||||||
|           mappings: [ |           mappings: [ | ||||||
|             { |             { | ||||||
|               id: 1, |               id: 1, | ||||||
|  | @ -241,7 +237,7 @@ function createEmptyDisplayOptions(extend = {}): GetFieldDisplayValuesOptions { | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function createDisplayOptions(extend = {}): GetFieldDisplayValuesOptions { | function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}): GetFieldDisplayValuesOptions { | ||||||
|   const options: GetFieldDisplayValuesOptions = { |   const options: GetFieldDisplayValuesOptions = { | ||||||
|     data: [ |     data: [ | ||||||
|       toDataFrame({ |       toDataFrame({ | ||||||
|  | @ -258,8 +254,10 @@ function createDisplayOptions(extend = {}): GetFieldDisplayValuesOptions { | ||||||
|     }, |     }, | ||||||
|     fieldOptions: { |     fieldOptions: { | ||||||
|       calcs: [], |       calcs: [], | ||||||
|       defaults: {}, |     }, | ||||||
|  |     fieldConfig: { | ||||||
|       overrides: [], |       overrides: [], | ||||||
|  |       defaults: {}, | ||||||
|     }, |     }, | ||||||
|     theme: {} as GrafanaTheme, |     theme: {} as GrafanaTheme, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  | @ -19,7 +19,8 @@ import { ReducerID, reduceField } from '../transformations/fieldReducer'; | ||||||
| import { ScopedVars } from '../types/ScopedVars'; | import { ScopedVars } from '../types/ScopedVars'; | ||||||
| import { getTimeField } from '../dataframe/processDataFrame'; | 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
 |   values?: boolean; // If true show each row value
 | ||||||
|   limit?: number; // if showing all values limit
 |   limit?: number; // if showing all values limit
 | ||||||
|   calcs: string[]; // when !values, pick one value for the whole field
 |   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) { |   if (fieldCount > 1 || !parts.length) { | ||||||
|     parts.push('${' + VAR_FIELD_NAME + '}'); |     parts.push('${' + VAR_FIELD_NAME + '}'); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   return parts.join(' '); |   return parts.join(' '); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -75,6 +77,7 @@ export interface FieldDisplay { | ||||||
| export interface GetFieldDisplayValuesOptions { | export interface GetFieldDisplayValuesOptions { | ||||||
|   data?: DataFrame[]; |   data?: DataFrame[]; | ||||||
|   fieldOptions: FieldDisplayOptions; |   fieldOptions: FieldDisplayOptions; | ||||||
|  |   fieldConfig: FieldConfigSource; | ||||||
|   replaceVariables: InterpolateFunction; |   replaceVariables: InterpolateFunction; | ||||||
|   sparkline?: boolean; // Calculate the sparkline
 |   sparkline?: boolean; // Calculate the sparkline
 | ||||||
|   theme: GrafanaTheme; |   theme: GrafanaTheme; | ||||||
|  | @ -84,7 +87,7 @@ export interface GetFieldDisplayValuesOptions { | ||||||
| export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25; | export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25; | ||||||
| 
 | 
 | ||||||
| export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): FieldDisplay[] => { | 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 calcs = fieldOptions.calcs.length ? fieldOptions.calcs : [ReducerID.last]; | ||||||
| 
 | 
 | ||||||
|   const values: FieldDisplay[] = []; |   const values: FieldDisplay[] = []; | ||||||
|  | @ -94,7 +97,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi | ||||||
|     const data = options.data; |     const data = options.data; | ||||||
|     let hitLimit = false; |     let hitLimit = false; | ||||||
|     const limit = fieldOptions.limit ? fieldOptions.limit : DEFAULT_FIELD_DISPLAY_VALUES_LIMIT; |     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 = {}; |     const scopedVars: ScopedVars = {}; | ||||||
| 
 | 
 | ||||||
|     for (let s = 0; s < data.length && !hitLimit; s++) { |     for (let s = 0; s < data.length && !hitLimit; s++) { | ||||||
|  | @ -194,7 +197,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi | ||||||
| 
 | 
 | ||||||
|   if (values.length === 0) { |   if (values.length === 0) { | ||||||
|     values.push(createNoValuesFieldDisplay(options)); |     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
 |     // Don't show title for single item
 | ||||||
|     values[0].display.title = undefined; |     values[0].display.title = undefined; | ||||||
|   } |   } | ||||||
|  | @ -237,8 +240,8 @@ export function getDisplayValueAlignmentFactors(values: FieldDisplay[]): Display | ||||||
| 
 | 
 | ||||||
| function createNoValuesFieldDisplay(options: GetFieldDisplayValuesOptions): FieldDisplay { | function createNoValuesFieldDisplay(options: GetFieldDisplayValuesOptions): FieldDisplay { | ||||||
|   const displayName = 'No data'; |   const displayName = 'No data'; | ||||||
|   const { fieldOptions } = options; |   const { fieldConfig } = options; | ||||||
|   const { defaults } = fieldOptions; |   const { defaults } = fieldConfig; | ||||||
| 
 | 
 | ||||||
|   const displayProcessor = getDisplayProcessor({ |   const displayProcessor = getDisplayProcessor({ | ||||||
|     field: { |     field: { | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ 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 } from './fieldOverrides'; | import { FieldConfigEditorRegistry, FieldConfigSource } from './fieldOverrides'; | ||||||
| 
 | 
 | ||||||
| export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; | export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; | ||||||
| 
 | 
 | ||||||
|  | @ -54,6 +54,10 @@ export interface PanelProps<T = any> { | ||||||
|   timeZone: TimeZone; |   timeZone: TimeZone; | ||||||
|   options: T; |   options: T; | ||||||
|   onOptionsChange: (options: T) => void; |   onOptionsChange: (options: T) => void; | ||||||
|  |   /** Panel fields configuration */ | ||||||
|  |   fieldConfig: FieldConfigSource; | ||||||
|  |   /** Enables panel field config manipulation */ | ||||||
|  |   onFieldConfigChange: (config: FieldConfigSource) => void; | ||||||
|   renderCounter: number; |   renderCounter: number; | ||||||
|   transparent: boolean; |   transparent: boolean; | ||||||
|   width: number; |   width: number; | ||||||
|  | @ -70,11 +74,23 @@ export interface PanelEditorProps<T = any> { | ||||||
|     callback?: () => void |     callback?: () => void | ||||||
|   ) => void; |   ) => void; | ||||||
|   data: PanelData; |   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> { | export interface PanelModel<TOptions = any> { | ||||||
|   id: number; |   id: number; | ||||||
|   options: TOptions; |   options: TOptions; | ||||||
|  |   fieldConfig: FieldConfigSource; | ||||||
|   pluginVersion?: string; |   pluginVersion?: string; | ||||||
|   scopedVars?: ScopedVars; |   scopedVars?: ScopedVars; | ||||||
| } | } | ||||||
|  | @ -98,6 +114,10 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta> | ||||||
|   editor?: ComponentClass<PanelEditorProps<TOptions>>; |   editor?: ComponentClass<PanelEditorProps<TOptions>>; | ||||||
|   customFieldConfigs?: FieldConfigEditorRegistry; |   customFieldConfigs?: FieldConfigEditorRegistry; | ||||||
|   defaults?: TOptions; |   defaults?: TOptions; | ||||||
|  |   fieldConfigDefaults?: FieldConfigSource = { | ||||||
|  |     defaults: {}, | ||||||
|  |     overrides: [], | ||||||
|  |   }; | ||||||
|   onPanelMigration?: PanelMigrationHandler<TOptions>; |   onPanelMigration?: PanelMigrationHandler<TOptions>; | ||||||
|   onPanelTypeChanged?: PanelTypeChangedHandler<TOptions>; |   onPanelTypeChanged?: PanelTypeChangedHandler<TOptions>; | ||||||
|   noPadding?: boolean; |   noPadding?: boolean; | ||||||
|  | @ -155,6 +175,19 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta> | ||||||
|     this.customFieldConfigs = registry; |     this.customFieldConfigs = registry; | ||||||
|     return this; |     return this; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Enables configuration of panel's default field config | ||||||
|  |    */ | ||||||
|  |   setFieldConfigDefaults(defaultConfig: Partial<FieldConfigSource>) { | ||||||
|  |     this.fieldConfigDefaults = { | ||||||
|  |       defaults: {}, | ||||||
|  |       overrides: [], | ||||||
|  |       ...defaultConfig, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return this; | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface PanelMenuItem { | export interface PanelMenuItem { | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ import { | ||||||
|   InterpolateFunction, |   InterpolateFunction, | ||||||
|   GrafanaTheme, |   GrafanaTheme, | ||||||
|   FieldMatcherID, |   FieldMatcherID, | ||||||
|   FieldDisplayOptions, |  | ||||||
|   MutableDataFrame, |   MutableDataFrame, | ||||||
|   DataFrame, |   DataFrame, | ||||||
|   toDataFrame, |   toDataFrame, | ||||||
|  | @ -82,7 +81,7 @@ describe('FieldOverrides', () => { | ||||||
|   it('will apply field overrides', () => { |   it('will apply field overrides', () => { | ||||||
|     const data = applyFieldOverrides({ |     const data = applyFieldOverrides({ | ||||||
|       data: [f0], // the frame
 |       data: [f0], // the frame
 | ||||||
|       fieldOptions: src as FieldDisplayOptions, // defaults + overrides
 |       fieldOptions: src as FieldConfigSource, // defaults + overrides
 | ||||||
|       replaceVariables: (undefined as any) as InterpolateFunction, |       replaceVariables: (undefined as any) as InterpolateFunction, | ||||||
|       theme: (undefined as any) as GrafanaTheme, |       theme: (undefined as any) as GrafanaTheme, | ||||||
|     })[0]; |     })[0]; | ||||||
|  | @ -108,7 +107,7 @@ describe('FieldOverrides', () => { | ||||||
|   it('will apply set min/max when asked', () => { |   it('will apply set min/max when asked', () => { | ||||||
|     const data = applyFieldOverrides({ |     const data = applyFieldOverrides({ | ||||||
|       data: [f0], // the frame
 |       data: [f0], // the frame
 | ||||||
|       fieldOptions: src as FieldDisplayOptions, // defaults + overrides
 |       fieldOptions: src as FieldConfigSource, // defaults + overrides
 | ||||||
|       replaceVariables: (undefined as any) as InterpolateFunction, |       replaceVariables: (undefined as any) as InterpolateFunction, | ||||||
|       theme: (undefined as any) as GrafanaTheme, |       theme: (undefined as any) as GrafanaTheme, | ||||||
|       autoMinMax: true, |       autoMinMax: true, | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| 
 | 
 | ||||||
| import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps, ValueMapping } from '@grafana/data'; | import { FieldOverrideContext, FieldOverrideEditorProps, FieldConfigEditorProps, ValueMapping } from '@grafana/data'; | ||||||
| import { ValueMappingsEditor } from '..'; | import { LegacyValueMappingsEditor } from '..'; | ||||||
| 
 | 
 | ||||||
| export interface ValueMappingFieldConfigSettings {} | export interface ValueMappingFieldConfigSettings {} | ||||||
| 
 | 
 | ||||||
|  | @ -27,7 +27,7 @@ export class ValueMappingsValueEditor extends React.PureComponent< | ||||||
|       value = []; |       value = []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return <ValueMappingsEditor valueMappings={value} onChange={onChange} />; |     return <LegacyValueMappingsEditor valueMappings={value} onChange={onChange} />; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,6 @@ import { | ||||||
|   toNumberString, |   toNumberString, | ||||||
|   toIntegerOrUndefined, |   toIntegerOrUndefined, | ||||||
|   SelectableValue, |   SelectableValue, | ||||||
|   FieldConfig, |  | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| 
 | 
 | ||||||
| const showOptions: Array<SelectableValue<boolean>> = [ | const showOptions: Array<SelectableValue<boolean>> = [ | ||||||
|  | @ -47,10 +46,6 @@ export class FieldDisplayEditor extends PureComponent<Props> { | ||||||
|     this.props.onChange({ ...this.props.value, calcs }); |     this.props.onChange({ ...this.props.value, calcs }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   onDefaultsChange = (value: FieldConfig) => { |  | ||||||
|     this.props.onChange({ ...this.props.value, defaults: value }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   onLimitChange = (event: ChangeEvent<HTMLInputElement>) => { |   onLimitChange = (event: ChangeEvent<HTMLInputElement>) => { | ||||||
|     this.props.onChange({ |     this.props.onChange({ | ||||||
|       ...this.props.value, |       ...this.props.value, | ||||||
|  |  | ||||||
|  | @ -34,7 +34,48 @@ describe('sharedSingleStatMigrationHandler', () => { | ||||||
|       type: 'bargauge', |       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', () => { |   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', () => { |   it('Remove unused `overrides` option', () => { | ||||||
|  | @ -90,6 +141,17 @@ describe('sharedSingleStatMigrationHandler', () => { | ||||||
|       type: 'bargauge', |       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, |   VizOrientation, | ||||||
|   PanelModel, |   PanelModel, | ||||||
|   FieldDisplayOptions, |   FieldDisplayOptions, | ||||||
|   ConfigOverrideRule, |  | ||||||
|   ThresholdsMode, |   ThresholdsMode, | ||||||
|   ThresholdsConfig, |   ThresholdsConfig, | ||||||
|   validateFieldConfig, |   validateFieldConfig, | ||||||
|  | @ -32,66 +31,15 @@ export function sharedSingleStatPanelChangedHandler( | ||||||
|   prevOptions: any |   prevOptions: any | ||||||
| ) { | ) { | ||||||
|   let options = panel.options; |   let options = panel.options; | ||||||
|  | 
 | ||||||
|  |   panel.fieldConfig = panel.fieldConfig || { | ||||||
|  |     defaults: {}, | ||||||
|  |     overrides: [], | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   // Migrating from angular singlestat
 |   // Migrating from angular singlestat
 | ||||||
|   if (prevPluginId === 'singlestat' && prevOptions.angular) { |   if (prevPluginId === 'singlestat' && prevOptions.angular) { | ||||||
|     const prevPanel = prevOptions.angular; |     return migrateFromAngularSinglestat(panel, prevOptions); | ||||||
|     const reducer = fieldReducers.getIfExists(prevPanel.valueName); |  | ||||||
|     options = { |  | ||||||
|       fieldOptions: { |  | ||||||
|         defaults: {} as FieldConfig, |  | ||||||
|         overrides: [] as ConfigOverrideRule[], |  | ||||||
|         calcs: [reducer ? reducer.id : ReducerID.mean], |  | ||||||
|       }, |  | ||||||
|       orientation: VizOrientation.Horizontal, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     const defaults = options.fieldOptions.defaults; |  | ||||||
|     if (prevPanel.format) { |  | ||||||
|       defaults.unit = prevPanel.format; |  | ||||||
|     } |  | ||||||
|     if (prevPanel.nullPointMode) { |  | ||||||
|       defaults.nullValueMode = prevPanel.nullPointMode; |  | ||||||
|     } |  | ||||||
|     if (prevPanel.nullText) { |  | ||||||
|       defaults.noValue = prevPanel.nullText; |  | ||||||
|     } |  | ||||||
|     if (prevPanel.decimals || prevPanel.decimals === 0) { |  | ||||||
|       defaults.decimals = prevPanel.decimals; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Convert thresholds and color values
 |  | ||||||
|     if (prevPanel.thresholds && prevPanel.colors) { |  | ||||||
|       const levels = prevPanel.thresholds.split(',').map((strVale: string) => { |  | ||||||
|         return Number(strVale.trim()); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       // One more color than threshold
 |  | ||||||
|       const thresholds: Threshold[] = []; |  | ||||||
|       for (const color of prevPanel.colors) { |  | ||||||
|         const idx = thresholds.length - 1; |  | ||||||
|         if (idx >= 0) { |  | ||||||
|           thresholds.push({ value: levels[idx], color }); |  | ||||||
|         } else { |  | ||||||
|           thresholds.push({ value: -Infinity, color }); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       defaults.thresholds = { |  | ||||||
|         mode: ThresholdsMode.Absolute, |  | ||||||
|         steps: thresholds, |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Convert value mappings
 |  | ||||||
|     const mappings = convertOldAngularValueMapping(prevPanel); |  | ||||||
|     if (mappings && mappings.length) { |  | ||||||
|       defaults.mappings = mappings; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (prevPanel.gauge && prevPanel.gauge.show) { |  | ||||||
|       defaults.min = prevPanel.gauge.minValue; |  | ||||||
|       defaults.max = prevPanel.gauge.maxValue; |  | ||||||
|     } |  | ||||||
|     return options; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   for (const k of optionsToKeep) { |   for (const k of optionsToKeep) { | ||||||
|  | @ -99,6 +47,70 @@ export function sharedSingleStatPanelChangedHandler( | ||||||
|       options[k] = cloneDeep(prevOptions[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); | ||||||
|  |   const options = { | ||||||
|  |     fieldOptions: { | ||||||
|  |       calcs: [reducer ? reducer.id : ReducerID.mean], | ||||||
|  |     }, | ||||||
|  |     orientation: VizOrientation.Horizontal, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const defaults: FieldConfig = {}; | ||||||
|  | 
 | ||||||
|  |   if (prevPanel.format) { | ||||||
|  |     defaults.unit = prevPanel.format; | ||||||
|  |   } | ||||||
|  |   if (prevPanel.nullPointMode) { | ||||||
|  |     defaults.nullValueMode = prevPanel.nullPointMode; | ||||||
|  |   } | ||||||
|  |   if (prevPanel.nullText) { | ||||||
|  |     defaults.noValue = prevPanel.nullText; | ||||||
|  |   } | ||||||
|  |   if (prevPanel.decimals || prevPanel.decimals === 0) { | ||||||
|  |     defaults.decimals = prevPanel.decimals; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Convert thresholds and color values
 | ||||||
|  |   if (prevPanel.thresholds && prevPanel.colors) { | ||||||
|  |     const levels = prevPanel.thresholds.split(',').map((strVale: string) => { | ||||||
|  |       return Number(strVale.trim()); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // One more color than threshold
 | ||||||
|  |     const thresholds: Threshold[] = []; | ||||||
|  |     for (const color of prevPanel.colors) { | ||||||
|  |       const idx = thresholds.length - 1; | ||||||
|  |       if (idx >= 0) { | ||||||
|  |         thresholds.push({ value: levels[idx], color }); | ||||||
|  |       } else { | ||||||
|  |         thresholds.push({ value: -Infinity, color }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     defaults.thresholds = { | ||||||
|  |       mode: ThresholdsMode.Absolute, | ||||||
|  |       steps: thresholds, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Convert value mappings
 | ||||||
|  |   const mappings = convertOldAngularValueMapping(prevPanel); | ||||||
|  |   if (mappings && mappings.length) { | ||||||
|  |     defaults.mappings = mappings; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (prevPanel.gauge && prevPanel.gauge.show) { | ||||||
|  |     defaults.min = prevPanel.gauge.minValue; | ||||||
|  |     defaults.max = prevPanel.gauge.maxValue; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   panel.fieldConfig.defaults = defaults; | ||||||
|  | 
 | ||||||
|   return options; |   return options; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -162,6 +174,22 @@ export function sharedSingleStatMigrationHandler(panel: PanelModel<SingleStatBas | ||||||
|     validateFieldConfig(defaults); |     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; |   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)) { |     if (isArray(item)) { | ||||||
|       onChange(item.map(v => v.value)); |       onChange(item.map(v => v.value)); | ||||||
|     } else { |     } else { | ||||||
|       onChange(item.value ? [item.value] : []); |       onChange(item && item.value ? [item.value] : []); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ const mappingOptions = [ | ||||||
|   { value: MappingType.RangeToText, label: 'Range' }, |   { value: MappingType.RangeToText, label: 'Range' }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| export default class MappingRow extends PureComponent<Props, State> { | export default class LegacyMappingRow extends PureComponent<Props, State> { | ||||||
|   constructor(props: Props) { |   constructor(props: Props) { | ||||||
|     super(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 React from 'react'; | ||||||
| import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||||
| 
 | 
 | ||||||
| import { ValueMappingsEditor, Props } from './ValueMappingsEditor'; | import { LegacyValueMappingsEditor, Props } from './LegacyValueMappingsEditor'; | ||||||
| import { MappingType } from '@grafana/data'; | import { MappingType } from '@grafana/data'; | ||||||
| 
 | 
 | ||||||
| const setup = (propOverrides?: object) => { | const setup = (propOverrides?: object) => { | ||||||
|  | @ -15,9 +15,9 @@ const setup = (propOverrides?: object) => { | ||||||
| 
 | 
 | ||||||
|   Object.assign(props, propOverrides); |   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 { |   return { | ||||||
|     instance, |     instance, | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import React, { PureComponent } from 'react'; | import React, { PureComponent } from 'react'; | ||||||
| 
 | 
 | ||||||
| import MappingRow from './MappingRow'; | import LegacyMappingRow from './LegacyMappingRow'; | ||||||
| import { MappingType, ValueMapping } from '@grafana/data'; | import { MappingType, ValueMapping } from '@grafana/data'; | ||||||
| import { Button } from '../Button/Button'; | import { Button } from '../Button/Button'; | ||||||
| import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; | import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; | ||||||
|  | @ -15,7 +15,7 @@ interface State { | ||||||
|   nextIdToAdd: number; |   nextIdToAdd: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class ValueMappingsEditor extends PureComponent<Props, State> { | export class LegacyValueMappingsEditor extends PureComponent<Props, State> { | ||||||
|   constructor(props: Props) { |   constructor(props: Props) { | ||||||
|     super(props); |     super(props); | ||||||
| 
 | 
 | ||||||
|  | @ -91,7 +91,7 @@ export class ValueMappingsEditor extends PureComponent<Props, State> { | ||||||
|         <div> |         <div> | ||||||
|           {valueMappings.length > 0 && |           {valueMappings.length > 0 && | ||||||
|             valueMappings.map((valueMapping, index) => ( |             valueMappings.map((valueMapping, index) => ( | ||||||
|               <MappingRow |               <LegacyMappingRow | ||||||
|                 key={`${valueMapping.text}-${index}`} |                 key={`${valueMapping.text}-${index}`} | ||||||
|                 valueMapping={valueMapping} |                 valueMapping={valueMapping} | ||||||
|                 updateValueMapping={this.updateGauge} |                 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" |   title="Value mappings" | ||||||
| > | > | ||||||
|   <div> |   <div> | ||||||
|     <MappingRow |     <LegacyMappingRow | ||||||
|       key="Ok-0" |       key="Ok-0" | ||||||
|       removeValueMapping={[Function]} |       removeValueMapping={[Function]} | ||||||
|       updateValueMapping={[Function]} |       updateValueMapping={[Function]} | ||||||
|  | @ -19,7 +19,7 @@ exports[`Render should render component 1`] = ` | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     /> |     /> | ||||||
|     <MappingRow |     <LegacyMappingRow | ||||||
|       key="Meh-1" |       key="Meh-1" | ||||||
|       removeValueMapping={[Function]} |       removeValueMapping={[Function]} | ||||||
|       updateValueMapping={[Function]} |       updateValueMapping={[Function]} | ||||||
|  | @ -28,7 +28,7 @@ export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; | ||||||
| export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover'; | export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover'; | ||||||
| export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; | export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; | ||||||
| export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid'; | export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid'; | ||||||
| export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor'; | export { LegacyValueMappingsEditor } from './ValueMappingsEditor/LegacyValueMappingsEditor'; | ||||||
| export { Switch } from './Switch/Switch'; | export { Switch } from './Switch/Switch'; | ||||||
| export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult'; | export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult'; | ||||||
| export { PieChart, PieChartType } from './PieChart/PieChart'; | 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 }); |     this.props.updateLocation({ query: { tab: tab.id }, partial: true }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   onFieldConfigsChange = (fieldOptions: FieldConfigSource) => { |   onFieldConfigChange = (config: FieldConfigSource) => { | ||||||
|     // NOTE: for now, assume this is from 'fieldOptions' -- TODO? put on panel model directly?
 |  | ||||||
|     const { panel } = this.props; |     const { panel } = this.props; | ||||||
|     const options = panel.getOptions(); | 
 | ||||||
|     panel.updateOptions({ |     panel.updateFieldConfig({ | ||||||
|       ...options, |       ...config, | ||||||
|       fieldOptions, // Assume it is from shared singlestat -- TODO own property?
 |  | ||||||
|     }); |     }); | ||||||
|     this.forceUpdate(); |     this.forceUpdate(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   renderFieldOptions(plugin: PanelPlugin) { |   renderFieldOptions(plugin: PanelPlugin) { | ||||||
|     const { panel, data } = this.props; |     const { panel, data } = this.props; | ||||||
|  |     const { fieldConfig } = panel; | ||||||
| 
 | 
 | ||||||
|     const fieldOptions = panel.options['fieldOptions'] as FieldConfigSource; |     if (!fieldConfig) { | ||||||
| 
 |  | ||||||
|     if (!fieldOptions) { |  | ||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <FieldConfigEditor |       <FieldConfigEditor config={fieldConfig} plugin={plugin} onChange={this.onFieldConfigChange} data={data.series} /> | ||||||
|         config={fieldOptions} |  | ||||||
|         plugin={plugin} |  | ||||||
|         onChange={this.onFieldConfigsChange} |  | ||||||
|         data={data.series} |  | ||||||
|       /> |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -130,7 +122,13 @@ export class PanelEditorUnconnected extends PureComponent<Props> { | ||||||
|     if (plugin.editor && panel) { |     if (plugin.editor && panel) { | ||||||
|       return ( |       return ( | ||||||
|         <div style={{ marginTop: '10px' }}> |         <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> |         </div> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -89,7 +89,7 @@ describe('ShareModal', () => { | ||||||
|         }, |         }, | ||||||
|       }; |       }; | ||||||
|       ctx.mount({ |       ctx.mount({ | ||||||
|         panel: { id: 22, options: {} }, |         panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | @ -101,7 +101,7 @@ describe('ShareModal', () => { | ||||||
|     it('should generate render url', () => { |     it('should generate render url', () => { | ||||||
|       mockLocationHref('http://dashboards.grafana.com/d/abcdefghi/my-dash'); |       mockLocationHref('http://dashboards.grafana.com/d/abcdefghi/my-dash'); | ||||||
|       ctx.mount({ |       ctx.mount({ | ||||||
|         panel: { id: 22, options: {} }, |         panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       const state = ctx.wrapper?.state(); |       const state = ctx.wrapper?.state(); | ||||||
|  | @ -113,7 +113,7 @@ describe('ShareModal', () => { | ||||||
|     it('should generate render url for scripted dashboard', () => { |     it('should generate render url for scripted dashboard', () => { | ||||||
|       mockLocationHref('http://dashboards.grafana.com/dashboard/script/my-dash.js'); |       mockLocationHref('http://dashboards.grafana.com/dashboard/script/my-dash.js'); | ||||||
|       ctx.mount({ |       ctx.mount({ | ||||||
|         panel: { id: 22, options: {} }, |         panel: { id: 22, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       const state = ctx.wrapper?.state(); |       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', () => { |     it('should remove fullscreen from image url when is first param in querystring and modeSharePanel is true', () => { | ||||||
|       mockLocationHref('http://server/#!/test?fullscreen&edit'); |       mockLocationHref('http://server/#!/test?fullscreen&edit'); | ||||||
|       ctx.mount({ |       ctx.mount({ | ||||||
|         panel: { id: 1, options: {} }, |         panel: { id: 1, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       const state = ctx.wrapper?.state(); |       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', () => { |     it('should remove edit from image url when is first param in querystring and modeSharePanel is true', () => { | ||||||
|       mockLocationHref('http://server/#!/test?edit&fullscreen'); |       mockLocationHref('http://server/#!/test?edit&fullscreen'); | ||||||
|       ctx.mount({ |       ctx.mount({ | ||||||
|         panel: { id: 1, options: {} }, |         panel: { id: 1, options: {}, fieldConfig: { defaults: {}, overrides: [] } }, | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       const state = ctx.wrapper?.state(); |       const state = ctx.wrapper?.state(); | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ import { | ||||||
|   PanelEvents, |   PanelEvents, | ||||||
|   PanelData, |   PanelData, | ||||||
|   PanelPlugin, |   PanelPlugin, | ||||||
|  |   FieldConfigSource, | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| 
 | 
 | ||||||
| const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; | const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; | ||||||
|  | @ -217,6 +218,10 @@ export class PanelChrome extends PureComponent<Props, State> { | ||||||
|     this.props.panel.updateOptions(options); |     this.props.panel.updateOptions(options); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   onFieldConfigChange = (config: FieldConfigSource) => { | ||||||
|  |     this.props.panel.updateFieldConfig(config); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   onPanelError = (message: string) => { |   onPanelError = (message: string) => { | ||||||
|     if (this.state.errorMessage !== message) { |     if (this.state.errorMessage !== message) { | ||||||
|       this.setState({ errorMessage: message }); |       this.setState({ errorMessage: message }); | ||||||
|  | @ -281,12 +286,14 @@ export class PanelChrome extends PureComponent<Props, State> { | ||||||
|             timeRange={timeRange} |             timeRange={timeRange} | ||||||
|             timeZone={this.props.dashboard.getTimezone()} |             timeZone={this.props.dashboard.getTimezone()} | ||||||
|             options={panelOptions} |             options={panelOptions} | ||||||
|  |             fieldConfig={panel.fieldConfig} | ||||||
|             transparent={panel.transparent} |             transparent={panel.transparent} | ||||||
|             width={panelWidth} |             width={panelWidth} | ||||||
|             height={innerPanelHeight} |             height={innerPanelHeight} | ||||||
|             renderCounter={renderCounter} |             renderCounter={renderCounter} | ||||||
|             replaceVariables={panel.replaceVariables} |             replaceVariables={panel.replaceVariables} | ||||||
|             onOptionsChange={this.onOptionsChange} |             onOptionsChange={this.onOptionsChange} | ||||||
|  |             onFieldConfigChange={this.onFieldConfigChange} | ||||||
|             onChangeTimeRange={this.onChangeTimeRange} |             onChangeTimeRange={this.onChangeTimeRange} | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  | @ -15,7 +15,14 @@ import { PanelModel, DashboardModel } from '../state'; | ||||||
| import { VizPickerSearch } from './VizPickerSearch'; | import { VizPickerSearch } from './VizPickerSearch'; | ||||||
| import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; | import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; | ||||||
| import { Unsubscribable } from 'rxjs'; | 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 { | interface Props { | ||||||
|   panel: PanelModel; |   panel: PanelModel; | ||||||
|  | @ -59,6 +66,11 @@ export class VisualizationTab extends PureComponent<Props, State> { | ||||||
|     return panel.getOptions(); |     return panel.getOptions(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   getReactPanelFieldConfig = () => { | ||||||
|  |     const { panel } = this.props; | ||||||
|  |     return panel.getFieldConfig(); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   renderPanelOptions() { |   renderPanelOptions() { | ||||||
|     const { plugin, dashboard, panel } = this.props; |     const { plugin, dashboard, panel } = this.props; | ||||||
| 
 | 
 | ||||||
|  | @ -72,6 +84,10 @@ export class VisualizationTab extends PureComponent<Props, State> { | ||||||
|           data={this.state.data} |           data={this.state.data} | ||||||
|           options={this.getReactPanelOptions()} |           options={this.getReactPanelOptions()} | ||||||
|           onOptionsChange={this.onPanelOptionsChanged} |           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); |     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 = () => { |   onOpenVizPicker = () => { | ||||||
|     this.setState({ isVizPickerOpen: true, scrollTop: 0 }); |     this.setState({ isVizPickerOpen: true, scrollTop: 0 }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { PanelModel } from './PanelModel'; | import { PanelModel } from './PanelModel'; | ||||||
| import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; | import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; | ||||||
| import { PanelProps } from '@grafana/data'; | import { ConfigOverrideRule, PanelProps } from '@grafana/data'; | ||||||
| import { ComponentClass } from 'react'; | import { ComponentClass } from 'react'; | ||||||
| 
 | 
 | ||||||
| class TablePanelCtrl {} | class TablePanelCtrl {} | ||||||
|  | @ -53,9 +53,31 @@ describe('PanelModel', () => { | ||||||
|         showColumns: true, |         showColumns: true, | ||||||
|         targets: [{ refId: 'A' }, { noRefId: true }], |         targets: [{ refId: 'A' }, { noRefId: true }], | ||||||
|         options: persistedOptionsMock, |         options: persistedOptionsMock, | ||||||
|  |         fieldConfig: { | ||||||
|  |           defaults: { | ||||||
|  |             unit: 'mpg', | ||||||
|  |           }, | ||||||
|  |           overrides: [ | ||||||
|  |             { | ||||||
|  |               matcher: { | ||||||
|  |                 id: '1', | ||||||
|  |                 options: {}, | ||||||
|  |               }, | ||||||
|  |               properties: [], | ||||||
|  |             }, | ||||||
|  |           ], | ||||||
|  |         }, | ||||||
|       }; |       }; | ||||||
| 
 | 
 | ||||||
|       model = new PanelModel(modelJson); |       model = new PanelModel(modelJson); | ||||||
|  |       const overrideMock: ConfigOverrideRule = { | ||||||
|  |         matcher: { | ||||||
|  |           id: '2', | ||||||
|  |           options: {}, | ||||||
|  |         }, | ||||||
|  |         properties: [], | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|       const panelPlugin = getPanelPlugin( |       const panelPlugin = getPanelPlugin( | ||||||
|         { |         { | ||||||
|           id: 'table', |           id: 'table', | ||||||
|  | @ -64,6 +86,13 @@ describe('PanelModel', () => { | ||||||
|         TablePanelCtrl // angular
 |         TablePanelCtrl // angular
 | ||||||
|       ); |       ); | ||||||
|       panelPlugin.setDefaults(defaultOptionsMock); |       panelPlugin.setDefaults(defaultOptionsMock); | ||||||
|  |       panelPlugin.setFieldConfigDefaults({ | ||||||
|  |         defaults: { | ||||||
|  |           unit: 'flop', | ||||||
|  |           decimals: 2, | ||||||
|  |         }, | ||||||
|  |         overrides: [overrideMock], | ||||||
|  |       }); | ||||||
|       model.pluginLoaded(panelPlugin); |       model.pluginLoaded(panelPlugin); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | @ -79,6 +108,17 @@ describe('PanelModel', () => { | ||||||
|       expect(model.getOptions().arrayWith2Values.length).toBe(1); |       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', () => { |     it('should set model props on instance', () => { | ||||||
|       expect(model.showColumns).toBe(true); |       expect(model.showColumns).toBe(true); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import { | ||||||
|   PanelEvents, |   PanelEvents, | ||||||
|   PanelPlugin, |   PanelPlugin, | ||||||
|   ScopedVars, |   ScopedVars, | ||||||
|  |   FieldConfigSource, | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| import { EDIT_PANEL_ID } from 'app/core/constants'; | import { EDIT_PANEL_ID } from 'app/core/constants'; | ||||||
| 
 | 
 | ||||||
|  | @ -81,6 +82,7 @@ const mustKeepProps: { [str: string]: boolean } = { | ||||||
|   pluginVersion: true, |   pluginVersion: true, | ||||||
|   queryRunner: true, |   queryRunner: true, | ||||||
|   transformations: true, |   transformations: true, | ||||||
|  |   fieldConfig: true, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const defaults: any = { | const defaults: any = { | ||||||
|  | @ -121,6 +123,7 @@ export class PanelModel implements DataConfigSource { | ||||||
|   options: { |   options: { | ||||||
|     [key: string]: any; |     [key: string]: any; | ||||||
|   }; |   }; | ||||||
|  |   fieldConfig: FieldConfigSource; | ||||||
| 
 | 
 | ||||||
|   maxDataPoints?: number; |   maxDataPoints?: number; | ||||||
|   interval?: string; |   interval?: string; | ||||||
|  | @ -177,9 +180,19 @@ export class PanelModel implements DataConfigSource { | ||||||
|   getOptions() { |   getOptions() { | ||||||
|     return this.options; |     return this.options; | ||||||
|   } |   } | ||||||
|  |   getFieldConfig() { | ||||||
|  |     return this.fieldConfig; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   updateOptions(options: object) { |   updateOptions(options: object) { | ||||||
|     this.options = options; |     this.options = options; | ||||||
|  | 
 | ||||||
|  |     this.render(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateFieldConfig(config: FieldConfigSource) { | ||||||
|  |     this.fieldConfig = config; | ||||||
|  | 
 | ||||||
|     this.resendLastResult(); |     this.resendLastResult(); | ||||||
|     this.render(); |     this.render(); | ||||||
|   } |   } | ||||||
|  | @ -273,6 +286,23 @@ export class PanelModel implements DataConfigSource { | ||||||
|         return srcValue; |         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) { |   pluginLoaded(plugin: PanelPlugin) { | ||||||
|  | @ -382,7 +412,7 @@ export class PanelModel implements DataConfigSource { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|       fieldOptions: this.options.fieldOptions, |       fieldOptions: this.fieldConfig, | ||||||
|       replaceVariables: this.replaceVariables, |       replaceVariables: this.replaceVariables, | ||||||
|       custom: this.plugin.customFieldConfigs, |       custom: this.plugin.customFieldConfigs, | ||||||
|       theme: config.theme, |       theme: config.theme, | ||||||
|  |  | ||||||
|  | @ -43,8 +43,43 @@ describe('BarGauge Panel Migrations', () => { | ||||||
|       targets: [], |       targets: [], | ||||||
|       title: 'Usage', |       title: 'Usage', | ||||||
|       type: 'bargauge', |       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, |   PanelProps, | ||||||
|   LoadingState, |   LoadingState, | ||||||
|   dateTime, |   dateTime, | ||||||
|  |   FieldConfigSource, | ||||||
|   toDataFrame, |   toDataFrame, | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| import { BarGaugeDisplayMode } from '@grafana/ui'; | import { BarGaugeDisplayMode } from '@grafana/ui'; | ||||||
|  | @ -66,13 +67,15 @@ function createBarGaugePanelWithData(data: PanelData): ReactWrapper<PanelProps<B | ||||||
|     displayMode: BarGaugeDisplayMode.Lcd, |     displayMode: BarGaugeDisplayMode.Lcd, | ||||||
|     fieldOptions: { |     fieldOptions: { | ||||||
|       calcs: ['mean'], |       calcs: ['mean'], | ||||||
|       defaults: {}, |  | ||||||
|       values: false, |       values: false, | ||||||
|       overrides: [], |  | ||||||
|     }, |     }, | ||||||
|     orientation: VizOrientation.Horizontal, |     orientation: VizOrientation.Horizontal, | ||||||
|     showUnfilled: true, |     showUnfilled: true, | ||||||
|   }; |   }; | ||||||
|  |   const fieldConfig: FieldConfigSource = { | ||||||
|  |     defaults: {}, | ||||||
|  |     overrides: [], | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   return mount<BarGaugePanel>( |   return mount<BarGaugePanel>( | ||||||
|     <BarGaugePanel |     <BarGaugePanel | ||||||
|  | @ -81,6 +84,8 @@ function createBarGaugePanelWithData(data: PanelData): ReactWrapper<PanelProps<B | ||||||
|       timeRange={timeRange} |       timeRange={timeRange} | ||||||
|       timeZone={'utc'} |       timeZone={'utc'} | ||||||
|       options={options} |       options={options} | ||||||
|  |       fieldConfig={fieldConfig} | ||||||
|  |       onFieldConfigChange={() => {}} | ||||||
|       onOptionsChange={() => {}} |       onOptionsChange={() => {}} | ||||||
|       onChangeTimeRange={() => {}} |       onChangeTimeRange={() => {}} | ||||||
|       replaceVariables={s => s} |       replaceVariables={s => s} | ||||||
|  |  | ||||||
|  | @ -51,9 +51,10 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   getValues = (): FieldDisplay[] => { |   getValues = (): FieldDisplay[] => { | ||||||
|     const { data, options, replaceVariables } = this.props; |     const { data, options, replaceVariables, fieldConfig } = this.props; | ||||||
|     return getFieldDisplayValues({ |     return getFieldDisplayValues({ | ||||||
|       ...options, |       fieldConfig, | ||||||
|  |       fieldOptions: options.fieldOptions, | ||||||
|       replaceVariables, |       replaceVariables, | ||||||
|       theme: config.theme, |       theme: config.theme, | ||||||
|       data: data.series, |       data: data.series, | ||||||
|  |  | ||||||
|  | @ -2,83 +2,94 @@ | ||||||
| import React, { PureComponent } from 'react'; | import React, { PureComponent } from 'react'; | ||||||
| 
 | 
 | ||||||
| import { | import { | ||||||
|   ThresholdsEditor, |  | ||||||
|   ValueMappingsEditor, |  | ||||||
|   PanelOptionsGrid, |   PanelOptionsGrid, | ||||||
|   FieldDisplayEditor, |   FieldDisplayEditor, | ||||||
|   FieldPropertiesEditor, |  | ||||||
|   PanelOptionsGroup, |   PanelOptionsGroup, | ||||||
|   FormLabel, |   FormLabel, | ||||||
|   Select, |   Select, | ||||||
|   DataLinksEditor, |  | ||||||
|   Switch, |   Switch, | ||||||
|  |   FieldPropertiesEditor, | ||||||
|  |   ThresholdsEditor, | ||||||
|  |   LegacyValueMappingsEditor, | ||||||
|  |   DataLinksEditor, | ||||||
| } from '@grafana/ui'; | } from '@grafana/ui'; | ||||||
| import { | import { | ||||||
|  |   DataLink, | ||||||
|  |   FieldConfig, | ||||||
|  |   FieldDisplayOptions, | ||||||
|  |   PanelEditorProps, | ||||||
|   ThresholdsConfig, |   ThresholdsConfig, | ||||||
|   ValueMapping, |   ValueMapping, | ||||||
|   FieldDisplayOptions, |  | ||||||
|   FieldConfig, |  | ||||||
|   DataLink, |  | ||||||
|   PanelEditorProps, |  | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| import { BarGaugeOptions, displayModes } from './types'; | import { BarGaugeOptions, displayModes } from './types'; | ||||||
| import { orientationOptions } from '../gauge/types'; | import { orientationOptions } from '../gauge/types'; | ||||||
| import { | import { | ||||||
|   getDataLinksVariableSuggestions, |  | ||||||
|   getCalculationValueDataLinksVariableSuggestions, |   getCalculationValueDataLinksVariableSuggestions, | ||||||
| } from 'app/features/panel/panellinks/link_srv'; |   getDataLinksVariableSuggestions, | ||||||
|  | } from '../../../features/panel/panellinks/link_srv'; | ||||||
| 
 | 
 | ||||||
| export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> { | 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) => |   onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) => | ||||||
|     this.props.onOptionsChange({ |     this.props.onOptionsChange({ | ||||||
|       ...this.props.options, |       ...this.props.options, | ||||||
|       fieldOptions, |       fieldOptions, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|   onDefaultsChange = (field: FieldConfig) => { |  | ||||||
|     this.onDisplayOptionsChanged({ |  | ||||||
|       ...this.props.options.fieldOptions, |  | ||||||
|       defaults: field, |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); |   onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); | ||||||
|   onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value }); |   onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value }); | ||||||
|   onToggleShowUnfilled = () => { |   onToggleShowUnfilled = () => { | ||||||
|     this.props.onOptionsChange({ ...this.props.options, showUnfilled: !this.props.options.showUnfilled }); |     this.props.onOptionsChange({ ...this.props.options, showUnfilled: !this.props.options.showUnfilled }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   onDataLinksChanged = (links: DataLink[]) => { |   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||||
|     this.onDefaultsChange({ |     const current = this.props.fieldConfig; | ||||||
|       ...this.props.options.fieldOptions.defaults, |     this.props.onFieldConfigChange({ | ||||||
|       links, |       ...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 |     const suggestions = fieldOptions.values | ||||||
|       ? getDataLinksVariableSuggestions(this.props.data.series) |       ? getDataLinksVariableSuggestions(this.props.data.series) | ||||||
|       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); |       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); | ||||||
|     const labelWidth = 6; |  | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <> |       <> | ||||||
|  | @ -105,14 +116,16 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge | ||||||
|                 value={displayModes.find(item => item.value === options.displayMode)} |                 value={displayModes.find(item => item.value === options.displayMode)} | ||||||
|               /> |               /> | ||||||
|             </div> |             </div> | ||||||
|             {options.displayMode !== 'lcd' && ( |             <> | ||||||
|               <Switch |               {options.displayMode !== 'lcd' && ( | ||||||
|                 label="Unfilled" |                 <Switch | ||||||
|                 labelClass={`width-${labelWidth}`} |                   label="Unfilled" | ||||||
|                 checked={options.showUnfilled} |                   labelClass={`width-${labelWidth}`} | ||||||
|                 onChange={this.onToggleShowUnfilled} |                   checked={options.showUnfilled} | ||||||
|               /> |                   onChange={this.onToggleShowUnfilled} | ||||||
|             )} |                 /> | ||||||
|  |               )} | ||||||
|  |             </> | ||||||
|           </PanelOptionsGroup> |           </PanelOptionsGroup> | ||||||
|           <PanelOptionsGroup title="Field"> |           <PanelOptionsGroup title="Field"> | ||||||
|             <FieldPropertiesEditor |             <FieldPropertiesEditor | ||||||
|  | @ -126,7 +139,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge | ||||||
|           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> |           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> | ||||||
|         </PanelOptionsGrid> |         </PanelOptionsGrid> | ||||||
| 
 | 
 | ||||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> |         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||||
| 
 | 
 | ||||||
|         <PanelOptionsGroup title="Data links"> |         <PanelOptionsGroup title="Data links"> | ||||||
|           <DataLinksEditor |           <DataLinksEditor | ||||||
|  |  | ||||||
|  | @ -7,37 +7,6 @@ Object { | ||||||
|     "calcs": Array [ |     "calcs": Array [ | ||||||
|       "mean", |       "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 [ |     "thresholds": Array [ | ||||||
|       Object { |       Object { | ||||||
|         "color": "green", |         "color": "green", | ||||||
|  |  | ||||||
|  | @ -3,10 +3,12 @@ import { PanelPlugin } from '@grafana/data'; | ||||||
| import { BarGaugePanel } from './BarGaugePanel'; | import { BarGaugePanel } from './BarGaugePanel'; | ||||||
| import { BarGaugePanelEditor } from './BarGaugePanelEditor'; | import { BarGaugePanelEditor } from './BarGaugePanelEditor'; | ||||||
| import { BarGaugeOptions, defaults } from './types'; | import { BarGaugeOptions, defaults } from './types'; | ||||||
|  | import { standardFieldConfig } from '../stat/types'; | ||||||
| import { barGaugePanelMigrationHandler } from './BarGaugeMigrations'; | import { barGaugePanelMigrationHandler } from './BarGaugeMigrations'; | ||||||
| 
 | 
 | ||||||
| export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel) | export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel) | ||||||
|   .setDefaults(defaults) |   .setDefaults(defaults) | ||||||
|  |   .setFieldConfigDefaults(standardFieldConfig) | ||||||
|   .setEditor(BarGaugePanelEditor) |   .setEditor(BarGaugePanelEditor) | ||||||
|   .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) |   .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) | ||||||
|   .setMigrationHandler(barGaugePanelMigrationHandler); |   .setMigrationHandler(barGaugePanelMigrationHandler); | ||||||
|  |  | ||||||
|  | @ -75,9 +75,68 @@ describe('Gauge Panel Migrations', () => { | ||||||
|       timeShift: null, |       timeShift: null, | ||||||
|       title: 'Panel Title', |       title: 'Panel Title', | ||||||
|       type: 'gauge', |       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', () => { |   it('change from angular singlestat to gauge', () => { | ||||||
|  | @ -95,11 +154,12 @@ describe('Gauge Panel Migrations', () => { | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     const newOptions = gaugePanelChangedHandler({} as any, 'singlestat', old); |     const panel = {} as PanelModel; | ||||||
|     expect(newOptions.fieldOptions.defaults.unit).toBe('ms'); |     const newOptions = gaugePanelChangedHandler(panel, 'singlestat', old); | ||||||
|     expect(newOptions.fieldOptions.defaults.min).toBe(-10); |     expect(panel.fieldConfig.defaults.unit).toBe('ms'); | ||||||
|     expect(newOptions.fieldOptions.defaults.max).toBe(150); |     expect(panel.fieldConfig.defaults.min).toBe(-10); | ||||||
|     expect(newOptions.fieldOptions.defaults.decimals).toBe(7); |     expect(panel.fieldConfig.defaults.max).toBe(150); | ||||||
|  |     expect(panel.fieldConfig.defaults.decimals).toBe(7); | ||||||
|     expect(newOptions.showThresholdMarkers).toBe(true); |     expect(newOptions.showThresholdMarkers).toBe(true); | ||||||
|     expect(newOptions.showThresholdLabels).toBe(true); |     expect(newOptions.showThresholdLabels).toBe(true); | ||||||
|   }); |   }); | ||||||
|  | @ -116,10 +176,10 @@ describe('Gauge Panel Migrations', () => { | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
| 
 |     const panel = {} as PanelModel; | ||||||
|     const newOptions = gaugePanelChangedHandler({} as any, 'singlestat', old); |     gaugePanelChangedHandler(panel, 'singlestat', old); | ||||||
|     expect(newOptions.fieldOptions.defaults.unit).toBe('ms'); |     expect(panel.fieldConfig.defaults.unit).toBe('ms'); | ||||||
|     expect(newOptions.fieldOptions.defaults.min).toBe(undefined); |     expect(panel.fieldConfig.defaults.min).toBe(undefined); | ||||||
|     expect(newOptions.fieldOptions.defaults.max).toBe(undefined); |     expect(panel.fieldConfig.defaults.max).toBe(undefined); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -40,8 +40,9 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   getValues = (): FieldDisplay[] => { |   getValues = (): FieldDisplay[] => { | ||||||
|     const { data, options, replaceVariables } = this.props; |     const { data, options, replaceVariables, fieldConfig } = this.props; | ||||||
|     return getFieldDisplayValues({ |     return getFieldDisplayValues({ | ||||||
|  |       fieldConfig, | ||||||
|       fieldOptions: options.fieldOptions, |       fieldOptions: options.fieldOptions, | ||||||
|       replaceVariables, |       replaceVariables, | ||||||
|       theme: config.theme, |       theme: config.theme, | ||||||
|  |  | ||||||
|  | @ -1,29 +1,29 @@ | ||||||
| // Libraries
 | // Libraries
 | ||||||
| import React, { PureComponent } from 'react'; | import React, { PureComponent } from 'react'; | ||||||
| import { | import { | ||||||
|   ThresholdsEditor, |  | ||||||
|   PanelOptionsGrid, |   PanelOptionsGrid, | ||||||
|   ValueMappingsEditor, |  | ||||||
|   FieldDisplayEditor, |   FieldDisplayEditor, | ||||||
|   FieldPropertiesEditor, |  | ||||||
|   Switch, |   Switch, | ||||||
|   PanelOptionsGroup, |   PanelOptionsGroup, | ||||||
|  |   FieldPropertiesEditor, | ||||||
|  |   ThresholdsEditor, | ||||||
|  |   LegacyValueMappingsEditor, | ||||||
|   DataLinksEditor, |   DataLinksEditor, | ||||||
| } from '@grafana/ui'; | } from '@grafana/ui'; | ||||||
| import { | import { | ||||||
|   PanelEditorProps, |   PanelEditorProps, | ||||||
|   FieldDisplayOptions, |   FieldDisplayOptions, | ||||||
|   ThresholdsConfig, |   ThresholdsConfig, | ||||||
|   ValueMapping, |  | ||||||
|   FieldConfig, |  | ||||||
|   DataLink, |   DataLink, | ||||||
|  |   FieldConfig, | ||||||
|  |   ValueMapping, | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| 
 | 
 | ||||||
| import { GaugeOptions } from './types'; | import { GaugeOptions } from './types'; | ||||||
| import { | import { | ||||||
|   getCalculationValueDataLinksVariableSuggestions, |   getCalculationValueDataLinksVariableSuggestions, | ||||||
|   getDataLinksVariableSuggestions, |   getDataLinksVariableSuggestions, | ||||||
| } from 'app/features/panel/panellinks/link_srv'; | } from '../../../features/panel/panellinks/link_srv'; | ||||||
| 
 | 
 | ||||||
| export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> { | export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> { | ||||||
|   labelWidth = 6; |   labelWidth = 6; | ||||||
|  | @ -37,27 +37,11 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption | ||||||
|       showThresholdMarkers: !this.props.options.showThresholdMarkers, |       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 = ( |   onDisplayOptionsChanged = ( | ||||||
|     fieldOptions: FieldDisplayOptions, |     fieldOptions: FieldDisplayOptions, | ||||||
|     event?: React.SyntheticEvent<HTMLElement>, |     event?: React.SyntheticEvent<HTMLElement>, | ||||||
|     callback?: () => void |     callback?: () => void | ||||||
|   ) => |   ) => { | ||||||
|     this.props.onOptionsChange( |     this.props.onOptionsChange( | ||||||
|       { |       { | ||||||
|         ...this.props.options, |         ...this.props.options, | ||||||
|  | @ -65,38 +49,57 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption | ||||||
|       }, |       }, | ||||||
|       callback |       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) => { |   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||||
|     this.onDefaultsChange( |     const current = this.props.fieldConfig; | ||||||
|       { |     this.props.onFieldConfigChange({ | ||||||
|         ...this.props.options.fieldOptions.defaults, |       ...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, |         links, | ||||||
|       }, |       }, | ||||||
|       undefined, |     }); | ||||||
|       callback |   }; | ||||||
|     ); | 
 | ||||||
|  |   onDefaultsChange = (field: FieldConfig) => { | ||||||
|  |     this.props.onFieldConfigChange({ | ||||||
|  |       ...this.props.fieldConfig, | ||||||
|  |       defaults: field, | ||||||
|  |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     const { options } = this.props; |     const { options, fieldConfig } = this.props; | ||||||
|     const { fieldOptions, showThresholdLabels, showThresholdMarkers } = options; |     const { showThresholdLabels, showThresholdMarkers, fieldOptions } = options; | ||||||
|     const { defaults } = fieldOptions; | 
 | ||||||
|  |     const { defaults } = fieldConfig; | ||||||
| 
 | 
 | ||||||
|     const suggestions = fieldOptions.values |     const suggestions = fieldOptions.values | ||||||
|       ? getDataLinksVariableSuggestions(this.props.data.series) |       ? getDataLinksVariableSuggestions(this.props.data.series) | ||||||
|       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); |       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); | ||||||
| 
 |  | ||||||
|     return ( |     return ( | ||||||
|       <> |       <> | ||||||
|         <PanelOptionsGrid> |         <PanelOptionsGrid> | ||||||
|  | @ -128,11 +131,9 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption | ||||||
|               value={defaults} |               value={defaults} | ||||||
|             /> |             /> | ||||||
|           </PanelOptionsGroup> |           </PanelOptionsGroup> | ||||||
| 
 |  | ||||||
|           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> |           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> | ||||||
|         </PanelOptionsGrid> |         </PanelOptionsGrid> | ||||||
| 
 |         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> |  | ||||||
| 
 | 
 | ||||||
|         <PanelOptionsGroup title="Data links"> |         <PanelOptionsGroup title="Data links"> | ||||||
|           <DataLinksEditor |           <DataLinksEditor | ||||||
|  |  | ||||||
|  | @ -6,51 +6,6 @@ Object { | ||||||
|     "calcs": Array [ |     "calcs": Array [ | ||||||
|       "last", |       "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", |   "orientation": "auto", | ||||||
|   "showThresholdLabels": true, |   "showThresholdLabels": true, | ||||||
|  |  | ||||||
|  | @ -2,10 +2,12 @@ import { PanelPlugin } from '@grafana/data'; | ||||||
| import { GaugePanelEditor } from './GaugePanelEditor'; | import { GaugePanelEditor } from './GaugePanelEditor'; | ||||||
| import { GaugePanel } from './GaugePanel'; | import { GaugePanel } from './GaugePanel'; | ||||||
| import { GaugeOptions, defaults } from './types'; | import { GaugeOptions, defaults } from './types'; | ||||||
|  | import { standardFieldConfig } from '../stat/types'; | ||||||
| import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations'; | import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations'; | ||||||
| 
 | 
 | ||||||
| export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel) | export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel) | ||||||
|   .setDefaults(defaults) |   .setDefaults(defaults) | ||||||
|  |   .setFieldConfigDefaults(standardFieldConfig) | ||||||
|   .setEditor(GaugePanelEditor) |   .setEditor(GaugePanelEditor) | ||||||
|   .setPanelChangeHandler(gaugePanelChangedHandler) |   .setPanelChangeHandler(gaugePanelChangedHandler) | ||||||
|   .setMigrationHandler(gaugePanelMigrationHandler); |   .setMigrationHandler(gaugePanelMigrationHandler); | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ export const GraphPanel: React.FunctionComponent<GraphPanelProps> = ({ | ||||||
|   width, |   width, | ||||||
|   height, |   height, | ||||||
|   options, |   options, | ||||||
|  |   fieldConfig, | ||||||
|   onOptionsChange, |   onOptionsChange, | ||||||
|   onChangeTimeRange, |   onChangeTimeRange, | ||||||
| }) => { | }) => { | ||||||
|  | @ -43,6 +44,7 @@ export const GraphPanel: React.FunctionComponent<GraphPanelProps> = ({ | ||||||
|       data={data} |       data={data} | ||||||
|       timeZone={timeZone} |       timeZone={timeZone} | ||||||
|       options={options} |       options={options} | ||||||
|  |       fieldConfig={fieldConfig} | ||||||
|       onOptionsChange={onOptionsChange} |       onOptionsChange={onOptionsChange} | ||||||
|       onChangeTimeRange={onChangeTimeRange} |       onChangeTimeRange={onChangeTimeRange} | ||||||
|     > |     > | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { GraphSeriesToggler } from '@grafana/ui'; | 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 { getGraphSeriesModel } from './getGraphSeriesModel'; | ||||||
| import { Options, SeriesOptions } from './types'; | import { Options, SeriesOptions } from './types'; | ||||||
|  | @ -18,6 +18,7 @@ interface GraphPanelControllerAPI { | ||||||
| interface GraphPanelControllerProps { | interface GraphPanelControllerProps { | ||||||
|   children: (api: GraphPanelControllerAPI) => JSX.Element; |   children: (api: GraphPanelControllerAPI) => JSX.Element; | ||||||
|   options: Options; |   options: Options; | ||||||
|  |   fieldConfig: FieldConfigSource; | ||||||
|   data: PanelData; |   data: PanelData; | ||||||
|   timeZone: TimeZone; |   timeZone: TimeZone; | ||||||
|   onOptionsChange: (options: Options) => void; |   onOptionsChange: (options: Options) => void; | ||||||
|  | @ -44,7 +45,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr | ||||||
|         props.options.series, |         props.options.series, | ||||||
|         props.options.graph, |         props.options.graph, | ||||||
|         props.options.legend, |         props.options.legend, | ||||||
|         props.options.fieldOptions |         props.fieldConfig | ||||||
|       ), |       ), | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  | @ -58,7 +59,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr | ||||||
|         props.options.series, |         props.options.series, | ||||||
|         props.options.graph, |         props.options.graph, | ||||||
|         props.options.legend, |         props.options.legend, | ||||||
|         props.options.fieldOptions |         props.fieldConfig | ||||||
|       ), |       ), | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -3,15 +3,15 @@ import _ from 'lodash'; | ||||||
| import React, { PureComponent } from 'react'; | import React, { PureComponent } from 'react'; | ||||||
| 
 | 
 | ||||||
| // Types
 | // Types
 | ||||||
| import { PanelEditorProps, FieldConfig } from '@grafana/data'; | import { FieldConfig, PanelEditorProps } from '@grafana/data'; | ||||||
| import { | import { | ||||||
|   Switch, |   Switch, | ||||||
|   LegendOptions, |   LegendOptions, | ||||||
|   GraphTooltipOptions, |   GraphTooltipOptions, | ||||||
|   PanelOptionsGrid, |   PanelOptionsGrid, | ||||||
|   PanelOptionsGroup, |   PanelOptionsGroup, | ||||||
|   FieldPropertiesEditor, |  | ||||||
|   Select, |   Select, | ||||||
|  |   FieldPropertiesEditor, | ||||||
| } from '@grafana/ui'; | } from '@grafana/ui'; | ||||||
| import { Options, GraphOptions } from './types'; | import { Options, GraphOptions } from './types'; | ||||||
| import { GraphLegendEditor } from './GraphLegendEditor'; | import { GraphLegendEditor } from './GraphLegendEditor'; | ||||||
|  | @ -47,13 +47,10 @@ export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> { | ||||||
|     this.onGraphOptionsChange({ showPoints: !this.props.options.graph.showPoints }); |     this.onGraphOptionsChange({ showPoints: !this.props.options.graph.showPoints }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   onDefaultsChange = (field: FieldConfig) => { |   onDefaultsChange = (field: FieldConfig, event?: React.SyntheticEvent<HTMLElement>, callback?: () => void) => { | ||||||
|     this.props.onOptionsChange({ |     this.props.onFieldConfigChange({ | ||||||
|       ...this.props.options, |       ...this.props.fieldConfig, | ||||||
|       fieldOptions: { |       defaults: field, | ||||||
|         ...this.props.options.fieldOptions, |  | ||||||
|         defaults: field, |  | ||||||
|       }, |  | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | @ -76,7 +73,7 @@ export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> { | ||||||
|             <FieldPropertiesEditor |             <FieldPropertiesEditor | ||||||
|               showMinMax={false} |               showMinMax={false} | ||||||
|               onChange={this.onDefaultsChange} |               onChange={this.onDefaultsChange} | ||||||
|               value={this.props.options.fieldOptions.defaults} |               value={this.props.fieldConfig.defaults} | ||||||
|             /> |             /> | ||||||
|           </PanelOptionsGroup> |           </PanelOptionsGroup> | ||||||
|           <PanelOptionsGroup title="Tooltip"> |           <PanelOptionsGroup title="Tooltip"> | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ import { | ||||||
|   GraphSeriesXY, |   GraphSeriesXY, | ||||||
|   getTimeField, |   getTimeField, | ||||||
|   DataFrame, |   DataFrame, | ||||||
|   FieldDisplayOptions, |  | ||||||
|   getSeriesTimeStep, |   getSeriesTimeStep, | ||||||
|   TimeZone, |   TimeZone, | ||||||
|   hasMsResolution, |   hasMsResolution, | ||||||
|  | @ -17,6 +16,7 @@ import { | ||||||
|   DEFAULT_DATE_TIME_FORMAT, |   DEFAULT_DATE_TIME_FORMAT, | ||||||
|   FieldColor, |   FieldColor, | ||||||
|   FieldColorMode, |   FieldColorMode, | ||||||
|  |   FieldConfigSource, | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| 
 | 
 | ||||||
| import { SeriesOptions, GraphOptions } from './types'; | import { SeriesOptions, GraphOptions } from './types'; | ||||||
|  | @ -28,7 +28,7 @@ export const getGraphSeriesModel = ( | ||||||
|   seriesOptions: SeriesOptions, |   seriesOptions: SeriesOptions, | ||||||
|   graphOptions: GraphOptions, |   graphOptions: GraphOptions, | ||||||
|   legendOptions: GraphLegendEditorLegendOptions, |   legendOptions: GraphLegendEditorLegendOptions, | ||||||
|   fieldOptions?: FieldDisplayOptions |   fieldOptions?: FieldConfigSource | ||||||
| ) => { | ) => { | ||||||
|   const graphs: GraphSeriesXY[] = []; |   const graphs: GraphSeriesXY[] = []; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,9 +16,10 @@ interface Props extends PanelProps<PieChartOptions> {} | ||||||
| 
 | 
 | ||||||
| export class PieChartPanel extends PureComponent<Props> { | export class PieChartPanel extends PureComponent<Props> { | ||||||
|   render() { |   render() { | ||||||
|     const { width, height, options, data, replaceVariables } = this.props; |     const { width, height, options, data, replaceVariables, fieldConfig } = this.props; | ||||||
| 
 | 
 | ||||||
|     const values = getFieldDisplayValues({ |     const values = getFieldDisplayValues({ | ||||||
|  |       fieldConfig, | ||||||
|       fieldOptions: options.fieldOptions, |       fieldOptions: options.fieldOptions, | ||||||
|       data: data.series, |       data: data.series, | ||||||
|       theme: config.theme, |       theme: config.theme, | ||||||
|  |  | ||||||
|  | @ -1,22 +1,25 @@ | ||||||
| import React, { PureComponent } from 'react'; | import React, { PureComponent } from 'react'; | ||||||
| import { | import { | ||||||
|   PanelOptionsGrid, |   PanelOptionsGrid, | ||||||
|   ValueMappingsEditor, |  | ||||||
|   FieldDisplayEditor, |   FieldDisplayEditor, | ||||||
|   FieldPropertiesEditor, |  | ||||||
|   PanelOptionsGroup, |   PanelOptionsGroup, | ||||||
|  |   FieldPropertiesEditor, | ||||||
|  |   LegacyValueMappingsEditor, | ||||||
| } from '@grafana/ui'; | } from '@grafana/ui'; | ||||||
| import { ValueMapping, FieldConfig, PanelEditorProps, FieldDisplayOptions } from '@grafana/data'; | import { PanelEditorProps, FieldDisplayOptions, ValueMapping, FieldConfig } from '@grafana/data'; | ||||||
| 
 | 
 | ||||||
| import { PieChartOptionsBox } from './PieChartOptionsBox'; | import { PieChartOptionsBox } from './PieChartOptionsBox'; | ||||||
| import { PieChartOptions } from './types'; | import { PieChartOptions } from './types'; | ||||||
| 
 | 
 | ||||||
| export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChartOptions>> { | export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChartOptions>> { | ||||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { |   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||||
|     const current = this.props.options.fieldOptions.defaults; |     const current = this.props.fieldConfig; | ||||||
|     this.onDefaultsChange({ |     this.props.onFieldConfigChange({ | ||||||
|       ...current, |       ...current, | ||||||
|       mappings, |       defaults: { | ||||||
|  |         ...current.defaults, | ||||||
|  |         mappings, | ||||||
|  |       }, | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | @ -27,16 +30,16 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|   onDefaultsChange = (field: FieldConfig) => { |   onDefaultsChange = (field: FieldConfig) => { | ||||||
|     this.onDisplayOptionsChanged({ |     this.props.onFieldConfigChange({ | ||||||
|       ...this.props.options.fieldOptions, |       ...this.props.fieldConfig, | ||||||
|       defaults: field, |       defaults: field, | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     const { onOptionsChange, options, data } = this.props; |     const { onOptionsChange, options, data, fieldConfig, onFieldConfigChange } = this.props; | ||||||
|     const { fieldOptions } = options; |     const { fieldOptions } = options; | ||||||
|     const { defaults } = fieldOptions; |     const { defaults } = fieldConfig; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <> |       <> | ||||||
|  | @ -49,10 +52,15 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart | ||||||
|             <FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} /> |             <FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} /> | ||||||
|           </PanelOptionsGroup> |           </PanelOptionsGroup> | ||||||
| 
 | 
 | ||||||
|           <PieChartOptionsBox data={data} onOptionsChange={onOptionsChange} options={options} /> |           <PieChartOptionsBox | ||||||
|  |             data={data} | ||||||
|  |             onOptionsChange={onOptionsChange} | ||||||
|  |             options={options} | ||||||
|  |             fieldConfig={fieldConfig} | ||||||
|  |             onFieldConfigChange={onFieldConfigChange} | ||||||
|  |           /> | ||||||
|         </PanelOptionsGrid> |         </PanelOptionsGrid> | ||||||
| 
 |         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> |  | ||||||
|       </> |       </> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -5,4 +5,5 @@ import { PieChartOptions, defaults } from './types'; | ||||||
| 
 | 
 | ||||||
| export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel) | export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel) | ||||||
|   .setDefaults(defaults) |   .setDefaults(defaults) | ||||||
|  |   .setFieldConfigDefaults({ defaults: { unit: 'short' } }) | ||||||
|   .setEditor(PieChartPanelEditor); |   .setEditor(PieChartPanelEditor); | ||||||
|  |  | ||||||
|  | @ -14,8 +14,5 @@ export const defaults: PieChartOptions = { | ||||||
|   fieldOptions: { |   fieldOptions: { | ||||||
|     ...standardFieldDisplayOptions, |     ...standardFieldDisplayOptions, | ||||||
|     calcs: [ReducerID.last], |     calcs: [ReducerID.last], | ||||||
|     defaults: { |  | ||||||
|       unit: 'short', |  | ||||||
|     }, |  | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -69,10 +69,11 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   getValues = (): FieldDisplay[] => { |   getValues = (): FieldDisplay[] => { | ||||||
|     const { data, options, replaceVariables } = this.props; |     const { data, options, replaceVariables, fieldConfig } = this.props; | ||||||
| 
 | 
 | ||||||
|     return getFieldDisplayValues({ |     return getFieldDisplayValues({ | ||||||
|       ...options, |       fieldConfig, | ||||||
|  |       fieldOptions: options.fieldOptions, | ||||||
|       replaceVariables, |       replaceVariables, | ||||||
|       theme: config.theme, |       theme: config.theme, | ||||||
|       data: data.series, |       data: data.series, | ||||||
|  |  | ||||||
|  | @ -2,48 +2,53 @@ | ||||||
| import React, { PureComponent } from 'react'; | import React, { PureComponent } from 'react'; | ||||||
| 
 | 
 | ||||||
| import { | import { | ||||||
|   ThresholdsEditor, |  | ||||||
|   PanelOptionsGrid, |   PanelOptionsGrid, | ||||||
|   ValueMappingsEditor, |  | ||||||
|   FieldDisplayEditor, |   FieldDisplayEditor, | ||||||
|   FieldPropertiesEditor, |  | ||||||
|   PanelOptionsGroup, |   PanelOptionsGroup, | ||||||
|   DataLinksEditor, |  | ||||||
|   FormLabel, |   FormLabel, | ||||||
|   Select, |   Select, | ||||||
|  |   FieldPropertiesEditor, | ||||||
|  |   ThresholdsEditor, | ||||||
|  |   LegacyValueMappingsEditor, | ||||||
|  |   DataLinksEditor, | ||||||
| } from '@grafana/ui'; | } from '@grafana/ui'; | ||||||
| 
 | 
 | ||||||
| import { | import { | ||||||
|   ThresholdsConfig, |  | ||||||
|   ValueMapping, |  | ||||||
|   FieldConfig, |  | ||||||
|   DataLink, |  | ||||||
|   PanelEditorProps, |   PanelEditorProps, | ||||||
|   FieldDisplayOptions, |   FieldDisplayOptions, | ||||||
|  |   FieldConfig, | ||||||
|  |   ValueMapping, | ||||||
|  |   ThresholdsConfig, | ||||||
|  |   DataLink, | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| 
 | 
 | ||||||
| import { StatPanelOptions, colorModes, graphModes, justifyModes } from './types'; | import { StatPanelOptions, colorModes, graphModes, justifyModes } from './types'; | ||||||
| import { orientationOptions } from '../gauge/types'; | import { orientationOptions } from '../gauge/types'; | ||||||
| 
 |  | ||||||
| import { | import { | ||||||
|   getDataLinksVariableSuggestions, |  | ||||||
|   getCalculationValueDataLinksVariableSuggestions, |   getCalculationValueDataLinksVariableSuggestions, | ||||||
| } from 'app/features/panel/panellinks/link_srv'; |   getDataLinksVariableSuggestions, | ||||||
|  | } from '../../../features/panel/panellinks/link_srv'; | ||||||
| 
 | 
 | ||||||
| export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOptions>> { | export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOptions>> { | ||||||
|   onThresholdsChanged = (thresholds: ThresholdsConfig) => { |   onThresholdsChanged = (thresholds: ThresholdsConfig) => { | ||||||
|     const current = this.props.options.fieldOptions.defaults; |     const current = this.props.fieldConfig; | ||||||
|     this.onDefaultsChange({ |     this.props.onFieldConfigChange({ | ||||||
|       ...current, |       ...current, | ||||||
|       thresholds, |       defaults: { | ||||||
|  |         ...current.defaults, | ||||||
|  |         thresholds, | ||||||
|  |       }, | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   onValueMappingsChanged = (mappings: ValueMapping[]) => { |   onValueMappingsChanged = (mappings: ValueMapping[]) => { | ||||||
|     const current = this.props.options.fieldOptions.defaults; |     const current = this.props.fieldConfig; | ||||||
|     this.onDefaultsChange({ |     this.props.onFieldConfigChange({ | ||||||
|       ...current, |       ...current, | ||||||
|       mappings, |       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 }); |   onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); | ||||||
| 
 | 
 | ||||||
|   onDefaultsChange = (field: FieldConfig) => { |   onDefaultsChange = (field: FieldConfig) => { | ||||||
|     this.onDisplayOptionsChanged({ |     this.props.onFieldConfigChange({ | ||||||
|       ...this.props.options.fieldOptions, |       ...this.props.fieldConfig, | ||||||
|       defaults: field, |       defaults: field, | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   onDataLinksChanged = (links: DataLink[]) => { |   onDataLinksChanged = (links: DataLink[]) => { | ||||||
|     this.onDefaultsChange({ |     const current = this.props.fieldConfig; | ||||||
|       ...this.props.options.fieldOptions.defaults, |     this.props.onFieldConfigChange({ | ||||||
|       links, |       ...current, | ||||||
|  |       defaults: { | ||||||
|  |         ...current.defaults, | ||||||
|  |         links, | ||||||
|  |       }, | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     const { options } = this.props; |     const { options, fieldConfig } = this.props; | ||||||
|     const { fieldOptions } = options; |     const { fieldOptions } = options; | ||||||
|     const { defaults } = fieldOptions; |     const { defaults } = fieldConfig; | ||||||
|  | 
 | ||||||
|     const suggestions = fieldOptions.values |     const suggestions = fieldOptions.values | ||||||
|       ? getDataLinksVariableSuggestions(this.props.data.series) |       ? getDataLinksVariableSuggestions(this.props.data.series) | ||||||
|       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); |       : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); | ||||||
|  | @ -126,7 +136,6 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt | ||||||
|               /> |               /> | ||||||
|             </div> |             </div> | ||||||
|           </PanelOptionsGroup> |           </PanelOptionsGroup> | ||||||
| 
 |  | ||||||
|           <PanelOptionsGroup title="Field"> |           <PanelOptionsGroup title="Field"> | ||||||
|             <FieldPropertiesEditor |             <FieldPropertiesEditor | ||||||
|               showMinMax={true} |               showMinMax={true} | ||||||
|  | @ -138,8 +147,7 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt | ||||||
| 
 | 
 | ||||||
|           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> |           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} /> | ||||||
|         </PanelOptionsGrid> |         </PanelOptionsGrid> | ||||||
| 
 |         <LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> | ||||||
|         <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} /> |  | ||||||
| 
 | 
 | ||||||
|         <PanelOptionsGroup title="Data links"> |         <PanelOptionsGroup title="Data links"> | ||||||
|           <DataLinksEditor |           <DataLinksEditor | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from '@grafana/ui'; | import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from '@grafana/ui'; | ||||||
| import { PanelPlugin } from '@grafana/data'; | import { PanelPlugin } from '@grafana/data'; | ||||||
| import { StatPanelOptions, defaults } from './types'; | import { StatPanelOptions, defaults, standardFieldConfig } from './types'; | ||||||
| import { StatPanel } from './StatPanel'; | import { StatPanel } from './StatPanel'; | ||||||
| import { StatPanelEditor } from './StatPanelEditor'; | import { StatPanelEditor } from './StatPanelEditor'; | ||||||
| 
 | 
 | ||||||
| export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel) | export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel) | ||||||
|   .setDefaults(defaults) |   .setDefaults(defaults) | ||||||
|  |   .setFieldConfigDefaults(standardFieldConfig) | ||||||
|   .setEditor(StatPanelEditor) |   .setEditor(StatPanelEditor) | ||||||
|   .setNoPadding() |   .setNoPadding() | ||||||
|   .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) |   .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,12 @@ | ||||||
| import { SingleStatBaseOptions, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode } from '@grafana/ui'; | 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
 | // Structure copied from angular
 | ||||||
| export interface StatPanelOptions extends SingleStatBaseOptions { | export interface StatPanelOptions extends SingleStatBaseOptions { | ||||||
|  | @ -26,6 +33,9 @@ export const justifyModes: Array<SelectableValue<BigValueJustifyMode>> = [ | ||||||
| export const standardFieldDisplayOptions: FieldDisplayOptions = { | export const standardFieldDisplayOptions: FieldDisplayOptions = { | ||||||
|   values: false, |   values: false, | ||||||
|   calcs: [ReducerID.mean], |   calcs: [ReducerID.mean], | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const standardFieldConfig: FieldConfigSource = { | ||||||
|   defaults: { |   defaults: { | ||||||
|     thresholds: { |     thresholds: { | ||||||
|       mode: ThresholdsMode.Absolute, |       mode: ThresholdsMode.Absolute, | ||||||
|  |  | ||||||
|  | @ -1,14 +1,7 @@ | ||||||
| import { FieldConfigSource } from '@grafana/data'; |  | ||||||
| 
 |  | ||||||
| export interface Options { | export interface Options { | ||||||
|   fieldOptions: FieldConfigSource; |  | ||||||
|   showHeader: boolean; |   showHeader: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const defaults: Options = { | export const defaults: Options = { | ||||||
|   fieldOptions: { |  | ||||||
|     defaults: {}, |  | ||||||
|     overrides: [], |  | ||||||
|   }, |  | ||||||
|   showHeader: true, |   showHeader: true, | ||||||
| }; | }; | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue