grafana/public/app/features/dashboard/state/getPanelOptionsWithDefaults.ts
Torkel Ödegaard a241f03167
TimeSeries: Support coloring series and line by thresholds or gradient color scales (#35910)
* TimeSeries: Adds support for color scheme series and line colors

* Updates

* fixed device issue

* Evaluate series color in legend

* two fixes

* It works with points

* Added test dashboard

* Minor fix

* Reset color mode to palette when switching to panel that supports by series mode

* Add support for relative thresholds

* Updated snapshots
2021-07-07 12:40:40 +02:00

230 lines
6.6 KiB
TypeScript

import {
ConfigOverrideRule,
DynamicConfigValue,
FieldColorConfigSettings,
FieldColorModeId,
fieldColorModeRegistry,
FieldConfigOptionsRegistry,
FieldConfigProperty,
FieldConfigSource,
PanelPlugin,
ThresholdsConfig,
ThresholdsMode,
} from '@grafana/data';
import { mergeWith, isArray, isObject, unset, isEqual } from 'lodash';
export interface Props {
plugin: PanelPlugin;
currentFieldConfig: FieldConfigSource;
currentOptions: Record<string, any>;
isAfterPluginChange: boolean;
}
export interface OptionDefaults {
options: any;
fieldConfig: FieldConfigSource;
}
export function getPanelOptionsWithDefaults({
plugin,
currentOptions,
currentFieldConfig,
isAfterPluginChange,
}: Props): OptionDefaults {
const optionsWithDefaults = mergeWith(
{},
plugin.defaults,
currentOptions || {},
(objValue: any, srcValue: any): any => {
if (isArray(srcValue)) {
return srcValue;
}
}
);
const fieldConfigWithDefaults = applyFieldConfigDefaults(currentFieldConfig, plugin);
const fieldConfigWithOptimalColorMode = adaptFieldColorMode(plugin, fieldConfigWithDefaults, isAfterPluginChange);
return { options: optionsWithDefaults, fieldConfig: fieldConfigWithOptimalColorMode };
}
function applyFieldConfigDefaults(existingFieldConfig: FieldConfigSource, plugin: PanelPlugin): FieldConfigSource {
const pluginDefaults = plugin.fieldConfigDefaults;
const result: FieldConfigSource = {
defaults: mergeWith(
{},
pluginDefaults.defaults,
existingFieldConfig ? existingFieldConfig.defaults : {},
(objValue: any, srcValue: any): any => {
if (isArray(srcValue)) {
return srcValue;
}
}
),
overrides: existingFieldConfig?.overrides ?? [],
};
cleanProperties(result.defaults, '', plugin.fieldConfigRegistry);
// Thresholds base values are null in JSON but need to be converted to -Infinity
if (result.defaults.thresholds) {
fixThresholds(result.defaults.thresholds);
}
// Filter out overrides for properties that cannot be found in registry
result.overrides = filterFieldConfigOverrides(result.overrides, (prop) => {
return plugin.fieldConfigRegistry.getIfExists(prop.id) !== undefined;
});
for (const override of result.overrides) {
for (const property of override.properties) {
if (property.id === 'thresholds') {
fixThresholds(property.value as ThresholdsConfig);
}
}
}
return result;
}
export function filterFieldConfigOverrides(
overrides: ConfigOverrideRule[],
condition: (value: DynamicConfigValue) => boolean
): ConfigOverrideRule[] {
return overrides
.map((x) => {
const properties = x.properties.filter(condition);
return {
...x,
properties,
};
})
.filter((x) => x.properties.length > 0);
}
function cleanProperties(obj: any, parentPath: string, fieldConfigRegistry: FieldConfigOptionsRegistry) {
let found = false;
for (const propName of Object.keys(obj)) {
const value = obj[propName];
const fullPath = `${parentPath}${propName}`;
const existsInRegistry = !!fieldConfigRegistry.getIfExists(fullPath);
// need to check early here as some standard properties have nested properies
if (existsInRegistry) {
found = true;
continue;
}
if (isArray(value) || !isObject(value)) {
if (!existsInRegistry) {
unset(obj, propName);
}
} else {
const childPropFound = cleanProperties(value, `${fullPath}.`, fieldConfigRegistry);
// If no child props found unset the main object
if (!childPropFound) {
unset(obj, propName);
}
}
}
return found;
}
function adaptFieldColorMode(
plugin: PanelPlugin,
fieldConfig: FieldConfigSource,
isAfterPluginChange: boolean
): FieldConfigSource {
if (!isAfterPluginChange) {
return fieldConfig;
}
// 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(fieldConfig.defaults.color?.mode);
// When no support fo value colors, use classic palette
if (!colorSettings.byValueSupport) {
if (!mode || mode.isByValue) {
fieldConfig.defaults.color = { mode: FieldColorModeId.PaletteClassic };
return fieldConfig;
}
}
// When supporting value colors and prefering thresholds, use Thresholds mode.
// Otherwise keep current mode
if (colorSettings.byValueSupport && colorSettings.preferThresholdsMode && mode?.id !== FieldColorModeId.Fixed) {
if (!mode || !mode.isByValue) {
fieldConfig.defaults.color = { mode: FieldColorModeId.Thresholds };
return fieldConfig;
}
}
// If panel support bySeries then we should default to that when switching to this panel as that is most likely
// what users will expect. Example scenario a user who has a graph panel (time series) and switches to Gauge and
// then back to time series we want the graph panel color mode to reset to classic palette and not preserve the
// Gauge prefered thresholds mode.
if (colorSettings.bySeriesSupport && mode?.isByValue) {
fieldConfig.defaults.color = { mode: FieldColorModeId.PaletteClassic };
return fieldConfig;
}
}
return fieldConfig;
}
function fixThresholds(thresholds: ThresholdsConfig) {
if (!thresholds.mode) {
thresholds.mode = ThresholdsMode.Absolute;
}
if (!thresholds.steps) {
thresholds.steps = [];
} else if (thresholds.steps.length) {
// First value is always -Infinity
// JSON saves it as null
thresholds.steps[0].value = -Infinity;
}
}
export function restoreCustomOverrideRules(current: FieldConfigSource, old: FieldConfigSource): FieldConfigSource {
const result = {
defaults: {
...current.defaults,
custom: old.defaults.custom,
},
overrides: [...current.overrides],
};
for (const override of old.overrides) {
for (const prop of override.properties) {
if (isCustomFieldProp(prop)) {
const currentOverride = result.overrides.find((o) => isEqual(o.matcher, override.matcher));
if (currentOverride) {
if (currentOverride !== override) {
currentOverride.properties.push(prop);
}
} else {
result.overrides.push(override);
}
}
}
}
return result;
}
export function isCustomFieldProp(prop: DynamicConfigValue): boolean {
return prop.id.startsWith('custom.');
}
export function isStandardFieldProp(prop: DynamicConfigValue): boolean {
return !isCustomFieldProp(prop);
}