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:
Dominik Prokop 2020-03-19 11:50:31 +01:00 committed by GitHub
parent d99a67075f
commit bf7579d984
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 679 additions and 475 deletions

View File

@ -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,
}; };

View File

@ -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: {

View File

@ -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 {

View File

@ -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,

View File

@ -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} />;
} }
} }

View File

@ -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,

View File

@ -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 [],
}
`);
}); });
}); });

View File

@ -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;
} }

View File

@ -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,
},
},
}
`;

View File

@ -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] : []);
} }
}; };

View File

@ -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);

View File

@ -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')} />;
});

View File

@ -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,

View File

@ -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}

View File

@ -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')} />;
});

View File

@ -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]}

View File

@ -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';

View File

@ -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>
); );
} }

View File

@ -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();

View File

@ -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>

View File

@ -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 });
}; };

View File

@ -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);
}); });

View File

@ -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,

View File

@ -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 [],
}
`);
}); });
}); });

View File

@ -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}

View File

@ -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,

View File

@ -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

View File

@ -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",

View File

@ -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);

View File

@ -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);
}); });
}); });

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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}
> >

View File

@ -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
), ),
}; };
} }

View File

@ -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">

View File

@ -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[] = [];

View File

@ -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,

View File

@ -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} />
</> </>
); );
} }

View File

@ -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);

View File

@ -14,8 +14,5 @@ export const defaults: PieChartOptions = {
fieldOptions: { fieldOptions: {
...standardFieldDisplayOptions, ...standardFieldDisplayOptions,
calcs: [ReducerID.last], calcs: [ReducerID.last],
defaults: {
unit: 'short',
},
}, },
}; };

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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.