NewPanelEditor/Panels: refactor setDefault to setPanelOptions (#23404)

* Remove deprecated setDefault usages

* Add simple support for conditinal field config properties

* Use new API in NewsPanel

* Update tests

* Fix check
This commit is contained in:
Dominik Prokop 2020-04-08 19:21:26 +02:00 committed by GitHub
parent a29056966c
commit ea792edd3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 375 additions and 1373 deletions

View File

@ -60,6 +60,8 @@ export interface StringFieldConfigSettings {
placeholder?: string;
maxLength?: number;
expandTemplateVars?: boolean;
useTextarea?: boolean;
rows?: number;
}
export const stringOverrideProcessor = (

View File

@ -10,6 +10,7 @@ export interface OptionsEditorItem<TOptions, TSettings, TEditorProps, TValue> ex
editor: ComponentType<TEditorProps>;
settings?: TSettings;
defaultValue?: TValue;
showIf?: (currentConfig: TOptions) => boolean;
}
/**

View File

@ -60,6 +60,7 @@ export interface FieldConfigEditorConfig<TOptions, TSettings = any, TValue = any
settings?: TSettings;
shouldApply?: (field: Field) => boolean;
defaultValue?: TValue;
showIf?: (currentConfig: TOptions) => boolean;
}
export interface FieldConfigPropertyItem<TOptions = any, TValue = any, TSettings extends {} = any>

View File

@ -124,6 +124,7 @@ export interface PanelOptionsEditorConfig<TOptions, TSettings = any, TValue = an
description: string;
settings?: TSettings;
defaultValue?: TValue;
showIf?: (currentConfig: TOptions) => boolean;
}
export interface PanelMenuItem {

View File

@ -1,17 +1,20 @@
import React from 'react';
import { FieldConfigEditorProps, StringFieldConfigSettings } from '@grafana/data';
import { Input } from '../Input/Input';
import { TextArea } from '../Forms/TextArea/TextArea';
export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFieldConfigSettings>> = ({
value,
onChange,
item,
}) => {
const Component = item.settings?.useTextarea ? TextArea : Input;
return (
<Input
<Component
placeholder={item.settings?.placeholder}
value={value || ''}
onChange={e => onChange(e.currentTarget.value)}
rows={item.settings?.useTextarea && item.settings.rows}
onChange={(e: React.FormEvent<any>) => onChange(e.currentTarget.value)}
/>
);
};

View File

@ -143,6 +143,9 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
const renderEditor = useCallback(
(item: FieldConfigPropertyItem) => {
if (item.isCustom && item.showIf && !item.showIf(config.defaults.custom)) {
return null;
}
const defaults = config.defaults;
const value = item.isCustom
? defaults.custom

View File

@ -20,6 +20,10 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
return (
<>
{optionEditors.list().map(e => {
if (e.showIf && !e.showIf(options)) {
return null;
}
return (
<Forms.Field label={e.name} description={e.description} key={e.id}>
<e.editor value={lodashGet(options, e.path)} onChange={value => onOptionChange(e.path, value)} item={e} />

View File

@ -4,6 +4,7 @@ import {
FieldConfigProperty,
identityOverrideProcessor,
PanelProps,
standardEditorsRegistry,
standardFieldConfigEditorRegistry,
} from '@grafana/data';
import { ComponentClass } from 'react';
@ -37,33 +38,30 @@ export const mockStandardProperties = () => {
shouldApply: () => true,
};
return [unit, decimals];
const boolean = {
id: 'boolean',
path: 'boolean',
name: 'Boolean',
description: '',
// @ts-ignore
editor: () => null,
// @ts-ignore
override: () => null,
process: identityOverrideProcessor,
shouldApply: () => true,
};
return [unit, decimals, boolean];
};
standardFieldConfigEditorRegistry.setInit(() => mockStandardProperties());
standardEditorsRegistry.setInit(() => mockStandardProperties());
describe('PanelModel', () => {
describe('when creating new panel model', () => {
let model: any;
let modelJson: any;
let persistedOptionsMock;
const defaultOptionsMock = {
fieldOptions: {
thresholds: [
{
color: '#F2495C',
index: 1,
value: 50,
},
{
color: '#73BF69',
index: 0,
value: null,
},
],
},
arrayWith2Values: [{ value: 'name' }, { value: 'name2' }],
showThresholds: true,
};
beforeEach(() => {
persistedOptionsMock = {
@ -114,11 +112,16 @@ describe('PanelModel', () => {
(null as unknown) as ComponentClass<PanelProps>, // react
TablePanelCtrl // angular
);
panelPlugin.setDefaults(defaultOptionsMock);
/* panelPlugin.useStandardFieldConfig([FieldConfigOptionId.Unit, FieldConfigOptionId.Decimals], {
[FieldConfigOptionId.Unit]: 'flop',
[FieldConfigOptionId.Decimals]: 2,
}); */
panelPlugin.setPanelOptions(builder => {
builder.addBooleanSwitch({
name: 'Show thresholds',
path: 'showThresholds',
defaultValue: true,
description: '',
});
});
panelPlugin.useFieldConfig({
standardOptions: [FieldConfigProperty.Unit, FieldConfigProperty.Decimals],
standardOptionsDefaults: {
@ -200,13 +203,17 @@ describe('PanelModel', () => {
});
describe('when changing panel type', () => {
const newPanelPluginDefaults = {
showThresholdLabels: false,
};
beforeEach(() => {
const newPlugin = getPanelPlugin({ id: 'graph' });
newPlugin.setDefaults(newPanelPluginDefaults);
newPlugin.setPanelOptions(builder => {
builder.addBooleanSwitch({
name: 'Show thresholds labels',
path: 'showThresholdLabels',
defaultValue: false,
description: '',
});
});
model.changePlugin(newPlugin);
model.alert = { id: 2 };
});

View File

@ -1,195 +0,0 @@
// Libraries
import React, { PureComponent, ChangeEvent } from 'react';
// Components
import { PanelOptionsGroup, PanelOptionsGrid, FormField, FormLabel, LegacyForms } from '@grafana/ui';
const { Switch } = LegacyForms;
import { PanelEditorProps, toIntegerOrUndefined, toNumberString } from '@grafana/data';
// Types
import { AnnoOptions } from './types';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
interface State {
tag: string;
}
export class AnnoListEditor extends PureComponent<PanelEditorProps<AnnoOptions>, State> {
constructor(props: PanelEditorProps<AnnoOptions>) {
super(props);
this.state = {
tag: '',
};
}
// Display
//-----------
onToggleShowUser = () =>
this.props.onOptionsChange({ ...this.props.options, showUser: !this.props.options.showUser });
onToggleShowTime = () =>
this.props.onOptionsChange({ ...this.props.options, showTime: !this.props.options.showTime });
onToggleShowTags = () =>
this.props.onOptionsChange({ ...this.props.options, showTags: !this.props.options.showTags });
// Navigate
//-----------
onNavigateBeforeChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onOptionsChange({ ...this.props.options, navigateBefore: event.target.value });
};
onNavigateAfterChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onOptionsChange({ ...this.props.options, navigateAfter: event.target.value });
};
onToggleNavigateToPanel = () =>
this.props.onOptionsChange({ ...this.props.options, navigateToPanel: !this.props.options.navigateToPanel });
// Search
//-----------
onLimitChange = (event: ChangeEvent<HTMLInputElement>) => {
const v = toIntegerOrUndefined(event.target.value);
this.props.onOptionsChange({ ...this.props.options, limit: v });
};
onToggleOnlyFromThisDashboard = () =>
this.props.onOptionsChange({
...this.props.options,
onlyFromThisDashboard: !this.props.options.onlyFromThisDashboard,
});
onToggleOnlyInTimeRange = () =>
this.props.onOptionsChange({ ...this.props.options, onlyInTimeRange: !this.props.options.onlyInTimeRange });
// Tags
//-----------
onTagTextChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ tag: event.target.value });
};
onTagClick = (e: React.SyntheticEvent, tag: string) => {
e.stopPropagation();
const tags = this.props.options.tags.filter(item => item !== tag);
this.props.onOptionsChange({
...this.props.options,
tags,
});
};
renderTags = (tags: string[]): JSX.Element => {
if (!tags || !tags.length) {
return null;
}
return (
<>
{tags.map(tag => {
return (
<span key={tag} onClick={e => this.onTagClick(e, tag)} className="pointer">
<TagBadge label={tag} removeIcon={true} count={0} />
</span>
);
})}
</>
);
};
render() {
const { options } = this.props;
const labelWidth = 8;
return (
<PanelOptionsGrid>
<PanelOptionsGroup title="Display">
<Switch
label="Show User"
labelClass={`width-${labelWidth}`}
checked={options.showUser}
onChange={this.onToggleShowUser}
/>
<Switch
label="Show Time"
labelClass={`width-${labelWidth}`}
checked={options.showTime}
onChange={this.onToggleShowTime}
/>
<Switch
label="Show Tags"
labelClass={`width-${labelWidth}`}
checked={options.showTags}
onChange={this.onToggleShowTags}
/>
</PanelOptionsGroup>
<PanelOptionsGroup title="Navigate">
<FormField
label="Before"
labelWidth={labelWidth}
onChange={this.onNavigateBeforeChange}
value={options.navigateBefore}
/>
<FormField
label="After"
labelWidth={labelWidth}
onChange={this.onNavigateAfterChange}
value={options.navigateAfter}
/>
<Switch
label="To Panel"
labelClass={`width-${labelWidth}`}
checked={options.navigateToPanel}
onChange={this.onToggleNavigateToPanel}
/>
</PanelOptionsGroup>
<PanelOptionsGroup title="Search">
<Switch
label="Only This Dashboard"
labelClass={`width-12`}
checked={options.onlyFromThisDashboard}
onChange={this.onToggleOnlyFromThisDashboard}
/>
<Switch
label="Within Time Range"
labelClass={`width-12`}
checked={options.onlyInTimeRange}
onChange={this.onToggleOnlyInTimeRange}
/>
<div className="form-field">
<FormLabel width={6}>Tags</FormLabel>
{this.renderTags(options.tags)}
<input
type="text"
className={`gf-form-input width-${8}`}
value={this.state.tag}
onChange={this.onTagTextChange}
onKeyPress={ev => {
if (this.state.tag && ev.key === 'Enter') {
const tags = [...options.tags, this.state.tag];
this.props.onOptionsChange({
...this.props.options,
tags,
});
this.setState({ tag: '' });
ev.preventDefault();
}
}}
/>
</div>
<FormField
label="Limit"
labelWidth={6}
onChange={this.onLimitChange}
value={toNumberString(options.limit)}
type="number"
/>
</PanelOptionsGroup>
</PanelOptionsGrid>
);
}
}

View File

@ -1,16 +0,0 @@
import { AnnoListPanel } from './AnnoListPanel';
import { AnnoOptions, defaults } from './types';
import { AnnoListEditor } from './AnnoListEditor';
import { PanelModel, PanelPlugin } from '@grafana/data';
export const plugin = new PanelPlugin<AnnoOptions>(AnnoListPanel)
.setDefaults(defaults)
.setEditor(AnnoListEditor)
// TODO, we should support this directly in the plugin infrastructure
.setPanelChangeHandler((panel: PanelModel<AnnoOptions>, prevPluginId: string, prevOptions: any) => {
if (prevPluginId === 'ryantxu-annolist-panel') {
return prevOptions as AnnoOptions;
}
return panel.options;
});

View File

@ -0,0 +1,80 @@
import React from 'react';
import { PanelModel, PanelPlugin } from '@grafana/data';
import { TagsInput } from '@grafana/ui';
import { AnnoListPanel } from './AnnoListPanel';
import { AnnoOptions } from './types';
export const plugin = new PanelPlugin<AnnoOptions>(AnnoListPanel)
.setPanelOptions(builder => {
builder
.addBooleanSwitch({
path: 'showUser',
name: 'Show user',
description: '',
defaultValue: true,
})
.addBooleanSwitch({
path: 'showTime',
name: 'Show time',
description: '',
defaultValue: true,
})
.addBooleanSwitch({
path: 'showTags',
name: 'Show tags',
description: '',
defaultValue: true,
})
.addTextInput({
path: 'navigateBefore',
name: 'Before',
defaultValue: '10m',
description: '',
})
.addTextInput({
path: 'navigateAfter',
name: 'After',
defaultValue: '10m',
description: '',
})
.addBooleanSwitch({
path: 'navigateToPanel',
name: 'To panel',
description: '',
defaultValue: true,
})
.addBooleanSwitch({
path: 'onlyFromThisDashboard',
name: 'Only this dashboard',
description: '',
defaultValue: false,
})
.addBooleanSwitch({
path: 'onlyInTimeRange',
name: 'Within Time Range',
description: '',
defaultValue: false,
})
.addCustomEditor({
id: 'tags',
path: 'tags',
name: 'Tags',
description: '',
editor: props => {
return <TagsInput tags={props.value} onChange={props.onChange} />;
},
})
.addNumberInput({
path: 'limit',
name: 'Limit',
description: '',
defaultValue: 10,
});
})
// TODO, we should support this directly in the plugin infrastructure
.setPanelChangeHandler((panel: PanelModel<AnnoOptions>, prevPluginId: string, prevOptions: any) => {
if (prevPluginId === 'ryantxu-annolist-panel') {
return prevOptions as AnnoOptions;
}
return panel.options;
});

View File

@ -1,170 +0,0 @@
// Libraries
import React, { PureComponent } from 'react';
import {
PanelOptionsGrid,
FieldDisplayEditor,
PanelOptionsGroup,
FormLabel,
LegacyForms,
FieldPropertiesEditor,
ThresholdsEditor,
LegacyValueMappingsEditor,
DataLinksEditor,
} from '@grafana/ui';
const { Select, Switch } = LegacyForms;
import {
DataLink,
FieldConfig,
ReduceDataOptions,
PanelEditorProps,
ThresholdsConfig,
ValueMapping,
} from '@grafana/data';
import { BarGaugeOptions, displayModes } from './types';
import { orientationOptions } from '../gauge/types';
import {
getCalculationValueDataLinksVariableSuggestions,
getDataLinksVariableSuggestions,
} from '../../../features/panel/panellinks/link_srv';
import { NewPanelEditorContext } from '../../../features/dashboard/components/PanelEditor/PanelEditor';
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
onDisplayOptionsChanged = (fieldOptions: ReduceDataOptions) =>
this.props.onOptionsChange({
...this.props.options,
reduceOptions: fieldOptions,
});
onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value });
onToggleShowUnfilled = () => {
this.props.onOptionsChange({ ...this.props.options, showUnfilled: !this.props.options.showUnfilled });
};
onThresholdsChanged = (thresholds: ThresholdsConfig) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
thresholds,
},
});
};
onValueMappingsChanged = (mappings: ValueMapping[]) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
mappings,
},
});
};
onDataLinksChanged = (links: DataLink[]) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
links,
},
});
};
onDefaultsChange = (field: FieldConfig, event?: React.SyntheticEvent<HTMLElement>, callback?: () => void) => {
this.props.onFieldConfigChange({
...this.props.fieldConfig,
defaults: field,
});
};
render() {
const { options, fieldConfig } = this.props;
const { reduceOptions: fieldOptions } = options;
const { defaults } = fieldConfig;
const labelWidth = 6;
const suggestions = fieldOptions.values
? getDataLinksVariableSuggestions(this.props.data.series)
: getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
return (
<NewPanelEditorContext.Consumer>
{useNewEditor => {
if (useNewEditor) {
return null;
}
return (
<>
<PanelOptionsGrid>
<PanelOptionsGroup title="Display">
<FieldDisplayEditor
onChange={this.onDisplayOptionsChanged}
value={fieldOptions}
labelWidth={labelWidth}
/>
<div className="form-field">
<FormLabel width={labelWidth}>Orientation</FormLabel>
<Select
width={12}
options={orientationOptions}
defaultValue={orientationOptions[0]}
onChange={this.onOrientationChange}
value={orientationOptions.find(item => item.value === options.orientation)}
/>
</div>
<div className="form-field">
<FormLabel width={labelWidth}>Mode</FormLabel>
<Select
width={12}
options={displayModes}
defaultValue={displayModes[0]}
onChange={this.onDisplayModeChange}
value={displayModes.find(item => item.value === options.displayMode)}
/>
</div>
<>
{options.displayMode !== 'lcd' && (
<Switch
label="Unfilled"
labelClass={`width-${labelWidth}`}
checked={options.showUnfilled}
onChange={this.onToggleShowUnfilled}
/>
)}
</>
</PanelOptionsGroup>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
showTitle={true}
onChange={this.onDefaultsChange}
value={defaults}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</PanelOptionsGrid>
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}}
</NewPanelEditorContext.Consumer>
);
}
}

