From bedd662c0727fbfd02578f84852f5afdfeb4ba19 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 18 Jan 2021 12:53:22 +0100 Subject: [PATCH] Panel options UI: Allow collapsible categories (#30301) --- .../src/panel/registryFactories.ts | 1 - packages/grafana-data/src/types/options.ts | 2 +- .../src/selectors/components.ts | 2 +- .../DefaultFieldConfigEditor.test.tsx | 174 ++++++++++++++++++ .../PanelEditor/DefaultFieldConfigEditor.tsx | 39 ++-- .../PanelEditor/DynamicConfigValueEditor.tsx | 5 +- .../PanelEditor/PanelOptionsEditor.tsx | 9 +- 7 files changed, 214 insertions(+), 18 deletions(-) diff --git a/packages/grafana-data/src/panel/registryFactories.ts b/packages/grafana-data/src/panel/registryFactories.ts index bf25b5c5602..694d6010c47 100644 --- a/packages/grafana-data/src/panel/registryFactories.ts +++ b/packages/grafana-data/src/panel/registryFactories.ts @@ -24,7 +24,6 @@ export function createFieldConfigRegistry( for (const customProp of builder.getRegistry().list()) { customProp.isCustom = true; - customProp.category = [`${pluginName} options`].concat(customProp.category || []); // need to do something to make the custom items not conflict with standard ones // problem is id (registry index) is used as property path // so sort of need a property path on the FieldPropertyEditorItem diff --git a/packages/grafana-data/src/types/options.ts b/packages/grafana-data/src/types/options.ts index d44879afefa..13ec9a43ca0 100644 --- a/packages/grafana-data/src/types/options.ts +++ b/packages/grafana-data/src/types/options.ts @@ -39,7 +39,7 @@ export interface OptionEditorConfig { /** * Array of strings representing category of the option. First element in the array will make option render as collapsible section. */ - category?: string[]; + category?: Array; /** * Set this value if undefined diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 33d8afab5c4..7b99a4756de 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -127,7 +127,7 @@ export const Components = { backArrow: 'Go Back button', }, OptionsGroup: { - toggle: (title: string) => `Options group ${title}`, + toggle: (title?: string) => (title ? `Options group ${title}` : 'Options group'), }, PluginVisualization: { item: (title: string) => `Plugin visualization item ${title}`, diff --git a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx index 36ccdd181c2..b3f322a3ee4 100644 --- a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx @@ -86,4 +86,178 @@ describe('DefaultFieldConfigEditor', () => { const editors = queryAllByLabelText(selectors.components.PanelEditor.FieldOptions.propertyEditor('Custom')); expect(editors).toHaveLength(2); }); + + describe('categories', () => { + it('should render uncategorized options under panel category', () => { + 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); + }, + }); + plugin.meta.name = 'Test plugin'; + + const { queryAllByLabelText } = render( + + ); + + expect( + queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${plugin.meta.name} options/0`)) + ).toHaveLength(1); + + expect(queryAllByLabelText(selectors.components.OptionsGroup.toggle(), { exact: false })).toHaveLength(1); + }); + + it('should render categorized options under custom category', () => { + const CATEGORY_NAME = 'Cat1'; + const plugin = new PanelPlugin(() => null).useFieldConfig({ + standardOptions: {}, + useCustomConfig: b => { + b.addTextInput({ + name: 'b', + path: 'b', + } as FieldConfigEditorConfig) + .addBooleanSwitch({ + name: 'a', + path: 'a', + category: [CATEGORY_NAME], + } as FieldConfigEditorConfig) + .addBooleanSwitch({ + name: 'c', + path: 'c', + category: [CATEGORY_NAME], + } as FieldConfigEditorConfig); + }, + }); + plugin.meta.name = 'Test plugin'; + + const { queryAllByLabelText } = render( + + ); + + expect( + queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${plugin.meta.name} options/0`)) + ).toHaveLength(1); + + expect(queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${CATEGORY_NAME}/1`))).toHaveLength(1); + + expect(queryAllByLabelText(selectors.components.OptionsGroup.toggle(), { exact: false })).toHaveLength(2); + }); + + it('should allow subcategories in panel category', () => { + const SUBCATEGORY_NAME = 'Sub1'; + const plugin = new PanelPlugin(() => null).useFieldConfig({ + standardOptions: {}, + useCustomConfig: b => { + b.addTextInput({ + name: 'b', + path: 'b', + category: [undefined, SUBCATEGORY_NAME], + } as FieldConfigEditorConfig) + .addBooleanSwitch({ + name: 'a', + path: 'a', + } as FieldConfigEditorConfig) + .addBooleanSwitch({ + name: 'c', + path: 'c', + } as FieldConfigEditorConfig); + }, + }); + plugin.meta.name = 'Test plugin'; + + const { queryAllByLabelText, queryAllByText } = render( + + ); + + expect( + queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${plugin.meta.name} options/0`)) + ).toHaveLength(1); + + expect(queryAllByText(SUBCATEGORY_NAME, { exact: false })).toHaveLength(1); + }); + + it('should allow subcategories in custom category', () => { + const CATEGORY_NAME = 'Cat1'; + const SUBCATEGORY_NAME = 'Sub1'; + 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', + category: [CATEGORY_NAME, SUBCATEGORY_NAME], + } as FieldConfigEditorConfig); + }, + }); + plugin.meta.name = 'Test plugin'; + + const { queryAllByLabelText, queryAllByText } = render( + + ); + + expect( + queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${plugin.meta.name} options/0`)) + ).toHaveLength(1); + expect(queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${CATEGORY_NAME}/1`))).toHaveLength(1); + + expect(queryAllByText(SUBCATEGORY_NAME, { exact: false })).toHaveLength(1); + }); + + it('should not render categories with hidden fields only', () => { + const CATEGORY_NAME = 'Cat1'; + const SUBCATEGORY_NAME = 'Sub1'; + 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', + hideFromDefaults: true, + category: [CATEGORY_NAME, SUBCATEGORY_NAME], + } as FieldConfigEditorConfig); + }, + }); + plugin.meta.name = 'Test plugin'; + + const { queryAllByLabelText, queryAllByText } = render( + + ); + + expect( + queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${plugin.meta.name} options/0`)) + ).toHaveLength(1); + + expect(queryAllByLabelText(selectors.components.OptionsGroup.toggle(`${CATEGORY_NAME}/1`))).toHaveLength(0); + + expect(queryAllByText(SUBCATEGORY_NAME, { exact: false })).toHaveLength(0); + }); + }); }); diff --git a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx index 946f7f04b33..8775198bb46 100644 --- a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx @@ -34,7 +34,7 @@ export const DefaultFieldConfigEditor: React.FC = ({ data, onChange, conf : get(defaults, item.path); let label: ReactNode | undefined = ( -