Field Config API: Add ability to hide field option or disable it from the overrides (#29879)

* Add ability to hide field option or disable it from the overrides

* Rename options

* Tests
This commit is contained in:
Dominik Prokop 2020-12-17 09:20:47 +01:00 committed by GitHub
parent 7adccf1e67
commit 332f2f1ae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 553 additions and 302 deletions

View File

@ -48,6 +48,12 @@ export interface FieldConfigEditorConfig<TOptions, TSettings = any, TValue = any
* @param field
*/
shouldApply?: (field: Field) => boolean;
/** Indicates that option shoukd not be available in the Field config tab */
hideFromDefaults?: boolean;
/** Indicates that option should not be available for the overrides */
hideFromOverrides?: boolean;
}
export interface FieldConfigPropertyItem<TOptions = any, TValue = any, TSettings extends {} = any>
@ -58,10 +64,16 @@ export interface FieldConfigPropertyItem<TOptions = any, TValue = any, TSettings
/** true for plugin field config properties */
isCustom?: boolean;
// Convert the override value to a well typed value
/** Hides option from the Field config tab */
hideFromDefaults?: boolean;
/** Indicates that option should not be available for the overrides */
hideFromOverrides?: boolean;
/** Convert the override value to a well typed value */
process: (value: any, context: FieldOverrideContext, settings?: TSettings) => TValue | undefined | null;
// Checks if field should be processed
/** Checks if field should be processed */
shouldApply: (field: Field) => boolean;
}

View File

