diff --git a/e2e/suite1/specs/panelEdit_base.spec.ts b/e2e/suite1/specs/panelEdit_base.spec.ts index c4d3b5db089..aad33c1f337 100644 --- a/e2e/suite1/specs/panelEdit_base.spec.ts +++ b/e2e/suite1/specs/panelEdit_base.spec.ts @@ -54,26 +54,18 @@ e2e.scenario({ // Panel sidebar is rendered open by default e2e.components.PanelEditor.OptionsPane.content().should('be.visible'); - // Can toggle on/off sidebar - e2e.components.PanelEditor.OptionsPane.close().should('be.visible'); - e2e.components.PanelEditor.OptionsPane.open().should('not.exist'); - // close options pane - e2e.components.PanelEditor.OptionsPane.close().click(); - e2e.components.PanelEditor.OptionsPane.open().should('be.visible'); - e2e.components.PanelEditor.OptionsPane.close().should('not.exist'); + e2e.components.PanelEditor.toggleVizOptions().click(); e2e.components.PanelEditor.OptionsPane.content().should('not.exist'); + e2e().wait(100); + // open options pane - e2e.components.PanelEditor.OptionsPane.open().click(); - e2e.components.PanelEditor.OptionsPane.close().should('be.visible'); - e2e.components.PanelEditor.OptionsPane.open().should('not.exist'); + e2e.components.PanelEditor.toggleVizOptions().should('be.visible').click(); e2e.components.PanelEditor.OptionsPane.content().should('be.visible'); - // Can change visualisation type - e2e.components.OptionsGroup.toggle('Panel type').should('be.visible').click(); - // Check that Graph is chosen + e2e.components.PanelEditor.toggleVizPicker().click(); e2e.components.PluginVisualization.item('Graph').should('be.visible'); e2e.components.PluginVisualization.current().within((div: JQuery) => { expect(div.text()).equals('Graph'); @@ -94,21 +86,14 @@ e2e.scenario({ expect(div.text()).equals('Table'); }); + // close viz picker + e2e.components.PanelEditor.toggleVizPicker().click(); + // Data pane should be rendered e2e.components.PanelEditor.DataPane.content().should('be.visible'); // Field & Overrides tabs (need to switch to React based vis, i.e. Table) - e2e.components.PanelEditor.OptionsPane.tab('Field').should('be.visible'); - e2e.components.PanelEditor.OptionsPane.tab('Overrides').should('be.visible'); - e2e.components.PanelEditor.OptionsPane.tab('Field').click(); - - e2e.components.FieldConfigEditor.content().should('be.visible'); - e2e.components.OverridesConfigEditor.content().should('not.exist'); - - e2e.components.PanelEditor.OptionsPane.tab('Field').should('be.visible'); - e2e.components.PanelEditor.OptionsPane.tab('Overrides').should('be.visible').click(); - - e2e.components.OverridesConfigEditor.content().should('be.visible'); - e2e.components.FieldConfigEditor.content().should('not.exist'); + e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Show header').should('be.visible'); + e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Column width').should('be.visible'); }, }); diff --git a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts index e0a09bea22e..74db315bd1e 100644 --- a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts +++ b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts @@ -5,7 +5,7 @@ import { DataFrame, InterpolateFunction, VariableSuggestionsScope, VariableSugge import { EventBus } from '../events'; export interface StandardEditorContext { - data?: DataFrame[]; // All results + data: DataFrame[]; // All results replaceVariables?: InterpolateFunction; eventBus?: EventBus; getSuggestions?: (scope?: VariableSuggestionsScope) => VariableSuggestion[]; diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index ae99ac312b2..7fb339d7254 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -58,7 +58,6 @@ export interface FieldConfigSource { export interface FieldOverrideContext extends StandardEditorContext { field?: Field; dataFrameIndex?: number; // The index for the selected field frame - data: DataFrame[]; // All results } export interface FieldConfigEditorProps extends Omit, 'item'> { diff --git a/packages/grafana-data/src/types/options.ts b/packages/grafana-data/src/types/options.ts index a3ea2bbc96d..d23ceb35c5e 100644 --- a/packages/grafana-data/src/types/options.ts +++ b/packages/grafana-data/src/types/options.ts @@ -41,7 +41,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?: Array; + category?: string[]; /** * 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 e3b31fd9a30..69552ada7da 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -61,18 +61,15 @@ export const Components = { }, OptionsPane: { content: 'Panel editor option pane content', - close: 'Page toolbar button Close options pane', - open: 'Page toolbar button Open options pane', select: 'Panel editor option pane select', - tab: (title: string) => `Panel editor option pane tab ${title}`, + fieldLabel: (type: string) => `${type} field property editor`, }, // not sure about the naming *DataPane* DataPane: { content: 'Panel editor data pane content', }, - FieldOptions: { - propertyEditor: (type: string) => `${type} field property editor`, - }, + toggleVizPicker: 'toggle-viz-picker', + toggleVizOptions: 'toggle-viz-options', }, PanelInspector: { Data: { diff --git a/packages/grafana-e2e/src/flows/configurePanel.ts b/packages/grafana-e2e/src/flows/configurePanel.ts index 07cedf08eb6..3f9304a9334 100644 --- a/packages/grafana-e2e/src/flows/configurePanel.ts +++ b/packages/grafana-e2e/src/flows/configurePanel.ts @@ -219,7 +219,7 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC const closeOptions = (): any => isOptionsOpen().then((isOpen: any) => { if (isOpen) { - e2e.components.PanelEditor.OptionsPane.close().click(); + e2e.components.PanelEditor.toggleVizOptions().click(); } }); @@ -271,7 +271,7 @@ const isOptionsOpen = (): any => const openOptions = (): any => isOptionsOpen().then((isOpen: any) => { if (!isOpen) { - e2e.components.PanelEditor.OptionsPane.open().click(); + e2e.components.PanelEditor.toggleVizOptions().click(); } }); diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index d0e27fe7626..ad43c2320ea 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -208,6 +208,11 @@ export function getPropertiesForVariant(theme: GrafanaTheme, variant: ButtonVari outline: none; text-decoration: underline; } + + &:hover { + color: ${theme.colors.linkExternal}; + text-decoration: underline; + } `, }; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx index b6818c2716c..5303b403694 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx @@ -8,7 +8,7 @@ interface DataLinkEditorModalContentProps { link: DataLink; index: number; data: DataFrame[]; - suggestions: VariableSuggestion[]; + getSuggestions: () => VariableSuggestion[]; onSave: (index: number, ink: DataLink) => void; onCancel: (index: number) => void; } @@ -16,7 +16,7 @@ interface DataLinkEditorModalContentProps { export const DataLinkEditorModalContent: FC = ({ link, index, - suggestions, + getSuggestions, onSave, onCancel, }) => { @@ -27,7 +27,7 @@ export const DataLinkEditorModalContent: FC = ( value={dirtyLink} index={index} isLast={false} - suggestions={suggestions} + suggestions={getSuggestions()} onChange={(index, link) => { setDirtyLink(link); }} diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx index bad8b7a5d61..c44d3bee268 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx @@ -11,11 +11,16 @@ import { DataLinkEditorModalContent } from './DataLinkEditorModalContent'; interface DataLinksInlineEditorProps { links?: DataLink[]; onChange: (links: DataLink[]) => void; - suggestions: VariableSuggestion[]; + getSuggestions: () => VariableSuggestion[]; data: DataFrame[]; } -export const DataLinksInlineEditor: React.FC = ({ links, onChange, suggestions, data }) => { +export const DataLinksInlineEditor: React.FC = ({ + links, + onChange, + getSuggestions, + data, +}) => { const theme = useTheme(); const [editIndex, setEditIndex] = useState(null); const [isNew, setIsNew] = useState(false); @@ -74,7 +79,6 @@ export const DataLinksInlineEditor: React.FC = ({ li onEdit={() => setEditIndex(i)} onRemove={() => onDataLinkRemove(i)} data={data} - suggestions={suggestions} /> ); })} @@ -95,7 +99,7 @@ export const DataLinksInlineEditor: React.FC = ({ li data={data} onSave={onDataLinkChange} onCancel={onDataLinkCancel} - suggestions={suggestions} + getSuggestions={getSuggestions} /> )} diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx index f8df089bedf..850f00b2b0c 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx @@ -17,7 +17,6 @@ function setupTestContext(options: Partial) { onChange: jest.fn(), onEdit: jest.fn(), onRemove: jest.fn(), - suggestions: [], }; const props = { ...defaults, ...options }; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx index f3fdb13c232..2929e2c2f26 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { css, cx } from 'emotion'; -import { DataFrame, DataLink, GrafanaTheme, VariableSuggestion } from '@grafana/data'; +import { DataFrame, DataLink, GrafanaTheme } from '@grafana/data'; import { stylesFactory, useTheme } from '../../../themes'; import { HorizontalGroup, VerticalGroup } from '../../Layout/Layout'; import { IconButton } from '../../IconButton/IconButton'; @@ -12,7 +12,6 @@ export interface DataLinksListItemProps { onChange: (index: number, link: DataLink) => void; onEdit: () => void; onRemove: () => void; - suggestions: VariableSuggestion[]; isEditing?: boolean; } diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx index 6fefdd70f97..7ae77bd743b 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx @@ -88,7 +88,6 @@ const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme, severity: AlertVari padding: ${theme.spacing.md}; border-radius: ${theme.border.radius.md}; position: relative; - box-shadow: 0 0 30px 10px rgba(0, 0, 0, ${theme.isLight ? 0.05 : 0.2}); z-index: 0; &:before { diff --git a/packages/grafana-ui/src/components/OptionsUI/links.tsx b/packages/grafana-ui/src/components/OptionsUI/links.tsx index 768f14ff556..844b9e75534 100644 --- a/packages/grafana-ui/src/components/OptionsUI/links.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/links.tsx @@ -17,7 +17,7 @@ export const DataLinksValueEditor: React.FC (context.getSuggestions ? context.getSuggestions(VariableSuggestionsScope.Values) : [])} /> ); }; diff --git a/packages/grafana-ui/src/components/OptionsUI/slider.tsx b/packages/grafana-ui/src/components/OptionsUI/slider.tsx index bc0f741e049..294f01c3dc2 100644 --- a/packages/grafana-ui/src/components/OptionsUI/slider.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/slider.tsx @@ -8,7 +8,6 @@ export const SliderValueEditor: React.FC { const { settings } = item; - const initialValue = typeof value === 'number' ? value : typeof value === 'string' ? +value : 0; return ( diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx index 827de262c4b..0b9e62de61d 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx @@ -135,7 +135,7 @@ const getStyles = (theme: GrafanaTheme) => { background: ${theme.colors.dashboardBg}; justify-content: flex-end; flex-wrap: wrap; - padding: 0 ${spacing.md} ${spacing.sm} ${spacing.md}; + padding: 0 ${spacing.sm} ${spacing.sm} ${spacing.md}; `, toolbarLeft: css` display: flex; diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index 5bf8794a30e..891314b344f 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -221,7 +221,7 @@ export const getStandardFieldConfigs = () => { category, }; - return [unit, min, max, decimals, displayName, noValue, color, thresholds, mappings, links]; + return [unit, min, max, decimals, displayName, color, noValue, thresholds, mappings, links]; }; /** diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 137242a4437..32b2b583c8e 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -268,16 +268,18 @@ func getPanelSort(id string) int { sort = 6 case "singlestat": sort = 7 - case "text": + case "piechart": sort = 8 - case "heatmap": + case "text": sort = 9 - case "alertlist": + case "heatmap": sort = 10 - case "dashlist": + case "alertlist": sort = 11 - case "news": + case "dashlist": sort = 12 + case "news": + sort = 13 } return sort } diff --git a/public/app/core/components/FilterInput/FilterInput.tsx b/public/app/core/components/FilterInput/FilterInput.tsx index 2106a9aebdf..ebaf7640daf 100644 --- a/public/app/core/components/FilterInput/FilterInput.tsx +++ b/public/app/core/components/FilterInput/FilterInput.tsx @@ -1,24 +1,33 @@ import React, { FC } from 'react'; import { escapeStringForRegex, unEscapeStringFromRegex } from '@grafana/data'; -import { Input, Icon } from '@grafana/ui'; +import { Input, Icon, Button } from '@grafana/ui'; export interface Props { value: string | undefined; placeholder?: string; - labelClassName?: string; - inputClassName?: string; + width?: number; onChange: (value: string) => void; + autoFocus?: boolean; } -export const FilterInput: FC = (props) => ( - } - width={40} - type="text" - value={props.value ? unEscapeStringFromRegex(props.value) : ''} - onChange={(event) => props.onChange(escapeStringForRegex(event.currentTarget.value))} - placeholder={props.placeholder ?? ''} - /> -); +export const FilterInput: FC = ({ value, placeholder, width, onChange, autoFocus }) => { + const suffix = + value !== '' ? ( + + ) : null; + + return ( + } + suffix={suffix} + width={width ?? 40} + type="text" + value={value ? unEscapeStringFromRegex(value) : ''} + onChange={(event) => onChange(escapeStringForRegex(event.currentTarget.value))} + placeholder={placeholder} + /> + ); +}; diff --git a/public/app/core/components/OrgActionBar/OrgActionBar.tsx b/public/app/core/components/OrgActionBar/OrgActionBar.tsx index f4afea627cc..ad4155c6e9a 100644 --- a/public/app/core/components/OrgActionBar/OrgActionBar.tsx +++ b/public/app/core/components/OrgActionBar/OrgActionBar.tsx @@ -21,13 +21,7 @@ export default class OrgActionBar extends PureComponent { return (
- +
{linkButton.title} diff --git a/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap index 98be19581a9..447b95e2cb4 100644 --- a/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap +++ b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap @@ -8,8 +8,6 @@ exports[`Render should render component 1`] = ` className="gf-form gf-form--grow" > {
- +
diff --git a/public/app/features/api-keys/ApiKeysActionBar.tsx b/public/app/features/api-keys/ApiKeysActionBar.tsx index 706470c75f6..54c11459f0c 100644 --- a/public/app/features/api-keys/ApiKeysActionBar.tsx +++ b/public/app/features/api-keys/ApiKeysActionBar.tsx @@ -13,13 +13,7 @@ export const ApiKeysActionBar: FC = ({ searchQuery, disabled, onAddClick, return (
- +
diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index b8b35812e4f..c6cb5df8c09 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -6,7 +6,7 @@ import tinycolor from 'tinycolor2'; import { locationService } from '@grafana/runtime'; import { Icon, IconButton, styleMixins, useStyles } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; -import { DateTimeInput, GrafanaTheme } from '@grafana/data'; +import { GrafanaTheme } from '@grafana/data'; import config from 'app/core/config'; import store from 'app/core/store'; @@ -140,9 +140,9 @@ export const AddPanelWidgetUnconnected: React.FC = ({ panel, dashboard }) {addPanelView ? ( dashboard.formatDate(dateString, 'L')} onClickCard={(panel) => onAddLibraryPanel(panel)} showSecondaryActions={false} + searchString={''} /> ) : (
diff --git a/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx b/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx index 92e429481ee..d1a5dd57efe 100644 --- a/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx @@ -114,6 +114,7 @@ export class AngularPanelOptionsUnconnected extends PureComponent { }; this.angularOptions = loader.load(this.element, scopeProps, template); + this.angularOptions.digest(); } render() { diff --git a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx b/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx deleted file mode 100644 index 04c58eccb06..00000000000 --- a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.test.tsx +++ /dev/null @@ -1,263 +0,0 @@ -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); - }); - - 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 deleted file mode 100644 index bce09875851..00000000000 --- a/public/app/features/dashboard/components/PanelEditor/DefaultFieldConfigEditor.tsx +++ /dev/null @@ -1,131 +0,0 @@ -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, data)) { - 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 GENERAL_OPTIONS_CATEGORY = `${plugin.meta.name} options`; - - const groupedConfigs = groupBy(plugin.fieldConfigRegistry.list(), (i) => { - if (!i.category) { - return GENERAL_OPTIONS_CATEGORY; - } - return i.category[0] ? i.category[0] : GENERAL_OPTIONS_CATEGORY; - }); - - return ( -
- {Object.keys(groupedConfigs).map((groupName, i) => { - const group = groupedConfigs[groupName]; - const groupItemsCounter = countGroupItems(group, config); - - if (!shouldRenderGroup(group)) { - return undefined; - } - - return ( - { - return ( - <> - {groupName} {!isExpanded && groupItemsCounter && } - - ); - }} - id={`${groupName}/${i}`} - key={`${groupName}/${i}`} - > - {group.map((c) => { - return renderEditor(c, group.length); - })} - - ); - })} -
- ); -}; - -function 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; -} - -function shouldRenderGroup(group: FieldConfigPropertyItem[]) { - const hiddenPropertiesCount = group.filter((i) => i.hideFromDefaults).length; - return group.length - hiddenPropertiesCount > 0; -} diff --git a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx index bf535368039..4a7a341a5c1 100644 --- a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx @@ -1,8 +1,14 @@ -import { DynamicConfigValue, FieldConfigOptionsRegistry, FieldOverrideContext, GrafanaTheme } from '@grafana/data'; +import { + DynamicConfigValue, + FieldConfigOptionsRegistry, + FieldConfigProperty, + FieldOverrideContext, + GrafanaTheme, +} from '@grafana/data'; import React from 'react'; import { Counter, Field, HorizontalGroup, IconButton, Label, stylesFactory, useTheme } from '@grafana/ui'; import { css, cx } from 'emotion'; -import { OptionsGroup } from './OptionsGroup'; +import { OptionsPaneCategory } from './OptionsPaneCategory'; interface DynamicConfigValueEditorProps { property: DynamicConfigValue; @@ -10,7 +16,6 @@ interface DynamicConfigValueEditorProps { onChange: (value: DynamicConfigValue) => void; context: FieldOverrideContext; onRemove: () => void; - isCollapsible?: boolean; isSystemOverride?: boolean; } @@ -20,7 +25,6 @@ export const DynamicConfigValueEditor: React.FC = registry, onChange, onRemove, - isCollapsible, isSystemOverride, }) => { const theme = useTheme(); @@ -30,15 +34,20 @@ export const DynamicConfigValueEditor: React.FC = if (!item) { return null; } + + const isCollapsible = + Array.isArray(property.value) || + property.id === FieldConfigProperty.Thresholds || + property.id === FieldConfigProperty.Links || + property.id === FieldConfigProperty.Mappings; + + const labelCategory = item.category?.filter((c) => c !== item.name); let editor; // eslint-disable-next-line react/display-name const renderLabel = (includeDescription = true, includeCounter = false) => (isExpanded = false) => ( -