NewPanelEdit: Move field defaults to own tab, and merge general and panel options to first options tab (#23350)

* use dropdown

* keep padding the same

* keep padding the same

* Refactoring and moving to components

* Updated

* Alt names

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Ryan McKinley 2020-04-05 05:09:43 -07:00 committed by GitHub
parent 3fae28be52
commit 3ec88a013f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 281 additions and 202 deletions

View File

@ -1,105 +0,0 @@
import React, { FC, useMemo } from 'react';
import { PanelModel } from '../../state';
import { SelectableValue } from '@grafana/data';
import { Forms, Select, DataLinksInlineEditor, Input } from '@grafana/ui';
import { OptionsGroup } from './OptionsGroup';
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { getVariables } from '../../../variables/state/selectors';
export const GeneralPanelOptions: FC<{
panel: PanelModel;
onPanelConfigChange: (configKey: string, value: any) => void;
}> = ({ panel, onPanelConfigChange }) => {
const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []);
const variableOptions = getVariableOptions();
const directionOptions = [
{ label: 'Horizontal', value: 'h' },
{ label: 'Vertical', value: 'v' },
];
const maxPerRowOptions = [2, 3, 4, 6, 8, 12].map(value => ({ label: value.toString(), value }));
return (
<div>
<OptionsGroup title="Panel settings">
<Forms.Field label="Panel title">
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
</Forms.Field>
<Forms.Field label="Description" description="Panel description supports markdown and links.">
<Forms.TextArea
defaultValue={panel.description}
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
/>
</Forms.Field>
<Forms.Field label="Transparent" description="Display panel without background.">
<Forms.Switch
value={panel.transparent}
onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)}
/>
</Forms.Field>
</OptionsGroup>
<OptionsGroup title="Panel links">
<DataLinksInlineEditor
links={panel.links}
onChange={links => onPanelConfigChange('links', links)}
suggestions={linkVariablesSuggestions}
data={[]}
/>
</OptionsGroup>
<OptionsGroup title="Panel repeats">
<Forms.Field
label="Repeat by variable"
description="Repeat this panel for each value in the selected variable.
This is not visible while in edit mode. You need to go back to dashboard and then update the variable or
reload the dashboard."
>
<Select
value={panel.repeat}
onChange={value => onPanelConfigChange('repeat', value.value)}
options={variableOptions}
/>
</Forms.Field>
{panel.repeat && (
<Forms.Field label="Repeat direction">
<Forms.RadioButtonGroup
options={directionOptions}
value={panel.repeatDirection || 'h'}
onChange={value => onPanelConfigChange('repeatDirection', value)}
/>
</Forms.Field>
)}
{panel.repeat && panel.repeatDirection === 'h' && (
<Forms.Field label="Max per row">
<Select
options={maxPerRowOptions}
value={panel.maxPerRow}
onChange={value => onPanelConfigChange('maxPerRow', value.value)}
/>
</Forms.Field>
)}
</OptionsGroup>
</div>
);
};
function getVariableOptions(): Array<SelectableValue<string>> {
const options = getVariables().map((item: any) => {
return { label: item.name, value: item.name };
});
if (options.length === 0) {
options.unshift({
label: 'No template variables found',
value: null,
});
}
options.unshift({
label: 'Disable repeating',
value: null,
});
return options;
}

View File

