Commit 53630b5f by Torkel Ödegaard Committed by GitHub

StatPanels: Refactoring DisplayValueOptions and renaming & adding new panel…

StatPanels: Refactoring DisplayValueOptions and renaming & adding new panel options to react panels  (#23153)

* StatPanels: Refactoring DisplayValueOptions and renaming

* added return

* Progress

* Updated

* Made radio groups full width by default in panel options

* Fixed ts issue

* Updated

* Added remaining options

* Removed unused type

* Updated snapshot

* Renamed to ReduceDataOptions
parent 8d2db9af
......@@ -26,7 +26,7 @@ describe('FieldDisplay', () => {
it('show first numeric values', () => {
const options = createDisplayOptions({
fieldOptions: {
reduceOptions: {
calcs: [ReducerID.first],
},
fieldConfig: {
......@@ -42,7 +42,7 @@ describe('FieldDisplay', () => {
it('show last numeric values', () => {
const options = createDisplayOptions({
fieldOptions: {
reduceOptions: {
calcs: [ReducerID.last],
},
});
......@@ -52,7 +52,7 @@ describe('FieldDisplay', () => {
it('show all numeric values', () => {
const options = createDisplayOptions({
fieldOptions: {
reduceOptions: {
values: true, //
limit: 1000,
calcs: [],
......@@ -64,7 +64,7 @@ describe('FieldDisplay', () => {
it('show 2 numeric values (limit)', () => {
const options = createDisplayOptions({
fieldOptions: {
reduceOptions: {
values: true, //
limit: 2,
calcs: [],
......@@ -173,12 +173,8 @@ describe('FieldDisplay', () => {
},
];
const options = createDisplayOptions({
fieldOptions: {
reduceOptions: {
calcs: [ReducerID.first],
override: {},
defaults: {
mappings: mappingConfig,
},
},
});
......@@ -202,13 +198,9 @@ describe('FieldDisplay', () => {
},
];
const options = createDisplayOptions({
fieldOptions: {
reduceOptions: {
calcs: [ReducerID.first],
values: true,
override: {},
defaults: {
mappings: mappingConfig,
},
},
});
......@@ -253,7 +245,7 @@ function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}
replaceVariables: (value: string) => {
return value;
},
fieldOptions: {
reduceOptions: {
calcs: [],
},
fieldConfig: {
......
......@@ -11,7 +11,6 @@ import {
FieldConfigSource,
FieldType,
InterpolateFunction,
ValueMapping,
} from '../types';
import { DataFrameView } from '../dataframe/DataFrameView';
import { GraphSeriesValue } from '../types/graph';
......@@ -20,15 +19,16 @@ import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { ScopedVars } from '../types/ScopedVars';
import { getTimeField } from '../dataframe/processDataFrame';
// export interface FieldDisplayOptions extends FieldConfigSource {
export interface FieldDisplayOptions {
values?: boolean; // If true show each row value
limit?: number; // if showing all values limit
calcs: string[]; // when !values, pick one value for the whole field
override?: any;
defaults?: {
mappings: ValueMapping[];
};
/**
* Options for how to turn DataFrames into an array of display values
*/
export interface ReduceDataOptions {
/* If true show each row value */
values?: boolean;
/** if showing all values limit */
limit?: number;
/** When !values, pick one value for the whole field */
calcs: string[];
}
// TODO: use built in variables, same as for data links?
......@@ -81,7 +81,7 @@ export interface FieldDisplay {
export interface GetFieldDisplayValuesOptions {
data?: DataFrame[];
fieldOptions: FieldDisplayOptions;
reduceOptions: ReduceDataOptions;
fieldConfig: FieldConfigSource;
replaceVariables: InterpolateFunction;
sparkline?: boolean; // Calculate the sparkline
......@@ -92,8 +92,8 @@ export interface GetFieldDisplayValuesOptions {
export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25;
export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): FieldDisplay[] => {
const { replaceVariables, fieldOptions, fieldConfig } = options;
const calcs = fieldOptions.calcs.length ? fieldOptions.calcs : [ReducerID.last];
const { replaceVariables, reduceOptions, fieldConfig } = options;
const calcs = reduceOptions.calcs.length ? reduceOptions.calcs : [ReducerID.last];
const values: FieldDisplay[] = [];
......@@ -101,7 +101,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
// Field overrides are applied already
const data = options.data;
let hitLimit = false;
const limit = fieldOptions.limit ? fieldOptions.limit : DEFAULT_FIELD_DISPLAY_VALUES_LIMIT;
const limit = reduceOptions.limit ? reduceOptions.limit : DEFAULT_FIELD_DISPLAY_VALUES_LIMIT;
const defaultTitle = getTitleTemplate(fieldConfig.defaults.title, calcs, data);
const scopedVars: ScopedVars = {};
......@@ -129,7 +129,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
const title = config.title ? config.title : defaultTitle;
// Show all rows
if (fieldOptions.values) {
if (reduceOptions.values) {
const usesCellValues = title.indexOf(VAR_CELL_PREFIX) >= 0;
for (let j = 0; j < field.values.length; j++) {
......
......@@ -13,8 +13,8 @@ const createTheme = (theme: GrafanaTheme) => {
colorSecondary: theme.colors.brandPrimary,
// UI
appBg: theme.colors.bodyBg,
appContentBg: theme.colors.bodyBg,
appBg: theme.colors.pageBg,
appContentBg: theme.colors.pageBg,
appBorderColor: theme.colors.pageHeaderBorder,
appBorderRadius: 4,
......@@ -29,7 +29,7 @@ const createTheme = (theme: GrafanaTheme) => {
// Toolbar default and active colors
barTextColor: theme.colors.formInputBorderActive,
barSelectedColor: theme.colors.brandPrimary,
barBg: theme.colors.bodyBg,
barBg: theme.colors.pageBg,
// Form colors
inputBg: theme.colors.formInputBg,
......
......@@ -5,6 +5,7 @@ import { css, cx } from 'emotion';
import { getFocusCss, getPropertiesForButtonSize } from '../commonStyles';
export type RadioButtonSize = 'sm' | 'md';
export interface RadioButtonProps {
size?: RadioButtonSize;
disabled?: boolean;
......@@ -12,9 +13,10 @@ export interface RadioButtonProps {
active: boolean;
id: string;
onChange: () => void;
fullWidth?: boolean;
}
const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize) => {
const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize, fullWidth?: boolean) => {
const { fontSize, height } = getPropertiesForButtonSize(theme, size);
const horizontalPadding = theme.spacing[size] ?? theme.spacing.md;
const c = theme.colors;
......@@ -79,6 +81,8 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButt
background: ${bg};
cursor: pointer;
z-index: 1;
flex-grow: ${fullWidth ? 1 : 0};
text-align: center;
user-select: none;
......@@ -99,9 +103,10 @@ export const RadioButton: React.FC<RadioButtonProps> = ({
onChange,
id,
name = undefined,
fullWidth,
}) => {
const theme = useTheme();
const styles = getRadioButtonStyles(theme, size);
const styles = getRadioButtonStyles(theme, size, fullWidth);
return (
<>
......
......@@ -17,7 +17,7 @@ export default {
const sizes: RadioButtonSize[] = ['sm', 'md'];
export const simple = () => {
const [selected, setSelected] = useState();
const [selected, setSelected] = useState('graphite');
const BEHAVIOUR_GROUP = 'Behaviour props';
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
const disabledItem = select('Disabled item', ['', 'graphite', 'prometheus', 'elastic'], '', BEHAVIOUR_GROUP);
......@@ -36,8 +36,37 @@ export const simple = () => {
disabled={disabled}
disabledOptions={[disabledItem]}
value={selected}
onChange={setSelected}
onChange={v => setSelected(v!)}
size={size}
/>
);
};
export const fullWidth = () => {
const [selected, setSelected] = useState('elastic');
const BEHAVIOUR_GROUP = 'Behaviour props';
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
const disabledItem = select('Disabled item', ['', 'graphite', 'prometheus', 'elastic'], '', BEHAVIOUR_GROUP);
const VISUAL_GROUP = 'Visual options';
const size = select<RadioButtonSize>('Size', sizes, 'md', VISUAL_GROUP);
const options = [
{ label: 'Prometheus', value: 'prometheus' },
{ label: 'Graphite', value: 'graphite' },
{ label: 'Elastic', value: 'elastic' },
];
return (
<div style={{ width: '100%' }}>
<RadioButtonGroup
options={options}
disabled={disabled}
disabledOptions={[disabledItem]}
value={selected}
onChange={v => setSelected(v!)}
size={size}
fullWidth
/>
</div>
);
};
......@@ -39,6 +39,7 @@ interface RadioButtonGroupProps<T> {
options: Array<SelectableValue<T>>;
onChange?: (value?: T) => void;
size?: RadioButtonSize;
fullWidth?: boolean;
}
export function RadioButtonGroup<T>({
......@@ -48,6 +49,7 @@ export function RadioButtonGroup<T>({
disabled,
disabledOptions,
size = 'md',
fullWidth,
}: RadioButtonGroupProps<T>) {
const handleOnChange = useCallback(
(option: SelectableValue<T>) => {
......@@ -75,6 +77,7 @@ export function RadioButtonGroup<T>({
onChange={handleOnChange(o)}
id={`option-${o.value}`}
name={groupName.current}
fullWidth
>
{o.label}
</RadioButton>
......
import React from 'react';
import { FieldConfigEditorProps, ReducerID } from '@grafana/data';
import { StatsPicker } from '../StatsPicker/StatsPicker';
export const StatsPickerEditor: React.FC<FieldConfigEditorProps<string[], any>> = ({ value, onChange }) => {
return <StatsPicker stats={value} onChange={onChange} allowMultiple={false} defaultStat={ReducerID.mean} />;
};
......@@ -9,7 +9,7 @@ import { StatsPicker } from '../StatsPicker/StatsPicker';
// Types
import Select from '../Select/Select';
import {
FieldDisplayOptions,
ReduceDataOptions,
DEFAULT_FIELD_DISPLAY_VALUES_LIMIT,
ReducerID,
toNumberString,
......@@ -32,8 +32,8 @@ const showOptions: Array<SelectableValue<boolean>> = [
export interface Props {
labelWidth?: number;
value: FieldDisplayOptions;
onChange: (value: FieldDisplayOptions, event?: React.SyntheticEvent<HTMLElement>) => void;
value: ReduceDataOptions;
onChange: (value: ReduceDataOptions, event?: React.SyntheticEvent<HTMLElement>) => void;
}
export class FieldDisplayEditor extends PureComponent<Props> {
......
......@@ -11,7 +11,7 @@ import {
MappingType,
VizOrientation,
PanelModel,
FieldDisplayOptions,
ReduceDataOptions,
ThresholdsMode,
ThresholdsConfig,
validateFieldConfig,
......@@ -19,11 +19,11 @@ import {
} from '@grafana/data';
export interface SingleStatBaseOptions {
fieldOptions: FieldDisplayOptions;
reduceOptions: ReduceDataOptions;
orientation: VizOrientation;
}
const optionsToKeep = ['fieldOptions', 'orientation'];
const optionsToKeep = ['reduceOptions', 'orientation'];
export function sharedSingleStatPanelChangedHandler(
panel: PanelModel<Partial<SingleStatBaseOptions>> | any,
......@@ -131,9 +131,9 @@ export function sharedSingleStatMigrationHandler(panel: PanelModel<SingleStatBas
options = moveThresholdsAndMappingsToField(options);
}
if (previousVersion < 6.6) {
const { fieldOptions } = options;
const { fieldOptions } = options;
if (previousVersion < 6.6) {
// discard the old `override` options and enter an empty array
if (fieldOptions && fieldOptions.override) {
const { override, ...rest } = options.fieldOptions;
......@@ -178,16 +178,24 @@ export function sharedSingleStatMigrationHandler(panel: PanelModel<SingleStatBas
panel.fieldConfig = panel.fieldConfig || { defaults: {}, overrides: [] };
panel.fieldConfig = {
defaults:
options.fieldOptions && options.fieldOptions.defaults
? { ...panel.fieldConfig.defaults, ...options.fieldOptions.defaults }
fieldOptions && fieldOptions.defaults
? { ...panel.fieldConfig.defaults, ...fieldOptions.defaults }
: panel.fieldConfig.defaults,
overrides:
options.fieldOptions && options.fieldOptions.overrides
? [...panel.fieldConfig.overrides, ...options.fieldOptions.overrides]
fieldOptions && fieldOptions.overrides
? [...panel.fieldConfig.overrides, ...fieldOptions.overrides]
: panel.fieldConfig.overrides,
};
delete options.fieldOptions.defaults;
delete options.fieldOptions.overrides;
if (fieldOptions) {
options.reduceOptions = {
values: fieldOptions.values,
limit: fieldOptions.limit,
calcs: fieldOptions.calcs,
};
}
delete options.fieldOptions;
}
return options as SingleStatBaseOptions;
......
......@@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
import isArray from 'lodash/isArray';
import difference from 'lodash/difference';
import { Select } from '../Select/Select';
import { Select } from '../Forms/Select/Select';
import { fieldReducers, SelectableValue } from '@grafana/data';
......@@ -11,7 +11,6 @@ interface Props {
placeholder?: string;
onChange: (stats: string[]) => void;
stats: string[];
width?: number;
allowMultiple?: boolean;
defaultStat?: string;
}
......@@ -63,12 +62,11 @@ export class StatsPicker extends PureComponent<Props> {
};
render() {
const { width, stats, allowMultiple, defaultStat, placeholder } = this.props;
const { stats, allowMultiple, defaultStat, placeholder } = this.props;
const select = fieldReducers.selectOptions(stats);
return (
<Select
width={width}
value={select.current}
isClearable={!defaultStat}
isMulti={allowMultiple}
......
......@@ -24,6 +24,7 @@ import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
import { UnitValueEditor } from '../components/OptionsUI/units';
import { DataLinksValueEditor } from '../components/OptionsUI/links';
import { ColorValueEditor } from '../components/OptionsUI/color';
import { StatsPickerEditor } from '../components/OptionsUI/stats';
/**
* Returns collection of common field config properties definitions
......@@ -227,7 +228,7 @@ export const getStandardOptionEditors = () => {
id: 'radio',
name: 'Radio',
description: 'Allows option selection',
editor: props => <Forms.RadioButtonGroup {...props} options={props.item.settings?.options} />,
editor: props => <Forms.RadioButtonGroup {...props} options={props.item.settings?.options} fullWidth />,
};
const unit: StandardEditorsRegistryItem<string> = {
......@@ -265,5 +266,12 @@ export const getStandardOptionEditors = () => {
editor: DataLinksValueEditor as any,
};
return [text, number, boolean, radio, select, unit, mappings, thresholds, links, color];
const statsPicker: StandardEditorsRegistryItem<string[]> = {
id: 'stats-picker',
name: 'Stats Picker',
editor: StatsPickerEditor as any,
description: '',
};
return [text, number, boolean, radio, select, unit, mappings, thresholds, links, color, statsPicker];
};
......@@ -65,10 +65,10 @@ export const OptionsPaneContent: React.FC<{
const renderCustomPanelSettings = useCallback(
(plugin: PanelPlugin) => {
const editors = [];
const editors: JSX.Element[] = [];
if (plugin.editor && panel) {
editors.push(
<div className={styles.legacyOptions}>
<div className={styles.legacyOptions} key="plugin custom panel settings">
<plugin.editor
data={data}
options={panel.getOptions()}
......@@ -83,12 +83,17 @@ export const OptionsPaneContent: React.FC<{
// When editor created declaratively
if (plugin.optionEditors && panel) {
editors.push(
<PanelOptionsEditor options={panel.getOptions()} onChange={onPanelOptionsChanged} plugin={plugin} />
<PanelOptionsEditor
key="panel options"
options={panel.getOptions()}
onChange={onPanelOptionsChanged}
plugin={plugin}
/>
);
}
if (editors.length > 0) {
return <>{editors}</>;
return editors;
}
return (
......
......@@ -228,7 +228,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
const { plugin, dashboard, data, panel } = this.props;
if (!plugin) {
return null;
return <div />;
}
return (
......
import React, { useMemo } from 'react';
import { set as lodashSet, get as lodashGet } from 'lodash';
import { PanelPlugin } from '@grafana/data';
import { Forms } from '@grafana/ui';
......@@ -12,18 +13,16 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
const optionEditors = useMemo(() => plugin.optionEditors, [plugin]);
const onOptionChange = (key: string, value: any) => {
onChange({
...options,
[key]: value,
});
const newOptions = lodashSet({ ...options }, key, value);
onChange(newOptions);
};
return (
<>
{optionEditors.list().map(e => {
return (
<Forms.Field label={e.name} description={e.description}>
<e.editor value={options[e.id]} onChange={value => onOptionChange(e.id, value)} item={e} />
<Forms.Field label={e.name} description={e.description} key={e.id}>
<e.editor value={lodashGet(options, e.id)} onChange={value => onOptionChange(e.id, value)} item={e} />
</Forms.Field>
);
})}
......
......@@ -45,7 +45,9 @@ describe('BarGauge Panel Migrations', () => {
type: 'bargauge',
} as Omit<PanelModel, 'fieldConfig'>;
expect(barGaugePanelMigrationHandler(panel as PanelModel)).toMatchSnapshot();
const newOptions = barGaugePanelMigrationHandler(panel as PanelModel);
// should mutate panel model and move field config out of panel.options
expect((panel as any).fieldConfig).toMatchInlineSnapshot(`
Object {
"defaults": Object {
......@@ -81,5 +83,20 @@ describe('BarGauge Panel Migrations', () => {
"overrides": Array [],
}
`);
// should options options
expect(newOptions).toMatchInlineSnapshot(`
Object {
"displayMode": "lcd",
"orientation": "vertical",
"reduceOptions": Object {
"calcs": Array [
"mean",
],
"limit": undefined,
"values": false,
},
}
`);
});
});
......@@ -65,7 +65,7 @@ function createBarGaugePanelWithData(data: PanelData): ReactWrapper<PanelProps<B
const options: BarGaugeOptions = {
displayMode: BarGaugeDisplayMode.Lcd,
fieldOptions: {
reduceOptions: {
calcs: ['mean'],
values: false,
},
......
......@@ -50,7 +50,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
const { data, options, replaceVariables, fieldConfig } = this.props;
return getFieldDisplayValues({
fieldConfig,
fieldOptions: options.fieldOptions,
reduceOptions: options.reduceOptions,
replaceVariables,
theme: config.theme,
data: data.series,
......
......@@ -16,7 +16,7 @@ import {
import {
DataLink,
FieldConfig,
FieldDisplayOptions,
ReduceDataOptions,
PanelEditorProps,
ThresholdsConfig,
ValueMapping,
......@@ -30,10 +30,10 @@ import {
import { NewPanelEditorContext } from '../../../features/dashboard/components/PanelEditor/PanelEditor';
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
onDisplayOptionsChanged = (fieldOptions: ReduceDataOptions) =>
this.props.onOptionsChange({
...this.props.options,
fieldOptions,
reduceOptions: fieldOptions,
});
onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
......@@ -84,7 +84,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
render() {
const { options, fieldConfig } = this.props;
const { fieldOptions } = options;
const { reduceOptions: fieldOptions } = options;
const { defaults } = fieldConfig;
const labelWidth = 6;
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BarGauge Panel Migrations from 6.2 1`] = `
Object {
"displayMode": "lcd",
"fieldOptions": Object {
"calcs": Array [
"mean",
],
"thresholds": Array [
Object {
"color": "green",
"index": 0,
"value": -Infinity,
},
Object {
"color": "orange",
"index": 1,
"value": 40,
},
Object {
"color": "red",
"index": 2,
"value": 80,
},
],
"values": false,
},
"orientation": "vertical",
}
`;
......@@ -2,7 +2,7 @@ import { sharedSingleStatPanelChangedHandler } from '@grafana/ui';
import { PanelPlugin } from '@grafana/data';
import { BarGaugePanel } from './BarGaugePanel';
import { BarGaugeOptions, defaults } from './types';
import { standardFieldConfig } from '../stat/types';
import { standardFieldConfig, addStandardDataReduceOptions } from '../stat/types';
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
import { barGaugePanelMigrationHandler } from './BarGaugeMigrations';
......@@ -11,20 +11,26 @@ export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
.setEditor(BarGaugePanelEditor)
.setFieldConfigDefaults(standardFieldConfig)
.setPanelOptions(builder => {
/* addStandardSingleValueOptions(builder); */
addStandardDataReduceOptions(builder);
builder.addRadio({
id: 'orientation',
name: 'Orientation',
description: 'Stacking direction for multiple bars',
settings: {
options: [
{ value: 'auto', label: 'Auto' },
{ value: 'horizontal', label: 'Horizontal' },
{ value: 'vertical', label: 'Vertical' },
],
},
});
builder
.addRadio({
id: 'displayMode',
name: 'Display mode',
description: 'Controls the bar style',
settings: {
options: [
{ value: 'basic', label: 'Basic' },
{ value: 'gradient', label: 'Gradient' },
{ value: 'lcd', label: 'Retro LCD' },
],
},
})
.addBooleanSwitch({
id: 'showUnfilled',
name: 'Show unfilled area',
description: 'When enabled renders the unfilled region as gray',
});
})
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
.setMigrationHandler(barGaugePanelMigrationHandler);
import { SingleStatBaseOptions, BarGaugeDisplayMode } from '@grafana/ui';
import { standardGaugeFieldOptions } from '../gauge/types';
import { commonValueOptionDefaults } from '../stat/types';
import { VizOrientation, SelectableValue } from '@grafana/data';
export interface BarGaugeOptions extends SingleStatBaseOptions {
......@@ -16,6 +16,6 @@ export const displayModes: Array<SelectableValue<string>> = [
export const defaults: BarGaugeOptions = {
displayMode: BarGaugeDisplayMode.Lcd,
orientation: VizOrientation.Horizontal,
fieldOptions: standardGaugeFieldOptions,
reduceOptions: commonValueOptionDefaults,
showUnfilled: true,
};
......@@ -82,10 +82,10 @@ describe('Gauge Panel Migrations', () => {
// Ignored due to the API change
//@ts-ignore
expect(result.fieldOptions.defaults).toBeUndefined();
expect(result.reduceOptions.defaults).toBeUndefined();
// Ignored due to the API change
//@ts-ignore
expect(result.fieldOptions.overrides).toBeUndefined();
expect(result.reduceOptions.overrides).toBeUndefined();
expect((panel as PanelModel).fieldConfig).toMatchInlineSnapshot(`
Object {
......
......@@ -43,7 +43,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
const { data, options, replaceVariables, fieldConfig } = this.props;
return getFieldDisplayValues({
fieldConfig,
fieldOptions: options.fieldOptions,
reduceOptions: options.reduceOptions,
replaceVariables,
theme: config.theme,
data: data.series,
......
......@@ -12,7 +12,7 @@ import {
} from '@grafana/ui';
import {
PanelEditorProps,
FieldDisplayOptions,
ReduceDataOptions,
ThresholdsConfig,
DataLink,
FieldConfig,
......@@ -39,14 +39,14 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
});
onDisplayOptionsChanged = (
fieldOptions: FieldDisplayOptions,
fieldOptions: ReduceDataOptions,
event?: React.SyntheticEvent<HTMLElement>,
callback?: () => void
) => {
this.props.onOptionsChange(
{
...this.props.options,
fieldOptions,
reduceOptions: fieldOptions,
},
callback
);
......@@ -94,24 +94,28 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
render() {
const { options, fieldConfig } = this.props;
const { showThresholdLabels, showThresholdMarkers, fieldOptions } = options;
const { showThresholdLabels, showThresholdMarkers, reduceOptions: valueOptions } = options;
const { defaults } = fieldConfig;
const suggestions = fieldOptions.values
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={fieldOptions}
value={valueOptions}
labelWidth={this.labelWidth}
/>
<Switch
......@@ -128,36 +132,27 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
/>
</PanelOptionsGroup>
<>
{!useNewEditor && (
<>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
showTitle={true}
onChange={this.onDefaultsChange}
value={defaults}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</>
)}
</>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
showTitle={true}
onChange={this.onDefaultsChange}
value={defaults}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</PanelOptionsGrid>
{!useNewEditor && (
<>
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
)}
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}}
......
......@@ -2,12 +2,14 @@
exports[`Gauge Panel Migrations from 6.1.1 1`] = `
Object {
"fieldOptions": Object {
"orientation": "auto",
"reduceOptions": Object {
"calcs": Array [
"last",
],
"limit": undefined,
"values": undefined,
},
"orientation": "auto",
"showThresholdLabels": true,
"showThresholdMarkers": true,
}
......
......@@ -2,12 +2,27 @@ import { PanelPlugin } from '@grafana/data';
import { GaugePanelEditor } from './GaugePanelEditor';
import { GaugePanel } from './GaugePanel';
import { GaugeOptions, defaults } from './types';
import { standardFieldConfig } from '../stat/types';
import { standardFieldConfig, addStandardDataReduceOptions } from '../stat/types';
import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations';
export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel)
.setDefaults(defaults)
.setFieldConfigDefaults(standardFieldConfig)
.setEditor(GaugePanelEditor)
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
builder
.addBooleanSwitch({
id: 'showThresholdLabels',
name: 'Show threshold Labels',
description: 'Render the threshold values around the gauge bar',
})
.addBooleanSwitch({
id: 'showThresholdMarkers',
name: 'Show threshold markers',
description: 'Renders the thresholds as an outer bar',
});
})
.setPanelChangeHandler(gaugePanelChangedHandler)
.setMigrationHandler(gaugePanelMigrationHandler);
import { VizOrientation, FieldDisplayOptions, SelectableValue } from '@grafana/data';
import { VizOrientation, SelectableValue } from '@grafana/data';
import { SingleStatBaseOptions } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
import { standardFieldDisplayOptions } from '../stat/types';
import { commonValueOptionDefaults } from '../stat/types';
export interface GaugeOptions extends SingleStatBaseOptions {
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
}
export const standardGaugeFieldOptions: FieldDisplayOptions = {
...standardFieldDisplayOptions,
};
export const orientationOptions: Array<SelectableValue<VizOrientation>> = [
{ value: VizOrientation.Auto, label: 'Auto' },
{ value: VizOrientation.Horizontal, label: 'Horizontal' },
......@@ -20,6 +16,6 @@ export const orientationOptions: Array<SelectableValue<VizOrientation>> = [
export const defaults: GaugeOptions = {
showThresholdMarkers: true,
showThresholdLabels: false,
fieldOptions: standardGaugeFieldOptions,
reduceOptions: commonValueOptionDefaults,
orientation: VizOrientation.Auto,
};
import { LegendOptions, GraphTooltipOptions } from '@grafana/ui';
import { YAxis, FieldDisplayOptions } from '@grafana/data';
import { YAxis } from '@grafana/data';
import { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
// TODO move out from single stat
import { standardFieldDisplayOptions } from '../stat/types';
export interface SeriesOptions {
color?: string;
......@@ -22,7 +19,6 @@ export interface Options {
series: {
[alias: string]: SeriesOptions;
};
fieldOptions: FieldDisplayOptions;
tooltipOptions: GraphTooltipOptions;
}
......@@ -38,6 +34,5 @@ export const defaults: Options = {
placement: 'under',
},
series: {},
fieldOptions: { ...standardFieldDisplayOptions },
tooltipOptions: { mode: 'single' },
};
......@@ -20,7 +20,7 @@ export class PieChartPanel extends PureComponent<Props> {
const values = getFieldDisplayValues({
fieldConfig,
fieldOptions: options.fieldOptions,
reduceOptions: options.reduceOptions,
data: data.series,
theme: config.theme,
replaceVariables: replaceVariables,
......
......@@ -6,7 +6,7 @@ import {
FieldPropertiesEditor,
LegacyValueMappingsEditor,
} from '@grafana/ui';
import { PanelEditorProps, FieldDisplayOptions, ValueMapping, FieldConfig } from '@grafana/data';
import { PanelEditorProps, ReduceDataOptions, ValueMapping, FieldConfig } from '@grafana/data';
import { PieChartOptionsBox } from './PieChartOptionsBox';
import { PieChartOptions } from './types';
......@@ -23,10 +23,10 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
});
};
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
onDisplayOptionsChanged = (fieldOptions: ReduceDataOptions) =>
this.props.onOptionsChange({
...this.props.options,
fieldOptions,
reduceOptions: fieldOptions,
});
onDefaultsChange = (field: FieldConfig) => {
......@@ -38,7 +38,7 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
render() {
const { onOptionsChange, options, data, fieldConfig, onFieldConfigChange } = this.props;
const { fieldOptions } = options;
const { reduceOptions: fieldOptions } = options;
const { defaults } = fieldConfig;
return (
......
import { PieChartType, SingleStatBaseOptions } from '@grafana/ui';
import { standardFieldDisplayOptions } from '../stat/types';
import { ReducerID, VizOrientation } from '@grafana/data';
import { commonValueOptionDefaults } from '../stat/types';
import { VizOrientation } from '@grafana/data';
export interface PieChartOptions extends SingleStatBaseOptions {
pieType: PieChartType;
......@@ -11,8 +11,5 @@ export const defaults: PieChartOptions = {
pieType: PieChartType.PIE,
strokeWidth: 1,
orientation: VizOrientation.Auto,
fieldOptions: {
...standardFieldDisplayOptions,
calcs: [ReducerID.last],
},
reduceOptions: commonValueOptionDefaults,
};
......@@ -42,7 +42,7 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
yMax: value.field.max,
};
const calc = options.fieldOptions.calcs[0];
const calc = options.reduceOptions.calcs[0];
if (calc === ReducerID.last) {
sparkline.highlightIndex = sparkline.data.length - 1;
}
......@@ -76,7 +76,7 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
return getFieldDisplayValues({
fieldConfig,
fieldOptions: options.fieldOptions,
reduceOptions: options.reduceOptions,
replaceVariables,
theme: config.theme,
data: data.series,
......
......@@ -15,7 +15,7 @@ import {
import {
PanelEditorProps,
FieldDisplayOptions,
ReduceDataOptions,
FieldConfig,
ValueMapping,
ThresholdsConfig,
......@@ -53,10 +53,10 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
});
};
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
onDisplayOptionsChanged = (fieldOptions: ReduceDataOptions) =>
this.props.onOptionsChange({
...this.props.options,
fieldOptions,
reduceOptions: fieldOptions,
});
onColorModeChanged = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, colorMode: value });
......@@ -84,21 +84,25 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
render() {
const { options, fieldConfig } = this.props;
const { fieldOptions } = options;
const { reduceOptions: valueOptions } = options;
const { defaults } = fieldConfig;
const suggestions = fieldOptions.values
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={fieldOptions} labelWidth={8} />
<FieldDisplayEditor onChange={this.onDisplayOptionsChanged} value={valueOptions} labelWidth={8} />
<div className="form-field">
<FormLabel width={8}>Orientation</FormLabel>
<Select
......@@ -140,36 +144,28 @@ export class StatPanelEditor extends PureComponent<PanelEditorProps<StatPanelOpt
/>
</div>
</PanelOptionsGroup>
<>
{!useNewEditor && (
<>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
onChange={this.onDefaultsChange}
value={defaults}
showTitle={true}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</>
)}
</>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
onChange={this.onDefaultsChange}
value={defaults}
showTitle={true}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</PanelOptionsGrid>
{!useNewEditor && (
<>
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
)}
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}}
......
import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from '@grafana/ui';
import { PanelPlugin } from '@grafana/data';
import { StatPanelOptions, defaults, standardFieldConfig } from './types';
import { StatPanelOptions, defaults, standardFieldConfig, addStandardDataReduceOptions } from './types';
import { StatPanel } from './StatPanel';
import { StatPanelEditor } from './StatPanelEditor';
......@@ -8,6 +8,56 @@ export const plugin = new PanelPlugin<StatPanelOptions>(StatPanel)
.setDefaults(defaults)
.setFieldConfigDefaults(standardFieldConfig)
.setEditor(StatPanelEditor)
.setPanelOptions(builder => {
addStandardDataReduceOptions(builder);
builder
.addRadio({
id: 'orientation',
name: 'Orientation',
description: 'Stacking direction for multiple bars',
settings: {
options: [
{ value: 'auto', label: 'Auto' },
{ value: 'horizontal', label: 'Horizontal' },
{ value: 'vertical', label: 'Vertical' },
],
},
})
.addRadio({
id: 'colorMode',
name: 'Color mode',
description: 'Color either the value or the background',
settings: {
options: [
{ value: 'value', label: 'Value' },
{ value: 'background', label: 'Background' },
],
},
})
.addRadio({
id: 'graphMode',
name: 'Graph mode',
description: 'Stat panel graph / sparkline mode',
settings: {
options: [
{ value: 'none', label: 'None' },
{ value: 'area', label: 'Area' },
],
},
})
.addRadio({
id: 'justifyMode',
name: 'Justify mode',
description: 'Value & title posititioning',
settings: {
options: [
{ value: 'auto', label: 'Auto' },
{ value: 'center', label: 'Center' },
],
},
});
})
.setNoPadding()
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
.setMigrationHandler(sharedSingleStatMigrationHandler);
......@@ -2,10 +2,11 @@ import { SingleStatBaseOptions, BigValueColorMode, BigValueGraphMode, BigValueJu
import {
VizOrientation,
ReducerID,
FieldDisplayOptions,
ReduceDataOptions,
SelectableValue,
FieldConfigSource,
ThresholdsMode,
standardEditorsRegistry,
} from '@grafana/data';
import { PanelOptionsEditorBuilder } from '@grafana/data/src/utils/OptionsUIBuilders';
......@@ -31,7 +32,7 @@ export const justifyModes: Array<SelectableValue<BigValueJustifyMode>> = [
{ value: BigValueJustifyMode.Center, label: 'Center' },
];
export const standardFieldDisplayOptions: FieldDisplayOptions = {
export const commonValueOptionDefaults: ReduceDataOptions = {
values: false,
calcs: [ReducerID.mean],
};
......@@ -50,9 +51,9 @@ export const standardFieldConfig: FieldConfigSource = {
overrides: [],
};
export function addStandardSingleValueOptions(builder: PanelOptionsEditorBuilder) {
export function addStandardDataReduceOptions(builder: PanelOptionsEditorBuilder) {
builder.addRadio({
id: 'values',
id: 'reduceOptions.values',
name: 'Show',
description: 'Calculate a single value per colum or series or show each row',
settings: {
......@@ -62,12 +63,44 @@ export function addStandardSingleValueOptions(builder: PanelOptionsEditorBuilder
],
},
});
builder.addNumberInput({
id: 'reduceOptions.limit',
name: 'Limit',
description: 'Max number of rows to display',
settings: {
placeholder: '5000',
integer: true,
min: 1,
max: 5000,
},
});
builder.addCustomEditor({
id: 'reduceOptions.calcs',
name: 'Value',
description: 'Choose a reducer function / calculation',
editor: standardEditorsRegistry.get('stats-picker').editor as any,
});
builder.addRadio({
id: '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' },
],
},
});
}
export const defaults: StatPanelOptions = {
graphMode: BigValueGraphMode.Area,
colorMode: BigValueColorMode.Value,
justifyMode: BigValueJustifyMode.Auto,
fieldOptions: standardFieldDisplayOptions,
reduceOptions: commonValueOptionDefaults,
orientation: VizOrientation.Auto,
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment