diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx index 0c7858aef69..8205599e936 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx @@ -1,5 +1,5 @@ // Libraries -import React, { PureComponent, ChangeEvent } from 'react'; +import React, { ChangeEvent, useState, useCallback } from 'react'; // Components import { FormField } from '../FormField/FormField'; @@ -8,7 +8,7 @@ import { UnitPicker } from '../UnitPicker/UnitPicker'; // Types import { Field } from '../../types/data'; -import { toNumberString, toIntegerOrUndefined } from '../../utils'; +import { toIntegerOrUndefined } from '../../utils'; import { SelectOptionItem } from '../Select/Select'; import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay'; @@ -21,92 +21,108 @@ export interface Props { onChange: (value: Partial, event?: React.SyntheticEvent) => void; } -export class FieldPropertiesEditor extends PureComponent { - onTitleChange = (event: ChangeEvent) => - this.props.onChange({ ...this.props.value, title: event.target.value }); +export const FieldPropertiesEditor: React.FC = ({ value, onChange, showMinMax }) => { + const { unit, title } = value; - // @ts-ignore - onUnitChange = (unit: SelectOptionItem) => this.props.onChange({ ...this.props.value, unit: unit.value }); + const [decimals, setDecimals] = useState( + value.decimals !== undefined && value.decimals !== null ? value.decimals.toString() : '' + ); + const [min, setMin] = useState(value.min !== undefined && value.min !== null ? value.min.toString() : ''); + const [max, setMax] = useState(value.max !== undefined && value.max !== null ? value.max.toString() : ''); - onDecimalChange = (event: ChangeEvent) => { - this.props.onChange({ - ...this.props.value, - decimals: toIntegerOrUndefined(event.target.value), - }); + const onTitleChange = (event: ChangeEvent) => { + onChange({ ...value, title: event.target.value }); }; - onMinChange = (event: ChangeEvent) => { - this.props.onChange({ - ...this.props.value, - min: toIntegerOrUndefined(event.target.value), - }); + const onDecimalChange = useCallback( + (event: ChangeEvent) => { + setDecimals(event.target.value); + }, + [value.decimals, onChange] + ); + + const onMinChange = useCallback( + (event: ChangeEvent) => { + setMin(event.target.value); + }, + [value.min, onChange] + ); + + const onMaxChange = useCallback( + (event: ChangeEvent) => { + setMax(event.target.value); + }, + [value.max, onChange] + ); + + const onUnitChange = (unit: SelectOptionItem) => { + onChange({ ...value, unit: unit.value }); }; - onMaxChange = (event: ChangeEvent) => { - this.props.onChange({ - ...this.props.value, - max: toIntegerOrUndefined(event.target.value), + const commitChanges = useCallback(() => { + onChange({ + ...value, + decimals: toIntegerOrUndefined(decimals), + min: toIntegerOrUndefined(min), + max: toIntegerOrUndefined(max), }); - }; + }, [min, max, decimals]); - render() { - const { showMinMax } = this.props; - const { unit, decimals, min, max } = this.props.value; + const titleTooltip = ( +
+ Template Variables: +
+ {'$' + VAR_SERIES_NAME} +
+ {'$' + VAR_FIELD_NAME} +
+ {'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC} +
+ ); + return ( + <> + - const titleTooltip = ( -
- Template Variables: -
- {'$' + VAR_SERIES_NAME} -
- {'$' + VAR_FIELD_NAME} -
- {'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC} +
+ Unit +
- ); - - return ( - <> - - -
- Unit - -
- {showMinMax && ( - <> - - - - )} - - - ); - } -} + {showMinMax && ( + <> + + + + )} + + + ); +}; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index bbebbbf2f65..e8a13cdeb71 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -253,7 +253,7 @@ export class PanelChrome extends PureComponent { id={panel.id} data={data} timeRange={data.request ? data.request.range : this.timeSrv.timeRange()} - options={panel.getOptions(plugin.defaults)} + options={panel.getOptions()} width={width - theme.panelPadding * 2} height={innerPanelHeight} renderCounter={renderCounter} diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index eb7bad65242..0eb352ca806 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -53,8 +53,8 @@ export class VisualizationTab extends PureComponent { } getReactPanelOptions = () => { - const { panel, plugin } = this.props; - return panel.getOptions(plugin.defaults); + const { panel } = this.props; + return panel.getOptions(); }; renderPanelOptions() { diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index f4d9e8d1a66..3dcb5f5cb06 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -7,45 +7,70 @@ describe('PanelModel', () => { describe('when creating new panel model', () => { let model; let modelJson; + let persistedOptionsMock; + const defaultOptionsMock = { + fieldOptions: { + thresholds: [ + { + color: '#F2495C', + index: 1, + value: 50, + }, + { + color: '#73BF69', + index: 0, + value: null, + }, + ], + }, + showThresholds: true, + }; beforeEach(() => { + persistedOptionsMock = { + fieldOptions: { + thresholds: [ + { + color: '#F2495C', + index: 1, + value: 50, + }, + { + color: '#73BF69', + index: 0, + value: null, + }, + ], + }, + }; + modelJson = { type: 'table', showColumns: true, targets: [{ refId: 'A' }, { noRefId: true }], - options: { - fieldOptions: { - thresholds: [ - { - color: '#F2495C', - index: 1, - value: 50, - }, - { - color: '#73BF69', - index: 0, - value: null, - }, - ], - }, - }, + options: persistedOptionsMock, }; + model = new PanelModel(modelJson); - model.pluginLoaded( - getPanelPlugin( - { - id: 'table', - }, - null, // react - TablePanelCtrl // angular - ) + const panelPlugin = getPanelPlugin( + { + id: 'table', + }, + null, // react + TablePanelCtrl // angular ); + panelPlugin.setDefaults(defaultOptionsMock); + model.pluginLoaded(panelPlugin); }); it('should apply defaults', () => { expect(model.gridPos.h).toBe(3); }); + it('should apply option defaults', () => { + expect(model.getOptions().showThresholds).toBeTruthy(); + }); + it('should set model props on instance', () => { expect(model.showColumns).toBe(true); }); @@ -89,11 +114,22 @@ describe('PanelModel', () => { }); describe('when changing panel type', () => { + const newPanelPluginDefaults = { + showThresholdLabels: false, + }; + beforeEach(() => { - model.changePlugin(getPanelPlugin({ id: 'graph' })); + const newPlugin = getPanelPlugin({ id: 'graph' }); + newPlugin.setDefaults(newPanelPluginDefaults); + model.changePlugin(newPlugin); model.alert = { id: 2 }; }); + it('should apply next panel option defaults', () => { + expect(model.getOptions().showThresholdLabels).toBeFalsy(); + expect(model.getOptions().showThresholds).toBeUndefined(); + }); + it('should remove table properties but keep core props', () => { expect(model.showColumns).toBe(undefined); }); @@ -153,19 +189,5 @@ describe('PanelModel', () => { expect(panelQueryRunner).toBe(sameQueryRunner); }); }); - - describe('get panel options', () => { - it('should apply defaults', () => { - model.options = { existingProp: 10 }; - const options = model.getOptions({ - defaultProp: true, - existingProp: 0, - }); - - expect(options.defaultProp).toBe(true); - expect(options.existingProp).toBe(10); - expect(model.options).toBe(options); - }); - }); }); }); diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 156ae6dfa72..044d097e86a 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -157,8 +157,8 @@ export class PanelModel { } } - getOptions(panelDefaults: any) { - return _.defaultsDeep(this.options || {}, panelDefaults); + getOptions() { + return this.options; } updateOptions(options: object) { @@ -179,7 +179,6 @@ export class PanelModel { model[property] = _.cloneDeep(this[property]); } - return model; } @@ -247,9 +246,18 @@ export class PanelModel { }); } + private applyPluginOptionDefaults(plugin: PanelPlugin) { + if (plugin.angularConfigCtrl) { + return; + } + this.options = _.defaultsDeep({}, this.options || {}, plugin.defaults); + } + pluginLoaded(plugin: PanelPlugin) { this.plugin = plugin; + this.applyPluginOptionDefaults(plugin); + if (plugin.panel && plugin.onPanelMigration) { const version = getPluginVersion(plugin); if (version !== this.pluginVersion) { @@ -284,7 +292,7 @@ export class PanelModel { // switch this.type = pluginId; this.plugin = newPlugin; - + this.applyPluginOptionDefaults(newPlugin); // Let panel plugins inspect options from previous panel and keep any that it can use if (newPlugin.onPanelTypeChanged) { this.options = this.options || {};