mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Field color: handling color changes when switching panel types (#28875)
* FieldColor: Per panel settings to filter out supported modes * Updates * Updated solution * Update panel plugin API for standard options support * Update FieldColorConfigSettings interface * Change color mode correctly when changing plugin type * Render only applicable color modes in field color config editor * Apply field config API changes * TS fixes Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
2887f3f68b
commit
2ea4a36bf7
@ -24,6 +24,7 @@ export const fieldColorModeRegistry = new Registry<FieldColorMode>(() => {
|
|||||||
{
|
{
|
||||||
id: FieldColorModeId.Thresholds,
|
id: FieldColorModeId.Thresholds,
|
||||||
name: 'From thresholds',
|
name: 'From thresholds',
|
||||||
|
isByValue: true,
|
||||||
description: 'Derive colors from thresholds',
|
description: 'Derive colors from thresholds',
|
||||||
getCalculator: (_field, theme) => {
|
getCalculator: (_field, theme) => {
|
||||||
return (_value, _percent, threshold) => {
|
return (_value, _percent, threshold) => {
|
||||||
|
@ -306,7 +306,6 @@ const processFieldConfigValue = (
|
|||||||
const currentConfig = get(destination, fieldConfigProperty.path);
|
const currentConfig = get(destination, fieldConfigProperty.path);
|
||||||
if (currentConfig === null || currentConfig === undefined) {
|
if (currentConfig === null || currentConfig === undefined) {
|
||||||
const item = context.fieldConfigRegistry.getIfExists(fieldConfigProperty.id);
|
const item = context.fieldConfigRegistry.getIfExists(fieldConfigProperty.id);
|
||||||
// console.log(item);
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -120,8 +120,15 @@ export const booleanOverrideProcessor = (
|
|||||||
return value; // !!!! likely not !!!!
|
return value; // !!!! likely not !!!!
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ColorFieldConfigSettings {
|
export interface FieldColorConfigSettings {
|
||||||
allowUndefined?: boolean;
|
/**
|
||||||
textWhenUndefined?: string; // Pick Color
|
* When switching to a visualization that does not support by value coloring then Grafana will
|
||||||
disableNamedColors?: boolean;
|
* switch to a by series palette based color mode
|
||||||
|
*/
|
||||||
|
byValueSupport?: boolean;
|
||||||
|
/**
|
||||||
|
* When switching to a visualization that has this set to true then Grafana will change color mode
|
||||||
|
* to from thresholds if it was set to a by series palette
|
||||||
|
*/
|
||||||
|
preferThresholdsMode?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@ describe('PanelPlugin', () => {
|
|||||||
standardFieldConfigEditorRegistry.setInit(() => {
|
standardFieldConfigEditorRegistry.setInit(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 'min',
|
id: FieldConfigProperty.Min,
|
||||||
path: 'min',
|
path: 'min',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'max',
|
id: FieldConfigProperty.Max,
|
||||||
path: 'max',
|
path: 'max',
|
||||||
},
|
},
|
||||||
] as any;
|
] as any;
|
||||||
@ -210,15 +210,15 @@ describe('PanelPlugin', () => {
|
|||||||
expect(panel.fieldConfigRegistry.list()).toHaveLength(2);
|
expect(panel.fieldConfigRegistry.list()).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('selected standard config', () => {
|
test('disabling standard config properties', () => {
|
||||||
const panel = new PanelPlugin(() => {
|
const panel = new PanelPlugin(() => {
|
||||||
return <div>Panel</div>;
|
return <div>Panel</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
panel.useFieldConfig({
|
panel.useFieldConfig({
|
||||||
standardOptions: [FieldConfigProperty.Min, FieldConfigProperty.Max],
|
disableStandardOptions: [FieldConfigProperty.Min],
|
||||||
});
|
});
|
||||||
expect(panel.fieldConfigRegistry.list()).toHaveLength(2);
|
expect(panel.fieldConfigRegistry.list()).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('default values', () => {
|
describe('default values', () => {
|
||||||
@ -228,10 +228,9 @@ describe('PanelPlugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
panel.useFieldConfig({
|
panel.useFieldConfig({
|
||||||
standardOptions: [FieldConfigProperty.Max, FieldConfigProperty.Min],
|
standardOptions: {
|
||||||
standardOptionsDefaults: {
|
[FieldConfigProperty.Max]: { defaultValue: 20 },
|
||||||
[FieldConfigProperty.Max]: 20,
|
[FieldConfigProperty.Min]: { defaultValue: 10 },
|
||||||
[FieldConfigProperty.Min]: 10,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -247,17 +246,16 @@ describe('PanelPlugin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore defaults that are not specified as available properties', () => {
|
it('should disable properties independently from the default values settings', () => {
|
||||||
const panel = new PanelPlugin(() => {
|
const panel = new PanelPlugin(() => {
|
||||||
return <div>Panel</div>;
|
return <div>Panel</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
panel.useFieldConfig({
|
panel.useFieldConfig({
|
||||||
standardOptions: [FieldConfigProperty.Max],
|
standardOptions: {
|
||||||
standardOptionsDefaults: {
|
[FieldConfigProperty.Max]: { defaultValue: 20 },
|
||||||
[FieldConfigProperty.Max]: 20,
|
|
||||||
[FieldConfigProperty.Min]: 10,
|
|
||||||
},
|
},
|
||||||
|
disableStandardOptions: [FieldConfigProperty.Min],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel.fieldConfigRegistry.list()).toHaveLength(1);
|
expect(panel.fieldConfigRegistry.list()).toHaveLength(1);
|
||||||
|
@ -15,33 +15,38 @@ import set from 'lodash/set';
|
|||||||
import { deprecationWarning } from '../utils';
|
import { deprecationWarning } from '../utils';
|
||||||
import { FieldConfigOptionsRegistry, standardFieldConfigEditorRegistry } from '../field';
|
import { FieldConfigOptionsRegistry, standardFieldConfigEditorRegistry } from '../field';
|
||||||
|
|
||||||
|
type StandardOptionConfig = {
|
||||||
|
defaultValue?: any;
|
||||||
|
settings?: any;
|
||||||
|
};
|
||||||
|
|
||||||
export interface SetFieldConfigOptionsArgs<TFieldConfigOptions = any> {
|
export interface SetFieldConfigOptionsArgs<TFieldConfigOptions = any> {
|
||||||
/**
|
/**
|
||||||
* Array of standard field config properties
|
* Configuration object of the standard field config properites
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* {
|
* {
|
||||||
* standardOptions: [FieldConfigProperty.Min, FieldConfigProperty.Max, FieldConfigProperty.Unit]
|
* standardOptions: {
|
||||||
* }
|
* [FieldConfigProperty.Decimals]: {
|
||||||
* ```
|
* defaultValue: 3
|
||||||
*/
|
* }
|
||||||
standardOptions?: FieldConfigProperty[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object specifying standard option properties default values
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* {
|
|
||||||
* standardOptionsDefaults: {
|
|
||||||
* [FieldConfigProperty.Min]: 20,
|
|
||||||
* [FieldConfigProperty.Max]: 100
|
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
standardOptionsDefaults?: Partial<Record<FieldConfigProperty, any>>;
|
standardOptions?: Partial<Record<FieldConfigProperty, StandardOptionConfig>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of standard field config properties that should not be available in the panel
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* {
|
||||||
|
* disableStandardOptions: [FieldConfigProperty.Min, FieldConfigProperty.Max, FieldConfigProperty.Unit]
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
disableStandardOptions?: FieldConfigProperty[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function that allows custom field config properties definition.
|
* Function that allows custom field config properties definition.
|
||||||
@ -305,13 +310,13 @@ export class PanelPlugin<TOptions = any, TFieldConfigOptions extends object = an
|
|||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
useFieldConfig(config?: SetFieldConfigOptionsArgs<TFieldConfigOptions>) {
|
useFieldConfig(config: SetFieldConfigOptionsArgs<TFieldConfigOptions> = {}) {
|
||||||
// builder is applied lazily when custom field configs are accessed
|
// builder is applied lazily when custom field configs are accessed
|
||||||
this._initConfigRegistry = () => {
|
this._initConfigRegistry = () => {
|
||||||
const registry = new FieldConfigOptionsRegistry();
|
const registry = new FieldConfigOptionsRegistry();
|
||||||
|
|
||||||
// Add custom options
|
// Add custom options
|
||||||
if (config && config.useCustomConfig) {
|
if (config.useCustomConfig) {
|
||||||
const builder = new FieldConfigEditorBuilder<TFieldConfigOptions>();
|
const builder = new FieldConfigEditorBuilder<TFieldConfigOptions>();
|
||||||
config.useCustomConfig(builder);
|
config.useCustomConfig(builder);
|
||||||
|
|
||||||
@ -326,20 +331,32 @@ export class PanelPlugin<TOptions = any, TFieldConfigOptions extends object = an
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config && config.standardOptions) {
|
for (let fieldConfigProp of standardFieldConfigEditorRegistry.list()) {
|
||||||
for (const standardOption of config.standardOptions) {
|
if (config.disableStandardOptions) {
|
||||||
const standardEditor = standardFieldConfigEditorRegistry.get(standardOption);
|
const isDisabled = config.disableStandardOptions.indexOf(fieldConfigProp.id as FieldConfigProperty) > -1;
|
||||||
registry.register({
|
if (isDisabled) {
|
||||||
...standardEditor,
|
continue;
|
||||||
defaultValue:
|
}
|
||||||
(config.standardOptionsDefaults && config.standardOptionsDefaults[standardOption]) ||
|
|
||||||
standardEditor.defaultValue,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
if (config.standardOptions) {
|
||||||
for (const fieldConfigProp of standardFieldConfigEditorRegistry.list()) {
|
const customDefault: any = config.standardOptions[fieldConfigProp.id as FieldConfigProperty]?.defaultValue;
|
||||||
registry.register(fieldConfigProp);
|
const customSettings: any = config.standardOptions[fieldConfigProp.id as FieldConfigProperty]?.settings;
|
||||||
|
if (customDefault) {
|
||||||
|
fieldConfigProp = {
|
||||||
|
...fieldConfigProp,
|
||||||
|
defaultValue: customDefault,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customSettings) {
|
||||||
|
fieldConfigProp = {
|
||||||
|
...fieldConfigProp,
|
||||||
|
settings: fieldConfigProp.settings ? { ...fieldConfigProp.settings, ...customSettings } : customSettings,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registry.register(fieldConfigProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return registry;
|
return registry;
|
||||||
|
@ -13,12 +13,10 @@ import {
|
|||||||
StringFieldConfigSettings,
|
StringFieldConfigSettings,
|
||||||
NumberFieldConfigSettings,
|
NumberFieldConfigSettings,
|
||||||
SliderFieldConfigSettings,
|
SliderFieldConfigSettings,
|
||||||
ColorFieldConfigSettings,
|
|
||||||
identityOverrideProcessor,
|
identityOverrideProcessor,
|
||||||
UnitFieldConfigSettings,
|
UnitFieldConfigSettings,
|
||||||
unitOverrideProcessor,
|
unitOverrideProcessor,
|
||||||
} from '../field';
|
} from '../field';
|
||||||
import { FieldColor } from '../types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fluent API for declarative creation of field config option editors
|
* Fluent API for declarative creation of field config option editors
|
||||||
@ -104,9 +102,7 @@ export class FieldConfigEditorBuilder<TOptions> extends OptionsUIRegistryBuilder
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addColorPicker<TSettings = any>(
|
addColorPicker<TSettings = any>(config: FieldConfigEditorConfig<TOptions, TSettings, string>) {
|
||||||
config: FieldConfigEditorConfig<TOptions, TSettings & ColorFieldConfigSettings, FieldColor>
|
|
||||||
) {
|
|
||||||
return this.addCustomEditor({
|
return this.addCustomEditor({
|
||||||
...config,
|
...config,
|
||||||
id: config.path,
|
id: config.path,
|
||||||
@ -203,9 +199,7 @@ export class PanelOptionsEditorBuilder<TOptions> extends OptionsUIRegistryBuilde
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addColorPicker<TSettings = any>(
|
addColorPicker<TSettings = any>(config: PanelOptionsEditorConfig<TOptions, TSettings, string>): this {
|
||||||
config: PanelOptionsEditorConfig<TOptions, TSettings & ColorFieldConfigSettings, string>
|
|
||||||
): this {
|
|
||||||
return this.addCustomEditor({
|
return this.addCustomEditor({
|
||||||
...config,
|
...config,
|
||||||
id: config.path,
|
id: config.path,
|
||||||
|
@ -545,7 +545,7 @@ export function getBarGradient(props: Props, maxSize: number): string {
|
|||||||
return gradient + ')';
|
return gradient + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode.colors) {
|
if (mode.isContinuous && mode.colors) {
|
||||||
const scheme = mode.colors.map(item => getColorForTheme(item, theme));
|
const scheme = mode.colors.map(item => getColorForTheme(item, theme));
|
||||||
for (let i = 0; i < scheme.length; i++) {
|
for (let i = 0; i < scheme.length; i++) {
|
||||||
const color = scheme[i];
|
const color = scheme[i];
|
||||||
|
@ -28,12 +28,6 @@ export const ColorValueEditor: React.FC<Props> = ({ value, onChange }) => {
|
|||||||
color={value ? getColorForTheme(value, theme) : theme.colors.formInputBorder}
|
color={value ? getColorForTheme(value, theme) : theme.colors.formInputBorder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className={styles.colorText} onClick={showColorPicker}>
|
|
||||||
{value ?? settings?.textWhenUndefined ?? 'Pick Color'}
|
|
||||||
</div>
|
|
||||||
{value && settings?.allowUndefined && (
|
|
||||||
<Icon className={styles.trashIcon} name="trash-alt" onClick={() => onChange(undefined)} />
|
|
||||||
)} */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -8,13 +8,14 @@ import {
|
|||||||
FieldColorMode,
|
FieldColorMode,
|
||||||
GrafanaTheme,
|
GrafanaTheme,
|
||||||
getColorForTheme,
|
getColorForTheme,
|
||||||
|
FieldColorConfigSettings,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { Select } from '../Select/Select';
|
import { Select } from '../Select/Select';
|
||||||
import { ColorValueEditor } from './color';
|
import { ColorValueEditor } from './color';
|
||||||
import { useStyles, useTheme } from '../../themes/ThemeContext';
|
import { useStyles, useTheme } from '../../themes/ThemeContext';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
|
|
||||||
export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | undefined, {}>> = ({
|
export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | undefined, FieldColorConfigSettings>> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
item,
|
item,
|
||||||
@ -22,7 +23,11 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = useStyles(getStyles);
|
const styles = useStyles(getStyles);
|
||||||
|
|
||||||
const options = fieldColorModeRegistry.list().map(mode => {
|
const availableOptions = item.settings?.byValueSupport
|
||||||
|
? fieldColorModeRegistry.list()
|
||||||
|
: fieldColorModeRegistry.list().filter(m => !m.isByValue);
|
||||||
|
|
||||||
|
const options = availableOptions.map(mode => {
|
||||||
let suffix = mode.isByValue ? ' (by value)' : '';
|
let suffix = mode.isByValue ? ' (by value)' : '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
identityOverrideProcessor,
|
identityOverrideProcessor,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
FieldColor,
|
FieldColor,
|
||||||
|
FieldColorConfigSettings,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
import { Switch } from '../components/Switch/Switch';
|
import { Switch } from '../components/Switch/Switch';
|
||||||
@ -204,15 +205,18 @@ export const getStandardFieldConfigs = () => {
|
|||||||
getItemsCount: value => (value ? value.length : 0),
|
getItemsCount: value => (value ? value.length : 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
const color: FieldConfigPropertyItem<any, FieldColor | undefined, {}> = {
|
const color: FieldConfigPropertyItem<any, FieldColor | undefined, FieldColorConfigSettings> = {
|
||||||
id: 'color',
|
id: 'color',
|
||||||
path: 'color',
|
path: 'color',
|
||||||
name: 'Color scheme',
|
name: 'Color scheme',
|
||||||
description: 'Select palette, gradient or single color',
|
|
||||||
editor: standardEditorsRegistry.get('fieldColor').editor as any,
|
editor: standardEditorsRegistry.get('fieldColor').editor as any,
|
||||||
override: standardEditorsRegistry.get('fieldColor').editor as any,
|
override: standardEditorsRegistry.get('fieldColor').editor as any,
|
||||||
process: identityOverrideProcessor,
|
process: identityOverrideProcessor,
|
||||||
shouldApply: () => true,
|
shouldApply: () => true,
|
||||||
|
settings: {
|
||||||
|
byValueSupport: true,
|
||||||
|
preferThresholdsMode: true,
|
||||||
|
},
|
||||||
category,
|
category,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import {
|
|||||||
standardFieldConfigEditorRegistry,
|
standardFieldConfigEditorRegistry,
|
||||||
PanelData,
|
PanelData,
|
||||||
DataSourceInstanceSettings,
|
DataSourceInstanceSettings,
|
||||||
|
FieldColorModeId,
|
||||||
|
FieldColorConfigSettings,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { ComponentClass } from 'react';
|
import { ComponentClass } from 'react';
|
||||||
import { PanelQueryRunner } from './PanelQueryRunner';
|
import { PanelQueryRunner } from './PanelQueryRunner';
|
||||||
@ -55,7 +57,20 @@ export const mockStandardProperties = () => {
|
|||||||
shouldApply: () => true,
|
shouldApply: () => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return [unit, decimals, boolean];
|
const fieldColor = {
|
||||||
|
id: 'color',
|
||||||
|
path: 'color',
|
||||||
|
name: 'color',
|
||||||
|
description: '',
|
||||||
|
// @ts-ignore
|
||||||
|
editor: () => null,
|
||||||
|
// @ts-ignore
|
||||||
|
override: () => null,
|
||||||
|
process: identityOverrideProcessor,
|
||||||
|
shouldApply: () => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [unit, decimals, boolean, fieldColor];
|
||||||
};
|
};
|
||||||
|
|
||||||
standardFieldConfigEditorRegistry.setInit(() => mockStandardProperties());
|
standardFieldConfigEditorRegistry.setInit(() => mockStandardProperties());
|
||||||
@ -147,10 +162,13 @@ describe('PanelModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
panelPlugin.useFieldConfig({
|
panelPlugin.useFieldConfig({
|
||||||
standardOptions: [FieldConfigProperty.Unit, FieldConfigProperty.Decimals],
|
standardOptions: {
|
||||||
standardOptionsDefaults: {
|
[FieldConfigProperty.Unit]: {
|
||||||
[FieldConfigProperty.Unit]: 'flop',
|
defaultValue: 'flop',
|
||||||
[FieldConfigProperty.Decimals]: 2,
|
},
|
||||||
|
[FieldConfigProperty.Decimals]: {
|
||||||
|
defaultValue: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
model.pluginLoaded(panelPlugin);
|
model.pluginLoaded(panelPlugin);
|
||||||
@ -239,6 +257,17 @@ describe('PanelModel', () => {
|
|||||||
describe('when changing panel type', () => {
|
describe('when changing panel type', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const newPlugin = getPanelPlugin({ id: 'graph' });
|
const newPlugin = getPanelPlugin({ id: 'graph' });
|
||||||
|
|
||||||
|
newPlugin.useFieldConfig({
|
||||||
|
standardOptions: {
|
||||||
|
[FieldConfigProperty.Color]: {
|
||||||
|
settings: {
|
||||||
|
byThresholdsSupport: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
newPlugin.setPanelOptions(builder => {
|
newPlugin.setPanelOptions(builder => {
|
||||||
builder.addBooleanSwitch({
|
builder.addBooleanSwitch({
|
||||||
name: 'Show thresholds labels',
|
name: 'Show thresholds labels',
|
||||||
@ -285,6 +314,66 @@ describe('PanelModel', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when changing panel type to one that does not support by value color mode', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
model.fieldConfig.defaults.color = { mode: FieldColorModeId.Thresholds };
|
||||||
|
|
||||||
|
const newPlugin = getPanelPlugin({ id: 'graph' });
|
||||||
|
newPlugin.useFieldConfig({
|
||||||
|
standardOptions: {
|
||||||
|
[FieldConfigProperty.Color]: {
|
||||||
|
settings: {
|
||||||
|
byValueSupport: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
model.editSourceId = 1001;
|
||||||
|
model.changePlugin(newPlugin);
|
||||||
|
model.alert = { id: 2 };
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change color mode', () => {
|
||||||
|
expect(model.fieldConfig.defaults.color.mode).toBe(FieldColorModeId.PaletteClassic);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when changing panel type from one not supporting by value color mode to one that supports it', () => {
|
||||||
|
const prepareModel = (colorOptions?: FieldColorConfigSettings) => {
|
||||||
|
const newModel = new PanelModel(modelJson);
|
||||||
|
newModel.fieldConfig.defaults.color = { mode: FieldColorModeId.PaletteClassic };
|
||||||
|
|
||||||
|
const newPlugin = getPanelPlugin({ id: 'graph' });
|
||||||
|
newPlugin.useFieldConfig({
|
||||||
|
standardOptions: {
|
||||||
|
[FieldConfigProperty.Color]: {
|
||||||
|
settings: {
|
||||||
|
byValueSupport: true,
|
||||||
|
...colorOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
newModel.editSourceId = 1001;
|
||||||
|
newModel.changePlugin(newPlugin);
|
||||||
|
newModel.alert = { id: 2 };
|
||||||
|
return newModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should keep supported mode', () => {
|
||||||
|
const testModel = prepareModel();
|
||||||
|
|
||||||
|
expect(testModel.fieldConfig.defaults.color!.mode).toBe(FieldColorModeId.PaletteClassic);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change to thresholds mode when it prefers to', () => {
|
||||||
|
const testModel = prepareModel({ preferThresholdsMode: true });
|
||||||
|
expect(testModel.fieldConfig.defaults.color!.mode).toBe(FieldColorModeId.Thresholds);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when changing to react panel from angular panel', () => {
|
describe('when changing to react panel from angular panel', () => {
|
||||||
let panelQueryRunner: any;
|
let panelQueryRunner: any;
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@ import {
|
|||||||
DataQueryResponseData,
|
DataQueryResponseData,
|
||||||
DataTransformerConfig,
|
DataTransformerConfig,
|
||||||
eventFactory,
|
eventFactory,
|
||||||
|
FieldColorConfigSettings,
|
||||||
|
FieldColorModeId,
|
||||||
|
fieldColorModeRegistry,
|
||||||
|
FieldConfigProperty,
|
||||||
FieldConfigSource,
|
FieldConfigSource,
|
||||||
PanelEvents,
|
PanelEvents,
|
||||||
PanelPlugin,
|
PanelPlugin,
|
||||||
@ -322,7 +326,35 @@ export class PanelModel implements DataConfigSource {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fieldConfig = applyFieldConfigDefaults(this.fieldConfig, this.plugin!.fieldConfigDefaults);
|
this.fieldConfig = applyFieldConfigDefaults(this.fieldConfig, plugin.fieldConfigDefaults);
|
||||||
|
this.validateFieldColorMode(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateFieldColorMode(plugin: PanelPlugin) {
|
||||||
|
// adjust to prefered field color setting if needed
|
||||||
|
const color = plugin.fieldConfigRegistry.getIfExists(FieldConfigProperty.Color);
|
||||||
|
|
||||||
|
if (color && color.settings) {
|
||||||
|
const colorSettings = color.settings as FieldColorConfigSettings;
|
||||||
|
const mode = fieldColorModeRegistry.getIfExists(this.fieldConfig.defaults.color?.mode);
|
||||||
|
|
||||||
|
// When no support fo value colors, use classic palette
|
||||||
|
if (!colorSettings.byValueSupport) {
|
||||||
|
if (!mode || mode.isByValue) {
|
||||||
|
this.fieldConfig.defaults.color = { mode: FieldColorModeId.PaletteClassic };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When supporting value colors and prefering thresholds, use Thresholds mode.
|
||||||
|
// Otherwise keep current mode
|
||||||
|
if (colorSettings.byValueSupport && colorSettings.preferThresholdsMode) {
|
||||||
|
if (!mode || !mode.isByValue) {
|
||||||
|
this.fieldConfig.defaults.color = { mode: FieldColorModeId.Thresholds };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginLoaded(plugin: PanelPlugin) {
|
pluginLoaded(plugin: PanelPlugin) {
|
||||||
|
@ -12,11 +12,11 @@ import { axesEditorComponent } from './axes_editor';
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest';
|
import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest';
|
||||||
import { PanelEvents, PanelPlugin, DataFrame, FieldConfigProperty, getColorForTheme } from '@grafana/data';
|
import { DataFrame, FieldConfigProperty, getColorForTheme, PanelEvents, PanelPlugin } from '@grafana/data';
|
||||||
|
|
||||||
import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
|
import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
|
||||||
import { graphPanelMigrationHandler } from './GraphMigrations';
|
import { graphPanelMigrationHandler } from './GraphMigrations';
|
||||||
import { DataWarning, GraphPanelOptions, GraphFieldConfig } from './types';
|
import { DataWarning, GraphFieldConfig, GraphPanelOptions } from './types';
|
||||||
|
|
||||||
import { auto } from 'angular';
|
import { auto } from 'angular';
|
||||||
import { AnnotationsSrv } from 'app/features/annotations/all';
|
import { AnnotationsSrv } from 'app/features/annotations/all';
|
||||||
@ -388,10 +388,14 @@ export class GraphCtrl extends MetricsPanelCtrl {
|
|||||||
// Use new react style configuration
|
// Use new react style configuration
|
||||||
export const plugin = new PanelPlugin<GraphPanelOptions, GraphFieldConfig>(null)
|
export const plugin = new PanelPlugin<GraphPanelOptions, GraphFieldConfig>(null)
|
||||||
.useFieldConfig({
|
.useFieldConfig({
|
||||||
standardOptions: [
|
disableStandardOptions: [
|
||||||
FieldConfigProperty.DisplayName,
|
FieldConfigProperty.NoValue,
|
||||||
FieldConfigProperty.Unit,
|
FieldConfigProperty.Thresholds,
|
||||||
FieldConfigProperty.Links, // previously saved as dataLinks on options
|
FieldConfigProperty.Max,
|
||||||
|
FieldConfigProperty.Min,
|
||||||
|
FieldConfigProperty.Decimals,
|
||||||
|
FieldConfigProperty.Color,
|
||||||
|
FieldConfigProperty.Mappings,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.setMigrationHandler(graphPanelMigrationHandler);
|
.setMigrationHandler(graphPanelMigrationHandler);
|
||||||
|
@ -1,63 +1,61 @@
|
|||||||
import { FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
import { PanelPlugin } from '@grafana/data';
|
||||||
import { GraphPanel } from './GraphPanel';
|
import { GraphPanel } from './GraphPanel';
|
||||||
import { Options } from './types';
|
import { Options } from './types';
|
||||||
|
|
||||||
export const plugin = new PanelPlugin<Options>(GraphPanel)
|
export const plugin = new PanelPlugin<Options>(GraphPanel).useFieldConfig().setPanelOptions(builder => {
|
||||||
.useFieldConfig({ standardOptions: [FieldConfigProperty.Unit, FieldConfigProperty.Decimals] })
|
builder
|
||||||
.setPanelOptions(builder => {
|
.addBooleanSwitch({
|
||||||
builder
|
path: 'graph.showBars',
|
||||||
.addBooleanSwitch({
|
name: 'Show bars',
|
||||||
path: 'graph.showBars',
|
description: '',
|
||||||
name: 'Show bars',
|
defaultValue: false,
|
||||||
description: '',
|
})
|
||||||
defaultValue: false,
|
.addBooleanSwitch({
|
||||||
})
|
path: 'graph.showLines',
|
||||||
.addBooleanSwitch({
|
name: 'Show lines',
|
||||||
path: 'graph.showLines',
|
description: '',
|
||||||
name: 'Show lines',
|
defaultValue: true,
|
||||||
description: '',
|
})
|
||||||
defaultValue: true,
|
.addBooleanSwitch({
|
||||||
})
|
path: 'graph.showPoints',
|
||||||
.addBooleanSwitch({
|
name: 'Show poins',
|
||||||
path: 'graph.showPoints',
|
description: '',
|
||||||
name: 'Show poins',
|
defaultValue: false,
|
||||||
description: '',
|
})
|
||||||
defaultValue: false,
|
.addBooleanSwitch({
|
||||||
})
|
path: 'legend.isVisible',
|
||||||
.addBooleanSwitch({
|
name: 'Show legend',
|
||||||
path: 'legend.isVisible',
|
description: '',
|
||||||
name: 'Show legend',
|
defaultValue: true,
|
||||||
description: '',
|
})
|
||||||
defaultValue: true,
|
.addBooleanSwitch({
|
||||||
})
|
path: 'legend.asTable',
|
||||||
.addBooleanSwitch({
|
name: 'Display legend as table',
|
||||||
path: 'legend.asTable',
|
description: '',
|
||||||
name: 'Display legend as table',
|
defaultValue: false,
|
||||||
description: '',
|
})
|
||||||
defaultValue: false,
|
.addRadio({
|
||||||
})
|
path: 'legend.placement',
|
||||||
.addRadio({
|
name: 'Legend placement',
|
||||||
path: 'legend.placement',
|
description: '',
|
||||||
name: 'Legend placement',
|
defaultValue: 'under',
|
||||||
description: '',
|
settings: {
|
||||||
defaultValue: 'under',
|
options: [
|
||||||
settings: {
|
{ value: 'under', label: 'Below graph' },
|
||||||
options: [
|
{ value: 'right', label: 'Right to the graph' },
|
||||||
{ value: 'under', label: 'Below graph' },
|
],
|
||||||
{ value: 'right', label: 'Right to the graph' },
|
},
|
||||||
],
|
})
|
||||||
},
|
.addRadio({
|
||||||
})
|
path: 'tooltipOptions.mode',
|
||||||
.addRadio({
|
name: 'Tooltip mode',
|
||||||
path: 'tooltipOptions.mode',
|
description: '',
|
||||||
name: 'Tooltip mode',
|
defaultValue: 'single',
|
||||||
description: '',
|
settings: {
|
||||||
defaultValue: 'single',
|
options: [
|
||||||
settings: {
|
{ value: 'single', label: 'Single series' },
|
||||||
options: [
|
{ value: 'multi', label: 'All series' },
|
||||||
{ value: 'single', label: 'Single series' },
|
],
|
||||||
{ value: 'multi', label: 'All series' },
|
},
|
||||||
],
|
});
|
||||||
},
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import { FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||||
import { AxisSide, GraphCustomFieldConfig } from '@grafana/ui';
|
import { AxisSide, GraphCustomFieldConfig } from '@grafana/ui';
|
||||||
import { GraphPanel } from './GraphPanel';
|
import { GraphPanel } from './GraphPanel';
|
||||||
import { Options } from './types';
|
import { Options } from './types';
|
||||||
|
|
||||||
export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPanel)
|
export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPanel)
|
||||||
.useFieldConfig({
|
.useFieldConfig({
|
||||||
standardOptions: [
|
standardOptions: {
|
||||||
// FieldConfigProperty.Min,
|
[FieldConfigProperty.Color]: {
|
||||||
// FieldConfigProperty.Max,
|
settings: {
|
||||||
FieldConfigProperty.Color,
|
byValueSupport: false,
|
||||||
FieldConfigProperty.Unit,
|
},
|
||||||
FieldConfigProperty.DisplayName,
|
defaultValue: {
|
||||||
FieldConfigProperty.Decimals,
|
mode: FieldColorModeId.PaletteClassic,
|
||||||
// NOT: FieldConfigProperty.Thresholds,
|
},
|
||||||
FieldConfigProperty.Mappings,
|
},
|
||||||
],
|
},
|
||||||
|
|
||||||
useCustomConfig: builder => {
|
useCustomConfig: builder => {
|
||||||
builder
|
builder
|
||||||
.addBooleanSwitch({
|
.addBooleanSwitch({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { sharedSingleStatMigrationHandler, BigValueTextMode } from '@grafana/ui';
|
import { BigValueTextMode, sharedSingleStatMigrationHandler } from '@grafana/ui';
|
||||||
import { PanelPlugin } from '@grafana/data';
|
import { PanelPlugin } from '@grafana/data';
|
||||||
import { StatPanelOptions, addStandardDataReduceOptions } from './types';
|
import { addStandardDataReduceOptions, StatPanelOptions } from './types';
|
||||||
import { StatPanel } from './StatPanel';
|
import { StatPanel } from './StatPanel';
|
||||||
import { statPanelChangedHandler } from './StatMigrations';
|
import { statPanelChangedHandler } from './StatMigrations';
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user