diff --git a/packages/grafana-data/src/panel/registryFactories.ts b/packages/grafana-data/src/panel/registryFactories.ts index 694d6010c47..7583df57e82 100644 --- a/packages/grafana-data/src/panel/registryFactories.ts +++ b/packages/grafana-data/src/panel/registryFactories.ts @@ -1,6 +1,6 @@ import { FieldConfigOptionsRegistry } from '../field/FieldConfigOptionsRegistry'; import { standardFieldConfigEditorRegistry } from '../field/standardFieldConfigEditorRegistry'; -import { FieldConfigProperty } from '../types/fieldOverrides'; +import { FieldConfigProperty, FieldConfigPropertyItem } from '../types/fieldOverrides'; import { FieldConfigEditorBuilder } from '../utils/OptionsUIBuilders'; import { SetFieldConfigOptionsArgs } from './PanelPlugin'; @@ -16,6 +16,8 @@ export function createFieldConfigRegistry( pluginName: string ): FieldConfigOptionsRegistry { const registry = new FieldConfigOptionsRegistry(); + const standardConfigs = standardFieldConfigEditorRegistry.list(); + const standardOptionsExtensions: Record = {}; // Add custom options if (config.useCustomConfig) { @@ -28,11 +30,18 @@ export function createFieldConfigRegistry( // problem is id (registry index) is used as property path // so sort of need a property path on the FieldPropertyEditorItem customProp.id = 'custom.' + customProp.id; - registry.register(customProp); + + if (isStandardConfigExtension(customProp, standardConfigs)) { + const currentExtensions = standardOptionsExtensions[customProp.category![0]] ?? []; + currentExtensions.push(customProp); + standardOptionsExtensions[customProp.category![0]] = currentExtensions; + } else { + registry.register(customProp); + } } } - for (let fieldConfigProp of standardFieldConfigEditorRegistry.list()) { + for (let fieldConfigProp of standardConfigs) { if (config.disableStandardOptions) { const isDisabled = config.disableStandardOptions.indexOf(fieldConfigProp.id as FieldConfigProperty) > -1; if (isDisabled) { @@ -58,7 +67,19 @@ export function createFieldConfigRegistry( } registry.register(fieldConfigProp); + + if (fieldConfigProp.category && standardOptionsExtensions[fieldConfigProp.category[0]]) { + for (let extensionProperty of standardOptionsExtensions[fieldConfigProp.category[0]]) { + registry.register(extensionProperty); + } + } } return registry; } + +function isStandardConfigExtension(property: FieldConfigPropertyItem, standardProperties: FieldConfigPropertyItem[]) { + return Boolean( + standardProperties.find((p) => property.category && p.category && property.category[0] === p.category[0]) + ); +} diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 416d84a2d3e..66de4449503 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -165,7 +165,8 @@ export const Components = { backArrow: 'Go Back button', }, OptionsGroup: { - toggle: (title?: string) => (title ? `Options group ${title}` : 'Options group'), + group: (title?: string) => (title ? `Options group ${title}` : 'Options group'), + toggle: (title?: string) => (title ? `Options group ${title} toggle` : 'Options group toggle'), }, PluginVisualization: { item: (title: string) => `Plugin visualization item ${title}`, diff --git a/packages/grafana-ui/src/options/builder/tooltip.tsx b/packages/grafana-ui/src/options/builder/tooltip.tsx index 849a20bcdef..95ccdb2a40b 100644 --- a/packages/grafana-ui/src/options/builder/tooltip.tsx +++ b/packages/grafana-ui/src/options/builder/tooltip.tsx @@ -5,7 +5,7 @@ export function addTooltipOptions(builder: PanelOp builder.addRadio({ path: 'tooltip.mode', name: 'Tooltip mode', - category: ['Legend'], + category: ['Tooltip'], description: '', defaultValue: 'single', settings: { diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx index a9517e20371..3aeeff070bb 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx @@ -71,7 +71,11 @@ export const OptionsPaneCategory: FC = React.memo( }); return ( -
+
diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx index 4f863f2d951..710659c8aa7 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; import { FieldConfigSource, LoadingState, @@ -211,4 +211,27 @@ describe('OptionsPaneOptions', () => { screen.queryByLabelText(selectors.components.ValuePicker.button('Add field override')) ).not.toBeInTheDocument(); }); + + it('should allow standard properties extension', async () => { + const scenario = new OptionsPaneOptionsTestScenario(); + + scenario.plugin = getPanelPlugin({ + id: 'TestPanel', + }).useFieldConfig({ + useCustomConfig: (b) => { + b.addBooleanSwitch({ + name: 'CustomThresholdOption', + path: 'CustomThresholdOption', + category: ['Thresholds'], + }); + }, + }); + + scenario.render(); + + const thresholdsSection = screen.getByLabelText(selectors.components.OptionsGroup.group('Thresholds')); + expect( + within(thresholdsSection).getByLabelText(OptionsPaneSelector.fieldLabel('Thresholds CustomThresholdOption')) + ).toBeInTheDocument(); + }); });