mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PanelOptions: Don't mutate panel options/field config object when updating (#36441)
* PanelOptions: Don't mutate panel options/field config object when updating * Review * maybe faster, plus value === '' fix Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { StandardEditorContext, VariableSuggestionsScope } from '@grafana/data';
|
||||
import { get as lodashGet, set as lodashSet } from 'lodash';
|
||||
import { get as lodashGet } from 'lodash';
|
||||
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
||||
import { OptionPaneRenderProps } from './types';
|
||||
import { updateDefaultFieldConfigValue } from './utils';
|
||||
import { updateDefaultFieldConfigValue, setOptionImmutably } from './utils';
|
||||
import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor';
|
||||
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
|
||||
|
||||
@@ -54,7 +54,7 @@ export function getVizualizationOptions(props: OptionPaneRenderProps): OptionsPa
|
||||
description: pluginOption.description,
|
||||
render: function renderEditor() {
|
||||
const onChange = (value: any) => {
|
||||
const newOptions = lodashSet({ ...currentOptions }, pluginOption.path, value);
|
||||
const newOptions = setOptionImmutably(currentOptions, pluginOption.path, value);
|
||||
onPanelOptionsChanged(newOptions);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FieldConfig, FieldConfigSource, PanelPlugin, standardFieldConfigEditorRegistry } from '@grafana/data';
|
||||
import { supportsDataQuery, updateDefaultFieldConfigValue } from './utils';
|
||||
import { setOptionImmutably, supportsDataQuery, updateDefaultFieldConfigValue } from './utils';
|
||||
|
||||
describe('standardFieldConfigEditorRegistry', () => {
|
||||
const dummyConfig: FieldConfig = {
|
||||
@@ -86,3 +86,26 @@ describe('updateDefaultFieldConfigValue', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('setOptionImmutably', () => {
|
||||
it.each`
|
||||
source | path | value | expected
|
||||
${{}} | ${'a'} | ${1} | ${{ a: 1 }}
|
||||
${{}} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
|
||||
${{ a: {} }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
|
||||
${{ b: {} }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } }, b: {} }}
|
||||
${{ a: { b: { c: 3 } } }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
|
||||
`('numeric-like text mapping, value:${value', ({ source, path, value, expected }) => {
|
||||
expect(setOptionImmutably(source, path, value)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('does not mutate object under a path', () => {
|
||||
const input = { a: { b: { c: { d: 1 }, e: { f: 1 } } }, aa: 1 };
|
||||
const result = setOptionImmutably(input, 'a.b.c', { d: 2 });
|
||||
expect(input.a).not.toEqual(result.a);
|
||||
expect(input.aa).toEqual(result.aa);
|
||||
expect(input.a.b).not.toEqual(result.a.b);
|
||||
expect(input.a.b.c).not.toEqual(result.a.b.c);
|
||||
expect(input.a.b.e).toEqual(result.a.b.e);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CSSProperties } from 'react';
|
||||
import { set as lodashSet, omit } from 'lodash';
|
||||
import { omit } from 'lodash';
|
||||
import { FieldConfigSource, PanelPlugin } from '@grafana/data';
|
||||
import { PanelModel } from '../../state/PanelModel';
|
||||
import { DisplayMode } from './types';
|
||||
@@ -41,22 +41,22 @@ export const updateDefaultFieldConfigValue = (
|
||||
isCustom?: boolean
|
||||
) => {
|
||||
let defaults = { ...config.defaults };
|
||||
const remove = value === undefined || value === null || '';
|
||||
const remove = value == null || value === '';
|
||||
|
||||
if (isCustom) {
|
||||
if (defaults.custom) {
|
||||
if (remove) {
|
||||
defaults.custom = omit(defaults.custom, name);
|
||||
} else {
|
||||
defaults.custom = lodashSet({ ...defaults.custom }, name, value);
|
||||
defaults.custom = setOptionImmutably(defaults.custom, name, value);
|
||||
}
|
||||
} else if (!remove) {
|
||||
defaults.custom = lodashSet({ ...defaults.custom }, name, value);
|
||||
defaults.custom = setOptionImmutably(defaults.custom, name, value);
|
||||
}
|
||||
} else if (remove) {
|
||||
defaults = omit(defaults, name);
|
||||
} else {
|
||||
defaults = lodashSet({ ...defaults }, name, value);
|
||||
defaults = setOptionImmutably(defaults, name, value);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -64,3 +64,21 @@ export const updateDefaultFieldConfigValue = (
|
||||
defaults,
|
||||
};
|
||||
};
|
||||
|
||||
export function setOptionImmutably<T extends object>(options: T, path: string | string[], value: any): T {
|
||||
const splat = !Array.isArray(path) ? path.split('.') : path;
|
||||
|
||||
const key = splat.shift()!;
|
||||
|
||||
if (!splat.length) {
|
||||
return { ...options, [key]: value };
|
||||
}
|
||||
|
||||
let current = (options as Record<string, any>)[key];
|
||||
|
||||
if (current == null || typeof current !== 'object') {
|
||||
current = {};
|
||||
}
|
||||
|
||||
return { ...options, [key]: setOptionImmutably(current, splat, value) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user