mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
grafana/data: Move getPanelOptionsWithDefaults from core (#60813)
* grafana/data: Move getPanelOptionsWithDefaults from core * Add internal comments
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { setContextSrv } from '../../../../core/services/context_srv';
|
||||
import { PanelModel } from '../../state/PanelModel';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { PanelModel } from '../../state/PanelModel';
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
standardFieldConfigEditorRegistry,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/OptionsUI/registry';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
|
||||
import { PanelModel } from '../../state';
|
||||
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
|
||||
import { panelModelAndPluginReady, removePanel } from 'app/features/panel/state/reducers';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
|
||||
import { thunkTester } from '../../../../../../test/core/thunk/thunkTester';
|
||||
import { PanelModel } from '../../../state';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { each, map } from 'lodash';
|
||||
|
||||
import { DataLinkBuiltInVars, MappingType } from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { setDataSourceSrv } from '@grafana/runtime';
|
||||
import { config } from 'app/core/config';
|
||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
||||
import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||
|
||||
import { VariableHide } from '../../variables/types';
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
dateTime,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { mockStandardFieldConfigOptions } from '@grafana/data/test/helpers/fieldConfig';
|
||||
import { setTemplateSrv } from '@grafana/runtime';
|
||||
import { queryBuilder } from 'app/features/variables/shared/testing/builders';
|
||||
|
||||
import { mockStandardFieldConfigOptions } from '../../../../test/helpers/fieldConfig';
|
||||
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
|
||||
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
import { TemplateSrv } from '../../templating/template_srv';
|
||||
import { variableAdapters } from '../../variables/adapters';
|
||||
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
PanelModel as IPanelModel,
|
||||
DataSourceRef,
|
||||
CoreApp,
|
||||
filterFieldConfigOverrides,
|
||||
getPanelOptionsWithDefaults,
|
||||
isStandardFieldProp,
|
||||
restoreCustomOverrideRules,
|
||||
} from '@grafana/data';
|
||||
import { getTemplateSrv, RefreshEvent } from '@grafana/runtime';
|
||||
import config from 'app/core/config';
|
||||
@@ -37,13 +41,6 @@ import { getVariablesUrlParams } from '../../variables/getAllVariableValuesForUr
|
||||
import { getTimeSrv } from '../services/TimeSrv';
|
||||
import { TimeOverrideResult } from '../utils/panel';
|
||||
|
||||
import {
|
||||
filterFieldConfigOverrides,
|
||||
getPanelOptionsWithDefaults,
|
||||
isStandardFieldProp,
|
||||
restoreCustomOverrideRules,
|
||||
} from './getPanelOptionsWithDefaults';
|
||||
|
||||
export interface GridPos {
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
@@ -1,439 +0,0 @@
|
||||
import { mockStandardFieldConfigOptions } from 'test/helpers/fieldConfig';
|
||||
|
||||
import {
|
||||
ConfigOverrideRule,
|
||||
FieldColorModeId,
|
||||
FieldConfig,
|
||||
FieldConfigProperty,
|
||||
FieldConfigSource,
|
||||
PanelPlugin,
|
||||
standardEditorsRegistry,
|
||||
standardFieldConfigEditorRegistry,
|
||||
StandardOptionConfig,
|
||||
ThresholdsMode,
|
||||
} from '@grafana/data';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
|
||||
import { getPanelOptionsWithDefaults, restoreCustomOverrideRules } from './getPanelOptionsWithDefaults';
|
||||
|
||||
standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions());
|
||||
standardEditorsRegistry.setInit(() => mockStandardFieldConfigOptions());
|
||||
|
||||
const pluginA = getPanelPlugin({ id: 'graph' });
|
||||
|
||||
pluginA.useFieldConfig({
|
||||
useCustomConfig: (builder) => {
|
||||
builder.addBooleanSwitch({
|
||||
name: 'Hide lines',
|
||||
path: 'hideLines',
|
||||
defaultValue: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
pluginA.setPanelOptions((builder) => {
|
||||
builder.addBooleanSwitch({
|
||||
name: 'Show thresholds',
|
||||
path: 'showThresholds',
|
||||
defaultValue: true,
|
||||
});
|
||||
builder.addTextInput({
|
||||
name: 'Name',
|
||||
path: 'name',
|
||||
defaultValue: 'hello',
|
||||
});
|
||||
builder.addNumberInput({
|
||||
name: 'Number',
|
||||
path: 'number',
|
||||
defaultValue: 10,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPanelOptionsWithDefaults', () => {
|
||||
describe('When panel plugin has no options', () => {
|
||||
it('Should set defaults', () => {
|
||||
const result = runScenario({
|
||||
plugin: getPanelPlugin({ id: 'graph' }),
|
||||
options: {},
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [],
|
||||
},
|
||||
"options": {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When current options are emtpy', () => {
|
||||
it('Should set defaults', () => {
|
||||
const result = getPanelOptionsWithDefaults({
|
||||
plugin: pluginA,
|
||||
currentOptions: {},
|
||||
currentFieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
isAfterPluginChange: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"hideLines": false,
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": -Infinity,
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"overrides": [],
|
||||
},
|
||||
"options": {
|
||||
"name": "hello",
|
||||
"number": 10,
|
||||
"showThresholds": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When there are current options and overrides', () => {
|
||||
it('Should set defaults', () => {
|
||||
const result = getPanelOptionsWithDefaults({
|
||||
plugin: pluginA,
|
||||
currentOptions: {
|
||||
number: 20,
|
||||
showThresholds: false,
|
||||
},
|
||||
currentFieldConfig: {
|
||||
defaults: {
|
||||
unit: 'bytes',
|
||||
decimals: 2,
|
||||
},
|
||||
overrides: [],
|
||||
},
|
||||
isAfterPluginChange: true,
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"hideLines": false,
|
||||
},
|
||||
"decimals": 2,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": -Infinity,
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80,
|
||||
},
|
||||
],
|
||||
},
|
||||
"unit": "bytes",
|
||||
},
|
||||
"overrides": [],
|
||||
},
|
||||
"options": {
|
||||
"name": "hello",
|
||||
"number": 20,
|
||||
"showThresholds": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when changing panel type to one that does not support by value color mode', () => {
|
||||
it('should change color mode', () => {
|
||||
const plugin = getPanelPlugin({ id: 'graph' }).useFieldConfig({
|
||||
standardOptions: {
|
||||
[FieldConfigProperty.Color]: {
|
||||
settings: {
|
||||
byValueSupport: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = getPanelOptionsWithDefaults({
|
||||
plugin,
|
||||
currentOptions: {},
|
||||
currentFieldConfig: {
|
||||
defaults: {
|
||||
color: { mode: FieldColorModeId.Thresholds },
|
||||
},
|
||||
overrides: [],
|
||||
},
|
||||
isAfterPluginChange: true,
|
||||
});
|
||||
|
||||
expect(result.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', () => {
|
||||
it('should keep supported mode', () => {
|
||||
const result = runScenario({
|
||||
defaults: {
|
||||
color: { mode: FieldColorModeId.PaletteClassic },
|
||||
},
|
||||
standardOptions: {
|
||||
[FieldConfigProperty.Color]: {
|
||||
settings: {
|
||||
byValueSupport: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.fieldConfig.defaults.color!.mode).toBe(FieldColorModeId.PaletteClassic);
|
||||
});
|
||||
|
||||
it('should change to thresholds mode when it prefers to', () => {
|
||||
const result = runScenario({
|
||||
defaults: {
|
||||
color: { mode: FieldColorModeId.PaletteClassic },
|
||||
},
|
||||
standardOptions: {
|
||||
[FieldConfigProperty.Color]: {
|
||||
settings: {
|
||||
byValueSupport: true,
|
||||
preferThresholdsMode: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
isAfterPluginChange: true,
|
||||
});
|
||||
expect(result.fieldConfig.defaults.color!.mode).toBe(FieldColorModeId.Thresholds);
|
||||
});
|
||||
|
||||
it('should change to classic mode when panel supports bySeries', () => {
|
||||
const result = runScenario({
|
||||
defaults: {
|
||||
color: { mode: FieldColorModeId.Thresholds },
|
||||
},
|
||||
standardOptions: {
|
||||
[FieldConfigProperty.Color]: {
|
||||
settings: {
|
||||
byValueSupport: true,
|
||||
bySeriesSupport: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
isAfterPluginChange: true,
|
||||
});
|
||||
expect(result.fieldConfig.defaults.color!.mode).toBe(FieldColorModeId.PaletteClassic);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when changing panel type to one that does not use standard field config', () => {
|
||||
it('should clean defaults', () => {
|
||||
const plugin = getPanelPlugin({ id: 'graph' });
|
||||
|
||||
const result = getPanelOptionsWithDefaults({
|
||||
plugin,
|
||||
currentOptions: {},
|
||||
currentFieldConfig: {
|
||||
defaults: {
|
||||
color: { mode: FieldColorModeId.Thresholds },
|
||||
thresholds: {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
steps: [],
|
||||
},
|
||||
},
|
||||
overrides: [],
|
||||
},
|
||||
isAfterPluginChange: true,
|
||||
});
|
||||
|
||||
expect(result.fieldConfig.defaults.thresholds).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when applying defaults clean properties that are no longer part of the registry', () => {
|
||||
it('should remove custom defaults that no longer exist', () => {
|
||||
const result = runScenario({
|
||||
defaults: {
|
||||
unit: 'bytes',
|
||||
custom: {
|
||||
customProp: 20,
|
||||
customPropNoExist: true,
|
||||
nested: {
|
||||
nestedA: 'A',
|
||||
nestedB: 'B',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.fieldConfig.defaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"custom": {
|
||||
"customProp": 20,
|
||||
"nested": {
|
||||
"nestedA": "A",
|
||||
},
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": -Infinity,
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80,
|
||||
},
|
||||
],
|
||||
},
|
||||
"unit": "bytes",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should remove custom overrides that no longer exist', () => {
|
||||
const result = runScenario({
|
||||
defaults: {},
|
||||
overrides: [
|
||||
{
|
||||
matcher: { id: 'byName', options: 'D-series' },
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.customPropNoExist',
|
||||
value: 'google',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: { id: 'byName', options: 'D-series' },
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.customProp',
|
||||
value: 30,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.fieldConfig.overrides.length).toBe(1);
|
||||
expect(result.fieldConfig.overrides[0].properties[0].id).toBe('custom.customProp');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('restoreCustomOverrideRules', () => {
|
||||
it('should add back custom rules', () => {
|
||||
const current = {
|
||||
defaults: {},
|
||||
overrides: [
|
||||
{
|
||||
matcher: { id: 'byName', options: 'SeriesA' },
|
||||
properties: [
|
||||
{
|
||||
id: 'decimals',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const old = {
|
||||
defaults: {},
|
||||
overrides: [
|
||||
{
|
||||
matcher: { id: 'byName', options: 'SeriesA' },
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.propName',
|
||||
value: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
matcher: { id: 'byName', options: 'SeriesB' },
|
||||
properties: [
|
||||
{
|
||||
id: 'custom.propName',
|
||||
value: 20,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = restoreCustomOverrideRules(current, old);
|
||||
expect(result.overrides.length).toBe(2);
|
||||
expect(result.overrides[0].properties[0].id).toBe('decimals');
|
||||
expect(result.overrides[0].properties[1].id).toBe('custom.propName');
|
||||
expect(result.overrides[1].properties.length).toBe(1);
|
||||
expect(result.overrides[1].matcher.options).toBe('SeriesB');
|
||||
});
|
||||
});
|
||||
|
||||
interface ScenarioOptions {
|
||||
defaults?: FieldConfig<any>;
|
||||
overrides?: ConfigOverrideRule[];
|
||||
disabledStandardOptions?: FieldConfigProperty[];
|
||||
standardOptions?: Partial<Record<FieldConfigProperty, StandardOptionConfig>>;
|
||||
plugin?: PanelPlugin;
|
||||
options?: any;
|
||||
isAfterPluginChange?: boolean;
|
||||
}
|
||||
|
||||
function runScenario(options: ScenarioOptions) {
|
||||
const fieldConfig: FieldConfigSource = {
|
||||
defaults: options.defaults || {},
|
||||
overrides: options.overrides || [],
|
||||
};
|
||||
|
||||
const plugin =
|
||||
options.plugin ??
|
||||
getPanelPlugin({ id: 'graph' }).useFieldConfig({
|
||||
standardOptions: options.standardOptions,
|
||||
useCustomConfig: (builder) => {
|
||||
builder.addNumberInput({
|
||||
name: 'Custom prop',
|
||||
path: 'customProp',
|
||||
defaultValue: 10,
|
||||
});
|
||||
builder.addTextInput({
|
||||
name: 'Nested prop',
|
||||
path: 'nested.nestedA',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return getPanelOptionsWithDefaults({
|
||||
plugin,
|
||||
currentOptions: options.options || {},
|
||||
currentFieldConfig: fieldConfig,
|
||||
isAfterPluginChange: !!options.isAfterPluginChange,
|
||||
});
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
import { mergeWith, isArray, isObject, unset, isEqual } from 'lodash';
|
||||
|
||||
import {
|
||||
ConfigOverrideRule,
|
||||
DynamicConfigValue,
|
||||
FieldColorConfigSettings,
|
||||
FieldColorModeId,
|
||||
fieldColorModeRegistry,
|
||||
FieldConfigOptionsRegistry,
|
||||
FieldConfigProperty,
|
||||
FieldConfigSource,
|
||||
PanelPlugin,
|
||||
ThresholdsConfig,
|
||||
ThresholdsMode,
|
||||
} from '@grafana/data';
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import { advanceTo, clear } from 'jest-date-mock';
|
||||
import { ComponentClass } from 'react';
|
||||
|
||||
import { dateTime, DateTime, PanelProps, TimeRange } from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { applyPanelTimeOverrides, calculateInnerPanelHeight } from 'app/features/dashboard/utils/panel';
|
||||
|
||||
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
|
||||
import { PanelModel } from '../state';
|
||||
|
||||
const dashboardTimeRange: TimeRange = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DataSourcePluginMeta } from '@grafana/data';
|
||||
import { getMockPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { getMockPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { buildCategories } from './buildCategories';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getMockPlugin, getMockPlugins } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { getMockPlugin, getMockPlugins } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { nameExits, findNewName } from './utils';
|
||||
|
||||
|
||||
@@ -11,12 +11,13 @@ import {
|
||||
compareDataFrameStructures,
|
||||
PluginContextProvider,
|
||||
ScopedVars,
|
||||
getPanelOptionsWithDefaults,
|
||||
OptionDefaults,
|
||||
} from '@grafana/data';
|
||||
import { getTemplateSrv, PanelRendererProps } from '@grafana/runtime';
|
||||
import { ErrorBoundaryAlert, useTheme2 } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/core';
|
||||
|
||||
import { getPanelOptionsWithDefaults, OptionDefaults } from '../../dashboard/state/getPanelOptionsWithDefaults';
|
||||
import { importPanelPlugin, syncGetPanelPlugin } from '../../plugins/importPanelPlugin';
|
||||
|
||||
const defaultFieldConfig = { defaults: {}, overrides: [] };
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { mockStandardFieldConfigOptions } from 'test/helpers/fieldConfig';
|
||||
|
||||
import { standardEditorsRegistry, standardFieldConfigEditorRegistry } from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { mockStandardFieldConfigOptions } from '@grafana/data/test/helpers/fieldConfig';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { panelPluginLoaded } from 'app/features/plugins/admin/state/actions';
|
||||
|
||||
import { thunkTester } from '../../../../test/core/thunk/thunkTester';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DataTransformerConfig, FieldConfigSource } from '@grafana/data';
|
||||
import { DataTransformerConfig, FieldConfigSource, getPanelOptionsWithDefaults } from '@grafana/data';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import { getPanelOptionsWithDefaults } from 'app/features/dashboard/state/getPanelOptionsWithDefaults';
|
||||
import { getLibraryPanel } from 'app/features/library-panels/state/api';
|
||||
import { LibraryElementDTO } from 'app/features/library-panels/types';
|
||||
import { getPanelPluginNotFound } from 'app/features/panel/components/PanelPluginError';
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import { ComponentType } from 'enzyme';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
import { PanelPluginMeta, PluginMeta, PluginType, PanelPlugin, PanelProps } from '@grafana/data';
|
||||
|
||||
export const getMockPlugins = (amount: number): PluginMeta[] => {
|
||||
const plugins = [];
|
||||
|
||||
for (let i = 0; i <= amount; i++) {
|
||||
plugins.push({
|
||||
defaultNavUrl: 'some/url',
|
||||
enabled: false,
|
||||
hasUpdate: false,
|
||||
id: `${i}`,
|
||||
info: {
|
||||
author: {
|
||||
name: 'Grafana Labs',
|
||||
url: 'url/to/GrafanaLabs',
|
||||
},
|
||||
description: 'pretty decent plugin',
|
||||
links: ['one link'],
|
||||
logos: { small: 'small/logo', large: 'large/logo' },
|
||||
screenshots: [{ path: `screenshot/${i}` }],
|
||||
updated: '2018-09-26',
|
||||
version: '1',
|
||||
},
|
||||
latestVersion: `1.${i}`,
|
||||
name: `pretty cool plugin-${i}`,
|
||||
pinned: false,
|
||||
state: '',
|
||||
type: '',
|
||||
module: {},
|
||||
});
|
||||
}
|
||||
|
||||
return plugins as any;
|
||||
};
|
||||
|
||||
export function getPanelPlugin(
|
||||
options: Partial<PanelPluginMeta>,
|
||||
reactPanel?: ComponentType<PanelProps>,
|
||||
angularPanel?: any
|
||||
): PanelPlugin {
|
||||
const plugin = new PanelPlugin(reactPanel!);
|
||||
plugin.angularPanelCtrl = angularPanel;
|
||||
plugin.meta = {
|
||||
id: options.id!,
|
||||
type: PluginType.panel,
|
||||
name: options.id!,
|
||||
sort: options.sort || 1,
|
||||
info: {
|
||||
author: {
|
||||
name: options.id + 'name',
|
||||
},
|
||||
description: '',
|
||||
links: [],
|
||||
logos: {
|
||||
large: '',
|
||||
small: '',
|
||||
},
|
||||
screenshots: [],
|
||||
updated: '',
|
||||
version: '',
|
||||
},
|
||||
hideFromList: options.hideFromList === true,
|
||||
module: options.module ?? '',
|
||||
baseUrl: '',
|
||||
};
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export function getMockPlugin(overrides?: Partial<PluginMeta>): PluginMeta {
|
||||
const defaults: PluginMeta = {
|
||||
defaultNavUrl: 'some/url',
|
||||
enabled: false,
|
||||
hasUpdate: false,
|
||||
id: '1',
|
||||
info: {
|
||||
author: {
|
||||
name: 'Grafana Labs',
|
||||
url: 'url/to/GrafanaLabs',
|
||||
},
|
||||
description: 'pretty decent plugin',
|
||||
links: [{ name: 'project', url: 'one link' }],
|
||||
logos: { small: 'small/logo', large: 'large/logo' },
|
||||
screenshots: [{ path: `screenshot`, name: 'test' }],
|
||||
updated: '2018-09-26',
|
||||
version: '1',
|
||||
},
|
||||
latestVersion: '1',
|
||||
name: 'pretty cool plugin 1',
|
||||
baseUrl: 'path/to/plugin',
|
||||
pinned: false,
|
||||
type: PluginType.panel,
|
||||
module: 'path/to/module',
|
||||
};
|
||||
|
||||
return defaultsDeep(overrides || {}, defaults) as PluginMeta;
|
||||
}
|
||||
@@ -5,13 +5,13 @@ import { Route, Router } from 'react-router-dom';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
|
||||
import { AppPlugin, PluginType, AppRootProps, NavModelItem } from '@grafana/data';
|
||||
import { getMockPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute';
|
||||
import { Echo } from 'app/core/services/echo/Echo';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { getMockPlugin } from '../__mocks__/pluginMocks';
|
||||
import { getPluginSettings } from '../pluginSettings';
|
||||
import { importAppPlugin } from '../plugin_loader';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { VizPanel } from './VizPanel';
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { DeepPartial } from '@reduxjs/toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import { AbsoluteTimeRange, FieldConfigSource, PanelModel, PanelPlugin, toUtc } from '@grafana/data';
|
||||
import {
|
||||
AbsoluteTimeRange,
|
||||
FieldConfigSource,
|
||||
PanelModel,
|
||||
PanelPlugin,
|
||||
toUtc,
|
||||
getPanelOptionsWithDefaults,
|
||||
} from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Field, Input } from '@grafana/ui';
|
||||
import { importPanelPlugin, syncGetPanelPlugin } from 'app/features/plugins/importPanelPlugin';
|
||||
|
||||
import { getPanelOptionsWithDefaults } from '../../../dashboard/state/getPanelOptionsWithDefaults';
|
||||
import { SceneObjectBase } from '../../core/SceneObjectBase';
|
||||
import { sceneGraph } from '../../core/sceneGraph';
|
||||
import { SceneComponentProps, SceneLayoutChildState } from '../../core/types';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||
import { getMockPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
import { getMockPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { SceneObject } from '../../core/types';
|
||||
import { CustomFormatterFn } from '../interpolation/sceneInterpolator';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { getMockPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { reduxTester } from '../../../../test/core/redux/reduxTester';
|
||||
import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks';
|
||||
import { variableAdapters } from '../adapters';
|
||||
import { changeVariableEditorExtended } from '../editor/reducer';
|
||||
import { datasourceBuilder } from '../shared/testing/builders';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { getMockPlugins } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
|
||||
import { reducerTester } from '../../../../test/core/redux/reducerTester';
|
||||
import { getMockPlugins } from '../../plugins/__mocks__/pluginMocks';
|
||||
import { getDataSourceInstanceSetting } from '../shared/testing/helpers';
|
||||
import { getVariableTestContext } from '../state/helpers';
|
||||
import { VariablesState } from '../state/types';
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { identityOverrideProcessor, ThresholdsMode } from '@grafana/data';
|
||||
|
||||
export function mockStandardFieldConfigOptions() {
|
||||
const category = ['Standard options'];
|
||||
|
||||
const unit = {
|
||||
category,
|
||||
id: 'unit',
|
||||
path: 'unit',
|
||||
name: 'Unit',
|
||||
description: 'Value units',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const decimals = {
|
||||
category,
|
||||
id: 'decimals',
|
||||
path: 'decimals',
|
||||
name: 'Decimals',
|
||||
description: 'Number of decimal to be shown for a value',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const boolean = {
|
||||
category,
|
||||
id: 'boolean',
|
||||
path: 'boolean',
|
||||
name: 'Boolean',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const fieldColor = {
|
||||
category,
|
||||
id: 'color',
|
||||
path: 'color',
|
||||
name: 'color',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const text = {
|
||||
category,
|
||||
id: 'text',
|
||||
path: 'text',
|
||||
name: 'text',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const number = {
|
||||
category,
|
||||
id: 'number',
|
||||
path: 'number',
|
||||
name: 'number',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const thresholds = {
|
||||
category: ['Thresholds'],
|
||||
id: 'thresholds',
|
||||
path: 'thresholds',
|
||||
name: 'thresholds',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
defaultValue: {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
steps: [
|
||||
{ value: -Infinity, color: 'green' },
|
||||
{ value: 80, color: 'red' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
return [unit, decimals, boolean, fieldColor, text, number, thresholds];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user