From c8b210250036aebaab4f641e608cac38cc6ddb41 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 28 Mar 2019 06:57:49 -0700 Subject: [PATCH] Feat: Singlestat panel react progress & refactorings (#16039) * big value component * big value component * editor for font and sparkline * less logging * remove sparkline from storybook * add display value link wrapper * follow tooltip * follow tooltip * merge master * Just minor refactoring * use series after last merge * Refactoring: moving shared singlestat stuff to grafana-ui * Refactor: Moved final getSingleStatDisplayValues func --- .../components/BigValue/BigValue.story.tsx | 37 ++++ .../src/components/BigValue/BigValue.test.tsx | 38 ++++ .../src/components/BigValue/BigValue.tsx | 134 +++++++++++++ .../src/components/BigValue/_BigValue.scss | 15 ++ .../SingleStatValueEditor.tsx | 30 ++- .../src/components/SingleStatShared/shared.ts | 122 ++++++++++++ packages/grafana-ui/src/components/index.scss | 1 + packages/grafana-ui/src/components/index.ts | 2 + packages/grafana-ui/src/types/displayValue.ts | 1 + .../plugins/panel/bargauge/BarGaugePanel.tsx | 12 +- .../panel/bargauge/BarGaugePanelEditor.tsx | 12 +- public/app/plugins/panel/bargauge/module.tsx | 5 +- public/app/plugins/panel/bargauge/types.ts | 3 +- public/app/plugins/panel/gauge/GaugePanel.tsx | 12 +- .../plugins/panel/gauge/GaugePanelEditor.tsx | 4 +- public/app/plugins/panel/gauge/module.tsx | 8 +- public/app/plugins/panel/gauge/types.ts | 3 +- .../plugins/panel/piechart/PieChartPanel.tsx | 14 +- .../panel/piechart/PieChartPanelEditor.tsx | 11 +- public/app/plugins/panel/piechart/types.ts | 3 +- .../panel/singlestat2/ColoringEditor.tsx | 68 +++++++ .../panel/singlestat2/FontSizeEditor.tsx | 67 +++++++ .../panel/singlestat2/SingleStatEditor.tsx | 18 +- .../panel/singlestat2/SingleStatPanel.tsx | 183 +++++++++++------- .../panel/singlestat2/SparklineEditor.tsx | 33 ++++ .../app/plugins/panel/singlestat2/module.tsx | 43 +--- public/app/plugins/panel/singlestat2/types.ts | 39 ++-- 27 files changed, 751 insertions(+), 167 deletions(-) create mode 100644 packages/grafana-ui/src/components/BigValue/BigValue.story.tsx create mode 100644 packages/grafana-ui/src/components/BigValue/BigValue.test.tsx create mode 100644 packages/grafana-ui/src/components/BigValue/BigValue.tsx create mode 100644 packages/grafana-ui/src/components/BigValue/_BigValue.scss rename {public/app/plugins/panel/singlestat2 => packages/grafana-ui/src/components/SingleStatShared}/SingleStatValueEditor.tsx (67%) create mode 100644 packages/grafana-ui/src/components/SingleStatShared/shared.ts create mode 100644 public/app/plugins/panel/singlestat2/ColoringEditor.tsx create mode 100644 public/app/plugins/panel/singlestat2/FontSizeEditor.tsx create mode 100644 public/app/plugins/panel/singlestat2/SparklineEditor.tsx diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx new file mode 100644 index 00000000000..8d206c9d73e --- /dev/null +++ b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx @@ -0,0 +1,37 @@ +import { storiesOf } from '@storybook/react'; +import { number, text } from '@storybook/addon-knobs'; +import { BigValue } from './BigValue'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; + +const getKnobs = () => { + return { + value: text('value', 'Hello'), + valueFontSize: number('valueFontSize', 120), + prefix: text('prefix', ''), + }; +}; + +const BigValueStories = storiesOf('UI/BigValue', module); + +BigValueStories.addDecorator(withCenteredStory); + +BigValueStories.add('Singlestat viz', () => { + const { value, prefix, valueFontSize } = getKnobs(); + + return renderComponentWithTheme(BigValue, { + width: 300, + height: 250, + value: { + text: value, + numeric: NaN, + fontSize: valueFontSize + '%', + }, + prefix: prefix + ? { + text: prefix, + numeric: NaN, + } + : null, + }); +}); diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx new file mode 100644 index 00000000000..9c22b42e61d --- /dev/null +++ b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { BigValue, Props } from './BigValue'; +import { getTheme } from '../../themes/index'; + +jest.mock('jquery', () => ({ + plot: jest.fn(), +})); + +const setup = (propOverrides?: object) => { + const props: Props = { + height: 300, + width: 300, + value: { + text: '25', + numeric: 25, + }, + theme: getTheme(), + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + const instance = wrapper.instance() as BigValue; + + return { + instance, + wrapper, + }; +}; + +describe('Render BarGauge with basic options', () => { + it('should render', () => { + const { wrapper } = setup(); + expect(wrapper).toBeDefined(); + // expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.tsx new file mode 100644 index 00000000000..f232beca108 --- /dev/null +++ b/packages/grafana-ui/src/components/BigValue/BigValue.tsx @@ -0,0 +1,134 @@ +// Library +import React, { PureComponent, ReactNode, CSSProperties } from 'react'; +import $ from 'jquery'; + +// Utils +import { getColorFromHexRgbOrName } from '../../utils'; + +// Types +import { Themeable, DisplayValue } from '../../types'; + +export interface BigValueSparkline { + data: any[][]; // [[number,number]] + minX: number; + maxX: number; + full: boolean; // full height + fillColor: string; + lineColor: string; +} + +export interface Props extends Themeable { + height: number; + width: number; + value: DisplayValue; + prefix?: DisplayValue; + suffix?: DisplayValue; + sparkline?: BigValueSparkline; + backgroundColor?: string; +} + +/* + * This visualization is still in POC state, needed more tests & better structure + */ +export class BigValue extends PureComponent { + canvasElement: any; + + componentDidMount() { + this.draw(); + } + + componentDidUpdate() { + this.draw(); + } + + draw() { + const { sparkline, theme } = this.props; + + if (sparkline && this.canvasElement) { + const { data, minX, maxX, fillColor, lineColor } = sparkline; + + const options = { + legend: { show: false }, + series: { + lines: { + show: true, + fill: 1, + zero: false, + lineWidth: 1, + fillColor: getColorFromHexRgbOrName(fillColor, theme.type), + }, + }, + yaxes: { show: false }, + xaxis: { + show: false, + min: minX, + max: maxX, + }, + grid: { hoverable: false, show: false }, + }; + + const plotSeries = { + data, + color: getColorFromHexRgbOrName(lineColor, theme.type), + }; + + try { + $.plot(this.canvasElement, [plotSeries], options); + } catch (err) { + console.log('sparkline rendering error', err, options); + } + } + } + + renderText = (value?: DisplayValue, padding?: string): ReactNode => { + if (!value || !value.text) { + return null; + } + const css: CSSProperties = {}; + if (padding) { + css.padding = padding; + } + if (value.color) { + css.color = value.color; + } + if (value.fontSize) { + css.fontSize = value.fontSize; + } + + return {value.text}; + }; + + render() { + const { height, width, value, prefix, suffix, sparkline, backgroundColor } = this.props; + + const plotCss: CSSProperties = {}; + plotCss.position = 'absolute'; + + if (sparkline) { + if (sparkline.full) { + plotCss.bottom = '5px'; + plotCss.left = '-5px'; + plotCss.width = width - 10 + 'px'; + const dynamicHeightMargin = height <= 100 ? 5 : Math.round(height / 100) * 15 + 5; + plotCss.height = height - dynamicHeightMargin + 'px'; + } else { + plotCss.bottom = '0px'; + plotCss.left = '-5px'; + plotCss.width = width - 10 + 'px'; + plotCss.height = Math.floor(height * 0.25) + 'px'; + } + } + + return ( +
+ + {this.renderText(prefix, '0px 2px 0px 0px')} + {this.renderText(value)} + {this.renderText(suffix)} + + + {sparkline &&
(this.canvasElement = element)} />} +
+ ); + } +} diff --git a/packages/grafana-ui/src/components/BigValue/_BigValue.scss b/packages/grafana-ui/src/components/BigValue/_BigValue.scss new file mode 100644 index 00000000000..13603daa457 --- /dev/null +++ b/packages/grafana-ui/src/components/BigValue/_BigValue.scss @@ -0,0 +1,15 @@ +.big-value { + position: relative; + display: table; +} + +.big-value__value { + line-height: 1; + display: table-cell; + vertical-align: middle; + text-align: center; + position: relative; + z-index: 1; + font-size: 3em; + font-weight: $font-weight-semi-bold; +} diff --git a/public/app/plugins/panel/singlestat2/SingleStatValueEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/SingleStatValueEditor.tsx similarity index 67% rename from public/app/plugins/panel/singlestat2/SingleStatValueEditor.tsx rename to packages/grafana-ui/src/components/SingleStatShared/SingleStatValueEditor.tsx index 715c8d1f524..b08b7ce383d 100644 --- a/public/app/plugins/panel/singlestat2/SingleStatValueEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatValueEditor.tsx @@ -1,11 +1,19 @@ // Libraries -import React, { PureComponent } from 'react'; +import React, { PureComponent, ChangeEvent } from 'react'; // Components -import { FormField, FormLabel, PanelOptionsGroup, StatsPicker, UnitPicker, StatID } from '@grafana/ui'; +import { + FormField, + FormLabel, + PanelOptionsGroup, + StatsPicker, + UnitPicker, + StatID, + SelectOptionItem, +} from '@grafana/ui'; // Types -import { SingleStatValueOptions } from './types'; +import { SingleStatValueOptions } from './shared'; const labelWidth = 6; @@ -15,15 +23,15 @@ export interface Props { } export class SingleStatValueEditor extends PureComponent { - onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value }); + onUnitChange = (unit: SelectOptionItem) => this.props.onChange({ ...this.props.options, unit: unit.value }); - onStatsChange = stats => { + onStatsChange = (stats: string[]) => { const stat = stats[0] || StatID.mean; this.props.onChange({ ...this.props.options, stat }); }; - onDecimalChange = event => { - if (!isNaN(event.target.value)) { + onDecimalChange = (event: ChangeEvent) => { + if (!isNaN(parseInt(event.target.value, 10))) { this.props.onChange({ ...this.props.options, decimals: parseInt(event.target.value, 10), @@ -36,14 +44,16 @@ export class SingleStatValueEditor extends PureComponent { } }; - onPrefixChange = event => this.props.onChange({ ...this.props.options, prefix: event.target.value }); - onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value }); + onPrefixChange = (event: ChangeEvent) => + this.props.onChange({ ...this.props.options, prefix: event.target.value }); + onSuffixChange = (event: ChangeEvent) => + this.props.onChange({ ...this.props.options, suffix: event.target.value }); render() { const { stat, unit, decimals, prefix, suffix } = this.props.options; let decimalsString = ''; - if (Number.isFinite(decimals)) { + if (decimals !== null && decimals !== undefined && Number.isFinite(decimals as number)) { decimalsString = decimals.toString(); } diff --git a/packages/grafana-ui/src/components/SingleStatShared/shared.ts b/packages/grafana-ui/src/components/SingleStatShared/shared.ts new file mode 100644 index 00000000000..0fdf69087fe --- /dev/null +++ b/packages/grafana-ui/src/components/SingleStatShared/shared.ts @@ -0,0 +1,122 @@ +import cloneDeep from 'lodash/cloneDeep'; +import { + ValueMapping, + Threshold, + VizOrientation, + PanelModel, + DisplayValue, + FieldType, + NullValueMode, + GrafanaTheme, + SeriesData, + InterpolateFunction, +} from '../../types'; +import { getStatsCalculators, calculateStats } from '../../utils/statsCalculator'; +import { getDisplayProcessor } from '../../utils/displayValue'; +export { SingleStatValueEditor } from './SingleStatValueEditor'; + +export interface SingleStatBaseOptions { + valueMappings: ValueMapping[]; + thresholds: Threshold[]; + valueOptions: SingleStatValueOptions; + orientation: VizOrientation; +} + +export interface SingleStatValueOptions { + unit: string; + suffix: string; + stat: string; + prefix: string; + decimals?: number | null; +} + +export interface GetSingleStatDisplayValueOptions { + data: SeriesData[]; + theme: GrafanaTheme; + valueMappings: ValueMapping[]; + thresholds: Threshold[]; + valueOptions: SingleStatValueOptions; + replaceVariables: InterpolateFunction; +} + +export const getSingleStatDisplayValues = (options: GetSingleStatDisplayValueOptions): DisplayValue[] => { + const { data, replaceVariables, valueOptions } = options; + const { unit, decimals, stat } = valueOptions; + + const display = getDisplayProcessor({ + unit, + decimals, + mappings: options.valueMappings, + thresholds: options.thresholds, + prefix: replaceVariables(valueOptions.prefix), + suffix: replaceVariables(valueOptions.suffix), + theme: options.theme, + }); + + const values: DisplayValue[] = []; + + for (const series of data) { + if (stat === 'name') { + values.push(display(series.name)); + } + + for (let i = 0; i < series.fields.length; i++) { + const column = series.fields[i]; + + // Show all fields that are not 'time' + if (column.type === FieldType.number) { + const stats = calculateStats({ + series, + fieldIndex: i, + stats: [stat], // The stats to calculate + nullValueMode: NullValueMode.Null, + }); + const displayValue = display(stats[stat]); + values.push(displayValue); + } + } + } + + if (values.length === 0) { + values.push({ + numeric: 0, + text: 'No data', + }); + } + + return values; +}; + +const optionsToKeep = ['valueOptions', 'stat', 'maxValue', 'maxValue', 'thresholds', 'valueMappings']; + +export const sharedSingleStatOptionsCheck = ( + options: Partial | any, + prevPluginId: string, + prevOptions: any +) => { + for (const k of optionsToKeep) { + if (prevOptions.hasOwnProperty(k)) { + options[k] = cloneDeep(prevOptions[k]); + } + } + return options; +}; + +export const sharedSingleStatMigrationCheck = (panel: PanelModel) => { + const options = panel.options; + + if (!options) { + // This happens on the first load or when migrating from angular + return {}; + } + + if (options.valueOptions) { + // 6.1 renamed some stats, This makes sure they are up to date + // avg -> mean, current -> last, total -> sum + const { valueOptions } = options; + if (valueOptions && valueOptions.stat) { + valueOptions.stat = getStatsCalculators([valueOptions.stat]).map(s => s.id)[0]; + } + } + return options; +}; diff --git a/packages/grafana-ui/src/components/index.scss b/packages/grafana-ui/src/components/index.scss index 91e5c88e33e..7db4206fa27 100644 --- a/packages/grafana-ui/src/components/index.scss +++ b/packages/grafana-ui/src/components/index.scss @@ -1,4 +1,5 @@ @import 'CustomScrollbar/CustomScrollbar'; +@import 'BigValue/BigValue'; @import 'DeleteButton/DeleteButton'; @import 'ThresholdsEditor/ThresholdsEditor'; @import 'Table/Table'; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 2cc86c83db2..273cc14159a 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -33,9 +33,11 @@ export { StatsPicker } from './StatsPicker/StatsPicker'; export { Input, InputStatus } from './Input/Input'; // Visualizations +export { BigValue } from './BigValue/BigValue'; export { Gauge } from './Gauge/Gauge'; export { Graph } from './Graph/Graph'; export { BarGauge } from './BarGauge/BarGauge'; export { VizRepeater } from './VizRepeater/VizRepeater'; +export * from './SingleStatShared/shared'; export { CallToActionCard } from './CallToActionCard/CallToActionCard'; diff --git a/packages/grafana-ui/src/types/displayValue.ts b/packages/grafana-ui/src/types/displayValue.ts index 65bf916b3f4..f1ca32c3c9c 100644 --- a/packages/grafana-ui/src/types/displayValue.ts +++ b/packages/grafana-ui/src/types/displayValue.ts @@ -3,6 +3,7 @@ export interface DisplayValue { numeric: number; // Use isNaN to check if it is a real number color?: string; // color based on configs or Threshold title?: string; + fontSize?: string; } export interface DecimalInfo { diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx index 2358b1ddab4..ceaced93547 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx @@ -2,12 +2,11 @@ import React, { PureComponent } from 'react'; // Services & Utils -import { DisplayValue, PanelProps, BarGauge } from '@grafana/ui'; +import { DisplayValue, PanelProps, BarGauge, getSingleStatDisplayValues } from '@grafana/ui'; import { config } from 'app/core/config'; // Types import { BarGaugeOptions } from './types'; -import { getSingleStatValues } from '../singlestat2/SingleStatPanel'; import { ProcessedValuesRepeater } from '../singlestat2/ProcessedValuesRepeater'; export class BarGaugePanel extends PureComponent> { @@ -28,7 +27,14 @@ export class BarGaugePanel extends PureComponent> { }; getProcessedValues = (): DisplayValue[] => { - return getSingleStatValues(this.props); + return getSingleStatDisplayValues({ + valueMappings: this.props.options.valueMappings, + thresholds: this.props.options.thresholds, + valueOptions: this.props.options.valueOptions, + data: this.props.data, + theme: config.theme, + replaceVariables: this.props.replaceVariables, + }); }; render() { diff --git a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx index 3404c8d8805..2984fb61544 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx @@ -2,13 +2,19 @@ import React, { PureComponent } from 'react'; // Components -import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui'; +import { + ThresholdsEditor, + ValueMappingsEditor, + PanelOptionsGrid, + PanelOptionsGroup, + FormField, + SingleStatValueOptions, + SingleStatValueEditor, +} from '@grafana/ui'; // Types import { FormLabel, PanelEditorProps, Threshold, Select, ValueMapping } from '@grafana/ui'; import { BarGaugeOptions, orientationOptions, displayModes } from './types'; -import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor'; -import { SingleStatValueOptions } from '../singlestat2/types'; export class BarGaugePanelEditor extends PureComponent> { onThresholdsChanged = (thresholds: Threshold[]) => diff --git a/public/app/plugins/panel/bargauge/module.tsx b/public/app/plugins/panel/bargauge/module.tsx index e84c64f3710..0d0312087e8 100644 --- a/public/app/plugins/panel/bargauge/module.tsx +++ b/public/app/plugins/panel/bargauge/module.tsx @@ -1,11 +1,10 @@ -import { ReactPanelPlugin } from '@grafana/ui'; +import { ReactPanelPlugin, sharedSingleStatOptionsCheck } from '@grafana/ui'; import { BarGaugePanel } from './BarGaugePanel'; import { BarGaugePanelEditor } from './BarGaugePanelEditor'; import { BarGaugeOptions, defaults } from './types'; -import { singleStatBaseOptionsCheck } from '../singlestat2/module'; export const reactPanel = new ReactPanelPlugin(BarGaugePanel) .setDefaults(defaults) .setEditor(BarGaugePanelEditor) - .setPanelChangeHandler(singleStatBaseOptionsCheck); + .setPanelChangeHandler(sharedSingleStatOptionsCheck); diff --git a/public/app/plugins/panel/bargauge/types.ts b/public/app/plugins/panel/bargauge/types.ts index 2cfd6bdd653..37e1edf0e10 100644 --- a/public/app/plugins/panel/bargauge/types.ts +++ b/public/app/plugins/panel/bargauge/types.ts @@ -1,5 +1,4 @@ -import { VizOrientation, SelectOptionItem, StatID } from '@grafana/ui'; -import { SingleStatBaseOptions } from '../singlestat2/types'; +import { VizOrientation, SelectOptionItem, StatID, SingleStatBaseOptions } from '@grafana/ui'; export interface BarGaugeOptions extends SingleStatBaseOptions { minValue: number; diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index a0fdf9ed405..998fcdebcb8 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -9,8 +9,7 @@ import { Gauge } from '@grafana/ui'; // Types import { GaugeOptions } from './types'; -import { DisplayValue, PanelProps } from '@grafana/ui'; -import { getSingleStatValues } from '../singlestat2/SingleStatPanel'; +import { DisplayValue, PanelProps, getSingleStatDisplayValues } from '@grafana/ui'; import { ProcessedValuesRepeater } from '../singlestat2/ProcessedValuesRepeater'; export class GaugePanel extends PureComponent> { @@ -33,7 +32,14 @@ export class GaugePanel extends PureComponent> { }; getProcessedValues = (): DisplayValue[] => { - return getSingleStatValues(this.props); + return getSingleStatDisplayValues({ + valueMappings: this.props.options.valueMappings, + thresholds: this.props.options.thresholds, + valueOptions: this.props.options.valueOptions, + data: this.props.data, + theme: config.theme, + replaceVariables: this.props.replaceVariables, + }); }; render() { diff --git a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx index e3b21302331..1229a881748 100644 --- a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx +++ b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx @@ -7,12 +7,12 @@ import { PanelOptionsGrid, ValueMappingsEditor, ValueMapping, + SingleStatValueOptions, + SingleStatValueEditor, } from '@grafana/ui'; import { GaugeOptionsBox } from './GaugeOptionsBox'; import { GaugeOptions } from './types'; -import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor'; -import { SingleStatValueOptions } from '../singlestat2/types'; export class GaugePanelEditor extends PureComponent> { onThresholdsChanged = (thresholds: Threshold[]) => diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index d811d29029a..7cf7841d225 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -1,12 +1,10 @@ -import { ReactPanelPlugin } from '@grafana/ui'; - +import { ReactPanelPlugin, sharedSingleStatMigrationCheck, sharedSingleStatOptionsCheck } from '@grafana/ui'; import { GaugePanelEditor } from './GaugePanelEditor'; import { GaugePanel } from './GaugePanel'; import { GaugeOptions, defaults } from './types'; -import { singleStatBaseOptionsCheck, singleStatMigrationCheck } from '../singlestat2/module'; export const reactPanel = new ReactPanelPlugin(GaugePanel) .setDefaults(defaults) .setEditor(GaugePanelEditor) - .setPanelChangeHandler(singleStatBaseOptionsCheck) - .setMigrationHandler(singleStatMigrationCheck); + .setPanelChangeHandler(sharedSingleStatOptionsCheck) + .setMigrationHandler(sharedSingleStatMigrationCheck); diff --git a/public/app/plugins/panel/gauge/types.ts b/public/app/plugins/panel/gauge/types.ts index a409e3524c0..0ebd162f862 100644 --- a/public/app/plugins/panel/gauge/types.ts +++ b/public/app/plugins/panel/gauge/types.ts @@ -1,5 +1,4 @@ -import { SingleStatBaseOptions } from '../singlestat2/types'; -import { VizOrientation, StatID } from '@grafana/ui'; +import { VizOrientation, StatID, SingleStatBaseOptions } from '@grafana/ui'; export interface GaugeOptions extends SingleStatBaseOptions { maxValue: number; diff --git a/public/app/plugins/panel/piechart/PieChartPanel.tsx b/public/app/plugins/panel/piechart/PieChartPanel.tsx index 9c6d7d1a072..fae799abc58 100644 --- a/public/app/plugins/panel/piechart/PieChartPanel.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanel.tsx @@ -5,20 +5,26 @@ import React, { PureComponent } from 'react'; import { config } from 'app/core/config'; // Components -import { PieChart } from '@grafana/ui'; +import { PieChart, getSingleStatDisplayValues } from '@grafana/ui'; // Types import { PieChartOptions } from './types'; import { PanelProps } from '@grafana/ui/src/types'; -import { getSingleStatValues } from '../singlestat2/SingleStatPanel'; interface Props extends PanelProps {} export class PieChartPanel extends PureComponent { render() { - const { width, height, options } = this.props; + const { width, height, options, data, replaceVariables } = this.props; - const values = getSingleStatValues(this.props); + const values = getSingleStatDisplayValues({ + valueMappings: options.valueMappings, + thresholds: options.thresholds, + valueOptions: options.valueOptions, + data: data, + theme: config.theme, + replaceVariables: replaceVariables, + }); return ( > { onValueMappingsChanged = (valueMappings: ValueMapping[]) => diff --git a/public/app/plugins/panel/piechart/types.ts b/public/app/plugins/panel/piechart/types.ts index b402eafe6a1..36beadbb45e 100644 --- a/public/app/plugins/panel/piechart/types.ts +++ b/public/app/plugins/panel/piechart/types.ts @@ -1,5 +1,4 @@ -import { PieChartType, StatID, VizOrientation } from '@grafana/ui'; -import { SingleStatBaseOptions } from '../singlestat2/types'; +import { PieChartType, StatID, VizOrientation, SingleStatBaseOptions } from '@grafana/ui'; export interface PieChartOptions extends SingleStatBaseOptions { pieType: PieChartType; diff --git a/public/app/plugins/panel/singlestat2/ColoringEditor.tsx b/public/app/plugins/panel/singlestat2/ColoringEditor.tsx new file mode 100644 index 00000000000..a71379af3a8 --- /dev/null +++ b/public/app/plugins/panel/singlestat2/ColoringEditor.tsx @@ -0,0 +1,68 @@ +// Libraries +import React, { PureComponent } from 'react'; + +// Components +import { Switch, PanelOptionsGroup } from '@grafana/ui'; + +// Types +import { SingleStatOptions } from './types'; + +const labelWidth = 6; + +export interface Props { + options: SingleStatOptions; + onChange: (options: SingleStatOptions) => void; +} + +// colorBackground?: boolean; +// colorValue?: boolean; +// colorPrefix?: boolean; +// colorPostfix?: boolean; + +export class ColoringEditor extends PureComponent { + onToggleColorBackground = () => + this.props.onChange({ ...this.props.options, colorBackground: !this.props.options.colorBackground }); + + onToggleColorValue = () => this.props.onChange({ ...this.props.options, colorValue: !this.props.options.colorValue }); + + onToggleColorPrefix = () => + this.props.onChange({ ...this.props.options, colorPrefix: !this.props.options.colorPrefix }); + + onToggleColorPostfix = () => + this.props.onChange({ ...this.props.options, colorPostfix: !this.props.options.colorPostfix }); + + render() { + const { colorBackground, colorValue, colorPrefix, colorPostfix } = this.props.options; + + return ( + + + + + + + + + ); + } +} diff --git a/public/app/plugins/panel/singlestat2/FontSizeEditor.tsx b/public/app/plugins/panel/singlestat2/FontSizeEditor.tsx new file mode 100644 index 00000000000..9b582843c2a --- /dev/null +++ b/public/app/plugins/panel/singlestat2/FontSizeEditor.tsx @@ -0,0 +1,67 @@ +// Libraries +import React, { PureComponent } from 'react'; + +// Components +import { FormLabel, Select, PanelOptionsGroup, SelectOptionItem } from '@grafana/ui'; + +// Types +import { SingleStatOptions } from './types'; + +const labelWidth = 6; + +export interface Props { + options: SingleStatOptions; + onChange: (options: SingleStatOptions) => void; +} + +const percents = ['20%', '30%', '50%', '70%', '80%', '100%', '110%', '120%', '150%', '170%', '200%']; +const fontSizeOptions = percents.map(v => { + return { value: v, label: v }; +}); + +export class FontSizeEditor extends PureComponent { + setPrefixFontSize = (v: SelectOptionItem) => this.props.onChange({ ...this.props.options, prefixFontSize: v.value }); + + setValueFontSize = (v: SelectOptionItem) => this.props.onChange({ ...this.props.options, valueFontSize: v.value }); + + setPostfixFontSize = (v: SelectOptionItem) => + this.props.onChange({ ...this.props.options, postfixFontSize: v.value }); + + render() { + const { prefixFontSize, valueFontSize, postfixFontSize } = this.props.options; + + return ( + +
+ Prefix + option.value === valueFontSize)} + /> +
+ +
+ Postfix +