diff --git a/packages/grafana-data/src/field/fieldDisplay.test.ts b/packages/grafana-data/src/field/fieldDisplay.test.ts index 1ed4e521152..9e223f847a6 100644 --- a/packages/grafana-data/src/field/fieldDisplay.test.ts +++ b/packages/grafana-data/src/field/fieldDisplay.test.ts @@ -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 = {} replaceVariables: (value: string) => { return value; }, - fieldOptions: { + reduceOptions: { calcs: [], }, fieldConfig: { diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index 9f91d3d3942..e98f0278b74 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -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++) { diff --git a/packages/grafana-ui/.storybook/storybookTheme.ts b/packages/grafana-ui/.storybook/storybookTheme.ts index 9e8f646cddd..8bfb24f19e5 100644 --- a/packages/grafana-ui/.storybook/storybookTheme.ts +++ b/packages/grafana-ui/.storybook/storybookTheme.ts @@ -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, diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx index 7172967f230..3fd8be33805 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx @@ -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 = ({ onChange, id, name = undefined, + fullWidth, }) => { const theme = useTheme(); - const styles = getRadioButtonStyles(theme, size); + const styles = getRadioButtonStyles(theme, size, fullWidth); return ( <> diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx index fbd6d7cdce8..f0acbc50a56 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx @@ -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('Size', sizes, 'md', VISUAL_GROUP); + + const options = [ + { label: 'Prometheus', value: 'prometheus' }, + { label: 'Graphite', value: 'graphite' }, + { label: 'Elastic', value: 'elastic' }, + ]; + + return ( +
+ setSelected(v!)} + size={size} + fullWidth + /> +
+ ); +}; diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx index 2ef95832a0e..b3c267971ed 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx @@ -39,6 +39,7 @@ interface RadioButtonGroupProps { options: Array>; onChange?: (value?: T) => void; size?: RadioButtonSize; + fullWidth?: boolean; } export function RadioButtonGroup({ @@ -48,6 +49,7 @@ export function RadioButtonGroup({ disabled, disabledOptions, size = 'md', + fullWidth, }: RadioButtonGroupProps) { const handleOnChange = useCallback( (option: SelectableValue) => { @@ -75,6 +77,7 @@ export function RadioButtonGroup({ onChange={handleOnChange(o)} id={`option-${o.value}`} name={groupName.current} + fullWidth > {o.label} diff --git a/packages/grafana-ui/src/components/OptionsUI/stats.tsx b/packages/grafana-ui/src/components/OptionsUI/stats.tsx new file mode 100644 index 00000000000..569e054afc0 --- /dev/null +++ b/packages/grafana-ui/src/components/OptionsUI/stats.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { FieldConfigEditorProps, ReducerID } from '@grafana/data'; +import { StatsPicker } from '../StatsPicker/StatsPicker'; + +export const StatsPickerEditor: React.FC> = ({ value, onChange }) => { + return ; +}; diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx index 6150b34bb9d..52e11827f1f 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx @@ -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> = [ export interface Props { labelWidth?: number; - value: FieldDisplayOptions; - onChange: (value: FieldDisplayOptions, event?: React.SyntheticEvent) => void; + value: ReduceDataOptions; + onChange: (value: ReduceDataOptions, event?: React.SyntheticEvent) => void; } export class FieldDisplayEditor extends PureComponent { diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts index 19b1fadc778..c8ee2408eef 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts @@ -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> | any, @@ -131,9 +131,9 @@ export function sharedSingleStatMigrationHandler(panel: PanelModel void; stats: string[]; - width?: number; allowMultiple?: boolean; defaultStat?: string; } @@ -63,12 +62,11 @@ export class StatsPicker extends PureComponent { }; render() { - const { width, stats, allowMultiple, defaultStat, placeholder } = this.props; + const { stats, allowMultiple, defaultStat, placeholder } = this.props; const select = fieldReducers.selectOptions(stats); return ( - <> - {!useNewEditor && ( - <> - - - - - - )} - - - {!useNewEditor && ( - <> - - - - - - )} + + + + + + + + + + + ); }} diff --git a/public/app/plugins/panel/stat/module.tsx b/public/app/plugins/panel/stat/module.tsx index 3e2510b06b6..a30bfd406f2 100644 --- a/public/app/plugins/panel/stat/module.tsx +++ b/public/app/plugins/panel/stat/module.tsx @@ -1,6 +1,6 @@ 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(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); diff --git a/public/app/plugins/panel/stat/types.ts b/public/app/plugins/panel/stat/types.ts index d65447bf931..3d990d74c51 100644 --- a/public/app/plugins/panel/stat/types.ts +++ b/public/app/plugins/panel/stat/types.ts @@ -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> = [ { 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, };