diff --git a/packages/grafana-data/src/field/fieldDisplay.test.ts b/packages/grafana-data/src/field/fieldDisplay.test.ts index 83d3ea54f5a..fe61810ef97 100644 --- a/packages/grafana-data/src/field/fieldDisplay.test.ts +++ b/packages/grafana-data/src/field/fieldDisplay.test.ts @@ -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 { const options: GetFieldDisplayValuesOptions = { data: [ toDataFrame({ @@ -258,8 +254,10 @@ function createDisplayOptions(extend = {}): GetFieldDisplayValuesOptions { }, fieldOptions: { calcs: [], - defaults: {}, + }, + fieldConfig: { overrides: [], + defaults: {}, }, theme: {} as GrafanaTheme, }; diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index 647c172b650..3fe6d431ea2 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -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: { diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index 746dafc178f..5be1f8c6f24 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -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 { 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 { 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 { id: number; options: TOptions; + fieldConfig: FieldConfigSource; pluginVersion?: string; scopedVars?: ScopedVars; } @@ -98,6 +114,10 @@ export class PanelPlugin extends GrafanaPlugin editor?: ComponentClass>; customFieldConfigs?: FieldConfigEditorRegistry; defaults?: TOptions; + fieldConfigDefaults?: FieldConfigSource = { + defaults: {}, + overrides: [], + }; onPanelMigration?: PanelMigrationHandler; onPanelTypeChanged?: PanelTypeChangedHandler; noPadding?: boolean; @@ -155,6 +175,19 @@ export class PanelPlugin extends GrafanaPlugin this.customFieldConfigs = registry; return this; } + + /** + * Enables configuration of panel's default field config + */ + setFieldConfigDefaults(defaultConfig: Partial) { + this.fieldConfigDefaults = { + defaults: {}, + overrides: [], + ...defaultConfig, + }; + + return this; + } } export interface PanelMenuItem { diff --git a/packages/grafana-ui/src/components/FieldConfigs/fieldOverrides.test.ts b/packages/grafana-ui/src/components/FieldConfigs/fieldOverrides.test.ts index 4509905e29f..9c31b64fcb4 100644 --- a/packages/grafana-ui/src/components/FieldConfigs/fieldOverrides.test.ts +++ b/packages/grafana-ui/src/components/FieldConfigs/fieldOverrides.test.ts @@ -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, diff --git a/packages/grafana-ui/src/components/FieldConfigs/mappings.tsx b/packages/grafana-ui/src/components/FieldConfigs/mappings.tsx index 79b79816f33..232b2d4ee4e 100644 --- a/packages/grafana-ui/src/components/FieldConfigs/mappings.tsx +++ b/packages/grafana-ui/src/components/FieldConfigs/mappings.tsx @@ -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 ; + return ; } } diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx index fc871da90a2..6150b34bb9d 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx @@ -15,7 +15,6 @@ import { toNumberString, toIntegerOrUndefined, SelectableValue, - FieldConfig, } from '@grafana/data'; const showOptions: Array> = [ @@ -47,10 +46,6 @@ export class FieldDisplayEditor extends PureComponent { this.props.onChange({ ...this.props.value, calcs }); }; - onDefaultsChange = (value: FieldConfig) => { - this.props.onChange({ ...this.props.value, defaults: value }); - }; - onLimitChange = (event: ChangeEvent) => { this.props.onChange({ ...this.props.value, diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts index 6943f96c690..c4ca7cf443c 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts @@ -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 [], + } + `); }); }); diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts index 2526ac39904..19b1fadc778 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts @@ -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> | 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 { if (isArray(item)) { onChange(item.map(v => v.value)); } else { - onChange(item.value ? [item.value] : []); + onChange(item && item.value ? [item.value] : []); } }; diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyMappingRow.tsx similarity index 98% rename from packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx rename to packages/grafana-ui/src/components/ValueMappingsEditor/LegacyMappingRow.tsx index 7aac4db238a..6dd596efe3d 100644 --- a/packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx +++ b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyMappingRow.tsx @@ -28,7 +28,7 @@ const mappingOptions = [ { value: MappingType.RangeToText, label: 'Range' }, ]; -export default class MappingRow extends PureComponent { +export default class LegacyMappingRow extends PureComponent { constructor(props: Props) { super(props); diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.story.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.story.tsx new file mode 100644 index 00000000000..00a6aa08a62 --- /dev/null +++ b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.story.tsx @@ -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 ; +}); diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.test.tsx similarity index 87% rename from packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx rename to packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.test.tsx index 9e5ea12fc5d..58d446f0536 100644 --- a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx +++ b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.test.tsx @@ -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(); + const wrapper = shallow(); - const instance = wrapper.instance() as ValueMappingsEditor; + const instance = wrapper.instance() as LegacyValueMappingsEditor; return { instance, diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.tsx similarity index 94% rename from packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx rename to packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.tsx index 3f65525254f..145c75efe78 100644 --- a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx +++ b/packages/grafana-ui/src/components/ValueMappingsEditor/LegacyValueMappingsEditor.tsx @@ -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 { +export class LegacyValueMappingsEditor extends PureComponent { constructor(props: Props) { super(props); @@ -91,7 +91,7 @@ export class ValueMappingsEditor extends PureComponent {
{valueMappings.length > 0 && valueMappings.map((valueMapping, index) => ( - { - return ; -}); diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/ValueMappingsEditor.test.tsx.snap b/packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/LegacyValueMappingsEditor.test.tsx.snap similarity index 95% rename from packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/ValueMappingsEditor.test.tsx.snap rename to packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/LegacyValueMappingsEditor.test.tsx.snap index 11257b7a47e..1698145ba54 100644 --- a/packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/ValueMappingsEditor.test.tsx.snap +++ b/packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/LegacyValueMappingsEditor.test.tsx.snap @@ -5,7 +5,7 @@ exports[`Render should render component 1`] = ` title="Value mappings" >
- - { 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 ( - + ); } @@ -130,7 +122,13 @@ export class PanelEditorUnconnected extends PureComponent { if (plugin.editor && panel) { return (
- +
); } diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx index e1a99f7419d..10caf5aeb5e 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx @@ -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(); diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 98d7ccd9aad..cd0f048bbb0 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -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 { 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 { 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} />
diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index f8d64d2c07d..0dbbe8c9230 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -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 { 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 { 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 { 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 }); }; diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 60f209949ab..16058904db3 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -1,6 +1,6 @@ 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); }); diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 8b37445e8cd..d9ecb4fc6c1 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -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, diff --git a/public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts b/public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts index 73bb3504007..0786c5923c1 100644 --- a/public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts +++ b/public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts @@ -43,8 +43,43 @@ describe('BarGauge Panel Migrations', () => { targets: [], title: 'Usage', type: 'bargauge', - } as PanelModel; + } as Omit; - 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 [], + } + `); }); }); diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx index ee4089a05a7..6d8981f4f31 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx @@ -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( {}} onOptionsChange={() => {}} onChangeTimeRange={() => {}} replaceVariables={s => s} diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx index 03cec8bdaae..8170233a795 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx @@ -51,9 +51,10 @@ export class BarGaugePanel extends PureComponent> { }; 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, diff --git a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx index 6e94448647b..3ac40f21a1a 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx @@ -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 { + DataLink, + FieldConfig, + FieldDisplayOptions, + PanelEditorProps, ThresholdsConfig, ValueMapping, - FieldDisplayOptions, - FieldConfig, - DataLink, - PanelEditorProps, } 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> { - 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) => this.props.onOptionsChange({ ...this.props.options, fieldOptions, }); - onDefaultsChange = (field: FieldConfig) => { - this.onDisplayOptionsChanged({ - ...this.props.options.fieldOptions, - 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, + onThresholdsChanged = (thresholds: ThresholdsConfig) => { + const current = this.props.fieldConfig; + this.props.onFieldConfigChange({ + ...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, 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 ? getDataLinksVariableSuggestions(this.props.data.series) : getCalculationValueDataLinksVariableSuggestions(this.props.data.series); - const labelWidth = 6; return ( <> @@ -105,14 +116,16 @@ export class BarGaugePanelEditor extends PureComponent item.value === options.displayMode)} />
- {options.displayMode !== 'lcd' && ( - - )} + <> + {options.displayMode !== 'lcd' && ( + + )} + - + (BarGaugePanel) .setDefaults(defaults) + .setFieldConfigDefaults(standardFieldConfig) .setEditor(BarGaugePanelEditor) .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) .setMigrationHandler(barGaugePanelMigrationHandler); diff --git a/public/app/plugins/panel/gauge/GaugeMigrations.test.ts b/public/app/plugins/panel/gauge/GaugeMigrations.test.ts index 3265549af10..3e5e13c8dc6 100644 --- a/public/app/plugins/panel/gauge/GaugeMigrations.test.ts +++ b/public/app/plugins/panel/gauge/GaugeMigrations.test.ts @@ -75,9 +75,68 @@ describe('Gauge Panel Migrations', () => { timeShift: null, title: 'Panel Title', type: 'gauge', - } as PanelModel; + } as Omit; - 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); }); }); diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 46620de7a97..585d21df83b 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -40,8 +40,9 @@ export class GaugePanel extends PureComponent> { }; 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, diff --git a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx index c9b7884aa62..4373b90813f 100644 --- a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx +++ b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx @@ -1,29 +1,29 @@ // 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> { labelWidth = 6; @@ -37,27 +37,11 @@ export class GaugePanelEditor extends PureComponent { - 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, callback?: () => void - ) => + ) => { this.props.onOptionsChange( { ...this.props.options, @@ -65,38 +49,57 @@ export class GaugePanelEditor extends PureComponent, callback?: () => void) => { - this.onDisplayOptionsChanged( - { - ...this.props.options.fieldOptions, - defaults: field, - }, - event, - callback - ); }; - onDataLinksChanged = (links: DataLink[], callback?: () => void) => { - this.onDefaultsChange( - { - ...this.props.options.fieldOptions.defaults, + onThresholdsChanged = (thresholds: ThresholdsConfig) => { + const current = this.props.fieldConfig; + this.props.onFieldConfigChange({ + ...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, }, - 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 ( <> @@ -128,11 +131,9 @@ export class GaugePanelEditor extends PureComponent - - - + (GaugePanel) .setDefaults(defaults) + .setFieldConfigDefaults(standardFieldConfig) .setEditor(GaugePanelEditor) .setPanelChangeHandler(gaugePanelChangedHandler) .setMigrationHandler(gaugePanelMigrationHandler); diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index 092d835303b..f8f383ba35d 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -14,6 +14,7 @@ export const GraphPanel: React.FunctionComponent = ({ width, height, options, + fieldConfig, onOptionsChange, onChangeTimeRange, }) => { @@ -43,6 +44,7 @@ export const GraphPanel: React.FunctionComponent = ({ data={data} timeZone={timeZone} options={options} + fieldConfig={fieldConfig} onOptionsChange={onOptionsChange} onChangeTimeRange={onChangeTimeRange} > diff --git a/public/app/plugins/panel/graph2/GraphPanelController.tsx b/public/app/plugins/panel/graph2/GraphPanelController.tsx index 75178906bf3..74d6f6a0c21 100644 --- a/public/app/plugins/panel/graph2/GraphPanelController.tsx +++ b/public/app/plugins/panel/graph2/GraphPanelController.tsx @@ -1,6 +1,6 @@ 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> { 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, callback?: () => void) => { + this.props.onFieldConfigChange({ + ...this.props.fieldConfig, + defaults: field, }); }; @@ -76,7 +73,7 @@ export class GraphPanelEditor extends PureComponent> { diff --git a/public/app/plugins/panel/graph2/getGraphSeriesModel.ts b/public/app/plugins/panel/graph2/getGraphSeriesModel.ts index 98cc37b478f..df53ab5c171 100644 --- a/public/app/plugins/panel/graph2/getGraphSeriesModel.ts +++ b/public/app/plugins/panel/graph2/getGraphSeriesModel.ts @@ -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[] = []; diff --git a/public/app/plugins/panel/piechart/PieChartPanel.tsx b/public/app/plugins/panel/piechart/PieChartPanel.tsx index ef5f2c68da6..be743d046d2 100644 --- a/public/app/plugins/panel/piechart/PieChartPanel.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanel.tsx @@ -16,9 +16,10 @@ interface Props extends PanelProps {} export class PieChartPanel extends PureComponent { 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, diff --git a/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx b/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx index df61b02d404..1d0f94f4805 100644 --- a/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx @@ -1,22 +1,25 @@ 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> { 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 { - 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 - + - - + ); } diff --git a/public/app/plugins/panel/piechart/module.tsx b/public/app/plugins/panel/piechart/module.tsx index c5dedf9ac67..5782ecc2131 100644 --- a/public/app/plugins/panel/piechart/module.tsx +++ b/public/app/plugins/panel/piechart/module.tsx @@ -5,4 +5,5 @@ import { PieChartOptions, defaults } from './types'; export const plugin = new PanelPlugin(PieChartPanel) .setDefaults(defaults) + .setFieldConfigDefaults({ defaults: { unit: 'short' } }) .setEditor(PieChartPanelEditor); diff --git a/public/app/plugins/panel/piechart/types.ts b/public/app/plugins/panel/piechart/types.ts index 4405fc7013a..204bb9674dc 100644 --- a/public/app/plugins/panel/piechart/types.ts +++ b/public/app/plugins/panel/piechart/types.ts @@ -14,8 +14,5 @@ export const defaults: PieChartOptions = { fieldOptions: { ...standardFieldDisplayOptions, calcs: [ReducerID.last], - defaults: { - unit: 'short', - }, }, }; diff --git a/public/app/plugins/panel/stat/StatPanel.tsx b/public/app/plugins/panel/stat/StatPanel.tsx index bccb76f7a73..7ae461e0c3c 100644 --- a/public/app/plugins/panel/stat/StatPanel.tsx +++ b/public/app/plugins/panel/stat/StatPanel.tsx @@ -69,10 +69,11 @@ export class StatPanel extends PureComponent> { }; 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, diff --git a/public/app/plugins/panel/stat/StatPanelEditor.tsx b/public/app/plugins/panel/stat/StatPanelEditor.tsx index 2530c11c892..be25c77bad6 100644 --- a/public/app/plugins/panel/stat/StatPanelEditor.tsx +++ b/public/app/plugins/panel/stat/StatPanelEditor.tsx @@ -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> { 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 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 - - - + (StatPanel) .setDefaults(defaults) + .setFieldConfigDefaults(standardFieldConfig) .setEditor(StatPanelEditor) .setNoPadding() .setPanelChangeHandler(sharedSingleStatPanelChangedHandler) diff --git a/public/app/plugins/panel/stat/types.ts b/public/app/plugins/panel/stat/types.ts index 1caae1bda7f..e90d16bed27 100644 --- a/public/app/plugins/panel/stat/types.ts +++ b/public/app/plugins/panel/stat/types.ts @@ -1,5 +1,12 @@ 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> = [ export const standardFieldDisplayOptions: FieldDisplayOptions = { values: false, calcs: [ReducerID.mean], +}; + +export const standardFieldConfig: FieldConfigSource = { defaults: { thresholds: { mode: ThresholdsMode.Absolute, diff --git a/public/app/plugins/panel/table2/types.ts b/public/app/plugins/panel/table2/types.ts index 8a4cf873f47..6814ce6a60c 100644 --- a/public/app/plugins/panel/table2/types.ts +++ b/public/app/plugins/panel/table2/types.ts @@ -1,14 +1,7 @@ -import { FieldConfigSource } from '@grafana/data'; - export interface Options { - fieldOptions: FieldConfigSource; showHeader: boolean; } export const defaults: Options = { - fieldOptions: { - defaults: {}, - overrides: [], - }, showHeader: true, }; diff --git a/public/e2e-tests/videos/queryVariableCrud.spec.ts.mp4 b/public/e2e-tests/videos/queryVariableCrud.spec.ts.mp4 new file mode 100644 index 00000000000..09f347c8472 Binary files /dev/null and b/public/e2e-tests/videos/queryVariableCrud.spec.ts.mp4 differ diff --git a/public/e2e-tests/videos/smoketests.spec.ts.mp4 b/public/e2e-tests/videos/smoketests.spec.ts.mp4 new file mode 100644 index 00000000000..848ea794b0e Binary files /dev/null and b/public/e2e-tests/videos/smoketests.spec.ts.mp4 differ