Field config: Add support for paths in default field config setup (#27570)

* Add support for paths in default field config setup

* Typecheck fix
This commit is contained in:
Dominik Prokop 2020-09-15 10:17:58 +02:00 committed by GitHub
parent e350e1fff6
commit e04e3e7d46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 31 deletions

View File

@ -1,13 +1,13 @@
import React, { useCallback, ReactNode } from 'react'; import React, { ReactNode, useCallback } from 'react';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { import {
DataFrame, DataFrame,
DocsId,
FieldConfigPropertyItem, FieldConfigPropertyItem,
FieldConfigSource, FieldConfigSource,
PanelPlugin, PanelPlugin,
SelectableValue, SelectableValue,
VariableSuggestionsScope, VariableSuggestionsScope,
DocsId,
} from '@grafana/data'; } from '@grafana/data';
import { Container, Counter, FeatureInfoBox, Field, fieldMatchersUI, Label, useTheme, ValuePicker } from '@grafana/ui'; import { Container, Counter, FeatureInfoBox, Field, fieldMatchersUI, Label, useTheme, ValuePicker } from '@grafana/ui';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv'; import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
@ -17,6 +17,7 @@ import { OptionsGroup } from './OptionsGroup';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { css } from 'emotion'; import { css } from 'emotion';
import { getDocsLink } from 'app/core/utils/docsLinks'; import { getDocsLink } from 'app/core/utils/docsLinks';
import { updateDefaultFieldConfigValue } from './utils';
interface Props { interface Props {
plugin: PanelPlugin; plugin: PanelPlugin;
@ -32,6 +33,7 @@ interface Props {
export const OverrideFieldConfigEditor: React.FC<Props> = props => { export const OverrideFieldConfigEditor: React.FC<Props> = props => {
const theme = useTheme(); const theme = useTheme();
const { config } = props; const { config } = props;
const onOverrideChange = (index: number, override: any) => { const onOverrideChange = (index: number, override: any) => {
const { config } = props; const { config } = props;
let overrides = cloneDeep(config.overrides); let overrides = cloneDeep(config.overrides);
@ -128,32 +130,9 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
}; };
export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, config, plugin }) => { export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, config, plugin }) => {
const setDefaultValue = useCallback( const onDefaultValueChange = useCallback(
(name: string, value: any, isCustom: boolean | undefined) => { (name: string, value: any, isCustom: boolean | undefined) => {
const defaults = { ...config.defaults }; onChange(updateDefaultFieldConfigValue(config, name, value, isCustom));
const remove = value === undefined || value === null || '';
if (isCustom) {
if (defaults.custom) {
if (remove) {
defaults.custom = { ...defaults.custom };
delete defaults.custom[name];
} else {
defaults.custom = { ...defaults.custom, [name]: value };
}
} else if (!remove) {
defaults.custom = { [name]: value };
}
} else if (remove) {
delete (defaults as any)[name];
} else {
(defaults as any)[name] = value;
}
onChange({
...config,
defaults,
});
}, },
[config, onChange] [config, onChange]
); );
@ -187,7 +166,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
<item.editor <item.editor
item={item} item={item}
value={value} value={value}
onChange={v => setDefaultValue(item.path, v, item.isCustom)} onChange={v => onDefaultValueChange(item.path, v, item.isCustom)}
context={{ context={{
data, data,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope), getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),

View File

@ -1,5 +1,5 @@
import { FieldConfig, PanelPlugin, standardFieldConfigEditorRegistry } from '@grafana/data'; import { FieldConfig, FieldConfigSource, PanelPlugin, standardFieldConfigEditorRegistry } from '@grafana/data';
import { supportsDataQuery } from './utils'; import { supportsDataQuery, updateDefaultFieldConfigValue } from './utils';
describe('standardFieldConfigEditorRegistry', () => { describe('standardFieldConfigEditorRegistry', () => {
const dummyConfig: FieldConfig = { const dummyConfig: FieldConfig = {
@ -50,3 +50,39 @@ describe('supportsDataQuery', () => {
}); });
}); });
}); });
describe('updateDefaultFieldConfigValue', () => {
it.each`
property | isCustom | newValue | expected
${'a'} | ${false} | ${2} | ${{ a: 2, b: { c: 'nested default' }, custom: { d: 1, e: { f: 'nested custom' } } }}
${'b.c'} | ${false} | ${'nested default updated'} | ${{ a: 1, b: { c: 'nested default updated' }, custom: { d: 1, e: { f: 'nested custom' } } }}
${'a'} | ${false} | ${undefined} | ${{ b: { c: 'nested default' }, custom: { d: 1, e: { f: 'nested custom' } } }}
${'b'} | ${false} | ${undefined} | ${{ a: 1, custom: { d: 1, e: { f: 'nested custom' } } }}
${'b.c'} | ${false} | ${undefined} | ${{ a: 1, b: {}, custom: { d: 1, e: { f: 'nested custom' } } }}
${'d'} | ${true} | ${2} | ${{ a: 1, b: { c: 'nested default' }, custom: { d: 2, e: { f: 'nested custom' } } }}
${'e.f'} | ${true} | ${'nested custom updated'} | ${{ a: 1, b: { c: 'nested default' }, custom: { d: 1, e: { f: 'nested custom updated' } } }}
${'d'} | ${true} | ${undefined} | ${{ a: 1, b: { c: 'nested default' }, custom: { e: { f: 'nested custom' } } }}
${'e'} | ${true} | ${undefined} | ${{ a: 1, b: { c: 'nested default' }, custom: { d: 1 } }}
${'e.f'} | ${true} | ${undefined} | ${{ a: 1, b: { c: 'nested default' }, custom: { d: 1, e: {} } }}
`(
'when updating property:$property (is custom: $isCustom) with $newValue',
({ property, isCustom, newValue, expected }) => {
const config = {
defaults: {
a: 1,
b: {
c: 'nested default',
},
custom: {
d: 1,
e: { f: 'nested custom' },
},
},
overrides: [],
};
expect(updateDefaultFieldConfigValue(config as FieldConfigSource, property, newValue, isCustom).defaults).toEqual(
expected
);
}
);
});

View File

@ -1,8 +1,9 @@
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
import { set as lodashSet, omit } from 'lodash';
import { FieldConfigSource, PanelPlugin } from '@grafana/data';
import { PanelModel } from '../../state/PanelModel'; import { PanelModel } from '../../state/PanelModel';
import { DisplayMode } from './types'; import { DisplayMode } from './types';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { PanelPlugin } from '@grafana/data';
export function calculatePanelSize(mode: DisplayMode, width: number, height: number, panel: PanelModel): CSSProperties { export function calculatePanelSize(mode: DisplayMode, width: number, height: number, panel: PanelModel): CSSProperties {
if (mode === DisplayMode.Fill) { if (mode === DisplayMode.Fill) {
@ -29,3 +30,34 @@ export function calculatePanelSize(mode: DisplayMode, width: number, height: num
export function supportsDataQuery(plugin: PanelPlugin | undefined): boolean { export function supportsDataQuery(plugin: PanelPlugin | undefined): boolean {
return plugin?.meta.skipDataQuery === false; return plugin?.meta.skipDataQuery === false;
} }
export const updateDefaultFieldConfigValue = (
config: FieldConfigSource,
name: string,
value: any,
isCustom?: boolean
) => {
let defaults = { ...config.defaults };
const remove = value === undefined || value === null || '';
if (isCustom) {
if (defaults.custom) {
if (remove) {
defaults.custom = omit(defaults.custom, name);
} else {
defaults.custom = lodashSet({ ...defaults.custom }, name, value);
}
} else if (!remove) {
defaults.custom = lodashSet({ ...defaults.custom }, name, value);
}
} else if (remove) {
defaults = omit(defaults, name);
} else {
defaults = lodashSet({ ...defaults }, name, value);
}
return {
...config,
defaults,
};
};