mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
d99a67075f
commit
bf7579d984
@ -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,
|
||||
|
@ -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]}
|
@ -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 });
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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 {
|
||||
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<PanelEditorProps<BarGaugeOptions>> {
|
||||
onThresholdsChanged = (thresholds: ThresholdsConfig) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
thresholds,
|
||||
});
|
||||
};
|
||||
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
mappings,
|
||||
});
|
||||
};
|
||||
|
||||
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
|
||||
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<HTMLElement>, callback?: () => void) => {
|
||||
this.props.onFieldConfigChange({
|
||||
...this.props.fieldConfig,
|
||||
defaults: field,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { options, fieldConfig } = this.props;
|
||||
const { fieldOptions } = options;
|
||||
const { defaults } = fieldConfig;
|
||||
|
||||
const labelWidth = 6;
|
||||
const suggestions = fieldOptions.values
|
||||
? 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,
|
||||
|
@ -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<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,
|
||||
},
|
||||
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 (
|
||||
<>
|
||||
<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}
|
||||
>
|
||||
|
@ -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<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,
|
||||
|
@ -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<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
|
||||
|
@ -1,11 +1,12 @@
|
||||
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)
|
||||
|
@ -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<SelectableValue<BigValueJustifyMode>> = [
|
||||
export const standardFieldDisplayOptions: FieldDisplayOptions = {
|
||||
values: false,
|
||||
calcs: [ReducerID.mean],
|
||||
};
|
||||
|
||||
export const standardFieldConfig: FieldConfigSource = {
|
||||
defaults: {
|
||||
thresholds: {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
|
@ -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,
|
||||
};
|
||||
|
BIN
public/e2e-tests/videos/queryVariableCrud.spec.ts.mp4
Normal file
BIN
public/e2e-tests/videos/queryVariableCrud.spec.ts.mp4
Normal file
Binary file not shown.
BIN
public/e2e-tests/videos/smoketests.spec.ts.mp4
Normal file
BIN
public/e2e-tests/videos/smoketests.spec.ts.mp4
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user