diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index deb0e6e673a..c1db1bd754f 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -48,6 +48,12 @@ export interface FieldConfigEditorConfig 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 @@ -58,10 +64,16 @@ export interface FieldConfigPropertyItem TValue | undefined | null; - // Checks if field should be processed + /** Checks if field should be processed */ shouldApply: (field: Field) => boolean; } diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index ac57809b073..5776090ae60 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -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: { diff --git a/packages/grafana-ui/src/components/Forms/Field.tsx b/packages/grafana-ui/src/components/Forms/Field.tsx index 0496c5c0e5d..96b1f7a5063 100644 --- a/packages/grafana-ui/src/components/Forms/Field.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.tsx @@ -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 { /** Form input element, i.e Input or Switch */ children: React.ReactElement; /** Label for the field */ @@ -59,6 +59,7 @@ export const Field: React.FC = ({ error, children, className, + ...otherProps }) => { const theme = useTheme(); let inputId; @@ -81,7 +82,7 @@ export const Field: React.FC = ({ ); return ( -
+
{labelElement}
{React.cloneElement(children, { invalid, disabled, loading })} diff --git a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx index 530612ef060..0dcb9394e6e 100644 --- a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx +++ b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx @@ -39,7 +39,13 @@ export function ValuePicker({ const [isPicking, setIsPicking] = useState(false); const buttonEl = ( - ); diff --git a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx new file mode 100644 index 00000000000..36ccdd181c2 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx @@ -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 = { + 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) + .addBooleanSwitch({ + name: 'c', + path: 'c', + } as FieldConfigEditorConfig) + .addTextInput({ + name: 'b', + path: 'b', + } as FieldConfigEditorConfig); + }, + }); + + const { queryAllByLabelText } = render( + + ); + + 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) + .addBooleanSwitch({ + name: 'c', + path: 'c', + } as FieldConfigEditorConfig) + .addTextInput({ + name: 'b', + path: 'b', + } as FieldConfigEditorConfig); + }, + }); + + const { queryAllByLabelText } = render( + + ); + + const editors = queryAllByLabelText(selectors.components.PanelEditor.FieldOptions.propertyEditor('Custom')); + expect(editors).toHaveLength(2); + }); +}); diff --git a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx new file mode 100644 index 00000000000..946f7f04b33 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx @@ -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 = ({ 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 = ( + + ); + + // 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 ( + + onDefaultValueChange(item.path, v, item.isCustom)} + context={{ + data, + getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope), + }} + /> + + ); + }, + [config] + ); + + const groupedConfigs = groupBy(plugin.fieldConfigRegistry.list(), i => i.category && i.category[0]); + + return ( +
+ {Object.keys(groupedConfigs).map((k, i) => { + const groupItemsCounter = countGroupItems(groupedConfigs[k], config); + + return ( + { + return ( + <> + {k} {!isExpanded && groupItemsCounter && } + + ); + }} + id={`${k}/${i}`} + key={`${k}/${i}`} + > + {groupedConfigs[k].map(c => { + return renderEditor(c, groupedConfigs[k].length); + })} + + ); + })} +
+ ); +}; + +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; +}; diff --git a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx b/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx deleted file mode 100644 index d96bdc54633..00000000000 --- a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx +++ /dev/null @@ -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 => { - 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) => { - 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 ( -
- {config.overrides.map((o, i) => { - // TODO: apply matcher to retrieve fields - return ( - onOverrideChange(i, value)} - onRemove={() => onOverrideRemove(i)} - registry={fieldConfigRegistry} - /> - ); - })} -
- ); - }; - - const renderAddOverride = () => { - return ( - - >(i => ({ label: i.name, value: i.id, description: i.description }))} - onChange={value => onOverrideAdd(value)} - isFullWidth={false} - /> - - ); - }; - - return ( -
- {config.overrides.length === 0 && ( - - Field override rules give you a fine grained control over how your data is displayed. - - )} - - {renderOverrides()} - {renderAddOverride()} -
- ); -}; - -export const DefaultFieldConfigEditor: React.FC = ({ 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 = ( - - ); - - // 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 ( - - onDefaultValueChange(item.path, v, item.isCustom)} - context={{ - data, - getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope), - }} - /> - - ); - }, - [config] - ); - - const groupedConfigs = groupBy(plugin.fieldConfigRegistry.list(), i => i.category && i.category[0]); - - return ( -
- {Object.keys(groupedConfigs).map((k, i) => { - const groupItemsCounter = countGroupItems(groupedConfigs[k], config); - - return ( - { - return ( - <> - {k} {!isExpanded && groupItemsCounter && } - - ); - }} - id={`${k}/${i}`} - key={`${k}/${i}`} - > - {groupedConfigs[k].map(c => { - return renderEditor(c, groupedConfigs[k].length); - })} - - ); - })} -
- ); -}; - -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; -}; diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx index ff7d0a31ab3..c97e0ff99ef 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneContent.tsx @@ -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'; diff --git a/public/app/features/dashboard/components/PanelEditor/OverrideEditor.test.tsx b/public/app/features/dashboard/components/PanelEditor/OverrideEditor.test.tsx new file mode 100644 index 00000000000..3df1e34d2a2 --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/OverrideEditor.test.tsx @@ -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( + {}} + 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( + {}} + onRemove={() => {}} + /> + ); + + fireEvent.click(getByLabelText(selectors.components.ValuePicker.button)); + const selectOptions = queryAllByLabelText(selectors.components.Select.option); + + expect(selectOptions).toHaveLength(2); + }); +}); diff --git a/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx b/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx index 2575cb5fe4b..f80e1e7b1df 100644 --- a/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx @@ -97,17 +97,20 @@ export const OverrideEditor: React.FC = ({ [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 = ({ if (!item) { return
Unknown property: {p.id}
; } + const isCollapsible = Array.isArray(p.value) || COLLECTION_STANDARD_PROPERTIES.includes(p.id as FieldConfigProperty); diff --git a/public/app/features/dashboard/components/PanelEditor/OverrideFieldConfigEditor.tsx b/public/app/features/dashboard/components/PanelEditor/OverrideFieldConfigEditor.tsx new file mode 100644 index 00000000000..484760366ad --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/OverrideFieldConfigEditor.tsx @@ -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 => { + 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) => { + 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 ( +
+ {config.overrides.map((o, i) => { + // TODO: apply matcher to retrieve fields + return ( + onOverrideChange(i, value)} + onRemove={() => onOverrideRemove(i)} + registry={fieldConfigRegistry} + /> + ); + })} +
+ ); + }; + + const renderAddOverride = () => { + return ( + + >(i => ({ label: i.name, value: i.id, description: i.description }))} + onChange={value => onOverrideAdd(value)} + isFullWidth={false} + /> + + ); + }; + + return ( +
+ {config.overrides.length === 0 && ( + + Field override rules give you a fine grained control over how your data is displayed. + + )} + + {renderOverrides()} + {renderAddOverride()} +
+ ); +}; diff --git a/public/app/features/dashboard/components/PanelEditor/types.ts b/public/app/features/dashboard/components/PanelEditor/types.ts index 63d181fdc6b..33c8a6fae3c 100644 --- a/public/app/features/dashboard/components/PanelEditor/types.ts +++ b/public/app/features/dashboard/components/PanelEditor/types.ts @@ -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[]; +} diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 5164f77532d..fd3257fddaf 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -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', diff --git a/public/test/helpers/fieldConfig.ts b/public/test/helpers/fieldConfig.ts new file mode 100644 index 00000000000..f6dc74e8f5b --- /dev/null +++ b/public/test/helpers/fieldConfig.ts @@ -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]; +}