Commit bf7579d9 by Dominik Prokop Committed by GitHub

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