@ -5,10 +5,11 @@ import { useTheme, Icon, stylesFactory } from '@grafana/ui';
interface Props {
title: string;
defaultToClosed?: boolean;
}
export const OptionsGroup: FC<Props> = ({ title, children }) => {
const [isExpanded, toggleExpand] = useState(true);
export const OptionsGroup: FC<Props> = ({ title, children, defaultToClosed }) => {
const [isExpanded, toggleExpand] = useState(defaultToClosed ? false : true);
const theme = useTheme();
const styles = getStyles(theme);

View File

@ -1,6 +1,6 @@
import React, { useCallback, useState, CSSProperties } from 'react';
import Transition from 'react-transition-group/Transition';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
import { DashboardModel, PanelModel } from '../../state';
import {
CustomScrollbar,
@ -8,22 +8,22 @@ import {
Tab,
TabContent,
TabsBar,
Select,
useTheme,
Container,
Icon,
Input,
} from '@grafana/ui';
import { DefaultFieldConfigEditor, OverrideFieldConfigEditor } from './FieldConfigEditor';
import { AngularPanelOptions } from './AngularPanelOptions';
import { css } from 'emotion';
import { GeneralPanelOptions } from './GeneralPanelOptions';
import { PanelOptionsEditor } from './PanelOptionsEditor';
import { PanelOptionsTab } from './PanelOptionsTab';
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
export const OptionsPaneContent: React.FC<{
plugin?: PanelPlugin;
plugin: PanelPlugin;
panel: PanelModel;
data: PanelData;
width: number;
dashboard: DashboardModel;
onClose: () => void;
onFieldConfigsChange: (config: FieldConfigSource) => void;
@ -33,6 +33,7 @@ export const OptionsPaneContent: React.FC<{
plugin,
panel,
data,
width,
onFieldConfigsChange,
onPanelOptionsChanged,
onPanelConfigChange,
@ -41,7 +42,7 @@ export const OptionsPaneContent: React.FC<{
}) => {
const theme = useTheme();
const styles = getStyles(theme);
const [activeTab, setActiveTab] = useState('defaults');
const [activeTab, setActiveTab] = useState('options');
const [isSearching, setSearchMode] = useState(false);
const renderFieldOptions = useCallback(
@ -54,7 +55,6 @@ export const OptionsPaneContent: React.FC<{
return (
<Container padding="md">
{renderCustomPanelSettings(plugin)}
<DefaultFieldConfigEditor
config={fieldConfig}
plugin={plugin}
@ -88,49 +88,54 @@ export const OptionsPaneContent: React.FC<{
[data, plugin, panel, onFieldConfigsChange]
);
const renderCustomPanelSettings = useCallback(
(plugin: PanelPlugin) => {
const editors: JSX.Element[] = [];
if (plugin.editor && panel) {
editors.push(
<div className={styles.legacyOptions} key="plugin custom panel settings">
<plugin.editor
data={data}
options={panel.getOptions()}
onOptionsChange={onPanelOptionsChanged}
fieldConfig={panel.getFieldConfig()}
onFieldConfigChange={onFieldConfigsChange}
return (
<div className={styles.panelOptionsPane}>
{plugin && (
<div className={styles.wrapper}>
<TabsBar className={styles.tabsBar}>
<TabsBarContent
width={width}
isSearching={isSearching}
styles={styles}
activeTab={activeTab}
onClose={onClose}
setSearchMode={setSearchMode}
setActiveTab={setActiveTab}
/>
</div>
);
}
// When editor created declaratively
if (plugin.optionEditors && panel) {
editors.push(
<PanelOptionsEditor
key="panel options"
options={panel.getOptions()}
onChange={onPanelOptionsChanged}
plugin={plugin}
/>
);
}
if (editors.length > 0) {
return editors;
}
return (
<div className={styles.legacyOptions}>
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
</TabsBar>
<TabContent className={styles.tabContent}>
<CustomScrollbar>
{activeTab === 'options' && (
<PanelOptionsTab
panel={panel}
plugin={plugin}
dashboard={dashboard}
data={data}
onPanelConfigChange={onPanelConfigChange}
onFieldConfigsChange={onFieldConfigsChange}
onPanelOptionsChanged={onPanelOptionsChanged}
/>
)}
{activeTab === 'defaults' && renderFieldOptions(plugin)}
{activeTab === 'overrides' && renderFieldOverrideOptions(plugin)}
</CustomScrollbar>
</TabContent>
</div>
);
},
[data, plugin, panel, onFieldConfigsChange]
)}
</div>
);
};
const renderSearchInput = useCallback(() => {
export const TabsBarContent: React.FC<{
width: number;
isSearching: boolean;
activeTab: string;
styles: OptionsPaneStyles;
onClose: () => void;
setSearchMode: (mode: boolean) => void;
setActiveTab: (tab: string) => void;
}> = ({ width, isSearching, activeTab, onClose, setSearchMode, setActiveTab, styles }) => {
if (isSearching) {
const defaultStyles = {
transition: 'width 50ms ease-in-out',
width: '50%',
@ -163,56 +168,66 @@ export const OptionsPaneContent: React.FC<{
}}
</Transition>
);
}, []);
}
return (
<div className={styles.panelOptionsPane}>
{plugin && (
<div className={styles.wrapper}>
<TabsBar className={styles.tabsBar}>
{isSearching && renderSearchInput()}
{!isSearching && (
<>
<Tab label="Options" active={activeTab === 'defaults'} onChangeTab={() => setActiveTab('defaults')} />
<Tab
label="Overrides"
active={activeTab === 'overrides'}
onChangeTab={() => setActiveTab('overrides')}
/>
<Tab label="General" active={activeTab === 'panel'} onChangeTab={() => setActiveTab('panel')} />
<div className="flex-grow-1" />
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-search"
tooltip="Search all options"
classSuffix="search-options"
onClick={() => setSearchMode(true)}
/>
</div>
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-chevron-right"
tooltip="Close options pane"
classSuffix="close-options"
onClick={onClose}
/>
</div>
</>
)}
</TabsBar>
<TabContent className={styles.tabContent}>
<CustomScrollbar>
{activeTab === 'defaults' && renderFieldOptions(plugin)}
{activeTab === 'overrides' && renderFieldOverrideOptions(plugin)}
{activeTab === 'panel' && <GeneralPanelOptions panel={panel} onPanelConfigChange={onPanelConfigChange} />}
</CustomScrollbar>
</TabContent>
<>
{width < 377 ? (
<div className="flex-grow-1">
<Select
options={tabSelections}
value={tabSelections.find(v => v.value === activeTab)}
onChange={v => {
setActiveTab(v.value);
}}
/>
</div>
) : (
<>
{tabSelections.map(item => {
return (
<Tab label={item.label} active={activeTab === item.value} onChangeTab={() => setActiveTab(item.value)} />
);
})}
<div className="flex-grow-1" />
</>
)}
</div>
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-search"
tooltip="Search all options"
classSuffix="search-options"
onClick={() => setSearchMode(true)}
/>
</div>
<div className={styles.tabsButton}>
<DashNavButton
icon="fa fa-chevron-right"
tooltip="Close options pane"
classSuffix="close-options"
onClick={onClose}
/>
</div>
</>
);
};
const tabSelections: Array<SelectableValue<string>> = [
{
label: 'Panel',
value: 'options',
},
{
label: 'Data',
value: 'defaults',
},
{
label: 'Overrides',
value: 'overrides',
},
];
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
@ -274,3 +289,5 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
`,
};
});
type OptionsPaneStyles = ReturnType<typeof getStyles>;

View File

@ -30,11 +30,6 @@ import { VariableModel } from 'app/features/templating/types';
import { getVariables } from 'app/features/variables/state/selectors';
import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems';
enum Pane {
Right,
Top,
}
interface OwnProps {
dashboard: DashboardModel;
sourcePanel: PanelModel;
@ -255,7 +250,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}
renderOptionsPane() {
const { plugin, dashboard, data, panel } = this.props;
const { plugin, dashboard, data, panel, uiState } = this.props;
if (!plugin) {
return <div />;
@ -267,6 +262,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
dashboard={dashboard}
data={data}
panel={panel}
width={uiState.rightPaneSize as number}
onClose={this.onTogglePanelOptions}
onFieldConfigsChange={this.onFieldConfigChange}
onPanelOptionsChanged={this.onPanelOptionsChanged}
@ -342,6 +338,11 @@ const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected);
enum Pane {
Right,
Top,
}
/*
* Styles
*/

View File

@ -0,0 +1,165 @@
import React, { FC, useMemo } from 'react';
import { PanelModel, DashboardModel } from '../../state';
import { SelectableValue, PanelPlugin, FieldConfigSource, PanelData } from '@grafana/data';
import { Forms, Select, DataLinksInlineEditor, Input } from '@grafana/ui';
import { OptionsGroup } from './OptionsGroup';
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { getVariables } from '../../../variables/state/selectors';
import { PanelOptionsEditor } from './PanelOptionsEditor';
import { AngularPanelOptions } from '../../panel_editor/AngularPanelOptions';
interface Props {
panel: PanelModel;
plugin: PanelPlugin;
data: PanelData;
dashboard: DashboardModel;
onPanelConfigChange: (configKey: string, value: any) => void;
onPanelOptionsChanged: (options: any) => void;
onFieldConfigsChange: (config: FieldConfigSource) => void;
}
export const PanelOptionsTab: FC<Props> = ({
panel,
plugin,
data,
dashboard,
onPanelConfigChange,
onPanelOptionsChanged,
onFieldConfigsChange,
}) => {
const elements: JSX.Element[] = [];
const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []);
const variableOptions = getVariableOptions();
const directionOptions = [
{ label: 'Horizontal', value: 'h' },
{ label: 'Vertical', value: 'v' },
];
const maxPerRowOptions = [2, 3, 4, 6, 8, 12].map(value => ({ label: value.toString(), value }));
// Fist common panel settings Title, description
elements.push(
<OptionsGroup title="Basic" key="basic settings">
<Forms.Field label="Panel title">
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
</Forms.Field>
<Forms.Field label="Description" description="Panel description supports markdown and links.">
<Forms.TextArea
defaultValue={panel.description}
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
/>
</Forms.Field>
<Forms.Field label="Transparent" description="Display panel without background.">
<Forms.Switch
value={panel.transparent}
onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)}
/>
</Forms.Field>
</OptionsGroup>
);
// Old legacy react editor
if (plugin.editor && panel && !plugin.optionEditors) {
elements.push(
<OptionsGroup title="Display" key="legacy react editor">
<plugin.editor
data={data}
options={panel.getOptions()}
onOptionsChange={onPanelOptionsChanged}
fieldConfig={panel.getFieldConfig()}
onFieldConfigChange={onFieldConfigsChange}
/>
</OptionsGroup>
);
}
if (plugin.optionEditors && panel) {
elements.push(
<OptionsGroup title="Display" key="panel plugin options">
<PanelOptionsEditor
key="panel options"
options={panel.getOptions()}
onChange={onPanelOptionsChanged}
plugin={plugin}
/>
</OptionsGroup>
);
}
if (plugin.angularPanelCtrl) {
elements.push(
<OptionsGroup title="Display" key="angular plugin editor">
<AngularPanelOptions panel={panel} dashboard={dashboard} plugin={plugin} />
</OptionsGroup>
);
}
elements.push(
<>
<OptionsGroup title="Panel links" key="panel links" defaultToClosed={true}>
<DataLinksInlineEditor
links={panel.links}
onChange={links => onPanelConfigChange('links', links)}
suggestions={linkVariablesSuggestions}
data={[]}
/>
</OptionsGroup>
<OptionsGroup title="Panel repeats" key="panel repeats" defaultToClosed={true}>
<Forms.Field
label="Repeat by variable"
description="Repeat this panel for each value in the selected variable.
This is not visible while in edit mode. You need to go back to dashboard and then update the variable or
reload the dashboard."
>
<Select
value={panel.repeat}
onChange={value => onPanelConfigChange('repeat', value.value)}
options={variableOptions}
/>
</Forms.Field>
{panel.repeat && (
<Forms.Field label="Repeat direction">
<Forms.RadioButtonGroup
options={directionOptions}
value={panel.repeatDirection || 'h'}
onChange={value => onPanelConfigChange('repeatDirection', value)}
/>
</Forms.Field>
)}
{panel.repeat && panel.repeatDirection === 'h' && (
<Forms.Field label="Max per row">
<Select
options={maxPerRowOptions}
value={panel.maxPerRow}
onChange={value => onPanelConfigChange('maxPerRow', value.value)}
/>
</Forms.Field>
)}
</OptionsGroup>
</>
);
return <>{elements}</>;
};
function getVariableOptions(): Array<SelectableValue<string>> {
const options = getVariables().map((item: any) => {
return { label: item.name, value: item.name };
});
if (options.length === 0) {
options.unshift({
label: 'No template variables found',
value: null,
});
}
options.unshift({
label: 'Disable repeating',
value: null,
});
return options;
}