mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
a29056966c
commit
ea792edd3a
@ -60,6 +60,8 @@ export interface StringFieldConfigSettings {
|
||||
placeholder?: string;
|
||||
maxLength?: number;
|
||||
expandTemplateVars?: boolean;
|
||||
useTextarea?: boolean;
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
export const stringOverrideProcessor = (
|
||||
|
@ -10,6 +10,7 @@ export interface OptionsEditorItem<TOptions, TSettings, TEditorProps, TValue> ex
|
||||
editor: ComponentType<TEditorProps>;
|
||||
settings?: TSettings;
|
||||
defaultValue?: TValue;
|
||||
showIf?: (currentConfig: TOptions) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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} />
|
||||
|
@ -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 };
|
||||
});
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
});
|
80
public/app/plugins/panel/annolist/module.tsx
Normal file
80
public/app/plugins/panel/annolist/module.tsx
Normal 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;
|
||||
});
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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[],
|
||||
|
@ -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' },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -112,4 +112,4 @@ export const getStyles = stylesFactory(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const plugin = new PanelPlugin(GrafanaLinksPanel).setDefaults({}).setNoPadding();
|
||||
export const plugin = new PanelPlugin(GrafanaLinksPanel).setNoPadding();
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
@ -6,10 +6,3 @@ export interface Options {
|
||||
wrapLogMessage: boolean;
|
||||
sortOrder: SortOrder;
|
||||
}
|
||||
|
||||
export const defaults: Options = {
|
||||
showLabels: false,
|
||||
showTime: true,
|
||||
wrapLogMessage: true,
|
||||
sortOrder: SortOrder.Descending,
|
||||
};
|
||||
|
@ -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({
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
2
public/app/plugins/panel/news/constants.ts
Normal file
2
public/app/plugins/panel/news/constants.ts
Normal 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/';
|
@ -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);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -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 = {
|
||||
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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' },
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
@ -7,8 +7,3 @@ export interface CustomFieldConfig {
|
||||
width: number;
|
||||
displayMode: string;
|
||||
}
|
||||
|
||||
export const defaults: Options = {
|
||||
showHeader: true,
|
||||
resizable: false,
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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/)
|
||||
|
||||
|
||||
`,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user