diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx index a05c67dad54..a797123d05c 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx @@ -106,7 +106,6 @@ export const OptionsPaneContent: React.FC = ({ dashboard={dashboard} data={currentData} onPanelConfigChange={onPanelConfigChange} - onFieldConfigsChange={onFieldConfigsChange} onPanelOptionsChanged={onPanelOptionsChanged} /> ) : ( diff --git a/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx b/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx index 85a741305eb..f6797ca85bc 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx @@ -1,6 +1,6 @@ import React, { FC, useMemo, useRef } from 'react'; import { DashboardModel, PanelModel } from '../../state'; -import { FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; +import { PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; import { Counter, DataLinksInlineEditor, Field, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui'; import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv'; import { getVariables } from '../../../variables/state/selectors'; @@ -16,7 +16,6 @@ interface Props { dashboard: DashboardModel; onPanelConfigChange: (configKey: string, value: any) => void; onPanelOptionsChanged: (options: any) => void; - onFieldConfigsChange: (config: FieldConfigSource) => void; } export const PanelOptionsTab: FC = ({ @@ -26,7 +25,6 @@ export const PanelOptionsTab: FC = ({ dashboard, onPanelConfigChange, onPanelOptionsChanged, - onFieldConfigsChange, }) => { const visTabInputRef = useRef(); const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []); diff --git a/public/app/features/dashboard/panel_editor/QueryOptions.tsx b/public/app/features/dashboard/panel_editor/QueryOptions.tsx index 17b962937a0..62b9c3d3cd7 100644 --- a/public/app/features/dashboard/panel_editor/QueryOptions.tsx +++ b/public/app/features/dashboard/panel_editor/QueryOptions.tsx @@ -1,5 +1,5 @@ // Libraries -import React, { PureComponent, ChangeEvent, FocusEvent, ReactText } from 'react'; +import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; // Utils import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; @@ -49,7 +49,7 @@ interface State { relativeTime: string; timeShift: string; cacheTimeout: string; - maxDataPoints: string | ReactText; + maxDataPoints: number | string; interval: string; hideTimeOverride: boolean; isOpen: boolean; @@ -63,7 +63,7 @@ export class QueryOptions extends PureComponent { relativeTime: props.panel.timeFrom || '', timeShift: props.panel.timeShift || '', cacheTimeout: props.panel.cacheTimeout || '', - maxDataPoints: props.panel.maxDataPoints || '', + maxDataPoints: props.panel.maxDataPoints ?? '', interval: props.panel.interval || '', hideTimeOverride: props.panel.hideTimeOverride || false, isOpen: false, @@ -124,6 +124,20 @@ export class QueryOptions extends PureComponent { this.setState({ ...this.state, [panelKey]: event.target.value }); }; + onMaxDataPointsBlur = () => { + const { panel } = this.props; + + const maxDataPoints = parseInt(this.state.maxDataPoints as string, 10); + + if (isNaN(maxDataPoints)) { + delete panel.maxDataPoints; + } else { + panel.maxDataPoints = maxDataPoints; + } + + panel.refresh(); + }; + renderCacheTimeoutOption() { const { dataSource } = this.props; const { cacheTimeout } = this.state; @@ -176,12 +190,12 @@ export class QueryOptions extends PureComponent { Max data points diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 1f6913fde59..7fa4600b4aa 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -86,6 +86,8 @@ describe('PanelModel', () => { modelJson = { type: 'table', + maxDataPoints: 100, + interval: '5m', showColumns: true, targets: [{ refId: 'A' }, { noRefId: true }], options: persistedOptionsMock, @@ -225,6 +227,14 @@ describe('PanelModel', () => { expect(model.editSourceId).toBe(1001); }); + it('should keep maxDataPoints', () => { + expect(model.maxDataPoints).toBe(100); + }); + + it('should keep interval', () => { + expect(model.interval).toBe('5m'); + }); + it('should apply next panel option defaults', () => { expect(model.getOptions().showThresholdLabels).toBeFalsy(); expect(model.getOptions().showThresholds).toBeUndefined(); @@ -313,18 +323,6 @@ describe('PanelModel', () => { expect(model.someProperty).toBeUndefined(); }); - it('Should preserve must keep properties', () => { - model.id = 10; - model.gridPos = { x: 0, y: 0, h: 10, w: 10 }; - model.restoreModel({ - title: 'New title', - options: {}, - }); - - expect(model.id).toBe(10); - expect(model.gridPos.h).toBe(10); - }); - it('Should remove old angular panel specfic props', () => { model.axes = [{ prop: 1 }]; model.thresholds = []; @@ -337,6 +335,13 @@ describe('PanelModel', () => { expect(model.axes).toBeUndefined(); expect(model.thresholds).toBeUndefined(); }); + + it('Should be able to set defaults back to default', () => { + model.transparent = true; + + model.restoreModel({}); + expect(model.transparent).toBe(false); + }); }); describe('destroy', () => { diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 452f00f15a6..e0aa0bb019a 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -82,6 +82,8 @@ const mustKeepProps: { [str: string]: boolean } = { transformations: true, fieldConfig: true, editSourceId: true, + maxDataPoints: true, + interval: true, }; const defaults: any = { @@ -90,6 +92,7 @@ const defaults: any = { cachedPluginOptions: {}, transparent: false, options: {}, + datasource: null, }; export class PanelModel implements DataConfigSource { @@ -146,9 +149,6 @@ export class PanelModel implements DataConfigSource { constructor(model: any) { this.events = new Emitter(); - // should not be part of defaults as defaults are removed in save model and - // this should not be removed in save model as exporter needs to templatize it - this.datasource = null; this.restoreModel(model); this.replaceVariables = this.replaceVariables.bind(this); } @@ -156,12 +156,8 @@ export class PanelModel implements DataConfigSource { /** Given a persistened PanelModel restores property values */ restoreModel(model: any) { // Start with clean-up - for (const property of Object.keys(this)) { - if (notPersistedProperties[property]) { - continue; - } - - if (mustKeepProps[property]) { + for (const property in this) { + if (notPersistedProperties[property] || !this.hasOwnProperty(property)) { continue; } @@ -169,10 +165,6 @@ export class PanelModel implements DataConfigSource { continue; } - if (!this.hasOwnProperty(property)) { - continue; - } - if (typeof (this as any)[property] === 'function') { continue; } @@ -215,7 +207,6 @@ export class PanelModel implements DataConfigSource { updateOptions(options: object) { this.options = options; - this.render(); } @@ -228,6 +219,7 @@ export class PanelModel implements DataConfigSource { getSaveModel() { const model: any = {}; + for (const property in this) { if (notPersistedProperties[property] || !this.hasOwnProperty(property)) { continue; @@ -239,6 +231,13 @@ export class PanelModel implements DataConfigSource { model[property] = _.cloneDeep(this[property]); } + + if (model.datasource === undefined) { + // This is part of defaults as defaults are removed in save model and + // this should not be removed in save model as exporter needs to templatize it + model.datasource = null; + } + return model; }