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
co-authored by Torkel Ödegaard
parent d99a67075f
commit bf7579d984
49 changed files with 679 additions and 475 deletions
@@ -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: {
+34 -1
View File
@@ -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,
@@ -1,7 +1,7 @@
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,66 +31,15 @@ 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,
};
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;
return migrateFromAngularSinglestat(panel, prevOptions);
}
for (const k of optionsToKeep) {
@@ -99,6 +47,70 @@ export function sharedSingleStatPanelChangedHandler(
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;
}
@@ -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;
}
@@ -1,75 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sharedSingleStatMigrationHandler Remove unused \`overrides\` option 1`] = `
Object {
"fieldOptions": Object {
"decimals": 5,
"defaults": Object {
"mappings": undefined,
"max": 100,
"min": 0,
"thresholds": undefined,
},
"overrides": Array [],
"stat": "last",
"unit": "watt",
},
}
`;
exports[`sharedSingleStatMigrationHandler from old valueOptions model without pluginVersion 1`] = `
Object {
"fieldOptions": Object {
"calcs": Array [
"last",
],
"defaults": Object {
"color": Object {
"mode": "thresholds",
},
"decimals": 5,
"mappings": Array [
Object {
"text": "OK",
"type": 1,
"value": "1",
},
],
"max": 100,
"min": 10,
"thresholds": Object {
"mode": "absolute",
"steps": Array [
Object {
"color": "green",
"index": 0,
"value": -Infinity,
},
Object {
"color": "orange",
"index": 1,
"value": 40,
},
Object {
"color": "red",
"index": 2,
"value": 80,
},
],
},
"unit": "watt",
},
},
}
`;
exports[`sharedSingleStatMigrationHandler move thresholds to scale 1`] = `
Object {
"fieldOptions": Object {
"defaults": Object {
"mappings": undefined,
"thresholds": undefined,
},
},
}
`;
@@ -58,7 +58,7 @@ export class StatsPicker extends PureComponent<Props> {
if (isArray(item)) {
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);
@@ -0,0 +1,10 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { LegacyValueMappingsEditor } from './LegacyValueMappingsEditor';
const ValueMappingsEditorStories = storiesOf('Panel/LegacyValueMappingsEditor', module);
ValueMappingsEditorStories.add('default', () => {
return <LegacyValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />;
});
@@ -1,7 +1,7 @@
import React from 'react';
import { 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,
@@ -1,6 +1,6 @@
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}
@@ -1,10 +0,0 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { ValueMappingsEditor } from './ValueMappingsEditor';
const ValueMappingsEditorStories = storiesOf('Panel/ValueMappingsEditor', module);
ValueMappingsEditorStories.add('default', () => {
return <ValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />;
});
@@ -5,7 +5,7 @@ exports[`Render should render component 1`] = `
title="Value mappings"
>
<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]}
+1 -1
View File
@@ -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';