@ -64,6 +64,9 @@ export const Components = {
DataPane: {
content: 'Panel editor data pane content',
},
FieldOptions: {
propertyEditor: (type: string) => `${type} field property editor`,
},
},
PanelInspector: {
Data: {
@ -151,6 +154,7 @@ export const Components = {
},
QueryField: { container: 'Query field' },
ValuePicker: {
button: 'Value picker add button',
select: (name: string) => `Value picker select ${name}`,
},
Search: {

View File

@ -1,11 +1,11 @@
import React from 'react';
import React, { HTMLAttributes } from 'react';
import { Label } from './Label';
import { stylesFactory, useTheme } from '../../themes';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { FieldValidationMessage } from './FieldValidationMessage';
export interface FieldProps {
export interface FieldProps extends HTMLAttributes<HTMLDivElement> {
/** Form input element, i.e Input or Switch */
children: React.ReactElement;
/** Label for the field */
@ -59,6 +59,7 @@ export const Field: React.FC<FieldProps> = ({
error,
children,
className,
...otherProps
}) => {
const theme = useTheme();
let inputId;
@ -81,7 +82,7 @@ export const Field: React.FC<FieldProps> = ({
);
return (
<div className={cx(styles.field, horizontal && styles.fieldHorizontal, className)}>
<div className={cx(styles.field, horizontal && styles.fieldHorizontal, className)} {...otherProps}>
{labelElement}
<div>
{React.cloneElement(children, { invalid, disabled, loading })}

View File

@ -39,7 +39,13 @@ export function ValuePicker<T>({
const [isPicking, setIsPicking] = useState(false);
const buttonEl = (
<Button size={size || 'sm'} icon={icon || 'plus'} onClick={() => setIsPicking(true)} variant={variant}>
<Button
size={size || 'sm'}
icon={icon || 'plus'}
onClick={() => setIsPicking(true)}
variant={variant}
aria-label={selectors.components.ValuePicker.button}
>
{label}
</Button>
);

View File

@ -0,0 +1,89 @@
import React from 'react';
import { render } from '@testing-library/react';
import { DefaultFieldConfigEditor } from './DefaultFieldConfigEditor';
import {
FieldConfigEditorConfig,
FieldConfigSource,
PanelPlugin,
standardEditorsRegistry,
standardFieldConfigEditorRegistry,
} from '@grafana/data';
import { mockStandardFieldConfigOptions } from '../../../../../test/helpers/fieldConfig';
import { selectors } from '@grafana/e2e-selectors';
interface FakeFieldOptions {
a: boolean;
b: string;
c: boolean;
}
standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions());
standardEditorsRegistry.setInit(() => mockStandardFieldConfigOptions());
const fieldConfigMock: FieldConfigSource<FakeFieldOptions> = {
defaults: {
custom: {
a: true,
b: 'test',
c: true,
},
},
overrides: [],
};
describe('DefaultFieldConfigEditor', () => {
it('should render custom options', () => {
const plugin = new PanelPlugin(() => null).useFieldConfig({
standardOptions: {},
useCustomConfig: b => {
b.addBooleanSwitch({
name: 'a',
path: 'a',
} as FieldConfigEditorConfig<FakeFieldOptions>)
.addBooleanSwitch({
name: 'c',
path: 'c',
} as FieldConfigEditorConfig<FakeFieldOptions>)
.addTextInput({
name: 'b',
path: 'b',
} as FieldConfigEditorConfig<FakeFieldOptions>);
},
});
const { queryAllByLabelText } = render(
<DefaultFieldConfigEditor data={[]} onChange={jest.fn()} plugin={plugin} config={fieldConfigMock} />
);
const editors = queryAllByLabelText(selectors.components.PanelEditor.FieldOptions.propertyEditor('Custom'));
expect(editors).toHaveLength(3);
});
it('should not render options that are marked as hidden from defaults', () => {
const plugin = new PanelPlugin(() => null).useFieldConfig({
standardOptions: {},
useCustomConfig: b => {
b.addBooleanSwitch({
name: 'a',
path: 'a',
hideFromDefaults: true,
} as FieldConfigEditorConfig<FakeFieldOptions>)
.addBooleanSwitch({
name: 'c',
path: 'c',
} as FieldConfigEditorConfig<FakeFieldOptions>)
.addTextInput({
name: 'b',
path: 'b',
} as FieldConfigEditorConfig<FakeFieldOptions>);
},
});
const { queryAllByLabelText } = render(
<DefaultFieldConfigEditor data={[]} onChange={jest.fn()} plugin={plugin} config={fieldConfigMock} />
);
const editors = queryAllByLabelText(selectors.components.PanelEditor.FieldOptions.propertyEditor('Custom'));
expect(editors).toHaveLength(2);
});
});

View File

@ -0,0 +1,114 @@
import React, { useCallback, ReactNode } from 'react';
import { get, groupBy } from 'lodash';
import { Counter, Field, Label } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
import { updateDefaultFieldConfigValue } from './utils';
import { FieldConfigPropertyItem, FieldConfigSource, VariableSuggestionsScope } from '@grafana/data';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { OptionsGroup } from './OptionsGroup';
import { Props } from './types';
export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, config, plugin }) => {
const onDefaultValueChange = useCallback(
(name: string, value: any, isCustom: boolean | undefined) => {
onChange(updateDefaultFieldConfigValue(config, name, value, isCustom));
},
[config, onChange]
);
const renderEditor = useCallback(
(item: FieldConfigPropertyItem, categoryItemCount: number) => {
if (item.isCustom && item.showIf && !item.showIf(config.defaults.custom)) {
return null;
}
if (item.hideFromDefaults) {
return null;
}
const defaults = config.defaults;
const value = item.isCustom
? defaults.custom
? get(defaults.custom, item.path)
: undefined
: get(defaults, item.path);
let label: ReactNode | undefined = (
<Label description={item.description} category={item.category?.slice(1)}>
{item.name}
</Label>
);
// hide label if there is only one item and category name is same as item, name
if (categoryItemCount === 1 && item.category?.[0] === item.name) {
label = undefined;
}
return (
<Field
label={label}
key={`${item.id}/${item.isCustom}`}
aria-label={selectors.components.PanelEditor.FieldOptions.propertyEditor(
item.isCustom ? 'Custom' : 'Default'
)}
>
<item.editor
item={item}
value={value}
onChange={v => onDefaultValueChange(item.path, v, item.isCustom)}
context={{
data,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
}}
/>
</Field>
);
},
[config]
);
const groupedConfigs = groupBy(plugin.fieldConfigRegistry.list(), i => i.category && i.category[0]);
return (
<div aria-label={selectors.components.FieldConfigEditor.content}>
{Object.keys(groupedConfigs).map((k, i) => {
const groupItemsCounter = countGroupItems(groupedConfigs[k], config);
return (
<OptionsGroup
renderTitle={isExpanded => {
return (
<>
{k} {!isExpanded && groupItemsCounter && <Counter value={groupItemsCounter} />}
</>
);
}}
id={`${k}/${i}`}
key={`${k}/${i}`}
>
{groupedConfigs[k].map(c => {
return renderEditor(c, groupedConfigs[k].length);
})}
</OptionsGroup>
);
})}
</div>
);
};
const countGroupItems = (group: FieldConfigPropertyItem[], config: FieldConfigSource) => {
let counter = 0;
for (const item of group) {
const value = item.isCustom
? config.defaults.custom
? config.defaults.custom[item.path]
: undefined
: (config.defaults as any)[item.path];
if (item.getItemsCount && item.getItemsCount(value) > 0) {
counter = counter + item.getItemsCount(value);
}
}
return counter === 0 ? undefined : counter;
};

View File

@ -1,225 +0,0 @@
import React, { ReactNode, useCallback } from 'react';
import { get as lodashGet, cloneDeep } from 'lodash';
import {
DataFrame,
DocsId,
FieldConfigPropertyItem,
FieldConfigSource,
PanelPlugin,
SelectableValue,
VariableSuggestionsScope,
} from '@grafana/data';
import { Container, Counter, FeatureInfoBox, Field, fieldMatchersUI, Label, useTheme, ValuePicker } from '@grafana/ui';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { OverrideEditor } from './OverrideEditor';
import groupBy from 'lodash/groupBy';
import { OptionsGroup } from './OptionsGroup';
import { selectors } from '@grafana/e2e-selectors';
import { css } from 'emotion';
import { getDocsLink } from 'app/core/utils/docsLinks';
import { updateDefaultFieldConfigValue } from './utils';
interface Props {
plugin: PanelPlugin;
config: FieldConfigSource;
onChange: (config: FieldConfigSource) => void;
/* Helpful for IntelliSense */
data: DataFrame[];
}
/**
* Expects the container div to have size set and will fill it 100%
*/
export const OverrideFieldConfigEditor: React.FC<Props> = props => {
const theme = useTheme();
const { config } = props;
const onOverrideChange = (index: number, override: any) => {
const { config } = props;
let overrides = cloneDeep(config.overrides);
overrides[index] = override;
props.onChange({ ...config, overrides });
};
const onOverrideRemove = (overrideIndex: number) => {
const { config } = props;
let overrides = cloneDeep(config.overrides);
overrides.splice(overrideIndex, 1);
props.onChange({ ...config, overrides });
};
const onOverrideAdd = (value: SelectableValue<string>) => {
const { onChange, config } = props;
onChange({
...config,
overrides: [
...config.overrides,
{
matcher: {
id: value.value!,
},
properties: [],
},
],
});
};
const renderOverrides = () => {
const { config, data, plugin } = props;
const { fieldConfigRegistry } = plugin;
if (config.overrides.length === 0) {
return null;
}
return (
<div>
{config.overrides.map((o, i) => {
// TODO: apply matcher to retrieve fields
return (
<OverrideEditor
name={`Override ${i + 1}`}
key={`${o.matcher.id}/${i}`}
data={data}
override={o}
onChange={value => onOverrideChange(i, value)}
onRemove={() => onOverrideRemove(i)}
registry={fieldConfigRegistry}
/>
);
})}
</div>
);
};
const renderAddOverride = () => {
return (
<Container padding="md">
<ValuePicker
icon="plus"
label="Add an override for"
variant="secondary"
options={fieldMatchersUI
.list()
.map<SelectableValue<string>>(i => ({ label: i.name, value: i.id, description: i.description }))}
onChange={value => onOverrideAdd(value)}
isFullWidth={false}
/>
</Container>
);
};
return (
<div aria-label={selectors.components.OverridesConfigEditor.content}>
{config.overrides.length === 0 && (
<FeatureInfoBox
title="Overrides"
url={getDocsLink(DocsId.FieldConfigOverrides)}
className={css`
margin: ${theme.spacing.md};
`}
>
Field override rules give you a fine grained control over how your data is displayed.
</FeatureInfoBox>
)}
{renderOverrides()}
{renderAddOverride()}
</div>
);
};
export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, config, plugin }) => {
const onDefaultValueChange = useCallback(
(name: string, value: any, isCustom: boolean | undefined) => {
onChange(updateDefaultFieldConfigValue(config, name, value, isCustom));
},
[config, onChange]
);
const renderEditor = useCallback(
(item: FieldConfigPropertyItem, categoryItemCount: number) => {
if (item.isCustom && item.showIf && !item.showIf(config.defaults.custom)) {
return null;
}
const defaults = config.defaults;
const value = item.isCustom
? defaults.custom
? lodashGet(defaults.custom, item.path)
: undefined
: lodashGet(defaults, item.path);
let label: ReactNode | undefined = (
<Label description={item.description} category={item.category?.slice(1)}>
{item.name}
</Label>
);
// hide label if there is only one item and category name is same as item, name
if (categoryItemCount === 1 && item.category?.[0] === item.name) {
label = undefined;
}
return (
<Field label={label} key={`${item.id}/${item.isCustom}`}>
<item.editor
item={item}
value={value}
onChange={v => onDefaultValueChange(item.path, v, item.isCustom)}
context={{
data,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
}}
/>
</Field>
);
},
[config]
);
const groupedConfigs = groupBy(plugin.fieldConfigRegistry.list(), i => i.category && i.category[0]);
return (
<div aria-label={selectors.components.FieldConfigEditor.content}>
{Object.keys(groupedConfigs).map((k, i) => {
const groupItemsCounter = countGroupItems(groupedConfigs[k], config);
return (
<OptionsGroup
renderTitle={isExpanded => {
return (
<>
{k} {!isExpanded && groupItemsCounter && <Counter value={groupItemsCounter} />}
</>
);
}}
id={`${k}/${i}`}
key={`${k}/${i}`}
>
{groupedConfigs[k].map(c => {
return renderEditor(c, groupedConfigs[k].length);
})}
</OptionsGroup>
);
})}
</div>
);
};
const countGroupItems = (group: FieldConfigPropertyItem[], config: FieldConfigSource) => {
let counter = 0;
for (const item of group) {
const value = item.isCustom
? config.defaults.custom
? config.defaults.custom[item.path]
: undefined
: (config.defaults as any)[item.path];
if (item.getItemsCount && item.getItemsCount(value) > 0) {
counter = counter + item.getItemsCount(value);
}
}
return counter === 0 ? undefined : counter;
};

View File

@ -3,7 +3,8 @@ import Transition from 'react-transition-group/Transition';
import { FieldConfigSource, GrafanaTheme, PanelPlugin, SelectableValue } from '@grafana/data';
import { DashboardModel, PanelModel } from '../../state';
import { CustomScrollbar, Icon, Input, Select, stylesFactory, Tab, TabContent, TabsBar, useTheme } from '@grafana/ui';
import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConfigEditor';
import { OverrideFieldConfigEditor } from './OverrideFieldConfigEditor';
import { DefaultFieldConfigEditor } from './DefaultFieldConfigEditor';
import { css } from 'emotion';
import { PanelOptionsTab } from './PanelOptionsTab';
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';

View File

@ -0,0 +1,96 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { FieldConfigOptionsRegistry } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { OverrideEditor } from './OverrideEditor';
describe('OverrideEditor', () => {
let registry: FieldConfigOptionsRegistry;
beforeEach(() => {
registry = new FieldConfigOptionsRegistry(() => {
return [
{
id: 'lineColor',
name: 'Line color',
path: 'lineColor',
isCustom: true,
shouldApply: () => true,
process: () => null,
override: () => null,
editor: () => null,
},
{
id: 'lineWidth',
name: 'Line width',
path: 'lineWidth',
isCustom: true,
shouldApply: () => true,
process: () => null,
override: () => null,
editor: () => null,
},
];
});
});
it('allow override option selection', () => {
const { queryAllByLabelText, getByLabelText } = render(
<OverrideEditor
name={'test'}
data={[]}
override={{
matcher: {
id: 'byName',
options: 'A-series',
},
properties: [],
}}
registry={registry}
onChange={() => {}}
onRemove={() => {}}
/>
);
fireEvent.click(getByLabelText(selectors.components.ValuePicker.button));
const selectOptions = queryAllByLabelText(selectors.components.Select.option);
expect(selectOptions).toHaveLength(2);
});
it('should not allow override selection that marked as hidden from overrides', () => {
registry.register({
id: 'lineStyle',
name: 'Line style',
path: 'lineStyle',
isCustom: true,
shouldApply: () => true,
process: () => null,
override: () => null,
editor: () => null,
hideFromOverrides: true,
});
const { queryAllByLabelText, getByLabelText } = render(
<OverrideEditor
name={'test'}
data={[]}
override={{
matcher: {
id: 'byName',
options: 'A-series',
},
properties: [],
}}
registry={registry}
onChange={() => {}}
onRemove={() => {}}
/>
);
fireEvent.click(getByLabelText(selectors.components.ValuePicker.button));
const selectOptions = queryAllByLabelText(selectors.components.Select.option);
expect(selectOptions).toHaveLength(2);
});
});

View File

@ -97,17 +97,20 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
[override, onChange]
);
let configPropertiesOptions = registry.list().map(item => {
let label = item.name;
if (item.category && item.category.length > 1) {
label = [...item.category!.slice(1), item.name].join(' > ');
}
return {
label,
value: item.id,
description: item.description,
};
});
let configPropertiesOptions = registry
.list()
.filter(o => !o.hideFromOverrides)
.map(item => {
let label = item.name;
if (item.category && item.category.length > 1) {
label = [...item.category!.slice(1), item.name].join(' > ');
}
return {
label,
value: item.id,
description: item.description,
};
});
const renderOverrideTitle = (isExpanded: boolean) => {
const overriddenProperites = override.properties.map(p => registry.get(p.id).name).join(', ');
@ -151,6 +154,7 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
if (!item) {
return <div>Unknown property: {p.id}</div>;
}
const isCollapsible =
Array.isArray(p.value) || COLLECTION_STANDARD_PROPERTIES.includes(p.id as FieldConfigProperty);

View File

@ -0,0 +1,111 @@
import React from 'react';
import { cloneDeep } from 'lodash';
import { DocsId, SelectableValue } from '@grafana/data';
import { Container, FeatureInfoBox, fieldMatchersUI, useTheme, ValuePicker } from '@grafana/ui';
import { OverrideEditor } from './OverrideEditor';
import { selectors } from '@grafana/e2e-selectors';
import { css } from 'emotion';
import { getDocsLink } from 'app/core/utils/docsLinks';
import { Props } from './types';
/**
* Expects the container div to have size set and will fill it 100%
*/
export const OverrideFieldConfigEditor: React.FC<Props> = props => {
const theme = useTheme();
const { config } = props;
const onOverrideChange = (index: number, override: any) => {
const { config } = props;
let overrides = cloneDeep(config.overrides);
overrides[index] = override;
props.onChange({ ...config, overrides });
};
const onOverrideRemove = (overrideIndex: number) => {
const { config } = props;
let overrides = cloneDeep(config.overrides);
overrides.splice(overrideIndex, 1);
props.onChange({ ...config, overrides });
};
const onOverrideAdd = (value: SelectableValue<string>) => {
const { onChange, config } = props;
onChange({
...config,
overrides: [
...config.overrides,
{
matcher: {
id: value.value!,
},
properties: [],
},
],
});
};
const renderOverrides = () => {
const { config, data, plugin } = props;
const { fieldConfigRegistry } = plugin;
if (config.overrides.length === 0) {
return null;
}
return (
<div>
{config.overrides.map((o, i) => {
// TODO: apply matcher to retrieve fields
return (
<OverrideEditor
name={`Override ${i + 1}`}
key={`${o.matcher.id}/${i}`}
data={data}
override={o}
onChange={value => onOverrideChange(i, value)}
onRemove={() => onOverrideRemove(i)}
registry={fieldConfigRegistry}
/>
);
})}
</div>
);
};
const renderAddOverride = () => {
return (
<Container padding="md">
<ValuePicker
icon="plus"
label="Add an override for"
variant="secondary"
options={fieldMatchersUI
.list()
.map<SelectableValue<string>>(i => ({ label: i.name, value: i.id, description: i.description }))}
onChange={value => onOverrideAdd(value)}
isFullWidth={false}
/>
</Container>
);
};
return (
<div aria-label={selectors.components.OverridesConfigEditor.content}>
{config.overrides.length === 0 && (
<FeatureInfoBox
title="Overrides"
url={getDocsLink(DocsId.FieldConfigOverrides)}
className={css`
margin: ${theme.spacing.md};
`}
>
Field override rules give you a fine grained control over how your data is displayed.
</FeatureInfoBox>
)}
{renderOverrides()}
{renderAddOverride()}
</div>
);
};

View File

@ -1,3 +1,5 @@
import { DataFrame, FieldConfigSource, PanelPlugin } from '@grafana/data';
export interface PanelEditorTab {
id: string;
text: string;
@ -23,3 +25,12 @@ export const displayModes = [
{ value: DisplayMode.Fit, label: 'Fit', description: 'Fit in the space keeping ratio' },
{ value: DisplayMode.Exact, label: 'Exact', description: 'Same size as the dashboard' },
];
/** @internal */
export interface Props {
plugin: PanelPlugin;
config: FieldConfigSource;
onChange: (config: FieldConfigSource) => void;
/* Helpful for IntelliSense */
data: DataFrame[];
}

View File

@ -2,7 +2,6 @@ import { PanelModel } from './PanelModel';
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
import {
FieldConfigProperty,
identityOverrideProcessor,
PanelProps,
standardEditorsRegistry,
standardFieldConfigEditorRegistry,
@ -19,9 +18,10 @@ import { TemplateSrv } from '../../templating/template_srv';
import { setTemplateSrv } from '@grafana/runtime';
import { variableAdapters } from '../../variables/adapters';
import { createQueryVariableAdapter } from '../../variables/query/adapter';
import { mockStandardFieldConfigOptions } from '../../../../test/helpers/fieldConfig';
standardFieldConfigEditorRegistry.setInit(() => mockStandardProperties());
standardEditorsRegistry.setInit(() => mockStandardProperties());
standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions());
standardEditorsRegistry.setInit(() => mockStandardFieldConfigOptions());
setTimeSrv({
timeRangeForUrl: () => ({
@ -450,62 +450,6 @@ describe('PanelModel', () => {
});
});
export const mockStandardProperties = () => {
const unit = {
id: 'unit',
path: 'unit',
name: 'Unit',
description: 'Value units',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
const decimals = {
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 = {
id: 'boolean',
path: 'boolean',
name: 'Boolean',
description: '',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
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];
};
const variablesMock = [
{
type: 'query',

View File

@ -0,0 +1,83 @@
import { identityOverrideProcessor } from '@grafana/data';
export function mockStandardFieldConfigOptions() {
const unit = {
id: 'unit',
path: 'unit',
name: 'Unit',
description: 'Value units',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
const decimals = {
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 = {
id: 'boolean',
path: 'boolean',
name: 'Boolean',
description: '',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
const fieldColor = {
id: 'color',
path: 'color',
name: 'color',
description: '',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
const text = {
id: 'text',
path: 'text',
name: 'text',
description: '',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
const number = {
id: 'number',
path: 'number',
name: 'number',
description: '',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
return [unit, decimals, boolean, fieldColor, text, number];
}