View File

@ -1,35 +1,29 @@
import { sharedSingleStatPanelChangedHandler } from '@grafana/ui';
import { PanelPlugin } from '@grafana/data';
import { BarGaugePanel } from './BarGaugePanel';
import { BarGaugeOptions, defaults } from './types';
import { BarGaugeOptions, displayModes } from './types';
import { addStandardDataReduceOptions } from '../stat/types';
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
import { barGaugePanelMigrationHandler } from './BarGaugeMigrations';
export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
.setDefaults(defaults)
.setEditor(BarGaugePanelEditor)
.useFieldConfig()
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
builder
.addRadio({
path: 'displayMode',
name: 'Display mode',
description: 'Controls the bar style',
settings: {
options: [
{ value: 'basic', label: 'Basic' },
{ value: 'gradient', label: 'Gradient' },
{ value: 'lcd', label: 'Retro LCD' },
],
options: displayModes,
},
defaultValue: 'lcd',
})
.addBooleanSwitch({
path: 'showUnfilled',
name: 'Show unfilled area',
description: 'When enabled renders the unfilled region as gray',
defaultValue: true,
});
})
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)

View File

@ -1,6 +1,5 @@
import { SingleStatBaseOptions, BarGaugeDisplayMode } from '@grafana/ui';
import { commonValueOptionDefaults } from '../stat/types';
import { VizOrientation, SelectableValue } from '@grafana/data';
import { SelectableValue } from '@grafana/data';
export interface BarGaugeOptions extends SingleStatBaseOptions {
displayMode: BarGaugeDisplayMode;
@ -12,10 +11,3 @@ export const displayModes: Array<SelectableValue<string>> = [
{ value: BarGaugeDisplayMode.Lcd, label: 'Retro LCD' },
{ value: BarGaugeDisplayMode.Basic, label: 'Basic' },
];
export const defaults: BarGaugeOptions = {
displayMode: BarGaugeDisplayMode.Lcd,
orientation: VizOrientation.Horizontal,
reduceOptions: commonValueOptionDefaults,
showUnfilled: true,
};

View File

@ -1,163 +0,0 @@
// Libraries
import React, { PureComponent } from 'react';
import {
PanelOptionsGrid,
FieldDisplayEditor,
LegacyForms,
PanelOptionsGroup,
FieldPropertiesEditor,
ThresholdsEditor,
LegacyValueMappingsEditor,
DataLinksEditor,
} from '@grafana/ui';
const { Switch } = LegacyForms;
import {
PanelEditorProps,
ReduceDataOptions,
ThresholdsConfig,
DataLink,
FieldConfig,
ValueMapping,
} from '@grafana/data';
import { GaugeOptions } from './types';
import {
getCalculationValueDataLinksVariableSuggestions,
getDataLinksVariableSuggestions,
} from '../../../features/panel/panellinks/link_srv';
import { NewPanelEditorContext } from '../../../features/dashboard/components/PanelEditor/PanelEditor';
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
labelWidth = 6;
onToggleThresholdLabels = () =>
this.props.onOptionsChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels });
onToggleThresholdMarkers = () =>
this.props.onOptionsChange({
...this.props.options,
showThresholdMarkers: !this.props.options.showThresholdMarkers,
});
onDisplayOptionsChanged = (
fieldOptions: ReduceDataOptions,
event?: React.SyntheticEvent<HTMLElement>,
callback?: () => void
) => {
this.props.onOptionsChange(
{
...this.props.options,
reduceOptions: fieldOptions,
},
callback
);
};
onThresholdsChanged = (thresholds: ThresholdsConfig) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
thresholds,
},
});
};
onValueMappingsChanged = (mappings: ValueMapping[]) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
mappings,
},
});
};
onDataLinksChanged = (links: DataLink[]) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
links,
},
});
};
onDefaultsChange = (field: FieldConfig) => {
this.props.onFieldConfigChange({
...this.props.fieldConfig,
defaults: field,
});
};
render() {
const { options, fieldConfig } = this.props;
const { showThresholdLabels, showThresholdMarkers, reduceOptions: valueOptions } = options;
const { defaults } = fieldConfig;
const suggestions = valueOptions.values
? getDataLinksVariableSuggestions(this.props.data.series)
: getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
return (
<NewPanelEditorContext.Consumer>
{useNewEditor => {
if (useNewEditor) {
return null;
}
return (
<>
<PanelOptionsGrid>
<PanelOptionsGroup title="Display">
<FieldDisplayEditor
onChange={this.onDisplayOptionsChanged}
value={valueOptions}
labelWidth={this.labelWidth}
/>
<Switch
label="Labels"
labelClass={`width-${this.labelWidth}`}
checked={showThresholdLabels}
onChange={this.onToggleThresholdLabels}
/>
<Switch
label="Markers"
labelClass={`width-${this.labelWidth}`}
checked={showThresholdMarkers}
onChange={this.onToggleThresholdMarkers}
/>
</PanelOptionsGroup>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
showTitle={true}
onChange={this.onDefaultsChange}
value={defaults}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</PanelOptionsGrid>
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}}
</NewPanelEditorContext.Consumer>
);
}
}

