Commit 08133ea3 by Torkel Ödegaard Committed by GitHub

BarGauge: First bar gauge panel option & some refactoring (#23128)

* First bar gauge panel option

* Update doc comments

* Minor changes

* progress

* Fixing typing errors

* Minor type updates

* Fix that TS!

* Bring satisfaction to that beast called typescript

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
parent 7e094ac5
......@@ -15,10 +15,10 @@ export interface NumberFieldConfigSettings {
export const numberOverrideProcessor = (
value: any,
context: FieldOverrideContext,
settings: NumberFieldConfigSettings
settings?: NumberFieldConfigSettings
) => {
const v = parseFloat(`${value}`);
if (settings.max && v > settings.max) {
if (settings && settings.max && v > settings.max) {
// ????
}
return v;
......@@ -29,7 +29,7 @@ export interface DataLinksFieldConfigSettings {}
export const dataLinksOverrideProcessor = (
value: any,
_context: FieldOverrideContext,
_settings: DataLinksFieldConfigSettings
_settings?: DataLinksFieldConfigSettings
) => {
return value as DataLink[];
};
......@@ -39,7 +39,7 @@ export interface ValueMappingFieldConfigSettings {}
export const valueMappingsOverrideProcessor = (
value: any,
_context: FieldOverrideContext,
_settings: ValueMappingFieldConfigSettings
_settings?: ValueMappingFieldConfigSettings
) => {
return value as ValueMapping[]; // !!!! likely not !!!!
};
......@@ -51,7 +51,7 @@ export interface SelectFieldConfigSettings<T> {
export const selectOverrideProcessor = (
value: any,
_context: FieldOverrideContext,
_settings: SelectFieldConfigSettings<any>
_settings?: SelectFieldConfigSettings<any>
) => {
return value;
};
......@@ -65,9 +65,9 @@ export interface StringFieldConfigSettings {
export const stringOverrideProcessor = (
value: any,
context: FieldOverrideContext,
settings: StringFieldConfigSettings
settings?: StringFieldConfigSettings
) => {
if (settings.expandTemplateVars && context.replaceVariables) {
if (settings && settings.expandTemplateVars && context.replaceVariables) {
return context.replaceVariables(value, context.field!.config.scopedVars);
}
return `${value}`;
......@@ -80,7 +80,7 @@ export interface ThresholdsFieldConfigSettings {
export const thresholdsOverrideProcessor = (
value: any,
_context: FieldOverrideContext,
_settings: ThresholdsFieldConfigSettings
_settings?: ThresholdsFieldConfigSettings
) => {
return value as ThresholdsConfig; // !!!! likely not !!!!
};
......@@ -90,7 +90,7 @@ export interface UnitFieldConfigSettings {}
export const unitOverrideProcessor = (
value: boolean,
_context: FieldOverrideContext,
_settings: UnitFieldConfigSettings
_settings?: UnitFieldConfigSettings
) => {
return value;
};
......@@ -98,7 +98,7 @@ export const unitOverrideProcessor = (
export const booleanOverrideProcessor = (
value: boolean,
_context: FieldOverrideContext,
_settings: ThresholdsFieldConfigSettings
_settings?: ThresholdsFieldConfigSettings
) => {
return value; // !!!! likely not !!!!
};
......
......@@ -9,7 +9,7 @@ describe('PanelPlugin', () => {
return <div>Panel</div>;
});
panel.setCustomFieldConfigEditor(builder => {
panel.setCustomFieldOptions(builder => {
builder.addCustomEditor({
id: 'custom',
name: 'Custom',
......@@ -31,7 +31,7 @@ describe('PanelPlugin', () => {
return <div>Panel</div>;
});
panel.setOptionsEditor(builder => {
panel.setPanelOptions(builder => {
builder.addCustomEditor({
id: 'option',
name: 'Option editor',
......
......@@ -110,7 +110,7 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
* interface ShapePanelOptions {}
*
* export const plugin = new PanelPlugin<ShapePanelOptions>(ShapePanel)
* .setCustomFieldConfigEditor(builder => {
* .setCustomFieldOptions(builder => {
* builder
* .addNumberInput({
* id: 'shapeBorderWidth',
......@@ -134,7 +134,7 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
*
* @public
**/
setCustomFieldConfigEditor(builder: (builder: FieldConfigEditorBuilder) => void) {
setCustomFieldOptions(builder: (builder: FieldConfigEditorBuilder) => void) {
// builder is applied lazily when custom field configs are accessed
this.registerCustomFieldConfigs = builder;
return this;
......@@ -151,7 +151,7 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
* interface ShapePanelOptions {}
*
* export const plugin = new PanelPlugin<ShapePanelOptions>(ShapePanel)
* .setOptionsEditor(builder => {
* .setPanelOptions(builder => {
* builder
* .addSelect({
* id: 'shape',
......@@ -170,7 +170,7 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
*
* @public
**/
setOptionsEditor(builder: (builder: PanelOptionsEditorBuilder) => void) {
setPanelOptions(builder: (builder: PanelOptionsEditorBuilder) => void) {
// builder is applied lazily when options UI is created
this.registerOptionEditors = builder;
return this;
......
......@@ -5,44 +5,46 @@ import { NumberFieldConfigSettings, SelectFieldConfigSettings, StringFieldConfig
/**
* Option editor registry item
*/
interface OptionsEditorItem<TSettings, TEditorProps> extends RegistryItem {
export interface OptionsEditorItem<TSettings, TEditorProps> extends RegistryItem {
editor: ComponentType<TEditorProps>;
settings?: TSettings;
editor?: ComponentType<TEditorProps>;
}
/**
* Configuration of option editor registry item
*/
type OptionEditorConfig<TSettings, TEditorProps> = Pick<
OptionsEditorItem<TSettings, TEditorProps>,
'id' | 'name' | 'description' | 'editor' | 'settings'
>;
interface OptionEditorConfig<TSettings> {
id: string;
name: string;
description: string;
settings?: TSettings;
}
/**
* Describes an API for option editors UI builder
*/
export interface OptionsUIRegistryBuilderAPI<TEditorProps, T extends OptionsEditorItem<any, TEditorProps>> {
addNumberInput?<TSettings extends NumberFieldConfigSettings = NumberFieldConfigSettings>(
config: OptionEditorConfig<TSettings, TEditorProps>
config: OptionEditorConfig<TSettings>
): this;
addTextInput?<TSettings extends StringFieldConfigSettings = StringFieldConfigSettings>(
config: OptionEditorConfig<TSettings, TEditorProps>
config: OptionEditorConfig<TSettings>
): this;
addSelect?<TOption, TSettings extends SelectFieldConfigSettings<TOption>>(
config: OptionEditorConfig<TSettings, TEditorProps>
config: OptionEditorConfig<TSettings>
): this;
addRadio?<TOption, TSettings extends SelectFieldConfigSettings<TOption> = SelectFieldConfigSettings<TOption>>(
config: OptionEditorConfig<TSettings, TEditorProps>
config: OptionEditorConfig<TSettings>
): this;
addBooleanSwitch?<TSettings = any>(config: OptionEditorConfig<TSettings, TEditorProps>): this;
addBooleanSwitch?<TSettings = any>(config: OptionEditorConfig<TSettings>): this;
addUnitPicker?<TSettings = any>(config: OptionEditorConfig<TSettings, TEditorProps>): this;
addUnitPicker?<TSettings = any>(config: OptionEditorConfig<TSettings>): this;
addColorPicker?<TSettings = any>(config: OptionEditorConfig<TSettings, TEditorProps>): this;
addColorPicker?<TSettings = any>(config: OptionEditorConfig<TSettings>): this;
/**
* Enables custom editor definition
......
......@@ -9,9 +9,10 @@ import {
GrafanaTheme,
TimeZone,
} from '../types';
import { Registry, RegistryItem } from '../utils';
import { Registry } from '../utils';
import { InterpolateFunction } from './panel';
import { StandardEditorProps } from '../field';
import { OptionsEditorItem } from './OptionsUIRegistryBuilder';
export interface DynamicConfigValue {
prop: string;
......@@ -53,24 +54,21 @@ export interface FieldOverrideEditorProps<TValue, TSettings> extends Omit<Standa
context: FieldOverrideContext;
}
export interface FieldConfigEditorConfig<TSettings = any, TValue = any>
extends Omit<Pick<FieldPropertyEditorItem<TValue, TSettings>, 'id' | 'description' | 'name'>, 'settings'> {
export interface FieldConfigEditorConfig<TSettings = any> {
id: string;
name: string;
description: string;
settings?: TSettings;
shouldApply?: (field: Field) => boolean;
}
export interface FieldPropertyEditorItem<TValue = any, TSettings = any> extends RegistryItem {
// An editor the creates the well typed value
editor: ComponentType<FieldConfigEditorProps<TValue, TSettings>>;
export interface FieldPropertyEditorItem<TValue = any, TSettings extends {} = any>
extends OptionsEditorItem<TSettings, FieldConfigEditorProps<TValue, TSettings>> {
// An editor that can be filled in with context info (template variables etc)
override: ComponentType<FieldOverrideEditorProps<TValue, TSettings>>;
// Convert the override value to a well typed value
process: (value: any, context: FieldOverrideContext, settings: TSettings) => TValue;
// Configuration options for the particular property
settings: TSettings;
process: (value: any, context: FieldOverrideContext, settings?: TSettings) => TValue;
// Checks if field should be processed
shouldApply: (field: Field) => boolean;
......
import { ComponentType } from 'react';
import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
import { PluginMeta } from './plugin';
import { ScopedVars } from './ScopedVars';
......@@ -6,8 +5,9 @@ import { LoadingState } from './data';
import { DataFrame } from './dataFrame';
import { AbsoluteTimeRange, TimeRange, TimeZone } from './time';
import { FieldConfigSource } from './fieldOverrides';
import { Registry, RegistryItem } from '../utils';
import { Registry } from '../utils';
import { StandardEditorProps } from '../field';
import { OptionsEditorItem } from './OptionsUIRegistryBuilder';
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
......@@ -115,14 +115,16 @@ export type PanelOptionEditorsRegistry = Registry<PanelOptionsEditorItem>;
export interface PanelOptionsEditorProps<TValue> extends StandardEditorProps<TValue> {}
export interface PanelOptionsEditorItem<TValue = any, TSettings = any> extends RegistryItem {
editor: ComponentType<PanelOptionsEditorProps<TValue>>;
export interface PanelOptionsEditorItem<TValue = any, TSettings = any>
extends OptionsEditorItem<TSettings, PanelOptionsEditorProps<TValue>> {}
export interface PanelOptionsEditorConfig<TSettings = any> {
id: string;
name: string;
description: string;
settings?: TSettings;
}
export interface PanelOptionsEditorConfig<TSettings = any, TValue = any>
extends Pick<PanelOptionsEditorItem<TValue, TSettings>, 'id' | 'description' | 'name' | 'settings'> {}
export interface PanelMenuItem {
type?: 'submenu' | 'divider';
text?: string;
......
......@@ -16,14 +16,14 @@ export const NumberValueEditor: React.FC<FieldConfigEditorProps<number, NumberFi
return (
<Forms.Input
value={isNaN(value) ? '' : value}
min={settings.min}
max={settings.max}
min={settings?.min}
max={settings?.max}
type="number"
step={settings.step}
placeholder={settings.placeholder}
step={settings?.step}
placeholder={settings?.placeholder}
onChange={e => {
onChange(
settings.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value)
settings?.integer ? toIntegerOrUndefined(e.currentTarget.value) : toFloatOrUndefined(e.currentTarget.value)
);
}}
/>
......
......@@ -7,5 +7,5 @@ export function SelectValueEditor<T>({
onChange,
item,
}: FieldConfigEditorProps<T, SelectFieldConfigSettings<T>>) {
return <Forms.Select<T> defaultValue={value} onChange={e => onChange(e.value)} options={item.settings.options} />;
return <Forms.Select<T> defaultValue={value} onChange={e => onChange(e.value)} options={item.settings?.options} />;
}
......@@ -13,7 +13,7 @@ interface Props<V, D> {
/**
* Render a single value
*/
renderValue: (value: V, width: number, height: number, dims: D) => JSX.Element;
renderValue: (props: VizRepeaterRenderValueProps<V, D>) => JSX.Element;
height: number;
width: number;
source: any; // If this changes, new values will be requested
......@@ -23,6 +23,14 @@ interface Props<V, D> {
itemSpacing?: number;
}
export interface VizRepeaterRenderValueProps<V, D = {}> {
value: V;
width: number;
height: number;
orientation: VizOrientation;
alignmentFactors: D;
}
interface DefaultProps {
itemSpacing: number;
}
......@@ -99,13 +107,14 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
itemStyles.width = `${vizWidth}px`;
itemStyles.height = `${vizHeight}px`;
const dims = getAlignmentFactors ? getAlignmentFactors(values, vizWidth, vizHeight) : ({} as D);
const alignmentFactors = getAlignmentFactors ? getAlignmentFactors(values, vizWidth, vizHeight) : ({} as D);
return (
<div style={repeaterStyle}>
{values.map((value, index) => {
return (
<div key={index} style={getItemStylesForIndex(itemStyles, index, values.length)}>
{renderValue(value, vizWidth, vizHeight, dims)}
{renderValue({ value, width: vizWidth, height: vizHeight, alignmentFactors, orientation })}
</div>
);
})}
......
......@@ -78,7 +78,7 @@ export { GraphWithLegend } from './Graph/GraphWithLegend';
export { GraphContextMenu } from './Graph/GraphContextMenu';
export { BarGauge, BarGaugeDisplayMode } from './BarGauge/BarGauge';
export { GraphTooltipOptions } from './Graph/GraphTooltip/types';
export { VizRepeater } from './VizRepeater/VizRepeater';
export { VizRepeater, VizRepeaterRenderValueProps } from './VizRepeater/VizRepeater';
export {
LegendOptions,
......
......@@ -315,6 +315,7 @@ export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEdi
*/
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const handleColor = theme.colors.blueLight;
const paneSpaceing = theme.spacing.md;
const resizer = css`
font-style: italic;
......@@ -354,21 +355,21 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
`,
panelWrapper: css`
width: 100%;
padding-left: ${theme.spacing.sm};
padding-left: ${paneSpaceing};
height: 100%;
`,
resizerV: cx(
resizer,
css`
cursor: col-resize;
width: 8px;
width: ${paneSpaceing};
border-right-width: 1px;
`
),
resizerH: cx(
resizer,
css`
height: 8px;
height: ${paneSpaceing};
cursor: row-resize;
position: relative;
top: 49px;
......
......@@ -4,7 +4,7 @@ import React, { PureComponent } from 'react';
// Services & Utils
import { config } from 'app/core/config';
import { BarGauge, VizRepeater, DataLinksContextMenu } from '@grafana/ui';
import { BarGauge, VizRepeater, VizRepeaterRenderValueProps, DataLinksContextMenu } from '@grafana/ui';
import { BarGaugeOptions } from './types';
import {
getFieldDisplayValues,
......@@ -16,13 +16,9 @@ import {
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
renderValue = (
value: FieldDisplay,
width: number,
height: number,
alignmentFactors: DisplayValueAlignmentFactors
): JSX.Element => {
renderValue = (valueProps: VizRepeaterRenderValueProps<FieldDisplay, DisplayValueAlignmentFactors>): JSX.Element => {
const { options } = this.props;
const { value, alignmentFactors, orientation, width, height } = valueProps;
const { field, display, view, colIndex } = value;
return (
......@@ -33,7 +29,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
value={display}
width={width}
height={height}
orientation={options.orientation}
orientation={orientation}
field={field}
display={view?.getFieldDisplayProcessor(colIndex)}
theme={config.theme}
......
......@@ -95,6 +95,10 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
return (
<NewPanelEditorContext.Consumer>
{useNewEditor => {
if (useNewEditor) {
return null;
}
return (
<>
<PanelOptionsGrid>
......@@ -135,38 +139,28 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
)}
</>
</PanelOptionsGroup>
<>
{!useNewEditor && (
<>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
showTitle={true}
onChange={this.onDefaultsChange}
value={defaults}
/>
</PanelOptionsGroup>
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
showTitle={true}
onChange={this.onDefaultsChange}
value={defaults}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</>
)}
</>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
</PanelOptionsGrid>
{!useNewEditor && (
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
)}
<LegacyValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
{!useNewEditor && (
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
)}
<PanelOptionsGroup title="Data links">
<DataLinksEditor
value={defaults.links}
onChange={this.onDataLinksChanged}
suggestions={suggestions}
maxLinks={10}
/>
</PanelOptionsGroup>
</>
);
}}
......
import { sharedSingleStatPanelChangedHandler } from '@grafana/ui';
import { PanelPlugin } from '@grafana/data';
import { BarGaugePanel } from './BarGaugePanel';
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
import { BarGaugeOptions, defaults } from './types';
import { standardFieldConfig } from '../stat/types';
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
import { barGaugePanelMigrationHandler } from './BarGaugeMigrations';
export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
.setDefaults(defaults)
.setFieldConfigDefaults(standardFieldConfig)
.setEditor(BarGaugePanelEditor)
.setFieldConfigDefaults(standardFieldConfig)
.setPanelOptions(builder => {
/* addStandardSingleValueOptions(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' },
],
},
});
})
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
.setMigrationHandler(barGaugePanelMigrationHandler);
......@@ -5,17 +5,17 @@ import React, { PureComponent } from 'react';
import { config } from 'app/core/config';
// Components
import { Gauge, DataLinksContextMenu } from '@grafana/ui';
import { Gauge, DataLinksContextMenu, VizRepeater, VizRepeaterRenderValueProps } from '@grafana/ui';
// Types
import { GaugeOptions } from './types';
import { VizRepeater } from '@grafana/ui';
import { FieldDisplay, getFieldDisplayValues, VizOrientation, PanelProps } from '@grafana/data';
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
renderValue = (value: FieldDisplay, width: number, height: number): JSX.Element => {
renderValue = (valueProps: VizRepeaterRenderValueProps<FieldDisplay>): JSX.Element => {
const { options } = this.props;
const { value, width, height } = valueProps;
const { field, display } = value;
return (
......
......@@ -6,7 +6,14 @@ import { config } from 'app/core/config';
// Types
import { StatPanelOptions } from './types';
import { VizRepeater, BigValue, DataLinksContextMenu, BigValueSparkline, BigValueGraphMode } from '@grafana/ui';
import {
VizRepeater,
VizRepeaterRenderValueProps,
BigValue,
DataLinksContextMenu,
BigValueSparkline,
BigValueGraphMode,
} from '@grafana/ui';
import {
PanelProps,
......@@ -21,13 +28,9 @@ import {
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
renderValue = (
value: FieldDisplay,
width: number,
height: number,
alignmentFactors: DisplayValueAlignmentFactors
): JSX.Element => {
renderValue = (valueProps: VizRepeaterRenderValueProps<FieldDisplay, DisplayValueAlignmentFactors>): JSX.Element => {
const { timeRange, options } = this.props;
const { value, alignmentFactors, width, height } = valueProps;
let sparkline: BigValueSparkline | undefined;
if (value.sparkline) {
......
......@@ -7,6 +7,7 @@ import {
FieldConfigSource,
ThresholdsMode,
} from '@grafana/data';
import { PanelOptionsEditorBuilder } from '@grafana/data/src/utils/OptionsUIBuilders';
// Structure copied from angular
export interface StatPanelOptions extends SingleStatBaseOptions {
......@@ -49,6 +50,20 @@ export const standardFieldConfig: FieldConfigSource = {
overrides: [],
};
export function addStandardSingleValueOptions(builder: PanelOptionsEditorBuilder) {
builder.addRadio({
id: 'values',
name: 'Show',
description: 'Calculate a single value per colum or series or show each row',
settings: {
options: [
{ value: false, label: 'Calculate' },
{ value: true, label: 'All values' },
],
},
});
}
export const defaults: StatPanelOptions = {
graphMode: BigValueGraphMode.Area,
colorMode: BigValueColorMode.Value,
......
......@@ -8,20 +8,18 @@ import { Options } from './types';
interface Props extends PanelProps<Options> {}
const paddingBottom = 16;
export class TablePanel extends Component<Props> {
constructor(props: Props) {
super(props);
}
render() {
const { data, height, width } = this.props;
const { data, height, width, options } = this.props;
if (data.series.length < 1) {
return <div>No Table Data...</div>;
}
return <Table height={height - paddingBottom} width={width} data={data.series[0]} />;
return <Table height={height} width={width} data={data.series[0]} noHeader={!options.showHeader} />;
}
}
......@@ -4,7 +4,7 @@ import { Options, defaults } from './types';
export const plugin = new PanelPlugin<Options>(TablePanel)
.setDefaults(defaults)
.setCustomFieldConfigEditor(builder => {
.setCustomFieldOptions(builder => {
builder
.addNumberInput({
id: 'width',
......@@ -20,7 +20,6 @@ export const plugin = new PanelPlugin<Options>(TablePanel)
id: 'displayMode',
name: 'Cell display mode',
description: 'Color value, background, show as gauge, etc',
settings: {
options: [
{ value: 'auto', label: 'Auto' },
......@@ -31,7 +30,7 @@ export const plugin = new PanelPlugin<Options>(TablePanel)
},
});
})
.setOptionsEditor(builder => {
.setPanelOptions(builder => {
builder.addBooleanSwitch({
id: 'showHeader',
name: 'Show header',
......
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