View File

@ -1,13 +1,10 @@
import { PanelPlugin } from '@grafana/data';
import { GaugePanelEditor } from './GaugePanelEditor';
import { GaugePanel } from './GaugePanel';
import { GaugeOptions, defaults } from './types';
import { GaugeOptions } from './types';
import { addStandardDataReduceOptions } from '../stat/types';
import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations';
export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel)
.setDefaults(defaults)
.setEditor(GaugePanelEditor)
.useFieldConfig()
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
@ -16,11 +13,13 @@ export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel)
path: 'showThresholdLabels',
name: 'Show threshold Labels',
description: 'Render the threshold values around the gauge bar',
defaultValue: false,
})
.addBooleanSwitch({
path: 'showThresholdMarkers',
name: 'Show threshold markers',
description: 'Renders the thresholds as an outer bar',
defaultValue: true,
});
})
.setPanelChangeHandler(gaugePanelChangedHandler)

View File

@ -1,6 +1,5 @@
import { VizOrientation, SelectableValue } from '@grafana/data';
import { SingleStatBaseOptions } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
import { commonValueOptionDefaults } from '../stat/types';
export interface GaugeOptions extends SingleStatBaseOptions {
showThresholdLabels: boolean;
@ -12,10 +11,3 @@ export const orientationOptions: Array<SelectableValue<VizOrientation>> = [
{ value: VizOrientation.Horizontal, label: 'Horizontal' },
{ value: VizOrientation.Vertical, label: 'Vertical' },
];
export const defaults: GaugeOptions = {
showThresholdMarkers: true,
showThresholdLabels: false,
reduceOptions: commonValueOptionDefaults,
orientation: VizOrientation.Auto,
};

View File

@ -1,105 +0,0 @@
import React from 'react';
import { LegendOptions, PanelOptionsGroup, LegacyForms, StatsPicker } from '@grafana/ui';
const { Input, Switch } = LegacyForms;
export interface GraphLegendEditorLegendOptions extends LegendOptions {
stats?: string[];
decimals?: number;
sortBy?: string;
sortDesc?: boolean;
}
interface GraphLegendEditorProps {
options: GraphLegendEditorLegendOptions;
onChange: (options: GraphLegendEditorLegendOptions) => void;
}
export const GraphLegendEditor: React.FunctionComponent<GraphLegendEditorProps> = props => {
const { options, onChange } = props;
const onStatsChanged = (stats: string[]) => {
onChange({
...options,
stats,
});
};
const onOptionToggle = (option: keyof LegendOptions) => (event?: React.ChangeEvent<HTMLInputElement>) => {
const newOption: Partial<LegendOptions> = {};
if (!event) {
return;
}
if (option === 'placement') {
newOption[option] = event.target.checked ? 'right' : 'under';
} else {
newOption[option] = event.target.checked;
}
onChange({
...options,
...newOption,
});
};
const labelWidth = 8;
return (
<PanelOptionsGroup title="Legend">
<div className="section gf-form-group">
<h4>Options</h4>
<Switch
label="Show legend"
labelClass={`width-${labelWidth}`}
checked={options.isVisible}
onChange={onOptionToggle('isVisible')}
/>
<Switch
label="Display as table"
labelClass={`width-${labelWidth}`}
checked={options.asTable}
onChange={onOptionToggle('asTable')}
/>
<Switch
label="To the right"
labelClass={`width-${labelWidth}`}
checked={options.placement === 'right'}
onChange={onOptionToggle('placement')}
/>
</div>
<div className="section gf-form-group">
<h4>Show</h4>
<div className="gf-form">
<StatsPicker
allowMultiple={true}
stats={options.stats ? options.stats : []}
onChange={onStatsChanged}
placeholder={'Pick Values'}
/>
</div>
<div className="gf-form">
<div className="gf-form-label">Decimals</div>
<Input
className="gf-form-input width-5"
type="number"
value={options.decimals}
placeholder="Auto"
onChange={event => {
onChange({
...options,
decimals: parseInt(event.target.value, 10),
});
}}
/>
</div>
</div>
<div className="section gf-form-group">
<h4>Hidden series</h4>
{/* <Switch label="With only nulls" checked={!!options.hideEmpty} onChange={onOptionToggle('hideEmpty')} /> */}
<Switch label="With only zeros" checked={!!options.hideZero} onChange={onOptionToggle('hideZero')} />
</div>
</PanelOptionsGroup>
);
};

View File

@ -42,7 +42,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr
graphSeriesModel: getGraphSeriesModel(
props.data.series,
props.timeZone,
props.options.series,
props.options.series || {},
props.options.graph,
props.options.legend,
props.fieldConfig
@ -56,7 +56,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr
graphSeriesModel: getGraphSeriesModel(
props.data.series,
props.timeZone,
props.options.series,
props.options.series || {},
props.options.graph,
props.options.legend,
props.fieldConfig

View File

@ -1,107 +0,0 @@
//// Libraries
import _ from 'lodash';
import React, { PureComponent } from 'react';
// Types
import { FieldConfig, PanelEditorProps } from '@grafana/data';
import {
LegendOptions,
GraphTooltipOptions,
PanelOptionsGrid,
PanelOptionsGroup,
LegacyForms,
FieldPropertiesEditor,
} from '@grafana/ui';
const { Select, Switch } = LegacyForms;
import { Options, GraphOptions } from './types';
import { GraphLegendEditor } from './GraphLegendEditor';
import { NewPanelEditorContext } from 'app/features/dashboard/components/PanelEditor/PanelEditor';
export class GraphPanelEditor extends PureComponent<PanelEditorProps<Options>> {
onGraphOptionsChange = (options: Partial<GraphOptions>) => {
this.props.onOptionsChange({
...this.props.options,
graph: {
...this.props.options.graph,
...options,
},
});
};
onLegendOptionsChange = (options: LegendOptions) => {
this.props.onOptionsChange({ ...this.props.options, legend: options });
};
onTooltipOptionsChange = (options: GraphTooltipOptions) => {
this.props.onOptionsChange({ ...this.props.options, tooltipOptions: options });
};
onToggleLines = () => {
this.onGraphOptionsChange({ showLines: !this.props.options.graph.showLines });
};
onToggleBars = () => {
this.onGraphOptionsChange({ showBars: !this.props.options.graph.showBars });
};
onTogglePoints = () => {
this.onGraphOptionsChange({ showPoints: !this.props.options.graph.showPoints });
};
onDefaultsChange = (field: FieldConfig, event?: React.SyntheticEvent<HTMLElement>, callback?: () => void) => {
this.props.onFieldConfigChange({
...this.props.fieldConfig,
defaults: field,
});
};
render() {
const {
graph: { showBars, showPoints, showLines },
tooltipOptions: { mode },
} = this.props.options;
return (
<NewPanelEditorContext.Consumer>
{useNewEditor => {
return (
<>
<div className="section gf-form-group">
<h5 className="section-heading">Draw Modes</h5>
<Switch label="Lines" labelClass="width-5" checked={showLines} onChange={this.onToggleLines} />
<Switch label="Bars" labelClass="width-5" checked={showBars} onChange={this.onToggleBars} />
<Switch label="Points" labelClass="width-5" checked={showPoints} onChange={this.onTogglePoints} />
</div>
<PanelOptionsGrid>
<>
{!useNewEditor && (
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={false}
onChange={this.onDefaultsChange}
value={this.props.fieldConfig.defaults}
/>
</PanelOptionsGroup>
)}
</>
<PanelOptionsGroup title="Tooltip">
<Select
value={{ value: mode, label: mode === 'single' ? 'Single' : 'All series' }}
onChange={value => {
this.onTooltipOptionsChange({ mode: value.value as any });
}}
options={[
{ label: 'All series', value: 'multi' },
{ label: 'Single', value: 'single' },
]}
/>
</PanelOptionsGroup>
<GraphLegendEditor options={this.props.options.legend} onChange={this.onLegendOptionsChange} />
</PanelOptionsGrid>
</>
);
}}
</NewPanelEditorContext.Consumer>
);
}
}

View File

@ -19,8 +19,7 @@ import {
FieldConfigSource,
} from '@grafana/data';
import { SeriesOptions, GraphOptions } from './types';
import { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
import { SeriesOptions, GraphOptions, GraphLegendEditorLegendOptions } from './types';
export const getGraphSeriesModel = (
dataFrames: DataFrame[],

View File

@ -1,6 +1,63 @@
import { PanelPlugin } from '@grafana/data';
import { GraphPanelEditor } from './GraphPanelEditor';
import { FieldConfigProperty, PanelPlugin } from '@grafana/data';
import { GraphPanel } from './GraphPanel';
import { Options, defaults } from './types';
import { Options } from './types';
export const plugin = new PanelPlugin<Options>(GraphPanel).setDefaults(defaults).setEditor(GraphPanelEditor);
export const plugin = new PanelPlugin<Options>(GraphPanel)
.useFieldConfig({ standardOptions: [FieldConfigProperty.Unit, FieldConfigProperty.Decimals] })
.setPanelOptions(builder => {
builder
.addBooleanSwitch({
path: 'graph.showBars',
name: 'Show bars',
description: '',
defaultValue: false,
})
.addBooleanSwitch({
path: 'graph.showLines',
name: 'Show lines',
description: '',
defaultValue: true,
})
.addBooleanSwitch({
path: 'graph.showPoints',
name: 'Show poins',
description: '',
defaultValue: false,
})
.addBooleanSwitch({
path: 'legend.isVisible',
name: 'Show legend',
description: '',
defaultValue: true,
})
.addBooleanSwitch({
path: 'legend.asTable',
name: 'Display legend as table',
description: '',
defaultValue: false,
})
.addRadio({
path: 'legend.placement',
name: 'Legend placement',
description: '',
defaultValue: 'under',
settings: {
options: [
{ value: 'under', label: 'Below graph' },
{ value: 'right', label: 'Right to the graph' },
],
},
})
.addRadio({
path: 'tooltipOptions.mode',
name: 'Tooltip mode',
description: '',
defaultValue: 'single',
settings: {
options: [
{ value: 'single', label: 'Single series' },
{ value: 'multi', label: 'All series' },
],
},
});
});

View File

@ -1,6 +1,5 @@
import { LegendOptions, GraphTooltipOptions } from '@grafana/ui';
import { YAxis } from '@grafana/data';
import { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
export interface SeriesOptions {
color?: string;
@ -36,3 +35,10 @@ export const defaults: Options = {
series: {},
tooltipOptions: { mode: 'single' },
};
export interface GraphLegendEditorLegendOptions extends LegendOptions {
stats?: string[];
decimals?: number;
sortBy?: string;
sortDesc?: boolean;
}

View File

@ -112,4 +112,4 @@ export const getStyles = stylesFactory(() => {
};
});
export const plugin = new PanelPlugin(GrafanaLinksPanel).setDefaults({}).setNoPadding();
export const plugin = new PanelPlugin(GrafanaLinksPanel).setNoPadding();

View File

@ -1,68 +0,0 @@
// Libraries
import React, { PureComponent } from 'react';
import { PanelOptionsGrid, PanelOptionsGroup, FormLabel, LegacyForms } from '@grafana/ui';
const { Select, Switch } = LegacyForms;
// Types
import { Options } from './types';
import { SortOrder } from 'app/core/utils/explore';
import { PanelEditorProps, SelectableValue } from '@grafana/data';
const sortOrderOptions = [
{ value: SortOrder.Descending, label: 'Descending' },
{ value: SortOrder.Ascending, label: 'Ascending' },
];
export class LogsPanelEditor extends PureComponent<PanelEditorProps<Options>> {
onToggleLabels = () => {
const { options, onOptionsChange } = this.props;
const { showLabels } = options;
onOptionsChange({ ...options, showLabels: !showLabels });
};
onToggleTime = () => {
const { options, onOptionsChange } = this.props;
const { showTime } = options;
onOptionsChange({ ...options, showTime: !showTime });
};
onTogglewrapLogMessage = () => {
const { options, onOptionsChange } = this.props;
const { wrapLogMessage } = options;
onOptionsChange({ ...options, wrapLogMessage: !wrapLogMessage });
};
onShowValuesChange = (item: SelectableValue<SortOrder>) => {
const { options, onOptionsChange } = this.props;
onOptionsChange({ ...options, sortOrder: item.value });
};
render() {
const { showLabels, showTime, wrapLogMessage, sortOrder } = this.props.options;
const value = sortOrderOptions.filter(option => option.value === sortOrder)[0];
return (
<>
<PanelOptionsGrid>
<PanelOptionsGroup title="Columns">
<Switch label="Time" labelClass="width-10" checked={showTime} onChange={this.onToggleTime} />
<Switch label="Unique labels" labelClass="width-10" checked={showLabels} onChange={this.onToggleLabels} />
<Switch
label="Wrap lines"
labelClass="width-10"
checked={wrapLogMessage}
onChange={this.onTogglewrapLogMessage}
/>
<div className="gf-form">
<FormLabel>Order</FormLabel>
<Select options={sortOrderOptions} value={value} onChange={this.onShowValuesChange} />
</div>
</PanelOptionsGroup>
</PanelOptionsGrid>
</>
);
}
}

View File

@ -1,6 +1,38 @@
import { PanelPlugin } from '@grafana/data';
import { Options, defaults } from './types';
import { Options } from './types';
import { LogsPanel } from './LogsPanel';
import { LogsPanelEditor } from './LogsPanelEditor';
import { SortOrder } from '../../../core/utils/explore';
export const plugin = new PanelPlugin<Options>(LogsPanel).setDefaults(defaults).setEditor(LogsPanelEditor);
export const plugin = new PanelPlugin<Options>(LogsPanel).setPanelOptions(builder => {
builder
.addBooleanSwitch({
path: 'showTime',
name: 'Time',
description: '',
defaultValue: false,
})
.addBooleanSwitch({
path: 'showLabels',
name: 'Unique labels',
description: '',
defaultValue: false,
})
.addBooleanSwitch({
path: 'wrapLogMessage',
name: 'Wrap lines',
description: '',
defaultValue: false,
})
.addRadio({
path: 'sortOrder',
name: 'Order',
description: '',
settings: {
options: [
{ value: SortOrder.Descending, label: 'Descending' },
{ value: SortOrder.Ascending, label: 'Ascending' },
],
},
defaultValue: SortOrder.Descending,
});
});

View File

@ -6,10 +6,3 @@ export interface Options {
wrapLogMessage: boolean;
sortOrder: SortOrder;
}
export const defaults: Options = {
showLabels: false,
showTime: true,
wrapLogMessage: true,
sortOrder: SortOrder.Descending,
};

View File

@ -12,7 +12,8 @@ import { loadRSSFeed } from './rss';
// Types
import { PanelProps, DataFrameView, dateTime } from '@grafana/data';
import { NewsOptions, NewsItem, DEFAULT_FEED_URL } from './types';
import { NewsOptions, NewsItem } from './types';
import { DEFAULT_FEED_URL, PROXY_PREFIX } from './constants';
interface Props extends PanelProps<NewsOptions> {}
@ -41,7 +42,11 @@ export class NewsPanel extends PureComponent<Props, State> {
async loadFeed() {
const { options } = this.props;
try {
const url = options.feedUrl ?? DEFAULT_FEED_URL;
const url = options.feedUrl
? options.useProxy
? `${PROXY_PREFIX}${options.feedUrl}`
: options.feedUrl
: DEFAULT_FEED_URL;
const res = await loadRSSFeed(url);
const frame = feedToDataFrame(res);
this.setState({

View File

@ -1,71 +0,0 @@
import React, { PureComponent } from 'react';
import { FormField, PanelOptionsGroup, Button } from '@grafana/ui';
import { PanelEditorProps } from '@grafana/data';
import { NewsOptions, DEFAULT_FEED_URL } from './types';
const PROXY_PREFIX = 'https://cors-anywhere.herokuapp.com/';
interface State {
feedUrl?: string;
}
export class NewsPanelEditor extends PureComponent<PanelEditorProps<NewsOptions>, State> {
constructor(props: PanelEditorProps<NewsOptions>) {
super(props);
this.state = {
feedUrl: props.options.feedUrl,
};
}
onUpdatePanel = () =>
this.props.onOptionsChange({
...this.props.options,
feedUrl: this.state.feedUrl,
});
onFeedUrlChange = ({ target }: any) => this.setState({ feedUrl: target.value });
onSetProxyPrefix = () => {
const feedUrl = PROXY_PREFIX + this.state.feedUrl;
this.setState({ feedUrl });
this.props.onOptionsChange({
...this.props.options,
feedUrl,
});
};
render() {
const feedUrl = this.state.feedUrl || '';
const suggestProxy = feedUrl && !feedUrl.startsWith(PROXY_PREFIX);
return (
<>
<PanelOptionsGroup title="Feed">
<>
<div className="gf-form">
<FormField
label="URL"
labelWidth={7}
inputWidth={30}
value={feedUrl || ''}
placeholder={DEFAULT_FEED_URL}
onChange={this.onFeedUrlChange}
tooltip="Only RSS feed formats are supported (not Atom)."
onBlur={this.onUpdatePanel}
/>
</div>
{suggestProxy && (
<div>
<br />
<div>If the feed is unable to connect, consider a CORS proxy</div>
<Button variant="secondary" onClick={this.onSetProxyPrefix}>
Use Proxy
</Button>
</div>
)}
</>
</PanelOptionsGroup>
</>
);
}
}

View File

@ -0,0 +1,2 @@
export const DEFAULT_FEED_URL = 'https://grafana.com/blog/news.xml';
export const PROXY_PREFIX = 'https://cors-anywhere.herokuapp.com/';

View File

@ -1,6 +1,24 @@
import { PanelPlugin } from '@grafana/data';
import { NewsPanel } from './NewsPanel';
import { NewsPanelEditor } from './NewsPanelEditor';
import { defaults, NewsOptions } from './types';
import { NewsOptions } from './types';
import { DEFAULT_FEED_URL, PROXY_PREFIX } from './constants';
export const plugin = new PanelPlugin<NewsOptions>(NewsPanel).setDefaults(defaults).setEditor(NewsPanelEditor);
export const plugin = new PanelPlugin<NewsOptions>(NewsPanel).setPanelOptions(builder => {
builder
.addTextInput({
path: 'feedUrl',
name: 'URL',
description: 'Only RSS feed formats are supported (not Atom).',
settings: {
placeholder: DEFAULT_FEED_URL,
},
})
.addBooleanSwitch({
path: 'useProxy',
name: 'Use Proxy',
description: 'If the feed is unable to connect, consider a CORS proxy',
showIf: currentConfig => {
return currentConfig.feedUrl && !currentConfig.feedUrl.startsWith(PROXY_PREFIX);
},
});
});

View File

@ -1,7 +1,6 @@
export const DEFAULT_FEED_URL = 'https://grafana.com/blog/news.xml';
export interface NewsOptions {
feedUrl?: string;
useProxy?: boolean;
}
export const defaults: NewsOptions = {

View File

@ -1,67 +0,0 @@
import React, { PureComponent } from 'react';
import {
PanelOptionsGrid,
FieldDisplayEditor,
PanelOptionsGroup,
FieldPropertiesEditor,
LegacyValueMappingsEditor,
} from '@grafana/ui';
import { PanelEditorProps, ReduceDataOptions, ValueMapping, FieldConfig } from '@grafana/data';
import { PieChartOptionsBox } from './PieChartOptionsBox';
import { PieChartOptions } from './types';
export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChartOptions>> {
onValueMappingsChanged = (mappings: ValueMapping[]) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
mappings,
},
});
};
onDisplayOptionsChanged = (fieldOptions: ReduceDataOptions) =>
this.props.onOptionsChange({
...this.props.options,
reduceOptions: fieldOptions,
});
onDefaultsChange = (field: FieldConfig) => {
this.props.onFieldConfigChange({
...this.props.fieldConfig,
defaults: field,
});
};
render() {
const { onOptionsChange, options, data, fieldConfig, onFieldConfigChange } = this.props;
const { reduceOptions: fieldOptions } = options;
const { defaults } = fieldConfig;
return (
<>
<PanelOptionsGrid>
<PanelOptionsGroup title="Display">
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={fieldOptions} />
</PanelOptionsGroup>
<PanelOptionsGroup title="Field (default)">
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
</PanelOptionsGroup>
<PieChartOptionsBox
data={data}
onOptionsChange={onOptionsChange}
options={options}
fieldConfig={fieldConfig}
onFieldConfigChange={onFieldConfigChange}
/>
</PanelOptionsGrid>
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
</>
);
}
}

View File

@ -1,8 +1,29 @@
import { PanelPlugin } from '@grafana/data';
import { PieChartPanelEditor } from './PieChartPanelEditor';
import { PieChartPanel } from './PieChartPanel';
import { PieChartOptions, defaults } from './types';
import { PieChartOptions } from './types';
import { addStandardDataReduceOptions } from '../stat/types';
import { PieChartType } from '@grafana/ui';
export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel)
.setDefaults(defaults)
.setEditor(PieChartPanelEditor);
export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel).setPanelOptions(builder => {
addStandardDataReduceOptions(builder, false);
builder
.addRadio({
name: 'Piechart type',
description: 'How the piechart should be rendered',
path: 'pieType',
settings: {
options: [
{ value: PieChartType.PIE, label: 'Pie' },
{ value: PieChartType.DONUT, label: 'Donut' },
],
},
defaultValue: PieChartType.PIE,
})
.addNumberInput({
name: 'Width',
description: 'Width of the piechart outline',
path: 'strokeWidth',
defaultValue: 1,
});
});

View File

@ -1,15 +1,6 @@
import { PieChartType, SingleStatBaseOptions } from '@grafana/ui';
import { commonValueOptionDefaults } from '../stat/types';
import { VizOrientation } from '@grafana/data';
export interface PieChartOptions extends SingleStatBaseOptions {
pieType: PieChartType;
strokeWidth: number;
}
export const defaults: PieChartOptions = {
pieType: PieChartType.PIE,
strokeWidth: 1,
orientation: VizOrientation.Auto,
reduceOptions: commonValueOptionDefaults,
};

View File

@ -1,177 +0,0 @@
// Libraries
import React, { PureComponent } from 'react';
import {
PanelOptionsGrid,
FieldDisplayEditor,
PanelOptionsGroup,
FormLabel,
LegacyForms,
FieldPropertiesEditor,
ThresholdsEditor,
LegacyValueMappingsEditor,
DataLinksEditor,
} from '@grafana/ui';
const { Select } = LegacyForms;
import {
PanelEditorProps,
ReduceDataOptions,
FieldConfig,
ValueMapping,
ThresholdsConfig,
DataLink,
} from '@grafana/data';
import { StatPanelOptions, colorModes, graphModes, justifyModes } from './types';
import { orientationOptions } from '../gauge/types';
import {
getCalculationValueDataLinksVariableSuggestions,
getDataLinksVariableSuggestions,
} from '../../../features/panel/panellinks/link_srv';
import { NewPanelEditorContext } from '../../../features/dashboard/components/PanelEditor/PanelEditor';
export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOptions>> {
onThresholdsChanged = (thresholds: ThresholdsConfig) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
thresholds,
},
});
};
onValueMappingsChanged = (mappings: ValueMapping[]) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
mappings,
},
});
};
onDisplayOptionsChanged = (fieldOptions: ReduceDataOptions) =>
this.props.onOptionsChange({
...this.props.options,
reduceOptions: fieldOptions,
});
onColorModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, colorMode: value });
onGraphModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, graphMode: value });
onJustifyModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, justifyMode: value });
onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
onDefaultsChange = (field: FieldConfig) => {
this.props.onFieldConfigChange({
...this.props.fieldConfig,
defaults: field,
});
};
onDataLinksChanged = (links: DataLink[]) => {
const current = this.props.fieldConfig;
this.props.onFieldConfigChange({
...current,
defaults: {
...current.defaults,
links,
},
});
};
render() {
const { options, fieldConfig } = this.props;
const { reduceOptions: valueOptions } = options;
const { defaults } = fieldConfig;
const suggestions = valueOptions.values
? getDataLinksVariableSuggestions(this.props.data.series)
: getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
return (
<NewPanelEditorContext.Consumer>
{useNewEditor => {
if (useNewEditor) {
return null;
}
return (
<>
<PanelOptionsGrid>
<PanelOptionsGroup title="Display">
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={valueOptions} labelWidth={8} />
<div className="form-field">
<FormLabel width={8}>Orientation</FormLabel>
<Select
width={12}
options={orientationOptions}
defaultValue={orientationOptions[0]}
onChange={this.onOrientationChange}
value={orientationOptions.find(item => item.value === options.orientation)}
/>
</div>
<div className="form-field">
<FormLabel width={8}>Color</FormLabel>
<Select
width={12}
options={colorModes}
defaultValue={colorModes[0]}
onChange={this.onColorModeChanged}
value={colorModes.find(item => item.value === options.colorMode)}
/>
</div>
<div className="form-field">
<FormLabel width={8}>Graph</FormLabel>
<Select
width={12}
options={graphModes}
defaultValue={graphModes[0]}
onChange={this.onGraphModeChanged}
value={graphModes.find(item => item.value === options.graphMode)}
/>
</div>
<div className="form-field">
<FormLabel width={8}>Justify</FormLabel>
<Select
width={12}
options={justifyModes}
defaultValue={justifyModes[0]}
onChange={this.onJustifyModeChanged}
value={justifyModes.find(item => item.value === options.justifyMode)}
/>
</div>
</PanelOptionsGroup>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
onChange={this.onDefaultsChange}
value={defaults}
showTitle={true}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</PanelOptionsGrid>
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}}
</NewPanelEditorContext.Consumer>
);
}
}

View File

@ -1,12 +1,9 @@
import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from '@grafana/ui';
import { PanelPlugin } from '@grafana/data';
import { StatPanelOptions, defaults, addStandardDataReduceOptions } from './types';
import { StatPanelOptions, addStandardDataReduceOptions } from './types';
import { StatPanel } from './StatPanel';
import { StatPanelEditor } from './StatPanelEditor';
export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel)
.setDefaults(defaults)
.setEditor(StatPanelEditor)
.useFieldConfig()
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
@ -16,6 +13,7 @@ export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel)
path: 'colorMode',
name: 'Color mode',
description: 'Color either the value or the background',
defaultValue: 'value',
settings: {
options: [
{ value: 'value', label: 'Value' },
@ -27,6 +25,7 @@ export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel)
path: 'graphMode',
name: 'Graph mode',
description: 'Stat panel graph / sparkline mode',
defaultValue: 'area',
settings: {
options: [
{ value: 'none', label: 'None' },
@ -38,6 +37,7 @@ export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel)
path: 'justifyMode',
name: 'Justify mode',
description: 'Value & title posititioning',
defaultValue: 'auto',
settings: {
options: [
{ value: 'auto', label: 'Auto' },

View File

@ -1,5 +1,5 @@
import { SingleStatBaseOptions, BigValueColorMode, BigValueGraphMode, BigValueJustifyMode } from '@grafana/ui';
import { VizOrientation, ReducerID, ReduceDataOptions, SelectableValue, standardEditorsRegistry } from '@grafana/data';
import { ReducerID, SelectableValue, standardEditorsRegistry } from '@grafana/data';
import { PanelOptionsEditorBuilder } from '@grafana/data/src/utils/OptionsUIBuilders';
// Structure copied from angular
@ -24,12 +24,10 @@ export const justifyModes: Array<SelectableValue<BigValueJustifyMode>> = [
{ value: BigValueJustifyMode.Center, label: 'Center' },
];
export const commonValueOptionDefaults: ReduceDataOptions = {
values: false,
calcs: [ReducerID.mean],
};
export function addStandardDataReduceOptions(builder: PanelOptionsEditorBuilder<StatPanelOptions>) {
export function addStandardDataReduceOptions(
builder: PanelOptionsEditorBuilder<SingleStatBaseOptions>,
includeOrientation = true
) {
builder.addRadio({
path: 'reduceOptions.values',
name: 'Show',
@ -40,6 +38,7 @@ export function addStandardDataReduceOptions(builder: PanelOptionsEditorBuilder<
{ value: true, label: 'All values' },
],
},
defaultValue: false,
});
builder.addNumberInput({
@ -60,26 +59,22 @@ export function addStandardDataReduceOptions(builder: PanelOptionsEditorBuilder<
name: 'Value',
description: 'Choose a reducer function / calculation',
editor: standardEditorsRegistry.get('stats-picker').editor as any,
defaultValue: [ReducerID.mean],
});
builder.addRadio({
path: 'orientation',
name: 'Orientation',
description: 'Stacking direction in case of multiple series or fields',
settings: {
options: [
{ value: 'auto', label: 'Auto' },
{ value: 'horizontal', label: 'Horizontal' },
{ value: 'vertical', label: 'Vertical' },
],
},
});
if (includeOrientation) {
builder.addRadio({
path: 'orientation',
name: 'Orientation',
description: 'Stacking direction in case of multiple series or fields',
settings: {
options: [
{ value: 'auto', label: 'Auto' },
{ value: 'horizontal', label: 'Horizontal' },
{ value: 'vertical', label: 'Vertical' },
],
},
defaultValue: 'auto',
});
}
}
export const defaults: StatPanelOptions = {
graphMode: BigValueGraphMode.Area,
colorMode: BigValueColorMode.Value,
justifyMode: BigValueJustifyMode.Auto,
reduceOptions: commonValueOptionDefaults,
orientation: VizOrientation.Auto,
};

View File

@ -1,26 +0,0 @@
//// Libraries
import React, { PureComponent } from 'react';
// Types
import { PanelEditorProps } from '@grafana/data';
import { LegacyForms } from '@grafana/ui';
const { Switch } = LegacyForms;
import { Options } from './types';
export class TablePanelEditor extends PureComponent<PanelEditorProps<Options>> {
onToggleShowHeader = () => {
this.props.onOptionsChange({ ...this.props.options, showHeader: !this.props.options.showHeader });
};
render() {
const { showHeader } = this.props.options;
return (
<div>
<div className="section gf-form-group">
<h5 className="section-heading">Header</h5>
<Switch label="Show" labelClass="width-6" checked={showHeader} onChange={this.onToggleShowHeader} />
</div>
</div>
);
}
}

View File

@ -1,9 +1,8 @@
import { PanelPlugin } from '@grafana/data';
import { TablePanel } from './TablePanel';
import { CustomFieldConfig, defaults, Options } from './types';
import { CustomFieldConfig, Options } from './types';
export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
.setDefaults(defaults)
.useFieldConfig({
useCustomConfig: builder => {
builder
@ -47,16 +46,17 @@ export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
},
})
.setPanelOptions(builder => {
builder.addBooleanSwitch({
path: 'showHeader',
name: 'Show header',
description: "To display table's header or not to display",
});
})
.setPanelOptions(builder => {
builder.addBooleanSwitch({
path: 'resizable',
name: 'Resizable',
description: 'Toggles if table columns are resizable or not',
});
builder
.addBooleanSwitch({
path: 'showHeader',
name: 'Show header',
description: "To display table's header or not to display",
defaultValue: true,
})
.addBooleanSwitch({
path: 'resizable',
name: 'Resizable',
description: 'Toggles if table columns are resizable or not',
defaultValue: false,
});
});

View File

@ -7,8 +7,3 @@ export interface CustomFieldConfig {
width: number;
displayMode: string;
}
export const defaults: Options = {
showHeader: true,
resizable: false,
};

View File

@ -1,41 +0,0 @@
// Libraries
import React, { PureComponent, ChangeEvent } from 'react';
// Components
import { PanelOptionsGroup, LegacyForms } from '@grafana/ui';
const { Select } = LegacyForms;
import { PanelEditorProps, SelectableValue } from '@grafana/data';
// Types
import { TextOptions, TextMode } from './types';
export class TextPanelEditor extends PureComponent<PanelEditorProps<TextOptions>> {
modes: Array<SelectableValue<TextMode>> = [
{ value: 'markdown', label: 'Markdown' },
{ value: 'text', label: 'Text' },
{ value: 'html', label: 'HTML' },
];
onModeChange = (item: SelectableValue<TextMode>) =>
this.props.onOptionsChange({ ...this.props.options, mode: item.value! });
onContentChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {
this.props.onOptionsChange({ ...this.props.options, content: (evt.target as any).value });
};
render() {
const { mode, content } = this.props.options;
return (
<PanelOptionsGroup title="Text">
<div className="gf-form-inline">
<div className="gf-form">
<span className="gf-form-label">Mode</span>
<Select onChange={this.onModeChange} value={this.modes.find(e => mode === e.value)} options={this.modes} />
</div>
</div>
<textarea value={content} onChange={this.onContentChange} className="gf-form-input" rows={10} />
</PanelOptionsGroup>
);
}
}

View File

@ -1,12 +1,38 @@
import { PanelModel, PanelPlugin } from '@grafana/data';
import { TextPanelEditor } from './TextPanelEditor';
import { TextPanel } from './TextPanel';
import { TextOptions, defaults } from './types';
import { TextOptions } from './types';
export const plugin = new PanelPlugin<TextOptions>(TextPanel)
.setDefaults(defaults)
.setEditor(TextPanelEditor)
.setPanelOptions(builder => {
builder
.addRadio({
path: 'mode',
name: 'Mode',
description: 'text mode of the panel',
settings: {
options: [
{ value: 'markdown', label: 'Markdown' },
{ value: 'text', label: 'Text' },
{ value: 'html', label: 'HTML' },
],
},
defaultValue: 'markdown',
})
.addTextInput({
path: 'content',
name: 'Content',
description: 'Content of the panel',
settings: {
useTextarea: true,
rows: 5,
},
defaultValue: `# Title
For markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)
`,
});
})
.setPanelChangeHandler((panel: PanelModel<TextOptions>, prevPluginId: string, prevOptions: any) => {
if (prevPluginId === 'text') {
return prevOptions as TextOptions;

View File

@ -3,13 +3,3 @@ export interface TextOptions {
mode: TextMode;
content: string;
}
export const defaults: TextOptions = {
mode: 'markdown',
content: `# Title
For markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)
`,
};